/* ============================================================
   WUSA v2
   One grid. One bleed direction. A real second type tier.
   No radii anywhere — editorial print has no rounded corners.
   Ice = atmosphere. Ice-deep = every functional stroke.
   Salmon = "now" — current project, selected topic, submit.
   ============================================================ */

:root {
  /* ---- surface ---- */
  --paper: #F7FAFD;
  --paper-sunk: #ECF2F7;
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.78' numOctaves='2' seed='11' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)' opacity='.042'/%3E%3C/svg%3E");

  /* ---- ink: near-neutral, so it never goes muddy over the ice ---- */
  --ink: #101010;
  --ink-soft: #43464A;   /*  8.9:1 */
  --ink-mute: #6E7175;   /*  4.7:1 */
  --line: #DBE3EB;
  --line-strong: #C2CCD6;
  --line-field: #A3A8AD;   /* form rules need to hold up over the ice */

  /* ---- ice ---- */
  --ice: #C3E1F8;        /* reference value — fluid.js and the mass
                            gradients carry it as literal rgb */
  --ice-deep: #4E7E9E;   /*  4.2:1 — strokes, focus, progress */

  --salmon: #FF756C;
  --salmon-ink: #C4483F; /*  4.6:1 */

  /* ---- type ---- */
  --f-display: "Sora", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --f-body: "Host Grotesk", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --f-mono: "Fragment Mono", "SFMono-Regular", Consolas, monospace;

  --t-display: clamp(32px, 3.3vw, 46px);
  --t-lead: clamp(26px, 2.6vw, 36px);
  --t-sub: clamp(18px, 1.45vw, 22px);
  --t-body: 16px;
  --t-small: 14px;
  --t-meta: 13px;
  --t-micro: 12px;

  --display-weight: 500;
  --display-track: -.015em;

  /* ---- grid: one width, one bleed ---- */
  --gutter: clamp(22px, 3vw, 54px);
  --dock-gutter: 62px;   /* ticks + the vertical name beside them */
  /* The measure reserves the dock's lane on BOTH sides, so content
     stays centred and can never reach the nav. 92vw left only ~4vw of
     margin, which is less than the dock occupies — that was the whole
     "it competes with the content" problem. */
  --measure: min(calc(100vw - 2 * (var(--gutter) + var(--dock-gutter))), 1320px);
  /* plates bleed right, but stop short of the dock so the nav never
     lands on top of a dark plate */
  --bleed: max(0px, calc((100vw - var(--measure)) / 2 - var(--dock-gutter)));

  --chrome-h: 72px;
  --rail-h: 74px;
  --screen-top: calc(var(--chrome-h) + var(--rail-h) + clamp(14px, 2.6vh, 34px));
  --screen-bottom: clamp(26px, 4vh, 48px);

  --e-out: cubic-bezier(.22, .78, .22, 1);
  --e-soft: cubic-bezier(.4, 0, .2, 1);
  --e-roll: cubic-bezier(.36, .02, .18, 1);

  /* ---- one line system ----
     thickness = state: 1px available, 2px currently true
     colour    = cause: ink = position/structure, salmon = you did this
     three durations, and one signature reserved for the CTA links   */
  --t-fast: .2s;    /* colour only */
  --t-move: .42s;   /* anything geometric */
  --t-sig: .56s;    /* the tension dip */

  font-family: var(--f-body);
  color: var(--ink);
  background: var(--paper);
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
}

* { box-sizing: border-box; }
body { margin: 0; overflow: hidden; background: var(--paper); }
button, input, textarea, select { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
h1, h2, h3 { font-family: var(--f-display); font-weight: var(--display-weight); letter-spacing: var(--display-track); margin: 0; }
p { margin: 0; }
img, video { display: block; max-width: 100%; }
button, a { -webkit-tap-highlight-color: transparent; }

:focus-visible { outline: 2px solid var(--ice-deep); outline-offset: 3px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: fixed; z-index: 200; left: 16px; top: 12px;
  padding: 10px 15px; background: var(--ink); color: var(--paper); font-size: var(--t-small);
  transform: translateY(-180%); transition: transform .22s var(--e-out);
}
.skip-link:focus { transform: translateY(0); }

/* ============================================================
   ATMOSPHERE
   Five masses, each with its own off-centre home, its own orbit,
   its own rotation, and its own parallax depth. Depth is what
   makes it read as layers instead of one flat glow.
   Transform only — never scale, that re-rasterises the blur.
   ============================================================ */

.atmosphere {
  position: fixed; inset: 0; z-index: 0;
  overflow: hidden; background: var(--paper); pointer-events: none;
  transition: opacity 1.1s var(--e-soft);
}
.mass {
  position: absolute; left: 50%; top: 50%; display: block;
  will-change: transform, opacity;
  transform: translate3d(-50%, -50%, 0);
}
@keyframes breathe { 0%, 100% { opacity: .78; } 50% { opacity: 1; } }

/* ---- the fluid ----
   Low-res sim, blurred hard, multiplied over the paper. The blur is
   doing double duty: it is the matte-glass look, and it is why the
   sim can run at 224px and still look smooth. Oversized so the
   blurred edges fall outside the viewport. */
.fluid {
  position: absolute; left: 50%; top: 50%;
  width: 116%; height: 116%;
  transform: translate(-50%, -50%);
  filter: blur(26px);
  mix-blend-mode: multiply;
  opacity: .92;
}

/* the sheet of paper it is trapped behind — only when the fluid ran,
   otherwise it would just wash out the CSS fallback */
.frost { display: none; }
.has-fluid .frost {
  display: block;
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  background: rgba(247, 250, 253, .40);
}

.grain {
  position: fixed; inset: 0; z-index: 2; pointer-events: none;
  background-image: var(--grain); background-repeat: repeat; background-size: 140px 140px;
}

.progress {
  position: fixed; z-index: 40; inset: 0 0 auto; height: 2px;
  background: rgba(78, 126, 158, .12); pointer-events: none;
}
.progress__fill {
  display: block; height: 100%; width: 100%;
  background: var(--ice-deep); transform: scaleX(0); transform-origin: left center;
  will-change: transform;
}

/* ============================================================
   CHROME
   ============================================================ */

.chrome {
  position: fixed; z-index: 50; inset: 0 0 auto; height: var(--chrome-h);
  display: flex; align-items: center; gap: 20px;
  padding: 0 var(--gutter); pointer-events: none;
}
.chrome > *, .chrome__left > * { pointer-events: auto; }

.chrome__left { display: flex; align-items: center; height: 100%; min-width: 0; }

.wordmark {
  display: inline-flex; align-items: center; overflow: hidden;
  width: 100px; min-height: 44px; flex: 0 0 auto;
  transition: width .46s var(--e-soft), opacity .3s var(--e-soft);
}
.wordmark img { width: 100px; min-width: 100px; height: auto; transition: transform .7s var(--e-out); }
.wordmark:hover img { transform: scaleX(1.012); }

.chrome__divider {
  width: 1px; height: 22px; flex: 0 0 auto; margin-inline: 16px;
  background: var(--line-strong); transform-origin: center;
  transition: width .46s var(--e-soft), margin .46s var(--e-soft),
              opacity .3s var(--e-soft), transform .46s var(--e-soft);
}

.lang { font-family: var(--f-mono); font-size: var(--t-micro); letter-spacing: .02em; }

.lang--inline {
  display: grid; grid-auto-flow: column; grid-auto-columns: max-content;
  max-width: 0; gap: 0; overflow: hidden; opacity: 0;
  clip-path: inset(0 100% 0 0); transform: scaleX(.7); transform-origin: left center;
  transition: max-width .5s var(--e-soft), gap .5s var(--e-soft),
              clip-path .5s var(--e-soft), transform .5s var(--e-soft), opacity .3s var(--e-soft);
}
.lang--inline button {
  position: relative; min-width: 25px; min-height: 34px;
  border: 0; padding: 0; background: none; color: var(--ink-mute); cursor: pointer;
  transition: color .22s var(--e-soft);
}
.lang--inline button::after {
  content: ""; position: absolute; right: 3px; bottom: 6px; left: 3px; height: 2px;
  background: var(--salmon); opacity: 0; transform: scaleX(.25);
  transition: opacity var(--t-fast) var(--e-soft), transform var(--t-move) var(--e-out),
              background-color var(--t-fast) var(--e-soft), height var(--t-fast) var(--e-soft);
}
/* hover shows a thin neutral rule — available. Selected is the 2px salmon
   one, so the two states can't be confused. Selected wins on hover too. */
.lang--inline button:hover { color: var(--ink); }
.lang--inline button:hover::after {
  opacity: 1; transform: scaleX(1); height: 1px; background: var(--ink-mute);
}
.lang--inline button[aria-pressed="true"] { color: var(--ink); }
.lang--inline button[aria-pressed="true"]::after {
  opacity: 1; transform: scaleX(1); height: 2px; background: var(--salmon);
}

.lang--menu {
  position: relative; width: 40px; flex: 0 0 auto; opacity: 1;
  transition: width .46s var(--e-soft), opacity .3s var(--e-soft);
}
.lang__trigger {
  width: 100%; min-height: 34px; display: flex; align-items: center;
  gap: 7px; padding: 5px 2px 5px 0;
  border: 0; background: transparent; color: var(--ink-mute); cursor: pointer;
  transition: color .2s var(--e-soft);
}
.lang__trigger:hover { color: var(--ink); }
.lang__caret {
  width: 5px; height: 5px; border-right: 1px solid currentColor; border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px); transform-origin: center;
  transition: transform .24s var(--e-out);
}
/* Not a dropdown panel — it unfolds sideways into exactly the same
   row the intro uses. One language treatment on the whole site, and
   the open/close is still the morph. */
