/* latin-ext */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: optional;
  src: url(/fonts/pxiTypc9vsFDm051Uf6KVwgkfoSxQ0GsQv8ToedPibnr0She1ZuWi3hKpA.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;
}
/* latin */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: optional;
  src: url(/fonts/pxiTypc9vsFDm051Uf6KVwgkfoSxQ0GsQv8ToedPibnr0SZe1ZuWi3g.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;
}
/* latin-ext */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: optional;
  src: url(/fonts/pxiTypc9vsFDm051Uf6KVwgkfoSxQ0GsQv8ToedPibnr0She1ZuWi3hKpA.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;
}
/* latin */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: optional;
  src: url(/fonts/pxiTypc9vsFDm051Uf6KVwgkfoSxQ0GsQv8ToedPibnr0SZe1ZuWi3g.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;
}
/* latin-ext */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: optional;
  src: url(/fonts/pxiTypc9vsFDm051Uf6KVwgkfoSxQ0GsQv8ToedPibnr0She1ZuWi3hKpA.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;
}
/* latin */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: optional;
  src: url(/fonts/pxiTypc9vsFDm051Uf6KVwgkfoSxQ0GsQv8ToedPibnr0SZe1ZuWi3g.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;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url(/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7W0Q5n-wU.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url(/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7W0Q5n-wU.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url(/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7W0Q5n-wU.woff2) format('woff2');
  unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url(/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7W0Q5n-wU.woff2) format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url(/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7W0Q5n-wU.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;
}
/* latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url(/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7W0Q5n-wU.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;
}
/* latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url(/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7W0Q5nw.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;
}
/* thai */
@font-face {
  font-family: 'Prompt';
  font-style: normal;
  font-weight: 500;
  font-display: optional;
  src: url(/fonts/-W_8XJnvUD7dzB2Ck_kIfWMuUZctdhow.woff2) format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
/* vietnamese */
@font-face {
  font-family: 'Prompt';
  font-style: normal;
  font-weight: 500;
  font-display: optional;
  src: url(/fonts/-W_8XJnvUD7dzB2Ck_kIZmMuUZctdhow.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;
}
/* latin-ext */
@font-face {
  font-family: 'Prompt';
  font-style: normal;
  font-weight: 500;
  font-display: optional;
  src: url(/fonts/-W_8XJnvUD7dzB2Ck_kIZ2MuUZctdhow.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;
}
/* latin */
@font-face {
  font-family: 'Prompt';
  font-style: normal;
  font-weight: 500;
  font-display: optional;
  src: url(/fonts/-W_8XJnvUD7dzB2Ck_kIaWMuUZctdg.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;
}

/* ================= page ================= */

  :root {
    /* The page reads light now — a warm off-white that echoes the Golden Gate
       wallpaper's sand, never a clinical pure white. Text inverts to near-black;
       the one thing that stays dark is the Mac on stage, because the product's
       whole trick is a black panel that fuses into the notch. */
    --bg: #ffffff;                /* pure white */
    /* Ink scale — a soft near-black headline down to a readable grey. Softened
       from the old hard #141 ramp toward Heptabase's #2E2E2E, so the page reads
       calm and considered rather than high-contrast and loud. */
    --ink1: rgba(38,38,42,1);     /* headline / primary text ≈ #26262A */
    --ink2: rgba(38,38,42,.82);   /* secondary */
    --ink3: rgba(38,38,42,.66);   /* lead paragraphs — deepened for legibility */
    --ink4: rgba(48,48,56,.46);   /* fine print */
    /* Type system, borrowed from the sites that read "expensive": a geometric
       medium-weight sans for headlines (Instrument Sans) and a workhorse UI sans
       for everything else (Inter). System stacks trail as the fallback. */
    --display: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
    /* The wordmark keeps its own voice — Prompt, the one place a little
       character earns its keep: the name of the thing. */
    --brand: "Prompt", -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
    --sf: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
    --mono: ui-monospace, "SF Mono", Menlo, monospace;
    /* SwiftUI .spring(response: 0.42, dampingFraction: 0.72) ≈ slight overshoot */
    --spring: cubic-bezier(.32, 1.08, .35, 1);
  }

  * { margin: 0; padding: 0; box-sizing: border-box; }
  html { scroll-behavior: smooth; }

  body {
    /* Typeless backdrops its illustrations in a soft wash — a cool blue-grey
       field with a warm peach bleed, never a flat fill. We carry the same wash
       across the whole page, sitting under the warm off-white so the floating
       cards below have that same dreamy ground to lift off of. */
    background:
      radial-gradient(120% 80% at 82% 4%, rgba(120,150,210,.03), transparent 46%),
      radial-gradient(120% 70% at 10% 30%, rgba(255,180,130,.02), transparent 50%),
      radial-gradient(140% 90% at 50% 120%, rgba(120,150,210,.025), transparent 55%),
      var(--bg);
    background-attachment: fixed;
    font-family: var(--sf);
    -webkit-font-smoothing: antialiased;
  }

  /* Screen one is its own 100svh stage: the Mac's bottom edge is cropped by
     the hero's fold, exactly as it was when the page ended here — and now the
     story continues underneath. */
  .hero {
    position: relative;
    height: 100svh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  ::selection { background: rgba(20,20,24,.16); color: #1a1a1f; }

  /* Dim light source at the top of the page — the wallpaper's own light
     bleeding past the screen edge: warm from the left, cool from the right.
     On the light page these read as gentle tints, not glows. */
  .glow {
    position: fixed; inset: 0; pointer-events: none; z-index: 0;
    background:
      radial-gradient(46% 36% at 34% -8%, rgba(255,120,60,.03), transparent 70%),
      radial-gradient(52% 40% at 68% -8%, rgba(70,110,255,.03), transparent 70%);
  }

  /* ================= header ================= */

  header {
    position: relative; z-index: 10;
    flex: none;
    /* the headline sits a touch lower than a poster would put it — the old
       12svh top read visually high once the badges left the header */
    padding: clamp(104px, 15svh, 152px) 24px 6.5svh;
    text-align: center;
  }

  /* The corner wordmark is retired — the flat top bar now carries the brand at
     the top-left, so this would only collide with it. Kept in the markup (it
     drove nothing else) but hidden. */
  .brand {
    position: absolute; top: 24px; left: 32px; z-index: 20;
    display: none;
    align-items: center; gap: 12px;
    font-family: var(--brand); font-weight: 500;
    font-variation-settings: 'opsz' 14;
    font-size: 22px; letter-spacing: 0;
    color: var(--ink1);
    animation: fadein .9s ease .05s both;
  }

  /* The badge row — Product-Hunt-style laurels seated right under the Mac:
     an icon on the left, a tiny uppercase eyebrow over a semibold claim.
     White cards with a crisp hairline, like the embeddable PH badge — but
     carrying our own claims, no fake "featured on" laurels. */
  .badgeline {
    position: relative; z-index: 10;
    display: flex; align-items: stretch; justify-content: center;
    flex-wrap: wrap; gap: 14px;
    margin: clamp(30px, 5svh, 48px) 24px 0;
  }
  .ph-badge {
    display: inline-flex; align-items: center; gap: 12px;
    min-height: 54px; padding: 8px 20px 8px 15px;
    border-radius: 12px;
    background: #fff;
    border: 1px solid rgba(0,0,0,.09);
    box-shadow: 0 1px 2px rgba(15,15,18,.04);
    text-decoration: none; cursor: default;
  }
  /* Hover only on the real link (GitHub); the material claim stays static. */
  a.ph-badge { transition: border-color .2s ease, box-shadow .2s ease; }
  a.ph-badge:hover { border-color: rgba(0,0,0,.18); box-shadow: 0 2px 8px rgba(15,15,18,.07); }
  .ph-badge > svg { flex: none; color: rgba(38,38,42,.88); }
  .phb-text { display: flex; flex-direction: column; gap: 2px; text-align: left; }
  .phb-eyebrow {
    font-family: var(--sf); font-size: 9.5px; font-weight: 600;
    letter-spacing: .14em; text-transform: uppercase;
    color: rgba(38,38,42,.5);
    white-space: nowrap;
  }
  .phb-main {
    font-family: var(--display); font-size: 16.5px; font-weight: 600;
    letter-spacing: -.01em; line-height: 1.2;
    color: var(--ink1);
    white-space: nowrap;
  }

  @media (max-width: 600px) {
    .ph-badge { min-height: 50px; padding: 7px 16px 7px 13px; }
    .phb-main { font-size: 15px; }
  }
  .brand img {
    width: 34px; height: 34px;
    filter: drop-shadow(0 2px 8px rgba(0,0,0,.18));
  }

  /* The headline is the manifesto and the focal point of the screen: the
     dismissal in grey, the verdict in light, at display size. The demo below
     carries all the literal explaining. */
  h1 {
    margin: 0 auto;
    font-family: var(--display); font-weight: 500;
    /* Heptabase's hero register: smaller than a poster headline, with a
       relaxed 1.2 line-height and a touch more negative tracking — reads
       calm and editorial rather than loud. */
    font-size: clamp(38px, 4.2vw, 54px);
    line-height: 1.2; letter-spacing: -.03em;
    color: var(--ink1);
    animation: up .8s var(--spring) .15s both;
  }
  h1 .dim { color: rgba(38,38,42,.42); }

  /* One quiet line under the headline carrying the two selling points —
     the speed and the craft. Sits in muted ink so the headline stays the
     focal point; the two claims lift in near-full ink. */
  .sub {
    margin: 20px auto 0;
    max-width: 52ch;
    font-family: var(--sf);
    font-size: clamp(16px, 1.6vw, 18px);
    line-height: 1.55; letter-spacing: -.006em;
    color: var(--ink3);
    animation: up .8s var(--spring) .25s both;
  }
  /* Emphasis in the sub-line stays quiet: no heavy bold — the same weight,
     just lifted in colour so the two claims read as accented, not shouted. */
  .sub strong { font-weight: 500; color: var(--ink1); }

  /* The install capsule shares the island's material: glass refracting the
     page light, a gradient hairline rim, a top-lit body — and on copy, one
     specular sweep across the surface. */
  /* the two install paths sit side by side: the canonical curl command and a
     plain-English request you hand to a coding agent. They wrap to two rows on
     narrow widths so neither capsule overruns the screen. */
  .install-row {
    margin-top: 44px;
    display: flex; flex-wrap: wrap; align-items: stretch;
    justify-content: center; gap: 12px;
  }
  /* ONE control, not two: the button and the command are the same action, so
     they share one body. A solid ink segment carries the verb (Heptabase's
     button register — 16px/600, 10px radius, ring + tight shadow); the literal
     command rides along as its lighter half. Click anywhere — it copies. */
  .install-combo {
    position: relative;
    display: inline-flex; align-items: stretch;
    padding: 0; border: 0; cursor: pointer;
    border-radius: 10px; overflow: hidden;
    max-width: calc(100vw - 32px);
    background-color: rgba(255,255,255,.92);
    box-shadow: 0 0 0 1px rgba(15,15,18,.12), 0 2px 4px rgba(15,15,18,.08);
    transition: box-shadow .2s ease, transform .35s var(--spring);
    animation: fadein .9s ease .3s both;
  }
  .install-combo:hover { box-shadow: 0 0 0 1px rgba(15,15,18,.22), 0 2px 4px rgba(15,15,18,.10); }
  .install-combo:active { transform: scale(.98); transition-duration: .12s; }
  .install-combo:focus-visible, .tb-cta:focus-visible {
    outline: 2px solid rgba(80,110,180,.6); outline-offset: 2px;
  }
  .combo-label {
    display: grid; place-items: center;
    padding: 14px 22px;
    background-color: rgba(38,38,42,.97);
    font-family: var(--sf); font-size: 16px; font-weight: 600; letter-spacing: -.005em;
    color: #fff; white-space: nowrap;
  }
  .combo-label .f {
    grid-area: 1 / 1;
    transition: opacity .25s ease, transform .45s var(--spring), filter .25s ease;
  }
  .combo-cmd {
    position: relative;
    display: flex; align-items: center; gap: 9px;
    padding: 0 9px 0 18px;
    font-family: var(--mono); font-size: clamp(11px, 3.1vw, 14px);
    color: rgba(38,38,42,.72); white-space: nowrap;
    transition: color .25s ease;
  }
  /* On copy the strip itself reports back: the command steps aside and the
     receipt takes its place, centred over the same space. The verb on the left
     never moves. */
  .cmd-line { display: flex; align-items: center; gap: 9px; }
  .cmd-done {
    position: absolute; left: 18px; right: 39px;
    text-align: center;
    font-family: var(--sf); font-size: 14px; font-weight: 600;
    letter-spacing: -.005em; color: rgba(38,38,42,.9);
  }
  .cmd-line, .cmd-done {
    transition: opacity .22s ease, transform .4s var(--spring), filter .22s ease;
  }
  .cmd-done { opacity: 0; transform: translateY(4px); filter: blur(3px); }
  .install-combo.copied .cmd-line { opacity: 0; transform: translateY(-4px); filter: blur(3px); }
  .install-combo.copied .cmd-done { opacity: 1; transform: none; filter: none; }
  /* The copy affordance at the far right of the command: the whole capsule is
     the click target, so this is a state indicator, not a nested button. The
     verb and the command never move — only this glyph turns into a tick. */
  .combo-copy {
    flex: none;
    display: grid; place-items: center;
    width: 30px; height: 30px;
    border-radius: 8px;
    color: rgba(38,38,42,.5);
    transition: background-color .2s ease, color .2s ease;
  }
  .install-combo:hover .combo-copy { background-color: rgba(40,40,48,.06); color: rgba(38,38,42,.8); }
  .combo-copy svg {
    grid-area: 1 / 1; display: block;
    transition: opacity .2s ease, transform .35s var(--spring);
  }
  .combo-copy .tick { opacity: 0; transform: scale(.7); color: #2f6a3a; }
  .install-combo.copied .combo-copy { background-color: rgba(60,150,80,.12); color: #2f6a3a; }
  .install-combo.copied .combo-copy .sheets { opacity: 0; transform: scale(.7); }
  .install-combo.copied .combo-copy .tick { opacity: 1; transform: none; }
  .install-combo:hover .combo-cmd { color: rgba(20,20,24,.9); }
  .combo-cmd .dollar { color: rgba(40,40,48,.40); }
  .combo-cmd .cmd-short { display: none; }
  /* one-shot specular sweep on copy, raked across the whole control */
  .install-combo::after {
    content: ""; position: absolute; top: -8px; bottom: -8px; left: -38%; width: 34%;
    pointer-events: none; opacity: 0;
    background: linear-gradient(105deg,
      transparent, rgba(255,255,255,.14) 42%, rgba(255,255,255,.26) 50%,
      rgba(255,255,255,.14) 58%, transparent);
    filter: blur(6px);
  }
  .install-combo.copied::after { animation: glint .7s ease-out; }
  @keyframes glint {
    from { opacity: 1; transform: translateX(0); }
    to   { opacity: 0; transform: translateX(430%); }
  }
  /* Trust row — the honest credibility signals a real product leads with.
     No vanity metrics: every item here is a verifiable fact about the app,
     set in small-caps SF so it reads as a spec strip, not marketing. */
  /* Entrances come into focus like something seen through the glass — an
     optical settle, not a slide. (No translate anywhere: Liquid Glass answers
     with light and focus, not position.) */
  @keyframes up {
    from { opacity: 0; filter: blur(12px); }
    to   { opacity: 1; filter: blur(0); }
  }

  /* ================= the Mac ================= */

  /* A real Mac display rising from the bottom edge: a thin aluminum edge
     line, a black glass bezel, then a true 16:10 panel. The notch fuses with
     the top bezel — black on black, exactly like the hardware. The lower
     part of the display sits below the fold; the page crops it. */
  .scene {
    position: relative; z-index: 10;
    flex: 1; min-height: 280px;
    display: flex; justify-content: center; align-items: flex-start;
    padding: 0 24px;
  }

  .mac {
    position: relative;
    width: min(100%, max(1060px, calc(96svh + 26px)));
    /* black glass bezel; its bottom edge lives below the fold */
    padding: 12px 12px 0;
    background: #000;
    border: 1px solid rgba(255,255,255,.13);
    border-bottom: none;
    border-radius: 22px 22px 0 0;
    /* the screen's own light spilling upward — warm on the left where the
       wallpaper burns orange, cool on the right where it runs blue */
    box-shadow:
      -60px -30px 110px -30px rgba(255,110,70,.10),
      60px -30px 110px -30px rgba(64,92,255,.14),
      0 -8px 40px -12px rgba(120,110,200,.10);
    animation: up .9s var(--spring) .25s both;
  }

  .screen {
    position: relative;
    overflow: hidden;
    border-radius: 10px 10px 0 0;
    aspect-ratio: 16 / 10;   /* real panel proportions */
    min-height: 60svh;       /* tall viewports never expose a raw cropped edge */
  }

  /* macOS 27 Golden Gate wallpaper (light) — graded down so it reads as set
     dressing: the glass is the subject, the wallpaper only supplies something
     for it to refract. */
  .wall {
    position: absolute; inset: 0;
    background: #c9c2bd url("golden-gate.jpg") center 38% / cover no-repeat;
    filter: brightness(.72) saturate(.92);
  }
  /* The living wallpaper: a Paper mesh-gradient canvas mounted inside .wall,
     drifting the still image's own dark-bronze/sand/blue family. It inherits
     the parent's grading filter and sits under .shade's evening light, so the
     stage lighting is untouched; the still image below is the fallback (and
     the page for reduced-motion / no-WebGL / offline). */
  .wall canvas {
    position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0; transition: opacity 2s ease;
  }
  .wall.live canvas { opacity: 1; }
  /* Evening light: darker at the top where the notch lives, vignetted at the
     edges, an untouched pool of light around centre stage. */
  .shade {
    position: absolute; inset: 0; pointer-events: none;
    background:
      linear-gradient(180deg, rgba(0,0,0,.42), rgba(0,0,0,.10) 130px, transparent 240px),
      radial-gradient(130% 100% at 50% 8%, transparent 38%, rgba(0,0,0,.38) 100%);
  }

  /* Menu bar — same 32px the hardware notch nests into. */
  .menubar {
    position: absolute; top: 0; left: 0; right: 0; height: 32px;
    z-index: 1;
    display: flex; align-items: center; justify-content: space-between;
    padding: 0 18px;
    font-size: 12px;
    text-shadow: 0 1px 8px rgba(0,0,0,.45);
  }
  .mb-left, .mb-right { display: flex; align-items: center; gap: 18px; }
  .mb-app { font-weight: 600; color: rgba(255,255,255,.92); }
  .mb-item { color: rgba(255,255,255,.68); }
  a.mb-item { text-decoration: none; transition: color .2s ease; }
  a.mb-item:hover { color: rgba(255,255,255,.95); }
  .mb-status { display: flex; align-items: center; gap: 14px; color: rgba(255,255,255,.68); }
  .mb-status svg { display: block; }
  @media (max-width: 640px) {
    .mb-menus, .mb-clock, .mb-status { display: none; }
    .menubar { padding: 0 14px; font-size: 12px; }
    /* phones: a landscape 16:10 panel is too short to host the demo — keep
       the bezel but let the screen fill the remaining height instead. */
    .mac { height: 100%; padding: 9px 9px 0; border-radius: 18px 18px 0 0; }
    .screen { aspect-ratio: auto; height: 100%; min-height: 0; border-radius: 8px 8px 0 0; }
    /* small screens show far less wallpaper — ease the grading off so the
       glass still has light to refract */
    .wall { filter: brightness(.8) saturate(.95); }
    .shade { opacity: .55; }
  }

  .hint {
    position: absolute; top: 48px; left: 50%; transform: translateX(-50%);
    z-index: 1; width: max-content; max-width: 86%; text-align: center;
    font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
    color: rgba(255,255,255,.38);
    transition: opacity .4s ease;
    animation: fadein 1s ease 1.6s backwards;
  }
  @keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
  .screen.opened .hint { opacity: 0; }

  /* The hand that demonstrates "hover": a macOS arrow that glides up to the
     notch before each unattended run, then slips away. */
  .pointer {
    position: absolute; z-index: 6; pointer-events: none;
    left: 0; top: 0; opacity: 0;
    transition: transform 1.1s cubic-bezier(.45,.05,.2,1), opacity .45s ease;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,.5));
  }
  .pointer.show { opacity: 1; }

  /* ================= the island — 1:1 with GlassMaterial / NotchIsland ================= */

  /* Resting form: 192×32, bottom radius 9 (Tokens.notchWidth / notchTopHeight /
     notchRestRadius). Open: bottom radius 30, width per state, spring
     (response .42, damping .72); collapse is a snappier non-springy ease-out .3
     (NotchIsland's asymmetric animation). */
  .notch {
    position: absolute; top: 0; left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    width: 192px; height: 32px;
    border-radius: 0 0 9px 9px;
    overflow: hidden;
    cursor: default;
    transition:
      width .6s var(--spring),
      height .6s var(--spring),
      border-radius .6s var(--spring),
      box-shadow .3s ease;
  }
  .notch.snap {
    transition: width .3s ease-out, height .3s ease-out, border-radius .3s ease-out;
  }

  .notch.open {
    border-radius: 0 0 30px 30px;
    /* tight, downward-only drop shadow (black .30, blur 9, y 5) */
    box-shadow: 0 5px 9px rgba(0,0,0,.30);
  }

  /* GlassMaterial, back to front, over a真 backdrop:
       blur+saturate  ≈ .glassEffect(.clear) refracting the wallpaper
       veil           = solid #000 through the 32pt camera zone (blackCap),
                        a ~60pt melt band easing to smoked glass (.62),
                        clearing toward the bottom edge (.34)
       sheen          = white .05 just under the black lip, fading over 40pt
     The px stops are anchored to the top, so the closed 32px form shows only
     the solid-black segment — same material in both states, like the app. */
  .glassbg {
    position: absolute; inset: 0; pointer-events: none;
    border-radius: inherit; /* keep the blur inside the rounded form (Safari) */
    -webkit-backdrop-filter: blur(26px) saturate(1.6);
            backdrop-filter: blur(26px) saturate(1.6);
    /* sheen ramps up softly from under the cap (no 1px lip line); the veil's
       extra stops approximate the app's blackCap fading out over ~46pt on top
       of the smooth glassTop→glassBottom gradient, so the melt eases out
       instead of breaking at the 32px boundary. */
    background:
      linear-gradient(180deg, transparent 32px, rgba(255,255,255,.05) 48px, transparent 78px),
      linear-gradient(180deg, #000 0, #000 32px, rgba(0,0,0,.88) 48px,
        rgba(0,0,0,.70) 70px, rgba(0,0,0,.62) 92px, rgba(0,0,0,.34) 100%);
  }

  /* Diagonal specular gloss raking the lower body (white ≤.10, −18°, blurred) */
  .gloss {
    position: absolute; inset: -40% -40%; pointer-events: none;
    opacity: 0; transition: opacity .5s ease .1s;
    background: linear-gradient(135deg,
      transparent 42%, rgba(255,255,255,.06) 50%, rgba(255,255,255,.10) 56%, transparent 66%);
    transform: rotate(-18deg) translate(6%, 14%);
    filter: blur(14px);
  }
  .notch.open .gloss { opacity: 1; }

  /* One-shot light sweep on expand (easeOut .7s, band h≈110, blur 6, peak .18) */
  .shimmer {
    position: absolute; left: 0; right: 0; top: -110px; height: 110px;
    pointer-events: none;
    background: linear-gradient(180deg,
      transparent 0%, rgba(255,255,255,.10) 45%, rgba(255,255,255,.18) 50%,
      rgba(255,255,255,.10) 55%, transparent 100%);
    filter: blur(6px);
    opacity: 0;
  }
  .shimmer.run { animation: sweep .7s ease-out forwards; }
  @keyframes sweep {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(var(--sweep, 340px)); }
  }

  /* IslandRim: crisp outer hairline (0→.08→.26 top-to-bottom) + soft inner
     glow at the bottom edge. No rim at the top — it fuses with the bezel. */
  .rim {
    position: absolute; inset: 0; pointer-events: none;
    border-radius: 0 0 9px 9px;
    transition: border-radius .6s var(--spring);
  }
  .notch.open .rim { border-radius: 0 0 30px 30px; }
  .notch.snap .rim { transition: border-radius .3s ease-out; }
  /* Hairline with a whisper of dispersion — cool where it catches the sky,
     warm where it catches the dunes. Real glass splits light; so does this. */
  .rim::before {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    padding: 1px;
    background: linear-gradient(180deg,
      rgba(190,210,255,0) 0%, rgba(190,210,255,.09) 50%, rgba(255,234,210,.27) 100%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
  }
  .rim::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    box-shadow: inset 0 -10px 14px -10px rgba(255,255,255,.13);
  }

  /* Camera dot — 7pt, radial highlight off-center, resting only */
  .cam {
    position: absolute; top: 12px; left: 50%; margin-left: -3.5px;
    width: 7px; height: 7px; border-radius: 50%;
    background: radial-gradient(circle at 35% 30%, #2c2c2c, #050505 80%);
    transition: opacity .2s ease;
  }
  .notch.open .cam { opacity: 0; }

  /* ================= panel content — 1:1 with NotchBody ================= */

  /* Body padding: 20 horizontal, 15 top, 22 bottom, below the 32pt black zone */
  .content {
    position: absolute; top: 32px; left: 0;
    padding: 15px 20px 22px;
    opacity: 0; transition: opacity .18s ease;
  }
  .notch.open .content { opacity: 1; transition: opacity .3s ease .15s; }

  .state { display: none; }
  .state.on { display: block; }
  .state.enter { animation: module .45s var(--spring) both; }
  @keyframes module {
    from { opacity: 0; transform: translateY(-6px) scale(.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
  }

  /* Idle: a 48pt row, 16.5pt SF regular, no button — the inline ghost hint
     ("— Ask" / "— Note" / "— Remind") trails the caret instead. */
  .input-row {
    position: relative;
    height: 48px;
    display: flex; align-items: center;
    font-size: 16.5px;
    white-space: nowrap; overflow: hidden;
    cursor: text;
  }
  /* The demo is not a movie: a real input sits over the row, so a click puts
     the visitor in the driver's seat. Invisible — the fake caret carries on. */
  .real-input {
    position: absolute; inset: 0; z-index: 2;
    width: 100%; opacity: 0;
    border: 0; outline: 0; background: none;
    font: inherit; color: transparent; caret-color: transparent;
    cursor: text;
  }
  .ph {
    position: absolute; left: 0;
    color: rgba(255,255,255,.38);             /* Tokens.placeholder */
    transition: opacity .15s ease;
  }
  /* Vanishes the instant the first character lands (TextField behavior);
     only the return fade is soft. */
  .ph.off { opacity: 0; transition: none; }
  .typed { color: rgba(255,255,255,.96); }    /* Tokens.text1 */
  .caret {
    width: 1px; height: 19px; margin-left: 1px;
    background: rgba(255,255,255,.9);
    flex: none;
    animation: blink 1.1s steps(1) infinite;
  }
  @keyframes blink { 50% { opacity: 0; } }

  /* Ghost hint: same size as the body text, placeholder grey, materialize
     in/out (blur 4 + fade), quiet in-place crossfade on word swap. */
  .ghost {
    margin-left: 8px;
    color: rgba(255,255,255,.38);
    opacity: 0; filter: blur(4px);
    transition: opacity .3s ease, filter .3s ease;
  }
  .ghost.show { opacity: 1; filter: blur(0); }
  .ghost.swap { transition: opacity .125s ease, filter .3s ease; opacity: 0; }

  /* Note-save feedback: one quiet 12pt line, text4 grey, no icon */
  .feedback {
    padding-top: 8px;
    font-size: 12px;
    color: rgba(255,255,255,.40);             /* Tokens.text4 */
    display: none;
  }
  .feedback.on { display: block; animation: module .45s var(--spring) both; }

  /* Load / result: back chevron header, 18pt gap, then the thread */
  .chev {
    width: 26px; height: 26px;
    display: flex; align-items: center; justify-content: flex-start;
    color: rgba(255,255,255,.74);             /* Tokens.text2 */
    cursor: pointer;
  }
  .thread-gap { height: 18px; }
  .turns { display: flex; flex-direction: column; gap: 16px; }

  .turn-user { display: flex; align-items: baseline; gap: 8px; }
  .turn-user .you {
    width: 30px; flex: none;
    font-size: 11px; font-weight: 600;
    color: rgba(255,255,255,.40);             /* Tokens.text4 */
  }
  .turn-user .q {
    font-size: 14.5px; font-weight: 500;
    color: rgba(255,255,255,.74);             /* Tokens.text2 */
  }
  .turn-ai {
    font-size: 15px; line-height: 1.5;
    color: rgba(255,255,255,.96);             /* Tokens.text1 */
  }

  /* ThinkingDots: 3 × 6pt, spacing 7, scale .82→1, opacity .22→.95, y 0→−2,
     .62s ease-in-out alternating, staggered .16s */
  .dots { display: flex; gap: 7px; min-height: 22px; align-items: center; padding: 5px 2px; }
  .dots i {
    width: 6px; height: 6px; border-radius: 50%;
    background: rgba(255,255,255,.74);
    animation: think .62s ease-in-out infinite alternate;
  }
  .dots i:nth-child(2) { animation-delay: .16s; }
  .dots i:nth-child(3) { animation-delay: .32s; }
  @keyframes think {
    from { transform: scale(.82) translateY(0);    opacity: .22; }
    to   { transform: scale(1)   translateY(-2px); opacity: .95; }
  }

  /* ================= below the fold ================= */

  /* The lower page borrows the hero's grammar — one serif line, one
     plain-words paragraph — then shows the evidence. No labels above the
     headlines: the headline is the label. */
  /* Heptabase's section grammar: a LEFT-aligned heading capped at 900px,
     sitting directly on a wide visual below — text narrow, visuals wide.
     Section rhythm tightens to ~100px; the airy poster spacing is gone. */
  .sec {
    position: relative; z-index: 10;
    max-width: 1200px;
    margin: 0 auto;
    padding: clamp(88px, 12svh, 128px) 24px 0;
    text-align: left;
  }
  .sec h2 {
    font-family: var(--display); font-weight: 500;
    font-size: clamp(30px, 3.4vw, 42px);
    line-height: 1.3; letter-spacing: -.02em;
    max-width: 900px;
    color: var(--ink1);
  }
  .lead {
    max-width: 56ch;
    margin: 16px 0 0;
    font-size: 18px; line-height: 1.55;
    color: var(--ink3);
  }

  /* No scroll-reveal below the fold — Heptabase's pages are static and
     instantly readable; the hero demo is the only performer. (.r remains in
     the markup purely as a layout hook.) */

  /* --------- act one: the three verbs --------- */

  /* Heptabase frames every visual in a pastel-gradient container (their
     --new-gradient-image-container). Ours carries the wallpaper's own cast —
     warm sand into cool sky into a rose bleed — so the dark glass slices sit
     on a lit ground instead of floating raw on the white page. */
  .verb { display: flex; flex-direction: column; }
  /* Scroll-snap gallery (Apple-style): one big screenshot card at a time,
     the next card peeking in from the right, arrows to step. The track
     scrolls horizontally with snap points — no library, a dozen lines of JS
     for the buttons. Replaces the cramped three-column grid. */
  .gal { position: relative; margin: 40px 0 0; }
  .gal-track {
    display: flex; gap: 18px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* breathing room so card shadows aren't clipped by overflow */
    padding: 6px 2px 26px;
  }
  .gal-track::-webkit-scrollbar { display: none; }
  .gal-slide {
    flex: 0 0 min(820px, 88%);
    scroll-snap-align: start;
    margin: 0;
    padding: clamp(18px, 2.6vw, 34px);
    border-radius: 16px;
    background: linear-gradient(135deg, #f2ece3, #e7ecf4 55%, #f2e8ea);
    text-align: left;
    /* Cards stay equal height (flex's default stretch) so their grounds line up
       across the row. A shot shorter than the tallest one centres instead of
       hanging from the top, so the leftover space reads as padding rather than
       a hole under the shot. */
    display: flex; flex-direction: column; justify-content: center;
  }
  .gal-slide .vd { margin: 18px 4px 0 !important; min-height: 0 !important; font-size: 15.5px; line-height: 1.6; }
  .gal-nav { display: flex; gap: 10px; justify-content: flex-end; margin-top: 2px; }
  .gal-btn {
    width: 38px; height: 38px; border-radius: 50%;
    border: 1px solid rgba(0,0,0,.12);
    background: rgba(255,255,255,.7);
    color: rgba(38,38,42,.72);
    display: grid; place-items: center;
    cursor: pointer;
    transition: background .18s ease, color .18s ease, opacity .18s ease;
  }
  .gal-btn:hover { background: #1d1d20; color: #fff; border-color: #1d1d20; }
  .gal-btn:disabled { opacity: .32; pointer-events: none; }
  /* The showcase cards are REAL screenshots of the shipping app — captured on
     the actual notch over the Golden Gate wallpaper via the app's NOTCH_DEMO_*
     debug hooks, then cropped below the menu bar. Rounded and hairlined so
     they sit on the pastel ground the way the old CSS glass slices did. */
  .shot {
    display: block; width: 100%; height: auto;
    border-radius: 14px;
    box-shadow: 0 0 0 1px rgba(0,0,0,.07), 0 14px 34px -20px rgba(15,15,18,.35);
  }
  /* Two moments stacked in one card — typing, then the saved confirmation. */
  .shotstack { display: flex; flex-direction: column; gap: 12px; }

  /* Each card is a literal slice of the open notch panel — the SAME surface the
     live demo above draws (GlassMaterial / NotchBody), not a card invented for
     the page. Same smoked-glass veil clearing toward the foot, same white-.05
     sheen, same dispersion rim. Everything inside uses the app's own components:
     a "You" turn (NotchBody.turnView) for the typed line, the app's text scale
     (Tokens.text1/2/4) for the answer, and the app's quiet "Notes ↗" capture
     badge (NotchBody.historyList) — never colour-tile app icons the app lacks. */
  .card {
    position: relative; flex: 1;
    border-radius: 20px; overflow: hidden;
    padding: 20px 22px 22px;
    /* the trio reads as one tidy row — the Ask card (question + answer) sets
       the height, the two confirmation cards match it instead of stopping
       short at their single feedback line */
    min-height: 126px;
    -webkit-backdrop-filter: blur(26px) saturate(1.6);
            backdrop-filter: blur(26px) saturate(1.6);
    /* veil: the app's blackCap→glassBottom melt. On the light pastel ground the
       foot must stay dark enough to carry white text — it clears toward the
       bottom like the open panel, but never below ~.56 black. */
    background:
      linear-gradient(180deg, rgba(255,255,255,.05), transparent 26px),
      linear-gradient(180deg, rgba(0,0,0,.92) 0, rgba(0,0,0,.78) 30%,
        rgba(0,0,0,.68) 64%, rgba(0,0,0,.56) 100%);
  }
  /* IslandRim: dispersion hairline (cool top → warm foot, faint at the top so it
     reads as the same edge the island carries) + a soft inner glow at the foot. */
  .card::before {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    padding: 1px; pointer-events: none;
    background: linear-gradient(180deg,
      rgba(190,210,255,.10) 0%, rgba(190,210,255,.16) 50%, rgba(255,234,210,.30) 100%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor; mask-composite: exclude;
  }
  .card::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    pointer-events: none;
    box-shadow: inset 0 -10px 14px -10px rgba(255,255,255,.13);
  }

  /* Glass-glow follow: the cursor lights the surface like a finger dragging
     across frosted glass. A radial wash, parked at the pointer via --mx/--my
     (set per-element by script), faded in only while that surface is hovered.
     It rides as a real child layer (.glow-light) rather than a pseudo-element
     because every glass surface here already spends ::before/::after on its rim
     and sheen. JS only attaches it on hover-capable, motion-OK pointers, so
     touch + reduce-motion get the static glass untouched. */
  .glow-surface { position: relative; }
  .glow-light {
    position: absolute; inset: 0; border-radius: inherit;
    pointer-events: none; z-index: 0;
    opacity: 0; transition: opacity .35s ease;
    background: radial-gradient(180px circle at var(--mx, 50%) var(--my, 50%),
      var(--glow-tint, rgba(255,255,255,.16)), transparent 60%);
  }
  .glow-surface.lit .glow-light { opacity: 1; }
  /* Dark cards (black veil) take a warm low-opacity wash so it reads as light
     grazing the surface, not a blown-out spot. */
  .card .glow-light { --glow-tint: rgba(255,243,225,.10); mix-blend-mode: screen; }
  /* The typed line, rendered as the app's "You" turn (NotchBody.turnView): a
     small semibold .40 "You" label in a fixed column, then the question at
     14.5/medium in text2 (.74) — no boxed input chip, the app doesn't draw one.
     The "— Ask"/"— Note"/"— Remind" connector trails as the app's inline send
     hint: placeholder grey (.40), regular weight, same size as the line. */
  .vrow {
    position: relative;
    display: flex; align-items: baseline; gap: 8px;
    overflow: hidden;
  }
  .vyou {
    flex: none; width: 30px;
    font-size: 11px; font-weight: 600; letter-spacing: .3px;
    color: rgba(255,255,255,.40);
  }
  .vt {
    flex: 1; min-width: 0;
    font-size: 14.5px; font-weight: 500; letter-spacing: -.1px;
    color: rgba(255,255,255,.74);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .vg { color: rgba(255,255,255,.40); font-weight: 400; letter-spacing: 0; }
  /* The settled answer: plain body text in text1 (.96), 15px — the app renders
     an Ask answer as markdown at this size/colour, no card around it. Sits in the
     "You" column's text gutter (30 + 8) so it lines up under the question. */
  .vai {
    position: relative; margin: 12px 0 0; padding-left: 38px;
    font-size: 15px; line-height: 1.5; color: rgba(255,255,255,.96);
  }
  /* The capture confirmation, exactly as the app shows it: a quiet text4 (.40)
     line — "Added to Apple Notes" — with the destination's "↗" badge (the same
     SF arrow.up.right cue the Recent list uses). No colour, no app-icon tile. */
  .vfb {
    position: relative; margin: 14px 0 0; padding-left: 38px;
    display: flex; align-items: center; gap: 5px;
    font-size: 12px; letter-spacing: .2px; color: rgba(255,255,255,.40);
  }
  .vfb .varr { flex: none; opacity: .9; }
  .vfb .varr svg { display: block; }
  /* Reserve three lines for every caption so the three cards above them come
     out exactly equal — the grid stretches each column to the same height, and
     equal captions mean equal cards. */
  .verb .vd { margin: 16px 4px 0; min-height: 4.65em; font-size: 15px; line-height: 1.55; color: rgba(38,38,42,.62); }

  /* --------- act one and a half: what asking can pull in --------- */

  /* The powers behind Ask (live web, vision, the clipboard, exact math) ride
     the same scroll-snap gallery as the verbs, on their own cooler pastel
     ground so the two strips stay distinguishable. */
  .gal-slide.cool { background: linear-gradient(135deg, #e9edf3, #f2ece3 55%, #edf0e9); }
  /* The agent strip takes a whisper of the Agent pill's violet — a third
     ground, so verbs / powers / agent each read as their own act. */
  .gal-slide.violet { background: linear-gradient(135deg, #edeaf3, #f2ece3 55%, #e9edf3); }
  /* the sources row under a searched answer — the app's citation chips */
  .vsrc {
    margin: 12px 0 0; padding-left: 38px;
    display: flex; flex-wrap: wrap; gap: 6px;
  }
  .vsrc span {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 3px 10px; border-radius: 999px;
    font-size: 11px; letter-spacing: .1px;
    color: rgba(255,255,255,.56);
    border: 1px solid rgba(255,255,255,.14);
    white-space: nowrap;
  }
  /* the clipboard attachment chip — a copied screenshot riding into the ask */
  .vatt {
    display: inline-flex; align-items: center; gap: 6px;
    margin-bottom: 12px;
    padding: 4px 10px; border-radius: 7px;
    font-size: 11.5px; letter-spacing: .1px;
    color: rgba(255,255,255,.56);
    border: 1px solid rgba(255,255,255,.14);
    background: rgba(255,255,255,.05);
  }
  .vatt svg { display: block; opacity: .8; }
  /* the clipboard quote preview — the app leads with a quotation mark */
  .vq {
    font-size: 14.5px; font-weight: 500; letter-spacing: -.1px;
    color: rgba(255,255,255,.74);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* confirmation line variants that don't sit in the "You" text gutter */
  .vfb.flush { padding-left: 0; }

  /* --------- act two: the material, annotated --------- */

  .spec { position: relative; max-width: 1020px; margin: 48px auto 0; }
  /* the implied screen edge the panel hangs from */
  .spec-edge {
    height: 1px; max-width: 760px; margin: 0 auto;
    background: linear-gradient(90deg,
      transparent, rgba(40,40,48,.16) 18%, rgba(40,40,48,.16) 82%, transparent);
  }
  .spec-stage { position: relative; }
  /* the open panel itself — same material stack as the live demo above */
  .spec-panel {
    position: relative; z-index: 1;
    width: min(560px, 100%); margin: -1px auto 0;
    border-radius: 0 0 30px 30px; overflow: hidden;
    text-align: left;
    padding: 47px 20px 22px;   /* 32px black cap + the body's 15px */
    -webkit-backdrop-filter: blur(26px) saturate(1.6);
            backdrop-filter: blur(26px) saturate(1.6);
    background:
      linear-gradient(180deg, transparent 32px, rgba(255,255,255,.05) 48px, transparent 78px),
      linear-gradient(180deg, #000 0, #000 32px, rgba(0,0,0,.90) 48px,
        rgba(0,0,0,.76) 70px, rgba(0,0,0,.68) 92px, rgba(0,0,0,.54) 100%);
    box-shadow: 0 5px 9px rgba(0,0,0,.30);
  }
  .spec-panel::before {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    padding: 1px; pointer-events: none;
    background: linear-gradient(180deg,
      rgba(190,210,255,0) 0%, rgba(190,210,255,.09) 50%, rgba(255,234,210,.27) 100%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
  }
  .spec-panel::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    pointer-events: none;
    box-shadow: inset 0 -10px 14px -10px rgba(255,255,255,.13);
  }
  /* blueprint callouts */
  .ann {
    position: absolute; z-index: 2; width: 208px;
    text-align: left;
    font-size: 13px; line-height: 1.55; color: rgba(40,40,48,.56);
  }
  .ann b {
    display: block; margin-bottom: 6px;
    font-size: 12px; font-weight: 500; letter-spacing: .16em; text-transform: uppercase;
    color: rgba(28,28,34,.66);
  }
  .ann i { position: absolute; height: 1px; background: rgba(40,40,48,.20); }
  .ann i::after {
    content: ""; position: absolute; top: -2px;
    width: 5px; height: 5px; border-radius: 50%;
    background: rgba(40,40,48,.40);
  }
  .a1 { top: 2px; right: 4px; }
  .a1 i { left: -46px; top: 9px; width: 38px; }
  .a1 i::after { left: -5px; }
  .a2 { top: 118px; left: 4px; }
  .a2 i { right: -46px; top: 9px; width: 38px; }
  .a2 i::after { right: -5px; }

  /* --------- act three: your model, your key --------- */

  .chips {
    display: flex; flex-wrap: wrap; justify-content: flex-start; gap: 10px;
    margin: 36px 0 0;
  }
  /* Sixteen chips in one wrap read as a heap, so they're split by what you
     actually have to bring — one aggregator key, your own provider key, or no
     key at all. The group label is the badge line's eyebrow recipe verbatim
     (.phb-eyebrow), not a new type style. */
  .chipgroup { margin: 30px 0 0; }
  .chipgroup:first-child { margin-top: 36px; }
  .chipgroup .chips { margin: 10px 0 0; }
  .chipgroup-k {
    font-family: var(--sf); font-size: 9.5px; font-weight: 600;
    letter-spacing: .14em; text-transform: uppercase;
    color: rgba(38,38,42,.5);
  }
  /* provider chips — Heptabase's flat feature-box recipe: a warm light fill,
     a 4% hairline, an 8px radius. No shadow, no rim, nothing floating. */
  .chip {
    position: relative;
    padding: 10px 16px; border-radius: 8px;
    font-size: 14px; color: rgba(38,38,42,.78);
    background: #f4f3ef;
    border: 1px solid rgba(0,0,0,.04);
  }

  /* The essentials strip, straight from Heptabase's closing grid: light warm
     boxes packed tight (12px gutters), a 24px glyph, a 20px/450 title (Inter's
     variable mid-weight), a 16px muted body. Nothing floats, nothing glows. */
  .facts {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
    margin: 48px 0 0;
    text-align: left;
  }
  .fact {
    display: flex; flex-direction: column; gap: 12px;
    padding: 17px 17px 18px;
    background: #f4f3ef;
    border: 1px solid rgba(0,0,0,.04);
    border-radius: 8px;
  }
  .fact svg { display: block; color: rgba(38,38,42,.72); }
  .fact h3 {
    font-size: 20px; font-weight: 450; letter-spacing: -.012em;
    line-height: 1.4; color: var(--ink1);
  }
  .fact p { margin-top: 4px; font-size: 15.5px; line-height: 1.5; color: rgba(38,38,42,.55); }

  /* --------- closing --------- */

  /* The sign-off is the one centered section — Heptabase does the same with
     its closing "All the essentials" moment. */
  .cta { padding-bottom: clamp(96px, 13svh, 140px); text-align: center; }
  .cta h2 {
    font-family: var(--display);
    font-weight: 500;
    font-size: clamp(32px, 3.6vw, 46px);
    line-height: 1.2; letter-spacing: -.024em;
    margin-left: auto; margin-right: auto;
    color: var(--ink1);
  }
  /* capsule and source link stack vertically, centered — without this the two
     inline boxes flow side-by-side and collide at wide widths */
  .cta .r { display: flex; flex-direction: column; align-items: center; }
  .src {
    margin-top: 22px;
    font-size: 12px; color: rgba(40,40,48,.50); text-decoration: none;
    border-bottom: 1px solid rgba(40,40,48,.18); padding-bottom: 1px;
    transition: color .2s ease, border-color .2s ease;
  }
  .src:hover { color: rgba(20,20,24,.9); border-color: rgba(40,40,48,.5); }

  /* Footer — a taller, structured sitemap in the Heptabase mould: a branded
     column on the left, grouped link columns to the right, and a quiet legal
     line hairline-separated below. Same material vocabulary as the rest of the
     page (hairlines, brand font, ink levels, the glass language picker). */
  footer { position: relative; z-index: 10; }
  .ft {
    max-width: 1200px; margin: 0 auto;
    padding: 72px 24px 40px;
    display: grid;
    grid-template-columns: minmax(220px, 1fr) auto;
    align-items: start; gap: 48px 72px;
  }
  .ft-brand {
    display: flex; align-items: center; gap: 8px;
    font-family: var(--brand); font-weight: 500;
    font-variation-settings: 'opsz' 14;
    font-size: 17px; color: rgba(28,28,34,.72);
  }
  .ft-brand img { width: 20px; height: 20px; }
  .ft-lang { margin-top: 18px; }
  .ft-lang .lang { margin-left: 0; }

  /* The link columns: a header, then a stack of quiet links under it. */
  .ft-cols {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(120px, max-content);
    gap: 56px;
  }
  .ft-col { display: flex; flex-direction: column; gap: 14px; }
  .ft-col h4 {
    margin: 0 0 2px; font-family: var(--sf); font-weight: 600;
    font-size: 14px; letter-spacing: .01em; color: rgba(28,28,34,.78);
  }
  .ft-col a {
    font-family: var(--sf); font-size: 14px; text-decoration: none;
    color: rgba(40,40,48,.5); transition: color .2s ease;
  }
  .ft-col a:hover { color: rgba(20,20,24,.9); }

  /* The legal line: full-width, dimmest text on the page. */
  .ft-legal {
    max-width: 1200px; margin: 0 auto;
    padding: 22px 24px 34px;
  }
  .ft-copy {
    font-family: var(--sf); font-size: 13px; letter-spacing: .01em;
    color: rgba(40,40,48,.36);
  }

  @media (max-width: 720px) {
    .ft {
      grid-template-columns: 1fr;
      justify-items: start;
      gap: 36px 0; padding: 44px 24px 28px;
    }
    .ft-cols { gap: 44px; }
  }
  @media (max-width: 460px) {
    .ft-cols { grid-auto-flow: row; grid-auto-columns: auto; gap: 30px; }
  }

  /* Language picker — a small floating glass dropdown seated next to the
     wordmark, the same material vocabulary as the install capsule and nav.
     A real <select> drives it, so it scales to any number of languages. */
  .lang {
    position: relative; display: inline-flex; align-items: center; margin-left: 12px;
    border-radius: 8px;
    background: #f4f3ef;
    border: 1px solid rgba(0,0,0,.05);
    transition: border-color .2s ease;
  }
  .lang:hover { border-color: rgba(0,0,0,.12); }
  .lang-select {
    -webkit-appearance: none; appearance: none; border: 0; outline: 0; cursor: pointer;
    font-family: var(--sf); font-size: 13px; line-height: 1.5; letter-spacing: .01em;
    color: rgba(28,28,34,.78); background: transparent;
    padding: 4px 28px 4px 12px; border-radius: 8px;
    transition: color .2s ease;
  }
  .lang-select:hover { color: rgba(20,20,24,.92); }
  .lang-select:focus-visible { box-shadow: 0 0 0 2px rgba(80,110,180,.4); }
  .lang-caret {
    position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
    color: rgba(40,40,48,.5); pointer-events: none;
  }
  /* lighter, more generous line-height when the page is set in Chinese */
  html[lang^="zh"] .sub,
  html[lang^="zh"] .lead,
  html[lang^="zh"] .fact p,
  html[lang^="zh"] .verb .vd,
  html[lang^="zh"] .ann { line-height: 1.7; }

  /* --------- flat top bar, the Heptabase-style header --------------------
     A full-width bar pinned to the top: brand on the left, links centered,
     the install CTA on the right. It stays put the whole way down the page.
     At the very top it sits on the bare backdrop; the moment you scroll it
     grows a glass wash + a hairline so it reads cleanly over the content
     sliding under it. */
  .topbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 44;
    display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
    padding: 10px clamp(20px, 4vw, 40px);
    border-bottom: 1px solid transparent;
    transition: background-color .25s ease, border-color .25s ease;
  }
  /* Stuck state, the Heptabase way: the bar simply becomes the page — a solid
     fill and one hairline. No glass wash, no shadow, no blur. */
  .topbar.stuck {
    background-color: var(--bg);
    border-bottom-color: rgba(40,40,48,.08);
  }
  .tb-brand {
    justify-self: start;
    display: flex; align-items: center; gap: 10px;
    font-family: var(--brand); font-weight: 500;
    font-variation-settings: 'opsz' 14;
    font-size: 19px; color: var(--ink1); text-decoration: none;
  }
  .tb-brand img { width: 24px; height: 24px; }
  .tb-links {
    justify-self: center;
    display: flex; align-items: center; gap: clamp(2px, .8vw, 10px);
  }
  /* Nav links wear Heptabase's clickable-overlay: near-full ink at 15px, and
     hover answers with a soft rounded fill instead of a colour change. */
  .tb-link {
    font-family: var(--sf); font-size: 15px; color: rgba(38,38,42,.85);
    padding: 8px 12px; border-radius: 8px;
    text-decoration: none; transition: background-color .2s ease, color .2s ease;
  }
  .tb-link:hover { background-color: rgba(40,40,48,.06); color: var(--ink1); }
  /* "Star on GitHub" — a pill that swaps its GitHub mark for a golden star +
     sparkle on hover, and springs on press. The React source it mirrors is dark;
     here it's re-tuned for the light bar. */
  .tb-star {
    position: relative; display: inline-flex; align-items: center;
    height: 36px; padding: 0 18px; border-radius: 40px;
    font-family: var(--sf); font-size: 13px; font-weight: 500; letter-spacing: -.01em;
    color: rgba(38,38,42,.85); text-decoration: none;
    background-color: rgba(40,40,48,.04);
    border: 1px solid rgba(40,40,48,.10);
    transition: background-color .15s ease, transform .35s var(--spring);
  }
  .tb-star:hover { background-color: rgba(40,40,48,.07); transform: scale(1.02); }
  .tb-star:active { transform: scale(.96); }
  .tb-star-ico {
    position: relative; width: 16px; height: 16px; flex: none; margin-right: 9px;
  }
  .tb-star-ico .ic { position: absolute; transition: opacity .2s ease, transform .3s var(--spring); }
  .ic-gh   { inset: 0; opacity: 1; transform: translateY(0) scale(1); color: rgba(38,38,42,.9); }
  .ic-star { inset: 0; opacity: 0; transform: translateY(15px) scale(.8); color: #f5a623; }
  .ic-spark {
    top: -5px; right: -4px; opacity: 0; transform: scale(0) rotate(-45deg);
    color: #ffd766; transition: opacity .25s ease .05s, transform .3s var(--spring) .05s;
  }
  .tb-star:hover .ic-gh    { opacity: 0; transform: translateY(-15px) scale(.8); }
  .tb-star:hover .ic-star  { opacity: 1; transform: translateY(0) scale(1); }
  .tb-star:hover .ic-spark { opacity: 1; transform: scale(1) rotate(0); }
  .tb-actions { justify-self: end; display: flex; align-items: center; }
  /* The install CTA — Heptabase's "Get started" seat, filled by the one action
     Notchi actually has: copy the brew command. Solid ink pill, flips to a green
     confirmation on click (shares the bindCopy machinery). */
  /* One motion, not two: the labels stay stacked in the same cell and simply
     cross-fade, while the pill's own width animates between the two measured
     sizes (--tbw / --tbw2, set once by script). The copied face is absolutely
     positioned so it never contributes to the resting width — the pill keeps its
     normal size until you click it. */
  .tb-cta {
    position: relative; display: inline-grid; place-items: center;
    /* the single cell tracks the pill's own width, never the wider label —
       otherwise the auto track grows to the copied sentence and knocks the
       resting label off centre */
    grid-template-columns: minmax(0, 1fr);
    border: 0; cursor: pointer; overflow: hidden;
    padding: 10px 18px; border-radius: 10px;
    width: var(--tbw, auto);
    font-family: var(--sf); font-size: 14px; font-weight: 600; letter-spacing: 0;
    color: #fff; background-color: rgba(38,38,42,.97);
    box-shadow: 0 1px 2px rgba(40,40,48,.10);
    transition: width .4s var(--spring), opacity .2s ease, transform .35s var(--spring);
  }
  .tb-cta.copied { width: var(--tbw2, auto); }
  .tb-cta:hover { opacity: .88; }
  .tb-cta:active { transform: scale(.96); }
  .tb-cta .f {
    grid-area: 1 / 1; white-space: nowrap;
    transition: opacity .22s ease, transform .4s var(--spring), filter .22s ease;
  }
  /* Both faces stay in flow, in the same grid cell, so `place-items: center`
     centres them honestly — the pill's explicit width (not the labels) decides
     how wide it is, and the label that doesn't fit yet simply overflows the clip
     evenly on both sides while the width catches up. */
  .tb-cta .f2 { opacity: 0; transform: translateY(5px); filter: blur(3px); }
  .tb-cta.copied .f1 { opacity: 0; transform: translateY(-5px); filter: blur(3px); }
  .tb-cta.copied .f2 { opacity: 1; transform: none; filter: none; color: #dff5cf; }

  /* while the pill is wide, the links shift rather than break onto two lines */
  .tb-link, .tb-star-label { white-space: nowrap; }

  @media (max-width: 620px) {
    /* on narrow screens the centered links crowd the brand + CTA — drop them,
       keep the two anchors (brand, install) that carry the bar */
    .topbar { grid-template-columns: 1fr auto; padding: 14px 18px; }
    .tb-links { display: none; }
    .tb-brand { font-size: 17px; }
    .tb-brand img { width: 21px; height: 21px; }
  }

  @media (max-width: 880px) {
    /* the galleries keep working by touch — slides just get narrower */
    .gal-slide { flex-basis: 90%; }
    /* single column: cards hug their content again, captions release the
       three-line reservation */
    .card { min-height: 0; }
    .verb .vd { min-height: 0; }
    .facts { grid-template-columns: repeat(2, 1fr); }
    /* callouts stack under the exhibit instead of floating beside it */
    .ann { position: static; width: auto; max-width: 460px; margin: 22px auto 0; }
    .ann i { display: none; }
  }
  @media (max-width: 560px) {
    .facts { grid-template-columns: 1fr; }
  }

  @media (max-width: 640px) {
    /* the long command no longer fits on one line — show the compact form */
    .combo-cmd .cmd-long { display: none; }
    .combo-cmd .cmd-short { display: inline; }
    /* the combo stacks: the ink button on top, the command strip attached
       underneath, both filling the row */
    .install-row { flex-direction: column; align-items: stretch; gap: 10px; }
    .install-combo { flex-direction: column; align-items: stretch; }
    .combo-label { padding: 13px 20px; }
    .combo-cmd { justify-content: center; padding: 11px 16px; }
  }

  @media (max-width: 480px) {
    .brand { top: 16px; left: 16px; font-size: 21px; gap: 9px; }
    .brand img { width: 28px; height: 28px; }
    h1 { font-size: 38px; }
    h1 br { display: none; }
    .combo-cmd { gap: 8px; }
    .scene { padding: 0 12px; }
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation: none !important; transition: none !important; }
  }

  /* ============ panic mode ============
     Set by the crash-loop breaker in <head>: this boot follows one that never
     survived load. Everything decorative goes static so the page cannot be
     what kills the WebView twice. Content and layout stay untouched. */
  html.panic *, html.panic *::before, html.panic *::after {
    animation: none !important; transition: none !important;
  }
  html.panic .glow, html.panic .gloss, html.panic .shimmer { display: none !important; }

  /* pre-rendered mobile stand-in for the whole Mac stage (hidden on desktop) */
  .scene-still { display: none; }

  /* ============ mobile lite ============
     Touch devices keep the full layout but drop the compositor heavyweights.
     In-app WKWebViews (X, WeChat…) give a page a fraction of Safari's
     GPU/memory budget, and this page's fixed-attachment body gradient — which
     WebKit rasterises against the full (very tall) document — plus the stacked
     backdrop-filters were crash-looping the content process into a white
     screen: content painted once or twice, then died. The gradient veils each
     glass surface already draws carry the material read on their own. */
  @media (hover: none) and (pointer: coarse) {
    body { background-attachment: scroll; }
    /* system fonts only on touch: webfont availability varies per load (cold vs
       warm cache), and the X preview container reload-loops any page whose
       consecutive first paints differ. System stacks render identically on
       every load — the same determinism level as the stable t-min rung. */
    :root {
      --display: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
      --brand: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
      --sf: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
    }
    /* fully static on touch: no fixed floating header, no animation frames,
       no transition repaints, no smooth-scroll — nothing re-renders, ever.
       (The hero keeps its own in-flow brand row, so branding survives.) */
    .topbar { display: none; }
    *, *::before, *::after { animation: none !important; transition: none !important; }
    html { scroll-behavior: auto; }
    /* the closing glass band (canvas + giant wordmark + isolation layer) does
       not render on touch at all */
    .glassband { display: none; }
    /* below-fold sections stay UNRENDERED until scrolled near: shrinks the
       compositor/snapshot workload from the whole ~12000px page to one
       viewport — aimed at the X preview sheet's failing presentation pass */
    .sec, footer, .badgeline {
      content-visibility: auto;
      contain-intrinsic-size: auto 800px;
    }
    /* mask-composite ribbons (1px decorative borders) are a classic beta-
       WebKit paint-regression surface — a failed mask paints the whole layer
       white. Touch devices drop them. */
    .rim::before, .card::before, .spec-panel::before { display: none; }
    /* no viewport-height-derived layout inside the hero (the sheet resizes
       its WebView; svh re-derivation shifts the top viewport's layout) */
    .screen { min-height: 0; }
    .mac { box-shadow: none; }
    /* the ENTIRE Mac stage is one pre-rendered <img> on touch — the top
       viewport contains no compositing feature that can fail, full stop */
    .scene { display: none; }
    .scene-still {
      display: block; width: calc(100% - 24px); max-width: 640px;
      height: auto; margin: 0 auto;
    }
    .glassbg, .card, .spec-panel {
      -webkit-backdrop-filter: none;
              backdrop-filter: none;
    }
    .gloss, .shimmer { display: none; }
    /* the notch demo is inert on touch — a still, not a control */
    .notch { pointer-events: none; }
    .hint { display: none; }
    /* wallpaper: the CSS grade is BAKED into a 1200px mobile variant — a live
       filter on the 2560px original forces a ~35MB offscreen raster the moment
       the hero scrolls into view, which is exactly when the X preview WebView
       went white. No filter, small decode, same look. */
    .wall { background-image: url("golden-gate-m.jpg"); filter: none; }
    .glow { display: none; }
    /* entrances settle with opacity alone — animating filter:blur allocates
       an offscreen surface per element, exactly the budget we don't have */
    @keyframes up {
      from { opacity: 0; }
      to   { opacity: 1; }
    }
  }

#m-root { display: none; }
  @media (hover: none) and (pointer: coarse) {
    #m-root { display: block; }
    #d-root { display: none; }
  }
  #m-root {
    font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
    color: #26262a; background: #ffffff;
    line-height: 1.6;
  }
  #m-root .m-wrap { max-width: 640px; margin: 0 auto; padding: 0 22px; }
  #m-root header.m-top { padding: 22px 0 6px; font-weight: 700; font-size: 20px; }
  #m-root header.m-top img { width: 26px; height: 26px; vertical-align: -5px; margin-right: 8px; }
  #m-root .m-h1 { font-size: 34px; line-height: 1.22; font-weight: 700; letter-spacing: -.01em; padding-top: 42px; }
  #m-root .m-lede { color: #55555e; font-size: 17px; padding-top: 14px; }
  #m-root .m-install { margin-top: 26px; border: 1px solid #e3e3e8; border-radius: 14px; overflow: hidden; }
  #m-root .m-install .m-install-head { background: #1c1c21; color: #fff; text-align: center; font-weight: 600; padding: 12px; }
  #m-root .m-install code { display: block; text-align: center; padding: 12px; font-family: ui-monospace, Menlo, monospace; font-size: 13px; color: #44444c; }
  #m-root .m-shot { display: block; width: 100%; height: auto; border-radius: 12px; margin-top: 16px; }
  #m-root h2 { font-size: 22px; font-weight: 700; padding-top: 44px; }
  #m-root .m-p { color: #55555e; padding-top: 8px; font-size: 15px; }
  #m-root footer.m-foot { padding: 48px 0 40px; color: #8a8a92; font-size: 13px; }
  #m-root footer.m-foot a { color: #55555e; text-decoration: none; margin-right: 16px; }

/* Height follows the viewport WIDTH (not height), so the band keeps a
     constant notch-like aspect on any screen — fullscreen no longer squashes
     it into an ultra-wide strip. */
  .glassband{ position:relative; width:100%; height:clamp(380px,34vw,720px); overflow:hidden; background:#000; isolation:isolate; }
  .glassband-canvas{ position:absolute; inset:0; width:100%; height:100%; display:block; }
  .glassband-fallback{ position:absolute; inset:0; margin:0; display:flex; align-items:center; justify-content:center;
    font-family:var(--display); font-weight:700; font-size:clamp(64px,16vw,240px); letter-spacing:.01em;
    color:rgba(255,255,255,.92); user-select:none; transition:opacity .6s ease; }
  .glassband.live .glassband-fallback{ opacity:0; }
