/* ==========================================================================
   TOKENS — prateeknair.design
   Every value the rest of the system reads. Nothing here is decorative;
   if a number appears twice in the site, it belongs in this file.
   ========================================================================== */

:root {

  /* --- Palette (locked, CLAUDE.md §2 — do not change) ------------------- */
  --paper:        #F4EEE2;   /* base background */
  --paper-deep:   #E9E0CE;   /* alternating sections */
  --kumkumam:     #9E2B25;   /* primary accent */
  --kumkumam-lit: #C1352E;   /* hover/active only */
  --ink:          #241C18;   /* body text */
  --surface:      #E4DED3;   /* backing tone behind flat/product shots */

  /* Derived ink tints. Opacity on --ink rather than new hex values, so a
     change to --ink carries through every muted state. */
  /* 70%, not 62%. Every element that uses this renders as NORMAL text —
     .card__sub at 23.7px/400 and .hero__tagline at 20.5px/400 are both under
     the 24px large-text bar and neither is bold — so all of them need 4.5:1,
     not 3:1. At 62% it measured 4.48 against bare paper and 3.84 against the
     grain composite at its darkest, i.e. it was already failing before the
     grain went up. 67.5% is the arithmetic minimum that clears 4.5 against
     the all-black bound; 70% takes it with headroom. */
  --ink-muted:    color-mix(in srgb, var(--ink) 70%, var(--paper));

  /* --ink-faint IS RETIRED. It was 34% ink — 2.07:1 against bare paper, which
     fails the 4.5 bar and the 3.0 one, on 12px text. A contrast below the
     legibility floor is not a way to make something recede; it is a way to
     make it unreadable. The year now takes --ink-muted and recedes through
     TYPE instead: half the subtitle's size, much wider tracking, and more
     space around it. The tonal ramp keeps three steps — ink, muted, and
     muted-set-small — without an illegal one. */
  --rule:         color-mix(in srgb, var(--ink) 16%, var(--paper));

  /* No gold. Anywhere in the shell. Studio Eikira's gold is scoped inside
     that case study's own contained frame — see CLAUDE.md §7. */

  --radius: 0px;             /* square corners, everywhere, no exceptions */

  /* THE ONE EXCEPTION, AND IT IS SCOPED TO CONTROLS.
     Frames, cards and images stay square: §6 asks the frame to present the
     work without editing it, and a radius on a frame shaves the corners off
     UI screenshots whose content runs to the edge. The About copy also says
     out loud that everything but the spiral runs straight, so rounding the
     page would contradict a sentence the visitor reads.

     A button is not the page — it is a control, and there a curve reads as an
     affordance rather than as styling. Applied only to the three controls
     that have a real box: the CTA, the zoom bar's buttons and the skip link.
     The video play button is already a circle, and the Spotify pills own
     their own shape under §7. */
  --radius-control: 14px;


  /* --- Type families ---------------------------------------------------- */
  --font-display: 'Gambetta', Georgia, 'Times New Roman', serif;
  --font-body:    'General Sans', system-ui, -apple-system, sans-serif;
  --font-mal:     'Manjari', 'Noto Sans Malayalam', sans-serif;


  /* --- Type scale -------------------------------------------------------
     Fluid between a 360px phone and a 1600px desktop. The display sizes are
     deliberately extreme at the top end — CLAUDE.md §1 asks the shell to be
     absurd in scale, and a timid hero is the failure mode to avoid. */

  /* MEASURED, not estimated: "Prateek" in Gambetta sets about 3.06x the font
     size wide. An earlier note here claimed 4.7x and concluded 19vw put the
     name across ~90% of the viewport — that was measuring the h1's block box,
     which is full-width by definition, rather than the glyphs. The real span
     at 19vw is ~58%, which was never the reason the hero collided.

     So 19vw stands, and the drop to 13vw was an over-correction chasing a
     bad number. The collision came from where the conch was placed, not from
     the type being too large.

     REVISED, and this time the constraint is vertical. Once the hero became a
     centred stack the binding limit stopped being how wide the glyphs run and
     became how tall the column is: at 19vw the two lines alone were 393px, so
     on a 779px laptop the hero measured 943px and overflowed by 164 — which
     pushed the conch up behind the fixed masthead. It was rendering the whole
     time; it was simply above the fold of its own section.

     So the size now takes the smaller of a width term and a height term. vh
     is what actually governs a stack, and vw keeps the name from running to
     the margins on a wide, short window.

     The two terms do different jobs. On a phone the column is tall and the
     height term never binds, so vw is what sets the name — 17vw keeps the
     glyphs at ~51% of the screen there. On a laptop the height term binds
     first and holds the whole stack inside one viewport. */
  /* HERO NAME MARK, SIZED TO SET ON ONE LINE.
     Measured off the glyphs, not guessed — §3 records that mistake being made
     once already: "Prateek Nair" in Gambetta at --ls-mega sets 5.027x the font
     size wide, against 3.056x for "Prateek" alone. So the whole name needs
     about 1.6x the room one word did, and the old 17vw/15.5vh was scaled for
     the word.
     7vw keeps it near 35% of the viewport on a desktop and the 3.25rem floor
     holds it at about 70% on a phone, which is 261px inside a 280px column at
     320 — the narrowest case, and it still clears. The vh term stops a short
     window blowing it up; it can be higher than it was because one line asks
     for half the height two did. */
  --type-mega:    clamp(3.25rem, min(7vw, 17vh), 9.5rem); /* hero name mark */
  --type-display: clamp(3rem, 11vw, 9rem);         /* section statements */
  --type-title:   clamp(2.25rem, 6vw, 4.5rem);     /* project titles */
  --type-head:    clamp(1.5rem, 3vw, 2.25rem);     /* section headers */
  --type-lead:    clamp(1.125rem, 1.6vw, 1.5rem);  /* opening paragraphs */
  --type-body:    clamp(1rem, 1.05vw, 1.125rem);   /* body copy */
  --type-small:   0.9375rem;                       /* captions, meta */
  --type-micro:   0.75rem;                         /* labels, eyebrows */

  --lh-tight:  0.92;   /* display only — set solid, never for body */
  --lh-snug:   1.14;
  --lh-normal: 1.6;    /* body copy */

  --ls-mega:   -0.035em;
  --ls-display:-0.025em;
  --ls-normal:  0em;
  --ls-wide:    0.18em;  /* eyebrows, small caps-ish labels */

  --measure: 62ch;       /* max line length for body copy */
  --measure-narrow: 46ch;


  /* --- Spacing -----------------------------------------------------------
     Ratio-based, not a linear 8px grid. Aggressive negative space is a
     stated goal, so the top of the scale is large on purpose. */

  --sp-3xs: 0.25rem;
  --sp-2xs: 0.5rem;
  --sp-xs:  0.75rem;
  --sp-s:   1.25rem;
  --sp-m:   2rem;
  --sp-l:   3.25rem;
  --sp-xl:  5.25rem;
  --sp-2xl: 8.5rem;
  --sp-3xl: 13.75rem;

  /* Section rhythm — fluid, so phones don't inherit desktop voids. */
  --sp-section: clamp(var(--sp-xl), 14vh, var(--sp-3xl));


  /* --- Layout ------------------------------------------------------------ */
  --gutter:      clamp(1.25rem, 5vw, 4rem);
  --w-full:      100%;
  --w-wide:      120rem;   /* near-full-bleed image runs */
  --w-content:   76rem;    /* default container */
  --w-text:      42rem;    /* copy blocks */

  /* The masthead is fixed, so nothing below it can learn its height from
     layout. Both numbers live here so the bar's real height and the space
     other sections reserve for it come from one place: masthead.css sets its
     padding from --masthead-pad, and --masthead-h is that padding plus the
     2rem the controls occupy. */
  --masthead-pad: clamp(0.7rem, 1.4vw, 1.15rem);
  --masthead-h:   calc(var(--masthead-pad) * 2 + 2rem);


  /* --- Image ratio buckets ----------------------------------------------
     Three buckets only (CLAUDE.md §6), set from the real files rather than
     chosen. Images are centered inside these frames, never stretched or
     cropped, so the frame absorbs odd source shapes and mixed exports stop
     showing. Measured across the 32 placed images (excludes _reference/
     and _cut-not-used/):

       TALL   n=8   0.46 x1  0.51 x2  0.56 x4  0.76 x1
       SQUARE n=10  1.00 x6  1.03 x2  1.17 x1  1.22 x1
       WIDE   n=14  1.34 x1  1.40 x2  1.41 x4  1.60 x4  1.90 x2  2.41 x1

     Because images are contained rather than cropped, the cost of a bucket
     is the letterbox it leaves. Where a bucket serves two clusters, the
     geometric mean equalises that cost proportionally across both.

     --ratio-wide 1.5 — the wide set has two real clusters, 1.40/1.41 (6
       files: Spotify desktop, Eikira hero, booth renders) and 1.60 (4
       files: the Mailchimp pairs). sqrt(1.406 x 1.60) = 1.4999, so 3:2
       leaves each cluster an even ~6% letterbox instead of fitting one
       perfectly and penalising the other at 12%.

     --ratio-square 1 — 1.00 is 6 of 10 outright. The 1.03 pair loses 2.8%,
       which is invisible. 1.17 and 1.22 are the packaging renders, and they
       run at native size in .row--native rather than in a frame.

     --ratio-tall 0.5625 — 9:16, matching the 4-file Spotify mobile cluster
       exactly. Chosen over the 0.535 geometric mean because the tall
       bucket's hardest job is the 4-up mobile row, where any letterbox is
       multiplied across four frames side by side. The Eikira pair at 0.508
       takes ~10% side padding, absorbed by the mat — which is the frame
       doing its job.

     TWO IMAGES ARE EXEMPT, both deliberately. Use .frame--solo:
       0.76  booths show-floor hero    — 26% letterbox in the tall frame
       2.41  POS production hero       — 61% letterbox in the wide frame

     Both are solo heroes with no neighbour to match, and both are named in
     §10 as among the strongest images on the site. A bucket costs them
     letterbox and buys nothing, so they size to their own ratio and are
     capped by viewport height instead. This is an exemption, not a fourth
     bucket — the three buckets still govern everything that shares a row
     or a section. */

  --ratio-tall:   0.5625;   /* 9:16 */
  --ratio-square: 1;        /* 1:1  */
  --ratio-wide:   1.5;      /* 3:2  */


  /* --- Motion ------------------------------------------------------------
     Every duration derives from --t-base so timing tunes from one number.
     980ms is locked (CLAUDE.md §2) — chosen on a live tuner. */

  --t-base:  980ms;
  --t-quick: calc(var(--t-base) * 0.6);
  --t-slow:  calc(var(--t-base) * 1.3);
  --t-draw:  calc(var(--t-base) * 2.8);
  --ease:    cubic-bezier(0.16, 1, 0.30, 1);


  /* --- Grain -------------------------------------------------------------
     Palm-leaf fibre. Procedural SVG turbulence, never a raster tile — the
     filter itself lives in base.css. Anisotropic: coarse along x, fine along
     y, which elongates the noise into parallel horizontal fibre rather than
     uniform static. The only pattern in the system besides the conch curve
     vocabulary.

     --grain-opacity is the one dial. 0.070 was picked off a four-way
     comparison at 1:1 (0.045 / 0.070 / 0.095 / 0.120): 0.045 read flat in
     the open stretches, 0.095 was clearly textured and 0.120 started to read
     as a repeating pattern rather than as stock. The note below describes the
     old 0.045 and is kept for the reasoning, not the number. 0.045 was near the
     threshold of visibility: it should register as paper stock, not as an
     effect anyone notices. */

  --grain-opacity: 0.07;
  --grain-scale:   260px;

  /* Dark-ground grain. multiply can only darken, so the paper grain is
     inherently a no-op on the dark containers in §7 and §10 — not something
     opacity tuning can reach. Those grounds get a second layer in screen
     instead, which can only lighten. Lower value because lifting a near-black
     reads far more strongly than darkening cream. */
  --grain-opacity-dark: 0.028;


  /* --- Layers ------------------------------------------------------------ */
  --z-grain:  1;
  --z-content: 2;
  --z-nav:    50;
  --z-cursor: 9999;
}


/* Motion tokens collapse to zero rather than being overridden at each call
   site. One rule here means no animation anywhere can outlive it. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --t-base:  1ms;
    --t-quick: 1ms;
    --t-slow:  1ms;
    --t-draw:  1ms;
  }
}

/* --- Contrast notes (measured, WCAG 2.1 relative luminance) --------------
   ink on paper 14.49   ink on paper-deep 12.77   ink on surface 12.51
   kumkumam on paper 6.43   on paper-deep 5.67   on surface 5.55
   kumkumam-lit on paper 4.76

   ONE EXCEPTION: --kumkumam-lit on --paper-deep is 4.20 — below the 4.5
   needed for normal text, above the 3.0 for large text. Since kumkumam-lit
   is a hover/active token only, restrict it on paper-deep sections to
   large text and UI affordances. For a body-size link inside a
   .section--deep, hover to --kumkumam rather than --kumkumam-lit. */