.lang__options {
  position: absolute; z-index: 60; top: 50%; left: 0; translate: 0 -50%;
  display: flex; align-items: center; gap: 13px;
  padding: 0; border: 0; background: none;
  opacity: 0; clip-path: inset(0 100% 0 0); transform: translateX(-4px);
  transition: opacity .2s var(--e-soft), clip-path .42s var(--e-out), transform .42s var(--e-out);
}
.lang__options[hidden] { display: none; }
.lang__options.is-open { opacity: 1; clip-path: inset(0 0 0 0); transform: none; }
.lang__options button {
  position: relative; min-width: 24px; min-height: 34px; padding: 0;
  border: 0; background: none; cursor: pointer;
  font-family: var(--f-mono); font-size: var(--t-micro); letter-spacing: .02em;
  color: var(--ink-mute); transition: color .2s var(--e-soft);
}
.lang__options button::after {
  content: ""; position: absolute; right: 2px; bottom: 6px; left: 2px; height: 2px;
  background: var(--salmon); opacity: 0; transform: scaleX(.25);
  transition: opacity var(--t-fast) var(--e-soft), transform var(--t-move) var(--e-out),
              background-color var(--t-fast) var(--e-soft), height var(--t-fast) var(--e-soft);
}
.lang__options button:hover { color: var(--ink); }
.lang__options button:hover::after {
  opacity: 1; transform: scaleX(1); height: 1px; background: var(--ink-mute);
}
.lang__options button[aria-checked="true"] { color: var(--ink); }
.lang__options button[aria-checked="true"]::after {
  opacity: 1; transform: scaleX(1); height: 2px; background: var(--salmon);
}

/* the trigger gets out of the way while the row is open */
.lang__trigger[aria-expanded="true"] .lang__current { opacity: 0; }
.lang__trigger[aria-expanded="true"] .lang__caret { opacity: 0; }
.lang__current, .lang__caret { transition: opacity .2s var(--e-soft); }

/* ---- link: underline hugs the label, not the arrow ---- */
.link { display: inline-flex; align-items: center; gap: 7px; min-height: 38px; font-size: var(--t-body); }
.link > span:first-child { position: relative; }
.link > span:first-child::after {
  content: ""; position: absolute; right: 0; bottom: -4px; left: 0; height: 1px;
  background: var(--line-field); transform-origin: right center;
  transition: background-color var(--t-fast) var(--e-soft);
}
.link__arrow { display: inline-block; transition: transform .3s var(--e-out); }
.talk { margin-left: auto; font-family: var(--f-display); white-space: nowrap; }

@media (hover: hover) and (pointer: fine) {
  .link:hover > span:first-child::after { background: var(--salmon); animation: tension var(--t-sig) var(--e-roll) both; }
  .link:hover .link__arrow { transform: translateX(5px); transition-delay: 200ms; }
}
@keyframes tension { 0%, 14% { transform: scaleX(1); } 46% { transform: scaleX(.82); } 100% { transform: scaleX(1); } }
/* a CTA underline flushes salmon once as it reveals, then settles back */
@keyframes reveal-underline {
  0% { background-color: var(--line-field); }
  38% { background-color: var(--salmon); }
  100% { background-color: var(--line-field); }
}

/* ============================================================
   RAIL
   ============================================================ */

.rail {
  position: fixed; z-index: 50; top: calc(var(--chrome-h) - 8px); left: 50%;
  width: min(92vw, 880px); height: var(--rail-h);
  display: grid; grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr);
  gap: clamp(16px, 2vw, 28px); align-items: center;
  transform: translateX(-50%); pointer-events: none;
  opacity: 0; visibility: hidden;
  transition: opacity .42s var(--e-soft), visibility 0s linear .42s,
              filter .95s var(--e-out), transform 1.05s var(--e-out);
}
/* ---- opening: the mark arrives first, on its own, with weight ---- */
body.pre-rail .rail {
  opacity: 0; filter: blur(16px); transform: translateX(-50%) scale(.955);
}
/* ---- and the utilities arrive last, once the sentence has landed ---- */
.chrome__left, .talk { transition: opacity .8s var(--e-out), filter .9s var(--e-out); }
body.pre-chrome .chrome__left,
body.pre-chrome .talk { opacity: 0; filter: blur(7px); pointer-events: none; }
.rail.is-visible { opacity: 1; visibility: visible; transition-delay: 0s; }
.rail__mask { position: relative; height: 56px; overflow: hidden; }
.rail__rule {
  position: relative; overflow: hidden;
  width: 1px; height: 38px; background: var(--line-strong); transition: background-color .3s var(--e-soft);
}
/* the divider flicks salmon once each time the section turns — the same
   gesture as the hero's scroll cue, but a single pass instead of a loop */
.rail__rule::after {
  content: ""; position: absolute; left: 0; top: -14px;
  width: 1px; height: 13px; background: var(--salmon); opacity: 0;
}
.rail.is-turning .rail__rule::after { animation: rail-run .72s var(--e-out) 1; }
@keyframes rail-run {
  0%   { top: -14px; opacity: 0; }
  18%  { opacity: 1; }
  72%  { opacity: 1; }
  100% { top: 100%; opacity: 0; }
}
.rail__item { position: absolute; inset: 0; display: flex; align-items: center; }
.rail__item--title {
  justify-content: flex-end; text-align: right; white-space: nowrap;
  font-family: var(--f-display); font-size: var(--t-display);
  font-weight: var(--display-weight); letter-spacing: var(--display-track); line-height: 1;
}
.rail__item--desc {
  justify-content: flex-start;
  font-family: var(--f-mono); font-size: var(--t-meta); line-height: 1.35; letter-spacing: .01em;
  translate: 0 3px;
}
.rail__mark { display: block; width: 132px; height: auto; }

/* ============================================================
   DOCK — the navigation, and the length cue the hidden
   scrollbar took away. Sits in the right gutter, which is why
   the plate bleed stops short of it.
   ============================================================ */

.dock {
  position: fixed; z-index: 45; top: 50%; right: clamp(12px, 1.5vw, 22px);
  transform: translateY(-50%);
  display: flex; align-items: center; gap: 13px;
}
/* Set vertically, so the name costs ~15px of width instead of 132.
   Always on, and it swaps to whichever tick you're pointing at — the
   feedback happens where your eye already is, not in the header. */
/* Off by default. It appears while you're pointing at the dock, and
   peeks for a moment when the section changes — enough to orient you,
   not enough to become furniture. */
/* Only while you're pointing at a specific tick, and it names THAT
   tick. Showing the current section permanently just repeated what
   the rail already says. */
.dock__name {
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-family: var(--f-mono); font-size: 11px; letter-spacing: .14em;
  color: var(--ink-mute); white-space: nowrap; pointer-events: none;
  opacity: 0; transition: opacity .22s var(--e-soft);
}
.dock.is-naming .dock__name { opacity: 1; }
.dock__ticks { display: grid; gap: 1px; justify-items: end; }
.dock a {
  display: flex; align-items: center; justify-content: flex-end;
  height: 28px; width: 40px;
}
/* Names live in the accessibility tree only. Showing them here needed
   132px of gutter and there are 58 — so instead, hovering a tick makes
   the centred rail preview that section. The rail is already the naming
   device, it's always over empty background, and it can never collide. */
.dock__label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap;
}
/* No colour here. Navigation isn't an accent — salmon is reserved for
   moments that mean something. Position is carried by length and
   darkness instead, which is a stronger signal anyway. */
.dock__tick {
  width: 16px; height: 1px; flex: 0 0 auto; background: var(--line-field);
  transition: width var(--t-move) var(--e-out), background-color var(--t-fast) var(--e-soft), height var(--t-fast) var(--e-soft);
}
/* Length carries position, not weight. A 2px full-ink bar was pulling
   the eye away from the page it was meant to be indexing. */
.dock a[aria-current] .dock__tick { width: 28px; background: var(--ink-soft); }
/* the tick itself is the focus indicator — a blue box around a hairline
   looked like a rendering fault */
.dock a:focus-visible { outline: none; }
.dock a:focus-visible .dock__tick { width: 30px; height: 2px; background: var(--ink); }

@media (hover: hover) and (pointer: fine) {
  .dock a:hover .dock__tick { width: 24px; background: var(--ink-mute); }
  .dock a[aria-current]:hover .dock__tick { width: 28px; background: var(--ink); }
}

/* ============================================================
   SCREENS
   ============================================================ */

/* No scrim over the background — the fluid runs edge to edge, all the
   way behind the header. Instead the scroller itself is masked, so any
   content approaching the rail dissolves before it can collide with it.
   At rest nothing is affected: content starts below --screen-top, well
   past the end of the fade. */
#site-scroll {
  position: relative; z-index: 3; height: 100svh;
  overflow-y: auto; overflow-x: hidden;
  scroll-snap-type: y mandatory;
  overscroll-behavior-y: contain;
  scrollbar-width: none; scroll-behavior: smooth;
  --fade: linear-gradient(to bottom,
    transparent 0,
    rgba(0, 0, 0, .06) calc(var(--chrome-h) * .5),
    rgba(0, 0, 0, .38) calc(var(--chrome-h) + 22px),
    rgba(0, 0, 0, .82) calc(var(--chrome-h) + var(--rail-h) - 6px),
    #000 calc(var(--chrome-h) + var(--rail-h) + 16px));
  -webkit-mask-image: var(--fade);
  mask-image: var(--fade);
}
#site-scroll::-webkit-scrollbar { display: none; }

