/* ═══════════════════════════════════════════════════════════════════════════════════════════
   hetoimos — styles.css

   The study (docs/REFERENCES.md, ten sites measured 2026-08-07) produced four numbers, and this
   file is built to hit them rather than to a taste:

     · under 200 words above the fold, under 400 on the landing — the tier's median is 110 DOM
       words and the old hetoimos site carried 1 463
     · a near-black ground — seven of the nine grounds measured were black or near-black, and the
       two light ones were the two with no 3D on them
     · a GROTESQUE display face, never humanist — Aeonik, Neue Montreal, Mona Sans, URW DIN, Fort
       across the set, and not one geometric-humanist face. Manrope, which the old site ships, is
       exactly that, and it is a large part of why that page reads as a competent template
     · one object, full bleed, and the page composed around it

   ── THE TYPE IS A SYSTEM STACK, ON PURPOSE, FOR NOW ──────────────────────────────────────────
   No webfont is loaded. That is a decision with a date on it, not an oversight: the study's weight
   budget is 700 kB total, a licensed grotesque is 30–90 kB per weight per subset, and this site
   needs Cyrillic AND latin-ext. The stack below picks the best grotesque already on the reader's
   machine — Inter on most Linux, Segoe UI Variable on Windows 11, SF Pro on Apple — all of which
   are grotesques with full Cyrillic. It looks intentional at every size and it costs zero bytes.
   When a face is chosen and subsetted, it replaces --display and --text here and nothing else
   changes.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

:root {
  --void: #07090C;
  --ground: #0B0E13;
  --ink: #F2EEE4;
  --ink-2: rgba(242, 238, 228, .74);
  --ink-3: rgba(242, 238, 228, .52);
  --hair: rgba(242, 238, 228, .13);
  --hair-2: rgba(242, 238, 228, .26);
  --beige: #D8CDAF;

  --display: "Segoe UI Variable Display", "SF Pro Display", Inter, "Helvetica Neue", Arial, sans-serif;
  --text: "Segoe UI Variable Text", "SF Pro Text", Inter, "Helvetica Neue", Arial, sans-serif;
  --mono: "Cascadia Mono", "SF Mono", "JetBrains Mono", ui-monospace, "Consolas", monospace;

  /* ⚠ THE GREEK HAS ITS OWN STACK, AND THE REASON IS ONE CODEPOINT.
     The word was set as ἕτοιμος — POLYTONIC, with U+1F15, epsilon with dasia and
     oxia. Almost no interface font covers Polytonic Greek, so that one letter fell through to
     whatever fallback the system had, at a different size, a different weight and usually a serif.
     Photographed across Bahnschrift, Franklin Gothic, Segoe UI and Arial: the epsilon is a foreign
     letterform in ALL FOUR. That is what "иголистый шрифт, что за помойка" was — not the stack, one
     character.
     The word is now set in CAPS, ΕΤΟΙΜΟΣ, which carries no diacritic at all, so there is
     nothing that can fall back. It also reads better at the size this thing is drawn at. The stack
     is grotesques that all cover Greek capitals; Bahnschrift is the DIN-like one and is first
     because the study found URW DIN and Neue Montreal in this tier and nothing humanist. */
  --display-gr: Bahnschrift, "Franklin Gothic Medium", "Helvetica Neue", "Segoe UI", Arial, sans-serif;

  --fs-h1: clamp(2.15rem, 5.4vw, 4.1rem);
  --fs-h2: clamp(1.7rem, 4.2vw, 3.1rem);
  --fs-h3: clamp(1.05rem, 1.6vw, 1.25rem);
  --fs-lede: clamp(1.02rem, 1.45vw, 1.3rem);
  --fs-body: clamp(0.96rem, 1.1vw, 1.06rem);
  --fs-xs: 0.72rem;

  --gutter: clamp(1.15rem, 5vw, 5rem);
  --bar-h: 4.25rem;
  --sec-y: clamp(5.5rem, 14vh, 11rem);
  --measure: 34ch;

  color-scheme: dark;
}

* { box-sizing: border-box; }

