/* GENERATED by `node scripts/i18n.mjs build` from fonts.css, chrome.css, lang.css, paper.css, home.css. Edit those; this file is overwritten. */

/* ---- fonts.css ---- */
/* Self-hosted copies of the three faces the paper system uses (Newsreader,
   Geist, Geist Mono), subset per script exactly as Google Fonts serves them.
   Served from our own CloudFront instead of fonts.googleapis.com: one fewer
   third-party connection, no render-blocking cross-origin stylesheet, and no
   font request leaving the EU for a GDPR-sensitive audience (SEO audit,
   2026-09-13). Regenerate by re-fetching the Google Fonts CSS with a modern
   browser UA and mapping each url() to assets/fonts/. */
@font-face{font-family:'Geist';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-400-cyrillic-ext.woff2) format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'Geist';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-400-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
@font-face{font-family:'Geist';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-400-vietnamese.woff2) format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Geist';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Geist';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Geist';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-400-cyrillic-ext.woff2) format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'Geist';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-400-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
@font-face{font-family:'Geist';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-400-vietnamese.woff2) format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Geist';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Geist';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Geist';font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/geist-400-cyrillic-ext.woff2) format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'Geist';font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/geist-400-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
@font-face{font-family:'Geist';font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/geist-400-vietnamese.woff2) format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Geist';font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/geist-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Geist';font-style:normal;font-weight:600;font-display:swap;src:url(/assets/fonts/geist-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-mono-400-cyrillic-ext.woff2) format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-mono-400-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-mono-400-symbols2.woff2) format('woff2');unicode-range:U+2000-2001, U+2004-2008, U+200A, U+23B8-23BD, U+2500-259F}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-mono-400-vietnamese.woff2) format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-mono-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/geist-mono-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-mono-400-cyrillic-ext.woff2) format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-mono-400-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-mono-400-symbols2.woff2) format('woff2');unicode-range:U+2000-2001, U+2004-2008, U+200A, U+23B8-23BD, U+2500-259F}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-mono-400-vietnamese.woff2) format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-mono-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/geist-mono-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Newsreader';font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/newsreader-300-vietnamese.woff2) format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Newsreader';font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/newsreader-300-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Newsreader';font-style:normal;font-weight:300;font-display:swap;src:url(/assets/fonts/newsreader-300-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Newsreader';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/newsreader-300-vietnamese.woff2) format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Newsreader';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/newsreader-300-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Newsreader';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/newsreader-300-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}

/* ---- chrome.css ---- */
/* Site chrome — the one header and the one footer.
 *
 * These rules used to be retyped inside every page's inline <style>, in two
 * incompatible versions: a 80px grid bar on the fourteen pages that carry their
 * own stylesheet, and a 64px flex bar in styles.css on the six that don't. Same
 * class names, different heights, layouts, CTA shapes and breakpoints — which
 * is why shared/nav-mobile.js has to MEASURE whether the links are visible
 * instead of just asking a media query. This file is the single version; the
 * markup that uses it is generated by scripts/i18n.mjs into the
 * <!-- i18n:header --> and <!-- i18n:footer --> blocks. Do not hand-edit either.
 *
 * Every page links this, including index.html, which carries a full inline
 * stylesheet and never loads styles.css — the same reason shared/lang.css is
 * its own file.
 *
 * Nothing here may depend on a page-local custom property, because the pages
 * define different ones. --accent is the single exception and it is always
 * given a literal fallback: index.html and styles.css both declare it, but a
 * new page might not, and a missing accent would render the CTA invisible.
 */

/* ---------------------------------------------------------------------------
 * Header
 *
 * position:fixed is what fourteen pages already do, and their heroes reserve
 * the space with their own top padding. A page without a hero (the legal pages,
 * 404) reserves it in its own layout rule — see .legal in styles.css.
 * ------------------------------------------------------------------------- */

.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  background: rgba(13, 13, 15, .9);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid rgba(255, 255, 255, .1);
}

/* The homepage nav sits over the top of a full-bleed hero video, so it starts
 * almost invisible and fades to solid once you scroll past the fold. Only the
 * homepage opts in, via variant="overlay" on the generated block; the class is
 * what shared/nav-mobile.js looks for before it binds the scroll listener. */
.nav--overlay {
  background: rgba(13, 13, 15, .12);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
  transition: background .35s ease, backdrop-filter .35s ease, border-color .35s ease;
}
.nav--overlay.scrolled {
  background: rgba(13, 13, 15, .9);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-bottom-color: rgba(255, 255, 255, .1);
}

/* The homepage is a light document, so its header is paper rather than ink: the
 * same overlay behaviour (near-transparent over the fold, solid once you scroll
 * past it) painted the other way round. It opts in with variant="light" on the
 * generated block, and shared/nav-mobile.js binds .scrolled for both variants.
 * Every colour below is a literal for the same reason the rest of this file is:
 * a page must not have to declare a token to get a working header. */