.screen {
  position: relative; width: 100%; min-height: 100svh;
  scroll-snap-align: start;
  /* one gesture = one screen. Momentum can't carry you past a section. */
  scroll-snap-stop: always;
  display: flex; flex-direction: column; justify-content: center;
  padding: var(--screen-top) var(--gutter) var(--screen-bottom);
}
.head { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }

/* ============================================================
   INTRO
   ============================================================ */

.intro__inner { width: var(--measure); margin-inline: auto; }
/* Equal columns so the three rules are the same length. max-content
   fixed the off-centre problem but left the most graphic element on
   the page — three pure hairlines — ragged, which is what made the
   composition feel unresolved. Constraining the group keeps it centred. */
.services {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  width: min(100%, 1140px); margin-inline: auto;
  gap: clamp(30px, 4.6vw, 88px);
}
/* centred, so the middle block sits on the same axis as the rail's
   divider — and so the whole screen has one alignment logic instead
   of centred rail / left blocks / centred sentence */
.service { position: relative; display: block; text-align: center; }
.service h2 {
  margin: 0 0 20px; font-size: clamp(29px, 3.05vw, 43px); line-height: 1.02;
}
/* The rule sits BETWEEN the name and the description. Above the block
   it was a header rule with no header — the eye kept looking for the
   thing that was missing. Here it has content on both sides. */
.service h2::after {
  content: ""; display: block; width: 48px; height: 1px; margin: 20px auto 0;
  background: var(--line-field);
  transition: width var(--t-move) var(--e-out), background-color var(--t-fast) var(--e-soft);
}
.service p {
  font-size: clamp(16px, 1.25vw, 18px); line-height: 1.45; color: var(--ink-soft);
  transition: color .3s var(--e-soft);
}

/* Nothing greys out and nothing moves. The rule extends and turns, the
   copy comes up to full ink — the block gets brighter, its neighbours
   are simply left alone. */
@media (hover: hover) and (pointer: fine) {
  .service:hover h2::after { width: 96px; background: var(--salmon); }
  .service:hover p { color: var(--ink); }
}
.service:focus-visible h2::after { width: 96px; background: var(--salmon); }

/* Deliberately below the service names (up to 38px) so it reads as the
   closing line rather than a fourth heading. It gets its scale from the
   opening sequence instead. */
.intro__note {
  margin: clamp(64px, 11vh, 132px) auto 0; max-width: 44ch; text-align: center;
  font-family: var(--f-display); font-size: clamp(20px, 1.65vw, 25px);
  font-weight: 400; letter-spacing: -.012em; line-height: 1.3;
  /* the opening sequence drives these three vars; they rest at identity */
  transform: translate(var(--nx, 0px), var(--ny, 0px)) scale(var(--ns, 1));
  transition: transform 1s var(--e-out), opacity .65s var(--e-out), filter .8s var(--e-out);
}
/* just the cue — the location line lives in the footer, and the hero
   doesn't need to say it twice */
.intro__foot {
  position: absolute; left: 50%; bottom: clamp(24px, 4.2vh, 48px); translate: -50% 0;
  display: grid; justify-items: center;
}

/* Wordless on purpose — four languages, and a travelling mark says it
   better than a verb anyway. Built from the same parts as everything
   else: a hairline and one salmon segment. */
.cue { display: block; position: relative; width: 1px; height: 44px; overflow: hidden; background: var(--line-strong); }
.cue__run { position: absolute; left: 0; width: 1px; height: 15px; background: var(--salmon); }
@keyframes cue-run {
  0%   { top: -16px; opacity: 0; }
  18%  { opacity: 1; }
  72%  { opacity: 1; }
  100% { top: 44px; opacity: 0; }
}
.motion-ready .cue__run { animation: cue-run 2.6s var(--e-soft) infinite; }

/* ============================================================
   OPENING SEQUENCE
   wordmark + descriptor  →  the sentence, large, centred
   →  it settles into place  →  the three services bloom in
   Starts in markup so there is no flash before the script runs.
   ============================================================ */

/* the sentence arrives with the same verb as everything else, so the
   opening reads as one gesture rather than two ideas */
.intro.is-priming .intro__note { opacity: 0; filter: blur(12px); }

.intro .service, .intro .intro__foot {
  transition: opacity .85s var(--e-out), filter 1s var(--e-out), transform .85s var(--e-out);
}
.intro.is-opening .service {
  opacity: 0; filter: blur(16px); transform: translateY(12px);
}
.intro.is-opening .intro__foot { opacity: 0; filter: blur(8px); }
.intro .service:nth-child(2) { transition-delay: 90ms; }
.intro .service:nth-child(3) { transition-delay: 180ms; }
.intro .intro__foot { transition-delay: 300ms; }

/* ============================================================
   WORK — geometry follows the medium
   ============================================================ */

.work__layout {
  width: var(--measure); margin-inline: auto;
  display: grid; grid-template-columns: minmax(230px, 300px) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 64px); align-items: center;
}
.work__copy { max-width: 300px; }
/* The 01/03 counter is hidden. The markup and the JS that fills it stay, so
   restoring it is a one-line change, and the gallery still carries
   aria-label="<name>, project N of M", so screen readers keep the position. */
.work__count { display: none; }
.work__count--shown { font-family: var(--f-mono); font-size: var(--t-micro); margin-bottom: 14px; letter-spacing: .04em; }
.work__now { color: var(--salmon-ink); }
.work__of { color: var(--ink-mute); }
.work__name { font-size: var(--t-lead); line-height: 1.04; margin-bottom: 10px; }
/* ready slot for "2025 · Luxembourg · Identity, Site, CRM" */
.work__meta { font-family: var(--f-mono); font-size: var(--t-micro); letter-spacing: .05em; color: var(--ink-mute); margin-bottom: 16px; }
.work__meta:empty { display: none; }
.work__desc { font-size: var(--t-body); line-height: 1.55; color: var(--ink-soft); }

/* Unboxed. The site has no bordered controls anywhere else — form
   fields, topic toggles and links are all text over a hairline — so
   a 1px box read as a component library dropped into the page. */
.work__nav { display: flex; align-items: center; gap: 6px; margin-top: clamp(20px, 3vh, 34px); margin-left: -10px; }
.step {
  position: relative; width: 44px; height: 44px; padding: 0;
  border: 0; background: none; cursor: pointer;
}
.step span { position: absolute; inset: 0; transition: transform var(--t-move) var(--e-out); }
.step span::before {
  content: ""; position: absolute; top: 50%; width: 17px; height: 1px; background: var(--ink);
  transition: width var(--t-move) var(--e-out), transform var(--t-move) var(--e-out), background-color var(--t-fast) var(--e-soft);
}
.step span::after {
  content: ""; position: absolute; top: 50%; width: 5px; height: 5px; border-top: 1px solid var(--ink);
  transition: transform var(--t-move) var(--e-out), border-color var(--t-fast) var(--e-soft);
}
.step--prev span::before { right: 13px; transform: translateY(-.5px); }
.step--prev span::after { left: 13px; border-left: 1px solid var(--ink); transform: translateY(-50%) rotate(-45deg); }
.step--next span::before { left: 13px; transform: translateY(-.5px); }
.step--next span::after { right: 13px; border-right: 1px solid var(--ink); transform: translateY(-50%) rotate(45deg); }
/* The shaft and the head were being animated separately — the shaft
   moved 3px and grew, the head moved 6px, so the arrow came apart
   mid-hover. Now the whole mark travels as one object. */
.step--prev:hover span { transform: translateX(-5px); }
.step--next:hover span { transform: translateX(5px); }
.step:focus-visible { outline-offset: -6px; }

.plates { display: grid; touch-action: pan-y; }
.plate {
  position: relative; overflow: hidden; margin: 0;
  background-color: var(--plate, var(--paper-sunk));
  background-image: var(--plate-img, none);
  background-size: cover; background-position: center top;
  transition: background-color .5s var(--e-soft);
}
/* a real screenshot needs its caption lifted off the picture; a flat
   colour does not, so only photo plates get the scrim */
.plate[data-photo] figcaption {
  background: rgba(247, 250, 253, .86); padding: 3px 8px; left: 10px; bottom: 10px;
}
.plate::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: var(--grain); background-size: 140px 140px; opacity: .55;
}
.plate figcaption {
  position: absolute; z-index: 1; left: 14px; bottom: 12px;
  font-family: var(--f-mono); font-size: var(--t-micro); letter-spacing: .04em;
  color: var(--plate-ink, var(--ink-soft)); opacity: .8;
}

/* BRAND — print sheets. Contained, tall, standing on one baseline. */
.work--brand .plates {
  width: 100%; grid-template-columns: repeat(3, 1fr);
  gap: clamp(10px, 1.3vw, 18px); align-items: end;
  height: min(56vh, 520px);
}
.work--brand .plate--a { height: 100%; }
.work--brand .plate--b { height: 86%; }
.work--brand .plate--c { height: 71%; }

/* WEBSITES — screens. One wide, two stacked, running off the right edge. */
.work--websites .plates {
  width: calc(100% + var(--bleed));
  grid-template-columns: 1.9fr 1fr; grid-template-rows: auto auto; gap: 10px;
}
.work--websites .plate--a { grid-area: 1 / 1 / 3 / 2; height: 100%; }
.work--websites .plate--b,
.work--websites .plate--c { aspect-ratio: 16 / 10; }

