/* =============================================================================
   main.css — tokens, reset, typography, layout primitives, markers,
   popup chrome, drawer, fixed UI, reduced motion.

   Contract: brief/SPEC.md §2 (tokens) §3 (type) §4 (layout) §5 (markers)
             §6 (popup) §10 (files).
   Per-block layout lives in blocks.css. Popup BODY vocabulary lives in
   popups.css (already written; not touched here).

   Load order in index.html: fonts.css → main.css → blocks.css → popups.css
   ========================================================================= */


/* ============================================================
   1. TOKENS (SPEC §2, §3)
   ============================================================ */

:root {
  /* stage */
  --bg:        #0E0F12;
  --bg-2:      #15171B;
  --bg-3:      #1C1F25;
  --line:      rgba(255,255,255,.10);
  --line-2:    rgba(255,255,255,.18);
  /* light sections */
  --paper:     #F4F1EC;
  --paper-2:   #EAE6DF;
  --ink:       #121316;
  --ink-2:     #5A5A57;
  /* text on dark */
  --fg:        #F3F1ED;
  --fg-2:      #A9A7A1;
  /* C3 - #6E6C67 scored 3.15:1 on --bg-3 for 11.5px labels; #85837E still
     missed at 4.36, so the token sits at 4.60:1 against --bg-3. */
  --fg-3:      #898782;
  /* accent */
  --accent:        #E43A6E;
  --accent-deep:   #C42A5A;
  --accent-soft:   rgba(228,58,110,.14);
  --accent-line:   rgba(228,58,110,.45);
  /* C3 - the brand raspberry is 4.03:1 on --bg-3 and 4.38:1 on --bg-2, so a
     label set in it fails AA. Small accent TEXT uses this lift instead
     (4.61:1 on --bg-3); rules, borders and large type keep --accent. */
  --accent-tx:     #EA4E7A;
  /* misc */
  --radius-s: 10px;
  --radius-m: 18px;
  --radius-l: 28px;
  /* TILE GRAMMAR (2026-09-08, loops round). One radius, one padding scale and
     one chip inset for every clickable card on the page — `.tile` (B2, dark),
     `.b3-num` (B3, paper) and `.b6-tile` (B6, paper). They were 18/18/18px on
     three different padding ramps and read as three families; the numbers below
     are `.b6-tile`'s, which was the tightest of the three and the only one that
     had to fit inside a grid cell. `.b4-fig` keeps its own scale: it is a
     figure inside a list item, not a card in a grid. */
  --tile-radius: 16px;
  --tile-pad:    clamp(22px, 2.2vw, 30px);
  --tile-pad-b:  clamp(56px, 4.6vw, 68px);
  --tile-chip-b: clamp(20px, 2vw, 26px);
  --shadow-pop: 0 40px 120px rgba(0,0,0,.55);
  --ease: cubic-bezier(.2,.7,.2,1);
  --container: 1280px;
  --content: 1120px;
  --gutter: clamp(20px, 5vw, 72px);

  /* type scale (SPEC §3) */
  --t-display:  clamp(46px, 7vw, 108px);
  --t-h1:       clamp(34px, 4.6vw, 64px);
  --t-h2:       clamp(26px, 3vw, 40px);
  --t-h3:       clamp(20px, 1.8vw, 24px);
  --t-body:     clamp(17px, 1.32vw, 19px);
  --t-small:    15px;
  --t-label:    12.5px;
  --t-num:      clamp(52px, 7.5vw, 120px);
  --t-num-s:    clamp(34px, 4vw, 56px);

  --font-ui: Manrope, "Segoe UI", Roboto, system-ui, sans-serif;
  --font-dp: Prata, "Times New Roman", Georgia, serif;

  /* B0 cover geometry — the diptych's own crop knobs live on `.b0` in
     blocks.css; only the copy column is global enough to sit here */
  --copy-max:   min(62%, 880px);

  /* 1 → 0 over the B9 tail (main.js). The fixed chrome multiplies its own
     `.is-on` opacity by this, so the class still owns show/hide. */
  --chrome-fade: 1;

  /* the one feTurbulence tile on the page. `.noise` (fixed, overlay, dark) and
     `.noise--paper` (per paper section, multiply) both read it, so the data URI
     is written down exactly once. */
  --noise-img: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}


/* ============================================================
   2. RESET / BASE
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-ui);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.55;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }
img { height: auto; }

p, h1, h2, h3, h4, h5, h6, figure, blockquote, dl, dd { margin: 0; }
h1, h2, h3, h4 { font-weight: 400; }
ul, ol { margin: 0; padding: 0; list-style: none; }

button { font: inherit; color: inherit; }
a { color: inherit; }

::selection { background: var(--accent); color: #fff; }

.vh {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}


/* ============================================================
   3. LAYOUT PRIMITIVES (SPEC §4)
   ============================================================ */

/* D1 — `<meta viewport-fit=cover>` lets the page paint under the notch. In
   LANDSCAPE on a notched phone the inset (≈47px) is wider than the gutter
   (5vw = 42px at 844px), so body copy would sit under the cutout. `max()`
   means the gutter still wins everywhere else, and the resolved value on a
   desktop is exactly `var(--gutter)`. */
.blk {
  position: relative;
  padding: clamp(96px, 12vw, 168px)
           max(var(--gutter), env(safe-area-inset-right, 0px))
           clamp(96px, 12vw, 168px)
           max(var(--gutter), env(safe-area-inset-left, 0px));
  background: var(--bg);
  color: var(--fg);
}

.wrap {
  position: relative;
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
}

/* --- paper (light) section: swap the token layer, everything else follows.
   Used by B3 and, in phase 2, by B6. -------------------------------------- */
