/* Base — Pavel Seldemirov. Global entry point. Import lines only. */










/* Webfonts — self-hosted, no CDN and no third-party request at runtime. §9.
   Three families, three fixed roles:
     Archivo Variable (OFL)      wght 100-900, wdth 62-125  — Latin reading + monumental
     Roboto Flex Variable (OFL)  wght 100-1000, wdth 25-151 — Cyrillic reading + monumental
     IBM Plex Mono Variable (OFL) wght 100-700, Roman+Italic — register, BOTH scripts
   The register voice is deliberately NOT unicode-range-scoped: v7.3.3 onward requires Plex to
   carry coordinates, IDs, units and legends in Cyrillic as well as Latin. Files live in
   assets/fonts/. Fallback metrics are measured, not published — see source/amendments.md A-01. */

@font-face{font-family:"Archivo";src:url("/ds/assets/fonts/archivo-latin-variable.woff2") format("woff2-variations");font-weight:100 900;font-stretch:62% 125%;font-style:normal;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}

/* Register voice — both scripts, no unicode-range. */
@font-face{font-family:"IBM Plex Mono";src:url("/ds/assets/fonts/plex-mono-variable-roman.woff2") format("woff2-variations");font-weight:100 700;font-style:normal;font-display:swap}
@font-face{font-family:"IBM Plex Mono";src:url("/ds/assets/fonts/plex-mono-variable-italic.woff2") format("woff2-variations");font-weight:100 700;font-style:italic;font-display:swap}

/* Cyrillic reading + monumental voice. Full reading locale, not archival decoration. */
@font-face{font-family:"Roboto Flex Variable";src:url("/ds/assets/fonts/roboto-flex-cyrillic.woff2") format("woff2-variations");font-weight:100 1000;font-stretch:25% 151%;font-style:normal;font-display:swap;unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:"Roboto Flex Variable";src:url("/ds/assets/fonts/roboto-flex-cyrillic-ext.woff2") format("woff2-variations");font-weight:100 1000;font-stretch:25% 151%;font-style:normal;font-display:swap;unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}

/* Metric-matched fallbacks. size-adjust measured against the real face at the same size,
   so the swap causes no layout shift. Archivo measures 107.9% against Helvetica Neue and
   only 1.3% against Arial — which is why Arial, not Helvetica, is the fallback.
   A Cyrillic fallback for Roboto Flex is measured and added only if a platform needs one;
   a fallback may never silently change the intended script hierarchy. */
@font-face{font-family:"Archivo Fallback";src:local("Arial"),local("Liberation Sans");size-adjust:98.7%;ascent-override:89.2%;descent-override:21.3%;line-gap-override:0%}
@font-face{font-family:"Plex Mono Fallback";src:local("Courier New"),local("Liberation Mono");size-adjust:100.0%;ascent-override:103.0%;descent-override:28.0%;line-gap-override:0%}
/* Colour. §11 of source/design.md (v7.4.0).
   OKLCH is the authoring space: every token below records its L / C / H anchor.
   A hex alone is not sufficient design-source data — hex is the delivery value.
   Shell greys are strictly neutral: EQUAL RGB CHANNELS, OKLCH C = 0. C ≤ .005 is
   conversion tolerance, never permission to tint. Machine-readable palette with
   curves, gamut status and tested pairs: tokens/palette.json.

   Cross-family combination (Pavel, 2026-08-14): the five living families below are not five
   sealed silos — more than one may appear on the same page. What makes that legal is that all
   five come from ONE shared OKLCH generation method (Family generation — OKLCH 50–950), so
   anything derived from it is coherent with everything else derived from it BY CONSTRUCTION;
   --base-bruise ("night / bodily overlap") is that shared code at a boundary, not a hand-tuned
   compromise. A colour that did NOT come from this method — an arbitrary hex, anything picked by
   eye — requires recalculating the WHOLE set's relationships against it, not a pairwise spot
   check. There is currently no automated pairwise contrast/harmony matrix across families —
   tokens/palette.json has no "tested pairs" yet; only within-family ramps are checked. */
:root{
  /* --- Persistent neutral research shell (C = 0, mandatory) --- */
  --base-white:#ffffff; /* @kind color */      /* oklch(100% 0 none)     — hard cut; text only on measured dark */
  --base-field:#fafafa; /* @kind color */      /* oklch(98.510% 0 none)  — canonical background + reading ground */
  --base-paper:var(--base-field); /* legacy alias; implies no material */
  --base-cut:#f8f8f8; /* @kind color */        /* oklch(97.913% 0 none)  — near-white exposure, local separation */
  --base-cut-white:var(--base-cut); /* @kind color */ /* legacy name, retained for call sites */
  --base-lab-01:#e8e8e8; /* @kind color */     /* oklch(93.100% 0 none)  — secondary neutral surface */
  --base-lab-02:#dddddd; /* @kind color */     /* oklch(89.755% 0 none)  — recessed neutral surface */
  --base-fog:#d4d4d4; /* @kind color */        /* oklch(86.994% 0 none)  — optical carrier, never body text */
  --base-lab-line:#b9b9b9; /* @kind color */   /* oklch(78.576% 0 none)  — divider, calibration line */
  --base-grey:#9a9a9a; /* @kind color */       /* oklch(68.622% 0 none)  — non-text structure, inactive marks */
  --base-text-quiet:#6a6a6a; /* @kind color */ /* oklch(52.430% 0 none)  — quietest functional text */
  --base-grey-text:var(--base-text-quiet); /* @kind color */ /* legacy name */
  --base-lab-meta:#606060; /* @kind color */   /* oklch(48.906% 0 none)  — secondary functional text */
  --base-shadow:#171717; /* @kind color */     /* oklch(20.463% 0 none)  — dark surface */
  --base-ink:#0a0a0a; /* @kind color */        /* oklch(14.479% 0 none)  — primary functional text */

  /* --- Legacy material aliases; neutral in the global shell --- */
  --base-plaster:var(--base-lab-01); /* @kind color */
  --base-polymer:var(--base-lab-02); /* @kind color */
  --base-cold-grey:var(--base-lab-meta); /* @kind color */

  /* --- Project-local living + material families. Never a global shell tint,
         never an automatic CTA colour. Each is a seed at step 500; generated
         steps are candidates for local use only (see palette.json). --- */
  --base-hazel-water:#60777d; /* @kind color */  /* oklch(55.304% .02847 215.35) — water/glass anchor */
  --base-bottle-glass:#173a34; /* @kind color */ /* oklch(32.044% .04212 180.77) — water/glass anchor */
  --base-project-cyan:#4a9a9a; /* @kind color */ /* oklch(63.638% .07826 195.37) — water/glass variation */
  --base-night-violet:#292733; /* @kind color */ /* oklch(28.007% .02200 292.56) — night fauna anchor */
  --base-bruise:#5c5268; /* @kind color */       /* oklch(45.661% .03728 305.58) — night / bodily overlap */
  --base-body-depth:#8c4f46; /* @kind color */   /* oklch(49.717% .08398  29.29) — bodily depth anchor */
  --base-muted-coral:#c2766a; /* @kind color */  /* oklch(64.466% .09808  29.66) — bodily depth variation */
  --base-red-earth:#7d4c3f; /* @kind color */    /* oklch(46.967% .07026  35.63) — route and ground anchor */
  --base-laser-trace:#8f3038; /* @kind color */  /* oklch(45.093% .12828  19.07) — short threshold event */

  /* --- Core hidden-process carrier calibration (Core dialect only, §7) --- */
  --base-carrier-blueblack:#151d2a; /* @kind other */ /* oklch(22.942% .02801 259.93) */
  --base-carrier-black:#08090b; /* @kind other */     /* oklch(13.945% .00483 262.80) */
  --base-carrier-steel:#58616d; /* @kind other */     /* oklch(48.918% .02229 255.62) */

  /* --- Semantic roles (§11, "Semantic colour roles").
         Palette membership never grants a colour every function. --- */
  /* ONLY THE FIELD IS OPAQUE — every element above it carries alpha (Pavel, 2026-08-28).
     This is a precondition of optical depth, not a preference: a plane drawn behind a page is only
     visible where the page lets it through, so an opaque panel does not "cover a shadow", it deletes
     the depth beneath it entirely. The four semantic surfaces are therefore stated as the SAME hue at
     an alpha share of the field, so a panel still reads as a panel while every plane behind it stays
     legible. `--surface-field` keeps no alpha of its own: it IS the field.
     Opaque values remain available as `--surface-*-solid` for the rare element that must occlude
     (a modal scrim, a print sheet) — naming it makes the exception visible in review. */
  --surface-field:var(--base-field); /* @kind color */
  --surface-quiet:color-mix(in srgb, var(--base-lab-01) 62%, transparent); /* @kind color */
  --surface-raised:color-mix(in srgb, var(--base-lab-02) 68%, transparent); /* @kind color */
  --surface-cut:color-mix(in srgb, var(--base-cut) 72%, transparent); /* @kind color */
  --surface-inverse:color-mix(in srgb, var(--base-ink) 88%, transparent); /* @kind color */
  --surface-quiet-solid:var(--base-lab-01); /* @kind color */
  --surface-raised-solid:var(--base-lab-02); /* @kind color */
  --surface-cut-solid:var(--base-cut); /* @kind color */
  --surface-inverse-solid:var(--base-ink); /* @kind color */

  /* Measured WCAG 2.1 on the canonical field #fafafa:
     ink 18.97:1 · meta 6.02:1 · quiet 5.18:1 · grey 2.70:1 (NOT text).
     Lightest chromatic anchor legal for normal text on the field: hazel water 4.54:1.
     Project cyan 3.15:1 and muted coral 3.30:1 are large-text / non-text only. */
  --text-primary:var(--base-ink); /* @kind color */
  --text-meta:var(--base-lab-meta); /* @kind color */
  /* Third step: 5.18:1 on --surface-field, 4.41:1 on --surface-quiet — use
     --text-meta on raised surfaces. NEVER --base-grey (2.70:1). Never reduce
     with opacity: contrast belongs to the final composite. */
  --text-quiet:var(--base-text-quiet); /* @kind color */
  /* Non-text tint for marks, hairlines and dividers — not legal for type. */
  --mark-quiet:var(--base-grey); /* @kind color */
  --text-inverse:var(--base-white); /* @kind color */

  --line-hairline:var(--base-lab-line); /* @kind color */
  --line-rule:rgba(10,10,10,.22); /* @kind color */
  /* MEASURED FROM THE REFERENCE SHEET, not chosen. Sampling `Gallery — Analytical Baseline — Index`
     (1672×941): its field is rgb(236,232,223) and the 1:1 rule reads L=118, i.e. 0.50 ink over the field,
     across a 2px run — 1.41px at a 1180px card, so ONE px. The stroke width was never the defect: the axis
     was drawn at `--base-ink`, full-opacity near-black, which is the ink reserved for TEXT, and perceived
     weight is width × contrast. Half-ink over #fafafa resolves to rgb(130,130,130) ≈ 3.4:1 — clear of the
     3:1 non-text floor, so the axis stays legible while it stops competing with the captions.
     Group dividers in the same sheet read L=207, i.e. 0.124 ink: fainter than `--line-rule` (0.22) and
     stronger than `--line-grid` (0.07), so neither existing token was the right one. */
  --line-axis:rgba(10,10,10,.50); /* @kind color */
  --line-column:rgba(10,10,10,.124); /* @kind color */
  --line-grid:rgba(10,10,10,.07); /* @kind color */
  --line-grid-minor:rgba(10,10,10,.04); /* @kind color */

  --link-ink:var(--base-ink); /* @kind color */
  --link-ink-hover:var(--base-lab-meta); /* @kind color */
  --link-underline:rgba(10,10,10,.42); /* @kind color */
  --focus-ring:var(--base-ink); /* @kind color */

  --veil-cold:rgba(21,29,42,.19); /* @kind color */
  --veil-neutral:rgba(10,10,10,.10); /* @kind color */
}
/* Type. §9 of source/design.md (v7.4.0). Three families, three fixed roles.
   The READING voice changes family with the script; the REGISTER voice does not.
   Latin reading + monumental : Archivo Variable
   Cyrillic reading + monumental : Roboto Flex Variable  (a complete locale, not an effect)
   Register, both scripts : IBM Plex Mono Variable
   Register voice occupies 15-30% of the functional text in a viewport, never more.
   No serif and no fourth visible family is authorised. */