html {
  /* ⚠ THE GROUND IS DECLARED HERE AND NOWHERE ELSE. The canvas clears to the same value, so a
     machine with no WebGL2 gets a page that is dark and complete rather than a hole where a render
     should be — src/main.js adds `no-gl` and stops, and nothing below cares. */
  background: var(--void);
  /* The native smooth scroll is turned OFF once src/smooth.js is running: two easing systems on the
     same input fight each other, and the result overshoots — measured, a wheel that landed the real
     position at 584 px had the content still travelling toward 948. The module handles anchors
     itself. */
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
html.smooth { scroll-behavior: auto; }

body {
  margin: 0;
  background: transparent;
  color: var(--ink-2);
  font-family: var(--text);
  font-size: var(--fs-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

h1, h2, h3 { margin: 0; font-family: var(--display); color: var(--ink); font-weight: 500; }
h1 { font-size: var(--fs-h1); line-height: 1.02; letter-spacing: -.035em; }
h2 { font-size: var(--fs-h2); line-height: 1.08; letter-spacing: -.028em; }
h3 { font-size: var(--fs-h3); line-height: 1.25; letter-spacing: -.012em; }
p  { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; }

/* The render, behind everything, out of every hit test. */
#stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: -1;
  pointer-events: none;
}
.no-gl #stage { display: none; }

/* ── the bar ───────────────────────────────────────────────────────────────────────────────
   Two elements: the lockup and one action. The study is unambiguous about this — Lando Norris,
   Awwwards Site of the Year, puts a wordmark top-left and a single accent CTA top-right and
   nothing else in the corner. Everything the old site kept up there (six section links, a
   language switch, a background picker) is chrome a prospect has to read past. */
.bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 10;
  height: var(--bar-h);
  padding: 0 var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  /* Glass, and only here. backdrop-filter blurs everything under an element every frame it is on
     screen; over a canvas that is itself redrawing, one full-bleed blurred rectangle is the whole
     screen composited twice. The bar is 68px tall, so it is affordable and it is the only place. */
  /* ⚠ NO backdrop-filter ON THE BAR. It is the one element on screen 100% of the time, and a
     backdrop filter re-blurs everything under it on EVERY FRAME — over a canvas that is itself
     redrawing, that is the whole viewport width composited twice, forever, for a 60px strip. The
     buttons keep theirs because they are small and because a control is where the material reads;
     the bar gets a flat translucent fill, which at this opacity over a near-black field is
     visually the same thing and costs one paint. */
  background: rgba(7, 9, 12, .72);
  border-bottom: 1px solid rgba(255, 255, 255, .09);
  box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, .3);
}
.bar-mk { display: flex; align-items: baseline; gap: .6rem; text-decoration: none; }
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.bar-gr { height: 1.35rem; width: auto; fill: var(--beige); display: block; }
.bar-la {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  letter-spacing: .34em;
  text-indent: .34em;
  text-transform: lowercase;
  color: var(--ink-3);
}
.bar-cta {
  font-size: .84rem;
  letter-spacing: .01em;
  text-decoration: none;
  color: var(--ink);
  padding: .56rem 1rem;
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 999px;
  background: rgba(255, 255, 255, .09);
  backdrop-filter: saturate(1.8) blur(24px);
  -webkit-backdrop-filter: saturate(1.8) blur(24px);
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, .2), inset 0 -1px 0 0 rgba(0, 0, 0, .3);
  transition: background .2s ease, border-color .2s ease;
  white-space: nowrap;
}
.bar-cta:hover { background: rgba(255, 255, 255, .13); border-color: rgba(255, 255, 255, .34); }

/* ── sections ─────────────────────────────────────────────────────────────────────────────
   A plain column. No pinning, no scroll hijack, no snap — which is what lets src/main.js measure
   the section tops ONCE on resize and never inside the frame. The deck model 01-gems uses is
   right for a seven-screen object study and wrong for a page that has to be scanned and bought
   from. */
main { padding-top: var(--bar-h); }
.s {
  min-height: 42svh;
  padding: var(--sec-y) var(--gutter);
  max-width: 78rem;
  margin: 0 auto;
}