.blk--paper {
  /* PERF 2026-09-08 — bounds `.noise--paper`'s `mix-blend-mode: multiply` to
     this section. Without it that child is a blend group in the ROOT stacking
     context, so its backdrop is the whole document and every repaint on the
     page re-blended it: B3 and B6 were the two slowest sections after B0
     (58.7 % and 44.1 % of frames over 32 ms, qa/perf/localise.json). One
     declaration takes the page from 3.8–6.0 % of frames over 32 ms to 1.2–1.6 %
     (qa/perf/fixprobe4.json, Y1). Pixel-identical: the multiply's backdrop was
     already only this section's own opaque background — nothing paints between
     `.blk--paper`'s background and its `.noise--paper` child — so isolating the
     group changes what Chromium has to READ, not what it draws. Verified at 0
     max channel delta (qa/perf/pixdiff.json).
     Deliberately `isolation` and not `contain: paint`: both measure the same,
     but `contain` would also clip the section and make it a containing block
     for positioned descendants, which is not a promise this rule needs to make. */
  isolation: isolate;
  --bg:      var(--paper);
  --bg-2:    var(--paper-2);
  --bg-3:    #E3DED5;
  --fg:      var(--ink);
  --fg-2:    #4A4A47;
  --fg-3:    #66645F;
  --line:    rgba(18,19,22,.12);
  --line-2:  rgba(18,19,22,.22);
  --accent:  var(--accent-deep);
  /* C3 — accent-deep is 4.87:1 on bare paper but only 4.20:1 once a tile
     tints its background with --accent-soft on hover, so small accent TEXT
     on paper goes one step darker again. */
  --accent-tx: #A82049;
  --accent-soft: rgba(196,42,90,.10);
  --accent-line: rgba(196,42,90,.42);
  background: var(--paper);
  color: var(--ink);
  /* B3 — flat #F4F1EC banded on a 2560px-wide section and the overlay noise is
     a no-op on it (measured L 241.3 at nine heights). A 2 L top-to-bottom ramp
     plus a multiply noise layer gives the paper a direction and a grain.
     The darkest stop is the contrast reference for every ≤14px pair here. */
  background-image: linear-gradient(178deg, #F6F3EF 0%, var(--paper) 42%, #EFEBE4 100%);
}
/* the grain sits under the copy; `.wrap` is lifted out of it */
.noise--paper {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: .03;
  mix-blend-mode: multiply;
  background-image: var(--noise-img);
}
.blk--paper > .wrap { position: relative; z-index: 1; }


/* ============================================================
   4. TYPOGRAPHY PRIMITIVES (SPEC §3)
   ============================================================ */

.eyebrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  color: var(--fg-3);
  font-size: var(--t-label);
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: .14em;
  text-transform: uppercase;
}
/* 32px accent hairline before the text (SPEC §4, grafted from mock-C) */
.eyebrow .rule {
  flex: 0 0 32px;
  height: 1px;
  background: var(--accent);
  opacity: .9;
}
.eyebrow .dot { margin: 0 .35em; opacity: .6; }

/* Prata block statement */
.statement {
  font-family: var(--font-dp);
  font-size: var(--t-h1);
  /* B6 — one Prata ramp: the bigger the size, the tighter the setting.
     Display 1.0/-.014em · statement 1.06/-.01em · long-form 1.16/-.004em. */
  line-height: 1.06;
  letter-spacing: -.01em;
  color: var(--fg);
  max-width: 17ch;
}
/* a long ТЗ sentence carried in Prata — smaller, more lines, still display */
.statement--intro {
  font-size: clamp(25px, 2.7vw, 40px);
  line-height: 1.16;
  letter-spacing: -.004em;
  max-width: 26ch;
}

.lead {
  max-width: 62ch;
  color: var(--fg-2);
  font-size: var(--t-body);
  line-height: 1.55;
}
.lead strong { color: var(--fg); font-weight: 600; }

/* C2 — every free-standing body paragraph in a block, not grid cells or
   card copy (those have their own, tighter caps).
   `ch` is the width of a zero, and Cyrillic lower case runs narrower than
   that, so 62ch still measured 77 characters on the longest B4 line; 54ch
   lands it at 67 (qa/phase3 report, b4_longest_line). */
.blk p:not([class]),
.b4-item p,
.b9-text p { max-width: 54ch; }

.small {
  color: var(--fg-3);
  font-size: var(--t-small);
  line-height: 1.5;
  max-width: 62ch;
}