:root{
  --font-reading-latin:"Archivo","Archivo Fallback",Arial,sans-serif; /* @kind font */
  --font-reading-cyrillic:"Roboto Flex Variable","Roboto Flex",Arial,sans-serif; /* @kind font */
  --font-register:"IBM Plex Mono","Plex Mono Fallback","Courier New",monospace; /* @kind font */

  /* Live reading face. Resolves per script through the :lang() rules below, so a
     component never has to choose a family — or restate the locale policy. */
  --font-reading:var(--font-reading-latin); /* @kind font */
  --font-monumental:var(--font-reading); /* @kind font */
  --font-source:var(--font-reading-cyrillic); /* @kind font */ /* deprecated alias, pre-7.3.3 */

  /* Reading axes. Archivo: wght 100-900, wdth 62-125. */
  --width-reading:100; /* @kind font */
  --width-monumental:125; /* @kind font */

  /* Primary title */
  --type-title-size:clamp(36px,6vw,96px); /* @kind font */
  --type-title-weight:600; /* @kind font */
  --type-title-tracking:-0.028em; /* @kind font */
  --type-title-leading:0.94; /* @kind font */

  /* Section title */
  --type-section-size:clamp(24px,3.2vw,56px); /* @kind font */
  --type-section-weight:520; /* @kind font */
  --type-section-tracking:-0.012em; /* @kind font */
  --type-section-leading:1.02; /* @kind font */

  /* Body */
  --type-body-size:clamp(16px,1.05vw,19px); /* @kind font */
  --type-body-weight:400; /* @kind font */
  --type-body-tracking:0.002em; /* @kind font */
  --type-body-leading:1.58; /* @kind font */

  /* Small human-language note — reading voice, sentence case. */
  --type-reading-note-size:clamp(11px,.78vw,13px); /* @kind font */
  --type-reading-note-weight:400; /* @kind font */
  --type-reading-note-tracking:0.005em; /* @kind font */
  --type-reading-note-leading:1.58; /* @kind font */

  /* Quiet instrumental register — exact action or source label without the
     compressed uppercase pressure of a micro label. */
  --type-register-quiet-size:11px; /* @kind font */
  --type-register-quiet-weight:400; /* @kind font */
  --type-register-quiet-tracking:0.025em; /* @kind font */
  --type-register-quiet-leading:1.55; /* @kind font */

  /* Metadata — register voice */
  --type-meta-size:12px; /* @kind font */
  --type-meta-weight:450; /* @kind font */
  --type-meta-tracking:0.13em; /* @kind font */
  --type-meta-leading:1.34; /* @kind font */

  /* Micro label — register voice */
  --type-micro-size:10px; /* @kind font */
  --type-micro-weight:500; /* @kind font */
  --type-micro-tracking:0.2em; /* @kind font */
  --type-micro-leading:1.2; /* @kind font */

  /* Core monumental glyph. Latin: Archivo Expanded Black, 900 / wdth 125. */
  --type-monumental-size:clamp(18vw,30vw,46vw); /* @kind font */
  --type-monumental-weight:900; /* @kind font */
  --type-monumental-tracking:-0.05em; /* @kind font */
  --type-monumental-leading:0.76; /* @kind font */

  /* Measures. §0.6 / long-form essays: a substantial block is 45-68ch; a hinge is
     18-34ch and stays a rare event (0-2 per major section). */
  --measure-body:62ch; /* @kind other */
  --measure-block-min:45ch; /* @kind other */
  --measure-block-max:68ch; /* @kind other */
  --measure-hinge:28ch; /* @kind other */
  --measure-hinge-min:18ch; /* @kind other */
  --measure-hinge-max:34ch; /* @kind other */
}

/* Language and script policy (§0.7, §13). English is the primary locale; Russian is a
   complete secondary locale, not a source-script exception. Set lang on the document root
   for a whole locale, or on the passage when only one passage changes language.
   A register element keeps IBM Plex Mono in either script: the register role outranks
   the reading role. Cyrillic monumental type uses Roboto Flex's NATIVE width axis —
   never the Latin expanded cut simulated by transforming outlines. */
/* One rule, one declaration set. [data-locale="ru"] is the registered theme scope, so the
   values read as tokens in tooling; the :lang() selectors carry the same switch in real
   documents, where the locale is expressed on the markup rather than on a data attribute. */
[data-locale="ru"],:lang(ru),:lang(uk),:lang(bg),:lang(sr),:lang(be),:lang(mk){
  --font-reading:var(--font-reading-cyrillic); /* @kind font */
  --width-reading:100; /* @kind font */
  --width-monumental:100; /* @kind font */
  --type-monumental-weight:950; /* @kind font */
}
/* Space, stage, grid, geometry.
   Base has no card component: grouping comes from spacing, shared axes and substrate change. */
:root{
  --space-1:2px; --space-2:4px; --space-3:8px; --space-4:12px;
  --space-5:16px; --space-6:24px; --space-7:32px; --space-8:48px;
  --space-9:64px; --space-10:96px; --space-11:128px; --space-12:192px;

  --gutter:clamp(24px,4vw,96px); /* @kind spacing */
  --stage-max:1800px; /* @kind spacing */
  --carrier-sliver:0.35%; /* @kind spacing */ /* rare thin vertical counterpoint (SmallMarks rule mark) */
  --grid-columns:12; /* @kind other */
  --grid-columns-tablet:8; /* @kind other */
  --grid-columns-mobile:4; /* @kind other */
  --grid-gap:clamp(12px,1.4vw,28px); /* @kind spacing */

  /* Paragraph Path rhythm */
  --rhythm-block-min:6vh; /* @kind spacing */
  --rhythm-block:11vh; /* @kind spacing */
  --rhythm-block-max:18vh; /* @kind spacing */
  --rhythm-after-event:22vh; /* @kind spacing */
  --event-cadence:2.2; /* @kind other */

  /* Geometry */
  --radius-control:0px; /* @kind radius */
  --radius-max:2px; /* @kind radius */
  --hairline:1px; /* @kind spacing */
  --rule-weight:1px; /* @kind spacing */
  --rule-weight-strong:2px; /* @kind spacing */
  --focus-width:2px; /* @kind spacing */
  --focus-offset:3px; /* @kind spacing */
  --hit-min:44px; /* @kind spacing */
  --shell-height:62px; /* @kind spacing */ /* persistent global bar; the ribbon opens below it */

  /* Core dialect proportions */
  --strip-height-min:1.5%; /* @kind spacing */
  --strip-height-max:8%; /* @kind spacing */
  --strip-shift-x:4%; /* @kind other */
  --white-cut-ratio:8; /* @kind other */
}
/* Compositing order (Core stack) and the two authorised shadows. */
:root{
  --layer-field:0; /* @kind other */
  --layer-evidence:1; /* @kind other */
  --layer-object:2; /* @kind other */
  --layer-glyph:3; /* @kind other */
  --layer-cut:4; /* @kind other */
  --layer-plane:5; /* @kind other */
  --layer-carrier:6; /* @kind other */
  --layer-type:7; /* @kind other */
  --layer-mark:8; /* @kind other */
  --layer-continuity:9; /* @kind other */
  --layer-ribbon:40; /* @kind other */
  --layer-shell:60; /* @kind other */ /* above the ribbon: shell labels are never occluded */
  --layer-viewer:80; /* @kind other */

  --shadow-none:none; /* @kind shadow */
  --shadow-contact:0 1px 5px rgba(0,0,0,.09); /* @kind shadow */
  --shadow-lifted:0 16px 36px rgba(0,0,0,.17); /* @kind shadow */
}
/* Optical softness is a vector, not one global blur.
   Functional text, navigation, controls and focus outlines are always 0.

   Recalibrated 2026-08-11 (A-47). Reviewed on the Material ground and Hard–soft lab cards, the
   verdict was that only `quiet` read as material — `balanced` and `volatile` read smeared. The
   whole ladder therefore moved down by roughly one step: the old `quiet` is about the new
   `balanced`, and the old `balanced` is about the new `volatile`. Three steps are still measurably
   distinct (that is what `data-optic` has to prove), but none of them defocuses a surface into
   atmosphere any more: softness is carried by real material and by the seam band, not by blur.

   `volatile` DEPRECATED 2026-08-14 (Pavel, A-52): it still renders every soft carrier as an
   unfocused, soapy smear and is not usable as shipped — do not set `data-optic="volatile"` on any
   real page. `quiet` is the primary preset system-wide; `balanced` only where the extra feather is
   doing real work. The tokens stay defined (existing comparison cards read all three), but nothing
   in `templates/` may use `volatile` until it is rebuilt. */
:root{
  --optic-layer-blur-quiet:.5px; /* @kind spacing */
  --optic-layer-blur-balanced:1.5px; /* @kind spacing */
  --optic-layer-blur-volatile:4px; /* @kind spacing */

  --optic-edge-feather-quiet:2px; /* @kind spacing */
  --optic-edge-feather-balanced:5px; /* @kind spacing */
  --optic-edge-feather-volatile:13px; /* @kind spacing */

  --optic-smear-quiet:1.5px; /* @kind spacing */
  --optic-smear-balanced:4px; /* @kind spacing */
  --optic-smear-volatile:12px; /* @kind spacing */

  --optic-veil-quiet:.05; /* @kind other */
  --optic-veil-balanced:.10; /* @kind other */
  --optic-veil-volatile:.20; /* @kind other */

  --optic-falloff-quiet:.05; /* @kind other */
  --optic-falloff-balanced:.11; /* @kind other */
  --optic-falloff-volatile:.24; /* @kind other */

  /* Active preset — pages override on a scope, never per element. */
  --optic-layer-blur:var(--optic-layer-blur-balanced); /* @kind spacing */
  --optic-edge-feather:var(--optic-edge-feather-balanced); /* @kind spacing */
  --optic-smear:var(--optic-smear-balanced); /* @kind spacing */
  --optic-veil:var(--optic-veil-balanced); /* @kind other */
  --optic-falloff:var(--optic-falloff-balanced); /* @kind other */
}
[data-optic="quiet"]{--optic-layer-blur:var(--optic-layer-blur-quiet);--optic-edge-feather:var(--optic-edge-feather-quiet);--optic-smear:var(--optic-smear-quiet);--optic-veil:var(--optic-veil-quiet);--optic-falloff:var(--optic-falloff-quiet)}
[data-optic="volatile"]{--optic-layer-blur:var(--optic-layer-blur-volatile);--optic-edge-feather:var(--optic-edge-feather-volatile);--optic-smear:var(--optic-smear-volatile);--optic-veil:var(--optic-veil-volatile);--optic-falloff:var(--optic-falloff-volatile)}
/* Material surfaces — softness as a SURFACE WITH HISTORY, not a computed blur
   (PS_DS 7.4.2, hard–soft temperament; §11 for the colour data).
   These are PROJECT-LOCAL surfaces. The shell greys stay equal-channel (OKLCH C = 0) and are
   not redefined here. A surface token never carries text, a control, a boundary or a focus ring. */
