/* ============================================================================
   tokens.css — design tokens (SHARED by site + PDF)
   Direction: EDITORIAL (magazine-grade) + a Bento homepage grid.
   One source of truth for color, type, spacing. Change here → changes both
   the website and the PDF. Aesthetic: warm paper, serif headlines, strict grid.
   ========================================================================== */

:root {
  /* ---- Color (light) ------------------------------------------------------ */
  --bg:        #f1f1f1;   /* white */
  --surface:   #ffffff;   /* cards / raised tiles */
  --ink:       #131313;   /* black */
  --ink-soft:  #4a4a4a;   /* secondary text */
  --ink-mute:  #85807c;   /* captions, meta, labels */
  --hairline:  #dcdcdc;   /* hairline rules */
  --accent:    #921224;   /* GC red — section labels, links, highlight */
  --accent-ink:#ffffff;
  --tag-bg:    #e6e6e6;   /* chip background */
  /* Full-screen transition surfaces (page-transition veil, first-visit loader).
     --veil-scrim is the scrim UNDER the curtain cells: deliberately one step
     past --bg (pure black behind #131313 cells, pure white behind #f1f1f1), so
     the curtain reads as depth rather than a flat fill. Everything else in the
     veil already maps onto --bg / --ink. */
  --veil-scrim: #ffffff;
  /* Grain visibility as a token so the light/dark split stays in tokens.css
     rather than repeating the media-query + [data-theme] selector pair in
     screen.css. The TV static is near-#131313 noise — texture on a dark veil,
     dirt on a light one — so light simply doesn't draw it (app.js also skips
     the loop; this hides any frame drawn before a mid-session switch). */
  --veil-grain: none;

  /* ---- Type -------------------------------------------------------------- */
  /* Loaded via Google Fonts in the page <head>; graceful fallbacks below. */
  --font-serif: "DM Sans", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;  /* display */
  --font-sans:  "DM Sans", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;  /* body */
  --font-mono:  "Azeret Mono", ui-monospace, "SF Mono", Menlo, monospace; /* meta/eyebrow */

  --t-eyebrow: 0.75rem;
  --t-meta:    0.875rem;
  --t-body:    1.0625rem;
  /* Mid-scale display sizes are FLUID (2026-07-15; were static rems, which froze
     every page head / section head from 992px up while the hero and ethos title
     kept scaling on their own inline clamps — the mismatch read as undersized
     type on large desktops). Each clamp is anchored to the OLD static value at
     ~992px and grows toward 1920, so nothing below small-desktop widths moves:
     print.css never consumes these (it sets its own pt sizes), and the 900px
     media override in screen.css still pins --t-h1 on mobile. */
  --t-lead:    clamp(1.2rem, 1.11rem + 0.32vw, 1.5rem);     /* was 1.3125rem */
  --t-h3:      clamp(1.35rem, 1.28rem + 0.35vw, 1.75rem);   /* was 1.5rem    */
  --t-h2:      clamp(2rem, 1.72rem + 0.86vw, 2.75rem);      /* was 2.25rem   */
  --t-h1:      clamp(2.75rem, 1.9rem + 2.2vw, 4.5rem);      /* was 3.25rem   */

  --leading-tight: 1.02;
  --leading-body:  1.6;

  /* ---- Spacing ----------------------------------------------------------- */
  --s-1: 0.25rem; --s-2: 0.5rem; --s-3: 0.75rem; --s-4: 1rem;
  --s-5: 1.5rem;  --s-6: 2rem;   --s-7: 3rem;    --s-8: 4rem;
  --s-9: 6rem;    --s-10: 8rem;

  /* ---- Layout grid --------------------------------------------------------
     One column of the site-wide 48-col grid (screen.css body::after draws the
     hairlines; the nav already aligns to this same unit). 1/48 of the viewport
     at desktop, 1/24 (double width) at tablet/mobile — same breakpoint the grid
     lines themselves switch at. Page gutters and grid gaps are both expressed
     in this unit so wrap edges and gaps between tiles land on grid lines instead
     of floating at an unrelated fluid value. --------------------------------- */
  --col: 2.0833vw;

  /* Page-content safe margin: content (images, text, the work/fragments grids)
     must never sit closer than 2 grid columns from either edge on desktop, so
     there's always a full column of clearance left over for hover-grow states
     (.card/.zoomable/.frag-cell scale to 1.04 on hover) to expand into without
     ever reaching the viewport edge and forcing a horizontal scrollbar. Tighter
     to 1 column on mobile/tablet (media query below) — screen real estate is
     scarcer there and hover states don't apply on touch anyway. */
  --gutter: calc(var(--col) * 2);

  /* Grid/gallery tile gap — locked to one column width (see --col above) so the
     seam between tiles falls on a grid line at any viewport size. Tiles still
     grow ~4% on hover (see .zoomable / homepage .card GROW) but a full column of
     clearance is comfortably more than that growth needs, at any screen size. */
  --grid-gap: var(--col);

  /* Parallax drift allowance. A case-study image's frame is this fraction of the
     art's true height, and the ~14% remainder is the headroom the scroll drift
     sweeps through. Shared contract: render.mjs multiplies the baked
     aspect-ratio by it, screen.css sizes .pv .px by 1/it, and app.js drifts
     yPercent by -(1 - it). Change it in one place and all three stay in step. */
  --pv-allow: 0.86;

  /* ---- Radii ------------------------------------------------------------- */
  --r-sm: 6px; --r-md: 8px; --r-lg: 14px; --r-pill: 999px;

  /* reading measure for case-study prose — a fixed width on purpose:
     grid-snapping text width would fight line-length readability, so prose is
     deliberately exempt from the --col system (images/grids are not). */
  --maxw-read: 660px;
}

@media (max-width: 991px) {
  :root { --col: 4.16667vw; --gutter: var(--col); }
}

/* Dark mode — site only (print.css forces light).
   Applied through TWO selectors on purpose. The OS preference is the default,
   but `data-theme` (set by the nav switch, persisted in localStorage, stamped
   on <html> by an inline head script before first paint) must be able to win in
   BOTH directions — a visitor on a dark OS who picks light has to actually get
   light, which a bare media query can't express. Hence the :not() guard on the
   media query and the explicit [data-theme="dark"] block for the reverse case.
   The two blocks are kept byte-identical; edit them together. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg:       #131313;
    --surface:  #1c1c1c;
    --ink:      #f1f1f1;
    --ink-soft: #c4c4c4;
    --ink-mute: #8d897c;
    --hairline: #2c2c2c;
    --accent:   #921224;
    --tag-bg:   #212121;
    --veil-scrim: #000000;
    --veil-grain: block;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg:       #131313;
  --surface:  #1c1c1c;
  --ink:      #f1f1f1;
  --ink-soft: #c4c4c4;
  --ink-mute: #8d897c;
  --hairline: #2c2c2c;
  --accent:   #921224;
  --tag-bg:   #212121;
  --veil-scrim: #000000;
  --veil-grain: block;
}
/* Explicit light choice must also reset color-scheme, or form controls and
   scrollbars stay dark on a dark-OS machine. */
:root[data-theme="light"] { color-scheme: light; }