/* AI & SOFTWARE — a system. Modular, equal gutters, no drama. */
.work--software .plates {
  width: calc(100% + var(--bleed));
  grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(2, 1fr);
  gap: 8px; height: min(52vh, 480px);
}
.work--software .plate--a { grid-area: 1 / 1 / 3 / 3; }
.work--software .plate--b { grid-area: 1 / 3 / 2 / 4; }
.work--software .plate--c { grid-area: 2 / 3 / 3 / 4; }

/* ============================================================
   PROCESS
   ============================================================ */

.steps {
  list-style: none; margin: 0 auto; padding: 0; width: var(--measure);
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: clamp(24px, 3.4vw, 56px);
}
.step-card { display: grid; grid-template-rows: 82px auto auto; align-content: start; }
/* hand-drawn illustrations. Sized by height so the four sit on one
   baseline regardless of how wide each drawing happens to be. */
.step-card__icon { width: auto; height: 68px; object-fit: contain; object-position: left bottom; }
.step-card h3 { font-size: var(--t-sub); line-height: 1.16; margin: 16px 0 9px; }
.step-card p { font-size: var(--t-small); line-height: 1.5; color: var(--ink-soft); max-width: 24ch; }

.process__foot {
  width: var(--measure); margin: clamp(28px, 5vh, 54px) auto 0;
  padding-top: 18px; border-top: 1px solid var(--line);
  display: flex; align-items: baseline; justify-content: space-between; gap: 28px; flex-wrap: wrap;
}
.process__note { font-family: var(--f-mono); font-size: var(--t-micro); color: var(--ink-mute); letter-spacing: .03em; }

/* ============================================================
   ABOUT — two people on the sides, one story in the middle
   ============================================================ */

.about__layout {
  width: var(--measure); margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(170px, 262px) minmax(0, 1fr) minmax(170px, 262px);
  gap: clamp(30px, 5vw, 92px); align-items: start;
}

/* the story has a shape: an opening, two facts, a conclusion.
   margin-inline auto keeps the capped block centred on the rail,
   not parked against the left of its column. */
.about__story { max-width: 54ch; margin-inline: auto; padding-top: clamp(0px, 1.2vh, 14px); }
.about__story p { font-size: var(--t-small); line-height: 1.62; color: var(--ink-soft); margin-bottom: 1.15em; }
.about__story p.about__lede { font-size: var(--t-body); color: var(--ink); }
.about__story p.about__claim {
  margin: clamp(20px, 3vh, 34px) 0 0;
  padding-top: clamp(16px, 2.4vh, 26px); border-top: 1px solid var(--line);
  font-family: var(--f-display); font-size: var(--t-sub); font-weight: 400;
  letter-spacing: -.014em; line-height: 1.24; color: var(--ink);
}

.who__photo {
  position: relative;
  aspect-ratio: 6 / 7; background-repeat: no-repeat; background-size: cover;
  background-position: center 8%; background-color: var(--paper-sunk);
}
/* a drawn moustache lands on Dasha on hover. Pointer devices only: on
   touch there is no hover to leave, so it would simply stick on. */
@media (hover: hover) and (pointer: fine) {
  .who--b .who__photo::after {
    content: ""; position: absolute; z-index: 1; pointer-events: none;
    left: 50%; top: 52%; width: 28%; aspect-ratio: 428 / 170;
    background: url("assets/moustache.png") center / contain no-repeat;
    transform: translate(-50%, -50%) scale(.82) rotate(-2deg);
    opacity: 0;
    transition: opacity .16s var(--e-soft), transform .34s var(--e-out);
  }
  .who--b:hover .who__photo::after {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1) rotate(-2deg);
  }
}
.who--a .who__photo { background-image: url("assets/portrait-lex-blue.png"); }
.who--b .who__photo { background-image: url("assets/portrait-dariia-blue.png"); }
.who h3 { display: flex; align-items: center; gap: 8px; font-size: var(--t-sub); margin: 16px 0 7px; flex-wrap: wrap; }
/* flag + LinkedIn stay one cluster, so a long name drops them to the next
   line together instead of stranding the icon alone (e.g. "Dariia Urvachova") */
.who__tags { display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.who__role { font-family: var(--f-mono); font-size: var(--t-micro); color: var(--ink-mute); line-height: 1.55; letter-spacing: .02em; }

.flag { width: 20px; height: 13px; flex: 0 0 auto; box-shadow: inset 0 0 0 .5px rgba(16, 16, 16, .18); }
.flag--lu { background: linear-gradient(to bottom, #EF3340 0 33.33%, #fff 33.33% 66.66%, #00A3E0 66.66% 100%); }
.flag--ua { background: linear-gradient(to bottom, #0057B7 0 50%, #FFD700 50% 100%); }

.who__in { width: 26px; height: 26px; display: inline-grid; place-items: center; flex: 0 0 auto; color: var(--ink-mute); transition: color .2s var(--e-soft); }
.who__in svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.who__in:hover { color: var(--ink); }

/* belongs to the whole studio, not to whoever it happens to sit under */
.prior { width: var(--measure); margin: clamp(24px, 4.5vh, 52px) auto 0; text-align: center; }
.prior__label { font-family: var(--f-mono); font-size: var(--t-micro); letter-spacing: .06em; color: var(--ink-mute); margin-bottom: 13px; }
.prior__row { display: flex; align-items: center; justify-content: center; gap: clamp(22px, 4vw, 60px); flex-wrap: wrap; padding-top: 15px; border-top: 1px solid var(--line); }
/* clean, readable greyscale — desaturate but keep tonal contrast so each
   mark stays legible (contrast(0) flattened them into faint blobs before) */
.prior__logo { width: auto; height: 20px; object-fit: contain; filter: grayscale(1); opacity: .58; }
.prior__logo--kpmg { height: 21px; }
.prior__logo--db { height: 17px; }
.prior__logo--climada { height: 24px; }
.prior__logo--reddit { height: 21px; opacity: .5; }
.prior__logo--delft { height: 25px; }

/* ============================================================
   NON-PROFITS — the video is the hero, the caption is a footnote
   ============================================================ */

/* All three sit on one baseline. The heights differ because the formats
   differ — which reads as a decision, where one arbitrary drop read as
   a mistake. */
.causes__layout {
  width: var(--measure); margin-inline: auto;
  display: grid; grid-template-columns: 1.55fr .66fr .66fr minmax(168px, 220px);
  gap: clamp(12px, 1.5vw, 20px); align-items: end;
}
.causes__reel { display: contents; }
.cause__media { position: relative; overflow: hidden; background: var(--paper-sunk); }
.cause__media::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: var(--grain); background-size: 140px 140px; opacity: .55;
}
.cause__media video { width: 100%; height: 100%; object-fit: cover; }
.cause--wide .cause__media { aspect-ratio: 5 / 4; }
.cause--reel .cause__media { aspect-ratio: 9 / 16; }
.cause__meta { padding-top: 11px; }
.cause__meta h3 { font-size: var(--t-small); line-height: 1.3; }
.cause__meta h3 a { border-bottom: 1px solid var(--line-field); padding-bottom: 2px; transition: border-color var(--t-fast) var(--e-soft); }
.cause__meta h3 a:hover { border-color: var(--salmon); }
.cause__meta p { margin-top: 5px; font-family: var(--f-mono); font-size: var(--t-micro); color: var(--ink-mute); letter-spacing: .02em; }

.causes__aside { align-self: center; padding-left: clamp(6px, 1.2vw, 18px); }
.causes__aside p {
  font-family: var(--f-display); font-size: var(--t-sub); font-weight: 400;
  letter-spacing: -.012em; line-height: 1.28; margin-bottom: 20px;
}

/* ============================================================
   CONTACT
   ============================================================ */

/* Two columns for the short fields. Single-column was calmer but ran
   ~150px taller than the screen, which pushed the form up under the
   rail and the footer off the bottom. The auto margins split the
   leftover space so the footer is always parked at the base. */
.contact { justify-content: flex-start; }
.contact__layout { width: min(var(--measure), 720px); margin: auto; }

#contact-form { display: grid; gap: clamp(12px, 1.7vh, 19px); }
.field { display: grid; gap: 5px; }
/* not mono: mono is for small accents, and at 12px grey these were
   the least readable thing on the site */
.field > span, .topics legend {
  font-family: var(--f-body); font-size: var(--t-small);
  letter-spacing: 0; color: var(--ink-soft);
}
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(16px, 2vw, 28px); }
/* A translucent fill so the field has a visible area to aim at. An
   underline alone gave no target on top of the moving background. */
input, textarea {
  width: 100%; padding: 11px 13px 10px; border: 0;
  border-bottom: 1px solid var(--ink-mute);
  background: rgba(255, 255, 255, .55); font-size: var(--t-sub); color: var(--ink);
  transition: border-color var(--t-fast) var(--e-soft), background-color var(--t-fast) var(--e-soft);
}
input { height: 48px; }
textarea { height: 94px; resize: vertical; }
/* helper text steps back so it can't be mistaken for a filled value */
/* was #A8ADB2 (2.15:1 — fails AA); ink-mute clears 4.5:1 and still reads
   clearly lighter than the near-black typed value */
input::placeholder, textarea::placeholder { color: var(--ink-mute); font-size: var(--t-body); }
input:hover, textarea:hover { border-bottom-color: var(--ink-soft); background: rgba(255, 255, 255, .74); }
/* the focus state is the field itself, not a browser box around it */
input:focus, input:focus-visible,
textarea:focus, textarea:focus-visible {
  outline: none;
  border-bottom-color: var(--ink); border-bottom-width: 2px; padding-bottom: 10px;
  background: rgba(255, 255, 255, .88);
}
input[aria-invalid="true"], textarea[aria-invalid="true"] { border-bottom-color: var(--salmon-ink); }

/* A rule under a word doesn't say "tick me", and four of them in a row
   said "pick one" when you can pick several. A real box does both. */
.topics { border: 0; padding: 0; margin: 0; display: grid; gap: 11px; }
.topics__row { display: flex; flex-wrap: wrap; gap: 10px 30px; }
.topic { position: relative; display: inline-block; }
.topic input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.topic span {
  display: inline-flex; align-items: center; gap: 10px; padding: 7px 0;
  font-size: var(--t-body); color: var(--ink-soft); cursor: pointer;
  transition: color .2s var(--e-soft);
}
.topic span::before {
  content: ""; width: 14px; height: 14px; flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px var(--ink-mute);
  transition: background-color var(--t-fast) var(--e-soft), box-shadow var(--t-fast) var(--e-soft);
}
.topic span:hover { color: var(--ink); }
.topic span:hover::before { box-shadow: inset 0 0 0 1px var(--ink); }
.topic input:checked + span { color: var(--ink); }
.topic input:checked + span::before { background: var(--salmon); box-shadow: inset 0 0 0 1px var(--salmon); }
.topic input:focus-visible + span::before { outline: 2px solid var(--ice-deep); outline-offset: 3px; }

.form__foot { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 6px; }
/* The floor of the form stands up.
   It was going ink → salmon, which meant two things changed at once
   and introduced the only black outline on the site. Now it is salmon
   the whole way: one object, one property, a line becoming a plane. */
/* The <button> is the hit area and NEVER moves. Only .send__face lifts.
   Transforming the button itself meant that hovering near its bottom
   edge moved the element out from under the cursor, which ended the
   hover, which dropped it back under the cursor — an infinite loop. */
.send {
  position: relative; display: block; width: 100%; height: 54px;
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--ink); font-size: var(--t-sub);
}
.send__face {
  position: relative; display: flex; align-items: center; justify-content: space-between;
  width: 100%; height: 100%; padding: 0 14px; overflow: hidden;
  transition: transform .32s var(--e-out), box-shadow .32s var(--e-out);
}
/* --fill is driven by how much of the form is actually valid, so the
   line rises as you complete it. Hover still previews the full state. */
.send__face::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--fill, 2px);
  background: var(--salmon);
  /* slow, and eased at both ends, so it rises like a liquid finding its
     level rather than snapping up. Hover overrides with the quick
     duration, because that's a response to you, not progress. */
  transition: height var(--fill-time, 1.5s) cubic-bezier(.45, .05, .2, 1);
}
.send__face > span { position: relative; z-index: 1; }
.send__icon { display: inline-block; transition: transform var(--t-move) var(--e-out); }
.send:hover .send__face::before,
.send:focus-visible .send__face::before { height: 100%; transition-duration: var(--t-move); }
.send:hover .send__icon { transform: translate(3px, -3px); }
.send:active .send__face::before { height: 100%; }
/* one beat when the last required field becomes valid — the same
   diagonal the arrow already travels on hover, done once */
