/* ============================================================
   INTERFACET® — interface intelligence studio
   Palette + typography carried over from the Interface
   Intelligence concept. One committed dark world; the footer
   is its acid inversion.

   Two rules this sheet is built on:
   · anything that must fit the screen is clamped by height as
     well as width — min(Xvw, Yvh)
   · separation is done with space, not with rules; a 1px line
     appears only where it carries meaning
   ============================================================ */

:root{
  /* ground */
  --ink:#090A0B;
  --ink-1:#0D0F11;
  --ink-2:#121417;

  /* ink on paper */
  --paper:#F0F1EB;
  --muted:rgba(240,241,235,.62);
  --dim:rgba(240,241,235,.40);
  --line:rgba(240,241,235,.13);
  --line-2:rgba(240,241,235,.06);
  --tint:rgba(240,241,235,.05);

  /* signal */
  --lime:#C8FF38;    /* found / fixed */
  --alert:#FF2E88;   /* loss / friction */

  /* type */
  --sans:"Onest","Inter","Helvetica Neue",Arial,sans-serif;
  --serif:"Playfair Display","Iowan Old Style",Baskerville,"Times New Roman",serif;
  --mono:"JetBrains Mono","SFMono-Regular","Roboto Mono",Consolas,monospace;

  --t-hero:clamp(34px,min(6.4vw,8.8vh),106px);
  --t-h2:clamp(29px,4.6vw,74px);
  --t-body:clamp(14px,1.02vw,16px);
  --t-lab:clamp(9px,.66vw,10px);

  /* rhythm */
  --pad:clamp(18px,3.2vw,58px);
  /* One content measure for the whole site. The case pages already set their
     column with --col; the home, cases and privacy pages used to run to the
     edge, so at 1920 their text started 282px left of a case page’s and the
     nav sat left of both. --gut is the side padding that also centres the
     column: it is --pad until the viewport passes --col, then it grows.
     Every band that carries content uses it; the marquee, the hero artboard
     and the intro curtain stay full-bleed on purpose. */
  --col:1240px;
  --gut:max(var(--pad),(100% - var(--col)) / 2);
  --sec:clamp(76px,8.4vw,140px);
  --nav-h:78px;
  --strip-h:52px;

  /* motion */
  --ez:cubic-bezier(.16,1,.3,1);
  --ez2:cubic-bezier(.65,0,.35,1);
  --ez3:cubic-bezier(.76,0,.24,1);

  /* runtime */
  --cx:50vw; --cy:50vh; --sp:0;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
html.js{scroll-behavior:smooth}
body{
  margin:0;background:var(--ink);color:var(--paper);
  font-family:var(--sans);font-size:var(--t-body);line-height:1.55;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
html.js body.is-locked{overflow:hidden;height:100svh}
h1,h2,h3{margin:0;font-weight:500;letter-spacing:-.05em;line-height:.94;text-wrap:balance}
p{margin:0}
ul,ol{margin:0;padding:0;list-style:none}
a{color:inherit;text-decoration:none}
img{display:block;max-width:100%;height:auto}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;text-align:left}
::selection{background:var(--lime);color:var(--ink)}
:focus-visible{outline:2px solid var(--lime);outline-offset:3px;border-radius:2px}
em{font-family:var(--serif);font-style:italic;font-weight:400;letter-spacing:-.012em}

body::after{
  content:"";position:fixed;inset:0;z-index:990;pointer-events:none;opacity:.15;
  mix-blend-mode:soft-light;background-size:7px 7px;
  background-image:repeating-radial-gradient(circle at 17% 32%,transparent 0,rgba(255,255,255,.06) 1px,transparent 2px 4px);
}

/* ---------------- atoms ---------------- */
.lab,.idx{
  font-family:var(--mono);font-size:var(--t-lab);font-weight:400;
  letter-spacing:.17em;text-transform:uppercase;color:var(--dim);
}
.lab-lime{color:var(--lime)}
.good{color:var(--lime)}
.dot{
  width:7px;height:7px;border-radius:50%;background:var(--lime);flex:none;
  box-shadow:0 0 16px rgba(200,255,56,.7);animation:pulseDot 2.6s var(--ez) infinite;
}
@keyframes pulseDot{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.68)}}

/* soft filled chips replace the outlined pills — fewer rectangles */
.case-tags li,.srv-tags li{
  font-family:var(--mono);font-size:10px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--muted);background:var(--tint);border-radius:2px;padding:7px 10px;
}

.sec{padding:var(--sec) var(--gut);position:relative}
section[id],footer[id]{scroll-margin-top:calc(var(--nav-h) + 14px)}
/* A .sec already opens with --sec of padding, so parking the anchor above
   its box left the nav plus 121px of nothing before the first word. Take
   that padding back out of the offset and the heading lands a constant
   24px under the bar at every width, because --sec cancels itself:
   gap = (nav-h + 24 - sec) + sec - nav-h. The hero and the footer are not
   .sec and keep the rule above. */
.sec[id]{scroll-margin-top:calc(var(--nav-h) + 24px - var(--sec))}
.sec-head{
  display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.75fr);
  gap:clamp(22px,4vw,72px);align-items:end;margin-bottom:clamp(36px,4.6vw,76px);
}
.sec-head h2{font-size:var(--t-h2);max-width:16ch}
/* Cyrillic sets wider per character, and `ch` is measured on "0", so a
   16ch measure holds noticeably fewer Russian words and these headings
   grow a line or two. Widen the measure rather than shrink the type —
   the display size is the voice of these sections. */
html[lang="ru"] .sec-head h2{max-width:19ch}
.sec-head > p{color:var(--muted);max-width:44ch;padding-bottom:.6em}
.sec-head-cta > .btn{justify-self:end}

/* /cases in Russian: «Что мы спроектировали» runs a word longer than the
   English line and does not fit the heading column above 1440, so it broke
   after «мы» and left the italic «и» stranded at the end of line two. Neither
   lever reaches on its own: the column is capped by --col and tops out near
   740px, short of the 788 the line needs at 74px. So widen the measure and
   stop the type growing past 66. Above 980 only — below that the head is
   already a single column and the button sits under it. */
@media (min-width:981px){
  html[lang="ru"] .work-all .sec-head{grid-template-columns:minmax(0,1.25fr) minmax(0,.75fr)}
  html[lang="ru"] .work-all .sec-head h2{font-size:min(var(--t-h2),66px)}
}
.eyebrow{display:flex;align-items:center;gap:10px;margin-bottom:24px}

/* ---------------- buttons ---------------- */
/* the fill runs left → right and the element never moves, so the
   pointer cannot chase it the way the old magnetic pull made it */
.btn{
  position:relative;display:inline-flex;align-items:center;justify-content:space-between;gap:24px;
  padding:15px 20px;min-width:206px;color:var(--paper);background:var(--tint);
  font-family:var(--mono);font-size:11px;letter-spacing:.13em;text-transform:uppercase;
  overflow:hidden;isolation:isolate;transition:color .45s var(--ez);
}
.btn span,.btn i{position:relative;z-index:2}
/* the arrow is a vector so it renders identically on every platform —
   the ↗ glyph fell back to a colour emoji font on mobile */
.arw{width:1em;height:1em;display:block;overflow:visible}
i:has(> .arw){display:inline-flex;align-items:center;font-style:normal}
.btn i{font-style:normal;transition:transform .45s var(--ez)}
.btn::before{
  content:"";position:absolute;inset:0;z-index:1;background:var(--lime);
  transform:scaleX(0);transform-origin:0 50%;transition:transform .55s var(--ez);
}
.btn:hover{color:var(--ink)}
.btn:hover::before{transform:scaleX(1)}
.btn:hover i{transform:translate(3px,-3px)}

.btn-solid{background:var(--lime);color:var(--ink)}
.btn-solid::before{background:var(--paper)}
.btn-solid:hover{color:var(--ink)}
.btn-ghost{background:none;box-shadow:inset 0 0 0 1px var(--line);min-width:170px}

/* progress + aura */
.progress{
  position:fixed;top:0;left:0;right:0;height:2px;background:var(--lime);z-index:1001;
  transform:scaleX(var(--sp));transform-origin:0 50%;pointer-events:none;
  box-shadow:0 0 14px rgba(200,255,56,.5);
}
.aura{
  position:fixed;top:0;left:0;width:440px;height:440px;border-radius:50%;z-index:3;pointer-events:none;
  background:radial-gradient(circle,rgba(200,255,56,.07),transparent 62%);mix-blend-mode:screen;
  transform:translate3d(calc(var(--cx) - 220px),calc(var(--cy) - 220px),0);
}

