/* ═══════════════════════════════════════════════════════════════════════════
   hetoimos — app.css
   Hand-written. Zero external dependencies, no framework, no build step.

   Sources, so the next agent does not re-derive:
     type / space / motion tokens ........ research/02-visual-language.md §3
     techniques V1–V16 ................... research/02-visual-language.md §1
     live-embed geometry + chrome ........ research/04-works.md §3, §7
     price / offer ....................... research/05-offer.md §1
     the procedural field (15 variants) .. site/bg.css  — DO NOT duplicate here
     laws L1–L7 .......................... docs/AUTOLOOP/CONTRACT_V2.md §6

   THE LAWS, and where each one is enforced in this file:
     L1  no animated factual values — there is no @keyframes anywhere that
         touches text content; only opacity / transform / clip-path.
     L2  text legible at first paint — every entrance is a MASK (clip-path)
         or a transform. Nothing on this page animates opacity from 0 on a
         text node for longer than 240 ms, and nothing above the fold is
         gated on an IntersectionObserver. §11.
     L3  the page always scrolls — no `position:sticky` height hack, no
         pinned full-height section, no scroll capture outside an iframe the
         visitor deliberately activated. §7.
     L4  prefers-reduced-motion — §11.4 kills every entrance and every
         ambient motion, and the authored state IS the final state, so the
         page is complete and correct with all motion removed.
     L5  the loader is capped — §3, plus a hard timeout in app.js.
     L6  ?still=1 — html.still, §11.4.
     L7  the procedural background always exists — owned by bg.css. This file
         must never set `display:none` on `.bg` or any `.bg > span`.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ─── 1 · FONTS ────────────────────────────────────────────────────────────
   Self-hosted Manrope, four subsets. `latin-ext` is NOT optional: Polish
   needs ą ć ę ł ń ś ź ż (U+0100–017F) and without it seven of the nine
   diacritic pairs fall back mid-word to a system font at a different weight
   and a different vertical metric. That is worse than tofu, because it reads
   as sloppiness rather than as a bug. (M3 §7.2.)
   There is deliberately NO Greek subset: every Greek glyph on this page —
   the wordmark and the fifteen background glyphs — is a drawn <path>. */