:root{
  /* The one real material source, as a PATH relative to the design-system root — not a url().
     Chrome resolves a relative url() inside a custom property against the DOCUMENT, not against
     the stylesheet that declares it, so a token holding a relative image reference 404s on every page
     that is not one level down. Components join this path onto the design-system root they were
     loaded from (the _ds_bundle.js / styles.css URL), which is correct at any depth and in a
     consuming project's _ds/<folder> tree. */
  --surface-material-path:"assets/materials/core/mineral-carrier-v1.png"; /* @kind other */

  --surface-satin:#23272c; /* @kind color */   /* oklch(27.62% 0.0126 253.6) — close dark, low chroma, reflective */
  --surface-plaster:#ece7dd; /* @kind color */ /* oklch(92.63% 0.0104 84.4)  — warm-neutral chalk */
  --surface-veil:#f4f2ee; /* @kind color */    /* oklch(96.09% 0.0053 91.8)  — atmosphere, never a tint of the field */

  /* Presence, not opacity: how much of the field the surface is allowed to carry. Scaled at use
     by --optic-falloff, so material travels the same axis as blur, feather and veil. */
  --surface-satin-presence:.17; /* @kind other */
  --surface-plaster-presence:.28; /* @kind other */
  --surface-veil-presence:.20; /* @kind other */
  /* Share of the presence carried by the material's own relief rather than by flat tone. */
  --surface-relief:.55; /* @kind other */
}
/* Motion exposes construction. No bounce, elastic, glitch loop or ambient drift. */
:root{
  --motion-micro:180ms; /* @kind other */
  --motion-micro-min:120ms; /* @kind other */
  --motion-micro-max:220ms; /* @kind other */
  --motion-hover:220ms; /* @kind other */
  --motion-open:420ms; /* @kind other */
  --motion-transition:480ms; /* @kind other */
  --motion-recompose:560ms; /* @kind other */
  --motion-stagger:40ms; /* @kind other */
  --motion-ribbon-in:220ms; /* @kind other */
  --motion-ribbon-out:160ms; /* @kind other */
  --motion-autonomous:12s; /* @kind other */
  /* Softness in time: ONE long duration for a layer that accumulates tone after arrival and
     stops in a persistent afterstate. Never a loop, never ambient drift, never on the path of a
     functional element. One layer per screen. */
  --motion-accumulate:2600ms; /* @kind other */

  --ease-base:cubic-bezier(0.22,0.72,0.18,1); /* @kind other */
  --ease-settle:cubic-bezier(0.16,0.84,0.24,1); /* @kind other */
  --ease-out:cubic-bezier(0,0,0.32,1); /* @kind other */
  --ease-linear:linear; /* @kind other */

  /* Interaction deltas */
  --hover-shift-x:6px; /* @kind spacing */
  --hover-lift-z:40px; /* @kind spacing */
  --hover-scale:1.07; /* @kind other */
  --press-opacity:.62; /* @kind other */
}
@media (prefers-reduced-motion:reduce){
  :root{
    /* The state change still happens — it arrives at once instead of travelling. */
    --motion-micro:0ms; /* @kind other */
    --motion-hover:0ms; /* @kind other */
    --motion-ribbon-in:0ms; /* @kind other */
    --motion-ribbon-out:0ms; /* @kind other */
    --motion-open:0ms; /* @kind other */
    --motion-transition:0ms; /* @kind other */
    --motion-recompose:0ms; /* @kind other */
    --motion-stagger:0ms; /* @kind other */
    --motion-autonomous:0s; /* @kind other */
    /* The accumulation still ARRIVES — it arrives already settled. */
    --motion-accumulate:0ms; /* @kind other */
    --hover-lift-z:0px; /* @kind spacing */
    --hover-scale:1; /* @kind other */
  }
}
/* Continuous authoring parameters. Store per page/component; never scatter literals.
   Raise at most three personal parameters per page. */
:root{
  /* Personal parameters */
  --ps-joint-visibility:.68; /* @kind other */
  --ps-transduction-distance:.60; /* @kind other */
  --ps-container-permeability:.56; /* @kind other */
  --ps-trace-persistence:.48; /* @kind other */
  --ps-route-commitment:.60; /* @kind other */
  --ps-address-specificity:.74; /* @kind other */
  --ps-response-autonomy:.58; /* @kind other */
  --ps-threshold-tension:.36; /* @kind other */
  --ps-repair-visibility:.52; /* @kind other */
  --ps-historical-layering:.40; /* @kind other */
  --ps-vitality-chroma:.30; /* @kind other */
  --ps-other-autonomy:.68; /* @kind other */

  /* Shell integrity controls — hard quality gates */
  --ps-instrument-legibility:.74; /* @kind other */
  --ps-coordinate-integrity:1; /* @kind other */
  --ps-route-legibility:.72; /* @kind other */
  --ps-register-density:.38; /* @kind other */
  --ps-shell-neutrality:1; /* @kind other */
  --ps-project-autonomy:1; /* @kind other */

  /* Mode dialect controls */
  --ps-fragment-scale:.55; /* @kind other */
  --ps-relation-explicitness:.60; /* @kind other */
  --ps-route-multiplicity:.60; /* @kind other */
  --ps-parent-continuity:.55; /* @kind other */
  --ps-paragraph-displacement:.62; /* @kind other */
  --ps-material-causality:.45; /* @kind other */
  --ps-state-mutation:.35; /* @kind other */

  /* Core-local material dialect; inactive values may be zero */
  --base-surface-physicality:.46; /* @kind other */
  --base-paper-presence:.06; /* @kind other */
  --base-plaster-presence:.56; /* @kind other */
  --base-polymer-presence:.20; /* @kind other */
  --base-digital-depth:.64; /* @kind other */
  --base-cold-veil:.00; /* @kind other */
  --base-dissonance:.48; /* @kind other */
  --base-recognition:.45; /* @kind other */
  --base-motion-response:.24; /* @kind other */

  /* Compressed Line geometry */
  --line-card-w:clamp(16rem,28vw,32rem); /* @kind spacing */
  --line-card-aspect:0.75; /* @kind other */
  --line-exposed-share:.12; /* @kind other */
}
[data-mode="core"]{
  --ps-fragment-scale:.72; /* @kind other */
  --ps-relation-explicitness:.24; /* @kind other */
  --ps-route-multiplicity:.34; /* @kind other */
  --ps-parent-continuity:.76; /* @kind other */
  --ps-paragraph-displacement:.72; /* @kind other */
  --ps-material-causality:.48; /* @kind other */
  --ps-state-mutation:.32; /* @kind other */
}
[data-mode="index"]{
  --ps-fragment-scale:.30; /* @kind other */
  --ps-relation-explicitness:.86; /* @kind other */
  --ps-route-multiplicity:.80; /* @kind other */
  --ps-parent-continuity:.20; /* @kind other */
  --ps-paragraph-displacement:.55; /* @kind other */
  --ps-material-causality:.15; /* @kind other */
  --ps-state-mutation:.38; /* @kind other */
}
[data-mode="corpus"]{
  --ps-fragment-scale:.48; /* @kind other */
  --ps-relation-explicitness:.55; /* @kind other */
  --ps-route-multiplicity:.60; /* @kind other */
  --ps-parent-continuity:.50; /* @kind other */
  --ps-paragraph-displacement:.82; /* @kind other */
  --ps-material-causality:.68; /* @kind other */
  --ps-state-mutation:.86; /* @kind other */
}
[data-mode="specimen"]{
  --ps-fragment-scale:.84; /* @kind other */
  --ps-relation-explicitness:.15; /* @kind other */
  --ps-route-multiplicity:.20; /* @kind other */
  --ps-parent-continuity:.82; /* @kind other */
  --ps-paragraph-displacement:.32; /* @kind other */
  --ps-material-causality:.92; /* @kind other */
  --ps-state-mutation:.32; /* @kind other */
}
/* Element defaults, stage, hidden construction grid, utilities.
   No card, no radius, no floating panel. */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--surface-field);
  color:var(--text-primary);
  font-family:var(--font-reading);
  font-size:var(--type-body-size);
  font-weight:var(--type-body-weight);
  line-height:var(--type-body-leading);
  letter-spacing:var(--type-body-tracking);
  -webkit-font-smoothing:antialiased;
  text-wrap:pretty;
}
h1,h2,h3,h4,p,figure,blockquote,ol,ul{margin:0}
ol,ul{padding:0;list-style:none}
img,video{display:block;max-width:100%}
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
a{color:var(--link-ink);text-decoration:none;text-underline-offset:.22em;text-decoration-thickness:from-font;text-decoration-color:var(--link-underline)}
a:hover{color:var(--link-ink-hover);text-decoration:underline;text-decoration-color:currentColor}
a:active{opacity:var(--press-opacity)}
/* Visited is a real state in the link grammar (Atlas 02 / 01): quieter ink, same underline. */
a:visited{color:var(--text-meta)}
a:visited:hover{color:var(--link-ink-hover)}
:where(a,button,[tabindex]):focus-visible{outline:var(--focus-width) solid var(--focus-ring);outline-offset:var(--focus-offset)}
::selection{background:var(--base-ink);color:var(--base-white)}

.base-title{font-size:var(--type-title-size);font-weight:var(--type-title-weight);letter-spacing:var(--type-title-tracking);line-height:var(--type-title-leading)}
.base-section-title{font-size:var(--type-section-size);font-weight:var(--type-section-weight);letter-spacing:var(--type-section-tracking);line-height:var(--type-section-leading)}
.base-reading-note{font-family:var(--font-reading);font-size:var(--type-reading-note-size);font-weight:var(--type-reading-note-weight);letter-spacing:var(--type-reading-note-tracking);line-height:var(--type-reading-note-leading);text-transform:none;color:var(--text-meta)}
.base-register-quiet{font-family:var(--font-register);font-size:var(--type-register-quiet-size);font-weight:var(--type-register-quiet-weight);letter-spacing:var(--type-register-quiet-tracking);line-height:var(--type-register-quiet-leading);text-transform:none;color:var(--text-meta)}
.base-meta{font-family:var(--font-register);font-size:var(--type-meta-size);font-weight:var(--type-meta-weight);letter-spacing:var(--type-meta-tracking);line-height:var(--type-meta-leading);text-transform:uppercase;color:var(--text-meta)}
.base-micro{font-family:var(--font-register);font-size:var(--type-micro-size);font-weight:var(--type-micro-weight);letter-spacing:var(--type-micro-tracking);line-height:var(--type-micro-leading);text-transform:uppercase;color:var(--text-meta)}

.base-artboard{position:relative;isolation:isolate;overflow:clip;min-height:100svh;color:var(--base-ink);background:var(--base-field)}
.base-artboard__field,.base-artboard__object,.base-artboard__shadow,.base-artboard__glyphs,.base-artboard__cuts,.base-artboard__marks{position:absolute;inset:0;pointer-events:none}
.base-artboard__content{position:relative;z-index:var(--layer-carrier)}
/* Fixed-format sheet: the content layer fills the artboard so a space-between stack spreads. */
.base-artboard[data-fill]>.base-artboard__content{position:absolute;inset:0}
/* Site-reference ribbon motion. Visibility is mount/unmount, so these animations are
   purely additive: if a timeline never advances the ribbon simply appears and
   disappears without travelling. Enter animates transform only — an opacity from-state
   would hide the ribbon while the animation sits at 0%. */