.label {
  color: var(--fg-3);
  font-size: var(--t-label);
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.acc { color: var(--accent); font-style: normal; }

/* keeps a two-word tail together so a line never orphans one word */
.nb { white-space: nowrap; }

/* inline ТЗ token: [ Реалити-шоу / Шоу трансформаций ] */
.tok { color: var(--fg); white-space: nowrap; }
.tok::before { content: "[\00a0"; color: var(--fg-3); }
.tok::after  { content: "\00a0]"; color: var(--fg-3); }

/* --- grid helpers -------------------------------------------------------- */
.grid { display: grid; gap: clamp(20px, 2.4vw, 32px); }
.grid--7-5 {
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
}
@media (max-width: 899px) {
  .grid--7-5 { grid-template-columns: minmax(0, 1fr); }
}


/* ============================================================
   5. TEXTURE — one page-wide noise overlay (SPEC §2)
   ============================================================ */

/* PERF 2026-09-08 — this layer used to be `opacity:.035; mix-blend-mode: overlay`
   and it was, on its own, the whole scroll stall (qa/perf/ablation.json).
   A full-viewport FIXED element with a blend mode has to read its backdrop, so
   every repaint underneath it — any GSAP tween anywhere on the page — forced the
   whole viewport through a blend group again. Measured at 1200 px/s, 1440x900:
   as shipped 66/71/94 frames > 32 ms; with this one layer neutralised 9/13/12,
   which is BETTER than blocking GSAP entirely (21/21/37). Raster was 5354 ms of
   a 15.2 s sweep against 190 ms of Paint (qa/perf/trace-baseline.json), i.e. a
   compositing cost, which is why the longtask API stayed silent and p50 sat at
   a clean 16.7 ms throughout.
   Deleting the layer is not an option: it is what SPEC §2 asks it to be, and it
   works — with it gone the B0 grade stack bands, the longest run of identical
   column luminance going 70 → 169 rows (qa/perf/bandprobe).
   So the blend goes and the opacity is re-tuned to land back on the shipped
   picture. `normal` at .035 was 5–7 max channel delta; at .016 it is 2–3, the
   same delta as removing the layer altogether, while the B0 plateau is 14 rows
   — a FINER dither than the .035 overlay ever gave (70).
   `mix-blend-mode` is deliberately absent rather than set to `normal`: same
   result, one less declaration.
   Do NOT give this element `will-change`/`translateZ`/`contain` — all three were
   measured and all three make it worse (qa/perf/fixprobe3.json, X2/X3/X4).
   `.noise--paper` keeps ITS `mix-blend-mode: multiply` on purpose: it is the last
   blend layer on the page and it is what holds Chromium to grayscale text
   antialiasing. Neutralise it too and every glyph edge on the page flips to LCD
   subpixel AA — max channel delta 141 on B8 (qa/perf/pixdiff optC/optE). */
.noise {
  position: fixed; inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: .016;
  background-image: var(--noise-img);
}


/* ============================================================
   6. FIXED UI (SPEC §4)
   ============================================================ */

/* E9 — the progress line is part of the fixed chrome, so it goes out with the
   counter and the pill over the B9 tail (ТЗ line 264, «медленное затемнение до
   чёрного»). Under html.rm the scrub never runs and --chrome-fade stays 1. */
.progress {
  position: fixed; inset: 0 0 auto 0;
  height: 2px; z-index: 70;
  pointer-events: none;
  opacity: var(--chrome-fade, 1);
}
.progress i {
  display: block; height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.counter {
  position: fixed;
  /* the pill's padding is 12px, so the scrim starts where the raw text used to */
  /* D1 — `viewport-fit=cover` means the fixed chrome is laid out over the
     rounded corners / home indicator on a notched phone. Every inset resolves
     to 0px on a desktop and on any browser without `env()`, so the computed
     box is byte-identical there. */
  left: calc(var(--gutter) - 12px + env(safe-area-inset-left, 0px));
  bottom: calc(26px + env(safe-area-inset-bottom, 0px));
  z-index: 65;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(14,15,18,.72);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  color: var(--fg-3);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  pointer-events: none;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .5s var(--ease), transform .5s var(--ease),
              /* B7 — the pill re-colours ON the section edge, not a third of
                 a second after it */
              color .12s linear, background-color .12s linear;
}
.counter.is-on { opacity: var(--chrome-fade, 1); transform: none; }
/* the counter floats over whichever section is active — JS flips this class
   when that section is a paper one, so it stays readable on both stages */
.counter.is-paper { color: var(--ink-2); background: rgba(244,241,236,.8); }
@media (max-width: 899px) { .counter { display: none; } }

/* "Источники" pill, bottom-right, appears once the cover has scrolled out */
.pill {
  position: fixed;
  /* D1 — safe-area insets; 0px everywhere except a notched phone */
  right: calc(var(--gutter) + env(safe-area-inset-right, 0px));
  bottom: calc(22px + env(safe-area-inset-bottom, 0px));
  z-index: 66;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 20px 0 16px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: rgba(14,15,18,.72);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  color: var(--fg);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  overflow: hidden;
  /* `max-width`, not `width`: the pill is sized by its label, and `auto` is not
     an interpolable width. 320px is well past the label's ~151px, so the resting
     pill is unchanged and only the collapse animates. */
  max-width: 320px;
  transform: translateY(10px);
  transition: opacity .45s var(--ease), transform .45s var(--ease),
              visibility .45s var(--ease), border-color .3s var(--ease),
              background-color .3s var(--ease),
              max-width .3s var(--ease), padding .3s var(--ease);
}
.pill.is-on { opacity: var(--chrome-fade, 1); visibility: visible; transform: none; }

/* B5's fifth stage «05 ВЫРУЧКА / МОНЕТИЗАЦИЯ» runs into the bottom-right corner
   between 900 and 1659px — exactly where the pill parks. From 1660 up the panel
   ends short of it, and below 900 the phone layout is a vertical rail with the
   dossier above the fold, so the collision only exists in that band. The upper
   edge was 1599px until the B5 grid was put back on one row (2026-09-08),
   which moved the fifth panel right; measured ink overlap between the pill and
   stage 05's own line boxes, worst over a full sweep of #b5:
   1600 348 px², 1610 213, 1620 144, 1640 38, 1660 0 (qa/phase13/pillband.json,
   pillband2.json). With the collapse on, every one of those is 0. There the
   pill drops its word and keeps its 44x44 target; `aria-label="Источники"` on
   the button is what a screen reader has always read, so nothing is lost.
   `.is-tight` is set by the section counter in main.js — the same "which
   section am I in" answer the counter already computes. */
.pill .pill-t {
  white-space: nowrap;
  transition: opacity .3s var(--ease), width .3s var(--ease);
}
@media (min-width: 900px) and (max-width: 1659px) {
  .pill.is-tight { max-width: 44px; width: 44px; padding: 0; gap: 0; justify-content: center; }
  /* the label collapses to zero width as well as zero opacity — with
     `justify-content: center` a label that still occupied its 90px would push
     the icon out of the 44px box */
  .pill.is-tight .pill-t { flex: 0 0 0; width: 0; opacity: 0; }
}
.pill:hover, .pill:focus-visible { border-color: var(--accent); background: rgba(30,12,20,.82); }
.pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.pill svg { flex: none; color: var(--accent); }

@media (max-width: 760px) {
  .pill {
    right: calc(16px + env(safe-area-inset-right, 0px));
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    min-height: 44px;
    padding: 0 16px 0 14px;
    gap: 8px;
    font-size: 11px;
  }
}
/* D8 — the icon-only pill was losing the word «Источники» on every phone we
   actually ship to (360/390/430). Measured at 360 the labelled pill is 151px
   wide against a 344px safe row and the section counter is `display:none`
   below 900px, so nothing can collide with it. The icon-only fallback stays,
   but only below 340px — narrower than any device in the ТЗ's 360–430 range. */
@media (max-width: 340px) {
  .pill { padding: 0; width: 44px; justify-content: center; }
  .pill .pill-t { display: none; }
}


/* ============================================================
   7. MARKERS (SPEC §5)
   ============================================================ */

/* --- A. chip ------------------------------------------------------------- */
.why {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  min-height: 44px;
  padding: 0 20px 0 22px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: transparent;
  color: var(--fg);
  font-family: inherit;
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
  transition: border-color .35s var(--ease), background-color .35s var(--ease),
              color .35s var(--ease);
}
/* A6 — the underline is the visible half of the hover: the cross is symmetric,
   so rotating it 90° changes nothing on screen (it only reads at 45°, i.e. the
   open state). The ring grows instead, and the label draws a hairline. */
.why-label { position: relative; line-height: 1.15; padding-top: 1px; }
.why-label::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -3px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .35s var(--ease);
}
.why-plus { display: inline-flex; color: var(--fg-2); transition: color .35s var(--ease); }
/* the ring grows past the 20×20 viewBox at 1.14, so the svg must not clip */
.why-plus svg { display: block; overflow: visible; }
.why-plus circle {
  transform-box: view-box;
  transform-origin: 10px 10px;
  transition: transform .35s var(--ease);
}
.why-cross { transform-origin: 10px 10px; transition: transform .35s var(--ease); }

.why:hover, .why:focus-visible { border-color: var(--accent); background: var(--accent-soft); }
.why:hover .why-plus, .why:focus-visible .why-plus { color: var(--accent); }
.why:hover .why-plus circle, .why:focus-visible .why-plus circle { transform: scale(1.14); }
.why:hover .why-label::after, .why:focus-visible .why-label::after { transform: scaleX(1); }
.why[aria-expanded="true"] .why-cross { transform: rotate(45deg); }
.why[aria-expanded="true"] { border-color: var(--accent); background: var(--accent-soft); }
.why:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.why--sm { min-height: 44px; padding: 0 16px 0 18px; gap: 11px; font-size: 11.5px; }

/* --- B. inline word ------------------------------------------------------ */
.why-inline {
  display: inline;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 5px;
  transition: text-decoration-thickness .3s var(--ease);
}
.why-inline .why-sup {
  display: inline-block;
  vertical-align: .42em;
  margin-left: 3px;
  color: var(--accent);
}
.why-inline .why-sup svg { display: inline-block; }
.why-inline .why-cross { transform-origin: 6px 6px; }
.why-inline:hover { text-decoration-thickness: 2px; }
.why-inline:hover .why-cross,
.why-inline:focus-visible .why-cross { transform: rotate(90deg); }
.why-inline[aria-expanded="true"] .why-cross { transform: rotate(45deg); }
.why-inline:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* --- Tiles --------------------------------------------------------------- */
.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  width: 100%;
  padding: var(--tile-pad) var(--tile-pad) var(--tile-pad-b);
  border: 1px solid var(--line-2);
  border-radius: var(--tile-radius);
  background: var(--bg-3);
  color: var(--fg);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  transition: border-color .4s var(--ease), background-color .4s var(--ease);
}
.tile::before {          /* accent hairline drawing along the top edge */
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .45s var(--ease);
}
.tile > * { transition: transform .4s var(--ease); }
.tile:hover, .tile:focus-visible { border-color: var(--accent-line); }
.tile:hover::before, .tile:focus-visible::before { transform: scaleX(1); }
.tile:hover > *, .tile:focus-visible > * { transform: translateY(-2px); }
.tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.tile[aria-expanded="true"] { border-color: var(--accent-line); }
.tile[aria-expanded="true"]::before { transform: scaleX(1); }