@font-face{font-family:Manrope;font-style:normal;font-weight:200 800;font-display:swap;
  src:url(/assets/fonts/manrope-latin.woff2) format('woff2');
  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+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:Manrope;font-style:normal;font-weight:200 800;font-display:swap;
  src:url(/assets/fonts/manrope-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:Manrope;font-style:normal;font-weight:200 800;font-display:swap;
  src:url(/assets/fonts/manrope-cyrillic.woff2) format('woff2');
  unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:Manrope;font-style:normal;font-weight:200 800;font-display:swap;
  src:url(/assets/fonts/manrope-cyrillic-ext.woff2) format('woff2');
  unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}

/* V3 — the fallback metric override. `font-display:swap` guarantees text is
   never invisible (L2) and guarantees a reflow when Manrope lands. These four
   numbers are MEASURED (TextMetrics at 100 px, real Chrome, 2026-07-29):
   Manrope ascent 107 / descent 30 / line box 137 %, and the advance width of
   a real headline is 1006.17 px against Segoe UI's 1007.62 — a 99.86 % match.
   This face downloads nothing; it only re-shapes the local fallback so the
   swap moves no pixels. */
@font-face{font-family:"Manrope Fallback";
  src:local("Segoe UI"),local("Arial"),local("Helvetica Neue"),local("Liberation Sans");
  ascent-override:107%;descent-override:30%;line-gap-override:0%;size-adjust:99.9%}


/* ─── 2 · TOKENS ─────────────────────────────────────────────────────────── */
:root{
  /* colour. `--accent`, `--accent-hi`, `--accent-dk`, `--hair*`, `--paper-2`,
     `--ink-3`, `--ink-4` and `--on-dark-3` are OVERRIDDEN per background
     variant by bg.css at `:root[data-bg]` (0,1,1) — which outranks this block
     whatever the source order. The values here are the correct standalone
     defaults, so the page is right even if bg.css never loads. */
  --paper:#F4F2ED; --paper-2:#EFECE5; --surface:#FFFFFF;
  --ink:#14171C; --ink-2:#2B3138;
  --ink-3:#4A5158;          /* G1 — retired from #5D656E, which was 3.94:1 on Θ */
  --ink-4:#6A727B;          /* G2 — hardened; NOT a text colour, decorative only */
  --dark:#101418; --dark-2:#171C22; --dark-3:#0B0E12;
  --on-dark:#EDEAE3; --on-dark-2:rgba(237,234,227,.74); --on-dark-3:rgba(237,234,227,.62);
  --accent:#1C4E4A; --accent-hi:#123734; --accent-dk:#93C7BE;

  /* type — V1: tracking is a function of size, and the curve is the rule new
     sizes are derived from, so a seventh value is never invented. */
  --fs-display:clamp(38px,6.2vw,74px);
  --fs-h2:clamp(28px,3.4vw,42px);
  --fs-h3:clamp(19px,1.5vw,23px);
  --fs-lead:clamp(17px,1.3vw,20px);
  --fs-body:clamp(16px,1.1vw,17px);
  --fs-small:15px;
  --fs-legal:13px;          /* footer legal links ONLY */
  --lh-display:1.04;        /* Ukrainian floor is 1.00 — й ї Й Ї above the cap
                               height, р у ф д ц щ below the baseline */
  --lh-h2:1.10; --lh-h3:1.28; --lh-lead:1.55; --lh-body:1.62; --lh-small:1.55;
  --ls-display:-.034em; --ls-h2:-.024em; --ls-h3:-.012em;
  --ls-lead:-.004em; --ls-body:0em; --ls-small:.006em; --ls-legal:.012em;
  --fw-display:400; --fw-h2:400; --fw-strong:500; --fw-emph:600;  /* nothing below 400 */
  --measure-display:20ch; --measure-lead:46ch; --measure-body:62ch;

  /* space — D8 is bought HERE and in element count, never by deleting the
     background (L7). */
  --s-1:4px;--s-2:8px;--s-3:12px;--s-4:16px;--s-5:24px;--s-6:32px;--s-7:40px;
  --s-8:48px;--s-9:64px;--s-10:80px;--s-11:96px;--s-12:128px;
  --section-y:clamp(104px,14.5vh,180px);
  --section-y-tight:clamp(64px,9.5vh,112px);
  --gutter:clamp(20px,5vw,64px);   /* 64:1180 ≈ 1:18 at 1440 */
  --container:1180px;
  --nav-h:60px;

  /* edges — V10 (the lit edge) and V11 (two-layer tinted shadows) */
  --r-xs:4px; --r-sm:8px; --r-md:12px; --r-pill:999px;
  --r-media:0;              /* load-bearing: a live embed has SQUARE corners */
  --hair:#E3E0D8; --hair-strong:#D4D0C5; --hair-w:1px;
  --hair-lit:rgba(255,255,255,.72);
  --hair-dark:rgba(237,234,227,.11); --hair-dark-2:rgba(237,234,227,.20);
  --hair-dark-lit:rgba(255,255,255,.06);
  --shadow-1:0 1px 2px rgba(20,23,28,.04),0 4px 12px -4px rgba(40,45,60,.08);
  --shadow-2:0 2px 6px -2px rgba(20,23,28,.06),0 8px 24px -8px rgba(40,45,60,.14);
  --shadow-3:0 12px 24px -16px rgba(20,23,28,.12),0 30px 60px -24px rgba(40,45,60,.20);

  /* motion — V7's assignment table, resolved once so no control re-derives it */
  --ease-out-quart:cubic-bezier(.165,.84,.44,1);
  --ease-out-circ:cubic-bezier(.075,.82,.165,1);
  --ease-out-expo:cubic-bezier(.19,1,.22,1);
  --ease-inout:cubic-bezier(.76,0,.24,1);
  --t-instant:120ms; --t-ui:170ms; --t-ui-slow:220ms; --t-reveal:620ms;
  --t-switch:130ms;   /* works tabs — interface tier, clicked repeatedly */
  --t-embed:260ms;    /* skeleton → live substitution, symmetric easing */
  --t-tip:160ms;
  --t-stagger:70ms;   /* total stagger span capped at 250 ms regardless of count */
  --enter:480ms;      /* the hero mask reveal; see §3 for the budget arithmetic */
  --enter-delay:360ms;
}
@media (min-resolution:2dppx){ :root{ --hair-w:.5px } }
/* Second and later navigations in one session skip the loader entirely, so the
   entrance must not re-spend its budget. */
html.no-loader{ --enter-delay:0ms; --enter:300ms }


/* ─── 3 · RESET AND 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} }
html,body{margin:0;padding:0}
/* overflow-x:clip (not hidden) — `hidden` on html would make `position:sticky`
   stop working on the nav. `clip` does not create a scroll container. */
html,body{overflow-x:clip}
body{background:var(--paper);color:var(--ink-2);
  font-family:Manrope,"Manrope Fallback","Segoe UI",-apple-system,Arial,sans-serif;
  font-weight:400;font-size:var(--fs-body);line-height:var(--lh-body);
  letter-spacing:var(--ls-body);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  font-variant-numeric:proportional-nums}
h1,h2,h3,h4,p,figure,dl,dd,blockquote{margin:0}
ul,ol{margin:0;padding:0;list-style:none}
img,svg,iframe{display:block;max-width:100%}
button,input,textarea,select{font:inherit;color:inherit;letter-spacing:inherit}
a{color:var(--accent);text-decoration:none;
  transition:color var(--t-instant) var(--ease-out-quart)}
a:hover{color:var(--accent-hi)}
::selection{background:#D8E4E0;color:var(--ink)}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}
.dark :focus-visible,.dark:focus-visible{outline-color:var(--accent-dk)}

.wrap{width:100%;max-width:var(--container);margin:0 auto;padding:0 var(--gutter)}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.sprite{position:absolute;width:0;height:0;overflow:hidden}
section{position:relative;scroll-margin-top:calc(var(--nav-h) + 14px)}
section>.wrap,section>.wstage{position:relative;z-index:1}

/* the display / heading scale */
h1,.h1{font-size:var(--fs-display);line-height:var(--lh-display);
  letter-spacing:var(--ls-display);font-weight:var(--fw-display);color:var(--ink);
  max-width:var(--measure-display);text-wrap:balance;hyphens:none}
h2,.h2{font-size:var(--fs-h2);line-height:var(--lh-h2);letter-spacing:var(--ls-h2);
  font-weight:var(--fw-h2);color:var(--ink);max-width:24ch;text-wrap:balance;hyphens:none}
h3,.h3{font-size:var(--fs-h3);line-height:var(--lh-h3);letter-spacing:var(--ls-h3);
  font-weight:var(--fw-strong);color:var(--ink)}
.lead{font-size:var(--fs-lead);line-height:var(--lh-lead);letter-spacing:var(--ls-lead);
  color:var(--ink-2);max-width:var(--measure-lead);text-wrap:pretty}
p{max-width:var(--measure-body);text-wrap:pretty}
.small{font-size:var(--fs-small);line-height:var(--lh-small);letter-spacing:var(--ls-small)}
.dark h1,.dark h2,.dark h3,.dark .h1,.dark .h2,.dark .h3{color:var(--on-dark)}
.dark,.dark p,.dark .lead{color:var(--on-dark-2)}
.dark a{color:var(--accent-dk)}
.dark a:hover{color:var(--on-dark)}

/* V2 — text-box-trim. Progressive enhancement ONLY: the spacing scale below is
   authored to look right WITHOUT it, and the trim tightens things by a few
   pixels where supported (Chrome 133+, Safari 18.2+, not Firefox). */
@supports (text-box: trim-both cap alphabetic){
  h1,h2,.h1,.h2{text-box:trim-both cap alphabetic}
  .sec-head{margin-bottom:clamp(30px,4.8vh,54px)}
}

/* section rhythm. The relational rules from research 02 §3:
   space ABOVE a heading = 1.6 × its own size; space BELOW it, before its
   paragraph, = 0.55 × — visibly smaller, or the pairing breaks. */
.sec{padding-block:var(--section-y)}
.sec-tight{padding-block:var(--section-y-tight)}
/* `.sec-head` carries SPACING ONLY and never a width. It once carried
   `max-width:46ch`, and on the one section where it shared an element with
   `.wrap` that override collapsed the whole container to 46ch and then
   `margin:0 auto` centred it — a heading floating in the middle of the page
   with the controls under it still left-aligned. The measures belong on the
   type (`h2` 24ch, `.lead` 46ch), where they cannot leak into a layout box. */
.sec-head{margin-bottom:clamp(34px,5.4vh,64px)}
/* Applies wherever a lead follows a heading, not only inside `.sec-head` —
   the contact block does not use `.sec-head` and its lead was touching the
   headline's last line with zero gap. A relationship rule belongs on the
   relationship, not on one container that happens to contain it. */
h2+.lead,.h2+.lead{margin-top:calc(var(--fs-h2) * .55)}
.dark{background:var(--dark);color:var(--on-dark-2)}


/* ─── 4 · THE LOADER (D2, L5) ─────────────────────────────────────────────
   Brand moment, not a gate. No text, no percentage, no «завантаження…»: every
   one of those is a promise about a duration the visitor will not experience
   (D-UX rule 3).

   THE CAP IS A CSS ANIMATION, NOT A TIMER — deliberately, and this is the
   whole of law L5. The loader dismisses itself at 620 ms and is gone at 860 ms
   with NO JavaScript involved at all: it cannot be delayed by a script, a
   font, a dictionary or a network. And the failure modes both land the right
   way up — if this stylesheet never arrives, `.load` has no `position:fixed`,
   so it is a plain block at the top of a fully visible page; if scripting is
   off entirely, the animation still runs. There is no state in which a visitor
   is left looking at it. A cap enforced by a `setTimeout` in a file that has
   to load first is not a cap. */
.load{position:fixed;inset:0;z-index:200;display:grid;place-items:center;
  background:var(--paper);
  animation:loadOut 240ms var(--ease-out-quart) 620ms both}
/* `visibility:hidden` already removes it from hit-testing — an extra
   `pointer-events` here would be the only animated property on the page that
   is neither opacity, transform, clip-path nor visibility, and the L1 checker
   flags exactly that, correctly. */
@keyframes loadOut{from{opacity:1;visibility:visible}
                   to  {opacity:0;visibility:hidden}}
html.no-loader .load,html.still .load{display:none}
.load-mk{display:grid;justify-items:center;gap:14px}
.load-mk .lk-gr{height:clamp(46px,9vw,64px);width:auto;fill:var(--ink);
  clip-path:inset(0 100% 0 0);animation:mkWipe 620ms var(--ease-out-expo) 90ms both}
.load-mk .lk-la{height:clamp(11px,1.7vw,13px);width:auto;fill:var(--ink-3);
  opacity:0;animation:mkFade 220ms var(--ease-out-quart) 460ms both}
@keyframes mkWipe{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}
@keyframes mkFade{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}


/* ─── 5 · NAV ─────────────────────────────────────────────────────────────
   V15 — the two corner controls are permanent page chrome and their failure
   mode is competing with the H1 for the first fixation. Nothing here is
   coloured, nothing exceeds 15 px, nothing has a filled background until it is
   hovered or focused, and everything sits on one optical baseline. */
.nav{position:sticky;top:0;z-index:60;background:color-mix(in srgb,var(--paper) 88%,transparent);
  backdrop-filter:saturate(1.4) blur(10px);-webkit-backdrop-filter:saturate(1.4) blur(10px);
  border-bottom:var(--hair-w) solid var(--hair)}
@supports not (backdrop-filter:blur(1px)){ .nav{background:var(--paper)} }
.nav-in{display:flex;align-items:center;gap:var(--s-4);height:var(--nav-h);
  flex-wrap:nowrap}
.nav-in>*{flex:0 0 auto}
/* `flex:0 0 auto` on the lockup is load-bearing, not tidiness. An <svg> with a
   height and `width:auto` has no intrinsic minimum in a flex row, so at 390 px
   the wordmark did not merely shrink — it collapsed to ZERO WIDTH and the nav
   showed a background button, three language links and a CTA with no brand at
   all, while the CTA still overflowed the viewport by 10 px. */
.nav-mk{display:flex;align-items:baseline;gap:9px;margin-right:auto;padding:6px 4px;
  border-radius:var(--r-xs);flex:0 0 auto}
.nav-mk .lk-gr{height:21px;width:auto;fill:var(--ink);transform:translateY(3px)}
.nav-mk .lk-la{height:11px;width:auto;fill:var(--ink-3);
  transition:fill var(--t-instant) var(--ease-out-quart)}
.nav-mk:hover .lk-la{fill:var(--ink)}

/* Four permanent controls — the Greek background button, the brand, three
   languages and the CTA — do not fit in 335 px of content, measured. So the
   phone nav is tightened, and below 420 px the LATIN wordmark leaves the nav
   while the Greek mark stays. The Latin reading is not lost on a phone: the
   loader shows the full lockup centred on first paint, and the footer carries
   it again at 24 px. What may never be dropped is the CTA, which is the whole
   point of the bar, or the language row, which correction (H) places here. */
@media (max-width:559px){
  .nav-in{gap:8px}
  .nav .bgsw{margin-left:-12px}
  .nav-mk{gap:7px;padding:6px 2px}
  .nav-mk .lk-gr{height:19px}
  .nav-mk .lk-la{height:10px}
  .lang a{padding:9px 6px;font-size:14px}
  .nav-cta{padding:9px 12px;font-size:14px}
}
/* F10 — the row is a budget, and the previous build spent it on a 14-character
   CTA and then paid for it by DELETING the Latin wordmark at exactly the two
   widths CONTRACT_V2 §8 names (360 / 390 px). The measurement that produced the
   480 px breakpoint is still right — at 430 px the wordmark reappears at 81 px
   and pushes the CTA 36 px past the edge — but it was taken with the long label.
   «Демо за 48 годин» → «Демо» gives back ~86 px at 13-14 px type, which is more
   than the 81 px the wordmark costs, so the Latin reading STAYS on a phone.
   D1's done-condition is a lockup legible at 360 px; a bare `ἕτ` is not one, and
   the loader is not a fallback for it — the head script skips the loader on every
   navigation after the first in a session. */
.nav-cta-s{display:none}
@media (max-width:479px){
  .nav-cta-l{display:none}
  .nav-cta-s{display:inline}
  .nav-mk .lk-la{height:9px}
}
/* P-2 — between 480 and 900 px the five section links are hidden and there is no
   menu, so the only route to Ціни was a long scroll. ONE link comes back there.
   BELOW 480 px it does NOT, and that is a measurement, not a preference: the row
   is a budget of 390 px and this link costs 46 px of it. Measured in a
   same-origin 390 px probe with the link present — nav content 392 px against a
   390 px viewport, i.e. 2 px OVER at 390 and 32 px over at 360 — and because
   `html`/`body` carry `overflow-x:clip`, that overflow does not scroll and does
   not show up in `scrollWidth`: it silently CLIPS the right edge of the CTA. The
   phone budget buys the Latin wordmark (D1 / F10) instead, which is the row a
   reviewer actually holds open; the price is still one tap away from the hero's
   «Скільки це коштує ↓» and from the footer. The new R1 check below makes this
   class of silent clipping fail out loud. */
.nav-price{display:none}
@media (min-width:480px) and (max-width:899px){
  .nav-price{display:inline;font-size:var(--fs-small);letter-spacing:var(--ls-small);
    color:var(--ink-3);padding:9px 4px}
  .nav-price:hover{color:var(--ink)}
  .dark .nav-price{color:var(--on-dark-3)}
}
/* Measured with the R1 check in a same-origin probe: at a 345 px layout width
   (a 360 px device that keeps a scrollbar) the row was 1 px over. These four
   values buy 8 px, which is the difference between «fits» and «the CTA loses a
   pixel of its right edge, invisibly, because overflow-x is clip». */
@media (max-width:399px){
  .nav-in{gap:5px;padding-left:12px;padding-right:12px}
  .lang a{padding:9px 3px;font-size:13px}
  .nav-cta{padding:9px 10px;font-size:13px}
}
/* Below 340 px the row genuinely does not fit — measured 46 px over at 320 —
   and the honest answer is a second row, not a clipped button or a truncated
   label. 320 px is outside the 360/390 target, but "outside the target" must
   still mean correct. */
@media (max-width:339px){
  .nav-in{flex-wrap:wrap;height:auto;row-gap:8px;padding-block:9px}
  .nav-cta{order:5;width:100%}
  :root{--nav-h:106px}
}
.nav-links{display:none;gap:var(--s-5)}
.nav-links a{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--ink-3)}
.nav-links a:hover{color:var(--ink)}
@media (min-width:900px){ .nav-links{display:flex} }
@media (min-width:1080px){ .nav-links{gap:var(--s-6)} }
/* below 1080 px the process link folds away first — it is the one nav item that
   is also fully answered by the section it points at. */
@media (max-width:1079px){ .nav-links a[data-fold]{display:none} }

/* the language switcher — a three-item text row separated by hairlines. The
   tell of a generated page here is a pill-shaped coloured selector with a globe
   and a caret; real multilingual studios ship this. */
.lang{display:flex;align-items:center}
/* M5-03 — `--ink-4` is the token bg.css §G declares BANNED for text: #6A727B on
   bare paper is 4.36:1, and on the Θ halftone field 3.25:1, both under AA. It
   was nevertheless carrying four pieces of 15 px body text, three of them
   load-bearing (the language row, the delivery times beside the prices, and the
   sentence that justifies every hryvnia figure). All four moved to `--ink-3`
   (#4A5158 — 7.19:1 on paper, 5.37:1 on Θ), which still reads as secondary.
   bg.js `check()` now includes `--ink-4` at the 3:1 non-text bar, so the prose
   rule has an executing counterpart. */
.lang a{display:block;padding:9px 10px;font-size:var(--fs-small);line-height:1;
  letter-spacing:.02em;color:var(--ink-3);border-radius:var(--r-xs);
  transition:color var(--t-instant) var(--ease-out-quart)}
.lang a:hover{color:var(--ink-2)}
.lang a[aria-current="true"]{color:var(--ink);font-weight:var(--fw-strong)}
.lang a+a{box-shadow:inset 1px 0 0 0 var(--hair)}
.dark .lang a{color:var(--on-dark-3)}
.dark .lang a[aria-current="true"]{color:var(--on-dark)}
.dark .lang a+a{box-shadow:inset 1px 0 0 0 var(--hair-dark)}

/* the Greek background button sits FIRST in the nav row, i.e. genuinely top
   left, which also means the wordmark can never end up underneath it. bg.css
   owns its ink; this only owns its place. */
.nav .bgsw{margin-left:-10px;flex:0 0 auto}
/* M5-10 — bg.js writes `data-bg` on <html> synchronously in <head>. If that file
   404s or is blocked, the attribute is never written and every variant block in
   bg.css goes inert; without this rule the nav would keep a focusable, labelled
   44 px control that draws nothing and does nothing, announced by a screen reader
   as an actionable button with no effect. Missing engine ⇒ no control. */
html:not([data-bg]) .bgsw{display:none}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font-weight:var(--fw-strong);letter-spacing:-.008em;border:0;cursor:pointer;
  border-radius:var(--r-pill);text-align:center;
  transition:background-color var(--t-ui) var(--ease-out-quart),
             color var(--t-ui) var(--ease-out-quart),
             transform var(--t-instant) var(--ease-out-quart)}
