/* tokens.css — V2 design tokens (Cashmere + Lamplight)
 *
 * Every visual value in the application comes from here.
 * Components reference these tokens via var(--token-name).
 * CSS custom properties inherit through shadow DOM boundaries,
 * so every component picks these up automatically.
 *
 * --bi-* tokens come from the V2 redesign and mirror docs/v2_redesign/tokens.json.
 * The .bink-col block at the bottom is shared between measurement
 * and display containers — the structural guarantee that column
 * properties cannot diverge.
 *
 * Switch palettes by setting [data-theme="dark"] on a parent element
 * (typically <html> or <body>). Without that attribute, the light
 * (Cashmere) palette is in effect.
 */

:root {
  /* ── Color · Light (Cashmere) ───────────────────────────────────── */
  --bi-bg:        #ebe4d6;
  --bi-bg-alt:    #e3dac6;
  --bi-bg-soft:   #f1ebde;
  --bi-ink:       #2e2820;
  --bi-ink-soft:  #4a4135;
  --bi-meta:      #8a7e6c;
  --bi-meta-soft: #a89a83;
  --bi-rule:      #cfc6b3;
  --bi-rule-soft: #dcd3bf;
  --bi-accent:    #9a5a1a;
  /* Brick: V2 dark accent + dev signaling + error states.
     Warm enough to be on-brand, red-toned enough to read as error. */
  --bi-brick:     #b8703a;

  /* ── Safe-area insets (PWA three-layer model) ───────────────────────
     Single source of truth for iOS safe-area insets. The reader's three-
     layer model (image / content / tap) routes every safe-area decision
     through these variables: content layers reserve space via padding,
     fixed chrome positions itself via top / bottom, and the image layer
     ignores them entirely so it can bleed screen-edge to screen-edge.
     env(safe-area-inset-*) returns 0 outside iOS PWA / viewport-fit=cover
     contexts, so these are no-ops on desktop and Android by default.

     For dev iteration, ?simulate_safe_area=top:54,bottom:34 in the URL
     overrides these props via the hook in index.html — same code path,
     different inputs. See docs/findings/2026_05_16_pwa_three_layer_reader_model.md. */
  --bi-safe-top:    env(safe-area-inset-top, 0px);
  --bi-safe-right:  env(safe-area-inset-right, 0px);
  --bi-safe-bottom: env(safe-area-inset-bottom, 0px);
  --bi-safe-left:   env(safe-area-inset-left, 0px);

  /* ── Type families ──────────────────────────────────────────────── */
  --bi-font-body:    "Lora", Georgia, serif;
  --bi-font-display: "Cormorant Garamond", "EB Garamond", Georgia, serif;
  --bi-font-sc:      "Cormorant SC", "Cormorant Garamond", Georgia, serif;
  --bi-font-mono:    "JetBrains Mono", "IBM Plex Mono", ui-monospace, monospace;

  /* ── Font weights ────────────────────────────────────────────────
     V2-named weight vocabulary. The V1 --font-weight-* aliases were
     retired in Phase 3 (e6f80663); these V2 names succeed them and
     give the token-usage lint test a real token to reach for.
     Four stops covering the V2 typographic ladder — normal body,
     medium subhead, semibold heading, bold chapter heading.
     Anything else surfaces a sharper question ("what is this weight
     for?") that earns its own token. */
  --bi-weight-normal:   400;
  --bi-weight-medium:   500;
  --bi-weight-semibold: 600;
  --bi-weight-bold:     700;

  /* ── Type scale ─────────────────────────────────────────────────── */
  --bi-size-body-landscape:    18px;
  --bi-leading-body-landscape: 1.62;
  --bi-size-body-phone:        16.5px;
  --bi-leading-body-phone:     1.58;

  --bi-size-meta:              10.5px;
  --bi-tracking-meta:          0.10em;          /* web/iPad */
  --bi-tracking-meta-phone:    0.18em;          /* phone — wider, see consistency note 2 */

  --bi-size-headline:          44px;            /* landing poster */
  --bi-leading-headline:       0.99;
  --bi-size-chapter-cue:       22px;            /* phone image-as-page */

  /* ── Purpose-named UI size tokens (V2 ladder, post-v1-alias) ────── *
   *
   * V2 leans into purpose-named tokens — every size carries intent.
   * These six fill the secondary-UI scale (everything that isn't the
   * canonical reader body, meta row, or display headline). Each name
   * answers "what is this size for", not "where on a generic scale
   * does it sit". A new use-site that doesn't map cleanly to one of
   * these should prefer a bespoke px value (consistent with
   * bink-auth's welcome-mode block) over reaching for a fuzzy match.
   * If a seventh purpose surfaces during work, add it here rather
   * than re-introducing a generic xs/sm/base ladder.
   */
  --bi-size-body-small:        13px;            /* error text, footer captions, secondary meta */
  --bi-size-body-ui:           16px;            /* form inputs, button text, UI body copy */
  --bi-size-card-meta:         12px;            /* book-card author / year / progress meta */
  --bi-size-card-title:        20px;            /* book card titles, section subheads */
  --bi-size-section-head:      28px;            /* page section heads (browse spotlight, library) */
  --bi-size-page-title:        36px;            /* overlay headlines (completion, welcome greeting) */

  /* ── Spacing · Phaidon-Wide column ──────────────────────────────── *
   * These fallback values only apply before the reader has run
   * _computeMetrics. The reader replaces them at runtime with
   * viewport-proportional values (0.12vh / 0.05vw / 0.11vh / 0.075vw)
   * scaled by --margin-scale-reader (Snug 0.6 / Default 1.0 /
   * Spacious 1.4). Tuning the proportions here would have no effect
   * on the rendered reader — see _computeMetrics in bink-reader.js. */
  --bi-pad-col-top:    120px;
  --bi-pad-col-right:   90px;
  --bi-pad-col-bottom: 110px;
  --bi-pad-col-left:   140px;

  /* ── Scrims ─────────────────────────────────────────────────────── */
  --bi-scrim-top: linear-gradient(to bottom,
    rgba(20,12,6,0.80) 0%,
    rgba(20,12,6,0.55) 45%,
    rgba(20,12,6,0)    100%);
  --bi-scrim-top-height: 160px;

  --bi-scrim-bottom: linear-gradient(to bottom,
    rgba(20,12,6,0.05) 0%,
    rgba(20,12,6,0)    40%,
    rgba(20,12,6,0.66) 78%,
    rgba(20,12,6,0.92) 100%);

  /* ── Photographic poster overlay text ───────────────────────────── *
   *
   * Text painted directly on a darkened scene image (D-009 phone
   * poster compositions, plus the welcome carousel's full-bleed
   * style picker). These do NOT theme-switch — the scrim above is
   * defined only in :root, never overridden in [data-theme="dark"],
   * because the scrim is part of the photograph, not part of the UI
   * chrome. The text painted on top is in the same world: it
   * interacts with the image, not the surrounding page background,
   * so it should follow image-world rules.
   *
   * Reach for these (not --bi-bg / --bi-ink) anywhere copy sits
   * directly on a photograph. --bi-bg flips to near-black in dark
   * mode and disappears into the scrim — that's the bug these
   * tokens prevent.
   *
   * Consumed by: bink-landing (phone poster), bink-book-welcome
   * (style picker carousel).
   */
  --bi-poster-ink:      #ebe4d6;                     /* primary text on scrim */
  --bi-poster-ink-soft: rgba(235, 228, 214, 0.85);   /* meta rows, sign-in link, dot affordances */
  --bi-poster-ink-on:   #2e2820;                     /* dark — CTA label on the cream button */

  /* ── Phone reader settings panel (D-014) ────────────────────────── */
  --bi-settings-top:             200px;
  --bi-settings-bottom:           50px;
  --bi-settings-underlay-opacity: 0.30;

  /* ── Phone reader chrome bar (D-013) ────────────────────────────── */
  --bi-chrome-bar-height: 54px;

  /* ── Radii ──────────────────────────────────────────────────────── */
  --bi-radius:           0;     /* default — buttons, cards, panels are squared */
  --bi-radius-back-chip: 2px;   /* the only intentional non-zero radius (D-016) */

  /* ── Rule ───────────────────────────────────────────────────────── */
  --bi-rule-thickness: 1px;

  /* ── Spacing · generic scale (carried over from v1; still used by
     the impersonation banner, status bar, settings panel chrome,
     etc. Component-level layout that hasn't been ported to V2's
     editorial grammar yet leans on these.) ────────────────────── */
  --space-xs:  4px;
  --space-sm:  8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 40px;
  --space-2xl: 64px;
  --space-3xl: 96px;

  /* ── V1 token aliases retired in Phase 3 (2026-05-12) ───────────
     The v1 --color-*, --font-{xs..2xl}, --font-{reader,ui},
     --font-weight-*, --radius-*, and --shadow-md aliases were
     deleted from this block once every consumer was migrated to
     the V2 purpose-named ladder above. The migration mapping (kept
     here as the durable record):

       --font-xs   (12px) → --bi-size-card-meta
       --font-sm   (13px) → --bi-size-body-small
       --font-base (16px) → --bi-size-body-ui
       --font-lg   (20px) → --bi-size-card-title
       --font-xl   (28px) → --bi-size-section-head
       --font-2xl  (36px) → --bi-size-page-title
       --font-weight-{normal,semibold,bold} → 400/600/700 inline
       --radius-* → var(--bi-radius) or inline (V2 is squared by
                    default; --bi-radius-back-chip is the one
                    intentional non-zero radius per D-016)
       --shadow-md → inline rgba(0,0,0,0.12) where genuinely needed
                     (V2 prefers hairline borders over shadows)
       --color-* → --bi-{bg,bg-soft,ink,ink-soft,meta,rule,accent}
                   as appropriate; error text now lives on
                   --bi-accent (brick) per Phase 3.

     The retirement is guarded by
     tests/unit/test_v1_token_aliases_retired_lint.py — adding a new
     reference to any of these names will fail CI with the same
     mapping table. */

  /* ── Reader density — set by display-settings at runtime ──────────
     The spread engine reads these via CSS custom properties.
     Don't rebind to fixed values here — runtime mutations on
     :root override at use-time and the spread engine depends on
     measurement and display agreeing. */
  --font-reader-multiplier:    1;
  /* Edge-to-edge reader frame on V2: zero outer margin so the
     spread (and any image-as-window slot inside it) reaches the
     viewport edges. The v1 reader carried 24/16 here, which left
     a visible Cashmere strip around the stage and made the
     half-page image read as bordered rather than full-bleed
     (D-001 calls for "edge-to-edge, no mat, no border"). */
  --margin-x-reader:           0px;
  --margin-y-reader:           0px;

  /* ── Column layout (set by reader at runtime) ────────────────────── */
  --col-width:  360px;
  --col-height: 600px;
  --col-gap:     40px;

  /* ── Transitions ─────────────────────────────────────────────────── */
  --transition-fast:     150ms ease;
  --transition-normal:   250ms ease;
  --transition-settings: 300ms ease;
}