.hero {
  min-height: calc(100svh - var(--bar-h));
  display: grid;
  align-content: center;
  position: relative;
}
/* The <br> in the markup is the intended break. A 30ch measure fought it and wrapped the
   headline to four lines, two of which went under the bar — the hero is exactly one screen tall,
   so a headline that grows sideways has nowhere to grow into. `min-content` on the heading lets
   the authored break stand and the lede keeps its own reading measure. */
.hero .copy { max-width: min(44rem, 100%); }
/* `ch` is the width of a ZERO in the current font, and the current font here is 5.6rem of display
   grotesque — so 15ch was 15 zeros wide at 90px, which the hero's own grid then had to fit beside
   the mark. It wrapped "Сайт вашого бізнесу." to three lines and the authored break made a fourth.
   An absolute measure is the honest unit when the type is this large: the headline gets a column,
   not a character count. */
.hero h1 { max-width: 22rem; }
.lede {
  margin-top: 1.15rem;
  max-width: var(--measure);
  font-size: var(--fs-lede);
  color: var(--ink-2);
  /* The mark is behind the copy at full exposure on this one screen. A shadow tracks the glyph,
     where a plate would draw a rectangle over the only object on the page. */
  text-shadow: 0 1px 18px rgba(7, 9, 12, .92), 0 0 46px rgba(7, 9, 12, .6);
}
.hero h1 { text-shadow: 0 2px 26px rgba(7, 9, 12, .9), 0 0 60px rgba(7, 9, 12, .55); }

.acts { margin-top: 2rem; display: flex; flex-wrap: wrap; gap: .7rem; }
/* ── GLASS ────────────────────────────────────────────────────────────────────────────────
   «Кнопки тоже должны этим эффектом обладать.» What Apple's material is, taken apart, because
   every one of the four parts is doing work and leaving any of them out is what makes a glass
   button look like a grey rectangle:

     1. a HEAVY blur          28px, not 8. A light blur reads as a translucent fill; a heavy one
                              reads as thickness, because the eye stops being able to resolve what
                              is behind it and only sees that something is.
     2. a SATURATION LIFT     1.8. This is the part everybody omits. Blurring averages colour
                              toward grey, so a blurred pane over a coloured ground comes out
                              duller than the ground — pushing saturation back up is what keeps
                              the pane feeling like glass over colour rather than like fog.
     3. a BRIGHTNESS FLOOR    the ground here is near-black, so a pane with no fill of its own is
                              invisible. 9% white is the least that reads as a surface.
     4. TWO EDGES, not one    a bright inset line along the top and a dark one along the bottom.
                              That pair is what makes a flat rectangle read as a solid with a
                              thickness catching a light from above, and it costs one box-shadow.

   ⚠ AND IT IS SPENT ONLY ON CONTROLS. Each backdrop-filter element forces its own backdrop root
   and re-blurs everything under it every frame it is on screen, over a canvas that is already
   redrawing. A full-bleed blurred panel is the whole screen composited twice. Buttons and the bar,
   nothing else. */
.btn {
  display: inline-flex;
  align-items: center;
  min-height: 2.85rem;
  padding: 0 1.35rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .12);
  background: rgba(255, 255, 255, .07);
  backdrop-filter: saturate(1.8) blur(28px);
  -webkit-backdrop-filter: saturate(1.8) blur(28px);
  color: var(--ink);
  text-decoration: none;
  font-size: .92rem;
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, .15),
              inset 0 -1px 0 0 rgba(0, 0, 0, .26),
              0 6px 20px -14px rgba(0, 0, 0, .7);
  transition: background .22s ease, border-color .22s ease, transform .22s var(--ease-out, cubic-bezier(.19,1,.22,1));
}
.btn:hover {
  background: rgba(255, 255, 255, .17);
  border-color: rgba(255, 255, 255, .32);
  transform: translateY(-1px);
}
.btn:active { transform: translateY(0); }
/* The secondary keeps the glass and loses the fill — a rule you can see round a word is a control,
   a filled pill is a panel. It still blurs, or the two buttons read as two design systems. */
.btn-q {
  background: rgba(255, 255, 255, .035);
  border-color: rgba(255, 255, 255, .11);
  color: var(--ink-2);
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, .10);
}
.btn-q:hover { background: rgba(255, 255, 255, .09); color: var(--ink); }