@keyframes base-ribbon-in{from{transform:translateY(-14px)}to{transform:translateY(0)}}
@keyframes base-ribbon-out{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-10px)}}
.base-ribbon{animation:base-ribbon-in var(--motion-ribbon-in) var(--ease-base)}
.base-ribbon[data-closing]{animation:base-ribbon-out var(--motion-ribbon-out) var(--ease-out) forwards}
@media (prefers-reduced-motion:reduce){.base-ribbon,.base-ribbon[data-closing]{animation:none}}
/* Narrow specimen shell: drop the secondary aperture before starving the contained event. */
@container (max-width:300px){.base-specimen-shell>header{grid-template-columns:minmax(0,1fr)}.base-specimen-shell>header>div:nth-child(2){display:none}}
@container (max-width:220px){.base-specimen-shell>header>div:first-child{letter-spacing:.06em}}

.paragraph-stage{width:min(100% - 2 * var(--gutter),var(--stage-max));margin-inline:auto;display:grid;grid-template-columns:repeat(var(--grid-columns),minmax(0,1fr));column-gap:var(--grid-gap)}@media (max-width:1023px){.paragraph-stage{grid-template-columns:repeat(var(--grid-columns-tablet),minmax(0,1fr))}}
@media (max-width:639px){.paragraph-stage{grid-template-columns:repeat(var(--grid-columns-mobile),minmax(0,1fr))}}
/* Clause field is a secondary carrier. Below the 12-track stage the body path reaches the
   last track, so there is no keep-out band left — drop the carrier rather than starve the
   dominant operation (same rule as the narrow specimen shell above). */
.base-clause-field{position:absolute;left:var(--clause-keepout,58%);right:var(--gutter);top:0;bottom:0}
@media (max-width:1023px){.base-clause-field{display:none}}

/* Statement / Corpus: the reading lane and the projected scene are peers. The field keeps
   a stable optical scale instead of being squeezed into the leftover edge of the prose. */
.base-statement-layout{display:grid;grid-template-columns:minmax(320px,42%) minmax(0,1fr);gap:clamp(24px,4vw,72px);align-items:start;padding:0 var(--gutter)}
.base-statement-copy{grid-column:1;grid-row:1;position:relative;z-index:1;display:grid;gap:clamp(22px,4.5vh,46px);max-width:66ch}
.base-statement-corpus{grid-column:2;grid-row:1;position:sticky;top:24px;height:clamp(520px,calc(100svh - var(--shell-height) - 48px),820px);min-width:0;overflow:hidden;background:radial-gradient(ellipse at 62% 64%,rgba(88,97,109,.11),rgba(212,212,212,.035) 38%,transparent 68%);-webkit-mask-image:linear-gradient(to right,transparent 0,#000 12%,#000 100%);mask-image:linear-gradient(to right,transparent 0,#000 12%,#000 100%)}
@media (max-width:1023px){
  .base-statement-layout{grid-template-columns:1fr}
  .base-statement-corpus{grid-column:1;grid-row:1;position:relative;top:auto;height:clamp(360px,62svh,620px);margin-bottom:clamp(28px,6vh,64px);-webkit-mask-image:none;mask-image:none}
  .base-statement-copy{grid-column:1;grid-row:2}
}
@media (max-width:639px){
  .base-statement-layout{gap:0}
  .base-statement-corpus{height:56svh;min-height:340px}
}

/* Index sentence: calm material field, compact register, and one continuous grammatical
   gallery. Project-owned plates provide the image tokens; no global hero is used as décor. */
.base-soft-index-field{position:absolute;inset:0;background:radial-gradient(ellipse at 63% 42%,rgba(42,55,68,.06),transparent 43%),linear-gradient(102deg,rgba(235,233,226,.76),transparent 28%),var(--base-field)}
/* The index is a single full-width column. The plate is NOT a grid track — see .base-page-plate. */
.base-index-layout{display:block;padding:clamp(14px,2vw,30px) var(--gutter) clamp(34px,6vh,76px);min-height:calc(100svh - var(--shell-height));overflow:visible}
.base-index-main{position:relative;z-index:var(--layer-evidence);min-width:0}
/* A 66% READING LANE WAS TRIED HERE AND REVERTED (2026-08-18). The intent was sound — keep the reading
   off the material so no run straddles the light/dark boundary — but the index sentence needs its full
   measure: at 909px the toolbar broke into three rows and "determines SOUND" truncated to "del". The
   straddle has to be solved by where the material starts, or by the reading's own ground — never by
   starving the sentence. */
/* THE PLATE IS A BACKGROUND FIELD, not a panel (Pavel, 2026-08-18: "должна быть за всеми остальными
   объектами… и за шапкой тоже… от начала страницы до самого конца").

   It was a grid track taking 33% of the layout, and that is what clipped the index sentence. The
   1200px threshold tried at A-59 did not fix it, it only moved it: `IndexSentence` tokens are
   VIEWPORT-relative (`clamp(2.4rem,5vw,6rem)` on boundary brackets, `5vw` growing with the screen),
   so the sentence's required width grows at nearly the rate the column does and the column never
   catches up — measured clipping at every width from 1200px to ~2100px. A threshold would have to sit
   at 2100px, i.e. the plate would never appear on a real display.

   A background layer steals no measure, so the constraint disappears rather than being re-tuned, and
   the media query is deleted rather than raised. `fixed` positions against the viewport, so the plate
   spans the full page height behind the shell; it is a SIBLING of .base-artboard in the DOM because
   that element sets `overflow:clip`, which some engines apply to fixed descendants. */
.base-index-screen{position:relative;isolation:isolate}
/* THE READING OVER THE BACKDROP IS INK, NOT THE QUIET REGISTER — solved, not tuned.
   Measured on the live index: `--text-quiet` rgb(96,96,96) over the plate's dark band composited to
   157 and read 2.32:1; the 10px meta year read 2.86:1. §11 wants ≥4.5:1.

   Solve for the two unknowns and the answer is binary. Keeping quiet grey over near-black material
   needs a ≤ 0.139 (250 − 230a ≥ 218), which turns the plate into a pale wash. Keeping the plate at its
   0.48 bound needs the text at ≤ rgb(24,24,24) (composite 130 → L 0.2159 → L_text ≤ 0.0091). There is no
   third value: this is why the quiet and meta runs are ink where the backdrop is STRONG — `index` and
   `core` — and stay quiet at `quiet`, where 0.06 presence lets the existing grey clear 4.5:1 on its own.
   That boundary is the whole point of solving the 0.06: overriding the token there would collapse the
   label tier into the body tier and buy no contrast at all.

   Declared under a `[data-*]` SCOPE, not on the component selectors. The first version set these two
   properties inside `.base-index-screen` and `… .base-global-shell`, which declares theme tokens from a
   component rule — the compiler flags exactly that, and it is right to: a token override belongs to a
   scope anyone can read, not to one screen's class. `PageField` marks the stack and the shell with the
   attribute, and only in index mode, so the scope travels with the backdrop's STRENGTH rather than with
   a selector. */
[data-plate-backdrop]{--text-quiet:var(--base-ink);--text-meta:var(--base-ink)}
/* The measured flip. `IndexScreen` samples the composite under every run and marks the ones standing on
   near-black material; those become paper. It is one declaration because the decision is made from the
   pixels, not from a layout assumption — and it carries `!important` because the runs it lands on are
   coloured inline by their own components. */
[data-plate-ink=paper],[data-plate-ink=paper] *{color:var(--base-field)!important;-webkit-text-decoration-color:var(--base-field);text-decoration-color:var(--base-field)}
/* THE LAYER CONTRACT, declared once (Pavel, 2026-08-18: "должен быть background, потом наша
   композиция, потом все остальное, включая текст").

   The stack is the page's own stacking context and the measured box for the material: its height is the
   route's content height, so the composition covers the page rather than the viewport. `isolation` keeps
   the whole contract local — no screen can reach out of it, and nothing outside can land between the
   material and the reading.

   Every child that is not the plate is lifted explicitly. Screens whose root is `position:static` would
   otherwise paint BELOW a positioned backdrop whatever its z-index, and the reading would go under the
   material — a rule that depends on each screen remembering to be positioned is a rule that will break
   on the next screen someone adds. */
.base-page-stack{position:relative;isolation:isolate;min-height:100%}
.base-page-stack>*{position:relative;z-index:1}
.base-page-stack>.base-page-plate{z-index:0}
.base-page-plate{position:absolute;top:0;left:0;right:0;bottom:0;width:auto;z-index:var(--layer-field);pointer-events:none;overflow:hidden}
/* The composition must not begin or end at a hard edge — it should read as scattered behind the UI,
   not as a picture cropped by the viewport (Pavel, 2026-08-18). The mask dissolves it inward on the
   left and away at top and bottom.

   THE PEAK ALPHA IS A CONTRAST BOUND, NOT A TASTE SETTING. Do not raise it.
   Solve it, don't tune it: the plate's darkest material is near-black, the field under it is
   `--base-field` #fafafa, and the text over it is `--base-ink` rgb(10,10,10). §11 wants ≥4.5:1, so
   the composite must reach L* ≥ 0.1885, i.e. ≥122/255 — and 250 × (1 − a) ≥ 122 solves to a ≤ 0.51
   even over pure black. 0.48 is that bound with headroom for the `.base-soft-index-field` tint;
   measured 5.5:1 over the darkest slab.

   The first version ramped to 0.82 on the stated reasoning that "the plate reaches full presence only
   past the text". That was FALSE and shipped a 2.09:1 ATLAS MODE button: `.base-index-main` is
   full-width now, so its right-aligned footer controls end at viewportWidth − gutter — exactly the
   plate's most opaque zone. Nothing stops the content column before the plate's strong band, so there
   is no "past the text" to hide opacity in, and the bound has to hold everywhere the layer exists.
   THE VERTICAL RAMP IS MEASURED IN ABSOLUTE LENGTHS AGAINST `--shell-height`, not in percentages of
   the viewport. A percentage ramp (`transparent 0%, #000 13%`) put 62 of its 70px UNDER the opaque
   shell, so the plate first became visible at alpha 0.883 and made a 95-level step at y=62 — the
   abrupt start Pavel ruled out. The dissolve now starts inside the shell band and completes 84px
   BELOW it, so the part a reader can see is the whole ramp. It is also viewport-height independent,
   which a percentage ramp is not.

   The shell is passed `translucent` at the call site so the composition genuinely reads behind it
   (Pavel: "она должна быть и за шапкой тоже"). That is `GlobalShell`'s own affordance — no shared
   component was modified, and over a plain field it composites identically. */
.base-page-plate::after{content:"";position:absolute;inset:0;pointer-events:none}
/* EDGE TO EDGE, FULL PAGE LENGTH (Pavel, 2026-08-18: "она как задник лежит от края до края").
   The horizontal ramp is GONE, and its removal is a consequence of the composition, not a taste
   change: the plate now owns its own left ending (`reach` · `falloff`), so masking one in CSS would
   dissolve the same edge twice — once as material, once as a filter over it.

   THE 0.48 CAP IS THE SAME CONTRAST BOUND, now carried by `opacity` so it is uniform and readable
   instead of hiding in the last stop of a gradient. Derivation unchanged: darkest material near-black,
   field `--base-field` #fafafa, text `--base-ink` rgb(10,10,10), §11 wants ≥4.5:1, and
   250 × (1 − a) ≥ 122 solves to a ≤ 0.51. At 0.48 the composite measures ~130/255 → 5.0:1.
   DO NOT RAISE IT — the first full-bleed version rode at 0.82 and shipped a 2.09:1 button.

   The vertical dissolve stays in ABSOLUTE lengths against `--shell-height` (a percentage ramp put 62
   of its 70px under the opaque shell, so the plate first appeared at alpha 0.883 — the abrupt start
   Pavel ruled out), and it now dissolves at the FOOT of the page as well, so a long page ends the
   material rather than cutting it. */
.base-page-plate canvas{display:block;width:100%;height:100%;object-fit:none;object-position:left top;opacity:.5;
  -webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 240px),transparent 100%);
  mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 240px),transparent 100%)}
