/* ============================================================
   Termdeck — home.css  (V2 marketing homepage, agent-ai theme)
   Self-contained. Geist + DM Mono, near-black, hairline rails,
   warm top-glow. Isolated from the shared homepage.css (which still
   styles /vs/* and /blog). Do not link both on one page.
   ============================================================ */
.skip-link{position:absolute;left:-9999px;top:0;z-index:200;background:var(--primary);color:var(--primary-fg);padding:8px 14px;border-radius:0 0 var(--r-md) 0;font-size:13px}
.skip-link:focus{left:0}

/* ============ FONTS (Geist + DM Mono, embedded) ============ */
@font-face{font-family:'Geist';src:url('/fonts/geist-variable.woff2') format('woff2');font-weight:100 900;font-style:normal;font-display:swap}
@font-face{font-family:'Geist Mono';src:url('/fonts/geist-mono-variable.woff2') format('woff2');font-weight:100 900;font-style:normal;font-display:swap}
/* DM Mono is the mono face here. Only the 400 and 500 cuts are embedded:
   this page asks for mono at 400/500/600/700, and DM Mono has no cut above
   500, so 600/700 synthesize from 500 rather than pulling another file. */
@font-face{font-family:'DM Mono';src:url('/fonts/dm-mono-400-normal-latin-ext.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap;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:'DM Mono';src:url('/fonts/dm-mono-400-normal-latin.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap;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:'DM Mono';src:url('/fonts/dm-mono-500-normal-latin-ext.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap;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:'DM Mono';src:url('/fonts/dm-mono-500-normal-latin.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap;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}

/* ============ TOKENS ============
   THE PALETTE IS THE APP'S, BY REFERENCE AND NOT BY COPY.
   These names all resolve to --workspace-* from tokens/colors.css, which every
   marketing page already links. That file's spine is Linear's dark theme copied
   value for value; this sheet used to carry a SECOND, older palette (#0a0a0a
   canvas, #131315 cards, #a1a1a1 body copy) that predated the swap and was
   never moved with it, so the public site and the product were two different
   greys. The worst of it was body copy: --muted at #a1a1a1 against the app's
   #d0d6e0, 47 eight-bit steps dim and with none of the blue cast the rest of
   the palette has.

   var(), not the hex, on purpose. A copied value is a value that goes stale the
   next time colors.css moves, which is exactly how this file got here. The
   local names stay because ~600 declarations spell them, and renaming those is
   not what a palette swap is. */
:root{
  --bg:var(--workspace-bg);                       /* #08090a  linear void */
  --bg-2:var(--workspace-nav-hover);              /* #0f1011  linear carbon */
  --card:var(--workspace-surface);                /* #1c1c1f  the card rung */
  --card-2:var(--workspace-surface);              /* flat cards: same rung, see --panel */
  --raised:var(--workspace-surface-raised);       /* #232326 */
  --muted-surface:var(--workspace-surface-hover); /* #28282c */
  --text:var(--workspace-text);                   /* #f7f8f8 */
  --muted:var(--workspace-muted);                 /* #d0d6e0  linear mist */
  --faint:var(--workspace-faint);                 /* #8a8f98  linear fog */
  --primary:var(--workspace-accent);              /* #e5e5e6 */
  --primary-fg:var(--workspace-primary-fg);       /* #08090a */
  --border:var(--workspace-border);               /* rgba(255,255,255,.14) */
  --border-soft:var(--workspace-nav-border);      /* #23252a  linear graphite */
  --rail:var(--workspace-nav-border);             /* #23252a  the lit column's edges */
  /* --input is the BRIGHTENED edge: a card on hover, the "after" compare column,
     a focused control. Linear's own inputs sit on rgba(255,255,255,.08), which
     measures about 1.3:1 and cannot carry a control that is identified by its
     boundary alone (WCAG 2.2 SC 1.4.11 asks 3:1). The text fields down in AUTH
     therefore take --workspace-control-border directly and brighten to --muted
     on focus, which is Linear's stated focus behaviour; everything else uses
     this, the app's strong hairline. */
  --input:var(--workspace-border-strong);         /* #34343a */
  --ring:var(--workspace-accent);                 /* #e5e5e6  same ring as the app */
  /* ---- surfaces: one elevation language for every component on the page ----
     desk   = the page behind the column        panel  = a card you read as a component
     sunken = a chip nested inside a card       well   = media / live data surface
     edge + lift are the only two shadows on the page, so every raised thing is lit
     from the same angle by the same amount. */
  --desk:var(--workspace-nav-bg);   /* #010102  linear's deepest, the page behind the column */
  --grid:rgba(255,255,255,.028);
  /* THE CARD IS LINEAR'S CARD NOW: a FLAT fill, a hairline, and nothing else.
     --panel was a linear-gradient(180deg, #171717, #131315) and the reference is
     explicit that a card is a solid #0f1011-class surface with an inset hairline,
     "no outer shadow, no glow". Both tokens keep their names so the ~20 rules
     that spell them do not move; they simply resolve to a colour instead of a
     gradient. */
  --panel:var(--card);
  --panel-2:var(--raised);
  --sunken:var(--workspace-surface-raised); /* #232326  a chip nested inside a card */
  --well:var(--workspace-overlay-bg);       /* #0f1011  media / live data surface */
  /* Both resting shadows are off. Linear's don'ts: "Do not use shadows to
     separate cards from the canvas, use hairline borders and inner inset shadows
     instead." The lift here is the surface step (#08090a -> #1c1c1f, 20 eight-bit
     steps) plus the hairline, which is the same argument tokens/colors.css makes
     for the app: on a near-black canvas a cast shadow has nowhere to go.

     0 0 #0000 and NOT none, because every consumer writes
     `box-shadow:var(--edge),var(--lift)` and `box-shadow:<x>,none` is invalid,
     which would drop the whole declaration. Same trap tokens/controls.css
     documents for --btn-bezel-none.

     --lift-2 survives as a real shadow because it is the HOVER state, and an
     interaction affordance is not resting elevation. It is Linear's --shadow-sm. */
  --edge:0 0 #0000;
  --lift:0 0 #0000;
  --lift-2:0 2px 4px rgba(0,0,0,.4);
  --grid-cols:repeating-linear-gradient(90deg,transparent 0 calc(12.5% - 1px),var(--grid) calc(12.5% - 1px) 12.5%);
  --link-rule:rgba(109,233,92,.7); /* = --brand-green-400 at 70%, literal so the rule survives without color-mix */
  --link-rule:color-mix(in srgb,var(--brand-green-400) 70%,transparent); /* 55% read as grey at 1x; a 1px rule needs the alpha */
  --danger:#ff6568; --live:#0ac864; --busy:#fcbb00; /* --live per DESIGN.md; kept distinct from --cat-emerald */
  /* ---- info: "here is a fact you did not ask for and cannot act on" ----
     The marketing-local mirror of the app's --workspace-info #3d86f7 (tokens/colors.css).
     Same hue, declared here because home.css is self-contained by design.
     The one thing that spends it — .price-tag — sits ON the flipped featured card and
     straddles its top edge, so the soft pair is BAKED OPAQUE rather than left as a tint:
     a translucent 14% would show near-black through the badge's top half and near-white
     through its bottom, and read as a two-tone chip.
       --info-soft = #3d86f7 at 14% over #ededed   --info-line = the same at 36%
     --info-ink is the app's soft-badge glyph run in reverse. On a DARK tint the label has
     to lift (the app's --workspace-info-text #7ab3fb); on a LIGHT tint it has to sink —
     #3d86f7 measures 2.62:1 on its own 14%-over-white tint — so it deepens instead, to
     4.88:1. Same rule (measure against the tint you just painted), opposite direction. */
  --info-soft:#d4dfee; --info-line:#aec8f1; --info-ink:#275bae;
  --cat-purple:#c084fc; --cat-orange:#fb923c; --cat-emerald:#34d399; --cat-blue:#60a5fa; --cat-pink:#f472b6;
  /* The page's one light source, and it is neutral now. It was
     rgba(255,200,150,.055), a warm peach wash, and it was the last warm thing
     left on a page whose every other value is a cool grey: side by side with the
     app it read as a different product. Linear's atmospheric floor runs
     rgb(8,9,10) -> rgb(208,214,224), so the wash is that endpoint, which is
     --workspace-muted / linear mist, at the same 5.5% it always had. Written as
     an rgba literal rather than a var() because a custom property cannot be
     handed to rgba() without color-mix, and a gradient stop is not where this
     sheet wants to find out that color-mix is unsupported. */
  --glow:radial-gradient(120% 80% at 50% 0%, rgba(208,214,224,.055), transparent 60%);
  /* Radii - Linear's five, and exactly its five: 2 / 4 / 6 / 12 / pill, with a
     hard cap of 12 on cards and panels. The old family (8/10/14/20) shared no
     value with the reference at all, and 12 hand-written literals sat off even
     that: 9px eight times, 15px five, plus 5, 7 and 11. See tokens/spacing.css,
     which took the same five values for the app family on the same pass.
     --r-xl is deliberately the same 12 as --r-lg: the name survives for the big
     frames that spell it, the value is capped where Linear caps it. */
  --r-xs:2px; --r-sm:4px; --r-md:6px; --r-lg:12px; --r-xl:12px; --pill:999px;
  --shell:1280px;
  --sans:'Geist',-apple-system,system-ui,sans-serif;
  --mono:'DM Mono',ui-monospace,'SF Mono',Menlo,monospace;
  --step:clamp(2.2rem,5.2vw,3.9rem);
  --ease:cubic-bezier(.22,.61,.36,1);
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;scroll-padding-top:80px} /* clears the 64px sticky .nav on in-page anchors */
body{background:var(--desk);color:var(--text);font-family:var(--sans);font-size:15px;line-height:1.55;-webkit-font-smoothing:antialiased;letter-spacing:-.01em}
a{color:inherit;text-decoration:none}
h1,h2,h3{font-weight:600;letter-spacing:-.028em;line-height:1.03;text-wrap:balance}
::selection{background:rgba(255,255,255,.16)}
:focus-visible{outline:none;box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--ring);border-radius:var(--r-md)}

/* ============ LAB CHROME ============ */
.labbar{position:sticky;top:0;z-index:100;display:flex;align-items:center;gap:14px;padding:12px 22px;background:rgba(10,10,10,.82);backdrop-filter:blur(14px);border-bottom:1px solid var(--border-soft)}
.labbar b{font-size:13px;font-family:var(--mono);letter-spacing:0}
.labbar span{font-size:12px;color:var(--faint)}
.labnav{margin-left:auto;display:flex;gap:2px;flex-wrap:wrap}
.labnav a{font-size:12px;color:var(--muted);padding:5px 10px;border-radius:var(--pill);font-family:var(--mono)}
.labnav a:hover{color:var(--text);background:var(--muted-surface)}
.lab-sec{border-bottom:1px solid var(--border-soft);padding:60px 0}
.lab-head{max-width:var(--shell);margin:0 auto 34px;padding:0 32px;display:flex;align-items:baseline;gap:14px}
.lab-head .n{font-family:var(--mono);font-size:12px;color:var(--faint)}
.lab-head h2.t{font-size:15px;font-weight:600;letter-spacing:0;text-transform:none}
.lab-head .d{margin-left:auto;font-size:12.5px;color:var(--muted);max-width:46ch;text-align:right}
.demo-wrap{max-width:var(--shell);margin:0 auto;padding:0 32px}

/* ============ PRIMITIVES ============ */
.eyebrow{display:inline-flex;font-family:var(--mono);font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.kicker{font-family:var(--mono);font-size:12px;letter-spacing:.02em;color:var(--muted)}
.btn{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 17px;border-radius:var(--r-md);font-family:var(--sans);font-size:14px;font-weight:500;border:1px solid transparent;cursor:pointer;transition:background .16s,border-color .16s,transform .16s,color .16s;white-space:nowrap}
.btn svg{width:15px;height:15px}
.btn-primary{background:var(--primary);color:var(--primary-fg)}
.btn-primary:hover{background:#fff}
.btn-dark{background:var(--raised);color:var(--text);border-color:var(--border)}
.btn-dark:hover{background:var(--muted-surface)}
.btn-ghost{background:transparent;color:var(--text);border-color:var(--border)}
.btn-ghost:hover{background:rgba(255,255,255,.05);border-color:var(--input)}
/* Nav sign-up. Linear's one shaped exception: a white PILL against the dark bar,
   "the second highest-contrast element after the acid-lime CTA". Pure #ffffff and
   not --primary (#e5e5e6) on purpose, because the whole point of the variant is
   that it out-contrasts every other control in the chrome; --primary is the fill
   for a control that must not out-shout the sentence beside it.
   The weight is .btn's own 500 and is deliberately NOT overridden. Linear specifies
   510, which is ten units away and not a difference anyone can see at 13px; it is
   also not a rung of this project's weight scale, and tests/sans-weight-scale.mjs
   fails any weight that is not one. A shape swap does not get to add a type rung.
   16px of side padding over .btn-sm's 32px height is their 8/16. */
/* .btn prefixed so the 16px survives .btn-sm's `padding:0 12px`, which is declared
   after this and would otherwise win on source order at equal specificity. */
.btn.btn-signup{background:#fff;color:var(--primary-fg);border-radius:var(--pill);padding:0 16px}
.btn.btn-signup:hover{background:var(--primary)}
.btn-danger{background:transparent;color:var(--danger);border-color:rgba(255,101,104,.35)}
.btn-danger:hover{background:rgba(255,101,104,.10)}
.btn-sm{height:32px;font-size:13px;padding:0 12px}
.btn .arrow{transition:transform .18s var(--ease)}
.btn:hover .arrow{transform:translateX(3px)}
.dot{width:6px;height:6px;border-radius:50%;background:var(--live);box-shadow:0 0 0 3px rgba(10,200,100,.18)}

.cat{display:inline-flex;align-items:center;gap:7px;padding:5px 11px 5px 9px;border-radius:var(--r-md);font-size:12.5px;font-weight:500;font-family:var(--mono);letter-spacing:.01em;border:1px solid}
.cat i{width:14px;height:14px;display:inline-flex}
.cat i svg{width:100%;height:100%} /* the vendor marks carry only a viewBox */
.cat.purple{color:var(--cat-purple);background:rgba(192,132,252,.10);border-color:rgba(192,132,252,.22)}
.cat.orange{color:var(--cat-orange);background:rgba(251,146,60,.10);border-color:rgba(251,146,60,.22)}
.cat.emerald{color:var(--cat-emerald);background:rgba(52,211,153,.10);border-color:rgba(52,211,153,.22)}
.cat.blue{color:var(--cat-blue);background:rgba(96,165,250,.10);border-color:rgba(96,165,250,.22)}

.pill{display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 10px;border-radius:var(--pill);font-family:var(--mono);font-size:11px;letter-spacing:.02em;border:1px solid var(--border);color:var(--muted);text-transform:uppercase}
.pill.live{color:var(--live);border-color:rgba(10,200,100,.3)}
.pill.live::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--live);box-shadow:0 0 8px var(--live)}
.pill.busy{color:var(--busy);border-color:rgba(252,187,0,.3)}
.pill.busy::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--busy)}
.pill.need{color:var(--danger);border-color:rgba(255,101,104,.3)}
.pill.need::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--danger)}