/* One small movement instead of an effect — the same lift as hover, at
   about half the distance. It reads as the button asking to be pressed. */
@keyframes nudge { 0% { transform: none; } 46% { transform: translateY(-5px); } 100% { transform: none; } }
/* lands after the 2.6s fill has finished, not on top of it */
.send.is-ready .send__face { animation: nudge .8s var(--e-out) 2.85s; }

/* smaller still, for when an optional field gets answered */
@keyframes nudge-sm { 0% { transform: none; } 46% { transform: translateY(-2px); } 100% { transform: none; } }
.send.is-nudge .send__face { animation: nudge-sm .46s var(--e-out); }


/* once it's full it lifts toward you — the one shadow on the site,
   on the one control that most wants to be pressed */
.send.is-ready:hover .send__face {
  transform: translateY(-3px);
  box-shadow: 0 12px 28px rgba(196, 72, 63, .26);
}
.send.is-ready:active .send__face { transform: translateY(-1px); box-shadow: 0 5px 14px rgba(196, 72, 63, .22); }
.send.is-sent .send__face::before { height: 100%; }
.send.is-sent .send__icon { transform: none; }
.form__status { font-family: var(--f-mono); font-size: var(--t-micro); color: var(--ink-soft); line-height: 1.45; min-height: 16px; }

.foot {
  width: var(--measure); margin: auto auto 0;
  display: flex; align-items: center; gap: 15px; flex-wrap: wrap;
  padding-top: 18px; border-top: 1px solid var(--line);
}
.foot__mark { display: inline-flex; align-items: center; min-height: 36px; }
.foot__mark img { width: 78px; height: auto; }
.foot__divider { width: 1px; height: 20px; background: var(--line-strong); }
.foot__desc { font-family: var(--f-mono); font-size: var(--t-micro); color: var(--ink-mute); letter-spacing: .03em; }
.foot__mail { margin-left: clamp(12px, 3vw, 44px); font-size: var(--t-body); }
.foot__mail span { position: relative; }
.foot__mail span::after {
  content: ""; position: absolute; right: 0; bottom: -4px; left: 0; height: 1px;
  background: var(--line-field); transition: background-color var(--t-fast) var(--e-soft);
}
.foot__mail:hover span::after { background: var(--salmon); }
.foot__legal {
  margin-left: auto; text-align: right;
  font-family: var(--f-mono); font-size: var(--t-micro); color: var(--ink-mute); letter-spacing: .03em; line-height: 1.6;
}
.foot__legal span { display: block; }

/* ============================================================
   ENTRANCE — content arrives out of focus and settles
   ============================================================ */

/* Focus only. The 8px lift was a second idea saying the same thing as
   the blur, and it cost a transform on every arriving element while a
   fluid sim is running. One verb, done properly. */
.motion-ready .screen [data-rise] {
  opacity: 0; filter: blur(9px);
  transition: opacity .72s var(--e-out), filter .82s var(--e-out);
}
.motion-ready .screen.is-active [data-rise] { opacity: 1; filter: blur(0); }
.motion-ready .screen.is-active [data-rise="2"] { transition-delay: 80ms; }
.motion-ready .screen.is-active [data-rise="3"] { transition-delay: 160ms; }
.motion-ready .screen.is-active [data-rise="4"] { transition-delay: 240ms; }

/* The icons are drawings now, not paths, so they can't be stroke-drawn.
   They settle in instead — same cascade, same timings as the old draw. */
@keyframes settle { to { opacity: 1; transform: none; } }
.motion-ready .step-card__icon { opacity: 0; transform: translateY(7px); }
.motion-ready .process.is-drawn .step-card__icon { animation: settle .8s var(--e-out) both; }
.motion-ready .process.is-drawn .step-card:nth-child(1) .step-card__icon { animation-delay: 150ms; }
.motion-ready .process.is-drawn .step-card:nth-child(2) .step-card__icon { animation-delay: 280ms; }
.motion-ready .process.is-drawn .step-card:nth-child(3) .step-card__icon { animation-delay: 410ms; }
.motion-ready .process.is-drawn .step-card:nth-child(4) .step-card__icon { animation-delay: 540ms; }

/* Portraits arrive toward each other: each wipes open from its own
   outer edge inward, and the crop drifts to centre as it lands.
   Two people, from opposite sides, meeting in the middle. */
@media (prefers-reduced-motion: no-preference) {
  .motion-ready .who__photo {
    transition: clip-path 1.05s var(--e-out), background-position 1.7s var(--e-out), opacity .5s var(--e-out);
  }
  .motion-ready .about:not(.is-converged) .who__photo { opacity: 0; }
  .motion-ready .about:not(.is-converged) .who--a .who__photo {
    clip-path: inset(0 100% 0 0); background-position: calc(50% - 40px) 8%;
  }
  .motion-ready .about:not(.is-converged) .who--b .who__photo {
    clip-path: inset(0 0 0 100%); background-position: calc(50% + 40px) 8%;
  }
  .motion-ready .about .who__photo { clip-path: inset(0 0 0 0); }
  .motion-ready .about.is-converged .who--b .who__photo { transition-delay: 130ms; }
}

body.is-intro .wordmark { width: 0; opacity: 0; pointer-events: none; }
body.is-intro .chrome__divider { width: 0; margin-inline: 0; opacity: 0; transform: scaleY(0); }
body.is-intro .lang--menu { width: 0; overflow: hidden; opacity: 0; pointer-events: none; }
body.is-intro .lang--inline { max-width: 130px; gap: 7px; clip-path: inset(0 0 0 0); transform: scaleX(1); opacity: 1; }

/* ============================================================
   RESPONSIVE
   ============================================================ */