/* ============================================================
   CALIBRATION — the acid curtain
   Three words arrive in the same spot, a rule fills underneath
   them, then the whole panel lifts away. The timing lives in CSS
   (the exit transition carries its own delay), so a throttled
   frame callback cannot leave the curtain stuck on screen.
   ============================================================ */
.intro{display:none}
html.js .intro{
  position:fixed;inset:0;z-index:2000;display:grid;place-items:center;overflow:hidden;
  background:var(--lime);color:var(--ink);
  transition:transform .85s cubic-bezier(.82,0,.18,1) 1.65s;
}
html.js .intro.out{pointer-events:none;transform:translateY(-105%)}
html.js.no-intro .intro{display:none}

.intro-word{
  position:absolute;opacity:0;transform:translateY(40px);
  font-size:clamp(52px,9.4vw,152px);line-height:.82;letter-spacing:-.07em;font-weight:500;
}
.intro-word-a{animation:introWord .5s 50ms both}
.intro-word-b{animation:introWord .5s .55s both}
.intro-word-c{animation:introWord .55s 1.05s both}
@keyframes introWord{
  0%{opacity:0;transform:translateY(45px)}
  35%,70%{opacity:1;transform:translateY(0)}
  100%{opacity:0;transform:translateY(-45px)}
}