.swatch{display:flex;flex-direction:column;gap:8px}
.swatch .chip{height:60px;border-radius:var(--r-md);border:1px solid var(--border)}
.swatch b{font-size:12px;font-weight:500}
.swatch code{font-family:var(--mono);font-size:11px;color:var(--faint)}
.grid-tokens{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:16px}

/* ============ CANVAS: lit column on a dark desk (signature) ============
   Every section is a full-bleed .hsec (its top hairline runs edge to edge)
   wrapping a .rail, the 1280 column that carries the page surface, the two
   signature hairline rails, and a vertical column grid. The grid is drawn per
   section but reads as one continuous field: vertical lines have no vertical
   phase, so a section boundary cannot break their alignment. The warm glow is
   the page's single light source and now fires once, on the hero, instead of
   restarting at the top of all thirteen sections. */
.rail{max-width:var(--shell);margin:0 auto;background:var(--bg);border-left:1px solid var(--rail);border-right:1px solid var(--rail);position:relative}
.rail::before{content:"";position:absolute;inset:0;pointer-events:none;background-image:var(--grid-cols);
  background-size:calc(100% + 1px) 100%} /* +1px pushes the 8th line past the right rail so the grid never doubles it */
#top .rail::before{background-image:var(--glow),var(--grid-cols);background-size:100% 100%,calc(100% + 1px) 100%}
.rail>*{position:relative}

/* ============ SURFACES ============
   Raised for components, sunken for what sits inside them, well for media.
   Nested surfaces never repeat the raised recipe — that is what keeps the page
   from reading as cards inside cards. */
.frame,.bento .cell,.compare .col,.price,.cta,.sess,.composer,.auth-card{
  background:var(--panel);border:1px solid var(--border);box-shadow:var(--edge),var(--lift)}
/* .price.feat used to sit here too. It doesn't any more: the featured tier is a polarity
   flip, not a lighter grey — see PRICING below. */
.compare .col.after{background:var(--panel-2);border-color:var(--input)}
.mini-agent,.u-metric,.u-card,.u-table,.appwin .card{background:var(--sunken);border:1px solid var(--border-soft)}
.frame-inner,.usage-panel,.diff .dh{background:var(--well)}
/* only the panels that are links move on hover; static cards stay put */
a.frame,a.cell{transition:border-color .18s var(--ease),box-shadow .18s var(--ease),transform .18s var(--ease)}
a.frame:hover,a.cell:hover{border-color:var(--input);box-shadow:var(--edge),var(--lift-2);transform:translateY(-2px)}

/* ============ NAV ============ */
/* the bar is full-bleed, its contents are not: the padding resolves to the
   column edge + the 40px section gutter, so the brand sits on the same vertical
   as the hero headline on any screen wider than the shell */
.nav{position:sticky;top:0;z-index:100;display:flex;align-items:center;gap:26px;height:64px;padding:0 max(26px,calc((100% - var(--shell))/2 + 40px));border-bottom:1px solid var(--border-soft);background:var(--bg);
  transition:background-color .3s var(--ease),border-color .3s var(--ease)}
@supports (backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px)){
  .nav{background:rgba(10,10,10,.82);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px)} /* opaque --bg stays the fallback so scrolled content never bleeds through */
}
/* Over the hero the bar carries nothing, so the band reads to the top of the
   window; it takes its surface as you scroll off. Set by /js/nav-chrome.js and
   ONLY on pages that open with a full-bleed hero — the default state in the
   stylesheet is the opaque bar, so no JS means no transparent header over
   ordinary content. Higher specificity than the @supports rule above, so the
   blur goes with it whichever order they land in. */
.nav.is-top{background:transparent;border-bottom-color:transparent;
  -webkit-backdrop-filter:none;backdrop-filter:none}
.brand{display:flex;align-items:center;gap:9px;font-weight:600;font-size:15px;letter-spacing:-.02em}
/* The app's own mark (icons/icon.svg), not a mono "›_" set in a green box: the
   marketing site and the dashboard were shipping two different logos for one
   product, and only the box was ever the brand colour. The tile is full-bleed,
   so the radius here is what makes it read as an app icon.
   The rim is a box-shadow, never a border: the tile is a fixed 26px under
   box-sizing:border-box, so a border would eat 2px of the artwork and thin the
   green bar. The mark's screen bottoms out at #0d1210 against the bar's --bg,
   so without the rim it has no left, right or bottom edge — only the green title
   bar read. Lit from the same angle and by the same amount as --edge/--lift, so
   it sits on the bar like every other raised thing on the page. Its twin is
   .brand-logo in style.css; the two chromes already shipped different marks once. */
.brand .mk{width:26px;height:26px;border-radius:var(--r-md);display:block;flex:none;
  box-shadow:0 0 0 1px rgba(255,255,255,.24),0 1px 2px rgba(0,0,0,.6)}
.nav-links{display:flex;gap:4px;margin:0 auto}
.nav-links a{position:relative;font-size:13.5px;color:var(--muted);padding:7px 11px;border-radius:var(--r-md);
  transition:color .2s var(--ease)}
.nav-links a:hover{color:var(--text);background:var(--muted-surface)}
/* Where you are. Green is this system's selection colour, and a marker under
   the label is the one that survives the header going transparent — a filled
   chip would need a surface the bar does not have over the hero. Driven by
   aria-current from /js/nav-chrome.js, so the semantics and the styling are
   the same fact rather than a class repeating one. */
.nav-links a[aria-current]{color:var(--text)}
.nav-links a[aria-current]::after{content:"";position:absolute;left:11px;right:11px;bottom:1px;
  height:2px;border-radius:var(--r-xs);background:var(--link-rule)}
.nav-actions{display:flex;align-items:center;gap:12px}
.nav-actions .login{font-size:13.5px;color:var(--muted)}
.nav-actions .login:hover{color:var(--text)}

/* ============ HERO ============
   Centred type over the warp canvas, then the marquee, then the run diagram —
   one section, three bands, all of it edge to edge.
   The hero is the ONE full-bleed band on this page. Everywhere else the lit
   column earns its keep by framing a measure of text; here it would be cutting
   its own hairlines and 12.5% grid straight through a diagram whose panels
   carry their own borders, which is why the column resumes at #compare and not
   before. The rail stays in the markup to keep the type and the marquee on the
   page's measure — it just paints nothing. */
.hsec-hero{background:var(--bg);overflow:hidden}
.hsec-hero .rail{background:transparent;border-left:0;border-right:0}
.hsec-hero .rail::before{content:none}
.hero{padding:84px 40px 40px;max-width:820px;margin:0 auto;text-align:center;display:flex;flex-direction:column;align-items:center}
.hero h1{font-size:clamp(2.6rem,5.6vw,4.25rem);letter-spacing:-.035em}
.hero h1 em{font-style:normal;color:var(--text);text-decoration:underline;text-decoration-color:rgba(255,255,255,.30);text-decoration-thickness:.055em;text-underline-offset:.14em} /* emphasis = full white + hairline rule, no hue */
.hero .lede{margin-top:20px;font-size:16.5px;color:var(--muted);max-width:60ch;line-height:1.5}
.hero .actions{margin-top:30px;display:flex;gap:12px;flex-wrap:wrap;justify-content:center}
.hero .note{margin-top:14px;font-family:var(--mono);font-size:11.5px;color:var(--faint)}
/* the down-arrow twin of .btn .arrow: nudges toward the demo it points at */
.btn .arrow-d{transition:transform .18s var(--ease)}
.btn:hover .arrow-d{transform:translateY(3px)}
/* the rule under "One console." fades in once the words have landed.
   text-decoration-color, not a pseudo — the em wraps on phones. */
@keyframes heroRule{from{text-decoration-color:transparent}}
.rv-on .hero h1 em{animation:heroRule 1.1s var(--ease) .95s both}
/* …then a console cursor takes over from the rule: a block caret blinks three
   times after "One console." and leaves. A ::after with content:'' so the
   headline text — the LCP candidate — is never hidden or retyped, and the
   caret sits outside the em, so it never joins the underline. Base opacity 0
   and no fill mode: invisible before its delay and after its last blink. The
   global reduced-motion kill at the foot of this sheet matches elements, not
   pseudo-elements, so this one carries its own motion guard. */
.hero h1 em::after{content:'';display:inline-block;width:.22em;height:.72em;
  margin-left:.14em;vertical-align:-.04em;background:var(--text);opacity:0}
@keyframes heroCaret{0%,15.9%{opacity:1}16%,32.9%{opacity:0}33%,48.9%{opacity:1}
  49%,65.9%{opacity:0}66%,82.9%{opacity:1}83%,100%{opacity:0}}
@media (prefers-reduced-motion:no-preference){
  .rv-on .hero h1 em::after{animation:heroCaret 2.6s linear 1.9s}
}

/* ---- the warp canvas ----
   Decorative depth behind the type: /js/hero-warp.js paints it, 'lighten' so it
   can only ever add light to the near-black column, never darken a glyph.
   TWO masks, intersected, and they are the whole reason it stays a background:
   one fades it out before it reaches the marquee, the other holds a corridor
   down the middle where the headline and lede sit. The script thins the centre
   too, but only per strip — a wide one near the camera can still straddle the
   middle, and a mask cannot miss it. */