/* THE SHELL PAINTS NO WASH OVER THE MATERIAL. It was 76% opaque field colour at z-index 60, so the
   plates stopped at the header band; Pavel's instruction is that the plates run under the shell and
   only its TEXT sits above them. The shell keeps its geometry, its stickiness and its own component —
   nothing shared was modified; this is a call-site scope that applies only while the index is mounted,
   and its text is raised to ink for the same measured reason the reading was.
   The top of the vertical mask is gone with it: a dissolve existed only because 62px of ramp were being
   wasted under an opaque header, and there is no longer an opaque header to hide behind. */
/* THE SHELL STOPS BEING A BAND. Two separate reasons the first attempt left one standing, both
   measured: `GlobalShell` sets its own translucent background and `position:sticky` as INLINE styles
   (its `translucent` affordance), which no selector can outrank — hence `!important`, and the component
   itself is still untouched. And `main[data-scroll]` computes `overflow:hidden auto` and starts 62px
   down, so the plate could not reach above it however it was positioned: the band was the page showing
   through where the plate was not allowed to go. Taking the shell out of flow gives that 62px back to
   the scroller, so the material runs to y=0 and only the shell's TEXT stands over it (Pavel,
   2026-08-18: "шапка будет, но плитки сверху... над плитками только текст"). The reading keeps
   its distance from the shell by paying that height back as padding, and the toolbar sticks below it. */
/* THE SHELL OVERLAYS THE SCROLLER ON EVERY ROUTE, not just the index (Pavel, 2026-08-27: the header is
   still not letting anything through). Its former translucency had nothing to composite:
   `main[data-scroll]` was `calc(100svh - var(--shell-height))` and began BELOW the shell, so no pixel
   ever passed behind it — a 76% field over the page's own field is that field. The scroller now fills
   the viewport and the shell stands over it out of flow, so the reading, the plates and the material all
   travel behind the matte and its 6px blur. The current matte adds no field-colour wash: Photoshop
   measurements proved that the old 76% #fafafa mixture lifted a 244/243/242 page to 250/250/250.
   Off-index each routed screen pays the shell's height back inside its own root. The screen box,
   MaterialGround, Page Field and Optical Depth layers therefore all begin at y=0, while the root's
   normal-flow reading begins below the Shell. The index keeps its own arrangement, where the plates
   run to y=0 and only the shell's TEXT stands over them. */