.btn:active{transform:translateY(1px)}
.btn-p{background:var(--accent);color:#fff;padding:14px 24px;font-size:var(--fs-body)}
.btn-p:hover{background:var(--accent-hi);color:#fff}
.dark .btn-p{background:var(--accent-dk);color:var(--dark)}
.dark .btn-p:hover{background:#B7DBD4;color:var(--dark)}
.btn-s{background:transparent;color:var(--ink);padding:13px 22px;font-size:var(--fs-body);
  box-shadow:inset 0 0 0 var(--hair-w) var(--hair-strong)}
.btn-s:hover{background:var(--paper-2);color:var(--ink)}
.nav-cta{padding:9px 16px;font-size:var(--fs-small);white-space:nowrap}
@media (max-width:479px){ .nav-cta{padding:9px 13px} }

/* V13 — the only element on the page that maps scroll POSITION to a visual
   value, which is what velocity-linked effects are usually faking. Runs on the
   compositor, so it cannot jitter during a fast phone scroll — which is exactly
   when a JS-driven progress bar does jitter. Costs NEGATIVE bytes: it replaces
   a scroll listener. */
.nav-prog{position:absolute;left:0;right:0;bottom:-1px;height:2px;
  background:var(--accent);transform-origin:0 50%;transform:scaleX(0)}
@supports (animation-timeline:scroll()){
  .nav-prog{animation:navProg linear both;animation-timeline:scroll(root block)}
}
@keyframes navProg{to{transform:scaleX(1)}}


/* ─── 6 · HERO ────────────────────────────────────────────────────────────
   D8, V16. The calm is bought by SUBTRACTION and SPACING, never by removing
   the field (L7). There is no eyebrow (D7), no coloured micro-label, no stat
   row above the fold, and the price ledger sits at the FOOT of the hero rather
   than beside the headline — it duplicates the price section and it was the
   fifth element competing for the first fixation. */
.hero{padding-top:clamp(52px,9.5vh,116px);padding-bottom:var(--section-y-tight)}
/* The COPY owns a screen; the ledger begins just past the fold with a one-row
   peek. Measured, and this is the D8 lever the research named second: with the
   ledger inside the first viewport the ink fraction at 390 × 844 was 0.539 —
   the price table is simply a lot of boxes, and it repeats the price section
   verbatim 4 000 px further down. A peek invites the scroll; a full table
   competes with the headline. */
.hero-copy{display:flex;flex-direction:column;justify-content:center;
  min-height:min(calc(80svh - var(--nav-h)),640px)}
@supports not (height:1svh){ .hero-copy{min-height:min(calc(80vh - var(--nav-h)),640px)} }
.hero-lead{margin-top:clamp(20px,3.2vh,32px);max-width:var(--measure-lead)}
.hero-act{margin-top:clamp(28px,4.4vh,44px);display:flex;flex-wrap:wrap;
  align-items:center;gap:var(--s-4) var(--s-6)}
.hero-alt{display:inline-flex;align-items:center;gap:7px;font-size:var(--fs-body);
  color:var(--ink-3)}
.hero-alt:hover{color:var(--ink)}
.hero-alt .ar{width:11px;height:11px;fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
  transition:transform var(--t-ui) var(--ease-out-quart)}
.hero-alt:hover .ar{transform:translateY(2px)}

/* the price ledger — three rows, tabular, no card, no shadow. The hryvnia
   figure is rendered in the HTML at build time from the compiled-in NBU rate,
   so it is TRUE on first paint with JavaScript disabled (L1). */
.ledger-wrap{margin-top:clamp(46px,8vh,96px)}
.ledger{display:block;color:inherit;border-top:var(--hair-w) solid var(--hair);
  max-width:640px}
.ledger:hover{color:inherit}
.led-row{display:grid;grid-template-columns:1fr auto;gap:2px var(--s-4);
  align-items:baseline;padding:13px 0;border-bottom:var(--hair-w) solid var(--hair);
  transition:border-color var(--t-ui) var(--ease-out-quart)}
.ledger:hover .led-row{border-color:var(--hair-strong)}
.led-k{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--ink-2)}
.led-t{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--ink-3);
  grid-column:1}
.led-money{grid-column:2;grid-row:1/span 2;display:flex;flex-direction:column;
  align-items:flex-end;gap:1px;font-variant-numeric:tabular-nums}
.led-usd{font-size:var(--fs-h3);letter-spacing:var(--ls-h3);color:var(--ink);
  font-weight:var(--fw-strong)}
.led-uah{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--ink-3)}
.led-note{margin-top:var(--s-4);font-size:var(--fs-small);letter-spacing:var(--ls-small);
  color:var(--ink-3);max-width:52ch}