.tile-w {                 /* the big word */
  font-size: var(--t-h2);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -.02em;
  color: var(--fg);
}
.tile-t {
  color: var(--fg-2);
  font-size: var(--t-small);
  line-height: 1.5;
  max-width: 34ch;
}
/* corner chip inside the tile */
.tile-chip {
  position: absolute;
  left: var(--tile-pad);
  bottom: var(--tile-chip-b);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--fg-3);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  transition: color .35s var(--ease);
}
.tile-chip svg { display: block; }
.tile-chip .why-cross { transform-origin: 10px 10px; transition: transform .35s var(--ease); }
.tile:hover .tile-chip, .tile:focus-visible .tile-chip { color: var(--accent-tx); }
.tile:hover .tile-chip .why-cross,
.tile:focus-visible .tile-chip .why-cross { transform: rotate(90deg); }
.tile[aria-expanded="true"] .tile-chip .why-cross { transform: rotate(45deg); }

/* A6b — `.why-plus { color: var(--fg-2) }` is more specific than the inherited
   chip colour, so on `.tile` / `.b3-num` / `.b4-fig` the label went raspberry
   while the ⊕ stayed grey. (`.b6-tile` already states its own.) */
.tile:hover .tile-chip .why-plus,   .tile:focus-visible .tile-chip .why-plus,
.b3-num:hover .tile-chip .why-plus, .b3-num:focus-visible .tile-chip .why-plus,
.b4-fig:hover .tile-chip .why-plus, .b4-fig:focus-visible .tile-chip .why-plus {
  color: var(--accent-tx);
}


/* --- Press state (ТЗ line 68 «Нажатие») ----------------------------------
   Every large tap target answers the finger before the popup opens. The short
   `transition-duration` applies only while the element is held, so the press is
   immediate and the release runs at the element's own easing. `.b5-btn` has no
   transition of its own, so it gets one.

   `!important` is load-bearing, not laziness: `.tile`, `.b3-num` and `.b6-tile`
   are `.rv-i`, and GSAP leaves `transform: translate(0px, 0px)` INLINE on them
   once the reveal tween finishes. An inline style beats a stylesheet rule, so
   without it the press is silently dead on exactly those three (measured — see
   the F8 probe in the ТЗ-compliance round). No transform under reduced motion. */
.b5-btn { transition: transform .16s var(--ease); }

.tile:active,
.why:active,
.b3-num:active,
.b4-fig:active,
.b6-tile:active,
.b5-btn:active,
.pill:active {
  transform: scale(.99) !important;
  transition-duration: .12s;
}

html.rm .tile:active,
html.rm .why:active,
html.rm .b3-num:active,
html.rm .b4-fig:active,
html.rm .b6-tile:active,
html.rm .b5-btn:active,
html.rm .pill:active,
html.rm .src-a:active,
html.rm .pop-act:active,
html.rm .pop-close:active,
html.rm .drawer-close:active { transform: none !important; }

/* The two overlay-chrome buttons and the two popup-body controls were outside
   the press list; they are tap targets like any other. `.pop-close` /
   `.drawer-close` sit inside an overlay and never get a sheen or a tilt — a
   card that leans away from the cursor while it is the modal focus reads as a
   bug — but the finger still gets an answer. */
.src-a:active,
.pop-act:active,
.pop-close:active,
.drawer-close:active {
  transform: scale(.985) !important;
  transition-duration: .12s;
}


/* ============================================================
   7c. AFFORDANCE — 3D tilt + accent sheen (added 2026-09-09, controls round)

   The brief: «любая кнопка должна быть как будто 3D и чтобы немножко
   мерцалось, чтобы было видно, что это активные кнопки». Read through ТЗ
   lines 52 / 61 / 66-67 / 70 that becomes three things and NOT a fourth:

     1. TILT  — the control leans toward the cursor, max 3.5° at the corners,
                plus scale(1.015). Driven from JS (`tilt()` in main.js) which
                writes `--tx` / `--ty` and toggles `.is-tilt`; the transform
                itself is here. Fine pointers only — on a phone there is no
                cursor to lean towards and ТЗ 70 forbids hover-only meaning.
     2. DEPTH — an elevation shadow, NOT a glow. ТЗ 52 bans «чрезмерное
                свечение», so the 3D is sold with the same neutral drop shadow
                a raised card would cast. No accent glow anywhere in this file.
     3. SHEEN — a narrow accent band crossing the surface once every 7s, ~900ms
                of travel, peak alpha .11 (.14 while hovered). This is the
                «мерцание», and it is the half that has to work on a phone,
                because it is the only affordance a touch device can see.

   `!important` on the tilt transform is load-bearing for the same reason the
   press state above documents: `.tile` / `.b3-num` / `.b6-tile` are `.rv-i`
   and GSAP leaves an inline `transform: translate(0px, 0px)` on them.

   Numbering note: this block sits BEFORE 7b (icons) in the file on purpose —
   every interaction state on the page is then in one run, §7 → §7c.
   ============================================================ */

/* --- 1+2. tilt and depth -------------------------------------------------- */
@media (hover: hover) and (pointer: fine) {
  html:not(.rm) .is-tilt {
    transform: perspective(760px)
               rotateX(var(--ty, 0deg))
               rotateY(var(--tx, 0deg))
               scale(1.015) !important;
    transition: transform .13s linear !important;
  }
  /* the ease-back on leave: JS zeroes --tx/--ty, swaps to this longer curve,
     then drops both classes once it has run */
  html:not(.rm) .is-tilt.is-tilt-out {
    transition: transform .45s var(--ease) !important;
  }
}

/* The depth is inside `(hover: hover)` on purpose. Chromium keeps an emulated
   touch pointer parked wherever the last tap landed, so on a phone a `:hover`
   rule STAYS on after the popup closes — measured: `el.matches(':hover')` is
   still true on the tapped tile after Esc and after a 700px scroll
   (`qa/phase15/controls/touch-active.json`). The hover rules that were already
   on the page have always behaved that way; this round is not going to add a
   raised shadow that a phone can never put down. */
@media (hover: hover) {
  .tile:hover, .b4-fig:hover { box-shadow: 0 18px 34px -20px rgba(0,0,0,.72); }
  /* B3 and B6 are paper sections (measured `rgb(244,241,236)` behind both) —
     a black shadow on paper reads as dirt, not as height */
  .b3-num:hover, .b6-tile:hover { box-shadow: 0 16px 30px -20px rgba(18,19,22,.42); }
}
.tile:focus-visible, .b4-fig:focus-visible { box-shadow: 0 18px 34px -20px rgba(0,0,0,.72); }
.b3-num:focus-visible, .b6-tile:focus-visible { box-shadow: 0 16px 30px -20px rgba(18,19,22,.42); }

/* Touch press — see `press()` in main.js: Blink never puts a touch target in
   the `:active` chain, so the phone's feedback is a class. */