:is(#portfolio-root,#screen-root) .base-global-shell{position:fixed!important;top:0;left:0;right:0}
:is(#portfolio-root,#screen-root):has([data-field=index]) .base-global-shell{background:transparent!important;border-bottom-color:transparent!important}
:is(#portfolio-root,#screen-root):has([data-field=index]) .base-index-layout{padding-top:calc(var(--shell-height) + clamp(14px,2vw,30px))}
:is(#portfolio-root,#screen-root):has([data-field=index]) .base-index-toolbar{background:transparent;top:var(--shell-height)}
/* Project directory hover is one local registered Optical Depth event, not a rebuild of the
   persistent Shell renderer. Each row owns an exact in-place graphical duplicate with a dark body
   and a wider feather. Only opacity changes: geometry, focus and filter remain stable, so pointer
   travel stays on the compositor and the semantic link/focus ring remains sharp above it. */
.base-shell-project-link{
  --shell-project-depth-body-opacity:0;
  --shell-project-depth-feather-opacity:0;
  display:block;
}
.base-shell-project-optical-depth{
  transform:none;
  transform-origin:center;
  mix-blend-mode:normal;
  transition:opacity var(--motion-hover) var(--ease-base);
}
.base-shell-project-optical-depth::before,
.base-shell-project-optical-depth::after{
  display:block;
  font-family:var(--font-register);
  font-size:var(--type-micro-size);
  font-weight:var(--type-micro-weight);
  letter-spacing:var(--type-micro-tracking);
  line-height:var(--type-micro-leading);
  text-transform:uppercase;
}
.base-shell-project-optical-depth::before{content:attr(data-shell-project-optical-title)}
.base-shell-project-optical-depth::after{content:attr(data-shell-project-optical-detail);text-align:right}
@media (max-width:639px){.base-shell-project-optical-depth::after{text-align:left}}
.base-shell-project-optical-depth--body{
  opacity:var(--shell-project-depth-body-opacity);
  filter:blur(clamp(3px,.55vmin,6px));
}
.base-shell-project-optical-depth--feather{
  opacity:var(--shell-project-depth-feather-opacity);
  filter:blur(clamp(8px,1.4vmin,15px));
}
@media (hover:hover){
  .base-shell-project-link:hover{
    --shell-project-depth-body-opacity:.68;
    --shell-project-depth-feather-opacity:.28;
  }
}
.base-shell-project-link:focus-visible{
  --shell-project-depth-body-opacity:.68;
  --shell-project-depth-feather-opacity:.28;
}
[data-psod-disabled=true] .base-shell-project-optical-depth{display:none}
/* PINNED, NOT FITTED. `object-fit:cover` CENTRES the bitmap in its box, so every change in page height
   — a row expanding under the pointer — moved the centring offset and the whole composition slid, with
   no redraw involved at all. That was the movement Pavel kept seeing after the redraw was already
   quantised. `none` + top-left pins the material to the start of the page: a taller page reveals more
   at the foot instead of shifting what is already there. The bitmap is drawn at the measured width and
   at a height snapped UP past the page's own, so there is nothing to stretch and nothing to letterbox.

   PRESENCE IS A MEASURED BOUND, AND IT DEPENDS ON WHETHER THERE IS ROOM. Measured at 909px: the reading
   reaches 0.976 of the width (REG HYBRID in the shell), so there is no text-free right margin for a black
   register to live in, and full strength put ink on 25-level ground — 1.14:1 against §11's 4.5. Ink needs
   a composite ≥122 over near-black material: 250 − 232a ≥ 122 solves to a ≤ 0.55, hence 0.50. Above
   1440px the sentence occupies little more than half the width and the black register has somewhere to
   be, so presence returns to 1 and the measured ink/paper flip carries the runs that reach into it.
   The compositional question — where the material starts relative to the reading — is Pavel's; this is
   the interim that never ships an illegible run. */
@media (min-width:1440px){[data-field=index] .base-page-plate canvas{opacity:1}}
/* PRESENCE IS PER DECLARED MODE, and `mode` is the only thing that changes — an attribute on the stack,
   never a prop of the plate, so switching route cannot redraw the material.

   `core` is Core-mode and Index-mode project pages, where the Core parent form is the page's own material.
   It takes the index's bound, 0.5, and the same measured ink/paper flip. It does NOT take the index's
   ≥1440px lift to full presence: that was justified by the index sentence occupying little more than half
   the width, and the project layout has not been measured for it. Raising it is one rule plus that
   measurement.

   `quiet` (statement, CV, essays, contact, atlas) is 0.06, and that number is solved, not chosen: it is the
   presence at which the EXISTING quiet register survives untouched. `--text-quiet` rgb(106,106,106) needs a
   composite of L* ≥ 0.824 to clear §11's 4.5:1, i.e. ≥235/255, and 250 × (1 − a) ≥ 235 gives a ≤ 0.06 even
   over pure black — measured 4.54:1. Above it those pages' meta and caption runs would have to become ink,
   which is a decision about their typography and Pavel's to make.

   `none` is Corpus and Specimen project pages. The canvas stays MOUNTED — that is the point of the
   persistent layer — and is simply never painted. */
[data-field=core] .base-page-plate canvas{opacity:.5}
[data-field=quiet] .base-page-plate canvas{opacity:.06}
[data-field=none] .base-page-plate canvas{opacity:0}
.base-page-plate canvas{object-fit:none;object-position:left top}
.base-index-toolbar{position:sticky;top:0;z-index:8;display:flex;justify-content:space-between;align-items:center;gap:var(--space-6);padding:0 0 var(--space-4);background:linear-gradient(var(--base-field) 72%,rgba(250,250,250,0));border-bottom:1px solid var(--line-rule)}
/* ONE ROW = ONE TARGET. The real anchor is the row grid itself. The earlier absolutely-positioned
   pseudo-element changed geometry during the hover expansion, so a fast first press could begin on one
   generated box and end on another; the browser then emitted no click. One persistent anchor now owns
   both the visible metadata and the expressive clause, while aria-label preserves the concise name. */
/* THE PROJECT PAGE'S TWO-COLUMN BLOCKS ARE CSS, NOT INLINE STYLE. They were inline on the elements,
   which put them beyond the reach of every media query in this file: at a 924px viewport the register
   column measured a pinned 220px against a 587px reading column, and on a phone that column — back link,
   year, research id, status pills and the CONTENTS nav — would have held its 220px while the reading
   shrank around it. An inline grid template is a layout decision that no breakpoint can revisit.

   The register column is the report's apparatus: sticky beside the reading on a desktop, and above it,
   unpinned and horizontally arranged, once there is no second column to be beside. */
/* THE OPENING IS A NAMED GRID, not a stack. Four things with four jobs: the identity column (title +
   instrument panel), the reading opposite it, the relation on its own band, and the first plate across the
   full width. Named areas so a breakpoint can reorder them without the markup moving — A-71. */
/* THE SOFT REGISTER'S OPTICAL DEPTH IS NOT BUILT, on purpose — see amendments A-72.
   An attempt using three `backdrop-filter` planes was reverted: on this route the field behind them
   measures a uniform 250,250,250 (the GeneratedPlate canvas is at opacity 0 on project routes), and a blur
   over a uniform field returns that field. The three radii were mathematically indistinguishable and what
   shipped was three flat translucent rectangles — the «coloured panel» this system rejects, and exactly the
   ΔL*-spread failure A-44 records. Depth needs real relief behind it; whether the composition extends
   behind project pages is Pavel's open scope decision. */
.base-project-record{display:grid;grid-template-columns:minmax(220px,26%) minmax(0,1fr);column-gap:clamp(20px,3vw,64px);row-gap:clamp(18px,3vh,40px);padding:var(--head-pad) var(--gutter) 0;align-items:start;grid-template-areas:"exit exit" "ident reading"}
/* THE CONTROL'S TEXT SITS ON THE HEAD LINE, not its 44px hit box (Pavel, 2026-08-27: raise the button,
   and standardise it everywhere). A `min-height:var(--hit-min)` control centres a ~15px label in a 44px
   box, so its text rendered ~15px below the head line while the reading's first heading started at it.
   `--exit-lift` is that inset, DERIVED from the two tokens that cause it — never a hand-tuned pixel —
   and the matching bottom margin gives the row its height back, so nothing below the exit moves. */
.base-project-exit{grid-area:exit;margin-top:calc(-1 * var(--exit-lift));margin-bottom:var(--exit-lift)}
.base-project-ident{grid-area:ident;display:grid;gap:clamp(16px,2.6vh,32px);align-content:start;min-width:0}
.base-project-identity{text-align:left}
.base-project-identity__eyebrow{display:block}
.base-project-identity__title{margin:0;font-family:var(--font-reading);font-variation-settings:'wdth' var(--width-reading)}
.base-project-identity__description{max-width:32ch;margin:0;font-family:var(--font-reading);font-size:clamp(15px,1.25vw,18px);font-weight:var(--type-body-weight);line-height:1.48;letter-spacing:var(--type-body-tracking);text-wrap:pretty}
.base-project-identity__primary{display:grid;gap:8px;padding-top:clamp(8px,1.5vh,18px)}
.base-project-identity__primary-value{font-family:var(--font-reading);font-size:clamp(31px,3vw,48px);font-weight:300;line-height:.92;letter-spacing:-.025em;font-variation-settings:'wdth' var(--width-reading)}
.base-project-identity__facts{max-width:300px}
.base-project-identity__fact{padding:12px 0 15px;border-top:1px solid var(--line-rule)}
.base-project-identity__fact[data-tone=quiet]{color:var(--text-quiet)}
.base-project-identity__label{margin:0 0 5px;color:inherit}
.base-project-identity__value{margin:0;font-family:var(--font-reading);font-size:var(--type-reading-note-size);font-weight:var(--type-reading-note-weight);line-height:var(--type-reading-note-leading);letter-spacing:var(--type-reading-note-tracking);color:var(--text-meta);text-transform:none;text-wrap:pretty;font-variation-settings:'wdth' var(--width-reading)}
.base-project-identity__value[data-project-identity-value=register]{font-family:var(--font-register);font-size:var(--type-register-quiet-size);font-weight:var(--type-register-quiet-weight);line-height:var(--type-register-quiet-leading);letter-spacing:var(--type-register-quiet-tracking)}
/* THE TITLE IS SIZED TO ITS COLUMN, not to the page. `.base-title` is the full-width heading size, and in
   a 262px identity column a single unbreakable word overflowed it and the description was drawn straight
   over the top — SUBVITALIS at the full size measures about 380px. A `max-width` in ch does not help: it
   caps the wrapping width, and one long word cannot wrap. So the size is capped instead, at a value where
   ten characters still fit the narrowest legal column, and `overflow-wrap` is the floor under it so no
   future title can ever overlap the reading again. */
.base-project-title{font-size:clamp(26px,3.4vw,46px);line-height:0.95;overflow-wrap:anywhere;max-width:none}
.base-project-reading{grid-area:reading}
/* The relation sits under the reading it anchors, at a distance THIS RULE sets. It used to have its own
   grid row inside the identity column's two-row span, which made its offset the leftover of the panel's
   height — 434px on one work, 667px on another, and neither number was a decision. */
.base-project-anchor{margin-top:clamp(24px,5vh,64px);padding-top:clamp(16px,3vh,32px);border-top:1px solid var(--line-hairline);max-width:34ch}
/* The panel: register label over reading value, one hairline per field. A fact is not automatically a
   register value merely because it lives in a technical passport: human-language values keep Archivo /
   Roboto Flex; only IDs, versions, states and actions opt into IBM Plex Mono. */
.base-project-panel{display:grid;grid-template-columns:minmax(0,1fr);margin:0}
.base-project-panel dt{color:var(--text-primary);padding-top:var(--space-4);border-top:1px solid var(--base-lab-02)}
.base-project-panel dd{margin:2px 0 var(--space-4);font-family:var(--font-reading);font-size:var(--type-reading-note-size);font-weight:var(--type-reading-note-weight);letter-spacing:var(--type-reading-note-tracking);line-height:var(--type-reading-note-leading);color:var(--text-meta);text-wrap:pretty}
.base-project-identity__facts dt{padding-top:0;border-top:0}
.base-project-identity__facts dd{margin:0}
.base-project-afterstate{display:grid;grid-template-columns:minmax(0,420px) 1fr;gap:clamp(24px,4vw,80px);align-items:start}
@media (max-width:719px){
  /* 719, not 1023. The two-column opening was unreachable at the width this site is actually reviewed at
     (~740px canvas): the whole point of the arrangement — title and panel at the left, the reading opposite
     — collapsed before it was ever seen. At 740px the first track resolves to its 220px floor and leaves
     the reading about 460px, which still reads. Below 720px there genuinely is no room for two columns.

     One column, and the OPENING STATEMENT still comes first: the identity column dissolves so the title
     stays above the reading while the panel drops below the first plate. A wall of register type between
     the title and the description is the arrangement Pavel rejected on the old register column. */
  .base-project-record{grid-template-columns:1fr;grid-template-areas:none;row-gap:clamp(16px,3vh,32px)}
  /* One column: the title has the whole measure again, so it takes the page's own heading size. */
  .base-project-title{font-size:var(--type-title-size);max-width:15ch}
  .base-project-ident{display:contents}
  .base-project-exit,.base-project-identity__eyebrow,.base-project-identity__title,.base-project-identity__description,.base-project-identity__primary,.base-project-panel,.base-project-reading{grid-area:auto}
  .base-project-exit{order:-6}
  .base-project-identity__eyebrow{order:-5}
  .base-project-identity__title{order:-4}
  .base-project-reading{order:-3}
  .base-project-identity__description{order:-2}
  .base-project-identity__primary{order:-1}
  .base-project-panel{order:2}
  .base-project-identity__facts{width:100%;max-width:none}
  .base-project-afterstate{grid-template-columns:1fr}
}
/* TEACHING's two zones. Not inline: A-71 — an inline `gridTemplateColumns` is a layout decision no
   media query can revisit, and the reading zone here must be allowed to take the whole width once
   there is no second column to sit beside. */
/* THE PROJECT PAGE'S CONTENTS RAIL. The page is a two-column shell — the whole reading, then a narrow
   right rail — so the rail's containing block is the PAGE and `position:sticky` can carry it across every
   section. Inside a section it would unstick at that section's end, which is what the old placement in
   the register column did. Same arrangement as the essay screen (Pavel, 2026-08-20).

   Under 900px the rail is removed rather than stacked: there is no room for a second column, and every
   place it could go in one column puts a table of contents above the title, which is the arrangement
   Pavel rejected. `SectionNav` already withholds itself under three sections. */
/* ONE WIDTH, DECLARED ONCE. The rail column, the collapsed panel and the row all have to be the same
   number; they were 56, 44 and 64, so the row overflowed the panel by 20px and gave the project page 20px
   of horizontal scroll. 72px is what the collapsed state actually needs: a 5px mark, its 16px of margin,
   and a three-letter abbreviation measuring 43px at 10px/.14em. */
/* ONE HEAD LINE FOR BOTH READING FAMILIES (Pavel, 2026-08-27). `--head-pad` is where the page's head
   starts — the exit control's top edge — and the reading line must START there, not 66px below it.
   `--rail-band` is ReadingRail's own clear band above its line (its `PCT_BAND`), so subtracting it from
   the sticky top puts the LINE, not the box, on the head line. Keep the two in step if that constant
   ever changes. */
/* EVERY ELEMENT IS ON ALPHA EXCEPT THE FIELD (Pavel, 2026-08-28). Written with `:where()`, so its
   specificity is ZERO: it changes the DEFAULT and loses to every intentional rule, including a
   component's own inline style. Without it a page accumulates opaque boxes by accident — a browser
   default here, a copied panel there — and each one deletes the optical depth behind it rather than
   merely covering it. `html` and `body` carry the field; replaced elements (images, video, canvas,
   iframes) keep their own painting; `[data-opaque]` is the named exception for something that must
   genuinely occlude. */
:where(*:not(html, body, img, video, canvas, iframe, [data-opaque])) { background-color: transparent }

/* THE MONUMENT STANDS UNDER THE INFORMATION BLOCK, IN THE SAME LEFT COLUMN (approved Core layout;
   Pavel, 2026-08-27: "она находится под блоком с информацией, а не сбоку от него"). A third grid track put
   it BESIDE that block and pushed the record right; before that, an absolute band over an unchanged grid
   crossed the panel's own text at full ink — ≈1:1 where a glyph stroke landed under MEDIUM or SHOWN, and
   invisible to a hit test because the band takes no pointer. Both gone. The band's top and width are
   MEASURED in `ProjectScreen` (the identity panel's bottom edge, the reading column's left edge), and the
   sections below the record take that same inset, so the left column below the panel is the monument's
   alone. */
.base-project-monument-band{position:absolute;left:0;bottom:0;overflow:hidden;pointer-events:none;z-index:0}
/* The monumental face and ITS OWN scale, not a hand-picked approximation of it: `--font-display` never
   existed in this system, so the declaration was dead and the run inherited Archivo — right by accident
   for a Latin title and wrong for a Cyrillic one, which the system routes to `--font-source`. */
.base-project-monument{font-family:var(--font-monumental);font-weight:var(--type-monumental-weight);line-height:var(--type-monumental-leading);letter-spacing:var(--type-monumental-tracking);text-transform:uppercase;color:var(--base-ink);white-space:nowrap}
.base-project-flow{position:relative;z-index:1;min-width:0}
/* The inset belongs to the sections that stand BESIDE the monument. Credits and the closing footer sit
   far below where the run ends, so insetting them only narrowed them into the middle of the page (Pavel,
   2026-08-27: "ты сдвинул кредиты в центр") — they keep the page's own gutter. Only padding-LEFT is
   overridden, so each section keeps the right gutter its own inline shorthand sets. */
.base-project-page[data-mode=core]>.base-project-flow>*:not(.base-project-record):not(.base-project-tail){padding-left:var(--monument-inset)!important}
.base-project-page{--rail-w:72px;--monument-inset:0px; /* @kind spacing */--head-pad:clamp(28px,3.4vw,56px);--rail-band:24px;--exit-lift:calc((var(--hit-min) - var(--type-meta-size) * 1.25) / 2);position:relative;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,var(--rail-w));align-items:start}
/* A STRETCHED, MEASURED BOX: ReadingRail draws its line across the height it is given and needs a real
   one, so the sticky box carries an explicit viewport-relative height instead of taking the height of
   its content. `top` clears the shell; the 24px band above the line holds the % readout. */
.base-project-rail{position:sticky;top:calc(var(--shell-height) + var(--head-pad) - var(--rail-band));align-self:start;height:calc(100svh - var(--shell-height) - var(--head-pad) - 16px);min-height:280px}
/* THE CONTENTS, COLLAPSED BY DEFAULT (Pavel, 2026-08-20: quieter, further right, a DOT per section and
   the abbreviation of the one you are in; hovering just returns the plain list it used to be — no panel,
   no background, no shadow, and no abbreviations once it is open).

   The open list is absolute and right-anchored, so it grows LEFTWARD over the reading and never reflows a
   line of text — the same no-reflow rule every gallery in this system keeps. */
.base-section-nav{position:relative;min-height:calc(var(--hit-min) * var(--entries, 4))}
.base-section-nav__panel{position:absolute;top:0;right:0;width:var(--rail-w);overflow:hidden;display:grid;gap:0;padding-top:var(--space-3);border-top:1px solid var(--line-hairline);transition:width var(--motion-hover) var(--ease-base)}
.base-section-nav[data-open=true] .base-section-nav__panel{width:clamp(150px,14vw,186px);border-top-color:var(--base-ink)}
.base-section-nav__head{margin-bottom:var(--space-3);opacity:0;transition:opacity var(--motion-hover) var(--ease-base)}
.base-section-nav[data-open=true] .base-section-nav__head{opacity:1}
.base-section-nav__list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:minmax(0,1fr);gap:0}
.base-section-nav__item{display:grid;grid-template-columns:0 auto minmax(0,1fr) 0;align-items:center;gap:0;width:100%;min-width:0;min-height:var(--hit-min);padding:0;border:0;background:transparent;cursor:pointer;font-family:var(--font-register);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--text-meta);outline-offset:var(--focus-offset);transition:grid-template-columns var(--motion-hover) var(--ease-base),color var(--motion-hover) var(--ease-base)}
.base-section-nav__item[data-on=true]{color:var(--base-ink);font-weight:600}
.base-section-nav__num{overflow:hidden;width:0;opacity:0;font-variant-numeric:tabular-nums;color:var(--text-quiet);transition:width var(--motion-hover) var(--ease-base),opacity var(--motion-hover) var(--ease-base)}
/* A DOT, not a square. Round, and it grows rather than changing colour for the section you are in — the
   state is carried by ink and size, never by hue (§11). */
.base-section-nav__mark{justify-self:center;width:5px;height:5px;margin:0 8px;border-radius:50%;background:var(--text-quiet);transition:background-color var(--motion-hover) var(--ease-base),transform var(--motion-hover) var(--ease-base)}
.base-section-nav__item[data-on=true] .base-section-nav__mark{background:var(--base-ink);transform:scale(1.5)}
.base-section-nav__short,.base-section-nav__full{overflow:hidden;min-width:0;text-align:left;white-space:nowrap}
/* The rule after the active label is the only mark of state in the open list, exactly as before. */
.base-section-nav__rule{width:0;height:1px;background:transparent;transition:width var(--motion-hover) var(--ease-base)}
.base-section-nav[data-open=true] .base-section-nav__item{grid-template-columns:2.2em auto minmax(0,auto) minmax(0,1fr)}
.base-section-nav[data-open=true] .base-section-nav__num{width:2.2em;opacity:1}
.base-section-nav[data-open=true] .base-section-nav__item[data-on=true] .base-section-nav__rule{width:100%;background:var(--base-ink);margin-left:.6em}
@media (max-width:899px){
  .base-project-page{grid-template-columns:1fr}
  .base-project-rail{display:none}
}
/* CREDITS. The registry's own role labels run long ("INTERACTION DESIGN, SYSTEM ARCHITECTURE & CREATIVE
   CODING"), so the label column is a bounded, wrapping track rather than `auto` — with `auto` one long
   label sets the column width for every row and drives the names off the measure. Not inline: A-71. */
.base-project-credits{display:grid;grid-template-columns:minmax(0,26ch) minmax(0,1fr);column-gap:var(--space-7);row-gap:var(--space-4);margin:0;max-width:78ch}
.base-project-credits dt{line-height:1.5}
.base-project-credits-section__label{margin-bottom:var(--space-5)}
.base-project-credits__term{color:var(--text-primary)}
.base-project-credits__value{margin:0;text-transform:none;letter-spacing:.05em}
@media (max-width:719px){
  .base-project-credits{grid-template-columns:1fr;row-gap:var(--space-2)}
  .base-project-credits dd{margin-bottom:var(--space-4)}
}
.base-teaching-body{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,240px);gap:clamp(24px,5vw,90px);align-items:start;margin-top:clamp(28px,6vh,74px)}
.base-record-header{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--space-8);align-items:end;border-bottom:1px solid var(--base-ink);padding-bottom:var(--space-6)}
.base-record-registers{display:grid;grid-template-columns:repeat(2,minmax(0,auto));gap:var(--space-3) var(--space-8)}
.base-record-registers--three{grid-template-columns:repeat(3,minmax(0,auto))}
.base-contact-body{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);gap:clamp(28px,6vw,110px);align-items:start;margin-top:clamp(30px,6vh,74px)}
.base-contact-direct{overflow-wrap:anywhere}
.base-cv-table-scroll{width:100%;max-width:100%;margin-top:clamp(28px,6vh,72px);overflow-x:auto;overscroll-behavior-inline:contain;scrollbar-gutter:stable}
.base-cv-table{min-width:680px}
.base-teaching-row{display:grid;grid-template-columns:auto minmax(0,1fr) minmax(0,232px);gap:clamp(14px,2.4vw,40px);align-items:start}
.base-teaching-meta{display:grid;gap:var(--space-3);align-content:start;justify-items:start;position:sticky;top:96px}
.base-teaching-areas{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 clamp(18px,3vw,48px);max-width:68ch}
@media (max-width:899px){
  .base-teaching-body{grid-template-columns:1fr;gap:clamp(20px,4vh,40px)}
  .base-teaching-row{grid-template-columns:auto minmax(0,1fr);gap:var(--space-4) var(--space-5)}
  /* The qualifier column drops under the reading instead of being squeezed into a third track, and
     unpins: a sticky block with nothing beside it just eats the top of every scroll. */
  .base-teaching-row > .base-teaching-meta{grid-column:2;position:static;grid-auto-flow:row}
  .base-teaching-areas{grid-template-columns:1fr}
}
@media (max-width:639px){
  .base-record-header{grid-template-columns:minmax(0,1fr);gap:var(--space-5)}
  .base-record-registers,.base-record-registers--three{grid-template-columns:repeat(auto-fit,minmax(86px,1fr));gap:var(--space-3) var(--space-5)}
  .base-contact-body{grid-template-columns:minmax(0,1fr);gap:clamp(34px,8vh,58px)}
}
/* ADDRESSED ATLAS. Desktop keeps the authored coordinate field. Tablet separates that field from its
   record instead of compressing both, and phone turns the same nodes into a selectable semantic flow;
   the data, coordinates and selection remain, while only the decorative route drawing recomposes. */