.cue {
  position: absolute;
  left: var(--gutter);
  bottom: 2rem;
  margin: 0;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  letter-spacing: .34em;
  text-indent: .34em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: grid;
  gap: .55rem;
  justify-items: start;
}
.cue::after {
  content: "";
  width: .5rem; height: .5rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  rotate: 45deg;
  animation: fall 2.1s cubic-bezier(.4, 0, .2, 1) infinite;
}
@keyframes fall {
  0%   { translate: 0 -.22rem; opacity: 0; }
  30%  { translate: 0 0;       opacity: 1; }
  70%  { translate: 0 .28rem;  opacity: .5; }
  100% { translate: 0 .5rem;   opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .cue::after { animation: none; opacity: .7; } }

.eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 1.1rem;
}
.eyebrow .num { color: var(--beige); margin-right: .9rem; }

/* ── the steps ───────────────────────────────────────────────────────────────────────────── */
.steps {
  margin-top: 2.6rem;
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  background: var(--hair);
  border: 1px solid var(--hair);
}
.steps li { background: rgba(255, 255, 255, .045); box-shadow: inset 0 1px 0 0 rgba(255,255,255,.07); padding: 1.6rem 1.5rem 1.8rem; display: grid; align-content: start; gap: .55rem; }
.steps .k { font-family: var(--mono); font-size: var(--fs-xs); letter-spacing: .2em; color: var(--beige); }
.steps p { max-width: 32ch; }
.note { color: var(--ink-3); font-family: var(--mono); font-size: var(--fs-xs); letter-spacing: .06em; margin-top: .35rem; }

/* ── the price ───────────────────────────────────────────────────────────────────────────── */
.tiers {
  margin-top: 2.6rem;
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  background: var(--hair);
  border: 1px solid var(--hair);
}
.tier { background: rgba(255, 255, 255, .045); box-shadow: inset 0 1px 0 0 rgba(255,255,255,.07); padding: 1.7rem 1.5rem 2rem; display: grid; align-content: start; gap: .5rem; }
.tier-hi { background: rgba(216, 205, 175, .10); box-shadow: inset 0 1px 0 0 rgba(255,255,255,.13); }
.price { font-family: var(--display); font-size: clamp(1.8rem, 3vw, 2.5rem); color: var(--ink); letter-spacing: -.03em; line-height: 1.05; }
.price span { font-size: .6em; color: var(--ink-3); }
.dur { font-family: var(--mono); font-size: var(--fs-xs); letter-spacing: .2em; text-transform: uppercase; color: var(--beige); }
.tier p:last-child { color: var(--ink-2); max-width: 28ch; }
.fine { margin-top: 1.4rem; color: var(--ink-3); max-width: 52ch; font-size: .92rem; }

/* ── the works ───────────────────────────────────────────────────────────────────────────── */
.works { margin-top: 2.2rem; border-top: 1px solid var(--hair); }
.works a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.15rem .2rem;
  border-bottom: 1px solid var(--hair);
  text-decoration: none;
  transition: padding-left .22s var(--ease, cubic-bezier(.19, 1, .22, 1)), color .18s ease;
}
.works a:hover { padding-left: .9rem; }
.works span { font-family: var(--display); font-size: clamp(1.15rem, 2.2vw, 1.7rem); color: var(--ink); letter-spacing: -.02em; }
.works em { font-style: normal; color: var(--ink-3); font-size: .92rem; text-align: right; }

/* ── contact ─────────────────────────────────────────────────────────────────────────────── */
#contact .lede { margin-bottom: 2rem; }
.meta { margin: 0 0 2rem; display: grid; gap: 1px; background: var(--hair); border: 1px solid var(--hair); max-width: 44rem; }
.meta > div { background: rgba(255, 255, 255, .04); padding: .9rem 1.1rem; display: flex; justify-content: space-between; gap: 1.5rem; align-items: baseline; }
.meta dt { color: var(--ink-3); font-family: var(--mono); font-size: var(--fs-xs); letter-spacing: .16em; text-transform: uppercase; }
.meta dd { margin: 0; color: var(--ink); text-align: right; }
.meta a { text-decoration: none; border-bottom: 1px solid var(--hair-2); }