.rate-line{margin-top:6px;font-size:var(--fs-small);letter-spacing:var(--ls-small);
  color:var(--ink-3);max-width:52ch;font-variant-numeric:tabular-nums}


/* ─── 7 · WHO ────────────────────────────────────────────────────────────── */
.who-grid{display:grid;gap:clamp(28px,5vw,72px)}
@media (min-width:900px){ .who-grid{grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  align-items:start} }
.who-name{margin-top:var(--s-3);color:var(--ink-2)}
/* The full Greek word sits on its OWN line above the sentence that explains
   it. Inline, immediately before a sentence beginning «hetoimos грецькою…»,
   it read as one run-on word — «ἕτοιμοςhetoimos» — because the mark and the
   next word are the same word in two alphabets. Above the line it is the
   subject of the sentence instead of a collision with it. */
.mk-word{display:block;margin-top:var(--s-6)}
.mk-word svg{height:clamp(19px,2.1vw,26px);width:auto;fill:var(--ink);opacity:.9}
.who-body{margin-top:var(--s-5)}
.stats{display:grid;gap:0;border-top:var(--hair-w) solid var(--hair)}
@media (min-width:560px) and (max-width:899px){ .stats{grid-template-columns:1fr 1fr} }
.stat{padding:var(--s-5) 0;border-bottom:var(--hair-w) solid var(--hair);
  display:grid;gap:3px;align-content:start}
@media (min-width:560px) and (max-width:899px){
  .stat:nth-child(odd){padding-right:var(--s-5)}
  .stat:nth-child(even){padding-left:var(--s-5);
    box-shadow:inset 1px 0 0 0 var(--hair)}
}
/* L1 — these four numbers are FACTS. They render true on first paint and no
   rule in this file animates their content. A count-up here displayed «1 год»
   where the truth is «48 годин»; that is not a cheap effect, it is the page
   stating a falsehood. */
.stat-v{font-size:clamp(24px,2.6vw,30px);line-height:1.12;letter-spacing:-.022em;
  color:var(--ink);font-weight:var(--fw-display);font-variant-numeric:tabular-nums}
.stat-k{font-size:var(--fs-small);line-height:1.45;letter-spacing:var(--ls-small);
  color:var(--ink-3);max-width:34ch}


/* ─── 8 · WORKS — one block, four switchers, the LIVE site ────────────────
   research/04-works.md is the specification; the three things that decide the
   shape of this CSS are all MEASURED, and all three invert what a reasonable
   engineer would build:

   1. Four cross-origin frames mounted at once paint NOTHING — every frame
      stays at the wrapper's background for 20 s. One frame paints in under a
      second. So `content-visibility` on four live panels is wrong here and
      app.js mounts exactly one <iframe> at a time. (§1.2)
   2. A BLOCKED frame still fires `load`, fastest of all five targets. There is
      no DOM event that tells us an embed worked, so every status below is a
      timer we own and the failure copy never claims to know a cause. (§1.1)
   3. An iframe's viewport IS its CSS box: at 360 px every one of the four
      renders its own MOBILE layout. Nothing is scaled — a 1280 px frame at
      scale(0.28) turns 16 px body text into 4.5 px and 44 px touch targets
      into 12 px. Refused in writing at every width. (§3.8) */
.works{overflow:hidden}
.works-head{margin-bottom:clamp(26px,4vh,44px)}

/* V12 — the tab strip is edge-masked, not wrapped and not shrunk. Four labels
   do not fit on one line at 360 px; a 2×2 block turns a control strip into a
   menu and doubles the vertical space above the work. The fade says «there is
   more» without an arrow, a dot row or a scrollbar. */
.wtabs{display:flex;gap:var(--s-2);overflow-x:auto;scrollbar-width:none;
  scroll-snap-type:x proximity;overscroll-behavior-x:contain;
  -webkit-mask-image:linear-gradient(to right,#000 0 88%,transparent 100%);
          mask-image:linear-gradient(to right,#000 0 88%,transparent 100%);
  padding-bottom:2px}
.wtabs::-webkit-scrollbar{display:none}
@media (min-width:860px){ .wtabs{overflow:visible;gap:var(--s-6);
  -webkit-mask-image:none;mask-image:none} }
.wtab{flex:0 0 auto;scroll-snap-align:start;display:grid;gap:2px;
  background:none;border:0;padding:10px 12px 12px;cursor:pointer;text-align:left;
  border-radius:var(--r-xs);position:relative;
  transition:color var(--t-switch) var(--ease-out-quart)}
@media (min-width:860px){ .wtab{padding:10px 0 12px} }
.wtab-l{font-size:var(--fs-body);font-weight:var(--fw-strong);letter-spacing:-.01em;
  color:var(--on-dark-3);white-space:nowrap;
  transition:color var(--t-switch) var(--ease-out-quart)}
.wtab-s{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--on-dark-3);
  opacity:.72;white-space:nowrap;transition:opacity var(--t-switch) var(--ease-out-quart)}
.wtab::after{content:"";position:absolute;left:12px;right:12px;bottom:0;height:2px;
  background:var(--accent-dk);transform:scaleX(0);transform-origin:0 50%;
  transition:transform var(--t-switch) var(--ease-out-quart)}
@media (min-width:860px){ .wtab::after{left:0;right:0} }
.wtab:hover .wtab-l{color:var(--on-dark)}
.wtab[aria-selected="true"] .wtab-l{color:var(--on-dark)}
.wtab[aria-selected="true"] .wtab-s{opacity:1}
.wtab[aria-selected="true"]::after{transform:scaleX(1)}

/* the chrome bar — the ONE piece of chrome that is honest. It carries the real
   hostname of what is in the frame and nothing else. NOT a drawn URL bar with
   a fake padlock and three traffic lights: a fabricated address bar is the same
   defect class as a fabricated testimonial. (A7, §4.6) */
.wchrome{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s-3) var(--s-5);
  justify-content:space-between;margin-top:var(--s-5);padding-bottom:var(--s-4)}
.whost{font-size:var(--fs-small);letter-spacing:.01em;color:var(--on-dark-3);
  font-variant-numeric:tabular-nums;display:inline-flex;align-items:center;gap:7px}
/* P-2 — at 390 px the strip measures scrollWidth 591 against clientWidth 335 and
   tab 4 starts at offsetLeft 472, entirely off-screen behind an edge fade. V12's
   argument against wrapping still holds (a 2×2 block turns a control strip into
   a menu), so the fix is a position readout rather than a second row. */
.wcount{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--on-dark-3);
  font-variant-numeric:tabular-nums;opacity:.8}
@media (min-width:860px){ .wcount{display:none} }
.whost::before{content:"";width:6px;height:6px;border-radius:50%;
  background:var(--accent-dk);opacity:.8;flex:0 0 auto}
.wopen{font-size:var(--fs-small);letter-spacing:var(--ls-small);
  display:inline-flex;align-items:center;gap:7px;color:var(--accent-dk)}
.wopen .ar{width:11px;height:11px;fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
  transition:transform var(--t-ui) var(--ease-out-quart)}
.wopen:hover .ar{transform:translate(2px,-2px)}

/* the stage. Full viewport width, square corners, no border, no shadow, no
   device bezel, no fake browser window. Both measured precedents — Vercel's
   demo shot and WordPress.org's live previewer — ship exactly this rectangle
   (border-radius 0, box-shadow none, transform none). A drop shadow around a
   live website says «a widget»; a full-bleed square edge says «the site».
   Height is FIXED per breakpoint, identical for all four works, so switching
   tabs moves nothing below the block by even a pixel. */
.wstage{position:relative;width:100%;height:560px;overflow:hidden;
  background:var(--dark-3);contain:layout paint;
  border-radius:var(--r-media);border:0;box-shadow:none;
  border-block:var(--hair-w) solid var(--hair-dark)}
@media (min-width:720px){ .wstage{height:min(70vh,640px)} }
@media (min-width:1024px){ .wstage{height:min(72vh,760px)} }
.wstage iframe{position:absolute;inset:0;width:100%;height:100%;border:0;
  z-index:1;background:transparent}

