/* =============================================================================
   tokens.css — the design system as custom properties.

   THE CONCEPT: a studio console.

   Not skeuomorphism. Nothing here pretends to be brushed aluminium. What is
   borrowed from a mixing desk is its INFORMATION DESIGN, which happens to be
   very good: every strip is numbered, every strip is labelled the same way,
   metadata is set in a monospace so columns align and digits do not jitter,
   and levels are shown as bars because a bar is read faster than a number.
   A console is dense, legible and completely consistent — which is exactly
   what a portfolio needs to be.

   THE RULES. Breaking one is how a site starts feeling like several sites.

   1. TYPE — two families with separate jobs.
      Space Grotesk is content: headlines, copy, anything a person reads for
      meaning. IBM Plex Mono is the console: channel numbers, labels, counts,
      years, tags, button text, readouts. If it tells you *what* something is
      or *where* you are, it is mono.

   2. CASE — content is sentence case, always. UPPERCASE belongs to mono and
      nowhere else. No heading in this project sets text-transform.

   3. SHAPE — --r for panels, --r-ctl for controls. Nothing else, no pills.
      Console hardware has tight consistent radii; so does this.

   4. ACCENT — amber is the record light. It marks STATE and the single
      primary action: the channel you are in, the active nav item, the one
      filled button, a live indicator, a meter's peak. It is never decoration,
      never a bullet, never a hover colour. Emphasis without state is --c-fg.

   5. MOTION IS PART OF THE INSTRUMENT — stated in full at --motion below.

   6. HOVER NEVER MOVES TEXT. A pointer landing on a row may light it, brighten
      it, run a cue mark up its edge or nudge its trailing arrow — it may not
      shift the words. Rows used to gain `padding-left` and titles used to
      `translateX` on hover, and the effect is that copy pops sideways out from
      under the cursor as you read down a list: it feels unstable, and on a
      long rack it is a page that will not sit still. State change yes,
      displacement no. The trailing `.ico` arrow is the one exception, because
      it is an icon describing where the link goes, not something being read.
   ============================================================================= */