/* ── the foot ────────────────────────────────────────────────────────────────────────────── */
.foot {
  padding: 2.5rem var(--gutter) calc(2.5rem + env(safe-area-inset-bottom));
  border-top: 1px solid var(--hair);
  display: flex;
  align-items: baseline;
  gap: 1.2rem;
  flex-wrap: wrap;
  color: var(--ink-3);
  font-size: .88rem;
}
.foot-mk { height: 1.15rem; width: auto; fill: var(--beige); display: block; }
.foot-c { margin-left: auto; }

:focus-visible { outline: 2px solid var(--beige); outline-offset: 3px; border-radius: 3px; }

@media (max-width: 640px) {
  :root { --bar-h: 3.6rem; }
  /* The copy takes the lower half; src/main.js puts the mark in the upper third. See the note
     there — these two rules are one decision and neither is safe to change alone. */
  .hero { align-content: end; padding-bottom: 5.5rem; }
  .bar-la { display: none; }
  .hero .copy { max-width: none; }
  .meta > div { flex-direction: column; gap: .2rem; }
  .meta dd { text-align: left; }
}


/* ── the smooth-scroll wrapper ────────────────────────────────────────────────────────────────
   src/smooth.js takes the content out of flow and translates it toward an eased position while a
   spacer of the same height keeps the real scrollbar real. Both rules only apply once that module
   has confirmed it is running — the class is added by JS, so a page with no JavaScript, or one
   where the module threw, scrolls natively and nothing here fires.

   ⚠ NOTHING INSIDE #scroller MAY BE `position: sticky`. A translated ancestor is a containing
   block, so a sticky child sticks to the moving box instead of to the viewport and simply stops
   working. The bar is `fixed` and deliberately lives OUTSIDE the wrapper. */
/* ⚠ NO overflow ON body. THIS LINE SAID overflow: clip AND IT STOPPED THE PAGE SCROLLING.
   When the root element overflow is visible, the BODY overflow is propagated to the viewport and
   the root uses visible instead. So overflow:clip on body clips the VIEWPORT — the document still
   has 3823 px of height and a scrollHeight to prove it, and it simply cannot be scrolled. It also
   passed a test, because window.scrollTo() moves the scroll position programmatically whether or
   not a reader could. The only honest check is a real wheel event or a real hand.
   Nothing is needed here: the wrapper is fixed and out of flow, the spacer carries the height, and
   the page scrolls natively. */
html.smooth #scroller {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  will-change: transform;
}
#scroll-spacer { width: 1px; pointer-events: none; }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   THE LIVE PORTFOLIO — the idea, in CSS
   src/works.js has the reasoning and the cost. This is the material.

   The frame is rendered at 1440 and SCALED, never sized down: an iframe 480 px wide shows that
   site's PHONE layout, which is not what a prospect is being shown. --frame-scale is written by
   works.js on resize.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */
.works { margin-top: 2.6rem; display: grid; gap: clamp(1.6rem, 4vw, 3rem); }

/* Two columns again, at Ihor's call: «меньше размер превью, верни как был». Full width made each
   site legible and made the section enormous — five browser windows stacked is a page of other
   people's sites, and this one is about ours. The frame still renders at 1280 rather than the
   original 1440, so it is sharper inside the smaller box than the first version was. */
.work {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: clamp(1.2rem, 3vw, 2.4rem);
  align-items: center;
  /* ⚠ NO content-visibility HERE, AND NO PAINT GATING EITHER — BOTH WERE TRIED AND BOTH ARE OUT.
     content-visibility: auto makes a skipped card measure its placeholder size instead of its real
     height, so the document height MOVES as cards enter and leave; on a page whose smooth scroller
     has a measured spacer that is a breathing scrollbar and drifting positions. Hiding far iframes
     was then aimed at a slowdown that turned out to be the harness measuring a background window
     (see the correction at the top of src/works.js). There is nothing to gate: at most one live
     document exists at a time and the rest are static images. */
}
/* An even card puts the frame on the right, and the COLUMN WIDTHS have to swap with it. Ordering
   alone left the frame in the 1fr column, so every second preview was 256 px tall against 410 —
   measured, and visible as a page of alternating big and small screenshots. */