@media (max-width: 1080px) {
  :root {
    --chrome-h: 62px; --rail-h: 62px;
    --t-display: clamp(24px, 4.4vw, 32px);
    --screen-bottom: 40px;
    --dock-gutter: 0px;   /* dock lies along the bottom here */
  }
  .rail__mask { height: 46px; }
  .rail__rule { height: 30px; }
  .screen { min-height: 100svh; height: auto; }

  /* dock lies down along the bottom on small screens */
  /* grid-auto-flow was being set on .dock, but .dock is a flex row and
     the ticks live in .dock__ticks — so the strip stayed a vertical
     column standing up the middle of the screen */
  .dock {
    top: auto; bottom: 12px; right: 50%; transform: translateX(50%);
    align-items: center; gap: 0;
    transition: opacity .3s var(--e-soft);
  }
  /* the tall contact form lands under the bottom dock; fade it out on the
     last section so Send is never crossed or intercepted (JS toggles this) */
  .dock.is-hidden { opacity: 0; pointer-events: none; }
  .dock__ticks {
    grid-auto-flow: column; align-items: center; justify-items: center; gap: 5px;
  }
  .dock a { height: 44px; width: 30px; justify-content: center; }

  /* 44px minimum tap targets. Measured at 500px these were 26x26
     (LinkedIn), 21px tall (non-profit links) and 25x34 (languages). */
  .link { min-height: 44px; }
  .lang--inline button, .lang__options button { min-width: 34px; min-height: 44px; }
  .who__in { width: 44px; height: 44px; margin: -9px -10px -9px -12px; }
  .cause__meta h3 a { display: inline-block; padding: 11px 0; }
  .cause__meta { padding-top: 2px; }
  .foot__mail { min-height: 44px; display: inline-flex; align-items: center; }
  /* keep the label sr-only (not display:none) so each dock link still has
     an accessible name in the a11y tree on touch devices */
  .dock__name { display: none; }   /* hover-only, and there is no hover here */
  .dock__tick { width: 100%; }
  .dock a[aria-current] .dock__tick { width: 100%; }

  /* three columns held down to ~620px; the phone band now starts at 767px — measured at 968px the single
     column left 910px of empty width and put the closing line on one
     unwrapped 910px line */
  .services { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(18px, 3vw, 40px); }
  .service h2 { font-size: clamp(21px, 3.3vw, 30px); }
  .service h2::after { margin-top: 14px; width: 38px; }
  .service p { font-size: 15px; line-height: 1.4; }
  .intro__note { margin-top: clamp(34px, 5vh, 56px); max-width: 34ch; text-align: center; }
  .intro__foot { position: static; translate: none; margin-top: 34px; }

  .work__layout { grid-template-columns: 1fr; gap: 24px; align-items: start; }
  .work__copy { max-width: 520px; }
  .work--brand .plates,
  .work--websites .plates,
  .work--software .plates {
    width: calc(100% + var(--gutter)); height: auto;
    grid-template-columns: 74vw 60vw 60vw; grid-template-rows: none;
    overflow-x: auto; scroll-snap-type: x mandatory; align-items: start; gap: 10px;
  }
  /* A fixed collage, not a scroll row. Two horizontal gestures meant two
     different things — swipe = next image, arrows = next project — with
     nothing to tell them apart. Now horizontal means one thing: swipe
     anywhere on the block for the next project. */
  .work--brand .plates,
  .work--websites .plates,
  .work--software .plates {
    width: 100%; height: auto; overflow: visible;
    scroll-snap-type: none; gap: 8px;
  }
  .plate { scroll-snap-align: none; aspect-ratio: auto; grid-area: auto; }

  .work--brand .plates { grid-template-columns: 1.2fr 1fr; grid-template-rows: auto auto; }
  .work--brand .plate--a { grid-area: 1 / 1 / 3 / 2; height: 100%; }
  .work--brand .plate--b,
  .work--brand .plate--c { height: min(16vh, 128px); }

  .work--websites .plates { grid-template-columns: 1fr 1fr; }
  .work--websites .plate--a { grid-column: 1 / -1; height: min(23vh, 180px); }
  .work--websites .plate--b,
  .work--websites .plate--c { height: min(14vh, 112px); }

  .work--software .plates { grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; }
  .work--software .plate--a { grid-area: 1 / 1 / 3 / 2; height: 100%; }
  /* reset the desktop column-3 placement (specificity beats the generic
     .plate { grid-area: auto } reset above) so b/c auto-flow into col 2 */
  .work--software .plate--b,
  .work--software .plate--c { grid-area: auto; height: min(16vh, 128px); }

  /* the four sketches sat at four different heights because each drawing
     fills its 88x88 box differently — a shared baseline settles them */
  .step-card { grid-template-rows: 58px auto auto; }
  .step-card__icon { align-self: end; }
  .step-card h3 br { display: none; }
  .step-card h3 { font-size: 19px; margin: 14px 0 7px; }
  .steps { gap: 30px 26px; }
  .plate { scroll-snap-align: start; }

  .steps { grid-template-columns: 1fr 1fr; gap: 26px 30px; }
  .step-card { grid-template-rows: 62px auto auto; }
  .step-card__icon { height: 52px; }

  .about__story p { font-size: 13px; line-height: 1.5; margin-bottom: .9em; }
  .prior { margin-top: clamp(16px, 2.4vh, 28px); }
  .about__story { grid-column: 1 / -1; grid-row: 2; max-width: none; padding-top: 0; }

  /* all three side by side with a capped height. aspect-ratio was
     deriving 796px reels from a 448px column — the section measured
     2027px against a 670px viewport */

  .foot { flex-wrap: wrap; }
  .foot__legal { margin-left: 0; width: 100%; }
}

/* ---- phones ----
   The rail's title is nowrap, and at 390px the title column is 165px
   while "For Non-Profits" needs 216px — it overflowed. Below 767px the
   rail drops to the title alone, centred and full width. The descriptor
   can't fit anyway: the longest French one is 68 characters. */
@media (max-width: 767px) {
  :root { --t-display: clamp(20px, 5.6vw, 26px); --rail-h: 56px; }
  .rail { grid-template-columns: minmax(0, 1fr); width: min(94vw, 560px); }
  .rail__rule, .rail__mask--desc { display: none; }
  .rail__mask { height: 40px; }
  .rail__item--title { justify-content: center; text-align: center; }
  .rail__mark { width: 108px; margin-inline: auto; }

  /* "Lassen Sie uns sprechen" is 23 characters — the header ran out of
     room in German before this */
  .chrome { gap: 12px; }
  .wordmark, .wordmark img { width: 78px; min-width: 78px; }
  .chrome__divider { margin-inline: 11px; }
  .talk { font-size: var(--t-small); }

  .send { height: 50px; }
  .topics__row { gap: 6px 20px; }
  .prior__row { gap: 20px 26px; }
}

@media (max-width: 767px) {
  .services { grid-template-columns: 1fr; gap: 22px; }
  .service h2::after { width: 44px; }
  .intro__note { max-width: none; }
}

@media (max-width: 560px) {
  :root { --gutter: 20px; }
  .field-row { grid-template-columns: 1fr; }
}

/* The longest CTA — German "Lassen Sie uns sprechen" — cannot share a
   320–360px header line with the wordmark, divider and language switcher
   without overlapping. Drop the small header wordmark at this width: the
   big WUSA still leads every section, the language switcher and full CTA
   stay, and the dock + skip link keep home reachable. (375px fits, so the
   wordmark returns there.) The intro FLIP measures .intro__note, not this
   wordmark, so hiding it here is safe. */
@media (max-width: 374px) {
  .chrome .wordmark, .chrome__divider { display: none; }
}

@media (max-width: 1080px), (max-height: 620px) {
  #site-scroll { scroll-snap-type: none; }
}

@media (prefers-reduced-motion: reduce) {
  html, #site-scroll { scroll-behavior: auto; }
  #site-scroll { scroll-snap-type: none; }
  *, *::before, *::after {
    transition-duration: .01ms !important; transition-delay: 0ms !important;
    animation-duration: .01ms !important; animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
  }
  .motion-ready .screen [data-rise] { opacity: 1; filter: none; transform: none; }
}


/* ============================================================
   ZOOM — tap a plate to see it whole
   Deliberately quiet: no icon at rest, just a corner mark that
   shows while you're touching or hovering it.
   ============================================================ */

.plate { cursor: zoom-in; }
.plate::before {
  content: ""; position: absolute; z-index: 2; right: 11px; bottom: 11px;
  width: 11px; height: 11px;
  border-right: 1px solid var(--plate-ink, var(--ink-soft));
  border-bottom: 1px solid var(--plate-ink, var(--ink-soft));
  opacity: 0; transition: opacity var(--t-fast) var(--e-soft);
}
.plate:hover::before, .plate:focus-visible::before, .plate:active::before { opacity: .5; }