html:not(.rm) .is-press {
  transform: scale(.985) !important;
  transition: transform .12s var(--ease) !important;
}

/* --- 3. the sheen --------------------------------------------------------- */
/* A two-element layer injected by `sheen()` in main.js, never a pseudo-element:
   ::before is already the top hairline on `.tile` / `.b3-num` / `.b4-fig` and
   ::after is already the connector on `.b6-tile`, so there is no one free slot
   across the family. The OUTER `<i>` does the clipping (its host cannot: the
   `.b6-tile` connector hangs outside the box and needs `overflow: visible`),
   the INNER `<i>` is the band and is the only thing that ever moves. */
.afx {
  position: absolute;
  inset: -1px;
  z-index: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
  /* the host families lift their CONTENT on hover (`.tile:hover > *`,
     `.b3-num:hover > *`, `.b4-fig` …) and this layer is a child. It must not
     travel with them — and, more importantly, a transition on `transform`
     outranks a running animation in the cascade, so leaving the inherited
     `transition: transform .4s` in place would stall the band on every hover. */
  transform: none !important;
  transition: none !important;
}
/* The gradient is 90deg, NOT the diagonal it looks like. A slanted gradient's
   line is `|W·sin θ| + |H·cos θ|` long and is centred on the box, so on a band
   that is taller than it is wide the 0 % and 100 % stops fall OUTSIDE the left
   and right edges and the band ends on a visible alpha step — measured 0.034
   at the edge of a 214x389 band at 104deg, and it read as a hard vertical seam
   in `qa/phase15/controls/sheen-0300ms.png` (first run). At 90deg the gradient
   line is exactly the width, so both ends are truly transparent. The slant is
   put back with a rotate baked into every keyframe, and the band is grown to
   ±60 % of the host's height so the rotation can never expose a corner. */
.afx > i {
  position: absolute;
  top: -60%; bottom: -60%;
  left: 0;
  width: 34%;
  min-width: 96px;
  background: linear-gradient(90deg,
    rgba(228,58,110,0)                    0%,
    rgba(228,58,110, var(--afx-a, .11))  50%,
    rgba(228,58,110,0)                  100%);
  transform: translate3d(-180%, 0, 0) rotate(8deg);
  opacity: 0;
}

/* Loop FIVE (SPEC §7a). Compositor-only: `transform` + `opacity`, no filter,
   no layout property. The travel is compressed into the first 13 % of the
   period — 910ms of movement, then 6.1s parked off-screen at opacity 0. */
@keyframes afx-sweep {
  0%     { transform: translate3d(-180%, 0, 0) rotate(8deg); opacity: 0 }
  1.4%   { opacity: 1 }
  11%    { opacity: 1 }
  12.9%  { transform: translate3d(430%, 0, 0)  rotate(8deg); opacity: 0 }
  100%   { transform: translate3d(430%, 0, 0)  rotate(8deg); opacity: 0 }
}

/* Gated by an IntersectionObserver exactly like the chain wave: off-view the
   animation NAME is gone, not merely paused, so the band falls back to its own
   resting style (off-box, opacity 0) instead of freezing mid-sweep. `--afx-i`
   is the control's index inside its section, so a block that scrolls in
   cascades rather than flashing in unison. */
html:not(.rm) .is-afx > .afx > i {
  animation: afx-sweep 7s linear infinite;
  animation-delay: calc(var(--afx-i, 0) * 1.15s);
  will-change: transform, opacity;
}
/* The pill is fixed chrome: an IntersectionObserver would call it on screen
   while it is still `visibility: hidden` before B1, so its gate is the class
   it already owns. It also carries the whole page's `--chrome-fade`, so the
   band goes out with it over the B9 tail. */
html:not(.rm) .pill.is-on > .afx > i {
  animation: afx-sweep 7s linear infinite;
  animation-delay: 2.4s;
  will-change: transform, opacity;
}

/* Hover raises the band to the ceiling ТЗ 61 allows for a clickable element.
   Inside `(hover: hover)` for the sticky-hover reason above — a phone that
   tapped a tile must not keep its sheen at the hover value for good. */
@media (hover: hover) {
  .tile:hover, .b3-num:hover, .b4-fig:hover, .b6-tile:hover,
  .why:hover, .pill:hover { --afx-a: .14; }
}
.tile:focus-visible, .b3-num:focus-visible, .b4-fig:focus-visible,
.b6-tile:focus-visible, .why:focus-visible,
.pill:focus-visible { --afx-a: .14; }

/* `.why` is the only host that was `position: static` */
.why { position: relative; }

/* B5's button is borderless and 460px wide, and its left third is the rail and
   the node. The sheen is confined to the text column (measured layer 410x258
   inside a 460x266 button, `qa/phase15/afx-boxes.py`) and runs quieter: there
   is no surface under it for the band to sit on, only type. */
.b5-btn > .afx {
  inset: 4px 0 4px calc(var(--b5-rail) + var(--b5-ring) / 2 + 10px);
  border-radius: 10px;
  --afx-a: .085;
}
@media (hover: hover) { .b5-btn:hover > .afx { --afx-a: .12; } }
.b5-btn:focus-visible > .afx { --afx-a: .12; }

/* SPEC §7a rule 4 — the media query kills it independently of `html.rm` */
@media (prefers-reduced-motion: reduce) {
  .afx > i { animation: none !important; will-change: auto; opacity: 0 }
  /* same specificity as the tilt rule above, and later in the sheet, so it
     wins the !important tie even if `html.rm` was never set */
  html:not(.rm) .is-tilt,
  html:not(.rm) .is-press { transform: none !important }
}


/* ============================================================
   7b. ICONS (SPEC §5 — Lucide 1.42.0, ISC; assets/icons/LICENSE.md)

   ONE inline `<symbol>` sprite at the top of <body> (generated by
   qa/phase9/build-sprite.py), used everywhere as

     <svg class="ic" aria-hidden="true" focusable="false"><use href="#ic-x"/></svg>

   so the page still makes zero external requests and `<use href>` resolves
   over `file://` too. Icons are decorative: the text label is always the
   accessible name.

   Colour is `currentColor`, i.e. whatever the surrounding copy is — never the
   accent, with one exception: the terminal node of a story chain (blocks.css
   § CHAIN). `--ic` is the size, `--icw` the stroke weight; Lucide draws at 2,
   which reads as a UI toolbar, so the page default is 1.5 and the two ends of
   the size range are corrected by eye (a 32px glyph at 1.5 looks heavier than a
   16px one at the same value).
   ============================================================ */

.ic-sprite { display: none; }

.ic {
  width: var(--ic, 24px);
  height: var(--ic, 24px);
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icw, 1.5);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ic--l  { --ic: 28px; --icw: 1.35; }  /* >= 28px — the thin optical step */
.ic--m  { --ic: 22px; }
.ic--s  { --ic: 16px; --icw: 1.75; }  /* <= 16px — the thick optical step */
.ic--xs { --ic: 14px; --icw: 1.75; }

/* Decorative apparatus everywhere outside the chains: the same token the
   indices and captions already use, so an icon never outweighs its own label.
   On a paper section `--fg-3` resolves to the ink value on its own. */