.base-atlas-screen{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,26%);min-height:calc(100svh - var(--shell-height))}
.base-atlas-field{position:relative;min-width:0;padding:clamp(20px,3vh,40px) 0 clamp(20px,3vh,40px) var(--gutter);border-inline-end:1px solid var(--line-hairline)}
.base-atlas-map{position:relative;height:clamp(520px,64vh,700px);margin-top:var(--space-6);margin-inline-end:90px}
.base-atlas-node{position:absolute;width:138px;min-width:0;text-align:left;display:grid;gap:6px;padding:0}
.base-atlas-node-visual{display:block;width:138px;max-width:100%;box-sizing:border-box;padding:3px}
.base-atlas-node-label{width:138px;max-width:100%;white-space:normal;line-height:1.4;font-size:9px}
.base-atlas-latitude-ticks{position:absolute;right:-70px;top:0;bottom:0;width:60px;border-inline-start:1px solid var(--line-hairline);display:flex;flex-direction:column;justify-content:space-between;padding-inline-start:8px}
.base-atlas-latitude-label{position:absolute;right:-96px;top:46%}
.base-atlas-longitude-ticks{margin-top:var(--space-6);margin-inline-end:90px;display:flex;justify-content:space-between;border-top:1px solid var(--line-hairline);padding-top:var(--space-3)}
.base-atlas-longitude-label{text-align:center;margin-top:6px;margin-inline-end:90px}
.base-atlas-record{min-width:0;padding:clamp(20px,3vh,40px) var(--gutter) clamp(20px,3vh,40px) clamp(16px,2vw,32px);display:grid;gap:var(--space-6);align-content:start}
@media (max-width:899px){
  .base-atlas-screen{grid-template-columns:minmax(0,1fr)}
  .base-atlas-field{padding-right:var(--gutter);border-inline-end:0;border-bottom:1px solid var(--line-hairline)}
  .base-atlas-record{padding-left:var(--gutter)}
}
@media (max-width:639px){
  .base-atlas-map{height:auto;margin-inline-end:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(18px,6vw,30px) var(--space-5)}
  .base-atlas-map>svg{display:none}
  .base-atlas-node{position:relative;left:auto!important;top:auto!important;width:100%;align-content:start}
  .base-atlas-node-visual,.base-atlas-node-label{width:100%}
  .base-atlas-node .base-micro{overflow-wrap:anywhere}
  .base-atlas-latitude-ticks{position:relative;right:auto;top:auto;bottom:auto;width:auto;grid-column:1/-1;border-inline-start:0;border-top:1px solid var(--line-hairline);padding:var(--space-3) 0 0;display:flex;flex-direction:row}
  .base-atlas-latitude-label{position:relative;right:auto;top:auto;grid-column:1/-1;writing-mode:horizontal-tb!important;transform:none!important}
  .base-atlas-longitude-ticks{margin-inline-end:0}
  .base-atlas-longitude-label{margin-inline-end:0}
}
@media (max-width:359px){.base-atlas-map{grid-template-columns:minmax(0,1fr)}}
.base-index-footer{display:flex;gap:var(--space-6);align-items:baseline;flex-wrap:wrap}
.base-index-sentence-stage{padding:var(--space-4) 0 0;overflow:visible}
/* TEXT HAS A MEASURE; MEDIA HAS THE TRACK. Both were capped at `34ch` so their right edges would align,
   and the result was a cover sized by a text rule: it began left of the nav's first word and ended under
   CONTACT — two arbitrary lines, aligned to nothing in the shell (Pavel, 2026-08-23: «фото сейчас меньше
   чем нужно… нужно вписать заглавное изображение в общий дизайн код сайта»).

   So the cap moves off the column and onto the runs that need it. The cover fills the READING TRACK, and
   its right edge is the line every chapter figure and every plate caption already sits on — measured 800px
   at a 924px viewport, all three identical. It is NOT the shell's own gutter (887px): the sticky CONTENTS
   rail occupies that 87px band in a sibling grid column, so the track cannot reach it without running
   under the rail. The paragraph and the relation keep 34ch at the reading size, so the text measure is
   unchanged — 390px, right edge 674. */
.base-project-reading{font-size:clamp(20px,2.1vw,27px)}
.base-project-reading>p{max-width:34ch}
.base-project-hero{position:relative;margin:clamp(24px,4vh,56px) 0 0}
/* THE COVER IS LANDSCAPE, AND 7:6 IS NOT. Pavel asked for «горизонтально ориентированные прямоугольники
   примерно 7 на 6» — but 7÷6 = 1.17, which at this measure is 460×394px and reads as a square; he called it
   square twice, and he was right about what he was seeing. The number was the miss, not the intent.

   3:2 because it is the aspect of most of the material: measured, ifrod / the-becoming / storm-cloud /
   labyrinth are 1.500 (zero crop), place-in-clouds 1.667 and laser-grass 1.333 (11.1% each). Stated on the
   WORST sample, not the majority: subvitalis-01 is natively PORTRAIT 1872×2736 and would lose 54.4% here,
   which is why the cover PLATE is chosen for orientation in ProjectScreen rather than taken from position
   1 — the box's aspect cannot rescue a portrait source. Per-work override on `--cover-aspect`. §16's
   inventory governs artboards, not a documentation crop. */
.base-project-hero__media{position:relative;aspect-ratio:var(--cover-aspect,3 / 2);overflow:hidden;background:var(--base-lab-02)}
/* The image is reached THROUGH the open-in-viewer button, not as a direct child. A `>` here silently
   stopped matching the moment the plate became clickable, and the hero lost its `object-fit:cover`
   entirely — the photograph rendered at 655×437 in a 260px box, 40% of it clipped, top-anchored instead
   of the authored centred crop. The button needs its own resolved box for a 100%/100% child to fill. */