.zoom {
  position: fixed; inset: 0; z-index: 120;
  display: grid; place-items: center; padding: var(--gutter);
  background: rgba(247, 250, 253, .82);
  -webkit-backdrop-filter: blur(22px); backdrop-filter: blur(22px);
  opacity: 0; transition: opacity .28s var(--e-soft); cursor: zoom-out;
}
.zoom[hidden] { display: none; }
.zoom.is-open { opacity: 1; }
.zoom__plate {
  position: relative; margin: 0; overflow: hidden;
  width: min(92vw, 900px); height: min(72vh, 660px);
  background-color: var(--plate, var(--paper-sunk));
  background-image: var(--plate-img, none);
  background-size: contain; background-position: center; background-repeat: no-repeat;
  transform: scale(.97); transition: transform .34s var(--e-out);
}
.zoom.is-open .zoom__plate { transform: none; }
.zoom__plate::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: var(--grain); background-size: 140px 140px; opacity: .55;
}
.zoom__plate figcaption {
  position: absolute; z-index: 1; left: 16px; bottom: 14px;
  font-family: var(--f-mono); font-size: var(--t-micro); letter-spacing: .04em;
  color: var(--plate-ink, var(--ink-soft)); opacity: .8;
}
/* tap anywhere — image included — to close, so the whole overlay reads as dismissable */
.zoom__plate { cursor: zoom-out; }
/* square, quiet close control — a 44px hit area with a drawn ✕ */
.zoom__close {
  position: absolute; top: var(--gutter); right: var(--gutter); z-index: 2;
  width: 44px; height: 44px; padding: 0; border: 0; background: transparent; cursor: pointer;
}
.zoom__close::before, .zoom__close::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 20px; height: 1px;
  background: var(--ink); transition: background-color var(--t-fast) var(--e-soft);
}
.zoom__close::before { transform: translate(-50%, -50%) rotate(45deg); }
.zoom__close::after { transform: translate(-50%, -50%) rotate(-45deg); }
.zoom__close:hover::before, .zoom__close:hover::after { background: var(--salmon-ink); }
.zoom__close:focus-visible { outline: 2px solid var(--ice-deep); outline-offset: 2px; }
/* the plate is now a real control; give it a visible keyboard focus ring */
.plate:focus-visible { outline: 2px solid var(--ice-deep); outline-offset: 2px; }


/* ============================================================
   SMALL SCREENS — about, non-profits, contact
   ============================================================ */

@media (max-width: 1080px) {

  /* the dock lies along the bottom here, so every screen has to keep
     its last line clear of it — the closing footer line was sitting
     underneath the tick strip */
  .screen { padding-bottom: calc(var(--screen-bottom) + 46px); }

  /* ---- ABOUT ----
     Side-by-side portraits only work while there is a middle to sit
     either side of. Stacked, the story was interrupted by a face.
     So: the story runs first, then both portraits together as a pair —
     which is the diptych the desktop layout is already making. */
  .about__layout {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "story story" "lex dasha";
    gap: clamp(20px, 3.4vh, 34px) 18px;
    align-items: start;
  }
  .about__story { grid-area: story; max-width: 62ch; padding-top: 0; }
  .who--a { grid-area: lex; }
  .who--b { grid-area: dasha; }
  .who__photo { max-width: none; width: 100%; aspect-ratio: 1 / 1; }
  .who h3 { font-size: var(--t-body); gap: 6px; margin: 11px 0 5px; }
  .who__role br { display: none; }
  .about__story p.about__claim { margin-top: clamp(14px, 2vh, 22px); }

  /* ---- NON-PROFITS ----
     Three reels side by side on a phone gave three thumbnails and no
     film. One at a time, full width, swipe for the next — the sliver
     of the next card is the only affordance needed. */
  .causes__layout { display: block; width: var(--measure); }
  .causes__reel {
    display: grid;
    grid-auto-flow: column; grid-auto-columns: 86%;
    gap: 10px; align-items: start;
    width: calc(100% + var(--gutter) * 2);
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory; scroll-padding-inline: var(--gutter);
    scrollbar-width: none; -ms-overflow-style: none;
  }
  .causes__reel::-webkit-scrollbar { display: none; }
  .cause { scroll-snap-align: start; }
  /* budget at 500x670: 141 top + 86 bottom leaves 443 for content.
     media 241 + meta 66 + aside 113 = 420. It fits on one screen. */
  .cause--wide .cause__media,
  .cause--reel .cause__media { aspect-ratio: auto; height: min(36vh, 300px); }
  .causes__aside { padding-left: 0; margin-top: clamp(12px, 2vh, 20px); }
  .causes__aside p { margin-bottom: 10px; }
  /* line breaks chosen for a 220px desktop column cost a third line here */
  .causes__aside p br, .causes .head p br { display: none; }

  /* ---- CONTACT ----
     Four checkboxes on one flex line wrapped into a ragged 1-3 split
     and gave 21px-tall targets. A fixed 2x2 with real rows fixes both. */
  .topics__row { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 16px; }
  .topic span { padding: 12px 0; min-height: 44px; }
  .topic span::before { width: 17px; height: 17px; }

  /* The footer repeated the wordmark that is already fixed at the top
     and the location line that already sits in the descriptor. On a
     phone that is three lines of chrome under the only thing that
     matters here — the address you can tap. */
  .foot { gap: 10px 14px; padding-top: 14px; }
  .foot__mark, .foot__divider, .foot__desc { display: none; }
  .foot__mail { margin-left: 0; }
  .foot__legal { margin-left: auto; text-align: right; }
  .foot__legal span[data-i18n="contact.place"] { display: none; }
}

@media (max-width: 400px) {
  /* two names side by side stop fitting somewhere around here */
  .about__layout { grid-template-columns: 1fr 1fr; gap: 16px 12px; }
  .who h3 { font-size: var(--t-small); }
  .topics__row { grid-template-columns: 1fr; }
}

/* ============================================================
   PHONE (<=767px) ONLY — app-like chrome. Tablet (768+) and desktop
   are untouched: these elements are hidden by default and only the
   phone block below turns them on.
   ============================================================ */
.burger, .intro__talk, .dock__extra { display: none; }

@media (max-width: 767px) {
  /* --- top bar: logo + burger; Let's talk and the language dropdown
         move into the slide-in menu --- */
  .chrome { z-index: 62; }
  .chrome .talk { display: none; }
  .chrome .lang--menu { display: none; }
  .chrome__divider { display: none; }   /* nothing sits beside the wordmark now */

  /* highlight a CTA's underline as its section first reveals on scroll */
  .screen.is-active .link > span:first-child::after {
    animation: reveal-underline 1.15s var(--e-soft) .3s both;
  }
  /* language stays easy to reach on the hero, with all four visible and
     enough gap that the row reads as its own line, not a clump */
  body.is-intro .lang--inline { max-width: none; gap: 12px; }

  .burger {
    display: inline-flex; align-items: center; justify-content: center;
    margin-left: auto; width: 44px; height: 44px; padding: 0;
    border: 0; background: transparent; color: var(--ink);
    cursor: pointer; pointer-events: auto; position: relative; z-index: 2;
  }
  .burger__box { position: relative; width: 22px; height: 12px; display: block; }
  .burger__box span {
    position: absolute; left: 0; width: 100%; height: 1.5px; background: currentColor;
    transition: transform .3s var(--e-out), opacity .2s var(--e-soft);
  }
  .burger__box span:first-child { top: 1px; }
  .burger__box span:last-child { bottom: 1px; }
  body.menu-open .burger__box span:first-child { transform: translateY(4.5px) rotate(45deg); }
  body.menu-open .burger__box span:last-child { transform: translateY(-4.5px) rotate(-45deg); }

  /* --- the dock becomes a full-screen slide-in menu --- */
  .dock {
    position: fixed; inset: 0; transform: none; z-index: 58;
    display: flex; flex-direction: column; justify-content: flex-start; gap: 0;
    padding: calc(var(--chrome-h) + 26px) var(--gutter) 40px;
    background: var(--paper); overflow-y: auto;
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity .3s var(--e-soft), visibility 0s linear .3s;
  }
  .dock.is-open { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: 0s; }
  .dock.is-hidden { opacity: 0; }
  .dock.is-open.is-hidden { opacity: 1; }   /* open always beats the contact-fade */
  .dock__name { display: none; }
  .dock__ticks { display: flex; flex-direction: column; align-items: flex-start; gap: 0; width: 100%; }
  .dock a { height: auto; width: auto; justify-content: flex-start; padding: 7px 0; }
  .dock__tick { display: none; }
  .dock__label {
    position: static; width: auto; height: auto; margin: 0; padding: 0;
    clip: auto; overflow: visible; white-space: normal;
    font-family: var(--f-display); font-size: clamp(24px, 7vw, 32px);
    font-weight: var(--display-weight); letter-spacing: var(--display-track);
    line-height: 1.14; color: var(--ink-mute); transition: color .2s var(--e-soft);
  }
  .dock a[aria-current] .dock__label,
  .dock a:active .dock__label { color: var(--ink); }
  .dock a:focus-visible { outline: 2px solid var(--ice-deep); outline-offset: 3px; }
  .dock__extra {
    display: flex; flex-direction: column; align-items: flex-start; gap: 20px;
    margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--line); width: 100%;
  }
  .dock__talk { font-family: var(--f-display); font-size: clamp(21px, 6vw, 27px); }
  .dock__lang {
    max-width: none; opacity: 1; clip-path: none; transform: none;
    display: flex; gap: 18px;
  }
  .dock__lang button { min-width: 40px; min-height: 44px; font-size: var(--t-small); }

  body.menu-open .rail { opacity: 0 !important; visibility: hidden !important; }

  /* --- static, stacked header: title then descriptor, no roll ---
     Three separate things live up here — language row, wordmark, descriptor.
     They were touching (the language row ended at 53px and the mark started
     at 54px), which read as one scattered clump. Each gets its own air. */
  :root { --rail-h: 88px; }
  .rail {
    top: calc(var(--chrome-h) + 18px);
    grid-template-columns: minmax(0, 1fr); width: min(94vw, 560px);
    height: auto; row-gap: 9px; align-content: center;
  }
  .rail__rule { display: none; }
  .rail__mask { position: static; height: auto; overflow: visible; display: block; }
  .rail__mask--desc { display: block; }
  .rail__item { position: static; justify-content: center; text-align: center; white-space: normal; }
  .rail__item--title { justify-content: center; text-align: center; }
  .rail__item--desc {
    justify-content: center; text-align: center; translate: none;
    color: var(--ink-mute); font-size: var(--t-micro); line-height: 1.3;
  }
  .rail__mark { width: clamp(150px, 46vw, 188px); margin-inline: auto; }

  /* --- off the hero, the header belongs to the section, not the bar ---
     The fixed rail sat on top of section copy while scrolling. Only the
     intro keeps it (for the wordmark); every other section prints its own
     header, which scrolls away with its content. */
  body:not(.is-intro) .rail { opacity: 0; visibility: hidden; }

  /* the mask existed to dissolve content passing under the rail; with the
     rail gone off the hero, only the slim top bar needs covering */
  #site-scroll {
    --fade: linear-gradient(to bottom,
      transparent 0,
      rgba(0, 0, 0, .35) calc(var(--chrome-h) * .58),
      #000 calc(var(--chrome-h) + 12px));
  }

  /* content starts under the bar instead of being centred in the section:
     centring left a screen-high gap above the header on the taller sections */
  .screen:not(.intro) { padding-top: calc(var(--chrome-h) + 20px); justify-content: flex-start; }
  .screen:not(.intro) > .head {
    position: static; width: auto; height: auto; overflow: visible; clip: auto;
    margin: 0 0 clamp(16px, 3vh, 28px); text-align: center;
  }
  /* the section name outranks the project name here — it was the other way
     round (21px header against a 26px project title) */
  .screen > .head h2 {
    font-family: var(--f-display); font-size: clamp(27px, 7.4vw, 34px);
    font-weight: var(--display-weight); letter-spacing: var(--display-track);
    line-height: 1.1; margin: 0;
  }
  .work__name { font-size: clamp(19px, 5.1vw, 23px); }
  .screen > .head p {
    font-family: var(--f-mono); font-size: var(--t-micro); line-height: 1.35;
    color: var(--ink-mute); margin: 7px 0 0;
  }
  .screen > .head p br { display: none; }

  /* --- the hero CTA treatment, shared by the other section CTAs --- */
  .intro__talk,
  .process__foot .cta,
  .causes__aside .link {
    display: inline-flex; align-items: center; gap: 8px;
    font-family: var(--f-display); font-size: 18px;
  }
  .process__foot { flex-direction: column; align-items: center; text-align: center; gap: 16px; }
  .causes__aside { text-align: center; }

  /* --- hero order: wordmark, then the sentence, then the blocks, then
         the CTA. Stacked, the closing line read as a stray fourth block
         at the bottom; up top it introduces the three. --- */
  .intro__inner { display: flex; flex-direction: column; }
  .intro__note {
    order: -1; margin: 0 auto clamp(30px, 6vh, 52px); max-width: none;
  }

  /* --- hero services: text blocks, with the rule moved out from under
         each heading to sit between blocks as a separator --- */
  .services { grid-template-columns: 1fr; gap: 0; width: 100%; }
  .service { padding: clamp(15px, 3.2vh, 24px) 0; }
  .service h2::after { display: none; }
  .service:not(:last-child) { border-bottom: 1px solid var(--line); }
  .service:first-child { padding-top: 0; }
  .service:last-child { padding-bottom: 0; }
  .service h2 { margin: 0 0 9px; }

  /* hero "Let's talk" button, at the foot of the hero */
  .intro__foot { position: static; translate: none; margin-top: 30px; display: flex; justify-content: center; }
  .intro__foot .cue { display: none; }
  .intro__talk { display: inline-flex; align-items: center; gap: 8px; font-family: var(--f-display); font-size: 18px; }

  /* --- contact topics become button chips --- */
  .topics__row { gap: 10px; }
  .topic span {
    border: 1px solid var(--line); padding: 11px 15px; gap: 0;
    background: rgba(255, 255, 255, .5);
    transition: background-color var(--t-fast) var(--e-soft), border-color var(--t-fast) var(--e-soft), color var(--t-fast) var(--e-soft);
  }
  .topic span::before { display: none; }
  .topic input:checked + span {
    color: var(--ink); border-color: var(--salmon-ink); background: rgba(255, 117, 108, .14);
  }
  .topic input:focus-visible + span { outline: 2px solid var(--ice-deep); outline-offset: 2px; }
}