/* ── Color · Dark (Lamplight) — opt in with [data-theme="dark"] ──── */
[data-theme="dark"] {
  --bi-bg:        #181410;
  --bi-bg-alt:    #1d1813;
  --bi-bg-soft:   #221c16;
  --bi-ink:       #bfb190;
  --bi-ink-soft:  #988a6f;
  --bi-meta:      #6f6249;
  --bi-meta-soft: #544835;
  --bi-rule:      rgba(143, 121, 82, 0.18);
  --bi-rule-soft: rgba(143, 121, 82, 0.09);
  --bi-accent:    #b8703a;             /* brick — accent and error in dark */

  /* Lamplight-only extras (no light-mode counterpart) */
  --bi-accent-muted: #8a5a30;
  --bi-accent-gold:  #c8a878;
}

/* ── Reduced motion ──────────────────────────────────────────────── *
 *
 * Collapse transition tokens to 0ms for users who prefer reduced
 * motion. Component-specific animations (welcome shimmer, etc.)
 * are gated in their own CSS.
 */
@media (prefers-reduced-motion: reduce) {
  :root {
    --transition-fast:     0ms;
    --transition-normal:   0ms;
    --transition-settings: 0ms;
  }
}


/* ── Convenience body styles ─────────────────────────────────────── */
.bi-body {
  background:  var(--bi-bg);
  color:       var(--bi-ink);
  font-family: var(--bi-font-body);
}