.work:nth-child(even) { grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr); }
.work:nth-child(even) .work-frame { order: 2; }

/* ⚠ THE BASE RULE. It was destroyed once by an edit that sliced from the first occurrence of
   ".work-frame {" in the file — which is inside the nth-child selector above, not here — and every
   declaration below ended up scoped to even cards only. Odd cards then had NO HEIGHT: the frame is
   an empty box whose height comes entirely from aspect-ratio, so losing that rule collapses it to
   zero and the section renders as a stack of black slivers. Measured: 656 x 0.
   If this block is ever moved, check that the base selector still exists on its own. */
.work-frame {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, .10);
  background: rgba(255, 255, 255, .035);
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, .10), 0 24px 60px -30px rgba(0, 0, 0, .9);
  transition: transform .5s cubic-bezier(.19, 1, .22, 1), box-shadow .5s ease;
  cursor: pointer;
}

/* THE SCREENSHOT is the resting state and it is REAL — the same images the studio site ships. A
   card nobody touches is still evidence, which a grey placeholder is not.
   Blurred and dimmed behind the glass; sharp when this is the card being read, and fully lit when
   the live site is running on top of it. */
.work-shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  filter: blur(5px) saturate(.72) brightness(.58);
  transition: filter .55s cubic-bezier(.19, 1, .22, 1);
}
.work.is-focus .work-shot { filter: blur(0) saturate(1) brightness(.9); }
.work:hover .work-shot { filter: blur(0) saturate(1) brightness(1); }
.work:hover .work-frame { transform: translateY(-2px); }

/* The live document, when there is one. Rendered wide and scaled — see the note in works.js. */
.work-frame iframe {
  position: absolute;
  top: 0; left: 0;
  width: 1800px;
  height: 1060px;
  border: 0;
  transform-origin: 0 0;
  transform: scale(var(--frame-scale, .4));
  opacity: 0;
  z-index: 1;
  transition: opacity .45s ease;
}
.work.is-live .work-frame iframe { opacity: 1; }

/* The whole card answers the click: it lifts, its edge lights, and a ring runs out once. */
.work.is-active .work-frame {
  outline: 1px solid rgba(255, 255, 255, .34);
  outline-offset: 3px;
  transform: translateY(-3px);
  animation: work-take .5s cubic-bezier(.19, 1, .22, 1);
}
@keyframes work-take {
  0%   { box-shadow: inset 0 1px 0 0 rgba(255,255,255,.10), 0 0 0 0 rgba(255,255,255,.30); }
  55%  { box-shadow: inset 0 1px 0 0 rgba(255,255,255,.10), 0 0 0 12px rgba(255,255,255,.05); }
  100% { box-shadow: inset 0 1px 0 0 rgba(255,255,255,.10), 0 0 0 26px rgba(255,255,255,0); }
}
@media (prefers-reduced-motion: reduce) {
  .work.is-active .work-frame { animation: none; transform: none; }
}

/* ⚠ THERE IS NO HINT ELEMENT ANY MORE, AND THAT IS THE FIX RATHER THAN A SMALLER ONE.
   It was resized three times and read as a giant slab every time, because it lived inside a box
   whose filter and whose stacking kept undoing the type scale. Ihor, on the third: «эта кнопка
   гигантская натиснет, я тебе уже пять раз сказал». A control that needs a caption explaining it
   is a control that is wrong; the cards start themselves now, so there is nothing to caption. */

.work-meta h3 { font-size: clamp(1.2rem, 2.2vw, 1.7rem); letter-spacing: -.024em; }
.work-meta p { margin-top: .5rem; color: var(--ink-2); max-width: 34ch; }
.work-go {
  display: inline-block;
  margin-top: .9rem;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  letter-spacing: .1em;
  color: var(--ink-3);
  text-decoration: none;
  border-bottom: 1px solid var(--hair);
  padding-bottom: 2px;
}
.work-go:hover { color: var(--ink); border-color: var(--hair-2); }