:root {
  /* ---- surfaces ----------------------------------------------------------
     A console is not black, it is very dark graphite. Pure black kills the
     sense of a physical rack and makes hairlines look harsh, so every surface
     sits a few points off it.

     NEUTRAL, not cool. These were all blue-cast (#0a0b0d / #101217 / #171a20,
     with matching blue-grey text) and it read as a tint sitting on top of the
     design rather than as the material of it — most visible on the splash,
     where a full screen of --c-panel had nowhere to hide. Straight greys also
     let the one amber be the only colour in the room, which is the entire
     point of rule 4. */
  --c-bg:      #0a0a0a;   /* the rack the strips are mounted in              */
  --c-panel:   #121212;   /* a channel strip                                 */
  --c-panel-2: #191919;   /* raised: cards, hover, inputs                    */

  --c-fg:      #ebebeb;
  --c-muted:   #9a9a9a;   /* body copy on dark — passes at body sizes        */
  --c-faint:   #5f5f5f;   /* metadata that must not compete                  */

  --c-line:    rgb(255 255 255 / .075);
  --c-line-2:  rgb(255 255 255 / .15);
  --c-glass:   rgb(255 255 255 / .035);
  --c-glass-2: rgb(255 255 255 / .07);

  /* ---- accent — rule 4 ---------------------------------------------------
     Amber, because on every desk ever built the amber lamp means armed and
     recording. One accent only: a second colour would need a second meaning,
     and two meanings is how an accent stops meaning anything. */
  --c-accent:     #ff7a2f;
  --c-accent-ink: #170800;
  --c-accent-dim: color-mix(in srgb, var(--c-accent) 22%, transparent);
  --c-accent-txt: var(--c-accent);

  --c-sel: rgb(255 122 47 / .26);

  /* The bloom behind a lit discipline in the hero legend. A halo works on a
     dark rack and only ever looks like a smudge on paper, so light mode turns
     it off and lets the colour lift carry the whole effect. */
  --disc-glow: color-mix(in srgb, var(--c-fg) 45%, transparent);

  /* ---- type -------------------------------------------------------------- */
  --f-display: 'Space Grotesk', 'Helvetica Neue', Arial, sans-serif;
  --f-mono:    'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --t-scale: 1;

  /* Fluid, and the CAPS are set to bind at roughly 1900px rather than 1400.
     A cap that binds early means a 1920 display renders at the same pixel
     sizes as a 1366 one and simply grows its margins, which is what "it
     doesn't fit my screen" actually looks like. */
  --t-mega: calc(clamp(2.9rem, 8.4vw, 10rem)     * var(--t-scale));
  --t-xxl:  calc(clamp(1.85rem, 3.4vw, 4rem)     * var(--t-scale));
  --t-xl:   calc(clamp(1.5rem, 2.4vw, 2.85rem)   * var(--t-scale));
  --t-lg:   calc(clamp(1.2rem, 1.7vw, 2rem)      * var(--t-scale));
  --t-md:   calc(clamp(1.02rem, 1.1vw, 1.3rem)   * var(--t-scale));
  --t-bd:   calc(clamp(0.97rem, 0.95vw, 1.14rem) * var(--t-scale));
  /* Flat rem here would freeze every console label at 11px on a 2560 display */
  --t-sm:   calc(clamp(0.85rem, 0.5rem  + 0.4vw,  1.02rem) * var(--t-scale));
  --t-xs:   calc(clamp(0.71rem, 0.44rem + 0.3vw,  0.86rem) * var(--t-scale));

  --lh-tight: 0.96;   /* sentence case has descenders; 0.88 collided */
  --lh-snug:  1.14;
  --lh-body:  1.62;

  --tr-mega:  -0.035em;
  --tr-head:  -0.02em;
  --tr-body:  -0.008em;
  --tr-label:  0.15em;   /* mono labels want the opposite */

  /* ---- space & layout ---------------------------------------------------- */
  --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: 132px;

  /* THE PAGE'S SIDE MARGIN, AND THE ONLY PLACE IT IS SET. Everything keys off
     it — the wrap, the nav, the mobile menu, the footer, the channel heads'
     full-bleed rules and the rail — so widening the margin is this one line
     and the whole rack moves in together. Never hard-code a side padding in a
     section; take it from here or the section will drift out of alignment
     with the head plate sitting above it.

     Widened twice: clamp(18px, 3.2vw, 60px) → clamp(20px, 4vw, 76px) → this.
     The rack kept reading as though it were pressed against the glass, and a
     console's strips are mounted IN a rack — the empty margin either side is
     what makes the page look mounted rather than trimmed to the window. */
  --gut:  clamp(26px, 5.6vw, 104px);

  /* CHROME IS NOT CONTENT, so it does not take the content margin. The nav, the
     mobile menu, the footer and the subpage top bar are the frame around the
     rack rather than strips inside it — they run the full width of the glass
     and hug it, and pushing them in by --gut made the top of the page look like
     a narrow column floating in the middle of the window instead of a bar.

     Deliberately a much flatter curve than --gut (3vw against 5.6vw), so the
     gap between chrome and content OPENS as the screen grows: at 390px they are
     nearly the same and at 1440 the rack sits ~38px further in than the bar,
     which is what makes the frame read as a frame. */
  --gut-chrome: clamp(18px, 3vw, 48px);
  --maxw: 1640px;

  /* THE FRAME INSET — what --gut-chrome becomes once the content column has
     stopped growing.

     `.wrap` is capped at --maxw and centred, so past ~1640px the rack stops
     widening and simply gains margin. The chrome does not: the nav and the
     mobile menu are fixed, full-width elements taking a flat --gut-chrome, so
     on a very wide viewport — or, much more commonly, at 50% browser zoom,
     which is the same thing — the wordmark and the Connect button end up
     pinned to the far edges of the glass while the channel numbers they are
     supposed to line up with sit hundreds of pixels inboard. The bar reads as
     belonging to the window rather than to the page.

     So: whichever is greater, the flat chrome inset or the wrap's own margin
     plus that inset. Below --maxw the first term wins and nothing changes;
     above it the second takes over and the chrome tracks the rack, which keeps
     the numbering spine — nav wordmark over CH ids over footer wordmark — in
     one vertical line at every width.

     THE `100%` RESOLVES AGAINST THE CONTAINING BLOCK, so this is only correct
     on an element that spans the viewport (a fixed bar with left/right 0, or a
     full-bleed block). Do not put it on something inside `.wrap`. */
  --gut-frame: max(var(--gut-chrome), calc((100% - var(--maxw)) / 2 + var(--gut-chrome)));
  --ch:   clamp(60px, 6.2vw, 112px);   /* vertical rhythm between channels   */

  /* ---- shape — rule 3 ---------------------------------------------------- */
  --r:     10px;
  --r-ctl: 6px;

  --nav-h:  58px;
  /* THE RAIL IS THE GUTTER. This deliberately mirrors --gut's own curve, so
     the rail's column is exactly the empty margin beside the content and never
     one pixel more: as wide as it can possibly be — which matters, because it
     is the area you can click — while being incapable of sitting on top of the
     page and stealing a click meant for a link.

     A flat 46px overlapped the content column at 1001px, where --gut is only
     32px; a flat 30px threw away 30 points of target at 1920. */
  --rail-w: clamp(30px, 3.6vw, 68px);

  /* ---- motion ------------------------------------------------------------
     Rule 5, added when the site was judged "super static and lame": MOTION IS
     PART OF THE INSTRUMENT. A console is a thing whose displays move — meters
     fill, tape runs, lamps pulse — so movement here is not decoration, it is
     the same information arriving with its physics attached.

     Two families of easing and nothing else. --e-out is the workhorse: things
     arrive fast and settle. --e-spring overshoots very slightly and is used
     only where something should feel like it has mass (a card landing, a meter
     hitting its level). --e-inout is for things that both start and stop on
     screen, like a curtain. Everything scales off --motion, which reduced
     motion collapses to nothing in one place. */
  --motion: 1;
  --d-1: calc(140ms * var(--motion));
  --d-2: calc(260ms * var(--motion));
  --d-3: calc(480ms * var(--motion));
  --d-4: calc(820ms * var(--motion));
  --d-5: calc(1150ms * var(--motion));
  --e-out:    cubic-bezier(.22, 1, .36, 1);
  --e-inout:  cubic-bezier(.76, 0, .24, 1);
  --e-spring: cubic-bezier(.2, 1.12, .32, 1);

  /* How much later each sibling in a revealed group starts. One number, so a
     six-card rack and an eight-row discography cascade at the same rate. */
  --stagger: calc(58ms * var(--motion));

  --z-page: 1; --z-rail: 40; --z-menu: 59; --z-nav: 60;
}