/* the skeleton lives UNDERNEATH the frame and the frame paints over it.
   Nothing has to hide it, so nothing can fail to hide it. */
.wskel{position:absolute;inset:0;z-index:0;display:grid;align-content:center;
  justify-items:start;gap:10px;padding:0 var(--gutter);color:var(--on-dark-2)}
.wskel-n{font-size:var(--fs-h3);letter-spacing:var(--ls-h3);color:var(--on-dark);
  font-weight:var(--fw-strong)}
.wskel-s{font-size:var(--fs-small);letter-spacing:var(--ls-small);
  color:var(--on-dark-3);max-width:44ch}
.wskel-a{margin-top:6px}
.wstage.is-fail .wskel{z-index:4;background:var(--dark-3)}
/* L3-04 — the no-JavaScript truth is its own sentence, shown only when the head
   script never ran. The default status is neutral, so nothing on this page
   asserts a failure that was not attempted. */
.wskel-nojs{display:none}
html.no-js .wskel-nojs{display:block}
html.no-js #wskel-status{display:none}

/* E-1 — the LOADING line, in its own layer ABOVE the frame. It used to live in
   the skeleton at z-index 0, under an iframe declared `background:transparent`;
   a framed document paints its own opaque background within ~200 ms, so our
   explanation was covered almost immediately. For lovemix — measured at 8 255 ms
   of its own splash — the visitor watched a stranger's spinner for eight seconds
   with our sentence and our 11-second escape hint both invisible underneath it.
   Anchored to the TOP of the stage for the same reason as the shield label. */
.wstatus{position:absolute;left:50%;top:14px;transform:translateX(-50%);z-index:3;
  display:none;max-width:calc(100vw - 2 * var(--gutter));
  font-size:var(--fs-small);line-height:1.4;letter-spacing:var(--ls-small);color:#fff;
  background:rgba(11,14,18,.82);border-radius:var(--r-pill);padding:9px 16px;
  text-align:center}
@supports (backdrop-filter:blur(1px)){ .wstatus{background:rgba(11,14,18,.62);backdrop-filter:blur(8px)} }
.wstage.is-busy .wstatus{display:block}
/* the two never coexist: while we are explaining the wait, the shield's «this is
   the real site» label would be a claim we cannot yet make. */
.wstage.is-busy .wshield-l{opacity:0;pointer-events:none}

/* THE SHIELD. The single worst failure mode of a full-width live embed on a
   phone: the thumb lands inside the frame and OUR page stops scrolling — the
   visitor is scrolling a stranger's site with no way back. At 360 px that is
   not an edge case, it is the default gesture. The frame is live and painted
   from the first moment (Ihor's requirement is met — it is the real site, not
   a picture); this transparent button just decides who gets the touch until
   the visitor deliberately asks for the site.
   Same mechanic at every width — one behaviour, learned once (D-UX rule 5). */
.wshield{position:absolute;inset:0;z-index:2;width:100%;height:100%;
  background:transparent;border:0;padding:0;cursor:pointer;display:block;
  -webkit-tap-highlight-color:transparent}
/* E-2 — anchored to the TOP of the stage, not the bottom. Measured at 390 px
   with the works heading in normal reading position, the bottom-pinned label's
   top edge sat 236 px BELOW the fold: the one sentence that tells a phone user
   the frame is clickable arrived after the thumb-flick it exists to prevent. */
.wshield-l{position:absolute;left:50%;top:14px;transform:translateX(-50%);
  display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
  font-size:var(--fs-small);letter-spacing:var(--ls-small);color:#fff;
  background:rgba(11,14,18,.78);border-radius:var(--r-pill);padding:9px 16px;
  max-width:calc(100vw - 2 * var(--gutter));
  transition:background-color var(--t-ui) var(--ease-out-quart),
             opacity var(--t-ui) var(--ease-out-quart)}
@supports (backdrop-filter:blur(1px)){
  .wshield-l{background:rgba(11,14,18,.58);backdrop-filter:blur(8px)}
}
.wshield:hover .wshield-l{background:rgba(11,14,18,.9)}
.wstage.is-live .wshield{display:none}
/* L3-04 — under ?still=1 no frame is ever mounted and under `no-js` the click
   handler does not exist, so in both states the shield is a control that cannot
   act. A control that cannot act must not invite a tap, and its label must not
   assert «це справжній сайт» over an empty rectangle. */
html.still .wshield,html.no-js .wshield,
html.still .wexit,html.no-js .wexit{display:none}

/* the exit. Without it a desktop visitor whose wheel is captured is also
   stuck, so this is not a phone affordance — it is the way out of the frame at
   every width. Escape does the same thing. */
/* E-3 — 44 px minimum, WORDED, and in our accent, offset from the top-right
   corner. Measured, the old unlabelled 36 px ✕ rendered at (327, 94) directly on
   top of the framed site's own «Демо» button, so it read as a control belonging
   to the site inside the frame rather than to us — which is the trap the shield
   exists to prevent, moved one tap later. Escape does not exist on a phone, so
   this is the ONLY way out there and it has to look like ours. */
.wexit{position:absolute;right:12px;bottom:12px;z-index:5;display:none;
  min-height:44px;padding:0 16px;gap:8px;align-items:center;
  border-radius:var(--r-pill);border:0;cursor:pointer;
  font-size:var(--fs-small);letter-spacing:var(--ls-small);font-weight:var(--fw-strong);
  background:var(--accent-dk);color:var(--dark-3);
  box-shadow:0 6px 22px rgba(0,0,0,.34);
  transition:background-color var(--t-ui) var(--ease-out-quart)}
.wstage.is-live .wexit{display:inline-flex}
.wexit:hover{background:#fff}
.wexit svg{width:14px;height:14px;flex:0 0 auto;fill:none;stroke:currentColor;
  stroke-width:1.9;stroke-linecap:round}

/* the panels. All four ship in the HTML — indexable, and complete with
   JavaScript switched off. They are stacked in ONE grid cell so the block's
   height is the tallest panel and a switch changes 0 px. */
/* L3-01 / M5-02 — `opacity:0` hides a panel from the EYE and from nothing else.
   All four kept `tabindex="0"`, so a keyboard visitor walked through three
   invisible tabpanels, a screen reader read all four works' descriptions as one
   run, Ctrl+F matched inside them, and pressing Enter on Vilicor's invisible
   <summary> while another tab was selected grew the grid-stacked block by ~600 px
   of blank space with no visible cause. `visibility` is transitionable, so the
   crossfade survives; app.js additionally sets `inert` + `aria-hidden`, and the
   accessible variant that already existed for `html.no-js` now also ships to
   everyone else. */
.wpanels{display:grid;margin-top:clamp(26px,4vh,44px)}
.wpanel{grid-area:1/1;opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity var(--t-switch) var(--ease-out-quart),
             visibility 0s linear var(--t-switch)}
.wpanel.is-on{opacity:1;visibility:visible;pointer-events:auto;
  transition:opacity var(--t-switch) var(--ease-out-quart),visibility 0s}
html.no-js .wpanel{opacity:1;visibility:visible;pointer-events:auto}
html.no-js .wpanel:not(.is-on){display:none}
.wp-grid{display:grid;gap:var(--s-5) clamp(28px,5vw,64px)}
@media (min-width:900px){ .wp-grid{grid-template-columns:minmax(0,1.5fr) minmax(0,1fr)} }
.wp-kind{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--on-dark-3)}
.wp-what{font-size:var(--fs-h3);line-height:1.34;letter-spacing:var(--ls-h3);
  color:var(--on-dark);margin-top:6px;max-width:34ch}
.wp-did{margin-top:var(--s-4)}
.wp-side{display:grid;gap:var(--s-4);align-content:start}
.wp-note{font-size:var(--fs-small);line-height:1.5;letter-spacing:var(--ls-small);
  color:var(--on-dark-3);padding-left:var(--s-4);
  box-shadow:inset 2px 0 0 0 var(--hair-dark-2);max-width:46ch}
.wp-tags{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--on-dark-3)}

/* the six-mechanism case study lives INSIDE work 1, never as a separate
   section (CONTRACT_V2 §2). Closed at every width, and app.js closes it on tab
   change so Vilicor's depth never holds the block tall for the other three. */
.deep{margin-top:var(--s-5);border-top:var(--hair-w) solid var(--hair-dark)}
.deep>summary{list-style:none;cursor:pointer;padding:var(--s-4) 0;
  display:flex;align-items:center;gap:10px;color:var(--on-dark);
  font-size:var(--fs-small);letter-spacing:var(--ls-small);font-weight:var(--fw-strong)}
.deep>summary::-webkit-details-marker{display:none}
.deep>summary .chev{width:12px;height:12px;flex:0 0 auto;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;
  transition:transform var(--t-ui-slow) var(--ease-out-circ)}
.deep[open]>summary .chev{transform:rotate(90deg)}
.deep-in{padding-bottom:var(--s-6);display:grid;gap:var(--s-5)}
.deep-i h4{font-size:var(--fs-body);font-weight:var(--fw-strong);color:var(--on-dark);
  letter-spacing:-.008em;margin-bottom:4px}