/* ── the sections the old site had and this one did not ─────────────────────────────────── */
.feats { margin-top: .9rem; display: grid; gap: .45rem; }
.feats li {
  position: relative;
  padding-left: 1.1rem;
  color: var(--ink-2);
  font-size: .95rem;
}
.feats li::before {
  content: "";
  position: absolute;
  left: 0; top: .62em;
  width: .38rem; height: .38rem;
  border-radius: 50%;
  background: var(--beige);
  opacity: .55;
}

.grid6, .care {
  margin-top: 2.4rem;
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  background: var(--hair);
  border: 1px solid var(--hair);
}
.grid6 li, .care li {
  background: rgba(255, 255, 255, .045);
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, .07);
  padding: 1.5rem 1.4rem 1.7rem;
  display: grid;
  align-content: start;
  gap: .5rem;
}
.grid6 p, .care p { color: var(--ink-2); max-width: 32ch; }

.rows { margin-top: 2.2rem; border-top: 1px solid var(--hair); }
.rows > div {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: 1.5rem;
  padding: 1.35rem .2rem;
  border-bottom: 1px solid var(--hair);
  align-items: start;
}
.rows p { color: var(--ink-2); max-width: 56ch; }

.qa { margin: 2.2rem 0 0; border-top: 1px solid var(--hair); }
.qa > div {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: 1.5rem;
  padding: 1.25rem .2rem;
  border-bottom: 1px solid var(--hair);
  align-items: start;
}
.qa dt { font-family: var(--display); color: var(--ink); font-size: 1.02rem; letter-spacing: -.012em; }
.qa dd { margin: 0; color: var(--ink-2); max-width: 56ch; }

@media (max-width: 860px) {
  .work { grid-template-columns: 1fr; }
  .work:nth-child(even) .work-frame { order: 0; }
  .rows > div, .qa > div { grid-template-columns: 1fr; gap: .4rem; }
}

/* ── the inner pages ──────────────────────────────────────────────────────────────────────────
   Generated by tools/build-het.mjs from src/pages.js. The shell is cut out of index.html, so
   everything below is only what an inner page needs that the home page does not. */

/* The nav exists on inner pages ONLY. A landing with six links in the corner is six things a
   prospect reads past before the one claim; a reader who has already gone one level in has chosen
   to look around, and there the links are a service. */
.bar-nav { display: flex; gap: clamp(.9rem, 2vw, 1.7rem); margin-left: auto; margin-right: 1.2rem; }
.bar-nav a, .bar-here {
  font-size: .84rem;
  text-decoration: none;
  color: var(--ink-3);
  white-space: nowrap;
  transition: color .18s ease;
}
.bar-nav a:hover { color: var(--ink); }
.bar-here { color: var(--ink); border-bottom: 1px solid var(--beige); padding-bottom: 2px; }

/* An inner hero is half a screen, not a whole one: the reader arrived on purpose and the thing they
   came for should not be one scroll below the fold. */
.hero-page { min-height: 52svh; }
.hero-page h1 { max-width: 18ch; }

/* ── the stack ────────────────────────────────────────────────────────────────────────────────
   Twenty-one marks in their own brand colours on a near-black ground. The colours are the point —
   a row of monochrome logos is a decoration, and a row in the real colours is a claim you can check
   against what you already recognise. */
.stack {
  margin-top: 2.4rem;
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  background: var(--hair);
  border: 1px solid var(--hair);
}
.stack li {
  background: rgba(255, 255, 255, .04);
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, .06);
  padding: .95rem 1rem;
  display: flex;
  align-items: center;
  gap: .75rem;
  min-width: 0;
}
.stack-i {
  flex: 0 0 auto;
  width: 1.35rem;
  height: 1.35rem;
  display: grid;
  place-items: center;
  /* The marks are drawn at their own saturation and would shout on a page this dark, so they are
     held back a little and come up to full on hover. The row reads as a texture until you look at
     one of them, which is the right order for a list nobody reads top to bottom. */
  opacity: .82;
  transition: opacity .2s ease;
}
.stack li:hover .stack-i { opacity: 1; }
.stack-i svg { width: 100%; height: 100%; display: block; }
.stack b {
  font-weight: 500;
  font-size: .9rem;
  color: var(--ink-2);
  letter-spacing: -.004em;
  overflow-wrap: anywhere;
}
.stack li:hover b { color: var(--ink); }