.tile > .ic,
.b3-num > .ic,
.b3-task > .ic,
.b4-src > .ic,
.b4-item-n > .ic,
.b4-cell > .ic,
.b6-tile > .ic,
.b8-cell > .ic,
.b8-row-l > .ic { color: var(--fg-3); }


/* ============================================================
   8. POPUP CHROME (SPEC §6)
   Body vocabulary (.stats, .bars, .chain-v, .chain-h, …) lives in popups.css.
   ============================================================ */

.pop {
  position: fixed; inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(20px, 4vh, 56px) var(--gutter);
}
.pop[hidden] { display: none; }

.pop-ov {
  position: absolute; inset: 0;
  background: rgba(8,9,11,.74);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
  opacity: 0;
}

.pop-card {
  position: relative;
  width: min(78vw, 1100px);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--radius-l);
  background: var(--bg-2);
  box-shadow: var(--shadow-pop);
  opacity: 0;
  overflow: hidden;
}
/* the card clips at 86vh / 94dvh; this fade is the only cue that content
   continues below, since Chrome's overlay scrollbar is invisible at rest */
.pop-card::after {
  content: "";
  position: absolute;
  left: 1px; right: 1px; bottom: 0;
  height: 56px;
  pointer-events: none;
  background: linear-gradient(to top, var(--bg-2), rgba(21,23,27,0));
}

.pop-scroll {
  /* flex:1 + min-height:0 is what makes the card's max-height actually clip
     into a scrollable area; without min-height:0 a column flex item grows to
     its content and the card silently truncates instead of scrolling */
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--line-2) transparent;
}
.pop-scroll::-webkit-scrollbar { width: 8px; }
.pop-scroll::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 999px; }
.pop-scroll::-webkit-scrollbar-track { background: transparent; }

.pop-inner {
  padding: 56px;
  padding-right: clamp(56px, 6vw, 92px);
}
@media (max-width: 1100px) { .pop-inner { padding: 40px; } }

/* --- head ---------------------------------------------------------------- */
.pop-eyebrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  color: var(--accent-tx);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.pop-eyebrow .pop-ref { color: var(--fg-3); }
.pop-eyebrow .pop-ref::before { content: "· "; }
/* the eyebrow LABEL is the accent object in a popup head; its glyph and its
   block reference are apparatus and stay muted, or the head reads as three
   raspberry things in a row */
.pop-eyebrow > .ic { color: var(--fg-3); }

.pop-q {
  margin-top: 22px;
  color: var(--fg-2);
  font-size: var(--t-h3);
  font-weight: 500;
  line-height: 1.3;
  max-width: 46ch;
}
.pop-a {
  margin-top: 16px;
  font-family: var(--font-dp);
  font-size: var(--t-h1);
  line-height: 1.06;                 /* B6 Prata ramp */
  letter-spacing: -.01em;
  color: var(--fg);
  max-width: 22ch;
}
.pop-a--wide {
  max-width: 30ch;
  font-size: clamp(28px, 3.2vw, 46px);
  line-height: 1.16;                 /* B6 Prata ramp — long-form step */
  letter-spacing: -.004em;
}
/* B13 — an ALL-CAPS answer (p2b: «ПРОФЕССИОНАЛЬНАЯ УСЛУГА → …») is a chain,
   not a sentence. Prata caps at --t-h1 with display tracking set solid; the
   size comes down a step and the letters get air instead. */
.pop-a--caps {
  max-width: 30ch;
  font-size: clamp(28px, 3.2vw, 44px);
  line-height: 1.16;
  letter-spacing: .012em;
}

.pop-body { margin-top: clamp(34px, 4vw, 52px); }

