/* SKI Noir — built stylesheet. Generated by scripts/build-css.mjs; edit

   tokens.css / components.css instead. */

@import url('https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,600..800&family=Inter+Tight:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap');

/* ============================================================================
   SKI NOIR — TOKENS
   A dark, single-hue identity system for SKI. Structure and discipline are
   adapted from the Lunetra financial identity (Titiran Design, Behance
   #247664493); every value is re-anchored on SKI's own brand colors and the
   real SKI marks. Nothing here is copied from that project.

   THE THESIS IN ONE LINE
   One ground (black), one hue (Glacier blue), one family (Inter Tight),
   and a single gradient that does all the work light and depth would.

   Depth comes from LUMINANCE, not from adding colors. If a design here needs
   another color to read, the layout is wrong — fix the layout.

   Dark-first. :root is the dark theme; [data-theme="light"] overrides it.
   The brand gradients are IDENTICAL in both — see the LIGHT THEME block at
   the foot of this file for what changes and what deliberately does not.
   See README.md for how this relates to ../ski-design-system/.
   ========================================================================= */

:root{

  /* =========================================================================
     1. TYPEFACES

     ONE family for everything you set. Lunetra's discipline is a single
     neo-grotesk carrying every size from 132px to 10px, and that restraint
     is most of the effect. The wordmark is the sole exception, and it is an
     exception because it is a logo, not text.

       sans — Inter Tight. Everything. 300/400/500/600 only; never 700+.

       mono — IBM Plex Mono. SKI is a developer tool, so real terminal
              output, file paths, and CLI transcripts must be monospaced.
              Mono is for LITERAL machine text only — never for eyebrows,
              labels, or decoration the way the older system used it.
       disp — Archivo (variable: wdth + wght). THE WORDMARK ONLY. Set
              semi-condensed, semibold, and OBLIQUED — see the wordmark
              geometry below. A logotype is artwork, not typography: it is
              drawn once and reused, so it does not count against the
              one-family rule, and it must never appear in running text,
              headlines, buttons, or stat numerals.

     Load:
       https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,600..800
         &family=Inter+Tight:wght@300;400;500;600
         &family=IBM+Plex+Mono:wght@400;500&display=swap
     ========================================================================= */
  --sans:'Inter Tight','Helvetica Neue',system-ui,-apple-system,sans-serif; /* @kind font */
  --mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,monospace; /* @kind font */
  --disp:'Archivo',var(--sans); /* @kind font */

  /* ---- WORDMARK GEOMETRY -------------------------------------------------
     Three things make this logotype read as speed, and they only work
     together:

       1. SLANT. The wordmark is a ROMAN obliqued by exactly --wm-slant, not
          the font's true italic. Archivo's italic leans at roughly 11 deg
          with redrawn letterforms; the mark is built on skewX(-10). Matching
          the angle exactly is the whole point — mark and wordmark then read
          as one object leaning into the same wind, instead of two things
          that happen to be slanted. If the mark's skew ever changes, change
          this with it.
       2. WIDTH. Semi-condensed (wdth 88). Narrow letterforms read as fast
          for the same reason a narrow car does; at full width the lean just
          looks like a mistake.
       3. TRACKING. Tight. Compression is speed — letters drafting off each
          other rather than sitting apart.

     Weight is 800, matching the lockup already shipping in the SKI widget.
     It is heavier than the page's own type tops out at, and that is correct:
     a logotype is drawn, not set, so it does not have to sit inside the
     text weights. It still stays smaller than the mark beside it — the mark
     is the identity, the wordmark only names it. */
  --wm-weight:800; /* @kind font */     /* matches the shipped widget lockup                */
  --wm-width:84; /* @kind other */       /* wdth axis. 62 = too far, reads as a squeeze.      */
  --wm-slant:-10deg; /* @kind other */   /* MUST equal the mark's skewX. Not a taste knob.    */
  --wm-scale:.70; /* @kind other */      /* wordmark cap size as a multiple of the mark's x   */
  --wm-track:-.05em; /* @kind other */   /* tight — compression is what sells the speed       */

  /* =========================================================================
     2. TYPE SCALE

     The load-bearing move is the JUMP, not the sizes. Hero to caption is
     roughly 9x. There is nothing in the middle on purpose — a headline and
     a small caption, no "medium" text competing with either. Resist adding
     a step between --t-h2 and --t-h3.

     Tracking tightens as size grows. At hero size the negative tracking is
     what makes the type read as designed rather than typed.
     ========================================================================= */
  --t-hero:clamp(52px,9.2vw,132px); /* @kind font */  --ls-hero:-.045em; /* @kind font */  --lh-hero:0.94; /* @kind font */
  --t-h1:clamp(40px,6vw,84px); /* @kind font */       --ls-h1:-.04em; /* @kind font */     --lh-h1:0.98; /* @kind font */
  --t-h2:clamp(28px,3.8vw,46px); /* @kind font */     --ls-h2:-.03em; /* @kind font */     --lh-h2:1.06; /* @kind font */
  --t-h3:22px; /* @kind font */                       --ls-h3:-.02em; /* @kind font */     --lh-h3:1.2; /* @kind font */
  --t-lead:19px; /* @kind font */                     --ls-lead:-.012em; /* @kind font */  --lh-lead:1.5; /* @kind font */
  --t-body:15px; /* @kind font */                     --ls-body:-.006em; /* @kind font */  --lh-body:1.62; /* @kind font */
  --t-cap:13px; /* @kind font */                      --ls-cap:0; /* @kind font */         --lh-cap:1.55; /* @kind font */
  --t-micro:10.5px; /* @kind font */                  --ls-micro:.16em; /* @kind font */   /* uppercase only */
  --t-code:13px; /* @kind font */                     --lh-code:1.7; /* @kind font */

  /* Weights. Regular is the default voice; medium is emphasis. The mixed-
     weight headline (light first word, medium rest) is a house device —
     see .h-mix in components.css. */
  --w-light:300; /* @kind font */ --w-reg:400; /* @kind font */ --w-med:500; /* @kind font */ --w-semi:600; /* @kind font */

  /* =========================================================================
     3. COLOR — THE DEPTH RAMP

     One hue, fifteen stops, black to white. Every surface, border, text
     color, and gradient in the system is drawn from this single ladder.
     --glacier is SKI's existing brand blue and is the ramp's anchor; the
     stops above and below it were solved to keep an even perceptual step.

     CONTRAST — READ THIS BEFORE SOFTENING ANYTHING
     The ramp is tuned deliberately wide. Primary text is pure white on pure
     black, and the gradients hold #FFF at one end and #000 at the other
     rather than stopping short at a near-white and a near-black. In a
     single-hue system there is no second color to carry separation, so the
     luminance spread has to do all of it. Compressing these values toward
     the middle to make the page look "softer" is what turns this system
     into grey mush — it is the one change that breaks it fastest.
     ========================================================================= */
  --void:      #000000;   /* the page. Pure black, not near-black.        */
  --ink-950:   #04060B;
  --ink-900:   #080D16;
  --ink-850:   #0B1220;   /* Alpine Ink — the legacy brand token          */
  --ink-800:   #101827;
  --ink-700:   #16202F;
  --steel-700: #1D3E5E;
  --steel-600: #2A5F8F;
  --steel-500: #3A7FBF;
  --glacier:   #3AA0FF;   /* Glacier — brand anchor                       */
  --glacier-400:#62B6FF;
  --ice-300:   #8CC6FF;
  --ice-200:   #B6DCFF;
  --ice-100:   #E4F1FF;
  --snow:      #F5F7FA;   /* Snow — the legacy brand token                */
  --white:     #FFFFFF;

  /* ---- semantic text -----------------------------------------------------
     Four levels, no more. Solved against --void and against the darkest
     third of --grad-depth, so they clear AA in both places. */
  --text:        #FFFFFF;  /* headlines, primary — pure white on pure black */
  --text-2:      #CBD9E8;  /* body copy                                     */
  --text-3:      #A2B4C7;  /* captions, metadata            — 9.9:1 on black */
  --text-faint:  #8494A9;  /* grid labels, disabled         — 6.8:1 on black */
  --code-fg:     var(--ice-100);   /* literal machine text                  */
  --accent-text: var(--ice-300);   /* the lit accent, for text and hairlines */
  --arc-tip:     var(--white);     /* the arc's light end and its head       */

  /* ---- ground ------------------------------------------------------------
     The page itself. Use --ground, never --void directly, so a surface can
     follow the theme. --ground-2 is the recessed step (code blocks, docs
     stages, the construction sheet). */
  --ground:   var(--void);
  --ground-2: var(--ink-950);

  /* ---- ON-ARTWORK COLORS — THEME-INVARIANT -------------------------------
     Text sitting ON a brand gradient is coloured by which END of the ramp it
     sits on, never by the page theme: the gradient is the same object in
     both themes, so its labels cannot flip with the page. Use these, not
     --text, for anything inside a .tile or over --grad-*. */
  --on-dark:  #FFFFFF;   /* over the black end                              */
  --on-light: #000000;   /* over the white end                              */
  --text-on-ice: var(--on-light);   /* legacy alias                         */

  /* ---- surfaces ----------------------------------------------------------
     Surfaces are lifts of the ground, never a different color. */
  --surface:    rgba(255,255,255,.050);
  --surface-hi: rgba(255,255,255,.090);
  --surface-solid:#080D16;
  --line:       rgba(174,205,236,.17);
  --line-strong:rgba(174,205,236,.36);
  --line-ice:   rgba(140,198,255,.62);
  --glass:      rgba(0,0,0,.78);

  /* =========================================================================
     4. GRADIENTS

     --grad-depth is the identity. It is not decoration; it is the brand's
     one image. Black at the bottom, a light source arriving at the top.

     Always lay --grain over any large gradient fill. The gradient is smooth
     enough to band on 8-bit displays and the grain is what stops it — it is
     also, at this point, part of how the brand looks. Don't drop it.
     ========================================================================= */
  --grad-depth:linear-gradient(180deg,
    #FFFFFF 0%, #FFFFFF 9%, #B6DCFF 20%, #3AA0FF 36%,
    #2A5F8F 50%, #0B1220 70%, #000000 88%); /* @kind color */

  /* the same ramp read left-to-right, for wide tiles.
     -x  puts the light at the RIGHT — use it when content sits bottom-left
         and should be Snow.
     -xr puts the light at the LEFT — use it when content sits bottom-left
         and should be Void (.on-ice). Picking the wrong one is the single
         most common way to end up with black text on black. */
  --grad-depth-x:linear-gradient(100deg,
    #000000 0%, #000000 14%, #0B1220 30%, #2A5F8F 54%, #3AA0FF 72%,
    #B6DCFF 90%, #FFFFFF 100%); /* @kind color */
  --grad-depth-xr:linear-gradient(260deg,
    #000000 0%, #000000 14%, #0B1220 30%, #2A5F8F 54%, #3AA0FF 72%,
    #B6DCFF 90%, #FFFFFF 100%); /* @kind color */

  /* a light source just off-frame — the house hero backdrop */
  --grad-source:radial-gradient(105% 88% at 80% 4%,
    #E4F1FF 0%, #62B6FF 14%, #2A5F8F 34%, #0B1220 62%, #000000 82%); /* @kind color */

  /* quiet panel fill, for cards that need to sit slightly above the void */
  --grad-panel:linear-gradient(145deg,#16202F 0%,#0B1220 44%,#000000 100%); /* @kind color */

  /* the edge-light: a hairline that catches the light source */
  --grad-edge:linear-gradient(90deg,transparent,rgba(140,198,255,.85),transparent); /* @kind color */

  /* --- Flow: PRESERVED FROM THE EXISTING BRAND ---------------------------
     Glacier -> Dusk. Under Noir this is demoted from a surface gradient to
     a VOICE-STATE gradient only: the mark's active fill, the listening
     waveform, the loading state. Never a background, never a text fill.
     This is the one place violet is allowed to exist. */
  --grad-flow:linear-gradient(135deg,#3AA0FF 0%,#7B5CFF 100%); /* @kind color */
  --dusk:#7B5CFF;

  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); /* @kind other */
  --grain-opacity:.10; /* @kind other */

  /* =========================================================================
     5. VOICE STATES

     Carried over unchanged from ../ski-design-system/. These are product
     semantics, not styling — the widget, the site mockups, and the films
     all mean the same thing by them. A visual refresh does not get to
     reassign them.
     ========================================================================= */
  --state-listening:#34D06A;   /* mic open, user talking     */
  --state-thinking: #F6C445;   /* agent working              */
  --state-speaking: #A678FF;   /* agent speaking back        */
  --state-idle:     #7C8DA1;   /* bound, waiting             */

  /* =========================================================================
     6. GLOW

     Noir has no drop shadows. On a black page a shadow is invisible; light
     is the only elevation signal there is. Objects glow instead.
     ========================================================================= */
  --glow-sm:0 0 0 1px rgba(174,205,236,.16), 0 8px 30px -12px rgba(58,160,255,.45);
  --glow-md:0 0 0 1px rgba(174,205,236,.24), 0 18px 60px -18px rgba(58,160,255,.60);
  --glow-lg:0 0 0 1px rgba(174,205,236,.32), 0 40px 120px -28px rgba(58,160,255,.78);
  --glow-ice:0 0 44px -4px rgba(140,198,255,.85);

  /* =========================================================================
     7. RADII

     Nearly everything is square. Lunetra's tiles are hard-cornered and the
     only rounded object is the app icon squircle. Keep it that way — a
     rounded card is the fastest way to make this system look generic.
     ========================================================================= */
  --r-0:0px;        /* tiles, panels, images — the default    */
  --r-sm:4px;       /* inputs, chips                          */
  --r-md:10px;      /* the rare floating UI surface           */
  --r-icon:22.37%; /* @kind radius */  /* app icon squircle, iOS/macOS ratio     */
  --r-pill:999px;   /* buttons only                           */

  /* =========================================================================
     8. MOTION
     Slow, and it settles. Nothing in Noir bounces.
     ========================================================================= */
  --ease:cubic-bezier(.22,.75,.22,1); /* @kind other */
  --ease-out:cubic-bezier(.16,1,.3,1); /* @kind other */
  --dur-fast:.2s; /* @kind other */
  --dur-base:.32s; /* @kind other */
  --dur-slow:.6s; /* @kind other */
  --dur-reveal:1.1s; /* @kind other */

  /* =========================================================================
     9. SPACE — 4px base
     ========================================================================= */
  --s-1:4px;   --s-2:8px;   --s-3:12px;  --s-4:16px;  --s-5:24px;
  --s-6:32px;  --s-7:48px;  --s-8:64px;  --s-9:96px;  --s-10:128px;
  --s-11:176px; --s-12:240px;

  --section-y:160px;      /* Noir breathes. Do not tighten this to save   */
  --section-y-sm:96px;    /* scroll — the emptiness is the premium signal */
  --head-to-body:64px;
  --gutter:2px;           /* mosaic tiles nearly touch — hard, thin gaps  */

  /* =========================================================================
     10. LAYOUT
     ========================================================================= */
  --wrap:1320px;
  --wrap-pad:clamp(24px,6vw,88px);   /* wide margins are part of the look */
  --measure:520px;                    /* caption / body max-width         */
  --measure-head:900px;               /* headline max-width               */
  --nav-h:82px;        /* sized to clear the 32px .logo-sm lockup + padding */

  /* Logo clear space, from the construction sheet. x = the mark's cap unit.
     Nothing may enter this zone — including the page edge. */
  --logo-clear:1; /* @kind other */      /* multiples of x on every side of the mark        */
  --logo-gap:0.20;     /* mark -> wordmark gap, in x. This is the OPTICAL
                          gap — ink to ink — not the box-to-box gap.
                          See --mark-bleed-r below for why the two differ. */

  /* ---- MARK BEARINGS ------------------------------------------------------
     The mark's artwork does not fill its own 100-unit viewBox. Measured, the
     ink occupies x 14.1 -> 77.8 and y 23.7 -> 75.4 — 63.7% of the box wide
     and only 51.7% of it tall. Both bearings have to be corrected or the
     lockup lies about itself:

       --mark-boost   scales the mark's box up so the artwork reads at the
                      size --x implies. 1.933 would make the ink exactly --x
                      tall; 1.35 is the shipped value, which lands the mark
                      a little taller than the wordmark's cap height, as in
                      the widget. Raise toward 1.933 for more presence.
       --mark-bleed-r the right side bearing. Uncorrected, that whitespace is
                      added to --logo-gap, so a lockup set to a tight 0.20x
                      reads as a loose 0.42x and the wordmark looks detached.

     RE-MEASURE BOTH if the mark artwork is ever redrawn. */
  --mark-boost:1.35; /* @kind other */
  --mark-bleed-r:0.222; /* @kind other */
  /* Minimum size, measured on the INK — not on --x and not on the mark's
     box, both of which are larger than the artwork inside them. At the
     current --mark-boost the ink is 0.698 * --x, so 20px of ink is --x 29
     (that is what .logo-xs is set to). Recompute if --mark-boost changes. */
  --logo-min:20px; /* @kind spacing */     /* minimum rendered mark INK height, screen         */
  --logo-min-print:6mm; /* @kind other */

  /* breakpoints (reference — custom props can't drive @media)
     1080px  mosaic 4-col -> 2-col
      860px  hero type steps down, side-by-side stacks
      620px  single column, --section-y drops to --section-y-sm          */
}

/* ============================================================================
   LIGHT THEME
   Set data-theme="light" on <html>.

   WHAT DOES NOT CHANGE, AND WHY
   --grad-depth, --grad-depth-x and --grad-depth-xr are IDENTICAL in both
   themes. They are the brand's one image; a gradient that inverts with the
   page is not an identity, it is a decoration. Everything inside a gradient
   tile is coloured with --on-dark / --on-light, so those tiles are pixel-for-
   pixel the same on a white page as on a black one. The theme flips the page
   around them.

   Structural tokens (type, space, radii, motion, logo geometry) never change.

   Two things genuinely differ in kind:
     - ELEVATION. Dark has no shadows because a shadow is invisible on black,
       so objects glow. On white the reverse is true: light does nothing and
       shadow reads. --glow-* therefore become real shadows here rather than
       being dimmed glows, which would just look grubby.
     - GRAIN. Halved. Overlay grain on a white ground reads as dirt long
       before it reads as texture.
   ========================================================================= */
[data-theme="light"]{
  --ground:   #FFFFFF;
  --ground-2: #F2F6FA;

  /* Text solved against #FFF and against --ground-2. --text is near-black
     rather than pure black: pure black on pure white is harsher than the
     dark theme's inverse, because white surfaces glare and black ones do
     not. */
  --text:       #04060B;
  --text-2:     #33465C;
  --text-3:     #47596E;   /* 7.2:1 on white */
  --text-faint: #4F6276;   /* 6.3:1 on white — was 4.5:1, AA but not strong */
  --code-fg:    #1D3E5E;
  --accent-text:#245277;   /* --ice-300 is 1.8:1 on white; this is 7.6:1   */
  --arc-tip:    #1D3E5E;   /* a white arc tip is invisible on a white panel */

  --surface:    rgba(6,24,48,.038);
  --surface-hi: rgba(6,24,48,.075);
  --surface-solid:#F2F6FA;
  --line:       rgba(11,40,72,.16);
  --line-strong:rgba(11,40,72,.34);
  --line-ice:   rgba(42,95,143,.55);
  --glass:      rgba(255,255,255,.78);

  /* The light source arrives on a white page as a wash, not a glare. */
  --grad-source:radial-gradient(105% 88% at 80% 4%,
    #B6DCFF 0%, #DCEDFF 24%, #F4F9FF 56%, #FFFFFF 78%); /* @kind color */
  --grad-panel:linear-gradient(145deg,#FFFFFF 0%,#F2F6FA 44%,#E2ECF6 100%); /* @kind color */
  --grad-edge:linear-gradient(90deg,transparent,rgba(42,95,143,.55),transparent); /* @kind color */

  /* Elevation by shadow, not by light. */
  --glow-sm:0 0 0 1px rgba(11,40,72,.08), 0 4px 14px -6px rgba(11,40,72,.16);
  --glow-md:0 0 0 1px rgba(11,40,72,.10), 0 12px 34px -12px rgba(11,40,72,.22);
  --glow-lg:0 0 0 1px rgba(11,40,72,.12), 0 28px 72px -20px rgba(11,40,72,.30);
  --glow-ice:0 10px 30px -10px rgba(42,95,143,.45);

  --grain-opacity:.05; /* @kind other */

  /* ---- VOICE STATES, RE-SOLVED FOR WHITE ---------------------------------
     The dark theme's values were solved against black and fail badly on a
     white ground — measured: listening 2.0:1, thinking 1.6:1, speaking 3.1:1.
     A status dot nobody can see does not report status.

     This is NOT a reassignment of the semantics, which the system forbids:
     the hue and its meaning are unchanged (listening is still green, thinking
     still amber, speaking still violet) and only the lightness moves, far
     enough to clear 4.5:1. Keep the hues if these are ever retuned. */
  --state-listening:#14793D;   /* 5.5:1 on white */
  --state-thinking: #96690A;   /* 4.9:1 */
  --state-speaking: #7B4FE0;   /* 5.2:1 */
  --state-idle:     #5A6E85;   /* 5.3:1 */
}

/* ============================================================================
   REDUCED MOTION
   ========================================================================= */
@media (prefers-reduced-motion:reduce){
  :root{--dur-fast:0s; /* @kind other */--dur-base:0s; /* @kind other */--dur-slow:0s; /* @kind other */--dur-reveal:0s /* @kind other */}
}


/* ============================================================================
   SKI NOIR — COMPONENTS
   Requires tokens.css.

   House rules encoded here:
     - Everything is left-aligned. There is no centered text in this system.
     - Borders are hairlines or nothing. No 2px strokes, no boxes-in-boxes.
     - Negative space is the divider. Reach for --s-9 before a border.
     - Cards are square. The squircle belongs to the app icon alone.
   ========================================================================= */

/* ============================== base ===================================== */
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--ground);
  color:var(--text-2);
  font-family:var(--sans);
  font-size:var(--t-body);
  font-weight:var(--w-reg);
  letter-spacing:var(--ls-body);
  line-height:var(--lh-body);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
  position:relative;          /* containing block for .bgfx */
}
::selection{background:var(--glacier);color:var(--white)}
a{color:inherit;text-decoration:none}
img,svg,video{display:block;max-width:100%}
hr{border:0;height:1px;background:var(--line)}

/* The themed root. SkiRoot sets data-theme here, and this rule is what makes a
   themed subtree actually paint its own ground — without it the root is
   transparent and borrows whatever the host document is painting, which is how
   a correctly-themed component still ends up looking wrong. */
.ski-root{background:var(--ground);color:var(--text-2);position:relative;isolation:isolate}

/* ============================== layout =================================== */
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--wrap-pad)}
.section{padding:var(--section-y) 0}
.section-sm{padding:var(--section-y-sm) 0}
.stack>*+*{margin-top:var(--s-4)}
.rule{height:1px;background:var(--line);margin:var(--s-8) 0}
.rule-lit{height:1px;background:var(--grad-edge);margin:var(--s-8) 0}

/* ============================== typography =============================== */
/* One family, four sizes that matter, and a very large gap between them. */
.hero{font-size:var(--t-hero);letter-spacing:var(--ls-hero);line-height:var(--lh-hero);
  font-weight:var(--w-reg);color:var(--text);max-width:var(--measure-head)}
.h1{font-size:var(--t-h1);letter-spacing:var(--ls-h1);line-height:var(--lh-h1);
  font-weight:var(--w-reg);color:var(--text);max-width:var(--measure-head)}
.h2{font-size:var(--t-h2);letter-spacing:var(--ls-h2);line-height:var(--lh-h2);
  font-weight:var(--w-reg);color:var(--text)}
.h3{font-size:var(--t-h3);letter-spacing:var(--ls-h3);line-height:var(--lh-h3);
  font-weight:var(--w-med);color:var(--text)}
.lead{font-size:var(--t-lead);letter-spacing:var(--ls-lead);line-height:var(--lh-lead);
  color:var(--text-2);max-width:var(--measure)}
.body{font-size:var(--t-body);color:var(--text-2);max-width:var(--measure)}
.cap{font-size:var(--t-cap);line-height:var(--lh-cap);color:var(--text-3);max-width:var(--measure)}

/* The house headline device: first phrase light, remainder medium. Carries
   emphasis without a second color, a second family, or italics. */
.h-mix>.lt{font-weight:var(--w-light);color:var(--text-3)}
.h-mix{font-weight:var(--w-med)}

/* Uppercase micro label. The ONLY place letter-spacing goes positive.
   Sans, not mono — mono is reserved for literal machine text. */
.label{display:inline-block;font-size:var(--t-micro);letter-spacing:var(--ls-micro);
  text-transform:uppercase;font-weight:var(--w-med);color:var(--text-3)}
.label-ice{color:var(--accent-text)}

/* Literal machine text: paths, commands, transcripts. */
.code,code,kbd,pre{font-family:var(--mono);font-size:var(--t-code);line-height:var(--lh-code);
  letter-spacing:0;color:var(--code-fg)}
pre{background:var(--ground-2);border:1px solid var(--line);padding:var(--s-5);
  overflow-x:auto;border-radius:var(--r-0)}

/* ============================== gradient surfaces ======================== */
/* .grad is the identity object. It always carries grain. */
.grad{position:relative;background:var(--grad-depth);isolation:isolate}
.grad-x{background:var(--grad-depth-x)}    /* light at right — Snow content */
.grad-xr{background:var(--grad-depth-xr)}  /* light at left  — .on-ice content */
.grad-source{background:var(--grad-source)}
.grad-panel{background:var(--grad-panel)}
.grad::after,.grain::after{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  opacity:var(--grain-opacity);mix-blend-mode:overlay;
  background-image:var(--grain);background-size:240px 240px}
.grad>*{position:relative;z-index:2}
.grain{position:relative;isolation:isolate}

/* Page backdrop: a light source off the top-right, falling to void.
   Deliberately NOT fixed and NOT full-page — it lights the first screen and
   then dies out. Below the fold the page is pure black and the only light
   comes from the tiles. A backdrop that follows you down the page turns
   every section into a blue wash and kills the contrast the system runs on. */
.bgfx{position:absolute;top:0;left:0;right:0;height:105vh;z-index:-1;pointer-events:none;
  background:var(--grad-source);opacity:.78;
  -webkit-mask-image:linear-gradient(180deg,#000 26%,transparent 88%);
  mask-image:linear-gradient(180deg,#000 26%,transparent 88%)}
.bgfx::after{content:"";position:absolute;inset:0;opacity:var(--grain-opacity);
  mix-blend-mode:overlay;background-image:var(--grain);background-size:240px 240px}

/* ============================== logo ===================================== */
/* Lockup: mark + wordmark, gap and clear space driven by the x unit.
   Set --x on .logo to size the whole lockup at once.
   Presets: .logo 38px (default) · .logo-lg 70px · .logo-sm 32px (nav, footer)
            .logo-xs 29px — the --logo-min floor, for cramped chrome only. */
.logo{--x:38px;display:inline-flex;align-items:center;
  gap:calc(var(--x) * var(--logo-gap));color:var(--text)}
.logo .mark{width:calc(var(--x) * var(--mark-boost));
  height:calc(var(--x) * var(--mark-boost));flex:none}
.logo .mark svg{width:100%;height:100%;display:block;fill:currentColor}
/* The wordmark is a roman obliqued to the mark's own angle — NOT the font's
   italic. See --wm-slant in tokens.css for why that distinction is load-bearing.
   font-stretch drives Archivo's wdth axis; without the variable font it falls
   back to normal width and the lockup still works, just less quick. */
.logo .wordmark{font-family:var(--disp);font-style:normal;font-weight:var(--wm-weight);
  font-stretch:calc(var(--wm-width) * 1%);
  font-variation-settings:'wdth' var(--wm-width);
  font-size:calc(var(--x) * var(--wm-scale));letter-spacing:var(--wm-track);
  line-height:1;color:currentColor;
  transform:skewX(var(--wm-slant));transform-origin:0 100%;
  /* close the mark's right side bearing so --logo-gap is the ink-to-ink gap */
  margin-left:calc(var(--x) * var(--mark-boost) * var(--mark-bleed-r) * -1);
  /* the lean throws the cap line out to the right — pad by cap x tan(slant)
     so the lockup's optical right edge matches its measured one */
  padding-right:calc(var(--x) * var(--wm-scale) * .19)}
.logo-lg{--x:70px}
.logo-sm{--x:32px}
/* The floor, not a preset that scales: --x 29 renders 20px of ink at the
   current --mark-boost, which is --logo-min. Do not go below it. */
.logo-xs{--x:29px}
.logo-stack{flex-direction:column;align-items:flex-start;
  gap:calc(var(--x) * .5)}

/* Clear space visualiser — style-guide use, never ship this on a page. */
.logo-clear{position:relative;display:inline-flex;
  padding:calc(var(--x) * var(--mark-boost) * var(--logo-clear))}
.logo-clear::before{content:"";position:absolute;inset:0;border:1px dashed var(--text-faint);
  opacity:.5}

/* Approved mark fills. There are exactly four. */
.mark-primary {color:var(--text)}         /* follows the theme — page chrome */
.mark-snow    {color:var(--snow)}         /* absolute — over dark artwork    */
.mark-void    {color:var(--void)}         /* on the light end of a gradient */
.mark-mono    {color:var(--text-faint)}   /* single-color / print / template */
/* Specificity matters here: `.logo .mark svg` is (0,2,1) and sets
   fill:currentColor, so a bare `.mark-flow svg` (0,1,1) loses and the Flow
   fill silently never applies — the mark just renders Snow. Match the depth. */
.logo.mark-flow .mark svg,
.appicon.mark-flow .mark svg,
.mark-flow>svg{fill:url(#skiFlow)}        /* voice-active state only          */

/* App icon. The single rounded object in the system. */
.appicon{width:141px;height:141px;border-radius:var(--r-icon);overflow:hidden;
  position:relative;display:grid;place-items:center;box-shadow:var(--glow-md)}
.appicon.dark{background:var(--grad-panel)}
.appicon.lit{background:var(--grad-depth-x)}
.appicon .mark{width:64%;height:64%}   /* the mark's ink fills ~52% of its box */
.appicon .mark svg{width:100%;height:100%;fill:currentColor}

/* ============================== buttons ================================== */
/* `background:transparent` and `font:inherit` are load-bearing, not tidiness:
   a bare <button> otherwise picks up the UA's grey button chrome and its own
   font stack. Any variant that doesn't set a background of its own — .quiet —
   renders as a grey box without this. */
.btn{display:inline-flex;align-items:center;gap:var(--s-2);
  background:transparent;font-family:inherit;
  border-radius:var(--r-pill);padding:13px 26px;
  font-size:var(--t-cap);font-weight:var(--w-med);letter-spacing:-.005em;
  border:1px solid transparent;cursor:pointer;position:relative;
  transition:background var(--dur-base) var(--ease),
             color var(--dur-base) var(--ease),
             border-color var(--dur-base) var(--ease),
             box-shadow var(--dur-base) var(--ease)}
.btn .ico{width:15px;height:15px;stroke:currentColor;stroke-width:1.6;fill:none;
  stroke-linecap:round;stroke-linejoin:round}

/* Primary = the light source made into an object. */
/* The primary button is always maximum contrast against the ground: a white
   pill on black, a near-black pill on white. */
.btn.primary{background:var(--text);color:var(--ground);font-weight:var(--w-semi)}
.btn.primary:hover{box-shadow:var(--glow-ice)}

/* Ghost = a hairline. The default for everything that isn't the one CTA. */
.btn.ghost{background:transparent;color:var(--text);border-color:var(--line-ice)}
.btn.ghost:hover{border-color:var(--text);background:var(--surface-hi)}

/* Quiet = text with an underline that lights on hover. */
.btn.quiet{padding:0;border-radius:0;color:var(--text-2);border-bottom:1px solid var(--line-strong)}
.btn.quiet:hover{color:var(--accent-text);border-bottom-color:var(--accent-text)}
.cta{display:flex;gap:var(--s-3);flex-wrap:wrap;align-items:center}

/* ============================== chips & states =========================== */
.chip{display:inline-flex;align-items:center;gap:var(--s-2);
  font-size:var(--t-micro);letter-spacing:var(--ls-micro);text-transform:uppercase;
  font-weight:var(--w-med);color:var(--text-3);
  border:1px solid var(--line);border-radius:var(--r-sm);padding:6px 10px}
.dot{width:6px;height:6px;border-radius:50%;background:var(--state-idle);flex:none}
.dot.listening{background:var(--state-listening);box-shadow:0 0 12px var(--state-listening)}
.dot.thinking {background:var(--state-thinking); box-shadow:0 0 12px var(--state-thinking)}
.dot.speaking {background:var(--state-speaking); box-shadow:0 0 12px var(--state-speaking)}

/* ============================== tiles & mosaic =========================== */
/* Lunetra's grammar: portrait-ratio tiles, 2px gutters, hard corners, and
   the gradient doing the visual work inside them. */
.mosaic{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--gutter)}
.tile{position:relative;overflow:hidden;border-radius:var(--r-0);
  aspect-ratio:3/4;padding:var(--s-5);display:flex;flex-direction:column;
  justify-content:flex-end;isolation:isolate}
.tile.wide{grid-column:span 2;aspect-ratio:3/2}
.tile.tall{grid-row:span 2;aspect-ratio:3/8}
.tile.flat{aspect-ratio:auto;min-height:180px}
.tile.void{background:var(--ground-2);border:1px solid var(--line)}
/* TWO FAMILIES OF FILL, TWO RULES FOR TEXT ON THEM.

   ARTWORK — --grad-depth / -x / -xr. Identical in both themes, so their labels
   must be too: --on-dark over the black end, --on-light over the white end
   (.on-ice). These never follow the theme.

   CHROME — --grad-panel, --grad-source, and the plain .tile.void. These DO
   flip with the theme, so text on them follows the theme like any other page
   text. Getting this backwards is what puts white labels on a near-white panel
   in light mode; the fill and its text must agree about which family they are
   in. */
.tile .t-title{font-size:var(--t-h3);letter-spacing:var(--ls-h3);font-weight:var(--w-med);
  color:var(--on-dark)}
.tile .t-cap{font-size:var(--t-cap);color:var(--on-dark);opacity:.78;margin-top:var(--s-2)}
.tile.on-ice .t-title,.tile.on-ice .t-cap{color:var(--on-light)}
.tile.on-ice .label{color:var(--on-light);opacity:.62}
.tile.void .t-title,
.tile.grad-panel .t-title,
.tile.grad-source .t-title{color:var(--text)}
.tile.void .t-cap,
.tile.grad-panel .t-cap,
.tile.grad-source .t-cap{color:var(--text-2);opacity:1}
.tile.grad-panel .label,
.tile.grad-source .label{color:var(--text-3)}
.tile.on-ice .t-cap{opacity:.78}

/* Card — a tile that reacts. Still square, still hairline. */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-0);
  padding:var(--s-6);transition:border-color var(--dur-base) var(--ease),
  background var(--dur-base) var(--ease),box-shadow var(--dur-base) var(--ease)}
.card:hover{border-color:var(--line-strong);background:var(--surface-hi);
  box-shadow:var(--glow-sm)}
.card .h3{margin-bottom:var(--s-3)}

/* ============================== stats ==================================== */
/* Big numeral, tiny label, huge gap. No dividers — the space separates them. */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s-7)}
.stat .n{font-size:clamp(34px,4.4vw,58px);letter-spacing:-.04em;line-height:1;
  font-weight:var(--w-light);color:var(--text)}
.stat .n b{font-weight:var(--w-med)}
.stat .l{margin-top:var(--s-3);font-size:var(--t-cap);color:var(--text-3);max-width:24ch}

/* ============================== the arc ================================== */
/* A light trail climbing to a point — the growth/latency motif. Used once
   per page at most. Give it a viewBox of 0 0 400 200 and stroke the path. */
.arc{width:100%;height:auto;overflow:visible}
.arc .trail{fill:none;stroke:url(#skiArc);stroke-width:2.2;stroke-linecap:round}
.arc .head{fill:var(--arc-tip);filter:drop-shadow(0 0 14px var(--accent-text))}
.arc .halo{fill:none;stroke:rgba(140,198,255,.6);stroke-width:1}

/* ============================== waveform ================================= */
/* The product mark in motion. Bars use the Flow gradient only while active. */
.wave{display:inline-flex;align-items:center;gap:3px;height:22px}
.wave i{display:block;width:2px;border-radius:var(--r-pill);background:var(--text-3);
  height:6px;transition:height var(--dur-fast) var(--ease),background var(--dur-fast) var(--ease)}
.wave.active i{background:var(--glacier-400)}
.wave.active i:nth-child(1){height:8px;animation:wv .9s var(--ease) infinite alternate}
.wave.active i:nth-child(2){height:16px;animation:wv .9s var(--ease) .1s infinite alternate}
.wave.active i:nth-child(3){height:22px;animation:wv .9s var(--ease) .2s infinite alternate}
.wave.active i:nth-child(4){height:16px;animation:wv .9s var(--ease) .3s infinite alternate}
.wave.active i:nth-child(5){height:8px;animation:wv .9s var(--ease) .4s infinite alternate}
@keyframes wv{to{height:4px}}

/* ============================== fluted glass ============================= */
/* A reeded-glass layer over an image: vertical cylindrical lenses that refract
   what is behind them, plus the specular bloom real glass throws.

   The refraction is REAL, not a blur. Each rib shows a slice WIDER than itself
   compressed into its own width — which is what a half-round lens does, and
   where the repeated, pinched edges come from. A backdrop-filter blur gives
   frosted glass, a different material, so don't "simplify" this into one.

   HOW A RIB WORKS. Each rib is a window; inside it sits a full-frame image
   layer, offset left so its own slice lines up, then squeezed horizontally
   about the rib's centre line:

     width           frame width   (100% of the rib x --flutes)
     left            -i rib widths (pulls this rib's slice into the window)
     transform-origin the rib's centre, in the layer's coordinates
     transform       scaleX(1 / --refraction)

   Squeezing with a TRANSFORM rather than background-size is what keeps the
   photo's aspect ratio: the layer stays `background-size: cover`, so any image
   works in any frame. An earlier version scaled the background directly and
   stretched every portrait photo put in a landscape frame.

   Tunables: --flutes (24), --refraction (1.9, 1 = flat glass), --bloom (.55),
   and --focus, the crop anchor. --focus matters more than it looks: the frame
   rarely matches the photo's aspect, so `cover` throws away real content — a
   0.80 portrait in a 21:9 frame keeps 34% of its height. Anchor the crop on
   the subject rather than accepting the centre. */
.glass{position:relative;overflow:hidden;isolation:isolate;
  --flutes:24; /* @kind other */ --refraction:1.9; /* @kind other */ --bloom:.55; /* @kind other */ --focus:center; /* @kind other */
  background:var(--ink-950)}
.glass>img{display:block;width:100%;height:auto}

/* the un-refracted base, dimmed — the ribs sit on top of it */
.glass .glass-base{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:var(--focus);
  filter:blur(10px) saturate(1.12) brightness(1.04);transform:scale(1.1);opacity:.7}

.glass .glass-ribs{position:absolute;inset:0;display:flex;pointer-events:none}
.glass .glass-ribs i{flex:1 1 0;position:relative;overflow:hidden}

/* the refracting layer — see the note above */
.glass .glass-ribs .lens{position:absolute;top:0;height:100%;
  width:calc(100% * var(--flutes));
  left:calc(-100% * var(--i));
  background-position:var(--focus);background-size:cover;background-repeat:no-repeat;
  transform-origin:calc(100% * (var(--i) + 0.5) / var(--flutes)) center;
  transform:scaleX(calc(1 / var(--refraction)))}

/* Cylindrical shading. Bright where the lens turns away at both edges, dark
   through the belly — this is the read that makes it glass rather than a
   striped overlay. soft-light keeps it from blowing out on pale images the way
   overlay does. */
.glass .glass-ribs i::after{content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(90deg,
    rgba(255,255,255,.62) 0%,  rgba(255,255,255,.10) 12%,
    rgba(4,10,20,.34) 44%,     rgba(4,10,20,.28) 60%,
    rgba(255,255,255,.14) 88%, rgba(255,255,255,.70) 100%);
  mix-blend-mode:soft-light}

/* The crisp specular seam where two lenses meet. This is the detail that reads
   as glass at a glance — screen blend so it survives a pale image, where the
   soft-light shading alone washes out to nothing. */
.glass .glass-ribs i::before{content:"";position:absolute;top:0;bottom:0;left:0;
  width:1.5px;z-index:2;mix-blend-mode:screen;
  background:linear-gradient(180deg,
    rgba(255,255,255,.85) 0%, rgba(198,226,255,.45) 38%,
    rgba(255,255,255,.30) 70%, rgba(255,255,255,.70) 100%)}

/* the light source blowing out through the glass */
.glass .glass-sheen{position:absolute;inset:0;pointer-events:none;z-index:2;
  mix-blend-mode:screen;opacity:var(--bloom);
  background:
    radial-gradient(58% 42% at 74% 16%, rgba(255,255,255,.85), transparent 72%),
    radial-gradient(46% 34% at 26% 88%, rgba(182,220,255,.55), transparent 74%)}

/* the same grain the gradients carry — glass without it bands badly */
.glass::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:3;
  opacity:var(--grain-opacity);mix-blend-mode:overlay;
  background-image:var(--grain);background-size:240px 240px}

/* caption sitting on the glass */
.glass .glass-cap{position:absolute;left:var(--s-5);bottom:var(--s-5);z-index:4;
  font-size:var(--t-cap);color:var(--on-dark);text-shadow:0 1px 12px rgba(0,0,0,.55)}

/* Chrome variant: frosted rather than reeded, for glass over the PAGE (a nav,
   a sticky bar) instead of over an image. Follows the theme. */
.glass-frost{backdrop-filter:blur(18px) saturate(1.2);background:var(--glass);
  border-bottom:1px solid var(--line)}

@media (prefers-reduced-motion:no-preference){
  .glass.glass-drift .glass-sheen{animation:glassDrift 14s var(--ease) infinite alternate}
}
@keyframes glassDrift{to{transform:translateX(6%) translateY(-3%)}}

/* ============================== nav & footer ============================= */
.nav{position:sticky;top:0;z-index:50;height:var(--nav-h);display:flex;align-items:center;
  backdrop-filter:blur(18px);background:var(--glass);border-bottom:1px solid var(--line)}
.nav .wrap{display:flex;align-items:center;justify-content:space-between;width:100%}
.nav a.link{font-size:var(--t-cap);color:var(--text-3);transition:color var(--dur-fast) var(--ease)}
.nav a.link:hover{color:var(--text)}
.foot{padding:var(--s-8) 0;border-top:1px solid var(--line);color:var(--text-faint);
  font-size:var(--t-cap)}

/* ============================== swatches (docs) ========================== */
.ramp{display:grid;grid-template-columns:repeat(15,1fr);gap:1px}
.ramp i{display:block;height:88px}
.swatches{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s-5)}
.sw i{display:block;height:120px;border:1px solid var(--line)}
.sw .n{margin-top:var(--s-3);font-size:var(--t-cap);color:var(--text)}
.sw .v{font-family:var(--mono);font-size:var(--t-micro);color:var(--text-3);letter-spacing:.04em}

/* ============================== reveal =================================== */
.rv{opacity:0;transform:translateY(16px);
  transition:opacity var(--dur-reveal) var(--ease-out),transform var(--dur-reveal) var(--ease-out)}
.rv.in{opacity:1;transform:none}

/* ============================== responsive =============================== */
@media (max-width:1080px){
  .mosaic{grid-template-columns:repeat(2,1fr)}
  .stats{grid-template-columns:repeat(2,1fr);gap:var(--s-6)}
  .swatches{grid-template-columns:repeat(2,1fr)}
  .ramp{grid-template-columns:repeat(5,1fr)}
}
@media (max-width:620px){
  :root{--section-y:var(--section-y-sm)}
  .mosaic{grid-template-columns:1fr}
  .tile.wide,.tile.tall{grid-column:auto;grid-row:auto;aspect-ratio:3/2}
  .stats{grid-template-columns:1fr}
  .swatches{grid-template-columns:1fr}
  .nav .links{display:none}
}