.base-project-hero__media>button{display:block;width:100%;height:100%;padding:0;border:0;background:none}
.base-project-hero__media img{width:100%;height:100%;object-fit:cover;display:block}
.base-project-hero__media>div{mix-blend-mode:normal;filter:contrast(1.03)}
.base-analytical-select{display:grid;grid-template-columns:auto minmax(130px,auto);align-items:center;gap:var(--space-3)}
.base-analytical-select select{min-height:var(--hit-min);border:0;border-bottom:1px solid var(--base-ink);border-radius:0;background:transparent;color:var(--text-primary);font:600 var(--type-micro-size)/1 var(--font-register);letter-spacing:.12em;padding:0 24px 0 4px}
@media (max-width:639px){
  /* On a phone the plate lies under the whole reading, so it drops to a third of its presence — present
     as material, never as a background image behind body text. Same bound, lower: 0.34. */
  [data-field=index] .base-page-plate canvas{opacity:.34}
  [data-field=core] .base-page-plate canvas{opacity:.34}
  [data-field=quiet] .base-page-plate canvas{opacity:.06}
  [data-field=none] .base-page-plate canvas{opacity:0}
  /* One vertical dissolve only, at the foot — the left ending belongs to the composition now. */
  .base-page-plate canvas{-webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 160px),transparent 100%);mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 160px),transparent 100%)}
  .base-index-toolbar{position:relative;align-items:flex-start;flex-direction:column;background:transparent}
  .base-index-sentence-stage{padding-top:var(--space-6)}
  .base-project-hero__media{aspect-ratio:var(--cover-aspect,4 / 3)}
}

.base-rule{border:0;border-top:var(--rule-weight) solid var(--line-hairline);width:100%}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* Essay / Corpus reading page: a narrow author rail, one stable reading column (§0.6.1), and the
   reading line. Three peers, not a sidebar squeezing a column. NO RIGHT PADDING: the line stands at
   the page's own right edge, exactly as it does on a project page (Pavel, 2026-08-27) — the gutter
   there belongs to the sections, not to the page, so the rail column is flush with the viewport. */
.base-essay-layout{--rail-w:72px;--head-pad:clamp(28px,3.4vw,56px);--rail-band:24px;--exit-lift:calc((var(--hit-min) - var(--type-meta-size) * 1.25) / 2);display:grid;grid-template-columns:200px minmax(0,1fr) var(--rail-w);gap:clamp(28px,4.5vw,72px);align-items:start;padding:0 0 0 var(--gutter)}
/* ONE ROW, AND THE HEAD PADDING BELONGS TO THE COLUMNS THAT READ, not to the page. Two attempts failed
   here and both are worth naming. A separate exit ROW plus a reading column spanning both rows made the
   grid distribute the reading's 32,700px across BOTH tracks: row 1 inflated to 16,241px and the author
   column, sitting in row 2, began 16,405px down the document — off-screen for half the essay. And page
   padding-top gave the rail column a 43.5px static offset, above its own 19.5px sticky threshold, so at
   rest it did not pin and the line sat 24px below the project page's. With the padding on the two
   reading columns only, the rail column starts at 0 exactly as the project rail does (a direct child of
   an unpadded page grid) and is pinned from the first frame; the exit is an ordinary first child of the
   author column, so 01 / PREFACE and ALL WRITING share the head line without a second track. */
.base-essay-exit{margin-top:calc(-1 * var(--exit-lift));margin-bottom:calc(clamp(14px,2.4vh,28px) + var(--exit-lift))}
/* The rail column still spans the reading row, but the identity itself is deliberately ordinary
   document content: it establishes the essay at the top and leaves as the reader moves on. The
   section map at the opposite edge remains the page's persistent navigation. */
.base-essay-rail{grid-column:1;align-self:stretch;padding-top:var(--head-pad)}
.base-essay-rail__inner{position:relative;top:auto;display:grid;gap:clamp(28px,5vh,56px)}
.base-essay-copy{grid-column:2;min-width:0;max-width:66ch;padding-top:var(--head-pad)}
.base-essay-copy{container-type:inline-size}
.base-essay-copy>section::after{content:"";display:block;clear:both}
.base-essay-interactive-paragraph{
  position:relative;
  /* The essay's page-section target resolves to a compact body plus a wide feather. Interactive
     paragraphs opt out of the page-wide rebuild while their glyphs move, so they reproduce that
     resolved visible mass locally instead of falling back to a single weak blur. */
  --essay-interactive-shadow-x:2px;
  --essay-interactive-shadow-y:2px;
  --essay-interactive-shadow-body-blur:4px;
  --essay-interactive-shadow-body-opacity:.45;
  --essay-interactive-shadow-feather-blur:20px;
  --essay-interactive-shadow-feather-opacity:.11;
}
/* One local two-pass shadow for the composed text field, not a second Optical Depth build for every
   moving glyph. It lives on the actually painted source/canvas rather than their parent:
   filter/transform on an ancestor would establish a containing block and break the viewport
   coordinate contract used by escaping serpents and vortex. */
.base-essay-interactive-paragraph :is([data-source-paragraph],.base-charged-paragraph__source,.base-charged-paragraph__canvas,.base-text-current-paragraph__source,.base-text-current-paragraph__canvas){
  filter:
    drop-shadow(var(--essay-interactive-shadow-x) var(--essay-interactive-shadow-y) var(--essay-interactive-shadow-body-blur) rgb(0 0 0 / var(--essay-interactive-shadow-body-opacity)))
    drop-shadow(var(--essay-interactive-shadow-x) var(--essay-interactive-shadow-y) var(--essay-interactive-shadow-feather-blur) rgb(0 0 0 / var(--essay-interactive-shadow-feather-opacity)));
}
/* Insets own a coarse two-pass mass rather than the page's readable glyph echo. Both passes are still
   derived from the real alpha group, but their deliberately broad blur merges letters into the overall
   paragraph silhouette. The authoring page writes these same custom properties onto its live specimen. */
[data-essay-inset-shadow="group"]{
  --essay-inset-shadow-x:0px;
  --essay-inset-shadow-body-y:10px;
  --essay-inset-shadow-body-blur:26px;
  --essay-inset-shadow-body-opacity:.18;
  --essay-inset-shadow-feather-y:18px;
  --essay-inset-shadow-feather-blur:52px;
  --essay-inset-shadow-feather-opacity:.09;
}
[data-essay-inset-shadow="group"] :is([data-essay-inset-shadow-source],.base-charged-paragraph__source,.base-charged-paragraph__canvas,.base-text-current-paragraph__source,.base-text-current-paragraph__canvas){
  filter:
    drop-shadow(var(--essay-inset-shadow-x) var(--essay-inset-shadow-body-y) var(--essay-inset-shadow-body-blur) rgb(0 0 0 / var(--essay-inset-shadow-body-opacity)))
    drop-shadow(var(--essay-inset-shadow-x) var(--essay-inset-shadow-feather-y) var(--essay-inset-shadow-feather-blur) rgb(0 0 0 / var(--essay-inset-shadow-feather-opacity)));
}
/* Pull quotes are genuine insets: the following prose occupies the remaining line measure and wraps
   past them. Unequal authored alignment is preserved while a section clearfix prevents one quote
   from leaking into the next chapter. */
.base-essay-pull-quote{width:min(44%,28ch);margin-block:clamp(22px,3.2vh,38px);margin-inline:0}
.base-essay-pull-quote--right{float:right;margin-left:clamp(20px,3vw,38px)}
.base-essay-pull-quote--left{float:left;margin-right:clamp(20px,3vw,38px)}
@media (max-width:639px){
  .base-essay-pull-quote{float:none;width:min(92%,46ch);margin-inline:0}
}
.base-charged-paragraph__source,.base-charged-paragraph__canvas,
.base-text-current-paragraph__source,.base-text-current-paragraph__canvas{transition:opacity 120ms linear}
[data-charged-presentation="paragraph"] .base-charged-paragraph__canvas{opacity:0}
[data-charged-presentation="paragraph"][data-rest-painter="canvas"][data-canvas-ready="true"] .base-charged-paragraph__canvas,
[data-charged-presentation="paragraph"][data-afterstate]:not([data-afterstate="rest"]) .base-charged-paragraph__canvas{opacity:1}
[data-charged-presentation="paragraph"][data-rest-painter="canvas"][data-canvas-ready="true"] .base-charged-paragraph__source,
[data-charged-presentation="paragraph"][data-afterstate]:not([data-afterstate="rest"]) .base-charged-paragraph__source{opacity:0}
[data-text-current-presentation="paragraph"] .base-text-current-paragraph__canvas{opacity:0}
[data-text-current-presentation="paragraph"][data-text-current="pretext"] .base-text-current-paragraph__canvas,
[data-text-current-presentation="paragraph"][data-text-current="fallback"] .base-text-current-paragraph__canvas{opacity:1}
[data-text-current-presentation="paragraph"][data-text-current="pretext"] .base-text-current-paragraph__source,
[data-text-current-presentation="paragraph"][data-text-current="fallback"] .base-text-current-paragraph__source{
  position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;
  overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important
}
@media (prefers-reduced-motion:reduce){
  [data-charged-presentation="paragraph"] .base-charged-paragraph__canvas,
  [data-text-current-presentation="paragraph"] .base-text-current-paragraph__canvas{display:none}
  [data-charged-presentation="paragraph"] .base-charged-paragraph__source,
  [data-text-current-presentation="paragraph"] .base-text-current-paragraph__source{
    position:relative!important;width:auto!important;height:auto!important;padding:0!important;margin:0!important;
    overflow:visible!important;clip:auto!important;white-space:normal!important;border:0!important;opacity:1
  }
}
.base-essay-rail-col{grid-column:3;align-self:stretch}
/* Just the box: ReadingRail (components/shell) owns the line, the dots and their states. Same two
   figures as `.base-project-rail`, and now they are the same figures for a reason — both resolve to the
   page's head line. */
.base-essay-map{position:sticky;top:calc(var(--shell-height) + var(--head-pad) - var(--rail-band));align-self:start;height:calc(100svh - var(--shell-height) - var(--head-pad) - 16px);min-height:280px}
@media (max-width:1180px){
  .base-essay-layout{grid-template-columns:clamp(150px,18vw,180px) minmax(0,1fr) var(--rail-w);gap:clamp(20px,3vw,36px)}
}
/* Match the project-page contract: the edge rail remains available throughout compact desktop and
   only yields when the reading itself becomes a single mobile lane. */
@media (max-width:899px){
  .base-essay-layout{grid-template-columns:1fr;gap:clamp(24px,5vh,48px);padding-right:var(--gutter)}
  .base-essay-rail-col{display:none}
  /* One lane: the author column already carries the head padding, so the reading must not repeat it. */
  .base-essay-rail,.base-essay-copy{grid-column:1}
  .base-essay-copy{padding-top:0}
  .base-essay-rail__inner{position:relative;top:auto}
}

/* Chapter sequence: one text block beside the material it comments on (design.md §3736,
   "quiet text alternates with one causal event"). Below the tablet breakpoint it becomes one
   stable lane with unequal insets and unequal plate widths — §4093, §4222. */
.base-chapters{width:min(100%,var(--stage-max));display:grid;grid-template-columns:repeat(var(--grid-columns),minmax(0,1fr));column-gap:var(--grid-gap);row-gap:clamp(30px,6vh,80px);align-items:start}
.base-chapters figure{margin:0}
.base-chapters figure img{display:block;width:100%;height:auto}
@media (max-width:1023px){
.base-chapters{grid-template-columns:1fr}
.base-chapters>*{grid-column:1!important;grid-row:auto!important;margin-top:0!important}
.base-chapters>p{max-width:62ch}
.base-chapters>p:nth-of-type(even){margin-inline-start:5%}
.base-chapters>figure:nth-of-type(2){width:72%}
.base-chapters>figure:nth-of-type(3){width:88%;margin-inline-start:7%}
}