.nav--light {
  /* A gradient rather than a flat translucent fill: the homepage photograph
   * runs up behind the bar, and a flat fill drew a hard horizontal edge across
   * it at 80px. This keeps the links legible over the dark hair and dissolves
   * into the photograph instead of ending on it. */
  background: linear-gradient(180deg, rgba(243, 242, 238, .96) 0%, rgba(243, 242, 238, .82) 62%, rgba(243, 242, 238, 0) 100%);
  border-bottom-color: transparent;
  transition: background .35s ease, border-color .35s ease;
}
.nav--light.scrolled {
  background: rgba(243, 242, 238, .94);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border-bottom-color: #dcd9d2;
}
.nav--light .nav-logo,
.nav--light .nav-logo svg { color: #111113; }
.nav--light .nav-links a.navlink {
  color: #4a4a51;
  position: relative; padding-bottom: 3px;
}
.nav--light .nav-links a.navlink:hover,
.nav--light .nav-links a.navlink[aria-current="page"] { color: #111113; }
/* The rule grows from the left rather than appearing: on a page this quiet the
 * hover has to be visible without being loud. */
.nav--light .nav-links a.navlink::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 0;
  background: currentColor; transition: width .28s cubic-bezier(.2, .7, .2, 1);
}
.nav--light .nav-links a.navlink:hover::after,
.nav--light .nav-links a.navlink[aria-current="page"]::after { width: 100%; }
@media (prefers-reduced-motion: reduce) {
  .nav--light .nav-links a.navlink::after { transition: none; }
}
.nav--light .nav-login { color: #111113; }
/* The light bar takes the DARKER accent: the brand orange is 2.7:1 on paper and
 * this is text, not a hairline. --accent-ink is home.css's; the literal is here
 * so the rule still works on a page that has not declared it. */
.nav--light .nav-login:hover { color: var(--accent-ink, #B83E08); }
/* Ink pill, not accent: the homepage's one accent is a hairline, never a surface. */
.nav--light .nav-cta {
  background: #111113; color: #fff !important;
  border-radius: 6px; padding: 12px 22px;
  font-size: 13px; font-weight: 600; text-transform: none; letter-spacing: -.005em;
}
.nav--light .nav-cta:hover { filter: none; background: #2d2d33; }
/* The burger is injected by shared/nav-mobile.js with a hardcoded white; the
 * drawer it opens is a dark full-width overlay and stays that way. */
.nav--light .mp-burger { color: #111113; }
/* The selector is generated with the dark variant on every page. Repaint it
 * here rather than teach the generator a second one. The border is 3.2:1 on
 * paper, not the page's decorative hairline: it is the edge of a control. */
.nav--light .lang-switch--dark > summary {
  color: #4a4a51; border-color: #888279; background: rgba(255, 255, 255, .5);
}
.nav--light .lang-switch--dark > summary:hover { color: #111113; border-color: #111113; }

.nav .wrap {
  max-width: 1240px; margin: 0 auto; padding: 0 24px;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; height: 80px;
}

.nav-logo {
  justify-self: start; display: flex; align-items: center; gap: 12px;
  text-decoration: none; font-weight: 800; letter-spacing: -.01em; color: #fff;
}
.nav-logo svg { height: 28px; width: auto; color: #fff; }
.nav-logo span { font-size: 22px; }

.nav-links {
  justify-self: center; display: flex; align-items: center; gap: 32px;
  font-size: 13px; font-weight: 600;
}
/* font-size/weight are repeated here on purpose. styles.css still ships on the
 * legal pages and its `.nav-links a` beats the 13px/600 inherited from
 * `.nav-links`, closing the language pill to a 0px gap against the last link. */
.nav-links a.navlink {
  font-size: 13px; font-weight: 600;
  text-decoration: none; color: rgba(255, 255, 255, .7); transition: color .15s;
}
.nav-links a.navlink:hover,
.nav-links a.navlink[aria-current="page"] { color: #fff; }

/* Log in sits beside Start now: a returning owner and a new one want different
 * doors, and only one of them was on the page. */
.nav-actions { justify-self: end; display: flex; align-items: center; gap: 20px; }
.nav-login {
  font-size: 13px; font-weight: 700; color: rgba(255, 255, 255, .9);
  text-decoration: none; transition: color .15s; white-space: nowrap;
}
.nav-login:hover { color: var(--accent, #FF2D78); }
.nav-cta {
  background: var(--accent, #FF2D78); color: #fff !important;
  padding: 11px 22px; border-radius: 4px;
  font-size: 13px; font-weight: 800; text-decoration: none;
  text-transform: uppercase; letter-spacing: .03em; white-space: nowrap;
}
.nav-cta:hover { filter: brightness(1.08); }

/* One breakpoint, not two. 820px is where the eight links stop fitting beside
 * the logo and the actions; below it nav-mobile.js clones them into a drawer. */
@media (max-width: 820px) {
  .nav-links { display: none; }
  .nav .wrap { grid-template-columns: 1fr auto; }
}
@media (max-width: 420px) { .nav-actions { gap: 13px; } }

/* One "Start free" at a time on a phone.
 *
 * The hero carries the primary CTA and the bar carries a second one 60px above
 * it: on a 390px screen that is the same words twice in one glance. The bar's
 * copy is held back until you have scrolled past the hero, at which point it is
 * the only one on screen and earns its place. Desktop shows both, where they
 * are far enough apart to read as a persistent bar and a page CTA.
 * `.scrolled` is set by shared/nav-mobile.js at 36px. */
@media (max-width: 820px) {
  .nav--light .nav-cta {
    opacity: 0; transform: scale(.94); pointer-events: none;
    transition: opacity .3s ease, transform .3s cubic-bezier(.2, .7, .2, 1);
  }
  .nav--light.scrolled .nav-cta { opacity: 1; transform: none; pointer-events: auto; }
  @media (prefers-reduced-motion: reduce) {
    .nav--light .nav-cta { transition: none; }
  }
}

/* An in-page jump must not land under the fixed bar. */
html { scroll-padding-top: 96px; }

/* shared/lang.js inserts its "also available in Portuguese" bar as the body's
 * first child, which worked when the header was in normal flow and the bar
 * pushed it down. The header is fixed now, so in flow the bar lands underneath
 * it and is unreadable. Pin it directly below instead. Scoped to this file, so
 * the help centre — which loads lang.css but not chrome.css and has its own
 * header — keeps the in-flow bar it was built for.
 *
 * `html .lang-nudge` rather than `.lang-nudge`: lang.css is linked AFTER this
 * file on every page, so at equal specificity its `position: relative` would
 * win and the bar would only appear to move (a relative offset still reserves
 * its space at the top of the flow).
 *
 * z-index 55 puts it under the mobile drawer (59) and its scrim (58), which
 * open at the same 80px. lang.css's 80 sat on top of both, so on a phone the
 * nudge bar painted through an open menu. */
html .lang-nudge { position: fixed; top: 80px; left: 0; right: 0; z-index: 55; }

/* The language menu opens and closes with motion.
 *
 * A <details> flips instantly by nature, so the open half is a keyframe on the
 * panel and the close half needs `::details-content` with
 * `transition-behavior: allow-discrete` (Chrome 131+) to hold the element
 * around long enough to animate out. Where that is unsupported the panel simply
 * appears on close-out, which is what it did before, so nothing regresses.
 * Scoped to the chrome's two switchers; the help centre has its own header. */
@media (prefers-reduced-motion: no-preference) {
  .nav details[open] > .lang-menu,
  .site-foot details[open] > .lang-menu {
    animation: langMenuIn .22s cubic-bezier(.2, .7, .2, 1) both;
  }
  @keyframes langMenuIn {
    from { opacity: 0; transform: translateY(-6px) scale(.985); }
    to   { opacity: 1; transform: none; }
  }
  .site-foot details[open] > .lang-menu { animation-name: langMenuInUp; }
  @keyframes langMenuInUp {
    from { opacity: 0; transform: translateY(6px) scale(.985); }
    to   { opacity: 1; transform: none; }
  }
  /* the closing half, where the browser can keep the content around for it */
  .nav details.lang-switch::details-content,
  .site-foot details.lang-switch::details-content {
    opacity: 0;
    transition: opacity .18s ease, content-visibility .18s allow-discrete;
  }
  .nav details.lang-switch[open]::details-content,
  .site-foot details.lang-switch[open]::details-content { opacity: 1; }
  /* the chevron turns with it */
  .nav .lang-switch > summary .lang-chevron,
  .site-foot .lang-switch > summary .lang-chevron { transition: transform .22s cubic-bezier(.2, .7, .2, 1); }
  .nav .lang-switch[open] > summary .lang-chevron,
  .site-foot .lang-switch[open] > summary .lang-chevron { transform: rotate(180deg); }
}

/* A runtime page (404.html) carries one copy of the chrome per language and
 * shows the reader's. Stated rather than left to the UA stylesheet, because a
 * header that fails to hide is a second fixed bar on top of the first. */
.chrome-lang[hidden] { display: none; }

/* ---------------------------------------------------------------------------
 * Footer
 *
 * Dark, because it has to sit at the bottom of a white marketing page, a white
 * legal page and a near-black article page without knowing which. The light
 * variant the homepage used to carry read its colours from that page's own
 * --bg2 / --ink / --border, none of which exist on a page served by styles.css.
 * Self-contained literals are what make one footer possible.
 * ------------------------------------------------------------------------- */

footer.site-foot {
  background: #0d0d0f; color: #fff; padding: 70px 0 34px;
  border-top: 0;
}
footer.site-foot .wrap { max-width: 1240px; margin: 0 auto; padding: 0 24px; }

/* The brand block, then one implicit 1fr column per link group, however many
   there are: a language with two groups and one with three (the English footer
   grew a Compare column, 14 Sep 2026) both lay out on one row. Not auto-fit:
   an auto-repeat beside an fr track is an invalid declaration and the whole
   footer stacked. */
.foot-top { display: grid; grid-auto-flow: column; grid-template-columns: 1.6fr; grid-auto-columns: 1fr; gap: 48px; }
.foot-brand {
  display: flex; align-items: center; gap: 10px;
  font-weight: 800; font-size: 20px; margin-bottom: 14px; color: #fff;
}
.foot-brand svg { height: 24px; width: auto; color: #fff; }
.foot-tag {
  font-size: 14px; line-height: 1.6; color: rgba(255, 255, 255, .5);
  max-width: 42ch; margin: 0 0 22px;
}
.foot-badges { display: flex; gap: 12px; flex-wrap: wrap; }
.foot-badges img { height: 40px; width: auto; display: block; }
.foot-badges a { transition: transform .15s; }
.foot-badges a:hover { transform: translateY(-2px); }

.foot-col h3 {
  font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
  /* .58, not .4: .4 measured 3.80:1 on the footer ground, under AA for 11px */
  color: rgba(255, 255, 255, .58); margin: 0 0 16px; font-weight: 800;
}
.foot-col a {
  display: block; font-size: 14px; color: rgba(255, 255, 255, .72);
  text-decoration: none; padding: 5px 0; transition: color .15s;
  /* styles.css sets margin-bottom on this same selector and is not overridden
   * by the padding above, which made footer rows 9px taller on the legal pages
   * than on the homepage. */
  margin-bottom: 0;
}
.foot-col a:hover,
.foot-col a[aria-current="page"] { color: #fff; }

.foot-bottom {
  display: flex; justify-content: space-between; align-items: center;
  gap: 16px; flex-wrap: wrap;
  margin-top: 52px; padding-top: 24px;
  border-top: 1px solid rgba(255, 255, 255, .1);
  font-size: 12.5px; color: rgba(255, 255, 255, .58);
}

@media (max-width: 820px) {
  .foot-top { grid-auto-flow: row; grid-template-columns: 1fr; gap: 36px; }
  .foot-bottom { flex-direction: column; align-items: flex-start; }
}

/* The footer's language selector.
 *
 * shared/lang.css's `--light` variant means two things at once: opens upward,
 * and painted for a light surface. The footer is dark now, so only the first
 * half still applies, and the second left a white pill sitting on #0d0d0f.
 * Repaint it here rather than in lang.css, because the help centre uses the
 * same file against a genuinely light page.
 */
footer.site-foot .lang-switch--light > summary {
  color: rgba(255, 255, 255, .72);
  border-color: rgba(255, 255, 255, .18);
  background: rgba(255, 255, 255, .06);
}
footer.site-foot .lang-switch--light > summary:hover {
  color: #fff;
  border-color: rgba(255, 255, 255, .38);
}
footer.site-foot .lang-switch--light .lang-menu {
  background: #17171a;
  border-color: rgba(255, 255, 255, .14);
  box-shadow: 0 -8px 28px rgba(0, 0, 0, .45);
}
footer.site-foot .lang-switch--light .lang-menu a,
footer.site-foot .lang-switch--light .lang-menu button { color: rgba(255, 255, 255, .72); }
footer.site-foot .lang-switch--light .lang-menu a:hover,
footer.site-foot .lang-switch--light .lang-menu button:hover,
footer.site-foot .lang-switch--light .lang-menu a[aria-current="page"],
footer.site-foot .lang-switch--light .lang-menu button[aria-current="true"] {
  background: rgba(255, 255, 255, .08);
  color: #fff;
}

/* ---- lang.css ---- */
/* Language selector — matpilot.io
   ---------------------------------------------------------------------------
   Lives in its own file rather than in styles.css because index.html carries a
   full inline stylesheet and never loads styles.css. One file, linked by every
   page that has a selector, keeps the widget from existing in two places.
   Every custom property below has a literal fallback, so this drops onto a page
   with any token set (or none).

   The selector is a <details>/<summary> disclosure. The expanded state is a
   browser primitive and each row is a plain <a href>, so it opens, closes and
   switches language with JavaScript disabled. shared/lang.js only adds
   conveniences on top. The markup is generated into each page by
   scripts/i18n.mjs from i18n.config.json.

   Built to grow: .lang-menu scrolls once the list outgrows its height, which is
   the point of a list rather than a two-way toggle. */

.lang-switch { position: relative; display: inline-block; }
.lang-switch > summary {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: 9999px; cursor: pointer;
  font-size: 12px; font-weight: 700; letter-spacing: 0.02em; line-height: 1.2;
  list-style: none; -webkit-user-select: none; user-select: none;
}
.lang-switch > summary::-webkit-details-marker { display: none; }
.lang-switch > summary::marker { content: ""; }
.lang-switch > summary:focus-visible { outline: 2px solid var(--accent, #ff2d78); outline-offset: 2px; }
/* The flag is sized in em by the .fi rule below, so this font-size IS its size:
   12px of font gives a 16x12 box. It was 14px while the flag was an emoji glyph,
   which needed the extra to match the label optically; a solid rectangle does
   not, and at 14px it overpowered the 12px code beside it. */
.lang-switch .lang-flag { font-size: 12px; line-height: 1; }
.lang-switch .lang-chevron {
  width: 0; height: 0; margin-left: 1px;
  border-left: 3.5px solid transparent; border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor; opacity: 0.75;
  transition: transform 0.15s ease;
}
.lang-switch[open] > summary .lang-chevron { transform: rotate(180deg); }

/* Flags — a trimmed subset of flag-icons 7.5.0 (MIT), NOT the library.
   ---------------------------------------------------------------------------
   Why not the emoji it replaced: Windows Chrome ships no country-flag font, so
   🇬🇧 rendered there as the bare letters "GB". SVG images render everywhere.

   Why not the full flag-icons.min.css: it is ~250 background-image rules, one
   per country, and web/ has no build step to tree-shake — `aws s3 sync web/`
   ships whatever is committed. Two languages do not need 250 rules or the
   ~10MB of SVGs behind them. So the base `.fi` rule and one `.fi-<cc>` rule per
   shipped country are copied here by hand, and only those countries' SVGs are
   vendored into shared/flags/. The class NAMES are flag-icons' own, so the
   generated markup stays portable: if a third language ever makes the real
   package worth carrying, swap this block for it and the HTML is unchanged.

   ADDING A LANGUAGE — three steps, none of them automatic:
     1. copy node_modules/flag-icons/flags/4x3/<cc>.svg to shared/flags/<cc>.svg
     2. add `.fi-<cc> { background-image: url("flags/<cc>.svg"); }` below
     3. set `country: "<cc>"` on the language in i18n.config.json, then
        `node scripts/i18n.mjs build`
   `node scripts/i18n.mjs check` fails if 1 or 2 is missed.

   The url()s are relative to THIS FILE, not to the page that links it, so the
   single copy at /shared/lang.css resolves to /shared/flags/<cc>.svg for a root
   page, a /pt/ page and a /help/ page alike.

   Unscoped, and at flag-icons' own specificity, so that swap stays a clean
   replacement rather than a fight neither rule wins. */
.fi {
  background-size: contain; background-position: 50%; background-repeat: no-repeat;
  position: relative; display: inline-block; width: 1.333333em; line-height: 1em;
}
.fi::before { content: "\00a0"; } /* the span is empty; this gives it height */
.fi-ie { background-image: url("flags/ie.svg"); }
.fi-gb { background-image: url("flags/gb.svg"); }
.fi-br { background-image: url("flags/br.svg"); }
.fi-us { background-image: url("flags/us.svg"); }
/* Spanish takes two flags, not one: shared/lang.js swaps a reader in the Americas
   onto the Mexican one, so both are shipped even though only `es` is named in
   i18n.config.json. Same reason `gb` sits above `ie` for English. */
.fi-es { background-image: url("flags/es.svg"); }
.fi-mx { background-image: url("flags/mx.svg"); }
/* Local touch, not from the library: the flags sit against a dark header and a
   white footer, and both tricolour and union flags reach the edge of their box
   in each. */
.lang-switch .lang-flag.fi { border-radius: 1px; }

.lang-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 70;
  min-width: 128px; max-height: 264px;
  overflow-y: auto; overscroll-behavior: contain;
  margin: 0; padding: 6px; list-style: none;
  border-radius: 12px; box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
}
/* On a phone the menu is IN FLOW, not floating.
   Anchoring it to the left edge stopped it growing off the side, and it was
   still cut off: on mobile the header's actions are cloned into .mp-drawer,
   which scrolls (`overflow-y: auto`), and a scroll container clips an
   absolutely positioned child at its own edge no matter where that child is
   anchored. The footer's switcher has the same shape. So below 820px the menu
   stops floating and simply takes its place in the column, pushing whatever is
   under it down and scrolling with the drawer. Nothing can crop what is not
   positioned out of the flow (reported twice on an iPhone, 10 Sep 2026). */
@media (max-width: 820px) {
  .lang-switch { display: block; }
  .lang-switch > summary { display: flex; width: max-content; }
  .lang-menu {
    position: static; left: auto; right: auto; top: auto; bottom: auto;
    width: 100%; min-width: 0; max-width: none; max-height: none;
    margin-top: 8px; box-shadow: none;
  }
  .lang-switch--light .lang-menu { top: auto; bottom: auto; }
}
.lang-menu li { margin: 0; }
/* A row is an <a> when the selector links to a sibling page, and a <button>
   when the page switches language in place (scripts/i18n.mjs mode="runtime",
   used by /help/). They are the same control to a reader, so every rule below
   names both rather than letting one mode quietly go unstyled. */
.lang-menu a,
.lang-menu button {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-radius: 8px; text-decoration: none;
  font-size: 12px; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap;
  width: 100%; border: 0; background: none; font-family: inherit;
  cursor: pointer; text-align: left;
}
.lang-menu .lang-check { margin-left: auto; padding-left: 14px; min-width: 10px; }
/* A ROW IS A COUNTRY, WITH ITS LANGUAGE UNDER IT: "Brasil / Português",
   "United States / English". The pages differ by country (prices, terms), so
   the picker is a country picker and says so (Vinny, 11 Sep 2026). The code
   stays in the closed pill, now the country's own (BR, IE, ES, US), where a
   flag and two letters are all that fit; in a row it is for screen readers. */
.lang-menu .lang-code {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.lang-menu a, .lang-menu button { padding-top: 7px; padding-bottom: 7px; }
.lang-menu .lang-place { display: flex; flex-direction: column; gap: 1px; line-height: 1.25; }
.lang-menu .lang-country { font-size: 13px; font-weight: 600; letter-spacing: 0; }
/* .85 of the row's own ink, not a fixed grey: on the dark menu that is 6:1,
   where a fixed muted grey measured under 4.5:1 behind the country above it */
.lang-menu .lang-name { font-size: 11.5px; font-weight: 400; letter-spacing: 0; opacity: .85; }
/* And the list says what it is a list OF. */
.lang-menu-head {
  white-space: nowrap;
  padding: 5px 10px 6px; font-size: 10px; font-weight: 800;
  letter-spacing: 0.09em; text-transform: uppercase; opacity: 0.6;
}

/* Dark variant — the black site header. */
.lang-switch--dark > summary { color: rgba(255, 255, 255, 0.7); border: 1px solid rgba(255, 255, 255, 0.2); }
.lang-switch--dark > summary:hover { color: #ffffff; border-color: rgba(255, 255, 255, 0.34); }
.lang-switch--dark .lang-menu { background: #141416; border: 1px solid rgba(255, 255, 255, 0.12); }
.lang-switch--dark .lang-menu-head { color: rgba(255, 255, 255, 0.5); }
.lang-switch--dark .lang-menu a,
.lang-switch--dark .lang-menu button { color: rgba(255, 255, 255, 0.7); }
.lang-switch--dark .lang-menu a:hover,
.lang-switch--dark .lang-menu button:hover { background: rgba(255, 255, 255, 0.08); color: #ffffff; }
/* A linked row carries aria-current="page"; a runtime row is repainted by
   the page script, which uses "true". Both mean "the language you are in". */
.lang-switch--dark .lang-menu a[aria-current="page"],
.lang-switch--dark .lang-menu button[aria-current="true"] { background: rgba(255, 255, 255, 0.1); color: #ffffff; }

/* Light variant — the footer. Opens upward: it sits at the bottom of the page. */
.lang-switch--light > summary {
  color: var(--text-secondary, #56565c); border: 1px solid var(--border, #e7e7ea);
  background: var(--bg, #ffffff);
}
.lang-switch--light > summary:hover { color: var(--ink, #0a0a0a); border-color: var(--text-muted, #8e8e95); }
.lang-switch--light .lang-menu {
  top: auto; bottom: calc(100% + 8px);
  background: var(--bg, #ffffff); border: 1px solid var(--border, #e7e7ea);
  box-shadow: 0 -8px 28px rgba(0, 0, 0, 0.12);
}
.lang-switch--light .lang-menu-head { color: var(--text-muted, #8e8e95); }
.lang-switch--light .lang-menu a,
.lang-switch--light .lang-menu button { color: var(--text-secondary, #56565c); }
.lang-switch--light .lang-menu a:hover,
.lang-switch--light .lang-menu button:hover { background: var(--surface, #f6f6f7); color: var(--ink, #0a0a0a); }
/* A linked row carries aria-current="page"; a runtime row is repainted by
   the page script, which uses "true". Both mean "the language you are in". */
.lang-switch--light .lang-menu a[aria-current="page"],
.lang-switch--light .lang-menu button[aria-current="true"] { background: var(--surface, #f6f6f7); color: var(--ink, #0a0a0a); }

/* Suggestion bar injected by shared/lang.js when the reader's language and the
   page's language disagree. It is a link, never a redirect, so there is no loop
   to get stuck in and nothing about it is required for switching to work. */
.lang-nudge {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  flex-wrap: wrap; padding: 10px 16px; position: relative; z-index: 80;
  background: var(--surface, #f6f6f7); border-bottom: 1px solid var(--border, #e7e7ea);
  font-family: inherit; font-size: 13px; color: var(--text-secondary, #56565c);
}
.lang-nudge a { font-weight: 600; color: var(--ink, #0a0a0a); text-decoration: underline; }
.lang-nudge button {
  border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: 16px; color: #64646c; padding: 2px 6px; line-height: 1;
}
.lang-nudge button:hover { color: var(--ink, #0a0a0a); }

/* ---- paper.css ---- */
/* The paper design system: editorial, monochrome, warm off-white.
 *
 * Every page on the new design loads this first and its own sheet after it:
 * the homepage adds shared/home.css, the product pages shared/page.css, the
 * legal pages shared/legal.css. It replaces shared/product.css and /styles.css
 * page by page; the two never load together, so nothing here has to
 * out-specify anything there.
 *
 * Palette is the brand's own (lib/theme/index.ts): black ink on warm paper,
 * with #FF6115 used only as a hairline or a dot, never as a surface. Contrast
 * against --paper is held above 4.5:1 for every ink token below.
 */
:root{
  --paper:#f3f2ee;      /* warm off-white; matches the hero photograph's ground */
  --paper-2:#eceae5;
  --white:#ffffff;
  /* Ratios are the WORST case, against --paper-2, which is the ground under the
     platform and pricing sections. Held to WCAG 2.2 AA: 4.5:1 for text, 3:1 for
     a border that carries meaning, which is why an interactive edge takes
     --rule-3 and a decorative hairline takes --rule. */
  --ink:#111113;        /* 15.7:1 */
  --ink-2:#4a4a51;      /*  7.3:1 */
  --ink-3:#64646c;      /*  4.9:1 — the lightest ink allowed on text */
  --rule:#dcd9d2;       /* hairline between blocks, decorative */
  --rule-2:#c9c5bc;     /* a heavier hairline, still decorative */
  --rule-3:#888279;     /*  3.2:1 — the edge of a button or a ringed arrow */
  --dark:#0b0b0c;
  --dark-2:#151517;
  /* Two oranges, because one cannot do both jobs. #FF6115 is the brand mark and
     it is 6.5:1 on the ink band but only 2.69:1 on paper, so anything that has
     to be READ or that carries a state on paper (a hover underline, a link
     colour) takes --accent-ink at 4.7:1 instead. */
  --accent:#FF6115;
  --accent-ink:#B83E08;
  --accent-rgb:255,97,21;
  --maxw:1280px;
  /* Three faces, one job each. Newsreader at 300 is the whole editorial voice:
     a thin serif only reads as premium when it is also LARGE, so the display
     sizes below are floors as much as ceilings. Geist carries body and UI at
     400/500, Geist Mono every uppercase micro-label. All three are on Google
     Fonts and all three load in the app through expo-font, so the site and the
     product can end up on one type system. */
  --serif:"Newsreader",ui-serif,Georgia,"Times New Roman",serif;
  --sans:"Geist","Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0;background:var(--paper);color:var(--ink);overflow-x:hidden;
  font-family:var(--sans);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  font-size:16px;line-height:1.55;
}
a{color:inherit}
img{max-width:100%;height:auto;display:block}
button{font-family:inherit}
p{margin:0}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 40px}
@media (max-width:760px){ .wrap{padding:0 22px} }

/* ---------- type ----------
 * The serif carries every display line and nothing else, at 300. A light weight
 * needs room: 80px for the h1, 40px for an h2, and negative tracking so the
 * thin stems still read as one word rather than a row of letters. */
h1,h2,h3,h4{margin:0;font-weight:300}
.display{font-family:var(--serif);font-weight:300;line-height:1.02;letter-spacing:-.022em}
/* The h1 alone sits at 400. A light weight thins out as the size grows, and at
   80px the 300 read as a hairline beside the photograph; the h2 down at 40px
   does not have that problem and stays at 300. */
h1.display{font-size:clamp(44px,5.8vw,80px);font-weight:400;letter-spacing:-.026em}
h2.display{font-size:clamp(30px,3.4vw,40px);line-height:1.06;letter-spacing:-.018em;text-wrap:balance}
h3.display{font-size:clamp(24px,2.5vw,30px);line-height:1.1;letter-spacing:-.016em}
.eyebrow{
  display:block;font-family:var(--mono);font-size:11px;font-weight:500;
  letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);
}
/* Mono's own sidebearings mean a label needs less tracking than the same label
   set in the sans did, hence .12em above.
   The lede is 19px, not the 24 it started at: at 24 a plain sans paragraph is
   the biggest block of default-looking text on the page and competes with the
   headline. Tracked in slightly, and `.on` promotes the one sentence that is
   doing the positioning work to full ink. */
.lede{font-size:19px;line-height:1.55;letter-spacing:-.012em;color:var(--ink-2);max-width:34ch;text-wrap:pretty}
/* Its own line, always. Left inline, the emphasis straddled the wrap and read
   like a bolded fragment rather than the sentence it is. */
.lede .on{display:block;color:var(--ink);font-weight:500}
.note{font-size:13.5px;letter-spacing:-.005em;color:var(--ink-3)}
/* read by a screen reader, not drawn */
.vh{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.section{padding:106px 0}
@media (max-width:760px){ .section{padding:76px 0} }

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  font-size:14px;font-weight:500;letter-spacing:-.012em;
  padding:14px 24px;border-radius:4px;text-decoration:none;border:1px solid transparent;
  cursor:pointer;white-space:nowrap;
  transition:background .18s ease,color .18s ease,border-color .18s ease,transform .12s ease;
}
.btn:active{transform:translateY(1px)}
.btn-solid{background:var(--ink);color:var(--white)}
.btn-solid:hover{background:#2d2d33}
/* The ghost fills from the bottom. The label sits in its own layer so it can
   flip to paper as the ink arrives; a plain background transition would cross
   through a grey that neither colour is legible against. */
.btn-line{position:relative;background:transparent;color:var(--ink);border-color:var(--rule-3);overflow:hidden;isolation:isolate}
.btn-line::before{
  content:"";position:absolute;inset:0;z-index:-1;background:var(--ink);
  transform:translateY(101%);transition:transform .42s cubic-bezier(.2,.7,.2,1);
}
.btn-line:hover{color:var(--paper);border-color:var(--ink)}
.btn-line:hover::before{transform:translateY(0)}
.btn .arw{font-family:var(--sans);transition:transform .2s ease}
.btn:hover .arw{transform:translateX(3px)}
.btn-invert{background:var(--white);color:var(--ink)}
.btn-invert:hover{background:#e9e7e2}

/* the "Get in touch" pairing: label plus a ringed arrow */
.btn-ring{
  display:inline-flex;align-items:center;gap:12px;text-decoration:none;
  font-size:14px;font-weight:500;letter-spacing:-.012em;color:var(--ink);padding:6px 4px;
}
.btn-ring i{
  display:grid;place-items:center;width:34px;height:34px;flex:none;
  border:1px solid var(--rule-3);border-radius:50%;font-style:normal;font-size:13px;
  transition:background .2s ease,color .2s ease,border-color .2s ease,transform .2s ease;
}
.btn-ring:hover i{background:var(--ink);border-color:var(--ink);color:var(--white);transform:translateX(3px)}

/* an underlined text link with a moving arrow */
.link-arw{
  display:inline-flex;align-items:center;gap:9px;text-decoration:none;
  font-size:14px;font-weight:500;letter-spacing:-.012em;color:var(--ink);
  border-bottom:1px solid var(--ink);padding-bottom:3px;
  transition:gap .18s ease,border-color .18s ease;
}
.link-arw:hover{gap:15px;border-color:var(--accent-ink)}

/* shared/chrome.css sets the wordmark at 800 for a page whose body font is
   Inter. Geist ships here at 400/500/600, so an 800 would be synthesised into a
   smeared bold. Ask for the heaviest weight the page actually loads. */
.nav--light .nav-logo,.site-foot .foot-brand{font-weight:600}
/* Smaller than shared/chrome.css sets it. On a page whose h1 is 80px, a 22px
   wordmark competes with the headline for the same eye. */
.nav--light .nav-logo svg{height:22px}
.nav--light .nav-logo span{font-size:18px;letter-spacing:-.02em}
.nav--light .nav-cta,.nav--light .nav-login,.nav--light .nav-links a.navlink{font-weight:500}

/* ---------- reveal ---------- */
[data-reveal]{opacity:0;transform:translateY(16px);transition:opacity .8s cubic-bezier(.2,.7,.2,1),transform .8s cubic-bezier(.2,.7,.2,1)}
[data-reveal].is-in{opacity:1;transform:none}
[data-reveal-delay="1"]{transition-delay:.08s}
[data-reveal-delay="2"]{transition-delay:.16s}
[data-reveal-delay="3"]{transition-delay:.24s}
@media (prefers-reduced-motion:reduce){ [data-reveal]{opacity:1;transform:none;transition:none} }
.progress{position:fixed;top:0;left:0;height:2px;width:0;background:var(--ink);z-index:61;pointer-events:none}
@keyframes mp-progress{from{width:0}to{width:100%}}

/* ---------- a section head: heading left, a rail of words right ----------
 * This carried a five-column capability grid under it. The grid listed
 * Members / Classes / Payments / Grading, which is exactly what the tabs 100px
 * below it already say, so it was four labels twice and it went. */
.sol-head{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:40px;align-items:end}
.sol-head .eyebrow{margin-bottom:22px}
.sol-head h2{max-width:16ch}
.sol-aside{display:grid;gap:8px;text-align:right;padding-bottom:8px}
.sol-aside span{font-family:var(--mono);font-size:10.5px;font-weight:400;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
@media (max-width:1024px){
  .sol-head{grid-template-columns:minmax(0,1fr)}
  .sol-aside{text-align:left}
}

/* ---------- device frames ----------
 * One laptop and one phone, drawn in CSS so the screenshot inside stays the
 * only raster. The laptop is a black lid on a lighter deck; the phone is a
 * thin bezel, because on this page the phone is the supporting device. */
.frame{position:relative}
.frame-top{background:#0d0d10;border:1px solid rgba(0,0,0,.55);border-bottom:0;border-radius:14px 14px 0 0;padding:11px 11px 0;box-shadow:0 30px 60px -34px rgba(0,0,0,.6)}
.frame-top::before{content:"";position:absolute;top:4.5px;left:50%;width:5px;height:5px;margin-left:-2.5px;border-radius:50%;background:#2b2b31}
.frame-top img{border-radius:6px 6px 0 0;width:100%}
.frame-base{height:15px;margin:0 -3.6%;background:linear-gradient(#33333a,#17171b 60%,#0d0d10);border-radius:0 0 12px 12px;position:relative}
.frame-base::after{content:"";position:absolute;top:0;left:50%;width:13%;height:4px;margin-left:-6.5%;background:#0a0a0c;border-radius:0 0 5px 5px}
.shot{position:relative}
.shot img{position:absolute;inset:0;opacity:0;transition:opacity .5s ease}
.shot img:first-child{position:relative}
.shot img.is-active{opacity:1}
/* The phone: a thin black bezel, an island in a status strip painted the app
   header's own white, then the screenshot. Every measure is in cqw of the
   phone's wrapper, so it is the same phone at 80px wide on a small screen and
   at 170px on a large one, rather than a bezel that fattens as it shrinks.
   The strip exists because the capture is the web app, which has no status
   bar: an island drawn straight onto the screenshot sat on the academy's name. */
.phone-float{container-type:inline-size}
.phone{position:relative;background:#111113;border-radius:15cqw;padding:2.4cqw;box-shadow:0 34px 60px -30px rgba(0,0,0,.55),0 0 0 1px rgba(0,0,0,.35)}
.phone-bar{position:relative;height:11cqw;background:#fff;border-radius:12.6cqw 12.6cqw 0 0}
.phone-bar::before{content:"";position:absolute;top:2.6cqw;left:50%;width:29cqw;height:7cqw;margin-left:-14.5cqw;border-radius:99px;background:#111113}
.phone .shot{border-radius:0 0 12.6cqw 12.6cqw;overflow:hidden;background:#fff}
.phone .shot img{width:100%}
/* Hung off the phone's right edge so it reads as the phone's caption at any
   width, including the ones where the phone is narrower than the line. */
.phone-note{position:absolute;right:0;top:calc(100% + 14px);white-space:nowrap;font-family:var(--mono);font-size:11px;letter-spacing:.02em;color:var(--ink-3)}

/* ---------- shared components ----------
 * The pieces the homepage and the product pages both use: a numbered list on
 * hairlines, three facts in a row, and the pricing furniture (a tier, plan
 * cards, rows, the add-on list). Written once here so a price looks the same
 * wherever it is quoted. */
.steps-list{display:grid;gap:0;margin:34px 0 32px;border-top:1px solid var(--rule)}
/* `> div` twice over: the row is the grid, the text block inside it is not. */
.steps-list > div{
  display:grid;grid-template-columns:38px minmax(0,1fr);gap:14px;
  padding:18px 14px 18px 0;border-bottom:1px solid var(--rule);
  transition:padding-left .35s cubic-bezier(.2,.7,.2,1),background .35s ease;
}
.steps-list > div:hover{padding-left:14px;background:rgba(0,0,0,.022)}
.steps-list > div:hover i{color:var(--accent-ink)}
.steps-list i{transition:color .3s ease}
.steps-list i{font-style:normal;font-family:var(--mono);font-size:13px;line-height:1.7;color:var(--ink-3)}
.steps-list b{display:block;font-size:15px;font-weight:500;letter-spacing:-.012em}
.steps-list p{font-size:15px;line-height:1.5;letter-spacing:-.006em;color:var(--ink-2);margin-top:3px}

/* three facts in a row, no heading
 * This used to carry two unattributed testimonials above it. An unattributed
 * quote is weaker than no quote, so they are gone and the facts stand alone. */
.claims{display:grid;grid-template-columns:repeat(3,1fr)}
.claim{padding:0 30px;border-left:1px solid var(--rule)}
.claim:first-child{border-left:0;padding-left:0}
.claim b{display:block;font-family:var(--serif);font-size:26px;font-weight:300;letter-spacing:-.016em;margin-bottom:9px;text-wrap:balance}
.claim span{font-size:14.5px;line-height:1.5;letter-spacing:-.006em;color:var(--ink-2);text-wrap:pretty}
@media (max-width:900px){
  .claims{grid-template-columns:1fr;gap:30px}
  .claim{border-left:0;padding:0}
}

/* pricing:
 * Gym management is two plans and needs cards. Websites and the white-label app
 * are one line each and would look absurd as cards, so they are rows: same
 * hairline language, a quarter of the ink. */
.tier{padding:38px 0;border-top:1px solid var(--rule-2);display:grid;grid-template-columns:minmax(0,.62fr) minmax(0,1.38fr);gap:48px;align-items:start}
.tier-head p{margin-top:14px;font-size:14.5px;line-height:1.5;letter-spacing:-.006em;color:var(--ink-2);max-width:30ch;text-wrap:pretty}
.tier-head .eyebrow{color:var(--ink)}
.plans{display:grid;grid-template-columns:1fr 1fr;gap:20px;align-items:stretch}
.plan{display:flex;flex-direction:column;background:var(--white);border:1px solid var(--rule);border-radius:14px;padding:30px 28px 28px;position:relative}
.plan.pro{background:var(--dark);color:var(--white);border-color:var(--dark)}
.plan-name{font-family:var(--mono);font-size:11px;font-weight:400;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);margin-bottom:16px}
.plan.pro .plan-name{color:rgba(255,255,255,.6)}
.plan-price{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
.plan-price .num{font-family:var(--serif);font-size:52px;font-weight:300;line-height:1;letter-spacing:-.028em}
.plan-price .per{font-size:14px;font-weight:400;letter-spacing:-.006em;color:var(--ink-3)}
.plan.pro .plan-price .per{color:rgba(255,255,255,.62)}
.plan-sub{margin:16px 0 24px;font-size:15px;line-height:1.5;letter-spacing:-.006em;color:var(--ink-2)}
.plan.pro .plan-sub{color:rgba(255,255,255,.74)}
/* align-content:start: the list takes the card's spare height so the buttons
   line up across cards, but its rows keep their own spacing rather than
   spreading out to fill it */
.plan ul{list-style:none;margin:0 0 28px;padding:0;display:grid;gap:10px;flex:1;align-content:start}
.plan li{font-size:14.5px;line-height:1.5;letter-spacing:-.006em;color:var(--ink-2);padding-left:18px;position:relative}
.plan.pro li{color:rgba(255,255,255,.8)}
.plan li::before{content:"";position:absolute;left:0;top:9px;width:6px;height:1px;background:var(--rule-3)}
.plan.pro li::before{background:rgba(255,255,255,.4)}
.plan .btn{width:100%}
/* the ghost button's ink fill has to invert against a dark card, not a light one */
.plan.pro .btn-invert:hover{background:#e9e7e2}

.rows{display:grid;gap:0}
.row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:32px;align-items:center;padding:22px 0;border-bottom:1px solid var(--rule)}
.row:last-of-type{border-bottom:0}
.row-what b{display:block;font-size:16px;font-weight:500;letter-spacing:-.014em;margin-bottom:5px}
.row-what span{display:block;font-size:14.5px;line-height:1.5;letter-spacing:-.006em;color:var(--ink-2);max-width:52ch;text-wrap:pretty}
.row-cost{text-align:right;white-space:nowrap}
.row-cost b{font-family:var(--serif);font-size:34px;font-weight:300;letter-spacing:-.024em}
.row-cost span{font-size:14px;color:var(--ink-3);margin-left:6px}
/* Website add-ons. Quoted, so there is no price to put in the right-hand
   column: "Quoted" holds the column's place in the same serif the prices use,
   and ONE button serves the group. Five identical "Get a quote" buttons in a
   stack was the version this replaced. */
/* no border-top: the row above already ends on a hairline, and two of them 10px
   apart read as a rendering fault */
.addons{padding-top:24px}
.addons-label{display:block;margin-bottom:6px;font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
.addons-list{list-style:none;margin:0 0 22px;padding:0;display:grid;grid-template-columns:1fr 1fr;column-gap:40px}
.addons-list li{display:flex;justify-content:space-between;align-items:baseline;gap:16px;padding:13px 0;border-bottom:1px solid var(--rule)}
.addons-list b{font-size:15px;font-weight:500;letter-spacing:-.012em}
.addons-list span{flex:none;font-family:var(--serif);font-size:17px;font-weight:300;letter-spacing:-.01em;color:var(--ink-3)}
/* the photography line carries the studio's own credit, logo first */
.addon-media b em{display:flex;align-items:center;gap:6px;margin-top:5px;font-style:normal;font-size:12.5px;font-weight:400;letter-spacing:-.004em;color:var(--ink-2)}
.addon-media b em svg{height:11px;width:auto;flex:none;color:var(--ink)}
.addons-list li{transition:padding-left .35s cubic-bezier(.2,.7,.2,1)}
.addons-list li:hover{padding-left:8px}
@media (max-width:640px){
  .addons-list{grid-template-columns:1fr}
  .addons .btn{width:100%}
}
.row-note{margin-top:20px;font-size:13.5px;line-height:1.55;color:var(--ink-3);max-width:64ch;text-wrap:pretty}
.row-note a{font-weight:500;color:var(--ink);text-decoration:underline;text-decoration-color:var(--accent-ink);text-decoration-thickness:1.5px;text-underline-offset:3px}

.plan-more{margin-top:38px;display:flex;flex-wrap:wrap;gap:6px 16px;align-items:center;justify-content:center;font-size:14px;letter-spacing:-.006em;color:var(--ink-2)}
.plan-more a{font-weight:500;color:var(--ink);text-decoration:underline;text-decoration-color:var(--accent-ink);text-decoration-thickness:1.5px;text-underline-offset:3px}
.plan-more .sep{color:var(--rule-2)}
@media (max-width:1024px){
  .tier{grid-template-columns:minmax(0,1fr);gap:26px}
  .tier-head p{max-width:52ch}
}
@media (max-width:640px){
  .plans{grid-template-columns:1fr}
  .row{grid-template-columns:minmax(0,1fr);gap:14px}
  .row-cost{text-align:left;white-space:normal}
  .row-cost .btn{width:100%}
}


/* the curriculum loop in three steps (homepage section, curriculum page)
 * Drawn, not screenshotted: the platform section just above already shows the
 * product, and what this section has to get across is an idea, that eighty
 * planned techniques become the two that were taught. Three columns on one
 * rule, the live moment lifted onto a white card, and the ticks draw as the
 * band crosses the window. The orange is a hairline that runs along the top
 * of each step in turn. */
.cur-flow{list-style:none;margin:64px 0 0;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:1px solid var(--rule-2)}
.cur-node{position:relative;padding:36px 40px 44px;border-left:1px solid var(--rule)}
.cur-node:first-child{border-left:0;padding-left:0}
.cur-node::before{content:"";position:absolute;left:0;right:0;top:-1px;height:1px;background:var(--accent);transform-origin:0 50%}
.cur-node:first-child::before{right:40px}
/* The card's own edge is the divider on both of its sides, so the hairlines
   either side of it go, and the two words that sit on those edges need no
   patch of background to cut the line behind them. */
.cur-live{background:var(--white);box-shadow:0 30px 60px -40px rgba(0,0,0,.35);border-left:0}
.cur-live + .cur-node{border-left:0}
.cur-tag{display:block;margin-bottom:18px;font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
.cur-node h3.display{margin-bottom:26px}
/* the word that happens between two steps, sat on the rule that divides them */
.cur-link{position:absolute;left:0;top:50%;transform:translate(-50%,-50%);z-index:1}
.cur-link i{display:block;font-style:normal;font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);writing-mode:vertical-rl;transform:rotate(180deg)}
.cur-list,.cur-ticks{list-style:none;margin:0;padding:0}
.cur-list li,.cur-ticks li{padding:11px 0;border-top:1px solid var(--rule);font-size:15px;letter-spacing:-.008em;color:var(--ink)}
.cur-list li:last-child,.cur-ticks li:last-child{border-bottom:1px solid var(--rule)}
.cur-list .faint,.cur-ticks .faint{color:var(--ink-3)}
.cur-ticks li{display:flex;align-items:center;gap:12px}
.cur-ticks .box{display:grid;place-items:center;width:18px;height:18px;flex:none;border:1px solid var(--rule-3);border-radius:4px;color:var(--white)}
.cur-ticks .on .box{background:var(--ink);border-color:var(--ink)}
.cur-ticks .box svg{width:12px;height:12px}
.cur-ticks .box path{stroke-dasharray:14;stroke-dashoffset:0}
.cur-people{margin:0}
.cur-people div{padding:11px 0;border-top:1px solid var(--rule)}
.cur-people div:last-child{border-bottom:1px solid var(--rule)}
.cur-people dt{font-size:15px;font-weight:500;letter-spacing:-.008em}
.cur-people dd{margin:3px 0 0;font-size:13.5px;color:var(--ink-3)}
.cur-foot{margin-top:22px;font-family:var(--serif);font-size:22px;font-weight:300;letter-spacing:-.014em;line-height:1.2}
.cur-close{margin-top:58px;display:flex;flex-wrap:wrap;gap:22px 48px;align-items:flex-end;justify-content:space-between}
.cur-close p{font-size:17px;line-height:1.6;letter-spacing:-.008em;color:var(--ink-2);max-width:58ch;text-wrap:pretty}
@media (max-width:900px){
  .cur-flow{grid-template-columns:minmax(0,1fr);border-top:0}
  .cur-node,.cur-node:first-child{border-left:0;border-top:1px solid var(--rule-2);padding:40px 0 36px}
  .cur-node:first-child::before{right:0}
  .cur-live{padding:40px 22px 36px;margin:0 -22px}
  .cur-link{left:22px;top:0;transform:translateY(-50%);padding:0 10px;background:var(--paper-2)}
  .cur-live .cur-link{background:var(--white)}
  .cur-live .cur-link{left:22px}
  .cur-node:not(.cur-live) .cur-link{left:0;padding-left:0}
  .cur-link i{writing-mode:horizontal-tb;transform:none}
}

/* ---------- closing band ---------- */
.closing{background:var(--dark);color:var(--white);padding:110px 0}
.closing .wrap{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:48px;align-items:center}
.closing .eyebrow{color:rgba(255,255,255,.6);margin-bottom:22px}
.closing h2{max-width:24ch}
.closing .note{color:rgba(255,255,255,.6);margin-top:14px}
.closing-cta{display:flex;flex-wrap:wrap;gap:12px;align-items:center}
/* The ink-band twin of .btn-line. It used to be an inline style on the anchor,
   which meant the hover state was simply missing. .36 white and not .3: a
   button's edge is a meaningful border, so it owes 3:1, and .3 measured 2.60
   against this band. */
.btn-onink{background:transparent;color:var(--white);border-color:rgba(255,255,255,.36)}
.btn-onink:hover{border-color:var(--white);background:rgba(255,255,255,.08)}
@media (max-width:980px){
  .closing{padding:80px 0}
  .closing .wrap{grid-template-columns:minmax(0,1fr);gap:34px}
}

/* ---------- motion ----------
 *
 * Two layers, and the browser picks one.
 *
 * The FALLBACK is the transition on [data-reveal] near the top of this file,
 * flipped by shared/product.js on an IntersectionObserver: an element fades up
 * once, on a timer, and stays. Safari and Firefox get this.
 *
 * The REAL one, under @supports below, is a scroll-driven animation
 * (`animation-timeline: view()`). The element's progress IS the scroll
 * position, so a reveal tracks the wheel and reverses when you scroll back,
 * which is what makes the page feel attached to the hand rather than triggered
 * by it. No JS, no rAF, no listener: the compositor runs it. Chrome and Edge.
 *
 * Everything here is decorative and everything is off under
 * prefers-reduced-motion, which is asserted at the end of the block. Any hover
 * transform is paired with an :active state, because a phone has no hover and a
 * press is the only feedback it can give.
 */
@keyframes rise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}
@keyframes photoIn{from{opacity:0;transform:scale(1.06)}to{opacity:1;transform:none}}
@keyframes revealUp{from{opacity:0;transform:translateY(46px) scale(.972)}to{opacity:1;transform:none}}
@keyframes liftIn{from{opacity:0;transform:translateY(60px)}to{opacity:1;transform:none}}
@keyframes tiltIn{from{opacity:0;transform:perspective(1600px) rotateX(11deg) translateY(40px)}to{opacity:1;transform:perspective(1600px) rotateX(0) translateY(0)}}
@keyframes driftY{from{transform:translateY(22px)}to{transform:translateY(-22px)}}
@keyframes heroPush{from{transform:scale(1) translateY(0)}to{transform:scale(1.10) translateY(2.5%)}}
@keyframes foldOut{from{transform:none;opacity:1}to{transform:translateY(-64px);opacity:.12}}
@keyframes railOut{from{transform:none;opacity:1}to{transform:translateY(38px);opacity:0}}
@keyframes driftUp{from{transform:translateY(30px)}to{transform:translateY(-30px)}}
@keyframes driftUpSmall{from{transform:translateY(18px)}to{transform:translateY(-18px)}}
@keyframes phoneDrift{from{transform:translateY(34px)}to{transform:translateY(-34px)}}
@keyframes growX{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@keyframes tickBox{from{background:transparent;border-color:var(--rule-3)}to{background:var(--ink);border-color:var(--ink)}}
@keyframes tickDraw{from{stroke-dashoffset:14}to{stroke-dashoffset:0}}
@keyframes driftDown{from{transform:translateY(-26px)}to{transform:translateY(26px)}}
@keyframes stepIn{from{opacity:0;transform:translateX(-26px)}to{opacity:1;transform:none}}
@keyframes tabTimer{from{width:0}to{width:100%}}

/* ---- pointer feedback ---- */
.plan{transition:transform .35s cubic-bezier(.2,.7,.2,1),box-shadow .35s ease}
.plan:hover{transform:translateY(-6px);box-shadow:0 30px 60px -34px rgba(0,0,0,.4)}
.plan.pro:hover{box-shadow:0 30px 60px -30px rgba(0,0,0,.65)}
.claim{transition:transform .35s cubic-bezier(.2,.7,.2,1)}
.claim:hover{transform:translateY(-3px)}
.btn:active{transform:translateY(1px) scale(.988)}
.btn-ring:active i{transform:scale(.94)}
.link-arw:active{opacity:.7}

/* the JS fallback's timed stagger for a row of facts */
.is-in .claim{animation:rise .8s cubic-bezier(.2,.7,.2,1) both}
.is-in .claim:nth-child(2){animation-delay:.1s}
.is-in .claim:nth-child(3){animation-delay:.2s}

/* the curriculum loop's fallback: the ticks draw once the list has revealed */
.cur-flow .on .box{background:transparent;border-color:var(--rule-3);transition:background .3s ease .9s,border-color .3s ease .9s}
.cur-flow .on + .on .box{transition-delay:1.25s}
.cur-flow .box path{stroke-dashoffset:14;transition:stroke-dashoffset .45s ease 1s}
.cur-flow .on + .on .box path{transition-delay:1.35s}
.cur-flow.is-in .on .box{background:var(--ink);border-color:var(--ink)}
.cur-flow.is-in .box path{stroke-dashoffset:0}
.cur-node::before{transform:scaleX(0);transition:transform 1.1s cubic-bezier(.2,.7,.2,1) .3s}
.cur-node:nth-child(2)::before{transition-delay:.7s}
.cur-node:nth-child(3)::before{transition-delay:1.1s}
.cur-flow.is-in .cur-node::before{transform:none}

/* ---- scroll-driven reveals ----
 * Late and quick: a block is still moving while it is below the middle of the
 * window and settled by the time you read it. The constant motion comes from
 * the DRIFT rules, which run across an element's whole crossing and so never
 * finish while it is on screen. Pages add their own under the same guard. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    [data-reveal]{
      opacity:1;transform:none;transition:none;
      animation:revealUp linear both;
      animation-timeline:view();
      animation-range:entry 18% cover 44%;
    }
    /* DRIFT: the section head and its rail pull apart as the band crosses */
    .sol-head > div:first-child{animation:driftUp linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    .sol-aside{animation:driftDown linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    /* Curriculum: the steps arrive left to right, the orange runs along each
       top in turn, and the two ticks draw in the middle of the crossing. */
    .cur-node{animation:liftIn linear both;animation-timeline:view();animation-range:entry 14% cover 40%}
    .cur-node:nth-child(2){animation-range:entry 20% cover 46%}
    .cur-node:nth-child(3){animation-range:entry 26% cover 52%}
    .cur-node::before,.cur-flow.is-in .cur-node::before{transition:none;animation:growX linear both;animation-timeline:view();animation-range:cover 20% cover 38%}
    .cur-node:nth-child(2)::before,.cur-flow.is-in .cur-node:nth-child(2)::before{animation-range:cover 30% cover 48%}
    .cur-node:nth-child(3)::before,.cur-flow.is-in .cur-node:nth-child(3)::before{animation-range:cover 40% cover 58%}
    .cur-flow .on .box,.cur-flow.is-in .on .box{transition:none;animation:tickBox linear both;animation-timeline:view();animation-range:cover 30% cover 38%}
    .cur-flow .on + .on .box,.cur-flow.is-in .on + .on .box{animation-range:cover 36% cover 44%}
    .cur-flow .on .box path,.cur-flow.is-in .on .box path{transition:none;animation:tickDraw linear both;animation-timeline:view();animation-range:cover 32% cover 40%}
    .cur-flow .on + .on .box path,.cur-flow.is-in .on + .on .box path{animation-range:cover 38% cover 46%}
    .cur-link{animation:fadeIn linear both;animation-timeline:view();animation-range:cover 26% cover 40%}
    .cur-close{animation:driftUpSmall linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    /* numbered steps, facts and pricing arrive one after another, each still
       moving while the next one starts */
    .claims{animation:driftUpSmall linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    .tier-head{animation:driftUpSmall linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    .steps-list > div{animation:stepIn linear both;animation-timeline:view();animation-range:entry 22% cover 46%}
    .steps-list > div:nth-child(2){animation-range:entry 28% cover 52%}
    .steps-list > div:nth-child(3){animation-range:entry 34% cover 58%}
    .claim{animation:liftIn linear both;animation-timeline:view();animation-range:entry 20% cover 44%}
    .claim:nth-child(2){animation-range:entry 26% cover 50%}
    .claim:nth-child(3){animation-range:entry 32% cover 56%}
    .is-in .claim{animation:liftIn linear both;animation-timeline:view()}
    .tier{animation:revealUp linear both;animation-timeline:view();animation-range:entry 18% cover 44%}
    .plan:first-child{animation:liftIn linear both;animation-timeline:view();animation-range:entry 22% cover 48%}
    .plan.pro{animation:liftIn linear both;animation-timeline:view();animation-range:entry 28% cover 54%}
    .row{animation:stepIn linear both;animation-timeline:view();animation-range:entry 22% cover 48%}
    .addons-list li{animation:stepIn linear both;animation-timeline:view();animation-range:entry 20% cover 46%}
    .addons-list li:nth-child(even){animation-range:entry 26% cover 52%}
  }
}
@media (prefers-reduced-motion:reduce){
  .is-in .claim{animation:none}
  .cur-flow .on .box{background:var(--ink);border-color:var(--ink);transition:none}
  .cur-flow .box path{stroke-dashoffset:0;transition:none}
  .cur-node::before{transform:none;transition:none}
  .shot img,.btn,.btn-ring i,.btn-line::before,.steps-list > div,.addons-list li,.plan,.claim{transition:none}
  .btn:active{transform:none}
}

/* The features hub and the home's feature grid (scripts/i18n.mjs renders the
   cards from i18n.config.json), and the "Related" block at the foot of every
   feature page. Cards are links: the anchor text is the query the page targets. */
.feature-hub{padding:clamp(40px,6vw,80px) 0}
.feature-grid{list-style:none;margin:32px 0 0;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
.feature-grid a,.related a{display:block;height:100%;padding:22px 22px 24px;border:1px solid var(--rule);border-radius:14px;background:var(--white);text-decoration:none;color:var(--ink);transition:border-color .3s,transform .4s var(--ease-out)}
.feature-grid a:hover,.related a:hover{border-color:var(--ink);transform:translateY(-2px)}
/* A hub card wears the page's own screenshot on top (14 Sep 2026): the picture
   is cropped to one aspect so phone and desktop shots sit in the same grid,
   anchored to the top where the screen's chrome is. */
.feature-grid a{padding:0;overflow:hidden}
.feature-grid img{display:block;width:100%;aspect-ratio:16/10;object-fit:cover;object-position:50% 0;border-bottom:1px solid var(--rule);background:var(--paper)}
.feature-grid .fg-body{padding:18px 22px 22px}
.feature-grid b,.related b{display:block;font-size:17px;font-weight:500;letter-spacing:-.014em;margin-bottom:6px}
.feature-grid span,.related span{display:block;font-size:14px;line-height:1.5;letter-spacing:-.006em;color:var(--ink-2);text-wrap:pretty}
.related{margin:clamp(48px,7vw,96px) auto 0;padding:0 40px;max-width:var(--maxw)}
.related-h{margin:0 0 14px;font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
.related ul{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
@media (max-width:900px){.feature-grid,.related ul{grid-template-columns:1fr}.related{padding:0 22px}}

/* A page's questions: a <details> list under a display heading. FAQPage
   schema in the page's head names the same pairs. */
.faq{padding:clamp(64px,8vw,110px) 0}
.faq h2{max-width:20ch;margin-bottom:34px}
.faq details{border-top:1px solid var(--rule);padding:20px 0}
.faq details:last-of-type{border-bottom:1px solid var(--rule)}
.faq summary{cursor:pointer;font-size:18px;font-weight:500;letter-spacing:-.012em;list-style:none;display:flex;justify-content:space-between;gap:24px}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"+";font-family:var(--mono);color:var(--ink-3)}
.faq details[open] summary::after{content:"\2013"}
.faq p{margin-top:12px;font-size:15.5px;line-height:1.6;color:var(--ink-2);max-width:62ch;text-wrap:pretty}

/* ---- home.css ---- */
/* The homepage's own sections, on top of shared/paper.css.
 *
 * paper.css holds the system every paper page shares: tokens, type, buttons,
 * the section head, device frames, the closing band and the motion primitives.
 * What is here is what only the homepage has: the hero, the three offers, the
 * disciplines strip, the platform tabs, the co-pilot, the claims and pricing.
 */
/* ---------- hero ----------
 * Copy on the left, the photograph on the right bleeding off the top and the
 * right edge, and a rail of words in the right gutter. The photograph's own
 * ground is a near-white studio sweep, so it is multiplied onto the paper and
 * masked on the left: no frame, no visible edge, no scrim. */
/* min-height 100vh so the fold IS the hero: the photograph's floor and the
   bottom of the window are the same line, with no paper strip under her. */
/* overflow:clip, not hidden. `hidden` makes the hero a scroll container, and a
   `view()` timeline on anything inside it then resolves against that container
   instead of the document: the photograph's scroll animation froze at the value
   it happened to hold on load. `clip` clips without establishing a scrollport. */
.hero{position:relative;background:var(--paper);padding-top:80px;overflow:clip;min-height:100vh;min-height:100svh;display:flex;flex-direction:column}
.hero-media{
  /* The frame starts where the header ends, not at the top of the page. The
     photograph's own studio sweep fills its first rows, so that edge is
     invisible, while the hair now clears the bar: overlapping them put a grey
     smudge behind the language pill however the header was painted. */
  /* top and bottom insets rather than a percentage height: whatever the hero
     ends up being, the frame's floor is the hero's floor. */
  position:absolute;top:80px;bottom:0;right:clamp(0px,7vw,124px);
  width:min(42vw,610px);z-index:0;pointer-events:none;
  /* No overflow property here, deliberately. `hidden` makes this a scroll
     container and a `view()` timeline then resolves against IT rather than the
     document, which froze the photograph's scroll animation at whatever value
     it held on load; `clip` behaved the same way in Chrome. Nothing needs
     clipping any more either: the img is exactly 100% of this box at every
     width, and `.hero{overflow:clip}` is the backstop. */
}
/* Two masks, not a scrim: the left one dissolves the photograph into the paper
   behind the headline, the right one hides the crop edge, which lands on the
   mid-grey of a gi sleeve and reads as a printed rectangle without it. */
.hero-media img{
  width:100%;height:100%;object-fit:cover;object-position:54% 0;
  mix-blend-mode:multiply;filter:grayscale(1) contrast(1.11);
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 20%,#000 88%,transparent 100%);
          mask-image:linear-gradient(90deg,transparent 0%,#000 20%,#000 88%,transparent 100%);
}
/* A short paper fade on the very top row, so the frame's edge cannot draw a
   line if a future crop puts something dark against it. */
.hero-media::after{
  content:"";position:absolute;top:0;left:0;right:0;height:34px;z-index:2;
  background:linear-gradient(180deg,var(--paper) 0%,rgba(243,242,238,0) 100%);
}
/* Newsprint grain. An SVG fractal noise as a background image rather than a CSS
   filter: a filter would have to re-run on every paint of a full-bleed photo,
   this is one tile the compositor reuses. It carries the img's own mask so the
   grain stops exactly where she does and never lands on bare paper. */
.hero-media::before{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  opacity:.62;mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.72' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)' opacity='.72'/%3E%3C/svg%3E");
  background-size:220px 220px;
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 20%,#000 88%,transparent 100%);
          mask-image:linear-gradient(90deg,transparent 0%,#000 20%,#000 88%,transparent 100%);
}
/* the words down the right gutter */
/* The veil sits between the photograph and the copy. It is off on a wide
   screen, where she has her own column; on a phone she is behind the words and
   the type needs a clean field to sit on. */
.hero::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;display:none}
.hero-rail{
  position:absolute;right:28px;top:min(20vh,168px);z-index:3;
  display:grid;gap:9px;text-align:right;
}
.hero-rail span{font-family:var(--mono);font-size:10.5px;font-weight:400;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3)}
/* Hidden up to 1260, not 1180: that is where the photograph moves to the edge
   to clear the claim row, and a narrow frame there put her hair under the third
   word, 1.46:1 at 1200x900. */
@media (max-width:1260px){ .hero-rail{display:none} }

/* The wrap is a column so the grid can absorb the slack and the claim row lands
   on the bottom edge of the fold rather than floating above it. */
/* z-index 2, above the veil at 1. At equal z-index the pseudo-element paints
   after its sibling, so the veil went over the headline instead of under it. */
.hero > .wrap{position:relative;z-index:2;display:flex;flex-direction:column;flex:1}
/* The claim row stands off the bottom edge rather than resting on it. Flush,
   it read as something cut off by the viewport instead of a row you land on. */
.hero > .wrap{padding-bottom:clamp(32px,7vh,76px)}
.hero-grid{display:grid;grid-template-columns:minmax(0,.94fr) minmax(0,1.06fr);align-items:center;flex:1}
.hero-copy{padding:24px 0 40px}
.hero-copy .eyebrow{margin-bottom:24px}
.hero h1{max-width:11ch}
.hero .lede{margin:30px 0 40px;max-width:34ch}
.hero-cta{display:flex;flex-wrap:wrap;gap:14px 26px;align-items:center}
.hero-note{margin-top:22px}
/* the claim row across the bottom of the fold: no counts, no vanity numbers */
/* As wide as its three cells and no wider. The photograph's frame starts left
   of where a fixed 600px row ended on anything short of a wide screen, and the
   third cell measured 2.1:1 against a seam in her gi at 1440x900, 1.22:1 at
   1152x720. Text cannot win against a photograph, so the row stops before it. */
.hero-facts{
  display:grid;
  grid-template-columns:repeat(3,auto);justify-content:start;
  gap:0;border-top:1px solid var(--rule);width:fit-content;max-width:100%;
}
.hero-facts div{padding:24px 26px 30px;border-left:1px solid var(--rule)}
.hero-facts div:first-child{border-left:0;padding-left:0}
.hero-facts b{display:block;font-family:var(--serif);font-size:25px;font-weight:300;letter-spacing:-.016em;line-height:1.1}
.hero-facts span{display:block;margin-top:9px;font-family:var(--mono);font-size:10.5px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}
/* A laptop screen scales her up relative to the page, so her shoulder reaches
   further left: the cells tighten a little and, up to 1260, where the word rail
   is hidden, the frame moves to the edge. Measured 1.67:1 at 1261x1000 and
   1.14:1 at 1200x900 without it. */
@media (min-width:1025px) and (max-width:1366px){
  .hero-facts div{padding:22px 22px 26px}
  .hero-facts b{font-size:23px}
}
@media (min-width:1025px) and (max-width:1260px){
  .hero-media{right:0}
}
@media (max-width:1024px){
  /* She becomes the BACKGROUND rather than a band underneath. The stacked
     version put the photograph below the copy as its own block, which read as
     two unrelated things and cost the fold its subject. Now the frame is
     full-bleed behind everything and the veil below carries the legibility. */
  .hero{padding-top:80px}
  /* The frame stops at the bottom of the FOLD, not the bottom of the hero. Run
     to the hero's floor it continued behind the claim row, which then sat on
     whatever part of the photograph happened to be there: measured 1.0:1
     against her hair at 768 wide. Now her floor is the window's floor and the
     claim row below it is on paper. */
  .hero-media{
    position:absolute;top:80px;bottom:auto;left:0;right:0;width:auto;
    height:calc(100svh - 80px);
  }
  .hero-media img{
    /* Back to 100%. At this frame ratio the source scales by height and fills
       vertically, so the whole portrait shows: her face in the upper half, the
       gi in the lower half. That split is what lets the copy sit under her
       rather than across her. */
    height:100%;object-position:50% 0;
    /* no side masks: at this width there is no paper beside her to dissolve into */
    -webkit-mask-image:none;mask-image:none;
  }
  .hero-media::before{-webkit-mask-image:none;mask-image:none}
  .hero-media::after{display:none}
  /* Paper over her head and shoulders, clearing by the bottom of the fold, so
     the words sit on a clean field and she resolves into a real photograph at
     the bottom edge instead of being flatly faded out. */
  /* No section-wide veil on a phone. A gradient pinned to the fold protects
     whatever happens to be at a given percentage of it, and the copy block's
     height relative to the fold changes with the device: at 390x667 the eyebrow
     rode up into her hair and measured 2.03:1. The wash below travels WITH the
     copy instead, so it is right at every screen height, and her face is left
     as a photograph with nothing over it. */
  .hero::after{display:none}
  .hero-copy{position:relative;isolation:isolate}
  .hero-copy::before{
    content:"";position:absolute;z-index:-1;
    left:-24px;right:-24px;top:-150px;bottom:-40px;
    background:linear-gradient(180deg,
      rgba(243,242,238,0) 0%,
      rgba(243,242,238,.34) 18%,
      rgba(243,242,238,.74) 32%,
      rgba(243,242,238,.90) 52%,
      rgba(243,242,238,.95) 100%);
  }
  /* The grid alone owns the fold, so the claim row lands just under it rather
     than competing for the first screen. */
  .hero-grid{grid-template-columns:minmax(0,1fr);min-height:calc(100svh - 80px);align-items:flex-end}
  /* The empty second column is the desktop layout's spacer. Left in place it
     became a second ROW here and took the slack, so flex-end had nothing to
     push the copy against and it floated in the middle. */
  .hero-grid > div:last-child{display:none}
  /* Anchored to the bottom of the fold, under her chin rather than across her
     eyes. The headline steps down a size to buy the room this costs. */
  .hero-copy{padding:0 0 30px}
  .hero h1{font-size:clamp(38px,10.4vw,52px)}
  .hero .lede{font-size:17.5px;margin:22px 0 30px}
  .hero h1,.hero .lede{max-width:none}
  /* Darker over the photograph. The eyebrow is the smallest type in the fold,
     and the lede measured 3.79:1 at 390x667, where a short screen pushes it
     below the strong part of the veil. The lede's second sentence keeps its
     emphasis through weight now that colour no longer separates them. */
  /* Full ink, both of them. The eyebrow is the first line of the block, which
     puts it where the wash is weakest, and it is 11px: it needs the headroom. */
  .hero .eyebrow{color:var(--ink)}
  .hero .lede{color:var(--ink)}
  .hero .lede .on{font-weight:600}
  /* And the reassurance line comes off the phone fold altogether. It was the
     lowest copy in the block, which put it over the clear part of the
     photograph: 2.94:1 at 390x667 against the darkest pixel behind it. It is
     one fewer line on a small screen, and the same promise is made by the claim
     row directly below the fold and again at the closing CTA. */
  .hero-note{display:none}
  .hero-facts{padding-top:6px}
  /* Below the fold here, clear of her, so the row can span again. Weighted,
     not equal thirds: the first caption is the longest and equal columns
     wrapped it to two lines beside two one-line neighbours. */
  .hero-facts{grid-template-columns:1.18fr 1fr .92fr;width:auto;max-width:min(600px,100%)}
  .hero > .wrap{padding-bottom:0}
}
@media (max-width:600px){
  /* One per row. Two-up put "Software, sites, payments" on two lines beside a
     one-line neighbour and the row stopped reading as a row. */
  .hero-facts{grid-template-columns:1fr;max-width:none}
  .hero-facts div{padding:16px 0;border-left:0;border-top:1px solid var(--rule)}
  .hero-facts div:first-child{border-top:0}
  .hero-facts b{font-size:24px}
  .hero-cta{gap:10px 18px}
}

/* ---------- the disciplines strip ----------
 * The label is pinned and the arts scroll under it, right to left, forever. The
 * track is duplicated in the markup and the keyframe travels exactly -50%, which
 * is what makes the loop seamless; hover pauses it. */
.strip{border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);background:var(--paper);overflow:hidden}
.strip .wrap{display:flex;align-items:center;gap:0;max-width:none;padding:0}
.strip .lbl{
  flex:none;padding:20px 28px 20px 40px;position:relative;z-index:2;background:var(--paper);
  font-family:var(--mono);font-size:10.5px;font-weight:400;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-3);white-space:nowrap;
}
.strip-rail{
  flex:1;min-width:0;overflow:hidden;display:flex;
  /* Only the trailing edge fades. A leading fade put a half-visible letter
     right next to the pinned label and read as a clipping bug. */
  -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 96px),transparent);
          mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 96px),transparent);
}
.strip-track{display:flex;align-items:center;flex:none;gap:38px;padding-right:38px;animation:marquee 42s linear infinite}
.strip-track span{font-family:var(--serif);font-size:20px;font-weight:300;color:var(--ink-2);white-space:nowrap}
.strip:hover .strip-track{animation-play-state:paused}
@media (max-width:760px){
  .strip .lbl{padding:16px 20px 16px 22px}
  .strip-track{gap:26px;padding-right:26px}
  .strip-track span{font-size:17px}
}

/* ---------- what MatPilot offers: three cards, edge to edge ----------
 * Three products, one anatomy each: eyebrow, heading, one line, one link. The
 * first is ink and carries the app, the second paper and carries the sites, the
 * third is type alone. A white-label app has no honest screenshot (the whole
 * point is that it is not branded MatPilot), and inventing one would be the
 * only dishonest pixel on the page, so that card is given air instead. */
.offer{display:grid;grid-template-columns:1fr 1.18fr .76fr;align-items:stretch}
/* clip, not hidden: this card contains three scroll-driven animations (the copy
   and both visuals) and overflow:hidden would make it their scroll container,
   freezing all three. Same reason as .hero. */
.offer-card{position:relative;overflow:clip;display:flex;flex-direction:column;justify-content:space-between;min-height:min(62vh,560px);padding:46px 42px 0}
.offer-ink{background:var(--dark);color:var(--white)}
.offer-ink .eyebrow{color:rgba(255,255,255,.62)}
.offer-ink .link-arw{color:var(--white);border-bottom-color:rgba(255,255,255,.5)}
.offer-ink .link-arw:hover{border-bottom-color:var(--white)}
.offer-paper{background:var(--paper-2)}
.offer-quiet{background:var(--paper);border-left:1px solid var(--rule);justify-content:flex-start;padding-bottom:46px}
.offer-copy{max-width:30ch}
.offer-card .eyebrow{margin-bottom:18px}
.offer-card h2{font-size:clamp(26px,2.3vw,32px);line-height:1.1;max-width:16ch;text-wrap:balance}
/* the quiet card's CTA is a button, so it needs a little more room above it */
.offer-quiet p{margin-bottom:30px}
.offer-card p{margin:18px 0 24px;font-size:15px;line-height:1.55;letter-spacing:-.006em;color:var(--ink-2);text-wrap:pretty}
/* AFTER .offer-card p, not before it. Declared above, at equal specificity, it
   lost and the ink card's paragraph rendered #4a4a51 on near-black: 2.2:1. */
.offer-ink p{color:rgba(255,255,255,.82)}
.offer-visual{position:relative;margin-top:34px}

/* the app, leaning off the card's right edge. 108% and not more: the cut has to
   land past the last readable column, or it reads as a broken crop. */
.offer-visual--screen{margin:34px -42px -1px 0;border-radius:8px 0 0 0;overflow:clip;box-shadow:0 -22px 60px -26px rgba(0,0,0,.8)}
.offer-visual--screen img{width:108%;max-width:none;display:block}

/* four real sites, fanned up and LEFT from a front card on the right. Each back
   card shows its left edge, which is where every one of these sites keeps its
   logo and headline; fanning right showed right edges instead, and Jaque's is
   a photograph of her rather than her brand. */
.offer-visual--sites{margin:30px 0 0 -42px;height:340px}
.offer-visual--sites .site{
  position:absolute;right:0;bottom:0;width:60%;aspect-ratio:16/10;
  border:1px solid var(--rule-2);border-radius:8px;overflow:hidden;background:var(--white);
  box-shadow:0 18px 40px -22px rgba(0,0,0,.4);
  transition:transform .55s cubic-bezier(.2,.7,.2,1),box-shadow .55s ease;
}
.offer-visual--sites img{width:100%;height:100%;object-fit:cover;object-position:top center}
/* Four deep. Each card clears the one in front by 22% of its own width, which
   is enough of the next site to recognise it, and drops 4% of scale so the deck
   reads as depth rather than as a misaligned grid. */
.site-1{z-index:4;transform:translate(0,0)}
.site-2{z-index:3;transform:translate(-22%,-38px) scale(.96)}
.site-3{z-index:2;transform:translate(-44%,-76px) scale(.92)}
.site-4{z-index:1;transform:translate(-66%,-114px) scale(.88)}
.offer-paper:hover .site-1{transform:translate(3%,10px)}
.offer-paper:hover .site-2{transform:translate(-25%,-48px) scale(.96)}
.offer-paper:hover .site-3{transform:translate(-50%,-94px) scale(.92)}
.offer-paper:hover .site-4{transform:translate(-75%,-140px) scale(.88)}

@media (max-width:1180px){
  .offer{grid-template-columns:1fr 1fr}
  .offer-quiet{grid-column:1/-1;border-left:0;border-top:1px solid var(--rule);min-height:0;padding:46px 42px}
  .offer-quiet .offer-copy{max-width:44ch}
}
@media (max-width:720px){
  .offer{grid-template-columns:1fr}
  .offer-card{padding:36px 22px 0;min-height:0}
  .offer-quiet{padding:36px 22px}
  /* 200% and left-anchored: the whole dashboard at 390px is a grey smear, so a
     phone gets the sidebar and the briefing at a size that can be read. */
  /* The whole screen, not a crop of it. At 200% a phone showed the sidebar and
     half the dashboard and cut the rest off at the edge (Vinny, 11 Sep 2026). */
  .offer-visual--screen{margin:28px 0 -1px;border-radius:8px 8px 0 0}
  .offer-visual--screen img{width:100%}
  .offer-visual--sites{margin:26px 0 0 -22px;height:236px}
  .offer-card h2,.offer-copy{max-width:none}
}

/* ---------- the platform: one laptop, four screens ---------- */
.platform{background:var(--paper)}
.platform-grid{display:grid;grid-template-columns:minmax(0,1.06fr) minmax(0,.94fr);gap:64px;align-items:center;padding-top:64px}
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--rule-2);margin-bottom:24px;overflow-x:auto;scrollbar-width:none}
.tabs::-webkit-scrollbar{display:none}
.tab{
  appearance:none;background:none;border:0;border-bottom:2px solid transparent;margin-bottom:-1px;
  padding:12px 15px;font-size:14px;font-weight:500;letter-spacing:-.01em;color:var(--ink-3);cursor:pointer;white-space:nowrap;
  transition:color .15s ease,border-color .15s ease;position:relative;
}
.tab:first-child{padding-left:0}
.tab:hover{color:var(--ink)}
.tab[aria-selected="true"]{color:var(--ink);border-bottom-color:var(--ink)}
.tab-panel[hidden]{display:none}
.tab-panel p{font-size:17px;line-height:1.6;letter-spacing:-.008em;color:var(--ink-2);max-width:42ch;margin-bottom:28px;text-wrap:pretty}
/* The phone stands in front of the laptop's right corner, dropped below its
   deck, so the two read as one set of devices rather than two pictures. */
.platform-screen{position:relative}
.platform-screen .phone-float{position:absolute;right:-6%;bottom:-8%;width:22%;z-index:2}
@media (max-width:1024px){
  .platform-grid{grid-template-columns:minmax(0,1fr);gap:44px}
  .platform-copy{order:-1}
  /* inside the column now, since there is no gutter to its right to hang into */
  .platform-screen{margin-bottom:40px}
  .platform-screen .phone-float{right:-1%;width:25%}
}

/* ---------- curriculum ---------- */
.curriculum{background:var(--paper-2);border-top:1px solid var(--rule)}

/* ---------- the co-pilot ---------- */
.copilot-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.8fr);gap:72px;align-items:center}
.copilot-copy .eyebrow{margin-bottom:22px}
.copilot-copy h2{max-width:20ch}
.copilot-copy .lede{margin:28px 0 0}
.copilot-copy .lede b{color:var(--ink);font-weight:500}
.copilot-card{position:relative;border-radius:18px;overflow:hidden;background:var(--white);border:1px solid var(--rule);box-shadow:0 40px 80px -44px rgba(0,0,0,.45);max-width:390px;margin:0 auto}
.copilot-note{display:flex;align-items:center;gap:9px;justify-content:center;margin-top:18px;font-size:13.5px;color:var(--ink-3)}
.copilot-note::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--accent);flex:none;animation:pulse 2.6s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.78)}}
@media (max-width:1024px){ .copilot-grid{grid-template-columns:minmax(0,1fr);gap:48px} .copilot-copy h2{max-width:none} }

/* the claims band */
.claims-band{border-top:1px solid var(--rule);background:var(--paper);padding:74px 0}

/* ---------- pricing ---------- */
.pricing{background:var(--paper-2);border-top:1px solid var(--rule)}
.pricing .sol-head{padding-bottom:14px}

/* the fold: still a timed entrance, because nothing has scrolled yet */
.hero-copy > *{animation:rise 1s cubic-bezier(.2,.7,.2,1) both}
.hero-copy .eyebrow{animation-delay:.06s}
.hero-copy h1{animation-delay:.14s}
.hero-copy .lede{animation-delay:.3s}
.hero-copy .hero-cta{animation-delay:.44s}
.hero-copy .hero-note{animation-delay:.56s}
.hero-media{animation:photoIn 1.5s .1s cubic-bezier(.2,.7,.2,1) both}
.hero-rail span,.hero-facts div{animation:rise .9s .7s cubic-bezier(.2,.7,.2,1) both}
.hero-facts div:nth-child(2){animation-delay:.78s}
.hero-facts div:nth-child(3){animation-delay:.86s}

/* ---- pointer feedback ---- */
.offer-card{transition:background .4s ease}
.offer-visual--screen img{transition:transform .7s cubic-bezier(.2,.7,.2,1)}
.offer-ink:hover .offer-visual--screen img{transform:translateY(-10px) scale(1.015)}
.offer-paper:hover .site{box-shadow:0 -18px 46px -18px rgba(0,0,0,.5)}
.tab{position:relative}
.tab::after{content:"";position:absolute;left:0;bottom:-2px;height:2px;width:0;background:var(--accent)}
.tabs.is-auto .tab[aria-selected="true"]::after{animation:tabTimer 4.6s linear both}
.tab-panel p{animation:rise .5s cubic-bezier(.2,.7,.2,1) both}

/* ---- the JS fallback's two compound reveals ---- */
.frame{perspective:1600px}
[data-reveal].platform-screen,[data-reveal].copilot-demo{opacity:1;transform:none}
.platform-screen .frame{opacity:0;transform:rotateX(11deg) translateY(38px);transform-origin:50% 100%;transition:transform 1.2s cubic-bezier(.2,.7,.2,1),opacity 1s ease}
.platform-screen.is-in .frame{opacity:1;transform:none}
.platform-screen .phone{opacity:0;transform:translateY(40px);transition:opacity .9s ease .25s,transform 1.1s cubic-bezier(.2,.7,.2,1) .25s}
.platform-screen.is-in .phone{opacity:1;transform:none}
.copilot-demo .copilot-card{opacity:0;transform:translateY(26px);transition:opacity .8s ease,transform 1s cubic-bezier(.2,.7,.2,1)}
.copilot-demo.is-in .copilot-card{opacity:1;transform:none}

/* ---- scroll-driven: the same reveals, tied to the scroll instead of a timer ----
 * `animation-range` is quoted in view-progress units: `entry 25%` is the moment
 * the element is a quarter of the way into the viewport, `cover 60%` is 60% of
 * the way through crossing it.
 *
 * These ranges start LATER than a conventional reveal and finish before the
 * middle of the crossing. The first pass began at `entry 6%` and was done by
 * `cover 34%`, which meant a block had settled while it was still near the
 * bottom of the window and nothing was moving by the time you read it. Pushing
 * the end past the middle instead left content half-faded while it was already
 * legible, which is worse. So the reveals are late and quick, and the constant
 * motion comes from the DRIFT layer below, which never finishes at all.
 *
 * Anything marked DRIFT runs across the element's entire crossing
 * (`cover 0% cover 100%`), so it never finishes while it is on screen.
 */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .offer-card[data-reveal-delay="1"]{animation-range:entry 24% cover 50%}
    .offer-card[data-reveal-delay="2"]{animation-range:entry 30% cover 56%}


    /* The drift and the reveal go on DIFFERENT elements on purpose. Stacked on
       one, they both animate `transform` and the later one simply replaces the
       earlier: the measured result was a card that moved into place and then sat
       perfectly still for the rest of its crossing. Outer element drifts for the
       whole crossing, inner element reveals once. */
    .platform-screen{animation:driftUp linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    .platform-screen .frame,.platform-screen.is-in .frame{
      opacity:1;transition:none;
      animation:tiltIn linear both;animation-timeline:view();animation-range:entry 12% cover 42%;
    }
    /* The phone runs faster than the laptop it stands in front of, so the pair
       has depth as it crosses: wrapper drifts, the phone itself arrives late. */
    .platform-screen .phone-float{animation:phoneDrift linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    .platform-screen .phone,.platform-screen.is-in .phone{
      opacity:1;transition:none;
      animation:liftIn linear both;animation-timeline:view();animation-range:entry 20% cover 50%;
    }
    .copilot-demo{animation:driftUp linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    .copilot-demo .copilot-card,.copilot-demo.is-in .copilot-card{
      opacity:1;transition:none;
      animation:liftIn linear both;animation-timeline:view();animation-range:entry 16% cover 46%;
    }



    /* DRIFT: the two screenshots move against the page for their whole crossing */
    .offer-visual--screen img{animation:driftY linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    .offer-ink:hover .offer-visual--screen img{animation-play-state:paused}
    .offer-visual--sites{animation:driftUpSmall linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    /* the photograph closes in as the fold leaves: a ken burns whose clock is
       the scroll, so it only ever moves while the reader is moving */
    .hero-media img{animation:heroPush linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
    /* The fold leaves before the page does: the type lifts and fades from the
       first flick while the photograph lags behind it, so the parallax is
       between the two rather than against the page.
       `scroll(root)` and not `view()`: a view timeline measures a subject
       crossing the viewport, and the hero starts already filling it, so every
       view range is degenerate here (`exit` begins and ends at the same scroll
       offset). The document's own scroll offset is the honest clock for a fold. */
    .hero-copy{animation:foldOut linear both;animation-timeline:scroll(root);animation-range:0 64vh}
    .hero-rail{animation:railOut linear both;animation-timeline:scroll(root);animation-range:0 42vh}
    /* On a desktop the claim row is IN the fold, so it keeps its timed entrance
       and leaves with the fold, a beat behind the copy. A view() reveal here
       was measured from the moment the row entered, which is page load: the
       first screen showed it at 0.1 to 0.2 opacity and it only resolved once
       you scrolled. On a phone it sits just under the fold, where a scroll
       reveal is what it is for. Container and cells take one each, since two
       animations on the same `transform` replace each other. */
    .hero-facts{animation:foldOut linear both;animation-timeline:scroll(root);animation-range:6vh 70vh}
    @media (max-width:1024px){
      .hero-facts{animation:none}
      .hero-facts > div{animation:stepIn linear both;animation-timeline:view();animation-range:entry 30% cover 52%}
      .hero-facts > div:nth-child(2){animation-range:entry 36% cover 58%}
      .hero-facts > div:nth-child(3){animation-range:entry 42% cover 64%}
    }
    /* the offer copy rides a little slower than the card it sits in */
    .offer-copy{animation:driftUpSmall linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
  }
}
@media (prefers-reduced-motion:reduce){
  .hero-copy > *,.hero-media,.hero-rail span,.hero-facts div,.tab-panel p,
  .strip-track,.copilot-note::before{animation:none}
  .platform-screen .frame,.platform-screen .phone{opacity:1;transform:none;transition:none}
  .copilot-demo .copilot-card{opacity:1;transform:none;transition:none}
  .offer-visual--screen img,.site{transition:none}
  .btn:active{transform:none}
}