/* ── .bi-meta-row — typography primitive for V2 meta chrome ──────── *
 *
 * The shared monospaced uppercase row that appears at the top and
 * bottom of every editorial surface (reader corners, library Continue
 * meta, browse spotlight overlay, completion overlay, book-welcome
 * counter, etc.).
 *
 * Typography only — consumers handle their own layout (flex, grid,
 * absolute positioning). Composes inside shadow DOM via tokens.css
 * being loaded as a stylesheet in each component's shadow root, the
 * same way `.bink-col` works for the column layout.
 *
 * Phone surfaces use a wider letter-spacing (D-014 spec, consistency
 * note 2 in STYLE_GUIDE.md) — the `--phone` variant or a media query
 * inside the consumer can opt into `--bi-tracking-meta-phone`. */
.bi-meta-row {
  font-family:           var(--bi-font-mono);
  font-size:             var(--bi-size-meta);
  letter-spacing:        var(--bi-tracking-meta);
  text-transform:        uppercase;
  color:                 var(--bi-meta);
  font-feature-settings: "tnum";
}

.bi-meta-row--phone {
  letter-spacing: var(--bi-tracking-meta-phone);
}

.bi-rule-h { border-top:  var(--bi-rule-thickness) solid var(--bi-rule); }
.bi-rule-v { border-left: var(--bi-rule-thickness) solid var(--bi-rule); }