/* --- "Что это означает для Тальписа" ------------------------------------- */
.pop-mean {
  margin-top: clamp(34px, 4vw, 52px);
  padding-left: clamp(20px, 2vw, 28px);
  border-left: 3px solid var(--accent);
}
.pop-mean-l {
  color: var(--fg-3);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.pop-mean-t {
  margin-top: 12px;
  color: var(--fg);
  font-size: var(--t-body);
  line-height: 1.55;
  max-width: 58ch;
}

/* --- actions ------------------------------------------------------------- */
.pop-actions {
  margin-top: clamp(30px, 3.4vw, 44px);
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}
/* an action navigates the page — it gets an arrow, never the marker "+" */
.pop-act {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  min-height: 44px;
  padding: 0 20px 0 22px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: transparent;
  color: var(--fg);
  font-family: inherit;
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color .35s var(--ease), background-color .35s var(--ease);
}
.pop-act svg { transition: transform .35s var(--ease); }
.pop-act:hover, .pop-act:focus-visible { border-color: var(--accent); background: var(--accent-soft); }
/* B14 — the action is a `.why` chip with an arrow instead of a ring: same
   rest (1px --line-2, 44px, uppercase label) and the same hover wash. The
   glyph takes the TEXT accent, which is the token that stays legible once
   --accent-soft has tinted the ground under it (C3). */
.pop-act:hover svg, .pop-act:focus-visible svg { transform: translateX(4px); color: var(--accent-tx); }
.pop-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* --- sources ------------------------------------------------------------- */
.pop-sources {
  margin-top: clamp(34px, 4vw, 52px);
  padding-top: clamp(24px, 2.6vw, 34px);
  border-top: 1px solid var(--line);
}
.pop-sources-l {
  color: var(--fg-3);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.src-list { margin-top: 20px; display: grid; gap: 4px; }
.src {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 12px 24px;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
}
.src:last-child { border-bottom: 0; }
.src-n { color: var(--fg); font-size: var(--t-small); line-height: 1.45; }
.src-m { display: block; margin-top: 6px; color: var(--fg-3); font-size: 13px; letter-spacing: .04em; }
.src-a {
  /* SPEC §2 caps the accent at ~3 objects per viewport; a list of ten sources
     would blow that, so the resting state is an underline and the raspberry
     only appears on hover/focus. Still obviously clickable at rest (§5).
     U14 — «ОТКРЫТЬ ИСТОЧНИК ↗» repeats on every row of the library, so at --fg
     it printed as a second column of full-strength text and the page read as a
     directory table. Muted to --fg-3 (5.0:1 on --bg-2, was 15.9:1), the underline is what
     carries the affordance and the source NAME is the loudest thing in the row. */
  flex: none;
  padding: 0;
  border: 0;
  border-bottom: 1px solid var(--line-2);
  background: none;
  color: var(--fg-3);
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color .3s var(--ease), color .3s var(--ease);
}
.src-a:hover, .src-a:focus-visible { color: var(--accent-tx); border-bottom-color: var(--accent); }
.src-a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* sources library groups */
.src-group + .src-group { margin-top: clamp(30px, 3.4vw, 44px); }
/* U14 — the library had six raspberry group headings stacked over a column of
   raspberry-on-hover links, which is a table, not a section. The heading takes
   the same construction as `.eyebrow` (SPEC §4): muted label + a 32px accent
   hairline, so the accent marks the START of a group instead of colouring the
   words. Built with ::before so `renderSources()` stays a one-line renderer. */
.src-group-l {
  display: flex;
  align-items: center;
  gap: 14px;
  color: var(--fg-3);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.src-group-l::before {
  content: "";
  flex: 0 0 32px;
  height: 1px;
  background: var(--accent);
  opacity: .9;
}
.src-group .src-list { margin-top: 10px; }

/* --- case popup fields --------------------------------------------------- */
.case-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 20px;
  padding-bottom: clamp(26px, 3vw, 38px);
  border-bottom: 1px solid var(--line);
}
.case-n {
  color: var(--fg);
  font-size: var(--t-num-s);
  font-weight: 700;
  line-height: .95;
  letter-spacing: -.035em;
  font-variant-numeric: tabular-nums;
}
.case-u {
  color: var(--accent);
  font-size: .46em;
  font-weight: 700;
  letter-spacing: 0;
  margin-left: .12em;
}
/* the collapsed "уточняется командой" run: keys inline, one value */
.case-f--tbd { padding: 12px 0; }
.case-f--tbd .case-fv { color: var(--fg-3); }
.case-l { color: var(--fg-2); font-size: var(--t-small); line-height: 1.5; max-width: 46ch; }

.case-fields { margin-top: clamp(26px, 3vw, 38px); display: grid; gap: 0; }
.case-f {
  display: grid;
  grid-template-columns: minmax(150px, 200px) minmax(0, 1fr);
  gap: 8px 28px;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
.case-f:last-child { border-bottom: 0; }
.case-fk {
  color: var(--fg-3);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  line-height: 1.5;
}
.case-fv { color: var(--fg); font-size: var(--t-small); line-height: 1.5; max-width: 54ch; }

.case-proof { margin-top: clamp(34px, 4vw, 52px); }
.case-proof-l {
  color: var(--fg-3);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.case-proof .proof-slot { margin-top: 18px; }
.case-proof figure { margin-top: 18px; }
.case-proof figcaption { margin-top: 12px; color: var(--fg-3); font-size: 13px; line-height: 1.5; }
.case-proof .note { margin-top: 14px; }

@media (max-width: 760px) {
  .case-f { grid-template-columns: minmax(0, 1fr); gap: 6px; }
}

/* --- close button + drag handle ------------------------------------------ */
.pop-close {
  position: absolute;
  top: 14px; right: 14px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  border: 1px solid var(--line-2);
  border-radius: 50%;
  background: rgba(21,23,27,.86);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
  color: var(--fg-2);
  cursor: pointer;
  transition: border-color .3s var(--ease), color .3s var(--ease), background-color .3s var(--ease);
}
.pop-close:hover, .pop-close:focus-visible {
  border-color: var(--accent); color: var(--accent); background: var(--accent-soft);
}
.pop-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.pop-handle { display: none; }

/* --- mobile bottom sheet (SPEC §6) --------------------------------------- */
@media (max-width: 760px) {
  .pop { padding: 0; align-items: flex-end; }
  .pop-card {
    width: 100%;
    height: min(94vh, 100%);
    height: min(94dvh, 100%);
    max-height: none;
    border-radius: 22px 22px 0 0;
    border-bottom: 0;
    /* D1 — the sheet sits on the bottom edge, so on a home-indicator phone the
       last row of a source list ends up under the indicator. The padding is on
       the CARD, not on `.pop-inner`: `.pop-scroll` is the flex child that
       clips, so reserving the strip here keeps the scrollable area (and its
       bottom fade) above the indicator instead of scrolling content into it.
       max(0px, …) so the resolved value is exactly the old 0 on every desktop. */
    padding-bottom: max(0px, env(safe-area-inset-bottom, 0px));
  }
  .pop-handle {
    display: block;
    position: relative;
    z-index: 3;
    flex: none;
    width: 36px; height: 4px;
    margin: 10px auto 0;
    border-radius: 999px;
    background: var(--line-2);
    touch-action: none;
  }
  .pop-grab {          /* invisible drag surface over the sheet header */
    position: absolute;
    inset: 0 0 auto 0;
    height: 64px;
    z-index: 2;
    touch-action: none;
  }
  .pop-inner { padding: 26px 20px 40px; }
  .pop-close { top: 10px; right: 10px; width: 40px; height: 40px; }
  .pop-eyebrow { padding-right: 46px; }
  .pop-q { margin-top: 18px; font-size: 18px; }
  .pop-a { font-size: clamp(26px, 7vw, 34px); max-width: none; }
  .pop-a--wide { font-size: clamp(24px, 6.4vw, 32px); }
  .pop-a--caps { font-size: clamp(22px, 5.8vw, 30px); max-width: none; }
  .src { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  /* C7 — 18.8px is not a tap target. The box grows and the underline moves onto
     the text itself, so it still hugs the glyphs.
     U14 — 40px was still under the 44px floor the rest of the page holds
     (`.why`, `.pill`, `.pop-close`); the sources library is the one screen made
     entirely of links, so it gets the same target as everything else. */
  .src-a {
    display: inline-flex; align-items: center; min-height: 44px;
    border-bottom: 0;
    text-decoration: underline;
    text-decoration-color: var(--line-2);
    text-decoration-thickness: 1px;
    text-underline-offset: 4px;
  }
  .src-a:hover, .src-a:focus-visible { text-decoration-color: var(--accent); }
  .case-hero { gap: 6px 14px; }
}


/* ============================================================
   9. DRAWER (SPEC §8 B6) — built now, used by phase 2
   ============================================================ */

.drawer {
  position: fixed; inset: 0;
  z-index: 88;
}
.drawer[hidden] { display: none; }

.drawer-ov {
  position: absolute; inset: 0;
  background: rgba(8,9,11,.66);
  -webkit-backdrop-filter: blur(4px);
          backdrop-filter: blur(4px);
  opacity: 0;
}
/* D1 — the inset lives on `.drawer-card`, NOT on `.drawer`. `.drawer` is the
   `position: fixed` containing block for both children, and an abs-positioned
   child resolves `inset: 0` against the PADDING box — which still includes the
   padding — so `padding-top` on `.drawer` would be inert. On the card it is
   real: `box-sizing: border-box` + `top/bottom: 0` shrinks the content area, so
   `.drawer-scroll` (and its title) start below the status bar / Dynamic Island.
   Every value is 0px without `env()` support and on any un-notched screen. */
.drawer-card {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: min(480px, 100%);
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--line);
  background: var(--bg-2);
  box-shadow: var(--shadow-pop);
  transform: translateX(100%);
  padding-top: max(0px, env(safe-area-inset-top, 0px));
  padding-right: max(0px, env(safe-area-inset-right, 0px));
  padding-bottom: max(0px, env(safe-area-inset-bottom, 0px));
}
.drawer-scroll {
  overflow: auto;
  overscroll-behavior: contain;
  padding: clamp(30px, 4vw, 52px) clamp(24px, 3vw, 44px) clamp(44px, 5vw, 64px);
  scrollbar-width: thin;
  scrollbar-color: var(--line-2) transparent;
}
/* D1 — the close button is absolute against `.drawer-card`'s padding box, which
   the padding above does not shrink, so it has to carry the inset itself or it
   lands back under the status bar (measured box [332,14,44,44] at 390). */
.drawer-close {
  position: absolute;
  top: calc(14px + env(safe-area-inset-top, 0px));
  right: calc(14px + env(safe-area-inset-right, 0px));
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  border: 1px solid var(--line-2);
  border-radius: 50%;
  background: rgba(21,23,27,.86);
  color: var(--fg-2);
  cursor: pointer;
  transition: border-color .3s var(--ease), color .3s var(--ease);
}
.drawer-close:hover, .drawer-close:focus-visible { border-color: var(--accent); color: var(--accent); }
.drawer-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.drawer-t {
  display: flex;
  align-items: center;
  gap: 14px;
  padding-right: 46px;
  color: var(--fg);
  font-size: var(--t-h3);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1.25;
}
/* the same glyph the B6 tile that opened this drawer carries */
.drawer-t > .ic { color: var(--fg-3); }
.drawer-s { margin-top: 10px; color: var(--fg-3); font-size: var(--t-small); line-height: 1.5; }
.drawer-i {
  margin-top: clamp(26px, 3vw, 36px);
  color: var(--fg-2);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.drawer-list { margin-top: 18px; display: grid; }
.drawer-list li {
  position: relative;
  padding: 11px 0 11px 22px;
  border-bottom: 1px solid var(--line);
  color: var(--fg);
  font-size: var(--t-small);
  line-height: 1.45;
}
.drawer-list li:last-child { border-bottom: 0; }
.drawer-list li::before {
  content: "";
  position: absolute; top: 1.32em; left: 0;
  width: 8px; height: 2px;
  background: var(--fg-3);
}
.drawer-o { margin-top: clamp(24px, 2.8vw, 34px); }
.drawer-o p { color: var(--fg-2); font-size: var(--t-small); line-height: 1.55; }
.drawer-o .principle {
  padding-left: 18px;
  border-left: 3px solid var(--accent);
  color: var(--fg);
}
.drawer-o .note { color: var(--fg-3); }

@media (max-width: 760px) {
  .drawer-card { width: 100%; border-left: 0; }
}


/* ============================================================
   10. ANIMATION INITIAL STATES
   Only applied when JS runs and motion is allowed (html.js:not(.rm)).
   GSAP gotcha (DECISIONS): never pre-hide with a translate that GSAP will
   later drive with yPercent — pre-hide with opacity/visibility instead.

   `.nogsap` is set by main.js when the GSAP bundle failed to load: nothing can
   ever un-hide these, so every pre-hidden state falls back to its resting value
   and the page renders finished. Same guard on every custom property below —
   each one already defaults to its FINAL value on the bare selector, so no-JS
   and reduced motion need no guard at all.
   ============================================================ */

.js:not(.rm):not(.nogsap) .rv,
.js:not(.rm):not(.nogsap) .rv-i,
.js:not(.rm):not(.nogsap) .rv-cell,
.js:not(.rm):not(.nogsap) .rv-chain,
.js:not(.rm):not(.nogsap) .fx { opacity: 0; }
/* 126 %, not 108 %: the B0 cinema round gave `.b0-title .ln` .14em/.18em of
   headroom so no Prata glyph is clipped (blocks.css), and the inner line has
   to clear the taller mask - 100 % + 18 % + the 3.3 % of ink that overshoots
   its own box. */
.js:not(.rm):not(.nogsap) .b0-title .ln-i { transform: translateY(126%); }

/* draw-on infographics (A3 B5 · A4 B4 · A8 B6 · A13 B8) */
.js:not(.rm):not(.nogsap) .b5-scenes { --b5-draw: 0; }
.js:not(.rm):not(.nogsap) .b4-arm   { --b4-draw: 0; --b4-dot: 0; }
.js:not(.rm):not(.nogsap) .b6-scheme{ --b6-draw: 0; }
.js:not(.rm):not(.nogsap) .b8-card  { --b8-rule: 0; }
.js:not(.rm):not(.nogsap) .b8-cell  { clip-path: inset(0 100% 0 0); }


/* ============================================================
   11. REDUCED MOTION (SPEC §0.8)
   ============================================================ */

/* ============================================================
   12. DESKTOP TYPE LIFT (>= 1024px)
   The client could not read the small uppercase captions on a desktop
   («это никто читать не будет»). The label token goes up one step from
   1024px (P2 — it was 1180px, which left every 1024–1179 laptop on the
   small setting) and everything built on it follows. Two families are deliberately
   pinned back to 12.5px because they were tuned separately and are not part
   of the complaint: the fixed chrome (section counter + «Источники» pill)
   and the inside of a popup card. Mobile sizes are untouched — the phone
   layout was already fine.
   Per-block captions that the token alone does not lift far enough live at
   the end of blocks.css under the same media query.
   ============================================================ */

@media (min-width: 1024px) {
  :root { --t-label: 13.5px; }

  /* fixed chrome — unchanged on purpose */
  .counter, .pill { font-size: 12.5px; }

  /* popup card internals — tuned in the ТЗ-compliance round, left alone */
  .pop-eyebrow, .pop-mean-l, .pop-act, .pop-sources-l,
  .src-group-l, .case-fk, .case-proof-l { font-size: 12.5px; }

  /* markers: the compact chip and the corner chip inside a tile */
  .why--sm   { font-size: 12.5px; }
  .tile-chip { font-size: 12.5px; }
}


/* ============================================================
   13. LARGE DESKTOP TYPE (>= 1600px)
   U15 — `--t-h1` tops out at 64px, which it reaches at 1391px (4.6vw). From
   there to 1920 the display type stopped growing while the 1280px measure and
   every gutter kept widening, so the statements read progressively smaller
   against the page. The cap moves to 78px, reached at 1696px.
   Deliberately NOT touched:
   • `--t-display` is declared in §1 and used by nothing — `.b0-title` carries
     its own `clamp(42px, 6.1vw, 90px)`. Raising it would change no pixel.
   • `.b0-title` itself. Its ceiling is not a taste call: `.ln-i` is `nowrap`
     and `.ln` clips, so an oversized line is CUT, not wrapped, and «Метода
     Тальписа» is the longest line. Measured headroom in the 793.6px copy
     column at 1600 and 1920 is ~2px of type size — see DECISIONS, U15.
   • `.b1-q` (its own clamp, capped below `.b0-title` by C1) and
     `.statement--intro` / `.pop-a--wide`, which are the long-form Prata step.
   ============================================================ */

@media (min-width: 1600px) {
  :root { --t-h1: clamp(34px, 4.6vw, 78px); }
}


@media (prefers-reduced-motion: reduce) {
  .rv, .rv-i, .rv-cell, .rv-chain, .fx { opacity: 1 !important; transform: none !important; }
  /* A13 — the B8 rows arrive by clip-path; reduced motion gets them written */
  .b8-cell { clip-path: none !important; }
  .tile:active, .why:active, .b3-num:active, .b4-fig:active,
  .b6-tile:active, .b5-btn:active, .pill:active { transform: none !important; }
  .tile::before { transition: none; }
  * {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