.deep-i p{font-size:var(--fs-small);line-height:1.58;letter-spacing:var(--ls-small);
  color:var(--on-dark-2);max-width:66ch}
.deep-scale{font-size:var(--fs-small);line-height:1.55;letter-spacing:var(--ls-small);
  color:var(--on-dark-3);padding-top:var(--s-4);
  border-top:var(--hair-w) solid var(--hair-dark);max-width:66ch}
@media (min-width:760px){ .deep-in{grid-template-columns:1fr 1fr;gap:var(--s-6) var(--s-8)}
  .deep-scale{grid-column:1/-1} }
/* height interpolation where the engine has it; a hard snap everywhere else,
   which is a correct page, not a degraded one. */
@supports (interpolate-size:allow-keywords){
  :root{interpolate-size:allow-keywords}
  .deep::details-content{height:0;overflow:hidden;
    transition:height var(--t-ui-slow) var(--ease-out-circ),content-visibility var(--t-ui-slow) allow-discrete}
  .deep[open]::details-content{height:auto}
}


/* ─── 9 · CAPABILITIES · PROCESS · PRICE · REFERRAL · FAQ ─────────────────
   V9 — content-visibility on the sections BELOW the works block. It does not
   look like anything; it makes the first scroll smooth on the mid-range
   Android our buyer is holding, which is the only «premium feel» that survives
   that device. NEVER on a section containing an active iframe. */
/* V9 + P-1. `contain-intrinsic-size: auto 900px` on five sections of very
   different heights made the reported document height start at 11 956 px and
   grow to 15 033 px as the reader scrolled — the scrollbar kept lying about how
   much was left, on the page whose whole job is a four-minute check. The values
   below are the measured 390 px heights, so the estimate is right before the
   section is ever rendered and `auto` still corrects it afterwards. */
.can{content-visibility:auto;contain-intrinsic-size:auto 1830px}
.how{content-visibility:auto;contain-intrinsic-size:auto 1370px}
.price{content-visibility:auto;contain-intrinsic-size:auto 3660px}
.ref{content-visibility:auto;contain-intrinsic-size:auto 690px}
.faq{content-visibility:auto;contain-intrinsic-size:auto 1020px}
/* the same five sections MEASURED at 1 200 px, where the grids go multi-column
   and every one of them is roughly half as tall. Without this the desktop
   scrollbar starts ~5 000 px too long and shortens as the reader scrolls, which
   is the same lie in the other direction. */
@media (min-width:900px){
  .can{contain-intrinsic-size:auto 1120px}
  .how{contain-intrinsic-size:auto 700px}
  .price{contain-intrinsic-size:auto 2170px}
  .ref{contain-intrinsic-size:auto 460px}
  .faq{contain-intrinsic-size:auto 975px}
}

.cards{display:grid;gap:0;border-top:var(--hair-w) solid var(--hair)}
@media (min-width:760px){ .cards{grid-template-columns:1fr 1fr;
  column-gap:clamp(32px,5vw,72px)} }
.card{padding:var(--s-6) 0;border-bottom:var(--hair-w) solid var(--hair)}
.card h3{margin-bottom:8px}
.card p{color:var(--ink-2);max-width:52ch}
.can-note{margin-top:var(--s-7);font-size:var(--fs-lead);line-height:var(--lh-lead);
  letter-spacing:var(--ls-lead);color:var(--ink-2);max-width:var(--measure-lead)}

.steps{display:grid;gap:0;border-top:var(--hair-w) solid var(--hair)}
@media (min-width:900px){ .steps{grid-template-columns:repeat(4,1fr);
  column-gap:clamp(24px,3vw,44px)} }
.step{padding:var(--s-6) 0;border-bottom:var(--hair-w) solid var(--hair);
  display:grid;gap:8px;align-content:start}
.step-n{font-size:var(--fs-small);letter-spacing:.08em;color:var(--ink-3);
  font-variant-numeric:tabular-nums}
.step h3{font-size:var(--fs-body);font-weight:var(--fw-strong);letter-spacing:-.008em}
.step p{font-size:var(--fs-small);line-height:1.55;letter-spacing:var(--ls-small);
  color:var(--ink-3);max-width:40ch}
.step-b{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--ink-2);
  font-weight:var(--fw-strong)}
.how-note{margin-top:var(--s-7);color:var(--ink-2);max-width:var(--measure-lead)}

/* PRICE. Subscription only — there are no tabs, because there is nothing to
   choose between: the «site without maintenance» SKU is deleted. The tiers are
   scoped by COMPLEXITY, never by industry; tier 3 is not «магазин». */
.price-model{margin-top:var(--s-6);font-size:var(--fs-lead);line-height:var(--lh-lead);
  letter-spacing:var(--ls-lead);color:var(--ink-2);max-width:var(--measure-lead)}
.tiers{display:grid;gap:var(--s-5);margin-top:clamp(34px,5vh,56px)}
@media (min-width:820px){ .tiers{grid-template-columns:repeat(3,1fr)} }
/* FLEX, not grid, and the reason is measured: with `display:grid` +
   `align-content:start` the rows pack at the top and `margin-top:auto` on the
   last item has no free space in its own row to consume — so the three CTAs
   ended 221 px / 32 px / 122 px above their card bottoms, three buttons on
   three different baselines in a row the buyer is comparing. In a flex column
   the auto margin eats the container's free space, so the monthly line and the
   button pin to the bottom and align across all three. */
.tier{background:var(--surface);border:var(--hair-w) solid var(--hair);
  border-radius:var(--r-md);padding:var(--s-6);
  display:flex;flex-direction:column;gap:var(--s-4);
  box-shadow:inset 0 1px 0 0 var(--hair-lit),var(--shadow-1)}
.tier .tier-monthly{margin-top:auto}
.tier-name{font-size:var(--fs-h3);letter-spacing:var(--ls-h3);color:var(--ink);
  font-weight:var(--fw-strong)}
.tier-sub{font-size:var(--fs-small);line-height:1.5;letter-spacing:var(--ls-small);
  color:var(--ink-3);margin-top:-6px;max-width:38ch}
.tier-money{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;
  font-variant-numeric:tabular-nums}
.tier-usd{font-size:clamp(30px,3.2vw,38px);line-height:1.05;letter-spacing:-.028em;
  color:var(--ink);font-weight:var(--fw-display)}
.tier-uah{font-size:var(--fs-body);color:var(--ink-3)}
.tier-term{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--ink-2);
  font-weight:var(--fw-strong)}
.tier-monthly{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--ink-3);
  padding-top:var(--s-4);border-top:var(--hair-w) solid var(--hair);
  font-variant-numeric:tabular-nums}
.tier-f{display:grid;gap:9px;margin-top:2px;flex:0 0 auto}
.tier-f li{font-size:var(--fs-small);line-height:1.5;letter-spacing:var(--ls-small);
  color:var(--ink-2);padding-left:17px;position:relative}
.tier-f li::before{content:"";position:absolute;left:0;top:.62em;width:8px;height:1px;
  background:var(--hair-strong)}
.tier .btn{margin-top:auto;width:100%}
.tier-e{border-color:var(--hair-strong);background:var(--paper-2);box-shadow:none}
.tier-e .tier-monthly{margin-top:0}
/* C-7 — every other tier carries a monthly line, and the section's own premise is
   that we do not sell «зробіть і забудьте». Enterprise carried none, so the tier
   aimed at the largest buyer was the one silent about the recurring cost. */
.tier-monthly-e{color:var(--ink-3)}

/* P-1 — the price section measured 3 939 px at 390 px, about 5.8 phone screens
   of a 15 033 px document. Four tiers a buyer is comparing must stay side by
   side in the reading order, so they are NOT collapsed into disclosure rows —
   the comparison is the section's whole job. What is compacted is the air inside
   each card at phone width, which is worth ~560 px across the four and costs the
   comparison nothing. */
@media (max-width:559px){
  .tiers{gap:var(--s-4)}
  .tier{padding:var(--s-5);gap:var(--s-3)}
  .tier-f{gap:7px}
  .tier-usd{font-size:30px}
}
@media (min-width:820px){ .tier-e{grid-column:1/-1;display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);column-gap:var(--s-8);
  align-items:start;align-content:start} }
@media (min-width:820px){ .tier-e .tier-name{grid-column:1}
  .tier-e .tier-sub{grid-column:1} .tier-e .tier-money{grid-column:1}
  .tier-e .tier-term{grid-column:1}
  .tier-e .tier-f{grid-column:2;grid-row:1/span 5;align-self:start}
  /* the CTA belongs under the PRICE, not under the feature list: in column 2
     it floated in the middle of a half-empty cell with nothing above it. */
  .tier-e .btn{grid-column:1;justify-self:start;width:auto;margin-top:var(--s-4)} }
.tier-e .tier-usd{font-size:var(--fs-h3);letter-spacing:var(--ls-h3);
  font-weight:var(--fw-strong)}

.subwhat{margin-top:clamp(34px,5vh,56px);border-top:var(--hair-w) solid var(--hair);
  padding-top:var(--s-6)}