.intro-index{
  position:absolute;top:28px;left:var(--pad);
  font:400 11px/1 var(--mono);letter-spacing:.14em;color:rgba(9,10,11,.75);
}
.intro-line{
  position:absolute;left:var(--pad);right:var(--pad);bottom:28px;
  height:1px;background:rgba(9,10,11,.22);overflow:hidden;
}
.intro-line i{
  display:block;width:100%;height:100%;background:var(--ink);
  transform-origin:0 50%;animation:introFill 1.55s cubic-bezier(.7,0,.2,1) both;
}
@keyframes introFill{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* ============================================================
   NAV
   ============================================================ */
.nav{
  position:sticky;top:0;z-index:60;height:var(--nav-h);
  /* Brand left, everything else gathered on the right: the links sit next
     to the language toggle rather than floating in the middle, so the bar
     reads as one control group instead of three. The brand keeps the 1fr
     so the right block stays put as the viewport changes. */
  display:grid;grid-template-columns:1fr auto auto;align-items:center;
  column-gap:clamp(20px,2.6vw,40px);
  padding:0 var(--gut);border-bottom:1px solid var(--line);
  background:rgba(9,10,11,.72);backdrop-filter:blur(20px) saturate(140%);
  -webkit-backdrop-filter:blur(20px) saturate(140%);
  transition:height .4s var(--ez),background .4s var(--ez);
}
.nav.tight{height:60px;background:rgba(9,10,11,.9)}
.brand{display:inline-flex;align-items:center;gap:11px;width:max-content;font-weight:600;font-size:14px;letter-spacing:-.02em}
.brand-glyph{width:24px;height:24px;flex:none;color:var(--paper);transition:transform .6s var(--ez)}
.brand:hover .brand-glyph{transform:rotate(90deg)}
.brand sup{font-size:10px;line-height:1;color:var(--lime);top:-.62em;position:relative;margin-left:2px}
.nav-links{display:flex;gap:clamp(18px,2.2vw,34px);justify-self:end}
.nav-links a{position:relative;font-size:13px;color:var(--muted);padding:6px 0;transition:color .3s var(--ez)}
.nav-links a::after{
  content:"";position:absolute;left:0;bottom:0;height:1px;width:100%;background:var(--lime);
  transform:scaleX(0);transform-origin:0 50%;transition:transform .45s var(--ez);
}
/* .on comes from the scrollspy on the home page; [aria-current] is set by
   sync-partials.py on the case pages, where Cases is a real page you are
   standing on rather than a section you scrolled past. */
.nav-links a:hover,.nav-links a.on,.nav-links a[aria-current="page"]{color:var(--paper)}
.nav-links a:hover::after,.nav-links a.on::after,
.nav-links a[aria-current="page"]::after{transform:scaleX(1)}
.nav-right{display:flex;align-items:center;gap:clamp(14px,1.6vw,26px);justify-self:end}
.lang{display:flex;align-items:center;background:var(--tint);border-radius:2px;overflow:hidden}
.lang-btn{font-family:var(--mono);font-size:9px;letter-spacing:.12em;padding:6px 9px;color:var(--dim);transition:color .3s,background .3s}
.lang-btn.is-on{background:var(--lime);color:var(--ink)}
/* the inactive side is the link to the same page in the other language;
   the active side is a span, so there is nothing to click that does nothing */
a.lang-btn:hover{color:var(--paper)}
.nav-cta{display:inline-flex;align-items:center;gap:12px;font-size:13px;white-space:nowrap}
.nav-cta i{font-style:normal;color:var(--lime);font-size:15px;transition:transform .3s var(--ez)}
.nav-cta:hover i{transform:translate(3px,-3px)}

/* ============================================================
   1 · HERO — the Figma frame (twjbtQeReaHbpRC0ex6Bq1, node 25:534)
   The design is a 1200 × 600 frame. .hero-frame reproduces it as a
   fixed-ratio box and every child is placed in % of it, so the whole
   composition is proportionally identical at any width. Type inside
   scales with the frame through container units (1cqw = 12px of the
   design), never with the viewport — that is what keeps the copy
   column and the cluster locked to each other.
   ============================================================ */
.hero{
  position:relative;display:grid;place-items:center;overflow:hidden;
  height:calc(100svh - var(--nav-h) - var(--strip-h));min-height:380px;
  background:var(--ink);
}
.hero-frame{
  container-type:size;
  position:relative;z-index:2;
  /* the frame is 2:1, so the height available on screen caps the width */
  width:min(100% - var(--pad) * 2, calc((100svh - var(--nav-h) - var(--strip-h)) * 2));
  aspect-ratio:1200 / 600;
  --px:0;--py:0;                       /* pointer, -1 … 1, set by the runtime */
  --card-r:.67cqw;                     /* 8 of 1200, shared by every hero card */
}

/* ---------- left column ---------- */
.hero-copy{
  position:absolute;left:16.6667%;top:50%;translate:0 -50%;
  /* The design column is 322 wide because that is what its headline measures.
     Sized from the headline here for the same reason — hardcoding 322 makes
     the headline wrap the moment a substituted font runs wider than Onest. */
  width:max-content;max-width:32%;
  display:flex;flex-direction:column;align-items:flex-start;gap:1cqw;
}
.hero-copy .eyebrow{display:flex;align-items:center;gap:.83cqw;margin:0}
.hero-copy .dot{width:.67cqw;height:.67cqw}
.hero-kicker{
  font-family:var(--sans);font-size:1cqw;font-weight:700;
  letter-spacing:0;text-transform:uppercase;color:var(--lime);white-space:nowrap;
}
.hero-h1{
  margin:0;font-size:4.17cqw;line-height:1.1;
  font-weight:700;letter-spacing:-.03em;color:#F7F8F4;
  /* The <br> in the markup are the only breaks — nowrap keeps it three lines
     whatever font the machine falls back to. */
  white-space:nowrap;
}
.hero-h1 em{font-family:var(--serif);font-style:italic;font-weight:400;letter-spacing:0}
.hero-lede{
  margin:0;font-size:1cqw;line-height:1.35;color:#E5E5E5;
  width:min-content;min-width:100%;    /* wraps to the column, never widens it */
}
.hero-cta{display:flex;align-items:center;gap:.83cqw;padding-top:1.67cqw;width:100%}
/* the fill runs left → right and the element never moves */
.hbtn{
  position:relative;flex:0 0 auto;
  display:inline-flex;align-items:center;justify-content:center;gap:.5cqw;
  padding:1cqw .83cqw;
  font-family:var(--sans);font-size:1cqw;font-weight:500;letter-spacing:-.005em;
  line-height:1.17;white-space:nowrap;      /* 12 + 12 padding + 14 line box = the design's 38 */
  overflow:hidden;isolation:isolate;transition:color .45s var(--ez);
}
.hbtn span,.hbtn i{position:relative;z-index:2}
.hbtn i{font-style:normal;transition:transform .45s var(--ez)}
.hbtn:hover i{transform:translate(.17cqw,-.17cqw)}
.hbtn-solid{background:var(--lime);color:var(--ink)}
.hbtn-solid::before{
  content:"";position:absolute;inset:0;z-index:1;background:var(--paper);
  transform:scaleX(0);transform-origin:0 50%;transition:transform .55s var(--ez);
}
.hbtn-solid:hover::before{transform:scaleX(1)}

/* ---------- the cluster: one layer per Figma node, placed in % of the frame ---------- */
.hero-stage,.hero-cluster{position:absolute;inset:0;pointer-events:none}
/* The 1200x600 artboard leaves a wide dead margin around the cluster, so on a
   desktop screen the right half reads as smaller than the headline it answers.
   Scale the cluster inside the frame — origin on the cluster's own centre, not
   the frame's, so it grows into that margin instead of drifting at the copy. */
@media (min-width:1181px){
  .hero-cluster{transform:scale(1.18);transform-origin:72% 42%}
}
.orbit{
  position:absolute;left:51.8065%;top:13.7795%;width:37.7786%;height:75.5573%;
  pointer-events:none;overflow:visible;
}
.orbit-ring{fill:none;stroke:#2E2E2E;stroke-width:1}
.orbit-ring-inner{stroke-opacity:.5}
.orbit-name text{
  fill:var(--lime);font-family:var(--sans);font-size:10px;
  font-weight:500;letter-spacing:2px;
}
/* the wordmark rides the ring — it starts where the design puts it */
.orbit-name{
  transform-box:view-box;transform-origin:230px 222px;
  animation:orbitTravel 48s linear infinite;
}
@keyframes orbitTravel{to{transform:rotate(360deg)}}

.fcard{
  position:absolute;pointer-events:none;
  translate:calc(var(--px) * var(--dx) * 1px) calc(var(--py) * var(--dy) * 1px);
  transition:translate var(--t) cubic-bezier(.22,.61,.36,1);
  animation:fcardBob var(--bob) ease-in-out var(--phase) infinite alternate;
}
@keyframes fcardBob{to{transform:translateY(-7px)}}
.fcard img{display:block;width:100%;height:auto;position:relative;z-index:1}
/* Each plate in the export is fill-opacity .2 over a hairline border — glass.
   Figma's Background blur is a LAYER EFFECT and does not survive SVG export,
   so the exported card keeps the translucent fill but loses the blur behind
   it, which is exactly what makes it read flat. Put it back in CSS, clipped
   to each plate's own radius. flow has no plate, so it gets none. */
.fcard-system,.fcard-metric,.fcard-proto{
  -webkit-backdrop-filter:blur(1cqw);backdrop-filter:blur(1cqw);
}
/* card2 draws no plate — its glass is the controls inside it. An SVG inside an
   <img> cannot blur the page behind it (SVG filters only ever see the SVG's own
   content), so each control gets a DOM pane underneath at its own coordinates,
   in % of the 220 x 130 card. The artwork paints over them, and because every
   control fill is .2 opaque the blur reads straight through. */
/* card2 ships as two layers. The wiring has to sit BEHIND the panes, or the
   blur has nothing to work on — in the design the lines running under a control
   are smeared by its glass, and a single flat image over the panes cannot do
   that. Back layer in flow (it gives the wrapper its height), panes over it,
   controls on top. */
.fcard-flow .lay-back{z-index:0}
.fcard-flow .lay-front{position:absolute;left:0;top:0;z-index:2}
.gpane{
  position:absolute;display:block;pointer-events:none;z-index:1;
  -webkit-backdrop-filter:blur(1cqw);backdrop-filter:blur(1cqw);
}
.gpane-plus  {left:34.091%;top:10.769%;width:20.909%;height:21.538%;border-radius:var(--card-r)}
.gpane-toggle{left:75%;    top:40%;    width:19.091%;height:21.538%;border-radius:999px}
.gpane-minus {left:75%;    top:69.231%;width:19.091%;height:21.538%;border-radius:var(--card-r)}
.gpane-node  {left:5.455%; top:44.615%;width:7.273%; height:12.308%;border-radius:999px}

.fcard-system{border-radius:999px}      /* 210 x 62 pill */
.fcard-metric{border-radius:var(--card-r)}
.fcard-proto {border-radius:var(--card-r)}
/* The lit edge. In Figma the plate carries a light rim — bright along the
   bottom and right, brightest at the bottom-right corner, fading out before it
   reaches the top-left. It is a layer effect, so the export flattens it to the
   plate's own even grey stroke and the card loses its thickness. Rebuilt as a
   one-pixel ring above the artwork: a directional gradient, masked to the ring
   with the padding-box XOR trick. Inset by one design pixel so it lands on the
   plate's own stroke instead of just outside it. */
.fcard-system::after,.fcard-metric::after,.fcard-proto::after{
  content:"";position:absolute;inset:1px;border-radius:inherit;pointer-events:none;z-index:2;
  padding:1px;
  /* Two lights rather than one diagonal: a single 315deg gradient dies at the
     bottom-LEFT corner, but in Figma the whole bottom edge stays lit. Bottom
     and right are separate layers, so they sum at the bottom-right corner —
     which is where the design is brightest — and neither edge goes dark. */
  background:
    linear-gradient(  0deg,rgba(247,248,244,.62) 0%,rgba(247,248,244,.10) 38%,rgba(247,248,244,0) 62%),
    linear-gradient(270deg,rgba(247,248,244,.48) 0%,rgba(247,248,244,.08) 40%,rgba(247,248,244,0) 66%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
          mask-composite:exclude;
}
/* left/top/width are the design's own coordinates over 1200 × 600; the
   motion values are deliberately all different so no two cards travel together */
.fcard-proto {left:70.25%;   top:56.5%;    width:15.3333%;--dx:26;--dy:20;--t:.55s;--bob:7.5s;--phase:0s}
.fcard-system{left:56.0833%; top:30.3333%; width:17.5%;   --dx:22;--dy:16;--t:.62s;--bob:8.0s;--phase:-2.1s}
.fcard-flow  {left:71.9167%; top:31.8333%; width:18.3333%;--dx:15;--dy:11;--t:.78s;--bob:9.1s;--phase:-3.2s}
.fcard-metric{left:54.25%;   top:48.3333%; width:12.75%;  --dx:34;--dy:26;--t:.48s;--bob:6.2s;--phase:-1.4s}

/* ---------- background layers ---------- */
/* the frame's own fill: a rotated violet ellipse at 20% over the ink.
   CSS cannot rotate a radial-gradient, and at 20% the −22° is not
   readable, so it is reproduced upright. */
/* copy sits above the absolute bg-tint / bg-grain overlays, which otherwise
   paint on top of the static stage and gray out the text */
.ahero-stage{position:relative;z-index:1}
.bg-tint{
  position:absolute;inset:0;pointer-events:none;opacity:.2;
  background:radial-gradient(ellipse 59.5% 238% at 50% 50%,
    #1D1441 0%,#0F0A21 50%,#070510 75%,#000 100%);
}
.bg-blob{position:absolute;border-radius:50%;pointer-events:none}
/* the design blurs these by 250 — a radial gradient is the same picture
   without asking the compositor for a 500px-wide blur every frame */
.bg-blob-purple{
  left:-43.33%;top:-86.17%;width:79.08%;aspect-ratio:1;
  background:radial-gradient(circle closest-side,
    rgba(129,25,255,.30) 0%,rgba(129,25,255,.28) 40%,
    rgba(129,25,255,.16) 62%,rgba(129,25,255,.05) 82%,transparent 100%);
}
.bg-blob-teal{
  left:65%;top:4.5%;width:75.83%;aspect-ratio:1;
  background:radial-gradient(circle closest-side,
    rgba(1,105,194,.30) 0%,rgba(1,105,194,.28) 40%,
    rgba(1,105,194,.16) 62%,rgba(1,105,194,.05) 82%,transparent 100%);
}
/* the UI texture. The design places one 1300px copy; it repeats so a
   screen wider than that never shows an edge. */
.bg-shot{
  position:absolute;inset:0;pointer-events:none;
  background:url(assets/hero/bg-glow.png?v=17) center/1300px auto repeat;
  /* .45 screened over the ink puts the background at mean luma 15 / sd 3,
     which is what the design frame itself measures. Unblurred — the panel
     edges are legible in the design and blur washes them out. */
  mix-blend-mode:screen;opacity:.45;
}
.bg-dots{
  position:absolute;inset:0;pointer-events:none;opacity:.035;
  background:radial-gradient(circle at center,#F0F1EB 0 1px,transparent 1px) 0 0/12px 12px;
  -webkit-mask-image:linear-gradient(180deg,#000 0%,#000 50%,transparent 100%);
          mask-image:linear-gradient(180deg,#000 0%,#000 50%,transparent 100%);
}
/* the frame carries a fractalNoise grain; rendered once as a tile rather
   than as a live filter over the whole hero */
.bg-grain{
  position:absolute;inset:0;pointer-events:none;opacity:.12;
  background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n' color-interpolation-filters='sRGB'><feTurbulence type='fractalNoise' baseFrequency='1 0.5' numOctaves='3' stitchTiles='stitch' seed='9918'/><feComponentTransfer><feFuncA type='discrete' tableValues='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0'/></feComponentTransfer><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)'/></svg>") repeat;
}

/* word-mask reveal */
.w{display:inline-block;overflow:hidden;vertical-align:top;padding-bottom:.08em;margin-bottom:-.08em}
html.js .w > i{
  display:inline-block;font-style:inherit;transform:translateY(112%);
  transition:transform 1s var(--ez);transition-delay:calc(var(--i) * .055s + .1s);
}
html.js body.ready .hero-h1 .w > i{transform:translateY(0)}
html.js [data-reveal].in .w > i{transform:translateY(0)}

html.js .hero-r{opacity:0;transform:translateY(28px);filter:blur(8px)}
html.js body.ready .hero-r{
  opacity:1;transform:none;filter:blur(0);
  transition:opacity .9s var(--ez),transform 1.1s var(--ez),filter 1s var(--ez);
}
html.js body.ready .hero-r3{transition-delay:.38s}
html.js body.ready .hero-r4{transition-delay:.56s}
html.js .fcard{opacity:0}
html.js body.ready .fcard{
  opacity:1;
  transition:opacity .9s var(--ez) calc(.30s + var(--dx) * .006s),
             translate var(--t) cubic-bezier(.22,.61,.36,1);
}

/* ============================================================
   CAPABILITY STRIP — acid, replacing the dark ticker
   ============================================================ */
.strip{
  --strip-h:52px;
  height:var(--strip-h);background:var(--lime);color:var(--ink);
  display:flex;align-items:center;overflow:hidden;position:relative;z-index:5;
}
.strip ::selection{background:var(--ink);color:var(--lime)}
.strip-track{display:flex;align-items:center;flex:none;animation:strip 42s linear infinite}
.strip:hover .strip-track{animation-play-state:paused}
.strip span{
  padding:0 clamp(26px,3.4vw,58px);white-space:nowrap;
  font-size:clamp(10px,.8vw,12px);font-weight:600;letter-spacing:.06em;text-transform:uppercase;
}
@keyframes strip{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* ============================================================
   THE COVER — the light page rides up over the dark one
   The transition sits on the colour change rather than after the hero:
   a paper sheet arriving over an ink one reads far harder than ink
   over ink. #cost simply holds still under the nav — no fade, no scale,
   no blur — and everything from the light section down rides up over it
   as one sheet with its own ground. All the motion is on the arriving
   page: its leading corners, its lift shadow and its edge light.
   ============================================================ */
.cost{position:sticky;top:var(--nav-h);z-index:0}
.cost.is-covered{visibility:hidden}
/* Pinned under the nav, #cost never moves again: only the first
   100svh - nav-h of it is ever reachable, and .sheet rides up over the rest,
   so anything past that fold cannot be read at all. Russian runs longer and
   lost its bottom row first. Trim the section’s own top padding once the
   viewport is short so the whole block fits the band it gets, and drop the
   pin below 780px — and on phones, where the grid stacks and the block is
   tallest — letting the section scroll as it already does under
   prefers-reduced-motion. 780 is measured, not guessed: above 720px of width
   the tallest the block ever gets is 752px (English at 980), so anything
   taller than that keeps the cover. padding-bottom is the gap between the last line and
   the arriving sheet, so it stays generous. */
@media (max-height:1000px){
  .sec.cost{padding-top:clamp(48px,5.5vh,140px);padding-bottom:clamp(64px,9vh,140px)}
}
@media (max-height:780px),(max-width:720px){
  .sec.cost{position:relative;top:0}
}

/* ============================================================
   THE SHEET — the second page
   Only border-radius, shadow and opacity animate; nothing here
   changes width or height, so covering the hero costs no reflow.
   ============================================================ */
.sheet{
  position:relative;z-index:2;background:var(--ink);overflow:hidden;
  box-shadow:0 -24px calc(60px + (1 - var(--cover,0)) * 70px) -18px
             rgba(0,0,0,calc(.5 + (1 - var(--cover,0)) * .45));
}
.sheet::before{
  content:"";position:absolute;top:0;left:0;right:0;height:1px;z-index:4;pointer-events:none;
  background:linear-gradient(90deg,
    transparent,
    rgba(200,255,56,calc((1 - var(--cover,0)) * .9)),
    transparent);
  box-shadow:0 0 calc((1 - var(--cover,0)) * 22px) rgba(200,255,56,calc((1 - var(--cover,0)) * .55));
}


/* ============================================================
   2 · PROOF
   ============================================================ */
.proof{
  display:grid;grid-template-columns:repeat(4,1fr);
  gap:clamp(20px,3vw,54px);padding:clamp(38px,4.4vw,68px) var(--gut);
}
.proof-cell{position:relative;display:flex;flex-direction:column;gap:11px;padding-bottom:16px}
.proof-cell b{
  font-size:clamp(34px,4.2vw,72px);font-weight:500;letter-spacing:-.06em;line-height:.85;
  font-variant-numeric:tabular-nums;
}
.proof-cell::after{
  content:"";position:absolute;left:0;bottom:0;height:2px;width:100%;background:var(--lime);
  transform:scaleX(0);transform-origin:0 50%;transition:transform 1.1s var(--ez);
}
.proof.in .proof-cell::after{transform:scaleX(1)}
.proof.in .proof-cell:nth-child(2)::after{transition-delay:.12s}
.proof.in .proof-cell:nth-child(3)::after{transition-delay:.24s}
.proof.in .proof-cell:nth-child(4)::after{transition-delay:.36s}

/* ============================================================
   4 · THE COST
   ============================================================ */
/* the second sentence of the heading is an aside, not part of the display
   line: reading size, and leading of its own — it would otherwise inherit
   the .94 of h1,h2,h3 and set solid. */
#cost .sec-head h2 em{font-size:22px;line-height:1.35}

.cost-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(20px,2.6vw,46px)}
/* .cost names two different things — the section and each metric card — so
   the card rule below was also turning the section into a flex column, where
   the grid shrank and its cards spilled into the section’s bottom padding.
   The section is a plain block. */
.sec.cost{display:block}
.cost{display:flex;flex-direction:column;gap:11px}
.cost b{
  font-size:clamp(31px,3.5vw,58px);font-weight:500;letter-spacing:-.06em;line-height:.85;
  font-variant-numeric:tabular-nums;
}
/* the same small-caps label the proof strip uses, so the two number rows on
   the page read as one component. flex:1 stays: it is what lines the bars up. */
.cost h3{
  font-family:var(--mono);font-size:var(--t-lab);font-weight:400;
  letter-spacing:.17em;text-transform:uppercase;line-height:1.6;
  color:rgba(240,241,235,.78);flex:1;
}
.cost-bar{position:relative;display:block;height:2px;background:var(--line);margin-top:6px}
.cost-bar::after{
  content:"";position:absolute;inset:0;background:var(--paper);
  transform:scaleX(0);transform-origin:0 50%;transition:transform 1.3s var(--ez) .25s;
}
.cost.in .cost-bar::after{transform:scaleX(calc(var(--v) / 100))}
.cost-crit b{color:var(--lime)}
.cost-crit .cost-bar::after{background:var(--lime)}


/* ============================================================
   5 · WORK — the one light section
   Redefining the four ground tokens flips everything scoped inside
   it. Lime is never text here: on paper it drops to ~1.4:1, so it
   only ever appears as a fill with ink on top.
   ============================================================ */
.is-light{
  background:#F5F7F9;color:var(--ink);
  --muted:rgba(9,10,11,.64);
  --dim:rgba(9,10,11,.48);
  --line:rgba(9,10,11,.14);
  --line-2:rgba(9,10,11,.07);
  --tint:rgba(9,10,11,.055);
}
.is-light ::selection{background:var(--ink);color:var(--lime)}
.is-light .btn{color:var(--ink)}
.is-light .case-n{
  background:var(--lime);color:var(--ink);
  padding:4px 8px;border-radius:2px;letter-spacing:.12em;
}
.is-light .good{
  background:var(--lime);color:var(--ink);
  width:max-content;padding:2px 8px;border-radius:2px;
}
.is-light .shot img{filter:grayscale(1) brightness(.94) contrast(.96)}
.is-light .case.in .shot img{filter:grayscale(0) brightness(1) contrast(1)}

/* ============================================================
   5b · WORK layout
   ============================================================ */
.cases{display:flex;flex-direction:column;gap:clamp(52px,6.4vw,124px)}
.case-meta{display:flex;flex-wrap:wrap;align-items:center;gap:10px clamp(12px,1.6vw,24px);margin-bottom:clamp(10px,1.1vw,18px)}
.case-n{font-family:var(--mono);font-size:clamp(12px,1.05vw,15px);color:var(--lime);letter-spacing:.1em}
/* names the service this case is an example of, so the work reads back to
   what can actually be bought */
.case-service{
  display:inline-flex;align-items:center;gap:7px;
  font-family:var(--mono);font-size:var(--t-lab);letter-spacing:.14em;
  text-transform:uppercase;color:var(--paper);white-space:nowrap;
}
.case-service i{width:6px;height:6px;background:var(--lime);border-radius:1px;flex:none}
.is-light .case-service{color:var(--ink)}
.case-main{
  display:grid;grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);
  gap:clamp(26px,4vw,80px);align-items:center;
}
.case:nth-child(even) .case-copy{order:2}
/* order:2 moves the copy after the visual, but not out of column 1 —
   without this swap the mirrored case gives the wide track to the text. */
.case:nth-child(even) .case-main{grid-template-columns:minmax(0,1.2fr) minmax(0,.8fr)}
/* The card headline steps per frame instead of sliding with the viewport:
   the sizes below are set, not interpolated, because the curve they draw is
   not a straight line and no single clamp() passes through all six. Same
   approach as the home hero. Order matters — these must stay ascending. */
.case-copy h3{font-size:24px;letter-spacing:-.05em;line-height:.96}
@media (min-width:980px) {.case-copy h3{font-size:30px}}
@media (min-width:1180px){.case-copy h3{font-size:36px}}
@media (min-width:1280px){.case-copy h3{font-size:40px}}
@media (min-width:1440px){.case-copy h3{font-size:42px}}
@media (min-width:1920px){.case-copy h3{font-size:48px}}
.case-copy > p{color:var(--muted);margin-top:clamp(14px,1.5vw,22px);max-width:50ch;font-size:clamp(14px,1vw,16px)}
.case-tags{display:flex;flex-wrap:wrap;gap:7px;margin-top:clamp(16px,1.7vw,24px)}
.case-res{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin-top:clamp(20px,2.2vw,34px)}
.case-res div{display:flex;flex-direction:column;gap:7px}
.case-res b{font-size:clamp(18px,1.8vw,29px);font-weight:500;letter-spacing:-.045em;line-height:1}

.case-visual{position:relative;perspective:1500px}
/* The case images arrive as finished device mockups on transparent ground,
   with their own shadow — so .mock draws nothing and only carries the lean.
   --px/--py come from the pointer loop in script.js. */
.mock{
  --px:0;--py:0;--lean:8deg;
  position:relative;
  transform:rotateY(calc(var(--lean) + var(--px) * -4deg))
            rotateX(calc(var(--py) * -3deg));
}
/* The lean is not decoration that alternates — it follows the side the
   mockup sits on, so every card turns towards the middle of the page and
   towards its own copy. Odd cards put the visual on the right (see the
   `order` rule above), so they turn left; even cards do the opposite.
   Change one of the two and this has to change with it. */
.case:nth-child(odd) .mock{--lean:-8deg}
.shot{position:relative;margin:0;overflow:hidden}
/* A case still on a flat screenshot rather than a device mockup: it has no
   edge of its own, so the frame comes from CSS until the artwork is redone. */
.shot-plain{
  border-radius:7px;
  box-shadow:0 0 0 1px var(--line),0 30px 64px -40px rgba(9,10,11,.5);
}
/* No overscale on reveal: inside a browser frame a 5% zoom reads as the
   screenshot being cropped by the chrome. Colour and the scan carry it. */
.shot img{
  width:100%;filter:grayscale(1) brightness(.62) contrast(1.05);
  transition:filter 1.3s var(--ez);
}
/* A near-square mockup at full column width towers over the wide one in case
   01. A fixed px cap is wrong here — it bites hard at 1600 and not at all at
   1388, where the column is already under it. Cap the BOX at 16:10 of the
   column so the limit scales with the layout, and let contain hold each
   picture's own aspect; the artwork is on transparent ground, so the slack
   reads as margin. max-height still ceilings it on very wide screens.
   Two-column layout only — stacked below 1180 the image should fill.
   Not on .shot-plain: there the frame is CSS, so it must hug the picture. */
@media (min-width:1181px){
  .shot:not(.shot-plain) img{aspect-ratio:16/11;max-height:560px;object-fit:contain}
}
.case.in .shot img{filter:grayscale(0) brightness(1) contrast(1)}
.scan{
  position:absolute;left:0;right:0;top:0;height:100%;opacity:0;pointer-events:none;
  background:linear-gradient(180deg,
    rgba(200,255,56,0) 0%,rgba(200,255,56,.05) 74%,
    rgba(200,255,56,.22) 94%,rgba(200,255,56,.95) 99.4%,rgba(200,255,56,0) 100%);
}
/* Cases dropped the sweep: their images are device mockups on transparent
   ground, so the line ran on past the device into empty page. */
.person.in .scan{animation:scanOnce 1.8s var(--ez) .15s}
@keyframes scanOnce{
  0%{opacity:0;transform:translateY(-101%)}
  14%{opacity:1}86%{opacity:1}
  100%{opacity:0;transform:translateY(101%)}
}

/* ============================================================
   6 · METHOD — four steps on one rail
   The per-step charts are gone on purpose: three of the four were
   rows of bars and read as one repeated texture, not four ideas.
   ============================================================ */
.steps{
  position:relative;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(22px,3vw,54px);padding-top:30px;
}
.steps-rail{position:absolute;top:0;left:0;right:0;height:1px;background:var(--line)}
.steps-rail::after{
  content:"";position:absolute;inset:0;background:var(--lime);
  transform:scaleX(0);transform-origin:0 50%;transition:transform 1.8s var(--ez) .2s;
  box-shadow:0 0 10px rgba(200,255,56,.5);
}
.steps.in .steps-rail::after{transform:scaleX(1)}
.step{position:relative;display:flex;flex-direction:column;gap:10px}
.step::before{
  content:"";position:absolute;top:-34px;left:0;width:7px;height:7px;border-radius:50%;
  background:var(--ink);box-shadow:0 0 0 1px var(--line);
  transition:background .5s var(--ez),box-shadow .5s var(--ez);
}
.steps.in .step::before{background:var(--lime);box-shadow:0 0 0 1px var(--lime),0 0 14px rgba(200,255,56,.6)}
.steps.in .step:nth-child(3)::before{transition-delay:.42s}
.steps.in .step:nth-child(4)::before{transition-delay:.84s}
.steps.in .step:nth-child(5)::before{transition-delay:1.26s}
.step-n{font-family:var(--mono);font-size:10px;letter-spacing:.15em;color:var(--lime)}
.step h3{font-size:clamp(19px,1.8vw,29px);letter-spacing:-.04em}
.step p{color:var(--muted);font-size:14px;line-height:1.55;max-width:34ch}

/* ============================================================
   7 · SERVICES
   ============================================================ */
/* the row content is inset from the row's own edges, so the number has
   air around it instead of butting into the tint when a row opens */
.srv-list{
  border-top:1px solid var(--line);
  --srv-pad:clamp(14px,1.6vw,26px);
  --srv-in:calc(var(--srv-pad) + 15px + clamp(14px,2.4vw,40px));
}
.srv{position:relative;border-bottom:1px solid var(--line)}
.srv::before{
  content:"";position:absolute;inset:0;pointer-events:none;opacity:0;
  background:linear-gradient(90deg,rgba(200,255,56,.05),transparent 60%);
  transition:opacity .5s var(--ez);
}
.srv:hover::before,.srv.is-open::before{opacity:1}
.srv-head{
  width:100%;display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;
  gap:clamp(14px,2.4vw,40px);align-items:center;
  padding:clamp(18px,2vw,30px) var(--srv-pad);
}
.srv-n{font-family:var(--mono);font-size:10px;letter-spacing:.15em;color:var(--dim);transition:color .4s}
.srv-t{
  font-size:clamp(19px,2.3vw,38px);font-weight:500;letter-spacing:-.045em;line-height:1;
  transition:transform .55s var(--ez);
}
.srv:hover .srv-n,.srv.is-open .srv-n{color:var(--lime)}
.srv:hover .srv-t{transform:translateX(10px)}
.srv-tag{white-space:nowrap}

/* Cyrillic hyphenated words break at the hyphen in display type ("из-
за").
   Wrap the word in .nb where that happens. */
.nb{white-space:nowrap}
.srv-x{position:relative;width:16px;height:16px;flex:none}
.srv-x::before,.srv-x::after{content:"";position:absolute;background:var(--paper);transition:transform .5s var(--ez),background .4s}
.srv-x::before{left:0;right:0;top:50%;height:1px;transform:translateY(-50%)}
.srv-x::after{top:0;bottom:0;left:50%;width:1px;transform:translateX(-50%)}
.srv.is-open .srv-x::after{transform:scaleY(0)}
.srv.is-open .srv-x::before{background:var(--lime)}
.srv-body{display:grid;grid-template-rows:0fr;transition:grid-template-rows .6s var(--ez)}
.srv.is-open .srv-body{grid-template-rows:1fr}
.srv-cols{
  overflow:hidden;display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.75fr);
  gap:clamp(20px,3vw,60px);padding:0 var(--srv-pad) 0 var(--srv-in);
}
.srv-cols > *{opacity:0;transform:translateY(14px);transition:opacity .5s var(--ez),transform .6s var(--ez)}
.srv.is-open .srv-cols > *{opacity:1;transform:none;transition-delay:.18s}
.srv-cols p{color:var(--muted);padding-bottom:clamp(20px,2.2vw,34px);max-width:58ch;font-size:clamp(14px,1vw,16px)}
.srv-tags{display:flex;flex-wrap:wrap;gap:7px;align-content:flex-start;padding-bottom:clamp(20px,2.2vw,34px)}

/* ============================================================
   8 · STUDIO
   ============================================================ */
.people{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(28px,4vw,80px)}
.person{display:grid;grid-template-columns:auto minmax(0,1fr);gap:clamp(20px,2.4vw,38px);align-items:start}
.person-ph{
  position:relative;margin:0;flex:none;border-radius:50%;overflow:hidden;
  width:clamp(92px,8.4vw,138px);height:clamp(92px,8.4vw,138px);
}
.person-ph img{width:100%;height:100%;object-fit:cover;filter:grayscale(1) brightness(.88);transition:filter 1.2s var(--ez)}
.person.in .person-ph img{filter:grayscale(.12) brightness(1)}
.person-copy{display:flex;flex-direction:column;gap:11px}
.person-copy h3{font-size:clamp(20px,2vw,32px);letter-spacing:-.04em}
.person-copy p{color:var(--muted);font-size:14px;line-height:1.6;max-width:42ch}
.person-skills{font-family:var(--mono);font-size:10px;letter-spacing:.11em;text-transform:uppercase;color:var(--dim)}

/* the roster reads as one full-width strip. This is the one place on the
   page where repeated vertical rules are right: they are columns of a
   single row, not a stack of parallel lines. */
.crew{
  margin-top:clamp(40px,4.6vw,84px);padding-top:clamp(18px,2vw,30px);
  border-top:1px solid var(--line);
  display:flex;flex-direction:column;gap:clamp(14px,1.6vw,22px);
}
.crew ul{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:0}
.crew li{
  padding-left:clamp(14px,1.8vw,28px);
  border-left:1px solid var(--line);
  font-size:clamp(15px,1.35vw,21px);letter-spacing:-.025em;line-height:1.2;
  transition:color .4s var(--ez);
}
.crew li:first-child{padding-left:0;border-left:0}
.crew li:hover{color:var(--lime)}

/* ============================================================
   9 · PRINCIPLE
   ============================================================ */
.creed{
  position:relative;padding:clamp(90px,10vw,170px) var(--gut);text-align:center;
  display:flex;flex-direction:column;align-items:center;gap:20px;overflow:hidden;
}
.creed-orbit{
  position:absolute;left:50%;top:50%;width:min(70vw,560px);height:min(70vw,560px);
  transform:translate(-50%,-50%);border:1px solid var(--line-2);border-radius:50%;
  animation:orbit 34s linear infinite;pointer-events:none;
}
.creed-orbit::before{content:"";position:absolute;inset:26%;border:1px dashed rgba(141,123,255,.14);border-radius:50%}
.creed-orbit i{
  position:absolute;width:8px;height:8px;border-radius:50%;background:var(--lime);
  box-shadow:0 0 18px var(--lime);top:50%;left:-4px;margin-top:-4px;
}
.creed-orbit i:last-child{background:var(--alert);box-shadow:0 0 18px var(--alert);top:12%;left:auto;right:11%}
@keyframes orbit{to{transform:translate(-50%,-50%) rotate(360deg)}}
.creed blockquote em{color:var(--lime);display:inline-block}
.studio .sec-head h2 em{color:var(--lime)}
.creed blockquote{
  position:relative;margin:0;font-size:clamp(28px,4.2vw,68px);font-weight:500;
  letter-spacing:-.055em;line-height:1;max-width:18ch;
}
.creed > p{position:relative;color:var(--muted);max-width:54ch;font-size:clamp(14px,1vw,16px)}
.creed .idx{position:relative}

/* ============================================================
   10 · ACID FOOTER — fits one screen
   ============================================================ */
footer{
  background:var(--lime);color:var(--ink);position:relative;z-index:4;
  min-height:100svh;display:flex;flex-direction:column;
}
footer ::selection{background:var(--ink);color:var(--lime)}
footer .idx,footer .lab{color:rgba(9,10,11,.6)}
.foot-main{
  flex:1;display:grid;grid-template-columns:minmax(0,1fr) auto;
  align-items:center;gap:clamp(24px,4vw,70px);
  padding:calc(var(--nav-h) + clamp(18px,3vh,44px)) var(--gut) clamp(28px,4vh,60px);
}
.foot-main h2{
  font-size:clamp(38px,min(9vw,16vh),176px);letter-spacing:-.062em;line-height:.92;
  margin:0;max-width:17ch;
}
/* Cyrillic sets wider per character than Latin and `ch` is measured on "0",
   so a measure tuned to English wraps this heading onto twice as many lines.
   The footer is sized to the viewport, so those extra lines land on top of the
   paragraph under it. Match the line COUNT, not the character count.
   html[lang="ru"] is stamped on every page in dist-interfacet/ru/ by mkru.py,
   so this needs no class in the markup. */
html[lang="ru"] .foot-main h2{
  max-width:20ch;font-size:clamp(34px,min(7vw,12.4vh),150px);
  /* .92 was set on a Latin italic with almost no descenders; the Russian
     line has them, and at .92 they ran into the paragraph below */
  line-height:.98;
}
.foot-main p{
  margin-top:min(2.4vw,2.6vh);color:rgba(9,10,11,.72);max-width:48ch;
  font-size:clamp(13px,min(1.35vw,2.3vh),20px);
}
.foot-orb{
  align-self:center;border-radius:50%;background:var(--ink);color:var(--paper);
  width:clamp(132px,min(13vw,20vh),190px);height:clamp(132px,min(13vw,20vh),190px);
  display:flex;flex-direction:column;justify-content:space-between;padding:clamp(24px,2.6vw,32px);
  font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;line-height:1.5;
  transition:transform .5s var(--ez),border-radius .5s var(--ez);
}
.foot-orb i{align-self:flex-end;font-style:normal;font-size:22px;line-height:1;color:var(--lime)}
.foot-orb:hover{transform:rotate(-8deg) scale(1.05);border-radius:34%}

.foot-links{
  display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid rgba(9,10,11,.28);
  /* each cell already carries --pad of its own, so the band takes only the
     extra: cell one’s text then starts exactly on the column edge. */
  padding-inline:calc(var(--gut) - var(--pad));
}
.foot-link{
  position:relative;display:flex;flex-direction:column;gap:8px;overflow:hidden;
  padding:clamp(16px,2vh,30px) var(--pad);transition:color .45s var(--ez);
}
.foot-link b{position:relative;z-index:2;font-weight:500;font-size:clamp(14px,1.3vw,20px);letter-spacing:-.03em}
.foot-link .lab{position:relative;z-index:2;transition:color .45s var(--ez)}
.foot-link i{
  position:absolute;right:var(--pad);bottom:clamp(16px,2vh,30px);z-index:2;
  font-style:normal;font-size:18px;transition:transform .45s var(--ez);
}
.foot-link::before{
  content:"";position:absolute;inset:0;background:var(--ink);
  transform:scaleX(0);transform-origin:0 50%;transition:transform .5s var(--ez);
}
.foot-link:hover{color:var(--paper)}
.foot-link:hover .lab{color:var(--lime)}
.foot-link:hover::before{transform:scaleX(1)}
.foot-link:hover i{transform:translate(3px,-3px)}

.foot-bottom{
  display:grid;grid-template-columns:1fr 1fr 1fr;align-items:center;gap:14px;
  padding:clamp(14px,2vh,22px) var(--gut);border-top:1px solid rgba(9,10,11,.28);
  font-family:var(--mono);font-size:9px;letter-spacing:.14em;text-transform:uppercase;
  color:rgba(9,10,11,.66);
}
.foot-bottom .brand{font-family:var(--sans);font-size:14px;letter-spacing:-.02em;color:var(--ink)}
.foot-bottom .brand sup,.foot-bottom .brand-glyph{color:var(--ink)}
.foot-bottom > span:nth-child(2){justify-self:center;text-align:center}
.foot-bottom > span:last-child{justify-self:end;text-align:right}

/* ============================================================
   REVEAL
   ============================================================ */
html.js [data-reveal]{
  opacity:0;transform:translateY(52px);filter:blur(6px);
  transition:opacity .95s var(--ez),transform 1.1s var(--ez),filter 1s var(--ez);
}
html.js [data-reveal].in{opacity:1;transform:none;filter:blur(0)}
html.js [data-reveal="soft"]{transform:none;filter:none;transition:opacity .9s var(--ez)}
html.js .cost[data-reveal]:nth-child(2){transition-delay:.09s}
html.js .cost[data-reveal]:nth-child(3){transition-delay:.18s}
html.js .cost[data-reveal]:nth-child(4){transition-delay:.27s}
html.js .person[data-reveal]:nth-child(2){transition-delay:.12s}
html.js .case[data-reveal]{clip-path:inset(5% 0 5% 0)}
html.js .case[data-reveal].in{
  clip-path:inset(0 0 0 0);
  transition:opacity .95s var(--ez),transform 1.1s var(--ez),filter 1s var(--ez),clip-path 1.35s var(--ez);
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width:1100px){
  /* Stacks: message first, cluster under it. The cluster keeps the design's
     coordinates — .hero-stage crops to the region the cluster actually
     occupies (461 x 453 of the 1200 x 600 frame) and .hero-cluster is the
     full frame shifted inside it, so nothing has to be re-measured. */
  .hero{height:auto;min-height:0;padding:clamp(34px,6vw,64px) 0}
  .hero-frame{
    width:min(100% - var(--pad) * 2,560px);aspect-ratio:auto;
    container-type:inline-size;
    display:flex;flex-direction:column;gap:clamp(26px,5vw,44px);
  }
  .hero-copy{position:static;translate:none;width:100%;max-width:none;gap:clamp(12px,2.4vw,18px)}
  .hero-stage{position:relative;inset:auto;width:100%;aspect-ratio:461 / 453;overflow:hidden}
  .hero-cluster{top:-18.26%;left:-134.87%;right:auto;bottom:auto;width:260.3%;aspect-ratio:1200 / 600}
  /* the cards render 2.603 x (frame/1200) of design size here */
  .fcard-system,.fcard-metric,.fcard-proto{-webkit-backdrop-filter:blur(2.6cqw);backdrop-filter:blur(2.6cqw)}
  .hero-frame{--card-r:1.74cqw}
  .gpane{-webkit-backdrop-filter:blur(2.6cqw);backdrop-filter:blur(2.6cqw)}
  /* 1cqw is the column now, not the 1200px frame, so the type is re-scaled */
  .hero-copy .dot{width:7px;height:7px}
  .eyebrow{gap:9px}
  .hero-kicker{font-size:max(11px,2.4cqw)}
  .hero-h1{font-size:clamp(34px,10.4cqw,58px)}
  .hero-lede{font-size:max(13px,2.9cqw);line-height:1.5}
  .hero-cta{gap:10px;padding-top:6px}
  .hbtn{font-size:max(12px,2.7cqw);padding:14px 16px;gap:8px}
  .hbtn:hover i{transform:translate(3px,-3px)}
}
@media (max-width:1180px){
  /* below this the hero stops trying to fit one screen: it stacks */
  .case-main,.case:nth-child(even) .case-main{grid-template-columns:1fr;gap:clamp(24px,4vw,44px)}
  .case:nth-child(even) .case-copy{order:0}
  .steps{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:clamp(30px,4vw,52px)}
  /* the rail only spans the first row, so node dots on wrapped rows
     would float with nothing to sit on */
  .step::before{display:none}
  .cost-grid{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:clamp(28px,3.4vw,48px)}
}
@media (max-width:980px){
  .nav-links{display:none}
  .nav{grid-template-columns:1fr auto;gap:16px}
  .proof{grid-template-columns:1fr 1fr;row-gap:clamp(26px,4vw,44px)}
  .people{grid-template-columns:1fr;gap:clamp(26px,4vw,44px)}
  .crew ul{grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(12px,1.8vw,20px) 0}
  .crew li:nth-child(odd){padding-left:0;border-left:0}
  .srv-cols{grid-template-columns:1fr}
  .srv-cols p{padding-bottom:16px}
  .sec-head{grid-template-columns:1fr;align-items:start;gap:18px}
  /* a button is narrow enough to stay beside the heading on a tablet —
     only the paragraph variants need to stack */
  .sec-head-cta{grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:24px}
  .sec-head-cta > .btn{justify-self:end}

/* /cases in Russian: «Что мы спроектировали» runs a word longer than the
   English line and does not fit the heading column above 1440, so it broke
   after «мы» and left the italic «и» stranded at the end of line two. Neither
   lever reaches on its own: the column is capped by --col and tops out near
   740px, short of the 788 the line needs at 74px. So widen the measure and
   stop the type growing past 66. Above 980 only — below that the head is
   already a single column and the button sits under it. */
@media (min-width:981px){
  html[lang="ru"] .work-all .sec-head{grid-template-columns:minmax(0,1.25fr) minmax(0,.75fr)}
  html[lang="ru"] .work-all .sec-head h2{font-size:min(var(--t-h2),66px)}
}
}
@media (max-width:720px){
  :root{--nav-h:62px}
  .nav-right{gap:12px}
  .nav-cta{font-size:12px}
  .proof{grid-template-columns:1fr}
  .cost-grid{grid-template-columns:1fr}
  .steps{grid-template-columns:1fr}
  /* keep the three metrics side by side while they fit; below ~330px of
     content width the grid drops to two of its own accord */
  .case-res{grid-template-columns:repeat(auto-fit,minmax(94px,1fr));gap:12px 10px}
  .case-res .lab{letter-spacing:.1em}
  .person{grid-template-columns:1fr}
  /* three across, two rows — a column of five ate too much height */
  .crew ul{grid-template-columns:repeat(3,minmax(0,1fr));gap:12px 0}
  .crew li{padding-left:10px;border-left:1px solid var(--line);font-size:14px}
  .crew li:nth-child(3n+1){padding-left:0;border-left:0}
  .foot-main{grid-template-columns:1fr;align-items:start;gap:30px}
  .foot-orb{align-self:flex-end}
  .foot-links{grid-template-columns:1fr}
  .foot-link{border-top:1px solid rgba(9,10,11,.28)}
  .foot-links .foot-link:first-child{border-top:0}
  .foot-bottom{grid-template-columns:1fr;justify-items:start;gap:10px;padding:20px var(--gut)}
  .foot-bottom > span:nth-child(2),
  .foot-bottom > span:last-child{justify-self:start;text-align:left}
  .srv-head{grid-template-columns:auto minmax(0,1fr) auto;gap:14px}
  .sec-head-cta{grid-template-columns:1fr;align-items:start;gap:18px}
  .sec-head-cta > .btn{justify-self:start}
  .srv-tag{display:none}
  .aura{display:none}

  /* Section rhythm was set by the 76px floor in --sec: at 390px the vw term
     is only 33px, so every gap between sections was a flat 152px. Retune the
     floor for phones; the desktop token is untouched above 720px. */
  :root{--sec:clamp(46px,11vw,76px)}
  .creed{padding-block:clamp(56px,14vw,90px)}
  .cases{gap:clamp(40px,10vw,52px)}
}
@media (max-width:560px){
  .brand span{display:none}
  footer{min-height:0}
}

/* ============================================================
   REDUCED MOTION — end states only
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  html.js{scroll-behavior:auto}
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
  html.js [data-reveal],
  html.js .hero-r{opacity:1 !important;transform:none !important;filter:none !important;clip-path:none !important}
  html.js .w > i{transform:none !important}
  .creed-orbit{display:none}
  .fcard{animation:none;translate:none}
  .orbit-name{animation:none}
  .cost{position:relative;top:0}
  .sheet{box-shadow:none;overflow:visible}
  .sheet::before{display:none}
}


/* a case preview that has a page behind it says so */
.case-more{
  display:inline-flex;align-items:center;gap:8px;
  margin-top:clamp(18px,2vw,28px);font-size:13px;
  border-bottom:1px solid var(--line);padding-bottom:3px;
  transition:border-color .3s var(--ez),gap .3s var(--ez);
}
.case-more:hover{border-color:currentColor;gap:11px}
.case-more .arw{width:.9em;height:.9em}

/* ════════════════════════════════════════════════════════════════════
   LEAD DIALOG — the footer enquiry form and the exit prompt
   Markup lives in partials/footer.html, behaviour in lead.js.

   Every colour here is declared locally. --ink/--paper/--lime never move,
   but case.css re-grounds --muted/--dim/--line to their dark-on-light
   values for the case pages, and the dialog has to look the same on all
   of them — so it carries its own set rather than inheriting the page's.
   ════════════════════════════════════════════════════════════════════ */
.lead{
  --l-line:rgba(240,241,235,.14);
  --l-muted:rgba(240,241,235,.62);
  --l-dim:rgba(240,241,235,.42);
  --l-field:rgba(240,241,235,.05);
  width:min(100% - 32px, 560px);
  padding:0;border:0;background:transparent;color:var(--paper);
  /* The height cap lives on .lead-form, not here. With it on the dialog,
     the dialog became the scroll container and its bar sat outside the
     panel, over the backdrop, in the browser's own light styling.

     Do NOT set `position` here. The UA gives an open modal dialog
     position:fixed; overriding it drops the dialog back into the footer's
     flow, and focusing the first field then scrolls the whole page up to
     reach it. The lime line below still anchors correctly — a fixed
     element is a positioned ancestor. */
  overflow:visible;
}
/* the lime hairline rides on the dialog so it stays put while the panel
   scrolls under it */
.lead::before{
  content:"";position:absolute;z-index:2;
  left:clamp(26px,4vw,44px);right:clamp(26px,4vw,44px);top:0;
  height:2px;background:var(--lime);border-radius:0 0 2px 2px;
  pointer-events:none;
}
.lead::backdrop{
  background:rgba(5,6,7,.72);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.lead-form{
  position:relative;
  background:var(--ink-1);border:1px solid var(--l-line);
  border-radius:18px;
  padding:clamp(26px,4vw,44px);
  max-height:min(92svh, 860px);
  overflow-y:auto;overscroll-behavior:contain;
  box-shadow:0 40px 90px -40px rgba(0,0,0,.9);
  /* Firefox and modern Chrome; the ::-webkit rules below cover the rest.
     The default bar is a light OS widget parked on a near-black panel —
     the one piece of the dialog that was not designed. */
  scrollbar-width:thin;
  scrollbar-color:rgba(240,241,235,.22) transparent;
}
.lead-form::-webkit-scrollbar{width:10px}
.lead-form::-webkit-scrollbar-track{background:transparent}
.lead-form::-webkit-scrollbar-thumb{
  background:rgba(240,241,235,.18);
  border-radius:999px;
  /* the border is transparent padding: it keeps the thumb off the rounded
     corner instead of letting it touch the panel edge */
  border:3px solid transparent;background-clip:content-box;
}
.lead-form::-webkit-scrollbar-thumb:hover{background:rgba(240,241,235,.3);background-clip:content-box}

.lead-x{
  position:absolute;top:14px;right:14px;width:34px;height:34px;
  display:grid;place-items:center;border-radius:8px;
  background:none;border:0;color:var(--l-dim);cursor:pointer;
  transition:color .3s var(--ez),background .3s var(--ez);
}
.lead-x:hover{color:var(--paper);background:var(--l-field)}
.lead-x svg{width:16px;height:16px}

.lead-kicker{
  display:block;font-family:var(--mono);font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--lime);margin-bottom:14px;
}
.lead-head h2{
  font-size:clamp(25px,3.4vw,38px);letter-spacing:-.045em;line-height:1.02;
  font-weight:500;margin:0 0 12px;text-wrap:balance;
}
.lead-head h2 em{font-family:var(--serif);font-style:italic;font-weight:400}
.lead-head p{
  color:var(--l-muted);font-size:14px;line-height:1.6;margin:0 0 clamp(20px,2.6vw,30px);
  max-width:46ch;
}

.lead-field{margin-bottom:16px}
.lead-field label{
  display:block;font-family:var(--mono);font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--l-dim);margin-bottom:8px;
}
.lead-field input,.lead-field textarea{
  width:100%;font:inherit;font-size:15px;color:var(--paper);
  background:var(--l-field);border:1px solid var(--l-line);border-radius:10px;
  padding:13px 15px;resize:vertical;
  transition:border-color .3s var(--ez),background .3s var(--ez);
}
.lead-field textarea{min-height:104px;line-height:1.5}
/* a step down from the 15px the field types at: the hint is not the content,
   and at field size the Russian placeholders ran past the right edge.
   The input keeps 15px, so iOS still does not zoom on focus. */
.lead-field ::placeholder{color:rgba(240,241,235,.34);font-size:13px}
.lead-field input:focus,.lead-field textarea:focus{
  outline:none;border-color:var(--lime);background:rgba(240,241,235,.07);
}
.lead-field .is-bad{border-color:#FF6FA9}
.lead-err{
  display:block;min-height:0;font-size:12px;line-height:1.4;color:#FF6FA9;
  margin-top:6px;
}
.lead-err:empty{margin-top:0}

/* Honeypot. Not display:none — some bots skip what is not rendered, and
   this one is meant to be found and filled in. */
.lead-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

.lead-cf:not(:empty){margin:4px 0 16px}

.lead-actions{
  display:flex;flex-wrap:wrap;align-items:center;gap:14px 20px;
  margin-top:clamp(18px,2.4vw,26px);
}
.lead-send{
  display:inline-flex;align-items:center;gap:12px;
  font:inherit;font-size:14px;font-weight:500;
  background:var(--lime);color:var(--ink);border:0;border-radius:999px;
  padding:14px 26px;cursor:pointer;
  transition:transform .3s var(--ez),opacity .3s var(--ez);
}
.lead-send:hover{transform:translateY(-1px)}
.lead-send:disabled{opacity:.5;cursor:default;transform:none}
.lead-send .arw{width:15px;height:15px}
.lead-send i{font-style:normal;display:inline-flex;transition:transform .3s var(--ez)}
.lead-send:hover i{transform:translate(3px,-3px)}
.lead-fine{color:var(--l-dim);font-size:12px;line-height:1.5;margin:0;flex:1 1 220px}

.lead-status{margin:16px 0 0;font-size:13px;line-height:1.55;color:var(--l-muted)}
.lead-status:empty{margin:0}
.lead-status.is-warn{color:#FFD166}
.lead-status a{color:var(--lime);text-decoration:underline;text-underline-offset:3px}

/* the sent state keeps the panel, drops the form */
.lead-form.is-sent .lead-field,
.lead-form.is-sent .lead-actions,
.lead-form.is-sent .lead-cf{display:none}
.lead-form.is-sent .lead-head p{margin-bottom:0}

@media (prefers-reduced-motion: no-preference){
  .lead[open]{animation:leadIn .32s var(--ez)}
  .lead[open]::backdrop{animation:leadFade .32s var(--ez)}
}
@keyframes leadIn{from{opacity:0;transform:translateY(10px) scale(.985)}}
@keyframes leadFade{from{opacity:0}}

/* the footer orb is a button now, not a link */
/* The orb is a button now. Reset only the chrome a button adds — `font`
   as a shorthand would also reset family and size, and this rule sits
   after the one that makes the orb mono 10px, so it would win and the
   label would come out in the body face. */
.foot-orb{
  border:0;cursor:pointer;text-align:left;
  -webkit-appearance:none;appearance:none;
}
/* the note is the only field you may skip, so say so on the label rather
   than leaving people to guess which ones matter */
.lead-opt{color:rgba(240,241,235,.3);letter-spacing:.1em}

/* the way back out of the sent screen, and the impatient route */
.lead-again{
  display:inline-flex;align-items:center;
  margin-top:clamp(18px,2.2vw,26px);
  font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--paper);background:none;cursor:pointer;
  border:1px solid var(--l-line);border-radius:999px;padding:11px 18px;
  transition:border-color .3s var(--ez),background .3s var(--ez);
}
.lead-again:hover{border-color:var(--lime);background:rgba(200,255,56,.08)}
/* .lead-head p is (0,1,1) and sets margin-top:0, so a plain .lead-direct
   at (0,1,0) never applied — the line sat right under the button. */
.lead-head .lead-direct{
  margin:clamp(22px,2.4vw,30px) 0 0;
  font-size:12px;line-height:1.6;color:var(--l-dim);
}
.lead-direct a{color:var(--lime);text-decoration:underline;text-underline-offset:3px}
/* the sent screen writes its own spacing, so drop the head's bottom gap */
.lead-form.is-sent .lead-head p:last-child{margin-bottom:0}
/* the privacy link sits in the footer's bottom rule; it has to read as part
   of that line, not as a call to action */
.foot-legal{text-decoration:underline;text-underline-offset:3px;opacity:.75;transition:opacity .3s var(--ez)}
.foot-legal:hover{opacity:1}
.lead-fine a{color:var(--l-muted);text-decoration:underline;text-underline-offset:3px}
.lead-fine a:hover{color:var(--paper)}