.hero-warp{position:absolute;inset:0 0 auto 0;width:100%;height:min(700px,92%);
  z-index:0;pointer-events:none;mix-blend-mode:lighten;opacity:.85;
  -webkit-mask-image:linear-gradient(180deg,#000 0%,#000 42%,transparent 96%),
    linear-gradient(90deg,#000 0,#000 calc(50% - 680px),rgba(0,0,0,.12) calc(50% - 420px),
      rgba(0,0,0,.12) calc(50% + 420px),#000 calc(50% + 680px),#000 100%);
  mask-image:linear-gradient(180deg,#000 0%,#000 42%,transparent 96%),
    linear-gradient(90deg,#000 0,#000 calc(50% - 680px),rgba(0,0,0,.12) calc(50% - 420px),
      rgba(0,0,0,.12) calc(50% + 420px),#000 calc(50% + 680px),#000 100%);
  -webkit-mask-composite:source-in;mask-composite:intersect}

/* ============ HERO RUN DIAGRAM ============
   A staged Termdeck run, laid out as the architecture it runs on: the engine's
   own transcript on the left, the session in the middle, the permission gate
   and the machine link on the right, the rest of the fleet underneath. The
   panels are placed on a percentage grid (--x is a centre line, --y a top edge)
   so the whole board reflows with the column instead of needing a fixed width.
   /js/hero-orchestrator.js only adds state classes and text — with no JS this
   is a still diagram, which is a perfectly good illustration on its own. */
.horc-alt{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
/* On the page's own column, framed like every section below it — the board's
   own section supplies the rule above it, so nothing here draws one. */
.horc{position:relative;height:620px;padding-top:44px}
/* The gutter lives here, not on .horc: absolute percentages resolve against the
   padding box, so padding on the parent would leave the outer panels flush with
   the rail's hairlines and clip their own borders. 40px is the page's inner
   gutter, so the panels sit on the same margin as every other section's copy. */
.horc-board{position:absolute;inset:44px 40px 0}
.horc-panel,.horc-card{position:absolute;left:var(--x);top:var(--y);width:var(--w);transform:translateX(-50%)}
/* Opaque, not translucent: the rail's 12.5% column grid runs behind this
   section like every other, and it should show in the gaps BETWEEN panels the
   way it does between cards elsewhere — not through their contents. */
.horc-panel{height:var(--h,auto);display:flex;flex-direction:column;padding:14px;
  border:1px solid var(--border-soft);background:var(--well);
  font-family:var(--mono);font-size:11px;color:var(--muted);
  opacity:.54;transition:opacity .5s var(--ease),border-color .5s var(--ease),box-shadow .5s var(--ease)}
/* A panel that has already had its moment keeps a trace of it rather than
   snapping back to inert — otherwise the board forgets the run as it goes. */
.horc-panel.is-done{opacity:.8}
.horc-panel.is-live{opacity:1;border-color:rgba(255,255,255,.2);box-shadow:var(--edge),0 0 34px -14px rgba(255,255,255,.28)}
.hp-cap{font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
.hp-path{margin-top:8px;font-size:10.5px;color:var(--muted);word-break:break-all;line-height:1.4}

/* the transcript: one row per turn already appended to the jsonl */
.hp-rows{margin-top:12px;flex:1;min-height:0;display:flex;flex-direction:column;gap:5px}
.hd-row{display:grid;grid-template-columns:44px 1fr auto;gap:8px;align-items:baseline;
  padding:6px 8px;border:1px solid rgba(255,255,255,.05);font-size:10.5px;
  animation:hdIn .34s var(--ease) both}
@keyframes hdIn{from{opacity:0;transform:translateY(-4px)}}
.hd-kind{color:var(--faint);text-transform:uppercase;letter-spacing:.04em;font-size:9px}
.hd-label{color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hd-size{color:rgba(255,255,255,.28)}
.hd-row.is-fresh{border-color:rgba(255,255,255,.16)}
.hd-row.is-fresh .hd-label{color:var(--text)}
.hp-foot{margin-top:10px;padding-top:10px;border-top:1px solid var(--border-soft);
  font-size:10px;line-height:1.5;color:rgba(255,255,255,.32)}
.hp-foot code{font-size:10px;color:var(--muted)}

/* the CTA panel is the one thing in here you can click, so it never dims */
.horc-say{opacity:1;justify-content:center;gap:12px;align-items:flex-start;
  border-color:var(--border);background:var(--panel);box-shadow:var(--edge)}
.horc-say .hp-cap{font-family:var(--sans);font-size:13.5px;letter-spacing:-.01em;
  text-transform:none;color:var(--text);font-weight:500}

/* the permission gate */
.hp-ask{margin-top:11px;display:flex;flex-direction:column;gap:5px;
  padding:8px;border:1px solid rgba(255,255,255,.06);background:var(--sunken)}
.hpa-tool{font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.hpa-cmd{font-size:11px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hp-choices{margin-top:9px;display:flex;gap:7px}
.hpc-allow,.hpc-deny{flex:1;text-align:center;font-size:10.5px;padding:5px 0;border:1px solid transparent}
.hpc-allow{background:rgba(255,255,255,.1);color:var(--muted)}
.hpc-deny{border-color:var(--border-soft);color:var(--faint)}
/* Go-Is-Green: while the run is actually parked on this prompt the panel
   depicts the app, where the one filled action is green and it pulses. */
.horc-perm.is-live .hpc-allow{background:var(--live);color:var(--primary-fg);font-weight:600;animation:hpcWait 1.6s var(--ease) infinite}
@keyframes hpcWait{50%{box-shadow:0 0 0 4px rgba(10,200,100,.16)}}
.horc-perm.is-done .hpc-allow{background:rgba(10,200,100,.22);color:var(--live)}
.hp-state{margin-top:auto;padding-top:9px;font-size:10px;letter-spacing:.04em;color:var(--faint)}
.hp-state[data-state="waiting"]{color:var(--busy)}
.hp-state[data-state="allowed"],.hp-state[data-state="online"]{color:var(--live)}

/* the machine link */
.hp-host{margin-top:11px;display:flex;flex-direction:column;gap:3px}
.hp-host b{font-size:13px;font-weight:500;color:var(--text);letter-spacing:-.01em}
.hp-host span{font-size:10px;color:var(--faint)}
.hp-wire{margin-top:9px;padding:6px 8px;border:1px solid rgba(255,255,255,.05);
  background:var(--sunken);font-size:10px;color:rgba(255,255,255,.34)}

/* the run's settings */
.hp-kv{margin-top:11px;display:flex;flex-direction:column;gap:0}
.hp-kv>div{display:flex;justify-content:space-between;gap:10px;align-items:baseline;
  padding:7px 0;border-bottom:1px solid rgba(255,255,255,.05)}
.hp-kv>div:last-child{border-bottom:0;padding-bottom:0}
.hp-kv dt{font-size:10px;letter-spacing:.05em;text-transform:uppercase;color:var(--faint)}
.hp-kv dd{font-size:11.5px;color:var(--text);text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---- the session card ----
   The one thing on the board that is a Termdeck screen rather than a diagram
   of one, so it gets the rounded, lit treatment the rest deliberately lacks.
   --tint is the running engine's mark colour; it is the only hue in here. */
.horc-card{--tint:#D97757;z-index:2;display:flex;flex-direction:column;overflow:hidden;
  height:500px;border-radius:var(--r-lg);border:1px solid var(--border);
  background:var(--card);box-shadow:var(--edge),var(--lift-2);
  transition:opacity .38s var(--ease),transform .38s var(--ease)}
.horc-card.is-swapping{opacity:0;transform:translateX(-50%) translateY(-34px)}
.hcard-head{display:flex;align-items:center;justify-content:center;gap:8px;
  padding:13px;border-bottom:1px solid var(--border-soft)}
.hcard-mark{width:16px;height:16px;display:inline-flex;color:var(--tint)}
.hcard-mark svg{width:100%;height:100%}
.hcard-title{font-size:12.5px;color:var(--muted);display:flex;gap:6px}
.hcard-title b{font-weight:500;color:var(--text)}
.hcard-log{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:15px;padding:18px 18px 8px}
.hcard-item{animation:hciIn .3s var(--ease) both;opacity:.42;transition:opacity .35s var(--ease)}
.hcard-item.is-current{opacity:1}
@keyframes hciIn{from{opacity:0;transform:translateY(9px)}}
.hcard-item.is-user{align-self:flex-end;max-width:86%;padding:10px 14px;border-radius:var(--r-lg);
  background:var(--muted-surface);color:var(--text);font-size:13.5px;line-height:1.5}
.hcard-item.is-assistant{font-size:14px;line-height:1.55;color:var(--text)}
.hci-tool{display:inline-flex;align-items:center;gap:5px;font-family:var(--mono);
  font-size:11.5px;letter-spacing:.02em;color:var(--faint)}
.hci-tool svg{width:11px;height:11px;flex:none}
/* the running tool call gets the sweep the app uses for a live one */
.hcard-item.is-tool.is-current .hci-tool{color:transparent;background-clip:text;-webkit-background-clip:text;
  background-image:linear-gradient(90deg,rgba(255,255,255,.3) 0%,rgba(255,255,255,.75) 42%,#fff 50%,rgba(255,255,255,.75) 58%,rgba(255,255,255,.3) 100%);
  background-size:220% 100%;animation:hciSweep 2s var(--ease) infinite}
.hcard-item.is-tool.is-current .hci-tool svg{color:var(--text)}
@keyframes hciSweep{from{background-position:120% 0}to{background-position:-120% 0}}
.hcard-item.is-thinking{display:flex;gap:5px;align-items:center;opacity:1}
.hcard-item.is-thinking i{width:5px;height:5px;border-radius:50%;background:var(--tint);
  animation:hciDot 1.4s var(--ease) infinite}
.hcard-item.is-thinking i:nth-child(2){animation-delay:.2s}
.hcard-item.is-thinking i:nth-child(3){animation-delay:.4s}
@keyframes hciDot{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}
.hcard-composer{margin:0 12px 12px;padding:11px 14px 9px;border-radius:var(--r-lg);background:var(--muted-surface)}
.hcc-ph{display:block;font-size:13.5px;color:var(--faint)}
.hcc-row{margin-top:9px;display:flex;align-items:center;gap:10px;color:var(--faint)}
.hcc-row>svg{width:16px;height:16px}
.hcc-model{margin-left:auto;font-family:var(--mono);font-size:11px}
.hcc-send{width:26px;height:26px;border-radius:50%;background:var(--tint);color:var(--primary-fg);
  display:inline-flex;align-items:center;justify-content:center}
.hcc-send svg{width:13px;height:13px}

/* ---- the wires ----
   Drawn from the end nearest the card outward, which is the direction the work
   actually travels. ::before is the soft halo under the hairline. */
.horc-wires{position:absolute;inset:0;pointer-events:none;z-index:1}
.horc-wire{position:absolute;left:var(--x);top:var(--y);background:var(--text);
  opacity:0;transition:transform .6s var(--ease),opacity .3s var(--ease)}
.horc-wire::before{content:"";position:absolute;inset:-3px;background:var(--text);opacity:.14}
.hw-h{width:var(--len);height:2px;transform:scaleX(0)}
.hw-v{width:2px;height:var(--len);transform:scaleY(0);margin-left:-1px}
.hw-from-right{transform-origin:right}
.hw-from-left{transform-origin:left}
.hw-v{transform-origin:top}
.horc-wire.is-drawn{opacity:1;transform:scale(1)}

/* ---- the rest of the fleet ---- */
.horc-fleet{padding:20px 40px 44px;border-top:1px solid var(--border-soft);
  opacity:.54;transition:opacity .5s var(--ease)}
.horc-fleet.is-done{opacity:.82}
.horc-fleet.is-live{opacity:1}
.hf-cap{display:flex;gap:12px;align-items:baseline;font-family:var(--mono);font-size:10px;
  letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
.hf-meta{letter-spacing:.02em;text-transform:none;color:rgba(255,255,255,.28)}
.hf-grid{margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:12px}
.hf-machine{padding:12px 14px;border:1px solid var(--border-soft);background:var(--well);
  font-family:var(--mono);transition:border-color .5s var(--ease)}
.hf-machine.is-running{border-color:rgba(255,255,255,.14)}
.hfm-head{display:flex;align-items:baseline;gap:9px;font-size:11px}
.hfm-head b{font-weight:500;font-size:12.5px;color:var(--text);letter-spacing:-.01em}
.hfm-head span{color:var(--faint)}
.hfm-head em{margin-left:auto;font-style:normal;font-size:10px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--faint)}
.hf-machine.is-running .hfm-head em{color:var(--live)}
.hfm-task{margin-top:7px;font-size:10.5px;color:rgba(255,255,255,.34)}
.hfm-bar{margin-top:9px;height:3px;background:rgba(255,255,255,.07);overflow:hidden}
/* --fill is a 0..1 scale, not a width: the fill is a transform so it runs on
   the compositor and never relays out the card it sits in. */
.hfm-bar i{display:block;height:100%;width:100%;background:var(--muted);
  transform:scaleX(var(--fill,0));transform-origin:left;
  transition:transform 1.1s var(--ease)}
.hf-machine.is-running .hfm-bar i{background:var(--live)}

/* ---- below the board's width, the same run as a plain column ----
   The absolute grid needs ~1120px. Under that the board stops being a board:
   the card, the permission gate and the machine link stack, and the panels that
   were context (the transcript, the settings, the fleet) drop out. Phones are
   half the point of this product, so the mobile fallback is the session and the
   approval — not a hidden diagram and an apology. */
@media (max-width:1120px){
  .horc{height:auto;width:100%;border-top:0;display:flex;flex-direction:column;gap:12px;
    margin-top:20px;padding:20px 20px 0}
  /* the board stops being a box so its children become .horc's flex items */
  .horc-board{position:static;display:contents}
  .horc-panel,.horc-card{position:static;left:auto;top:auto;width:auto;height:auto;transform:none}
  .horc-card{order:1;height:420px}
  .horc-card.is-swapping{transform:translateY(-34px)}
  .horc-perm{order:2}
  .horc-link{order:3}
  .horc-say{order:4}
  .horc-disk,.horc-cfg,.horc-wires{display:none}
  .horc-fleet{padding:18px 20px 26px}
}
@media (max-width:640px){
  .hero{padding:56px 20px 32px}
  .horc{padding:16px 14px 0}
  .horc-card{height:380px}
  .hf-grid{grid-template-columns:1fr}
}

/* framed screenshot — a raised panel holding a recessed media well */
.frame{display:block;border-radius:var(--r-xl);padding:6px}
.frame-inner{border-radius:var(--r-md);overflow:hidden;border:1px solid var(--border-soft)}
.usage-panel{padding:20px}

/* fake app window for showcases */
.appwin{font-family:var(--mono);font-size:11px;color:var(--muted);background:var(--card)}
.appwin .abar{display:flex;align-items:center;gap:8px;padding:9px 12px;border-bottom:1px solid var(--border-soft)}
.appwin .abar i{width:9px;height:9px;border-radius:50%;background:var(--muted-surface)}
.appwin .abody{display:grid;grid-template-columns:150px 1fr;min-height:210px}
.appwin .aside{border-right:1px solid var(--border-soft);padding:12px 10px;display:flex;flex-direction:column;gap:7px}
.appwin .aside .row{display:flex;gap:7px;align-items:center;color:var(--faint);font-size:11px}
.appwin .aside .row.on{color:var(--text)}
.appwin .aside .row b{width:5px;height:5px;border-radius:50%;background:currentColor}
.appwin .amain{padding:14px}
.appwin .card{border-radius:var(--r-md);padding:10px;margin-bottom:9px}
.appwin .card .h{color:var(--text);font-size:11.5px;margin-bottom:6px;display:flex;justify-content:space-between}
.appwin .meter{height:5px;border-radius:var(--pill);background:rgba(255,255,255,.08);overflow:hidden}
.appwin .meter i{display:block;height:100%;background:var(--muted)}

/* ============ LOGO STRIP ============
   Static since 2026-08-03 (was a 30s marquee loop): the vendor marks sit still,
   centered, wrapping on narrow screens. The class names stay so the markup and
   the motion-gate history don't churn. */
.marquee{overflow:hidden;padding:26px 0}
.marquee-track{display:flex;gap:52px;flex-wrap:wrap;justify-content:center}
.logo{display:flex;align-items:center;gap:11px;white-space:nowrap;color:var(--muted);opacity:.85;transition:opacity .2s,color .2s}
.logo:hover{opacity:1;color:var(--text)}
.logo svg{width:21px;height:21px;flex-shrink:0}
.logo b{font-size:16px;font-weight:600;letter-spacing:-.02em}

/* ============ FEATURE SHOWCASE (sticky list + panel) ============ */
.showcase{display:grid;grid-template-columns:290px 1fr;gap:44px;padding:56px 40px}
.feat-list{display:flex;flex-direction:column;align-self:start;position:sticky;top:84px}
.feat-item{padding:18px 0;border-top:1px solid var(--border-soft);cursor:pointer;transition:opacity .2s}
.feat-item:first-child{border-top:0}
.feat-item .fi-h{display:flex;align-items:center;gap:9px;font-weight:500;font-size:14.5px}
.feat-item .fi-h svg{width:16px;height:16px}
.feat-item p{margin-top:7px;font-size:13px;color:var(--faint);line-height:1.45;padding-left:25px}
.feat-item[aria-current="false"]{opacity:.5}
.feat-item[aria-current="false"] p{display:none}
.showcase-panel h3{font-size:clamp(1.9rem,3.4vw,2.7rem);margin:16px 0 14px;max-width:16ch}
.showcase-panel .body{font-size:16px;color:var(--muted);max-width:54ch;margin-bottom:22px}

/* ============ BENTO ============ */
.bento{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;padding:48px 40px}
.bento .cell{border-radius:var(--r-lg);padding:24px;min-height:210px;display:flex;flex-direction:column}
.bento .cell.wide{grid-column:span 2}
.bento .idx{font-family:var(--mono);font-size:11px;color:var(--faint);margin-bottom:auto}
.bento h3{font-size:19px;margin:18px 0 8px}
.bento .cell>p{font-size:13.5px;color:var(--muted);max-width:44ch}

/* ---- the live fleet: the page's one authored sequence ----
   The board runs six beats, one state change each, ending on the board it
   started from. States are the app's own signal set: amber working, red waiting
   on you, green attached, grey finished. Nothing here reports progress, because
   a turn has no percentage — working shows as an indeterminate run line, the way
   the terminal shows it. The markup ships the resting board, so a reader with
   reduced motion, a failed script, or no JS sees the same honest state. */
.mini-fleet{display:grid;grid-template-columns:repeat(3,1fr);gap:9px;margin-top:16px}
.mini-agent{position:relative;overflow:hidden;border:1px solid var(--border-soft);border-radius:var(--r-md);padding:10px;transition:border-color .32s var(--ease),background-color .32s var(--ease)}
.mini-agent .mh{display:flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--faint);margin-bottom:7px}
.mini-agent .mh i{width:6px;height:6px;border-radius:50%;background:var(--faint);transition:background-color .32s var(--ease)}
.mini-agent .mst{display:inline-block} /* the word that swaps on a beat */
.mini-agent strong{display:block;font-size:12.5px;font-weight:500;margin-bottom:3px}
.mini-agent .mp{font-family:var(--mono);font-size:11.5px;color:var(--faint)}
.st-run .mh i{background:var(--busy)}
.st-read .mh i{background:var(--live)}
.st-need .mh i{background:var(--danger)}
.st-done .mh i{background:var(--faint)}
.st-need{border-color:rgba(255,101,104,.30);background:rgba(255,101,104,.05)}
.mini-agent::after{content:"";position:absolute;left:0;bottom:0;width:34%;height:1px;background:linear-gradient(90deg,transparent,var(--busy),transparent);opacity:0}
/* .in is set by the observer, so every loop below is dead weight off screen */
.mini-fleet.in .st-run::after{opacity:.9;animation:runline 1.9s linear infinite}
.mini-fleet.in .st-need .mh i{animation:needring 2.4s var(--ease) infinite}
@keyframes runline{from{transform:translateX(-100%)}to{transform:translateX(294%)}}
@keyframes needring{0%{box-shadow:0 0 0 0 rgba(255,101,104,.45)}70%,100%{box-shadow:0 0 0 5px rgba(255,101,104,0)}}

/* ---- bento vignettes, staged like the hero (2026-08-03) ----
   The four small cells act out their claims: a push banner drops in, a diff
   paints its remove-then-add, terminal panes tile in, allowlist rows cascade.
   All one-shot behind .rv-in; the wide cell's fleet board keeps the page's
   only loop. Resting markup is complete for no-JS and reduced motion. */
.bento .bv{margin-top:16px}
.pushrow{display:flex;align-items:center;gap:10px;background:var(--sunken);border:1px solid var(--border-soft);border-radius:var(--r-md);padding:9px 11px}
.push-ic{width:26px;height:26px;border-radius:var(--r-md);background:var(--muted-surface);display:grid;place-items:center;flex:none}
.push-ic img{display:block;border-radius:var(--r-sm)}
.push-tx{min-width:0;display:flex;flex-direction:column;gap:1px}
.push-tx b{font-size:12px;font-weight:500}
.push-tx small{font-family:var(--mono);font-size:10.5px;color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.push-when{margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--faint);flex:none}
.bento .diff .dh{padding:7px 11px;font-size:11px}
.bento .diff pre{padding:9px 11px;font-size:11px;line-height:1.6}
.diff .ctx{display:block}
.bv-term{display:grid;grid-template-columns:1fr 1fr;gap:7px}
.tpane{display:flex;align-items:center;gap:7px;background:var(--well);border:1px solid var(--border-soft);border-radius:var(--r-md);padding:8px 10px;font-family:var(--mono);font-size:10.5px;color:var(--muted);white-space:nowrap;overflow:hidden}
/* .tpane has always said overflow:hidden, which is a truncation it could not
   actually perform: text-overflow does nothing on a flex container, and the
   bare text node was an anonymous flex item with no box to hang an ellipsis
   on. So the narrowest pane hard-clipped mid-word, which reads as a rendering
   fault rather than a label that ran out of room. min-width:0 lets the span
   shrink past its own min-content; nowrap it inherits from .tpane. */
.tpane span{min-width:0;overflow:hidden;text-overflow:ellipsis}
.tpane i{width:6px;height:6px;border-radius:50%;background:var(--faint);flex:none}
.tpane.run i{background:var(--busy)}
.tpane.live i{background:var(--live)}
.bv-rules{display:flex;flex-direction:column;gap:7px}
.rule{display:flex;align-items:center;justify-content:space-between;gap:10px;background:var(--sunken);border:1px solid var(--border-soft);border-radius:var(--r-md);padding:6px 11px}
.rule code{background:none;padding:0;font-size:11.5px;color:var(--muted)}
@media (prefers-reduced-motion:no-preference){
  .rv-on .cell.rv-in .pushrow{animation:pushIn .55s var(--ease) .5s both}
  .rv-on .cell.rv-in .diff .del{animation:vinLine .4s var(--ease) .55s both}
  .rv-on .cell.rv-in .diff .add{animation:vinLine .4s var(--ease) .75s both}
  .rv-on .cell.rv-in .tpane{animation:paneIn .4s var(--ease) both}
  .rv-on .cell.rv-in .tpane:nth-child(1){animation-delay:.45s}
  .rv-on .cell.rv-in .tpane:nth-child(2){animation-delay:.55s}
  .rv-on .cell.rv-in .tpane:nth-child(3){animation-delay:.65s}
  .rv-on .cell.rv-in .tpane:nth-child(4){animation-delay:.75s}
  .rv-on .cell.rv-in .rule{animation:vinLine .45s var(--ease) both}
  .rv-on .cell.rv-in .rule:nth-child(1){animation-delay:.45s}
  .rv-on .cell.rv-in .rule:nth-child(2){animation-delay:.55s}
  .rv-on .cell.rv-in .rule:nth-child(3){animation-delay:.65s}
}
@keyframes pushIn{from{opacity:0;transform:translateY(-10px)}} /* a push arrives from the top, the way a real banner does */
@keyframes vinLine{from{opacity:0;transform:translateY(5px)}}
@keyframes paneIn{from{opacity:0;transform:scale(.96)}}

/* ============ STEPS + STAT ============ */
.steps{display:grid;grid-template-columns:auto repeat(3,1fr);gap:34px;padding:52px 40px;align-items:start}
.bigstat{font-size:clamp(2.4rem,5vw,3.6rem);font-weight:600;letter-spacing:-.03em;line-height:1}
.bigstat span{display:block;font-family:var(--mono);font-size:12px;font-weight:400;color:var(--faint);letter-spacing:.02em;margin-top:8px}
.step .sn{font-family:var(--mono);font-size:11px;color:var(--faint);letter-spacing:.06em}
.step h3{font-size:17px;margin:12px 0 8px}
.step p{font-size:13.5px;color:var(--muted)}
/* ---- how-it-works vignettes, staged like the rest (2026-08-04) ----
   Sunken mono rows in the app's grammar: green dot = online, emerald check =
   signed in, amber = a turn in flight, and the one green fill is the send.
   Rows cascade one-shot after their step lands; nothing loops. */
.step .hv{margin-top:14px;display:flex;flex-direction:column;gap:7px}
.hvrow{display:flex;align-items:center;gap:8px;background:var(--sunken);border:1px solid var(--border-soft);border-radius:var(--r-md);padding:7px 10px;font-family:var(--mono);font-size:11px;color:var(--muted);min-width:0}
.hdot{width:6px;height:6px;border-radius:50%;background:var(--faint);flex:none}
.hdot.live{background:var(--live)}
.hdot.busy{background:var(--busy)}
.hv-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hv-meta{margin-left:auto;color:var(--faint);font-size:10px;flex:none}
.hv-ck{width:12px;height:12px;color:var(--cat-emerald);flex:none}
.hv-caret{color:var(--live);flex:none}
.hv-send{margin-left:auto;width:18px;height:18px;border-radius:var(--r-sm);background:var(--live);color:var(--primary-fg);display:grid;place-items:center;font-size:11px;font-weight:500;flex:none}
@media (prefers-reduced-motion:no-preference){
  .rv-on .step.rv-in .hvrow{animation:vinLine .45s var(--ease) both}
  .rv-on .step.rv-in .hvrow:nth-child(1){animation-delay:.35s}
  .rv-on .step.rv-in .hvrow:nth-child(2){animation-delay:.45s}
  .rv-on .step.rv-in .hvrow:nth-child(3){animation-delay:.55s}
}

/* ============ STATS ROW ============ */
/* a recessed readout strip, not a card — data rows and cards read differently */
.stats{display:grid;grid-template-columns:repeat(4,1fr);margin:26px 40px 52px;border:1px solid var(--border-soft);border-radius:var(--r-lg);background:var(--sunken);overflow:hidden}
.stat{padding:30px 24px;border-left:1px solid var(--border-soft)}
.stat:first-child{border-left:0}
.stat .v{font-size:clamp(1.9rem,3.4vw,2.6rem);font-weight:600;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.stat .l{margin-top:8px;font-size:12.5px;color:var(--muted)}

/* ============ TESTIMONIAL ============ */
.tsti{padding:76px 40px;text-align:center}
.tsti blockquote{font-size:clamp(1.6rem,3.4vw,2.4rem);font-weight:500;letter-spacing:-.03em;line-height:1.16;max-width:20ch;margin:0 auto}
.tsti .who{margin-top:38px;display:flex;flex-direction:column;align-items:center;gap:4px}
.tsti .who .av{width:38px;height:38px;border-radius:var(--r-md);border:1px solid var(--border);display:grid;place-items:center;font-family:var(--mono);font-size:13px;margin-bottom:8px}
.tsti .who b{font-weight:600;font-size:14.5px}
.tsti .who span{font-size:13px;color:var(--faint)}
.tsti .dots{margin-top:26px;display:flex;gap:8px;justify-content:center}
.tsti .dots i{width:7px;height:7px;border-radius:50%;background:var(--muted-surface);cursor:pointer;transition:.2s}
.tsti .dots i.on{width:22px;border-radius:var(--pill);background:var(--text)}

.tstis{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--border-soft)}
.tstis .t{padding:30px 26px;border-left:1px solid var(--border-soft);display:flex;flex-direction:column;gap:22px}
.tstis .t:first-child{border-left:0}
.tstis .t p{font-size:14px;color:var(--muted);line-height:1.5}
.tstis .t .who{display:flex;align-items:center;gap:10px}
.tstis .t .who .av{width:34px;height:34px;border-radius:50%;background:var(--muted-surface);display:grid;place-items:center;font-size:12px;font-family:var(--mono)}
.tstis .t .who b{font-size:13.5px;font-weight:600;display:block}
.tstis .t .who span{font-size:12px;color:var(--faint)}

/* ============ PRICING ============ */
.pricing-head{text-align:center;padding:56px 40px 14px}
.pricing-head h2{font-size:var(--step);margin:14px 0}
.pricing-head p{color:var(--muted);max-width:52ch;margin:0 auto;font-size:15.5px}
.pricing{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:26px 40px 40px}
.pricing.two{grid-template-columns:repeat(2,minmax(0,1fr));max-width:760px;margin:0 auto;padding:26px 40px 20px}
.price{border-radius:var(--r-lg);padding:24px;display:flex;flex-direction:column}
.price .pn{font-size:16px;font-weight:600}
.price .pd{font-size:13px;color:var(--faint);margin-top:6px;min-height:34px}
.price .amt{font-size:clamp(2.2rem,4vw,3rem);font-weight:600;letter-spacing:-.03em;margin:16px 0 2px}
.price .amt small{font-size:13px;font-weight:400;color:var(--faint);letter-spacing:0}
.price .unit{font-family:var(--mono);font-size:11px;color:var(--faint);margin-bottom:18px}
.price ul{list-style:none;display:flex;flex-direction:column;gap:11px;padding:18px 0;border-top:1px solid var(--border-soft);margin-bottom:20px}
.price li{display:flex;gap:10px;font-size:13.5px;color:var(--muted);align-items:flex-start}
/* the checkmark tile from the hero's proof list — one grammar for "you get this" */
.price li svg{width:20px;height:20px;padding:4px;background:var(--sunken);border:1px solid var(--border-soft);border-radius:var(--r-md);color:var(--cat-emerald);flex-shrink:0;margin-top:0}
.price .btn{margin-top:auto;justify-content:center}

/* ---- the featured tier: THE POLARITY FLIP (DESIGN.md § Pricing tiers) ----
   The reference system marks its featured tier by inverting it — dark fill, light text,
   against a white page. Inverted for a near-black page, that is the opposite move: a
   near-white fill with near-black text. White is already this system's identity accent
   (--primary), so this is the strongest single gesture the palette allows, and exactly
   one tier per table gets it — the same discipline as one filled action per view.
   It replaces `background:var(--panel-2)`, which on a dark page is the same card in a
   slightly different grey and does not read as featured at all.

   The flip is done by REDECLARING this card's surface tokens rather than by restating a
   colour on every child. Everything inside already spends --text, --muted, --faint,
   --border-soft, --sunken, --edge, --primary and --ring, so one local block turns the
   whole subtree over at once and anything added to the card later inherits the flip for
   free. The greys are the dark card's own contrast ladder mirrored, not merely legal
   ones: 17.48 / 7.06 / 5.36 on the dark card's #151516 becomes 15.73 / 6.51 / 5.31 on
   --primary, so the three tiers read at the same three strengths. Those numbers moved
   with the palette swap: the ground was the literal #ededed and is the token now, which
   resolves to #e5e5e6, so every ratio here was remeasured rather than carried over. */
.price.feat{
  /* --workspace-accent and NOT var(--primary), even though :root maps one to the
     other. --primary is REDECLARED five lines down, and custom properties on one
     element resolve against that element's final value for the name, not against
     the declaration order in the block. Pointing --feat-ground at --primary here
     therefore resolved it to --feat-ink and painted the featured card near-black:
     caught by tests/pricing-tiers.spec.js, which pins the ground. */
  --feat-ground:var(--workspace-accent);     /* the identity accent, as a ground */
  --feat-ink:var(--workspace-primary-fg);    /* 15.83:1 on it */
  --text:var(--feat-ink);                    /* .pn, .amt, inherited, so set on the card */
  --muted:#4f4f4f;                           /*  6.51:1, .price li */
  --faint:#5c5c5c;                           /*  5.31:1, .pd, .amt small, .unit */
  --border:rgba(0,0,0,.14);                  /* the card's own edge */
  --border-soft:rgba(0,0,0,.10);             /* the ul rule and the tick tiles — 1.25:1, the dark side's 1.19:1 mirrored */
  --sunken:rgba(0,0,0,.05);                  /* the tick tile ground */
  --primary:var(--feat-ink);                 /* the one filled action inverts WITH its card... */
  --primary-fg:var(--feat-ground);           /* ...so .btn-primary stays 15.8:1 either way */
  --edge:inset 0 1px 0 rgba(255,255,255,.55);/* same light, same angle: on a light ground the top edge is a sheen, not a white line */
  --bg:var(--feat-ground);                   /* :focus-visible is a two-stop ring whose inner stop is the ground it sits on */
  --ring:rgba(0,0,0,.45);                    /* 3.28:1 over the card */
  background:var(--feat-ground);color:var(--text);border-color:var(--border)}
/* The tick keeps its tile and loses its hue: --cat-emerald is 1.9:1 on #ededed, and the
   glyph carries "you get this" by shape, not by colour. Inside a flipped card the ink IS
   the accent. */
.price.feat li svg{color:var(--text)}
/* .btn-primary:hover goes to #fff, which on this card is a button that dissolves. */
.price.feat .btn-primary:hover{background:#000}

/* ---- pricing, staged like the hero (2026-08-03) ----
   Cards land on their rv stagger, each plan's checklist cascades in while the
   card settles, and the featured card's tag drops in last. One-shot only —
   the fleet board keeps the page's single loop. Resting state is complete for
   no-JS and reduced motion: every animation hides nothing until .rv-in lands. */
.price{position:relative}
.price .amt{font-variant-numeric:tabular-nums}
/* "Most popular" is a fact the reader did not ask for and cannot act on, which is exactly
   the soft info badge's job (DESIGN.md § Badges, § Pricing tiers). It used to wear
   --primary / --primary-fg — the near-white identity accent, i.e. the one fill this page
   reserves for its primary action — so a decorative label was dressed as a call to action
   it cannot honour: a Color-Is-Semantic violation. After the flip it would also have been
   #ededed on an #ededed card, and simply disappeared.
   Soft, never filled: filled is for a state someone has to act on.
   Opaque, never a tint: the badge straddles the card's top edge (top:-11px on a 22px box),
   so it is painted on two grounds at once and a translucent 14% would come out two-tone.
   See --info-soft / --info-line / --info-ink for what the bake is and why the ink deepens.
   Radius steps off --pill to --r-sm: a badge is a label, not a control, and a capsule is
   this page's shape for things you press. */
.price-tag{position:absolute;top:-11px;left:50%;transform:translate(-50%,0);display:inline-flex;align-items:center;height:22px;padding:0 11px;border-radius:var(--r-sm);background:var(--info-soft);border:1px solid var(--info-line);color:var(--info-ink);font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
@media (prefers-reduced-motion:no-preference){
  .rv-on .price.rv-in li{animation:planLine .45s var(--ease) both}
  .rv-on .price.rv-in li:nth-child(1){animation-delay:.30s}
  .rv-on .price.rv-in li:nth-child(2){animation-delay:.38s}
  .rv-on .price.rv-in li:nth-child(3){animation-delay:.46s}
  .rv-on .price.rv-in li:nth-child(4){animation-delay:.54s}
  .rv-on .price.rv-in li:nth-child(5){animation-delay:.62s}
  .rv-on .price.feat.rv-in .price-tag{animation:tagIn .5s var(--ease) .8s both}
}
@keyframes planLine{from{opacity:0;transform:translateY(6px)}}
@keyframes tagIn{from{opacity:0;transform:translate(-50%,7px)}}

/* ============ FAQ ============ */
.faq{display:grid;grid-template-columns:.85fr 1.15fr;gap:48px;padding:56px 40px}
.faq-l{position:sticky;top:84px;align-self:start} /* same rail as .feat-list, clears the 64px sticky nav */
.faq-l h2{font-size:var(--step);margin:14px 0}
.faq-l p{color:var(--muted);font-size:14.5px;max-width:36ch;margin-bottom:22px}
/* Monthly/annual switch above the pricing cards (MONETIZATION.md Phase 5).
   Deliberately the SAME pill vocabulary as .faq-tab below rather than a new
   control: it is the page's existing "pick one of these" shape, and inventing a
   second one for the pricing section would make the two read as different kinds
   of choice when they are the same kind. Centred, because the cards it governs
   are. Hidden until pricing.js confirms this box can actually sell a year. */
.billing-toggle{display:flex;justify-content:center;gap:8px;margin:0 0 28px}
.billing-opt{height:34px;padding:0 16px;border-radius:var(--pill);border:1px solid var(--border);background:transparent;color:var(--muted);font-family:var(--sans);font-size:13px;cursor:pointer;transition:.16s}
.billing-opt:hover{color:var(--text);background:var(--sunken)}
.billing-opt.on{background:var(--primary);color:var(--primary-fg);border-color:var(--primary)}
.billing-opt [data-save]{opacity:.8}
.faq-tabs{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px}
.faq-tab{height:34px;padding:0 15px;border-radius:var(--pill);border:1px solid var(--border);background:transparent;color:var(--muted);font-family:var(--sans);font-size:13px;cursor:pointer;transition:.16s}
.faq-tab:hover{color:var(--text);background:var(--sunken)}
.faq-tab.on{background:var(--primary);color:var(--primary-fg);border-color:var(--primary)}
.faq-item{border-bottom:1px solid var(--border-soft)}
.faq-q{width:100%;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px 4px;background:none;border:0;color:var(--text);font-family:var(--sans);font-size:16px;font-weight:500;text-align:left;cursor:pointer}
.faq-tog{width:28px;height:28px;flex-shrink:0;border-radius:50%;border:1px solid var(--border);display:grid;place-items:center;position:relative;transition:.2s}
.faq-tog::before,.faq-tog::after{content:"";position:absolute;background:var(--text);transition:transform .25s var(--ease),opacity .25s}
.faq-tog::before{width:11px;height:1.5px}
.faq-tog::after{width:1.5px;height:11px}
.faq-item.open .faq-tog{background:var(--primary)}
.faq-item.open .faq-tog::before,.faq-item.open .faq-tog::after{background:var(--primary-fg)}
.faq-item.open .faq-tog::after{transform:rotate(90deg);opacity:0}
.faq-a{display:grid;grid-template-rows:0fr;transition:grid-template-rows .3s var(--ease)}
.faq-item.open .faq-a{grid-template-rows:1fr}
.faq-a>div{overflow:hidden}
.faq-a p{padding:0 4px 22px;font-size:14px;color:var(--muted);line-height:1.55;max-width:60ch}
/* ---- faq, staged like the rest (2026-08-04) ----
   Tabs pop in first, questions cascade after the panel lands. Tab filtering
   toggles display, which replays the cascade for the filtered set — that one
   is user-initiated and still one-shot per click. The accordion's own
   open/close motion above is untouched; nothing loops. */
.faq-q:hover .faq-tog{border-color:var(--input);background:var(--sunken)}
@media (prefers-reduced-motion:no-preference){
  .rv-on .faq-r.rv-in .faq-tab{animation:vinLine .4s var(--ease) both}
  .rv-on .faq-r.rv-in .faq-tab:nth-child(1){animation-delay:.22s}
  .rv-on .faq-r.rv-in .faq-tab:nth-child(2){animation-delay:.28s}
  .rv-on .faq-r.rv-in .faq-tab:nth-child(3){animation-delay:.34s}
  .rv-on .faq-r.rv-in .faq-tab:nth-child(4){animation-delay:.40s}
  .rv-on .faq-r.rv-in .faq-tab:nth-child(5){animation-delay:.46s}
  .rv-on .faq-r.rv-in .faq-item{animation:vinLine .45s var(--ease) both}
  .rv-on .faq-r.rv-in .faq-item:nth-child(1){animation-delay:.40s}
  .rv-on .faq-r.rv-in .faq-item:nth-child(2){animation-delay:.46s}
  .rv-on .faq-r.rv-in .faq-item:nth-child(3){animation-delay:.52s}
  .rv-on .faq-r.rv-in .faq-item:nth-child(4){animation-delay:.58s}
  .rv-on .faq-r.rv-in .faq-item:nth-child(5){animation-delay:.64s}
  .rv-on .faq-r.rv-in .faq-item:nth-child(6){animation-delay:.70s}
  .rv-on .faq-r.rv-in .faq-item:nth-child(7){animation-delay:.76s}
  .rv-on .faq-r.rv-in .faq-item:nth-child(8){animation-delay:.82s}
  .rv-on .faq-r.rv-in .faq-item:nth-child(n+9){animation-delay:.88s}
}

/* ============ FINAL CTA ============
   The page opens on the hero glow and closes on the same light, this time
   contained by a panel so the last thing you scroll to is a component. */
.cta{position:relative;overflow:hidden;text-align:center;padding:72px 40px;margin:52px 40px;border-radius:var(--r-xl)}
.cta::before{content:"";position:absolute;inset:0;background:var(--glow);pointer-events:none}
.cta>*{position:relative}
.cta h2{font-size:var(--step);margin:14px 0}
.cta p{color:var(--muted);max-width:44ch;margin:0 auto 26px;font-size:16px}
.cta .actions{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
/* ---- final cta, staged like the rest (2026-08-04) ----
   The page's closing light TURNS ON as the panel lands (still the One-Light
   rule — same lamp, it just arrives), the copy cascades, and a "run finished"
   chip closes the story the hero's approval card opened. All one-shot. */
.cta-done{margin-top:22px;display:inline-flex;align-items:center;gap:8px;padding:7px 12px;background:var(--sunken);border:1px solid var(--border-soft);border-radius:var(--pill);font-family:var(--mono);font-size:11px;color:var(--faint)}
@media (prefers-reduced-motion:no-preference){
  .rv-on .cta.rv-in::before{animation:lampOn 1.4s var(--ease) both}
  .rv-on .cta.rv-in .kicker{animation:vinLine .5s var(--ease) .15s both}
  .rv-on .cta.rv-in h2{animation:vinLine .5s var(--ease) .25s both}
  .rv-on .cta.rv-in p{animation:vinLine .5s var(--ease) .35s both}
  .rv-on .cta.rv-in .actions{animation:vinLine .5s var(--ease) .45s both}
  .rv-on .cta.rv-in .cta-done{animation:vinLine .5s var(--ease) .65s both}
}
@keyframes lampOn{from{opacity:0}}

/* ============ FOOTER ============ */
.foot{padding:56px 40px 26px}
.foot-grid{display:grid;grid-template-columns:1.4fr repeat(4,1fr);gap:32px}
.foot .fb .brand{margin-bottom:14px}
.foot .fb p{font-size:13px;color:var(--muted);max-width:30ch;margin-bottom:16px}
.foot .socials{display:flex;gap:10px}
.foot .socials a{width:32px;height:32px;border-radius:var(--r-md);border:1px solid var(--border);display:grid;place-items:center;color:var(--muted)}
.foot .socials a:hover{color:var(--text);border-color:var(--input)}
.foot-col :is(h3,h4){font-size:12px;font-family:var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--faint);margin-bottom:14px;font-weight:500}
.foot-col a{display:block;font-size:13.5px;color:var(--muted);padding:5px 0}
.foot-col a:hover{color:var(--text)}
.foot-tm{margin-top:38px;padding-top:20px;border-top:1px solid var(--border-soft);font-size:11.5px;line-height:1.6;color:var(--faint);max-width:92ch}
.foot-tm h3{font-size:12px;font-family:var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--faint);margin-bottom:8px;font-weight:500}
.foot-base{display:flex;justify-content:space-between;align-items:center;margin-top:20px;padding-top:20px;border-top:1px solid var(--border-soft);font-size:12.5px;color:var(--faint)}
.foot-base .ops{display:flex;align-items:center;gap:7px}
.foot-base .ops::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--live);box-shadow:0 0 8px var(--live)}
.foot-base.in .ops::before{animation:opsBreath 3.4s var(--ease) infinite} /* the page's other live claim, breathing at rest */
@keyframes opsBreath{0%,100%{box-shadow:0 0 6px var(--live);opacity:.82}50%{box-shadow:0 0 13px var(--live);opacity:1}}
/* ---- footer, staged like the rest (2026-08-04) ----
   Brand block and the three columns cascade one-shot after the footer lands,
   with the trademark notice and baseline arriving last. The footer is a
   shared partial and this CSS ships in home.css, so every marketing page
   gets the identical chrome AND the identical entrance. The ops dot keeps
   its sanctioned breath; nothing else loops. */
@media (prefers-reduced-motion:no-preference){
  .rv-on .foot.rv-in .foot-grid>*{animation:vinLine .5s var(--ease) both}
  .rv-on .foot.rv-in .foot-grid>*:nth-child(1){animation-delay:.15s}
  .rv-on .foot.rv-in .foot-grid>*:nth-child(2){animation-delay:.25s}
  .rv-on .foot.rv-in .foot-grid>*:nth-child(3){animation-delay:.35s}
  .rv-on .foot.rv-in .foot-grid>*:nth-child(4){animation-delay:.45s}
  .rv-on .foot.rv-in .foot-grid>*:nth-child(5){animation-delay:.55s} /* the 5th column the grid always had a track for */
  .rv-on .foot.rv-in .foot-tm{animation:vinLine .5s var(--ease) .65s both}
  .rv-on .foot.rv-in .foot-base{animation:vinLine .5s var(--ease) .75s both}
}

/* ============ APP-SURFACE PREVIEW ============ */
.app-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px}
.sess{border-radius:var(--r-lg);padding:16px;display:flex;flex-direction:column;gap:11px}
.sess .top{display:flex;align-items:center;gap:8px}
.sess .eng{width:24px;height:24px;border-radius:var(--r-md);display:grid;place-items:center;font-family:var(--mono);font-size:11px;font-weight:500}
.sess .eng.claude,.acct-av.claude{background:#d97757;color:#1a0e08}
.sess .eng.codex,.acct-av.openai{background:var(--primary);color:var(--primary-fg)}
.sess .eng.grok{background:#111;color:#fff;border:1px solid var(--border)}
.sess .title{font-size:14px;font-weight:500}
.sess .path{font-family:var(--mono);font-size:11px;color:var(--faint)}
.sess .foot{display:flex;justify-content:space-between;align-items:center;font-family:var(--mono);font-size:11px;color:var(--faint)}
.composer{border-radius:var(--r-xl);padding:14px 16px;display:flex;flex-direction:column;gap:12px}
.composer .ta{font-family:var(--mono);font-size:13px;color:var(--muted);min-height:44px}
.composer .cbar{display:flex;align-items:center;gap:8px}
.composer .cbar .sel{height:30px;padding:0 10px;border:1px solid var(--border);border-radius:var(--r-md);font-family:var(--mono);font-size:11.5px;color:var(--muted);display:flex;align-items:center;gap:6px}
.composer .send{margin-left:auto}
.diff{border:1px solid var(--border);border-radius:var(--r-md);overflow:hidden;font-family:var(--mono);font-size:12px}
.diff .dh{display:flex;justify-content:space-between;padding:9px 12px;border-bottom:1px solid var(--border-soft);color:var(--muted)}
.diff pre{padding:12px;line-height:1.6;color:var(--muted);overflow-x:auto}
.diff .add{color:var(--live);background:rgba(10,200,100,.08);display:block}
.diff .del{color:var(--danger);background:rgba(255,101,104,.08);display:block}

/* ============ RESPONSIVE ============ */
/* The nav's squeeze band. .nav-links only disappears at 900px, so between there and
   ~1060px the full row — brand + every link + three actions — has to fit on one line,
   and it does not: at 901px the 7-link nav already pushed 10px of horizontal scroll onto
   the whole page, and adding /about took that to 72px. Nothing wraps (the row is flex
   with no wrap), so the overflow is invisible on a wide monitor and shows up only as a
   page that scrolls sideways on a small laptop. Buy the width back from padding and the
   nav's own gap instead of dropping a link. Must precede the 900px block: below 900 the
   links are display:none and these rules are moot, but the narrower query has to win. */
@media(max-width:1060px){
  .nav{gap:16px}
  .nav-links a{padding:7px 7px}
  /* The sign-up pill gives back its 4px of extra side padding here. Linear's
     8/16 is what it wears where there is room; inside the squeeze band it drops
     to .btn-sm's 12 like every other button, because 8px of chrome is 8px the
     nine links do not have. tests/nav-squeeze.spec.js measured the pill spending
     more than the budget had left at 941px. */
  .btn.btn-signup{padding:0 12px}
}
/* The last of that budget, spent by /docs. Nine links no longer clear 901px on
   padding alone (39px short, measured), so the bottom of the band also gives up
   the link gap and 0.5px of tracking. Scoped to 940px so the type stays at its
   designed metrics everywhere it fits, and so the next reader knows the room ran
   out here rather than finding it quietly narrower at 1280. Past this, a tenth
   link means dropping one instead — tests/nav-squeeze.spec.js is the gate. */
@media(max-width:940px){
  .nav{gap:12px}
  .nav-links{gap:0}
  .nav-links a{padding:7px 6px;letter-spacing:-.015em}
}
@media(max-width:900px){
  .hero,.showcase,.faq,.foot-grid{grid-template-columns:1fr}
  .pricing,.stats,.tstis,.steps{grid-template-columns:1fr}
  /* minmax(0,…), not 1fr — same reason .u-metrics gives further down. `1fr` is
     minmax(AUTO,1fr), and that auto floor is the track's min-content, so one
     unbreakable child sets the width of the whole column. The approvals cell
     holds .pushrow, whose notification line is white-space:nowrap with an
     ellipsis ready for exactly this: at 320px its min-content is 273px, the
     track grew to 319 to seat it, and the ellipsis never fired — the page got
     20px of horizontal scroll instead. Flooring the track at 0 hands the row a
     definite width to shrink into, which is the truncation .push-tx already
     asked for with min-width:0. */
  .bento{grid-template-columns:minmax(0,1fr)}
  .bento .cell.wide{grid-column:auto} /* a span-2 child manufactures a 2nd implicit track even at 1fr */
  .stat,.tstis .t{border-left:0;border-top:1px solid var(--border-soft)}
  .stat:first-child,.tstis .t:first-child{border-top:0}
  .pricing.two{grid-template-columns:1fr} /* 0-2-0, must restate or the base rule keeps 2 cols */
  .feat-list,.faq-l{position:static}
  :root{--grid-cols:repeating-linear-gradient(90deg,transparent 0 calc(25% - 1px),var(--grid) calc(25% - 1px) 25%)} /* 8 columns is a mesh on a phone; drop to 4 */
  .nav-links{display:none}
  .nav-actions{margin-left:auto} /* .nav-links carried the auto margin; it is hidden here */
  .lab-head{flex-direction:column;gap:4px}.lab-head .d{text-align:left;margin-left:0}
}
@media(max-width:600px){
  .nav{gap:12px;padding:0 16px}
  .nav-actions{gap:8px}
  /* One nav CTA on a phone. It used to be the sign-up that survived, because the
     sign-up was the filled one; the Linear pass swapped which button carries the
     fill, so it is the sign-up that folds now. Both are still reachable in the
     hero and the footer, and /dashboard has to stay because tests/vs-pages.spec.js
     asserts a phone can reach it from the header. */
  .nav-actions .login,.nav-actions .btn-signup{display:none}
  .btn,.btn-sm{height:auto;min-height:44px;padding-top:6px;padding-bottom:6px}
  .faq-tab{height:auto;min-height:44px}
  .mini-fleet{grid-template-columns:1fr} /* 3 tracks clip the agent paths below ~600px */
}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ============ USAGE ============ */
.usage{padding:24px;max-width:1400px}
.usage h1{font-size:22px;font-weight:600;letter-spacing:-.03em;margin-bottom:4px}
.usage .lead{color:var(--muted);font-size:13px;margin-bottom:20px;font-family:var(--mono)}
.u-metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:14px}
.u-metric{border-radius:var(--r-lg);padding:16px} /* the accent border-top per metric is inline in the markup */
.u-metric .ml{font-size:11.5px;color:var(--faint);font-family:var(--mono);text-transform:uppercase;letter-spacing:.05em}
.u-metric .mv{font-size:26px;font-weight:600;letter-spacing:-.03em;margin-top:8px;font-variant-numeric:tabular-nums}
.u-metric .mn{font-size:11.5px;color:var(--muted);margin-top:4px}
.u-analytics{display:grid;grid-template-columns:1.4fr 1fr;gap:12px;margin-bottom:14px}
.u-card{border-radius:var(--r-lg);padding:16px}
.u-card-h{display:flex;justify-content:space-between;font-size:12px;color:var(--muted);margin-bottom:16px;font-family:var(--mono)}
.u-chart{display:flex;align-items:flex-end;gap:5px;height:120px;position:relative;background:linear-gradient(rgba(255,255,255,.05) 1px,transparent 1px) 0 0/100% 30px}
.u-bar{flex:1;background:linear-gradient(180deg,var(--cat-blue),rgba(96,165,250,.18));border-radius:var(--r-sm) var(--r-sm) 0 0;transform-origin:bottom}
.u-bar:hover{filter:brightness(1.25)}
.u-bar.peak{background:linear-gradient(180deg,var(--live),rgba(10,200,100,.2));box-shadow:0 0 14px rgba(10,200,100,.35)}
/* the bars grow when you arrive at the chart, not four screens earlier. growBar
   was referenced here for weeks without ever being defined, so the entrance has
   never actually run; bars stay at full height when the observer never fires. */
.u-chart.in .u-bar{animation:growBar .55s var(--ease) both}
@keyframes growBar{from{transform:scaleY(.04);opacity:.3}to{transform:scaleY(1);opacity:1}}
.u-chart.in .u-bar:nth-child(2){animation-delay:.04s}.u-chart.in .u-bar:nth-child(3){animation-delay:.08s}.u-chart.in .u-bar:nth-child(4){animation-delay:.12s}.u-chart.in .u-bar:nth-child(5){animation-delay:.16s}.u-chart.in .u-bar:nth-child(6){animation-delay:.2s}.u-chart.in .u-bar:nth-child(7){animation-delay:.24s}.u-chart.in .u-bar:nth-child(8){animation-delay:.28s}.u-chart.in .u-bar:nth-child(9){animation-delay:.32s}.u-chart.in .u-bar:nth-child(10){animation-delay:.36s}.u-chart.in .u-bar:nth-child(11){animation-delay:.4s}.u-chart.in .u-bar:nth-child(12){animation-delay:.44s}
.u-axis{display:flex;justify-content:space-between;font-family:var(--mono);font-size:10px;color:var(--faint);margin-top:8px}
/* ---- usage showcase, staged like the rest (2026-08-04) ----
   The metric tiles cascade in after the framed panel lands, and the peak bar
   names its number once the bars have grown. The chart and model-mix
   entrances above already run on the .in observer; everything stays one-shot
   and the resting panel is complete for no-JS and reduced motion. */
.u-bar.peak{position:relative}
.u-bar.peak::after{content:"$31";position:absolute;top:-20px;left:50%;transform:translateX(-50%);font-family:var(--mono);font-size:10px;color:var(--live)}
@media (prefers-reduced-motion:no-preference){
  .rv-on .rv-in .u-metric{animation:vinLine .45s var(--ease) both}
  .rv-on .rv-in .u-metric:nth-child(1){animation-delay:.25s}
  .rv-on .rv-in .u-metric:nth-child(2){animation-delay:.35s}
  .rv-on .rv-in .u-metric:nth-child(3){animation-delay:.45s}
  .rv-on .rv-in .u-metric:nth-child(4){animation-delay:.55s}
  .u-chart.in .u-bar.peak::after{animation:peakTag .5s var(--ease) .8s both}
}
@keyframes peakTag{from{opacity:0;transform:translate(-50%,4px)}}
.u-models{display:flex;flex-direction:column;gap:12px}
.u-model{display:grid;grid-template-columns:120px 1fr 60px;align-items:center;gap:10px;font-size:12px}
.u-model .nm{display:flex;align-items:center;gap:7px;color:var(--muted)}
.u-model .nm i{width:8px;height:8px;border-radius:50%}
.u-model .tk{height:6px;border-radius:var(--pill);background:rgba(255,255,255,.08);overflow:hidden}
.u-model .tk i{display:block;height:100%;transform-origin:left}
/* the model mix arrives with the chart beside it, so the panel reads as one
   readout waking up rather than half a panel moving. scaleX, never width. */
.u-models.in .tk i{animation:sweepIn .5s var(--ease) both}
@keyframes sweepIn{from{transform:scaleX(0)}to{transform:scaleX(1)}}
.u-models.in .u-model:nth-child(2) .tk i{animation-delay:.07s}.u-models.in .u-model:nth-child(3) .tk i{animation-delay:.14s}.u-models.in .u-model:nth-child(4) .tk i{animation-delay:.21s}
.u-model .ct{text-align:right;font-family:var(--mono);color:var(--muted);font-variant-numeric:tabular-nums}
.u-table{border-radius:var(--r-lg);overflow:hidden}
.u-table-h{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid var(--border-soft)}
.u-table-h b{font-size:14px;font-weight:600}.u-table-h span{font-size:11.5px;color:var(--faint);font-family:var(--mono)}
.u-cols,.u-row{display:grid;grid-template-columns:2.4fr 1fr 1.4fr .8fr 1.2fr 1fr;gap:12px;align-items:center;padding:11px 16px}
.u-cols{font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--faint);border-bottom:1px solid var(--border-soft)}
.u-row{border-bottom:1px solid var(--border-soft);font-size:12.5px}
.u-row:last-child{border-bottom:0}
.u-row .pj{display:flex;align-items:center;gap:9px;min-width:0}
.u-row .pj .ci{width:24px;height:24px;border-radius:var(--r-md);background:var(--muted-surface);display:grid;place-items:center;font-family:var(--mono);font-size:11px;font-weight:500;flex-shrink:0}
.u-row .pj .pn{font-weight:500}.u-row .pj .pp{font-family:var(--mono);font-size:10px;color:var(--faint)}
.u-row .num{font-family:var(--mono);text-align:right;font-variant-numeric:tabular-nums;color:var(--muted)}
.u-row .cost{font-family:var(--mono);text-align:right;color:var(--text);font-weight:500}


/* ---- homepage structural helpers ---- */
.hsec{position:relative;border-top:1px solid var(--border-soft)}
main>.hsec:first-child{border-top:0} /* :first-of-type also matched the footer, which is the only <footer> */
/* ---- links wear one mark ----
   The rule under a link is brand green, the same green as the ›_ chip, and
   nothing else on this page uses it. That keeps it clear of the status greens:
   --live means a session is running, this means you can act on it. Text stays
   monochrome, so the colour lives entirely in the 1px rule. Thickness is fixed
   rather than auto so the rule stays hairline inside a 19px heading too. */
.engine-link{color:var(--muted);text-decoration:underline;text-decoration-color:var(--link-rule);text-decoration-thickness:1px;text-underline-offset:.16em;text-decoration-skip-ink:auto;transition:color .15s,text-decoration-color .15s}
.engine-link:hover{color:var(--text);text-decoration-color:var(--brand-green-400)}
/* in a heading the heading owns the colour: muted link text inside a white
   headline reads as words that got dimmer, not as links. The rule alone marks
   them, which is the whole point of putting the colour in the rule. */
:is(h1,h2,h3) .engine-link{color:inherit}
:is(h1,h2,h3) .engine-link:hover{color:inherit;text-decoration-color:var(--brand-green-400)}
/* inside a category pill the pill's hue owns the link; a third colour in a chip
   that small reads as a mistake */
.cat .engine-link{color:inherit;text-decoration-color:color-mix(in srgb,currentColor 45%,transparent)}
.cat .engine-link:hover{color:inherit;text-decoration-color:currentColor}
/* footer columns are navigation, not prose: the rule arrives on hover */
.foot-col a{text-decoration:underline;text-decoration-color:transparent;text-decoration-thickness:1px;text-underline-offset:.16em;transition:color .15s,text-decoration-color .15s}
.foot-col a:hover{color:var(--text);text-decoration-color:var(--brand-green-400)}
code{font-family:var(--mono);font-size:.86em;background:var(--muted-surface);padding:1px 5px;border-radius:var(--r-sm)}
.usage-wrap{padding:52px 40px}
.usage-head{margin-bottom:26px}
.compare{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:52px 40px;position:relative}
.compare .col{border-radius:var(--r-lg);padding:24px}
.compare .col h3{font-size:14px;font-family:var(--mono);font-weight:500;text-transform:uppercase;letter-spacing:.06em;color:var(--faint);margin-bottom:14px}
.compare .col.after h3{color:var(--text)}
.compare ul{list-style:none;display:flex;flex-direction:column;gap:11px}
.compare li{display:flex;gap:10px;font-size:14px;color:var(--muted);align-items:flex-start}
/* the same sunken icon tiles as the hero and pricing — one "have / lack" grammar */
.compare li svg{width:20px;height:20px;padding:4px;background:var(--sunken);border:1px solid var(--border-soft);border-radius:var(--r-md);flex-shrink:0;margin-top:0}
.compare .before li svg{color:var(--faint)}
.compare .after li svg{color:var(--cat-emerald)}
/* ---- compare, staged like the rest (2026-08-04) ----
   The pains cascade in first, the answers trail on the after column's own rv
   delay, and a VS marker floats on the seam. All one-shot; nothing loops. */
.compare::after{content:"VS";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:34px;height:34px;border-radius:50%;background:var(--panel-2);border:1px solid var(--border);display:grid;place-items:center;font-family:var(--mono);font-size:10px;letter-spacing:.08em;color:var(--muted);box-shadow:var(--edge),var(--lift)}
@media(max-width:900px){.compare::after{display:none}} /* stacked columns have no seam */
@media (prefers-reduced-motion:no-preference){
  .rv-on .compare .col.rv-in li{animation:vinLine .45s var(--ease) both}
  .rv-on .compare .col.rv-in li:nth-child(1){animation-delay:.3s}
  .rv-on .compare .col.rv-in li:nth-child(2){animation-delay:.38s}
  .rv-on .compare .col.rv-in li:nth-child(3){animation-delay:.46s}
  .rv-on .compare .col.rv-in li:nth-child(4){animation-delay:.54s}
}
.sec-head{padding:52px 40px 0}
.sec-head .kicker{font-family:var(--mono);font-size:12px;color:var(--muted)}
.sec-head h2{font-size:var(--step);margin:14px 0}
.sec-head p{color:var(--muted);max-width:60ch;font-size:15.5px}
.video-frame,.shot-frame{margin:26px 40px 52px}
/* A screenshot sits in the same raised-panel-over-media-well seat the film
   uses: same .frame recipe, same --r-md inner radius, so the two pieces of
   media on this page read as one treatment rather than two. The intrinsic
   width/height on the <img> reserve the box, so nothing below it moves when
   the bytes land. */
.shot-frame img{width:100%;height:auto;display:block;border-radius:var(--r-md)}
.shot-frame figcaption{margin-top:14px;text-align:center;
  font-family:var(--mono);font-size:12px;color:var(--faint)}
.shot-pan-hint{display:none}
/* PANNING THE SHOT ON A TOUCH SCREEN
   This one picture is a desktop canvas four windows wide. Fit to a 390px
   viewport it renders at a fifth of capture scale, which is texture, not
   information — you can see that there are four sessions and read nothing in
   any of them. So at touch widths it stops shrinking and starts panning: the
   well scrolls sideways and the image holds a floor of 1000px, about 0.6 of
   capture scale, which is the point where the transcript type comes back.
   1000px, not more: width drives the box and height follows the ratio, so
   every pixel of extra scale is also a taller block on a short screen. At the
   floor the frame is ~595px — most of a phone, which a deliberate media moment
   can spend, and not more than one.
   max(100%, …) rather than a flat width because the floor has to lose to the
   container once the container is wider: a fixed 1000px would leave a strip of
   bare well beside the image between 900px and the breakpoint.
   overflow-y stays hidden, which is what keeps a vertical swipe scrolling the
   page rather than being eaten here, and overscroll-behavior-x:contain stops a
   pan that reaches the end from turning into the browser's back gesture.
   The image's own radius goes with it: .frame-inner already clips to 15px, and
   a rounded corner arriving mid-pan reads as a rendering fault.
   900px is the boundary because it is where the two treatments cross: below
   it, fit-to-width renders smaller than the panned floor does. */
@media(max-width:900px){
  .shot-frame .frame-inner{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;
    -webkit-overflow-scrolling:touch}
  .shot-frame img{width:max(100%,1000px);max-width:none;height:auto;border-radius:0}
  /* An image that overflows its box says "there is more" only if you notice
     the cut. Say it in words too — this is the one place on the page where a
     reader has to do something to see the content. */
  .shot-pan-hint{display:block;margin-top:6px;color:var(--muted)}
}
/* height:auto lets the intrinsic 1920x1080 from the width/height attrs drive
   aspect-ratio, so the player reserves its box before metadata loads (no CLS) */
.video-frame video{width:100%;height:auto;display:block;border-radius:var(--r-md)}
/* The Bunny Stream iframe has no intrinsic ratio, so the same no-CLS seat is
   declared here; black backfills the box until the lazy player document lands */
.video-frame iframe{width:100%;aspect-ratio:16/9;display:block;border:0;border-radius:var(--r-md);background:#000}
/* ---- the long-tail grid (#everything-else). Every value is an existing rung:
   the bento's 16px gap and 40px gutter, the shot-frame caption register one
   size down. Tiles reuse the .frame recipe untouched: one media treatment per
   page. */
.tail-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:26px 40px 52px}
.tail-shot{margin:0}
.tail-shot img{width:100%;height:auto;display:block;border-radius:var(--r-md)}
.tail-shot figcaption{margin-top:8px;text-align:center;font-family:var(--mono);font-size:11px;color:var(--faint)}
@media(max-width:900px){.tail-grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:600px){.tail-grid{padding-left:20px;padding-right:20px}}
@media(max-width:900px){.compare{grid-template-columns:1fr}}
/* phone gutters — same trailing-block rule as below: the section paddings are
   declared at the end of this file, so an earlier media query would lose */
@media(max-width:600px){
  .hero-phone{right:-6px;bottom:-16px;width:206px}
  .hero,.compare,.bento,.steps,.faq,.sec-head,.pricing-head,.pricing,.pricing.two,.usage-wrap,.foot{padding-left:20px;padding-right:20px}
  .video-frame,.shot-frame{margin-left:20px;margin-right:20px}
  .stats{margin:22px 20px 36px}
  .cta{margin:36px 20px;padding:56px 20px}
}
/* usage-panel mobile reflow — must sit after the .u-* rules above, media queries add no specificity */
@media(max-width:640px){
  .u-metrics{grid-template-columns:repeat(2,minmax(0,1fr))} /* minmax(0,…) or the long labels blow the track out */
  .u-analytics{grid-template-columns:1fr}
  .u-model{grid-template-columns:90px 1fr 56px}
  .u-metric .mv{font-size:21px} /* $284.62 at 26px is wider than the half-width tile */
}

/* ============ ACCOUNTS + SAVINGS (homepage sections) ============
   The switcher mock follows the usage panel's grammar: sunken rows inside a
   framed well, meters in the .appwin recipe, status colour only where it is
   semantic — green is the account you are on, red is the meter that ran dry. */
.acct-wrap{padding:26px 40px 52px;max-width:900px;margin:0 auto}
.acct-list{display:flex;flex-direction:column;gap:9px}
.acct-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:18px;padding:13px 16px;background:var(--sunken);border:1px solid var(--border-soft);border-radius:var(--r-md)}
.acct-row.on{border-color:rgba(10,200,100,.28)}
.acct-row.warn{border-color:rgba(252,187,0,.28)}
.acct-id{display:flex;align-items:center;gap:11px;min-width:0}
.acct-av{width:28px;height:28px;border-radius:var(--r-md);display:grid;place-items:center;font-family:var(--mono);font-size:12px;font-weight:500;font-style:normal;flex:none}
.acct-who{min-width:0}
.acct-who b{display:block;font-size:13px;font-weight:500;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.acct-who small{display:block;font-family:var(--mono);font-size:10.5px;color:var(--faint)}
.acct-meters{display:flex;gap:22px;justify-content:flex-end}
.acct-m{display:grid;grid-template-columns:auto 90px auto;align-items:center;gap:8px;font-family:var(--mono);font-size:10.5px;color:var(--faint)}
.acct-m .meter{height:5px;border-radius:var(--pill);background:rgba(255,255,255,.08);overflow:hidden}
.acct-m .meter i{display:block;height:100%;border-radius:var(--pill);background:var(--muted)}
.acct-row.on .meter i{background:var(--live)}
.acct-m.m-hot .meter i{background:var(--danger)}
.acct-m em{font-style:normal;font-variant-numeric:tabular-nums;min-width:30px;text-align:right}
.acct-chip{font-family:var(--mono);font-size:10.5px;letter-spacing:.04em;padding:5px 10px;border-radius:var(--pill);border:1px solid var(--border);color:var(--muted);white-space:nowrap}
.acct-chip.live{color:var(--live);border-color:rgba(10,200,100,.3)}
.acct-chip.go{color:var(--text);background:var(--muted-surface)}
.acct-note{margin-top:12px;font-family:var(--mono);font-size:11px;color:var(--faint);text-align:center}
.stats.three{grid-template-columns:repeat(3,1fr)}
.stat .v small{font-size:13px;font-weight:400;color:var(--faint);letter-spacing:0}
.save-note{padding:0 40px 52px;font-family:var(--mono);font-size:11.5px;color:var(--faint);max-width:82ch}
@media(max-width:900px){.stats.three{grid-template-columns:1fr}}
@media(max-width:720px){
  .acct-row{grid-template-columns:1fr auto;row-gap:10px}
  .acct-meters{grid-column:1 / -1;justify-content:flex-start;flex-wrap:wrap;gap:12px}
}
@media(max-width:600px){.acct-wrap,.save-note{padding-left:20px;padding-right:20px}}

/* ============ LOAD-IN REVEAL ============
   The entrance: every .rv rests a step low and slides up to its seat when it
   enters the viewport — hero on load, sections as you reach them, siblings
   staggered by the markup's own --rvd. Gated on .rv-on, which only the head
   script sets, so a no-JS reader (and reduced motion, via this media query)
   gets the resting page with nothing hidden. The hero screenshot is the LCP
   element, so .rv-move slides without fading: an opacity:0 LCP candidate does
   not count as painted until it fades in, and that read as a slow page. */
@media (prefers-reduced-motion:no-preference){
  .rv-on .rv{opacity:0;transform:translateY(24px)}
  .rv-on .rv-move{opacity:1;transform:translateY(44px)}
  .rv-on .rv-fade{transform:none}
  .rv-on .rv.rv-in{opacity:1;transform:none;
    transition:opacity 1s cubic-bezier(.22,1,.36,1) var(--rvd,0s),transform 1s cubic-bezier(.22,1,.36,1) var(--rvd,0s)}
  .rv-on .rv-move.rv-in{transition-duration:1.2s,1.2s}
}

/* ===== AUTH (/login, /signup) ==============================================
   The seam between the landing and the console. It inherits this file's world
   whole — the lit column, the hairline rails, the column grid — and adds no new
   scale, radius or hue. The page's ONE warm light lives on .auth-wrap, which is
   also the top of the page, so the One-Light Rule holds without an exception.

   The card is the app's own instrument grammar, not a marketing panel: flat,
   hairline, --panel with --edge/--lift, and every nested field on --well with no
   shadow of its own (Nested-Never-Raised). Labels and controls are Geist Mono
   because that is the working typeface; the fields themselves are sans, since an
   email a user is proofreading for a typo is prose, not a value to verify.

   No green dot on the primary here. A green dot means live, and nothing on this
   page is running — decorating the button with one would be a Color-Is-Semantic
   bug. The filled action stays the marketing surface's white --primary, matching
   the CTA the visitor just clicked to arrive.
   ========================================================================== */
.auth-wrap{position:relative;padding:clamp(36px,7vh,80px) 20px clamp(48px,8vh,88px);display:flex;flex-direction:column;
  align-items:center;justify-content:center;min-height:calc(100vh - 64px)}
.auth-wrap::before{content:"";position:absolute;inset:0;background:var(--glow);pointer-events:none}
.auth-wrap>*{position:relative}

/* The split shell (the community "Simple Sign Up Screen" two-pane layout in
   this file's grammar): brand pane left on --well — a media surface holding
   the board vignette, Nested-Never-Raised so it casts nothing — form pane
   right on the shell's own --panel. One card, one border, one lift: the
   panes are regions of it, never two cards. */
.auth-shell{width:100%;max-width:980px;display:grid;grid-template-columns:1.08fr 1fr;
  background:var(--panel);border:1px solid var(--border-soft);border-radius:var(--r-xl);
  box-shadow:var(--edge),var(--lift-2);overflow:hidden}
.auth-side{display:flex;flex-direction:column;background:var(--well);
  border-right:1px solid var(--border-soft);padding:clamp(26px,3.4vw,40px)}
.auth-side-brand{display:inline-flex;align-items:center;gap:9px;align-self:flex-start;
  font-weight:600;font-size:15.5px;letter-spacing:-.02em}
.auth-side-brand img{border-radius:var(--r-md)}
.auth-side-body{margin:auto 0;padding:30px 0}
.auth-side-body h2{font-size:clamp(22px,2.4vw,27px);line-height:1.14;max-width:16ch}
.auth-side-body p{color:var(--muted);font-size:13.5px;line-height:1.6;margin-top:12px;max-width:36ch}
/* The vignette: two board rows at postage-stamp scale. Colour stays the
   product's semantics — amber waits on you, green is live. */
.auth-scene{display:flex;flex-direction:column;gap:8px;margin-top:26px;max-width:340px}
.auth-sess{display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--sunken);
  border:1px solid var(--border-soft);border-radius:var(--r-md)}
.auth-dot{flex:none;width:7px;height:7px;border-radius:50%}
.auth-dot.busy{background:var(--busy)}
.auth-dot.live{background:var(--live)}
.auth-sess-text{display:flex;flex-direction:column;gap:1px;min-width:0}
.auth-sess-text b{font-size:12.5px;font-weight:500;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.auth-sess-text i{font-style:normal;font-family:var(--mono);font-size:10.5px;color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.auth-chip{margin-left:auto;flex:none;font-family:var(--mono);font-size:10px;letter-spacing:.04em;
  padding:3px 7px;border-radius:var(--pill);border:1px solid var(--border-soft)}
.auth-chip.busy{color:var(--busy);border-color:rgba(252,187,0,.3)}
.auth-chip.live{color:var(--live);border-color:rgba(10,200,100,.3)}
.auth-main{display:flex;flex-direction:column;justify-content:center;padding:clamp(24px,3.4vw,42px) clamp(22px,3.2vw,40px)}
/* Panels inside the shell wear no chrome of their own — the shell is the card. */
.auth-shell .auth-card{max-width:400px;margin:0 auto;width:100%;padding:0;
  background:none;border:none;box-shadow:none;border-radius:0}
.auth-oauth{width:100%;justify-content:center;margin-top:22px}

.auth-card{width:100%;max-width:452px;border-radius:var(--r-lg);padding:clamp(24px,4vw,34px)}
.auth-card h1{font-size:23px;font-weight:600;letter-spacing:-.028em;line-height:1.15}
.auth-card .sub{color:var(--muted);font-size:13.5px;margin-top:8px;line-height:1.5}

/* Fields. The label sits ON the well's top edge rather than floating above it,
   so a stack of four reads as one instrument instead of eight loose rows. */
.auth-form{display:flex;flex-direction:column;gap:13px;margin-top:22px}
.field{display:flex;flex-direction:column;gap:6px}
.field label{font-family:var(--mono);font-size:11.5px;letter-spacing:.03em;color:var(--muted)}
.field input{height:42px;padding:0 13px;background:var(--well);border:1px solid var(--border);border-radius:var(--r-md);
  font-family:var(--sans);font-size:14.5px;color:var(--text);width:100%;transition:border-color .14s,background .14s}
.field input::placeholder{color:var(--faint)}
/* A text field is identified by its boundary and nothing else, which is the one
   place WCAG 2.2 SC 1.4.11 asks 3:1 of an edge, so the resting border is the app's
   --workspace-control-border (5.9:1 on --well) rather than the grouping hairline
   every card uses. Linear's own input sits on rgba(255,255,255,.08) at about
   1.3:1 and cannot carry that job; its FOCUS behaviour, "border brightens to
   #d0d6e0", is copied exactly, because #d0d6e0 is --muted here. */
.field input{border-color:var(--workspace-control-border)}
.field input:hover,.field input:focus{border-color:var(--muted)}
.field input:focus{background:var(--well);outline:none;box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--ring)}
.field input[aria-invalid="true"]{border-color:rgba(255,101,104,.5)}
.field .hint{font-family:var(--mono);font-size:11px;color:var(--faint)}
.auth-form .btn{width:100%;justify-content:center;margin-top:3px}

/* The Turnstile holder. Hidden until a password has been typed (public/js/turnstile.js),
   so the resting form is unchanged; min-height reserves the widget's own 65px the moment
   it is revealed, so the iframe arriving a beat later does not shove the submit button
   down under a cursor that is already on its way to it. Cloudflare owns everything inside
   — we set the width floor and nothing else. */
.captcha{display:flex;justify-content:center;align-items:center;min-height:65px}
.captcha[hidden]{display:none}
.captcha iframe{max-width:100%}

/* The forgot link rides the password label's row, so it costs no vertical space and
   cannot be mistaken for a field. It is positioned rather than placed there in markup:
   in DOM order it must come AFTER the input, or Tab from Email stops on the link
   instead of the password — which is the one tab step every returning user takes. */
.field{position:relative}
.field .forgot{position:absolute;top:0;right:0;font-family:var(--mono);font-size:11.5px;color:var(--muted);
  text-decoration:underline;text-decoration-color:var(--link-rule);text-decoration-thickness:1px;text-underline-offset:.16em}
.field .forgot:hover{color:var(--text);text-decoration-color:var(--brand-green-400)}

/* Leading icon + the eye ride INSIDE the well (community-reference fidelity):
   both are chrome of the control, so they take --faint and neither steals a
   tab stop before the input — the eye follows it in DOM order, like .forgot. */
.control{position:relative;display:flex;align-items:center}
.control .lead{position:absolute;left:12px;width:15px;height:15px;color:var(--faint);pointer-events:none}
.control input{padding-left:38px}
.control:has(.eye) input{padding-right:44px}
.eye{position:absolute;right:3px;top:50%;translate:0 -50%;display:flex;align-items:center;justify-content:center;
  width:38px;height:36px;background:none;border:none;border-radius:var(--r-md);color:var(--faint);cursor:pointer}
.eye svg{width:16px;height:16px}
.eye:hover,.eye[aria-pressed="true"]{color:var(--text)}

.auth-sep{display:flex;align-items:center;gap:14px;margin:20px 0;font-family:var(--mono);font-size:11px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.auth-sep::before,.auth-sep::after{content:"";flex:1;height:1px;background:var(--border-soft)}

/* Status. Errors name the problem; the tint is a wash, never a left bar. */
.auth-msg{display:none;margin-top:16px;padding:11px 13px;border-radius:var(--r-md);font-size:13px;line-height:1.5;border:1px solid transparent}
.auth-msg.show{display:block}
.auth-msg.err{color:var(--danger);background:rgba(255,101,104,.09);border-color:rgba(255,101,104,.28)}
.auth-msg.ok{color:var(--cat-emerald);background:rgba(52,211,153,.09);border-color:rgba(52,211,153,.26)}
.auth-msg.info{color:var(--muted);background:var(--sunken);border-color:var(--border-soft)}

/* The free tier, stated as a spec rather than a promise — these are the real
   caps from lib/cloud/billing.js PLANS.free, which is why they can be this flat. */
/* A 2x2 grid, not a wrapping row. Four items in a flex-wrap broke 3+1 and read as
   a layout accident; on a fixed grid the same four read as a spec table, which is
   what they are. */
.auth-spec{display:grid;grid-template-columns:1fr 1fr;gap:8px 14px;margin-top:20px;padding-top:18px;
  border-top:1px solid var(--border-soft);font-family:var(--mono);font-size:11.5px;color:var(--faint)}
/* The signup panel's copy of the spec shows only while the pane that carries
   the canonical one is folded away (two fixed nodes, one display rule). */
.auth-spec-m{display:none}
.auth-spec span{display:inline-flex;align-items:center;gap:6px}
.auth-spec svg{width:12px;height:12px;color:var(--cat-emerald)}

.auth-alt{margin-top:22px;font-size:13.5px;color:var(--muted);text-align:center}
.auth-alt a{color:var(--text);text-decoration:underline;text-decoration-color:var(--link-rule);
  text-decoration-thickness:1px;text-underline-offset:.16em}
.auth-alt a:hover{text-decoration-color:var(--brand-green-400)}
.auth-foot{margin-top:26px;font-family:var(--mono);font-size:11px;color:var(--faint);text-align:center;max-width:44ch;line-height:1.6}
.auth-foot a{color:var(--muted);text-decoration:underline;text-decoration-color:var(--link-rule);text-underline-offset:.16em}

/* Busy state: the label is replaced by its progressive verb, and the button
   holds its width so the card cannot reflow under the pointer mid-click. */
.btn[data-busy="true"]{pointer-events:none;opacity:.62}

/* The one authored moment on this page: a panel arriving. Exponential ease-out
   from an already-visible default, so a failed script or reduced motion shows
   the same honest card. */
.auth-panel[hidden]{display:none}
.auth-panel{animation:auth-in .34s var(--ease) both}
@keyframes auth-in{from{opacity:0;transform:translateY(6px);filter:blur(3px)}to{opacity:1;transform:none;filter:none}}
@media (prefers-reduced-motion:reduce){.auth-panel{animation:none}}

/* ---- staged arrival, same grammar as the homepage (2026-08-04) ----
   Pure CSS, no reveal.js on this page: the shell rises, the brand pane's
   board vignette cascades, and each panel settles heading-first. Panels
   toggle [hidden], so a panel switch replays its own cascade — still
   one-shot per view. Fast on purpose (the form is the errand), silenced
   wholesale by the reduced-motion kill above, complete without JS. */
.auth-shell{animation:authShell .55s var(--ease) both}
@keyframes authShell{from{opacity:0;transform:translateY(12px)}}
.auth-side-body h2{animation:vinLine .5s var(--ease) .15s both}
.auth-side-body p{animation:vinLine .5s var(--ease) .22s both}
.auth-scene .auth-sess:nth-child(1){animation:vinLine .5s var(--ease) .32s both}
.auth-scene .auth-sess:nth-child(2){animation:vinLine .5s var(--ease) .42s both}
.auth-side>.auth-spec{animation:vinLine .5s var(--ease) .52s both}
.auth-panel h1{animation:vinLine .4s var(--ease) .06s both}
.auth-panel .sub{animation:vinLine .4s var(--ease) .12s both}
.auth-panel .auth-oauth,.auth-panel .auth-sep,.auth-panel .auth-form,.auth-panel .auth-alt,.auth-panel .auth-spec-m{animation:vinLine .45s var(--ease) .18s both}

/* home.css folds .nav-actions .btn-signup below 600px because the homepage repeats
   that link in its hero. This page has no hero, so hiding it here would delete the
   only way to reach the signup without scrolling, which is the errand someone on
   this page is most likely on. */
.auth-page .nav-actions .btn-signup{display:inline-flex}

/* The brand pane folds first: the header's brand keeps the identity, the form
   keeps the errand, and the shell tightens to the single-card width. The
   signup panel's spec twin takes over from the folded pane's canonical copy. */
@media (max-width:880px){
  .auth-shell{grid-template-columns:1fr;max-width:452px}
  .auth-side{display:none}
  .auth-spec-m{display:grid}
}

@media (max-width:600px){
  /* The panel-switch link and "Forgot?" are the two primary actions on this page that are
     not buttons, and both were ~15px tall — well under the 44px DESIGN.md requires on
     mobile. Padding grows the hit area and an equal negative margin keeps the sentence
     sitting exactly where it did, so nothing reflows to buy the target. */
  .auth-alt a,.field .forgot{display:inline-block;padding:14px 8px;margin:-14px -8px}
  .field .forgot{top:-14px;right:-8px}
  /* 42px is two short of the floor, which is nothing on a mouse and real on a thumb. */
  .field input{height:44px}
  /* The eye is a mobile control too, so it takes the same floor. */
  .eye{width:44px;height:44px}
}

@media (max-width:560px){
  .auth-wrap{min-height:auto;padding-top:24px;justify-content:flex-start}
  /* Full-bleed on a phone: a bordered shell inside a 360px viewport is a card
     with margins, and the margins do nothing. */
  .auth-shell{border:none;background:none;box-shadow:none;border-radius:0}
  .auth-main{padding:4px 0}
}