.subwhat h3{margin-bottom:var(--s-4)}
.subwhat ul{display:grid;gap:10px}
@media (min-width:760px){ .subwhat ul{grid-template-columns:1fr 1fr;
  column-gap:clamp(32px,5vw,72px)} }
.subwhat li{font-size:var(--fs-small);line-height:1.5;letter-spacing:var(--ls-small);
  color:var(--ink-2);padding-left:17px;position:relative;max-width:48ch}
.subwhat li::before{content:"";position:absolute;left:0;top:.62em;width:8px;height:1px;
  background:var(--hair-strong)}
.subwhat-cancel{margin-top:var(--s-5);color:var(--ink-2);max-width:var(--measure-body)}
/* The honest sentences are set at body size, not as fine print. «Ціна залежить
   від складності, від курсу й від того, що зараз в економіці» is the sentence
   Ihor asked for and it earns full size. */
.notes{margin-top:clamp(30px,4.5vh,48px);display:grid;gap:10px}
.notes p{color:var(--ink-3);max-width:var(--measure-body)}

/* REFERRAL. Two columns from 900 px: the offer on the left, how it is paid and
   what to do about it on the right. As one column the card was 1052 px wide
   with text in the left 700 and nothing at all in the right 350 — an empty
   third of a surface reads as a layout that was not finished. */
.ref-in{background:var(--surface);border:var(--hair-w) solid var(--hair);
  border-radius:var(--r-md);padding:clamp(26px,4vw,48px);
  box-shadow:inset 0 1px 0 0 var(--hair-lit),var(--shadow-1);
  display:grid;gap:var(--s-5) clamp(32px,5vw,72px)}
@media (min-width:900px){ .ref-in{grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);
  align-items:end} }
.ref-main{display:grid;gap:var(--s-4);align-content:start}
.ref-side{display:grid;gap:var(--s-5);align-content:end}
.ref-in .lead{max-width:46ch}
.ref-pay{font-size:var(--fs-small);line-height:1.55;letter-spacing:var(--ls-small);
  color:var(--ink-3);max-width:40ch;padding-left:var(--s-4);
  box-shadow:inset 2px 0 0 0 var(--hair)}
.ref-act{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-4) var(--s-5)}
.ref-terms{font-size:var(--fs-small);letter-spacing:var(--ls-small)}

/* FAQ */
.faq-list{border-top:var(--hair-w) solid var(--hair)}
.qa{border-bottom:var(--hair-w) solid var(--hair)}
.qa>summary{list-style:none;cursor:pointer;padding:var(--s-5) 34px var(--s-5) 0;
  position:relative;font-size:var(--fs-h3);line-height:1.35;letter-spacing:var(--ls-h3);
  color:var(--ink);font-weight:var(--fw-strong);
  transition:color var(--t-instant) var(--ease-out-quart)}
.qa>summary::-webkit-details-marker{display:none}
.qa>summary:hover{color:var(--accent)}
.qa>summary .chev{position:absolute;right:4px;top:calc(var(--s-5) + .38em);
  width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;color:var(--ink-4);
  transition:transform var(--t-ui-slow) var(--ease-out-circ)}
.qa[open]>summary .chev{transform:rotate(90deg)}
.qa-a{padding:0 0 var(--s-6);color:var(--ink-2);max-width:var(--measure-body)}
@supports (interpolate-size:allow-keywords){
  .qa::details-content{height:0;overflow:hidden;
    transition:height var(--t-ui-slow) var(--ease-out-circ),content-visibility var(--t-ui-slow) allow-discrete}
  .qa[open]::details-content{height:auto}
}


/* ─── 10 · CONTACT, FORM, FOOTER ─────────────────────────────────────────── */
.contact-grid{display:grid;gap:clamp(34px,5vw,80px)}
@media (min-width:900px){ .contact-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)} }
.contact-links{display:grid;gap:var(--s-3);margin-top:var(--s-6)}
.contact-links a{font-size:var(--fs-h3);letter-spacing:var(--ls-h3);
  color:var(--on-dark);display:inline-flex;align-items:center;gap:9px;width:fit-content}
.contact-links a:hover{color:var(--accent-dk)}
.contact-note{margin-top:var(--s-4);font-size:var(--fs-small);
  letter-spacing:var(--ls-small);color:var(--on-dark-3)}

.form{display:grid;gap:var(--s-4)}
.field{display:grid;gap:7px}
.field label{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--on-dark-2)}
/* D5 — no placeholder anywhere on this form. The old build invented a person
   («Олена») and a Lviv dental practice to fill the boxes; the labels carry the
   meaning and nothing has to be imagined. */
.field input,.field textarea{width:100%;background:rgba(255,255,255,.045);
  border:var(--hair-w) solid var(--hair-dark-2);border-radius:var(--r-sm);
  padding:13px 15px;color:var(--on-dark);font-size:var(--fs-body);
  transition:border-color var(--t-ui) var(--ease-out-quart),
             background-color var(--t-ui) var(--ease-out-quart)}
.field textarea{min-height:104px;resize:vertical;line-height:1.5}
.field input:hover,.field textarea:hover{border-color:var(--hair-dark)}
.field input:focus,.field textarea:focus{outline:none;border-color:var(--accent-dk);
  background:rgba(255,255,255,.075)}
.field-note{font-size:var(--fs-small);letter-spacing:var(--ls-small);color:var(--on-dark-3)}
/* T4 — THE HONEYPOT, and why the old shape lost real leads. `position:absolute;
   left:-9999px` is precisely the shape Chrome autofill and password managers DO
   fill (they skip display:none far more reliably than off-screen positioning),
   and `company` is precisely the field name an address-book heuristic targets;
   `autocomplete=off` is widely ignored for it. The endpoint answered a literal
   {ok:true} to a filled honeypot, so that visitor read «Готово. Заявка у нас»
   and no lead existed anywhere. display:none, and a name nothing autofills. */
.hp{display:none}
.form-act{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-4)}
.form-privacy{font-size:var(--fs-small);letter-spacing:var(--ls-small);
  color:var(--on-dark-3);max-width:40ch}
/* The status line NEVER fakes a success. On a network failure it says so and
   points at Telegram, where the message will actually arrive. */
.form-note{font-size:var(--fs-small);line-height:1.5;letter-spacing:var(--ls-small);
  min-height:1.5em;color:var(--on-dark-2)}