/* -----------------------------------------------------------------------------
   Light mode — a designed second theme, not an inversion. The console becomes
   a paper patch sheet: warm off-white, ink, the same amber.
----------------------------------------------------------------------------- */
:root[data-theme='light'] {
  --c-bg:      #f1efea;
  --c-panel:   #e9e6e0;
  --c-panel-2: #ffffff;
  /* Ink, warm-neutral to sit on the paper. It was blue-grey against a warm
     background, which is the same mismatch the dark theme had. */
  --c-fg:      #121211;
  --c-muted:   #5a5854;
  --c-faint:   #93908a;
  --c-line:    rgb(0 0 0 / .11);
  --c-line-2:  rgb(0 0 0 / .2);
  --c-glass:   rgb(0 0 0 / .03);
  --c-glass-2: rgb(0 0 0 / .06);
  /* Amber on paper is too light to read as text — darken it for type only,
     derived so the accent stays a single source of truth. */
  --c-accent-txt: color-mix(in srgb, var(--c-accent) 62%, #2b1200);
  --c-sel: rgb(255 122 47 / .3);
  --disc-glow: transparent;
}

/* -----------------------------------------------------------------------------
   REDUCED MOTION

   Collapsing --motion handles every transition on the site in one line, because
   all of them are expressed in --d-*. It does NOT reach keyframe animations,
   and the things that loop forever — the tape, the scan line, the hero trace,
   the sleeve bloom, the availability lamp — are exactly what this setting
   exists to stop. So they are named and stopped explicitly.

   `animation: none` rather than a zero duration, so nothing is left holding a
   half-applied keyframe. The boot overlay is skipped entirely in app.js.
----------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  :root { --motion: .001; }

  .tape__run,
  .hero__grid,
  .hero__scan,
  .hero__scrub::after,
  .feat__art::after,
  .rel__row:hover .rel__wave::after,
  .led {
    animation: none !important;
  }
  /* The tape still has to say what it says, it just stops moving. */
  .tape { mask-image: linear-gradient(90deg, #000 88%, transparent); }
  .hero__scrub { display: none; }
}