/* ============================================================
   PHONE TYPE SCALE (<=767px) — one ladder for the whole phone
   layout. Audited against the rendered sizes: several sections had
   inverted hierarchy (a supporting sentence at 18px above the 14px
   names it supported, person names below body text). Placed last so
   it settles every earlier phone rule. Desktop/tablet untouched.

     28   section header
     23   hero statement
     18-20 item names (project, person, cause, step, service) + CTA
     15   body
     13-14 secondary body
     12   mono meta and labels
   ============================================================ */
@media (max-width: 767px) {
  .intro__note { font-size: 23px; }
  .service h2 { font-size: 20px; }
  .service p { font-size: 14px; }

  .work__name { font-size: 20px; }
  .work__desc { font-size: 15px; }

  /* the cause name outranks the sentence beside it — it was 14px under
     an 18px supporting line */
  .cause__meta h3 { font-size: 18px; }
  .causes__aside p { font-size: 15px; }

  /* a person's name should not sit below the body copy around it */
  .who h3 { font-size: 16px; }
  .about__story p { font-size: 14px; }
}

/* ---- phone spacing: sections size to their content ----
   Scroll-snap is off below 1080px, so forcing every section to 100svh
   only produced dead space — short sections trailed 167px of empty
   ground before the next header, which is the gap that showed while
   scrolling. Content height plus one consistent rhythm instead. The
   old 46px bottom reserve is gone too: it cleared the bottom dock,
   which is a burger menu here. The intro still fills the screen. */
@media (max-width: 767px) {
  .screen:not(.intro) {
    min-height: 0;
    padding-bottom: clamp(46px, 7.5vh, 72px);
  }
}

/* ---- phone: contact topics sit two-up ----
   The <=400px rule dropped them to a single column, which made the
   four choices a tall list. Two columns reads as one compact group. */
@media (max-width: 767px) {
  .topics__row {
    display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  }
  .topic { display: block; }
  .topic span { justify-content: center; text-align: center; width: 100%; }
}

/* ---- phone: the hero separators ----
   Short centred rules instead of full-width borders, and each one warms
   to salmon and settles back so the hero has one quiet moving accent.
   Staggered so they pulse in sequence, not together. */
@media (max-width: 767px) {
  .service:not(:last-child) { border-bottom: 0; }
  .service:not(:last-child)::after {
    content: ""; position: absolute; left: 50%; bottom: 0;
    transform: translateX(-50%);
    width: 46px; height: 1px; background: var(--line-field);
  }

  /* The CTA rule hugged the label and left the arrow outside it, so a
     centred block sat above an off-centre line. Move the rule onto the
     whole link — label and arrow — and every line on the screen is
     centred on the same axis. */
  .intro__talk,
  .process__foot .cta,
  .causes__aside .link { position: relative; }
  .intro__talk > span:first-child::after,
  .process__foot .cta > span:first-child::after,
  .causes__aside .link > span:first-child::after { display: none; }
  .intro__talk::after,
  .process__foot .cta::after,
  .causes__aside .link::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 5px;
    height: 1px; background: var(--line-field);
  }

  /* the salmon runs down the hero: first rule, second rule, then the
     button — one travelling accent rather than three blinking ones */
  .service:not(:last-child)::after,
  .intro__talk::after {
    animation: hero-rule 9s var(--e-soft) infinite;
  }
  .service:nth-child(2)::after { animation-delay: 3s; }
  .intro__talk::after { animation-delay: 6s; }
}
@keyframes hero-rule {
  0%, 24%, 100% { background-color: var(--line-field); }
  8%            { background-color: var(--salmon); }
}

/* ---- the wide end of the phone band (621-767px) ----
   The phone layout is single-column. At 375px that fills the screen, but
   at 700px an uncapped column runs a 650px line, which is far past a
   comfortable measure. Cap the text and let the margins take the rest. */
@media (min-width: 621px) and (max-width: 767px) {
  .intro__inner,
  .work__layout,
  .steps,
  .about__layout,
  .causes__layout,
  .contact__layout,
  .process__foot,
  .screen > .head { max-width: 540px; margin-inline: auto; }
  .service p,
  .screen > .head p,
  .causes__aside p { max-width: 42ch; margin-inline: auto; }
}

/* ---- reveal per block, not per section, wherever snap is off ----
   Bodies were transparent until their whole section became "active", which
   the observer only does near the section midpoint. With snap disabled
   below 1080px two sections can share the screen, so content sat on screen
   invisible — a blank screen while scrolling. JS adds .is-revealed as each
   block enters view; the step icons ride the same signal. Above 1080px snap
   keeps one section in view and the section-level reveal is correct. */
@media (max-width: 1080px), (max-height: 620px) {
  .motion-ready .screen [data-rise] { transition-delay: 0s; }
  .motion-ready .screen [data-rise].is-revealed { opacity: 1; filter: blur(0); }
  .motion-ready .steps.is-revealed .step-card__icon { animation: settle .8s var(--e-out) both; }
  .motion-ready .steps.is-revealed .step-card:nth-child(1) .step-card__icon { animation-delay: 60ms; }
  .motion-ready .steps.is-revealed .step-card:nth-child(2) .step-card__icon { animation-delay: 150ms; }
  .motion-ready .steps.is-revealed .step-card:nth-child(3) .step-card__icon { animation-delay: 240ms; }
  .motion-ready .steps.is-revealed .step-card:nth-child(4) .step-card__icon { animation-delay: 330ms; }
}