.form-note.is-ok{color:var(--accent-dk)}
.form-note.is-err{color:#F0B4A6}
/* M5-01 — the form now POSTs to /api/lead natively, so a missing app.js still
   delivers the lead instead of doing a GET that wrote the prospect's name and
   contact into the query string, the history and every downstream referrer log
   while showing a cleared form and sending nothing. This line is the visible
   half of that contract and disappears once app.js has wired the submit. */
.form-fallback{margin-top:var(--s-3);font-size:var(--fs-small);line-height:1.5;
  letter-spacing:var(--ls-small);color:var(--on-dark-3)}
.form.is-ready .form-fallback{display:none}
.form.is-busy button[type=submit]{opacity:.7;pointer-events:none}

/* The box-shadow paints the footer's colour into the over-scroll area below
   the document without adding a pixel of layout. Without it, rubber-banding at
   the bottom of a phone shows PAPER under a dark footer — the one place where
   a light background is visibly wrong. */
.foot{background:var(--dark);color:var(--on-dark-2);
  border-top:var(--hair-w) solid var(--hair-dark);padding-block:var(--s-10) var(--s-7);
  box-shadow:0 50vh 0 50vh var(--dark)}
.foot-top{display:grid;gap:clamp(34px,5vw,64px)}
@media (min-width:900px){ .foot-top{grid-template-columns:minmax(0,1.3fr) repeat(4,minmax(0,1fr))} }
.foot-mk{display:grid;gap:var(--s-4);align-content:start}
.foot-lk{display:flex;align-items:baseline;gap:10px}
.foot-lk .lk-gr{height:24px;width:auto;fill:var(--on-dark);transform:translateY(3px)}
.foot-lk .lk-la{height:12px;width:auto;fill:var(--on-dark-3)}
.foot-tag{font-size:var(--fs-small);line-height:1.55;letter-spacing:var(--ls-small);
  color:var(--on-dark-3);max-width:34ch}
.foot-col{display:grid;gap:11px;align-content:start}
.foot-col h4{font-size:var(--fs-small);letter-spacing:.02em;color:var(--on-dark);
  font-weight:var(--fw-strong);margin-bottom:2px}
.foot-col a,.foot-col span{font-size:var(--fs-small);line-height:1.45;
  letter-spacing:var(--ls-small);color:var(--on-dark-3)}
.foot-col a:hover{color:var(--on-dark)}
.foot-col a[aria-current="true"]{color:var(--on-dark);font-weight:var(--fw-strong)}
.foot-bot{margin-top:clamp(38px,6vh,72px);padding-top:var(--s-5);
  border-top:var(--hair-w) solid var(--hair-dark);
  display:flex;flex-wrap:wrap;gap:var(--s-3) var(--s-6);align-items:baseline;
  justify-content:space-between}
.foot-bot p,.foot-bot a,.foot-bot span{font-size:var(--fs-legal);
  letter-spacing:var(--ls-legal);color:var(--on-dark-3);max-width:none}
.foot-entity{flex:1 1 340px}
.foot-top-link{display:inline-flex;align-items:center;gap:6px;color:var(--on-dark-3)}
.foot-top-link:hover{color:var(--on-dark)}
.foot-top-link svg{width:10px;height:10px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;
  transition:transform var(--t-ui) var(--ease-out-quart)}
.foot-top-link:hover svg{transform:translateY(-2px)}


/* ─── 11 · TOOLTIPS (D14) ─────────────────────────────────────────────────
   A `?` affordance that works on HOVER, on TAP and on KEYBOARD FOCUS.
   Three rules decided the design:

   1  the page must be COMPLETE with every one of them closed. A tooltip may
      add depth; it may never carry a fact the page needs.
   2  OPENING IS CSS-FIRST — `:hover` on a fine pointer, `:focus-within`
      everywhere. So the control works with JavaScript disabled, which a
      JS-driven `aria-expanded` toggle alone would not. app.js adds a third
      opening mechanism (an explicit tap, for touch browsers that do not focus
      a button on tap), plus Escape and close-on-outside-tap.
   3  no external library, and no positioning library either: one class flips
      the panel to the right edge when it would otherwise leave the viewport. */
.tt{position:relative;display:inline}
.tt-t{border-bottom:1px dashed var(--hair-strong);padding-bottom:1px}
.dark .tt-t{border-bottom-color:var(--hair-dark-2)}
.tt-q{position:relative;display:inline-grid;place-items:center;vertical-align:1px;
  width:16px;height:16px;margin-left:4px;padding:0;border:0;cursor:pointer;
  border-radius:50%;background:var(--hair);color:var(--ink-3);
  font-size:11px;line-height:1;font-weight:var(--fw-emph);
  transition:background-color var(--t-instant) var(--ease-out-quart),
             color var(--t-instant) var(--ease-out-quart)}
.tt-q::before{content:"?"}
.tt-q:hover,.tt-q:focus-visible,.tt-q[aria-expanded="true"]{background:var(--accent);color:#fff}
.dark .tt-q{background:rgba(237,234,227,.16);color:var(--on-dark)}
.dark .tt-q:hover,.dark .tt-q:focus-visible,.dark .tt-q[aria-expanded="true"]{
  background:var(--accent-dk);color:var(--dark)}
/* 16 px of ink, 36 px of hit area. They are different measurements and only
   one of them is a design decision. */
.tt-q::after{content:"";position:absolute;left:50%;top:50%;width:36px;height:36px;
  transform:translate(-50%,-50%)}
.tt-b{position:absolute;left:0;top:calc(100% + 9px);z-index:70;
  width:max(240px,min(340px,72vw));
  background:var(--surface);color:var(--ink-2);border:var(--hair-w) solid var(--hair);
  border-radius:var(--r-sm);padding:14px 16px;
  font-size:var(--fs-small);line-height:1.52;letter-spacing:var(--ls-small);
  box-shadow:inset 0 1px 0 0 var(--hair-lit),var(--shadow-2);
  text-align:left;font-weight:400;
  opacity:0;visibility:hidden;transform:translateY(-4px) scale(.985);
  transform-origin:0 0;
  transition:opacity var(--t-tip) var(--ease-out-quart),
             transform var(--t-tip) var(--ease-out-quart),
             visibility 0s linear var(--t-tip)}
.tt:focus-within .tt-b,.tt.is-open .tt-b{
  opacity:1;visibility:visible;transform:none;transition-delay:0s}
@media (hover:hover) and (pointer:fine){
  .tt:hover .tt-b{opacity:1;visibility:visible;transform:none;transition-delay:0s}
}
/* An explicit dismissal outranks hover AND focus-within, and does it with the
   transition removed — see app.js §3. Without `transition:none` the 160 ms
   visibility delay survives the `!important`, so Escape left a fully
   transparent panel still occupying the layer for a sixth of a second. */
.tt.is-shut .tt-b{opacity:0!important;visibility:hidden!important;
  transform:translateY(-4px) scale(.985)!important;transition:none!important}
.tt-b.at-right{left:auto;right:0;transform-origin:100% 0}
.dark .tt-b{background:var(--dark-2);color:var(--on-dark-2);border-color:var(--hair-dark-2);
  box-shadow:inset 0 1px 0 0 var(--hair-dark-lit),var(--shadow-3)}
@media (max-width:479px){ .tt-b{width:min(340px,86vw)} }


/* ─── 12 · MOTION ────────────────────────────────────────────────────────
   V5 — reveals are MASKS aligned to the INK, not to the line box. The dead
   travel at the top of a clip-path inset equals the half-leading as a fraction
   of the line box: (line-height − cap-height) / 2 / line-height. For Manrope
   (cap ≈ 0.71 em) that is 15.9 % at lh 1.04 and 20.5 % at lh 1.10.

   Why a mask and not an opacity fade, mechanically: every visible pixel is at
   FULL opacity for the whole animation, so a mask reveal cannot produce an L2
   violation. And the hard rule that goes with it — `clip-path` on a PARAGRAPH
   clips the descenders of the last visible line during travel and looks like a
   rendering bug, so paragraphs get `translateY` and nothing else. */

/* above the fold: TIME-based, never IntersectionObserver-gated (L2).
   Budget arithmetic, because it is the thing Ihor caught shipping: the loader
   holds ~460 ms and fades over 260 ms; the hero reveal starts at 360 ms, i.e.
   BEFORE the loader has finished fading, and completes at 840 ms. The headline
   is therefore fully legible inside the 900 ms the loader is capped at, and
   nothing on this page is dim for a second. */
/* TWO animations per element, and the split is the whole point of L2.
   The OPACITY animation is 200 ms with ZERO delay for every element — so no
   text on this page is below full opacity for more than 200 ms after it starts
   painting, whatever its position in the stagger. The stagger lives entirely
   in the MASK and the TRANSFORM, which move fully-opaque, legible type.

   Written the obvious way — one keyframe set carrying both opacity and
   transform, staggered — the fourth element would have been transparent for
   360 + 210 + 480 = 1 050 ms. That is the defect Ihor caught on the live site
   («the hero faded in from invisible for ~1 s»), reintroduced by a stagger
   nobody would think to question. */
.en{animation:enterMask var(--enter) var(--ease-out-expo) var(--d,var(--enter-delay)) both,
              enterFade 200ms linear 0ms both}
.en-p{animation:enterUp var(--enter) var(--ease-out-expo) var(--d,var(--enter-delay)) both,
                enterFade 200ms linear 0ms both}
@keyframes enterFade{from{opacity:0}to{opacity:1}}
/* a 9 % inset, not 16 %: the V5 formula gives the half-leading for ONE line,
   and these headings run to four lines at 390 px, where 16 % of the block is
   most of the first line rather than the space above it. */
@keyframes enterMask{
  from{clip-path:inset(9% 0 0 0);transform:translateY(.26em)}
  to  {clip-path:inset(0 0 0 0);transform:none}}
@keyframes enterUp{
  from{transform:translateY(10px)}
  to  {transform:none}}
.en-1{--d:calc(var(--enter-delay) + 0ms)}
.en-2{--d:calc(var(--enter-delay) + 70ms)}
.en-3{--d:calc(var(--enter-delay) + 140ms)}
.en-4{--d:calc(var(--enter-delay) + 210ms)}

/* below the fold: scroll-driven, so the entrance is a property of where the
   reader is rather than of a timer. Support is ~84 % global; the other 16 %
   get the AUTHORED state, which is the finished page — not a degraded one.
   Costs negative bytes: it replaces an IntersectionObserver. */
@media (prefers-reduced-motion:no-preference){
  @supports (animation-timeline:view()){
    .rv{animation:rvMask linear both;animation-timeline:view();
      animation-range:cover 2% cover 20%}
    .rv-p{animation:rvUp linear both;animation-timeline:view();
      animation-range:cover 2% cover 22%}
  }
}
@keyframes rvMask{
  from{clip-path:inset(20% 0 0 0);transform:translateY(.26em)}
  to  {clip-path:inset(0 0 0 0);transform:none}}
@keyframes rvUp{from{opacity:.001;transform:translateY(14px)}to{opacity:1;transform:none}}

/* L4 + L6. The authored state IS the final visible state everywhere on this
   page, so removing every animation leaves a complete, correct document —
   which is the whole test. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-delay:0s!important;
    animation-iteration-count:1!important;transition-duration:.001ms!important}
  .nav-prog{display:none}
}
html.still{scroll-behavior:auto}
html.still *,html.still *::before,html.still *::after{
  animation:none!important;transition:none!important;scroll-behavior:auto!important}
html.still .nav-prog{display:none}
html.still .load{display:none}


/* ─── 13 · PRINT ─────────────────────────────────────────────────────────── */
@media print{
  .nav,.load,.bgsw,.wstage,.wtabs,.foot-top-link,.bg{display:none!important}
  body{background:#fff;color:#000}
  .dark{background:#fff;color:#000}
  .dark h1,.dark h2,.dark h3,.dark p{color:#000}
}