/* ── .bink-col — shared column layout class ────────────────────── *
 *
 * Applied to both measurement and display containers. This is the
 * structural guarantee that column properties cannot diverge between
 * the two — one class, two consumers.
 *
 * Container properties are defined here. Content typography
 * (.bink-col p, .bink-col h1, etc.) is defined in bink-reader.css —
 * also scoped to .bink-col so both containers render identically.
 * Together they ensure measurement and display always agree on
 * column count.
 *
 * Properties validated by Experiment 1 (2026-03-20):
 *   column-fill: auto — sequential fill, not balanced
 *   orphans: 2, widows: 2 — validated across 43,451 columns
 *   text-align: justify + hyphens: auto — zero issues on tablet
 *   font-family, font-size, line-height — must match exactly
 */
.bink-col {
  column-fill:    auto;
  column-width:   var(--col-width);
  height:         var(--col-height);
  column-gap:     var(--col-gap);
  font-family:    var(--bi-font-body);
  font-size:      calc(var(--bi-size-body-landscape) * var(--font-reader-multiplier));
  /* --line-height-reader is set by the reader from the line-spacing
     pills (Tight 1.4 / Default 1.62 / Generous 1.8); falls back to
     --bi-leading-body-landscape (1.62) when no user override has
     been applied yet. The pills were previously dead because this
     property hard-coded the V2 token directly.
     The body line-height is the typographic baseline grid module
     (L). Wrapping it in `calc(round(..., 1px))` snaps the per-line
     advance to whole CSS pixels — Chromium already quantizes to its
     LayoutUnit (1/64 px) without drift (docs/findings/
     2026_05_23_baseline_grid_line_height_precision.md), so the
     round() is cross-engine paranoia for Safari/Firefox where
     subpixel container-height calculations have historically
     differed. --grid-L exposes the same resolved L to downstream
     rules (heading margins, scene-break rows, blockquote/list
     spacing) so every vertical contribution inside a column is a
     whole multiple of L and lines land at identical y-positions
     page after page. */
  line-height:    calc(round(
                    var(--bi-size-body-landscape)
                      * var(--font-reader-multiplier)
                      * var(--line-height-reader, var(--bi-leading-body-landscape)),
                    1px
                  ));
  --grid-L:       calc(round(
                    var(--bi-size-body-landscape)
                      * var(--font-reader-multiplier)
                      * var(--line-height-reader, var(--bi-leading-body-landscape)),
                    1px
                  ));
  letter-spacing: var(--letter-spacing-reader, normal);
  word-spacing:   var(--word-spacing-reader, normal);
  text-align:     justify;
  hyphens:        auto;
  orphans:        2;
  widows:         2;

  /* Phaidon-Wide column padding (D-005). The reader's _applyMetrics
     sets --bi-pad-col-* per mode on its host: landscape gets the
     canonical 120/90/110/140 from above; single-column (phone,
     narrow tablet) gets 0/0/0/0 until phone's own chrome treatment
     lands in Commit 3. Living on .bink-col (rather than on a
     wrapper) means the .measure off-screen container sees the same
     padded content box, so column count and offsets stay consistent
     between measurement and display. */
  padding-top:    var(--bi-pad-col-top);
  padding-right:  var(--bi-pad-col-right);
  padding-bottom: var(--bi-pad-col-bottom);
  padding-left:   var(--bi-pad-col-left);
  box-sizing:     border-box;
}
