*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, p, figure { margin: 0; }

/* Nav links travel rather than teleport, so the descent is something you watch
   happen. The rabbit and the tunnel are scroll-driven, so an animated scroll
   plays them through on the way instead of jumping past them. batsignal.js
   already scrolls this way with an explicit `behavior: 'smooth'` — this rule is
   what makes plain `<a href="#work">` do the same, and the two now agree.
   Motion the visitor did not ask for is exactly what the media query is for. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* Landing room under the fixed header. Enhanced, `.pocket` is absolutely
   positioned with `padding: 0` (pockets.css), which drops the 8vh the
   unenhanced rule gives it — so a pocket's box top IS its first line, and an
   anchor jump parks the label beneath the wordmark. The header is ~58px
   (18 + 22 + 18) and the rest is air. In vh so the landing keeps its
   proportion from a short laptop to a tall display, with a px floor because
   16vh of a 500px window is not enough to clear a header that does not shrink.
   Selector is structural rather than a list of the four nav ids, so a new
   section arrives already landing correctly. #top is <main> itself; scrolling
   it clamps at 0, so the margin costs nothing there. */
main[id], main section[id] { scroll-margin-top: max(96px, 16vh); }

:root {
  --bg: #07070A;
  --soil: #1A1410;
  --ink: #F4F2EC;
  --muted: #A9A499;
  --accent: #72bd02;
  --line: rgba(255,255,255,.12);
  --mono: 'Space Mono', ui-monospace, monospace;
  --sans: 'Schibsted Grotesk', system-ui, sans-serif;
  /* THE FLOOR BELOW IS NO LONGER BINDING. The shipping wordmark is `-h2`
     (settled 2026-07-30), which draws AMOROS as a FILLED shape, and a fill has
     no width to lose — so 22px is a choice by eye rather than the smallest
     legible size. Raise it freely; only a return to a stroked lockup brings
     the arithmetic below back into force.

     The stroked lockups — the retired `-h` and the stacked `franck-logo` — draw AMOROS as
     an outline at 1.5 units, so the stroke thins with the logo and there is a
     size below which it disappears on a 1x display. The floor moves with the
     artboard: the stacked wordmark is 93.77 units tall and wants ~40px, the
     horizontal one is 44.65 and holds the same rendered 0.74px hairline at 22.
     Letter sizes end up within a hair of each other, since the stacked version
     spends half its height on the second row. */
  --logo-h: 22px;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 60;
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px clamp(18px, 4vw, 44px);
}
.brand { display: block; color: var(--ink); text-decoration: none; }
.brand img { display: block; height: var(--logo-h); width: auto; }
/* Hidden until the narrow block at the foot of this file turns it on, so the
   breakpoint that decides it lives in exactly one place. */
.nav-toggle {
  display: none;
  appearance: none; background: none; border: 0; padding: 6px 0; cursor: pointer;
  font: 400 12px var(--mono); letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink); opacity: .75;
}
.nav-toggle:hover, .nav-toggle:focus-visible { opacity: 1; }

.site-nav { display: flex; gap: 22px; }
.site-nav a { color: var(--ink); text-decoration: none; font-size: 13px; font-weight: 500; opacity: .75; }
.site-nav a:hover, .site-nav a:focus-visible { opacity: 1; }

/* Underground, the tunnel owns the screen and the surface furniture leaves —
   the same argument that already sends the time control away, so this uses the
   same class, the same signal from main.js and the same .4s.

   Scoped to .brand and .site-nav rather than applied to .site-header itself,
   because the header's third child is the time control, which fades on its own
   rule with its own delay. Fading the shared parent would apply a second
   opacity on top of that one and the clock would leave faster than everything
   else for no stated reason.

   `visibility` and not opacity alone, for the reason given at .timetravel.faded:
   an invisible link that still takes focus is worse than no link. Tabbing
   underground must not land on a wordmark nobody can see. Delayed so it takes
   effect only once the fade has finished, and 0s on the way back so the header
   is present the instant the visitor climbs out. */
.enhanced .brand,
.enhanced .site-nav {
  transition: opacity .4s ease, visibility 0s;
}
.enhanced .site-header.faded .brand,
.enhanced .site-header.faded .site-nav {
  opacity: 0;
  visibility: hidden;
  transition: opacity .4s ease, visibility 0s linear .4s;
}

/* Absolutely centred rather than a third flex child: the header is
   space-between and the wordmark and nav are not the same width, so a flex
   child would sit visibly off centre. Safe to position against the header
   because the header is outside <main> — see STATE.md, idea 3b. */
.timetravel { display: none; }

.enhanced .timetravel {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  justify-items: center;
  gap: 10px;
  transition: opacity .4s ease, visibility 0s;
}

/* The cycle owns the surface, so underground the control goes. `visibility`
   rather than opacity alone: an invisible control that still takes focus is
   worse than no control. Delayed so it hides only once the fade has finished. */
.enhanced .timetravel.faded {
  opacity: 0;
  visibility: hidden;
  transition: opacity .4s ease, visibility 0s linear .4s;
}

.timetravel-row { display: flex; align-items: baseline; gap: 20px; }

.timetravel-readout { font: 400 16px var(--mono); letter-spacing: .12em; color: var(--ink); }

.timetravel-now, .timetravel-play, .timetravel-speed {
  appearance: none; background: none; border: 0; padding: 0; cursor: pointer;
  font: 400 12px var(--mono); letter-spacing: .18em; text-transform: uppercase;
  color: var(--accent);
}

/* Disabled while the clock is already real — there is nothing to return from,
   and this takes it out of the tab order for the same reason. */
.timetravel-now[disabled] { color: rgba(255,255,255,.28); cursor: default; }

/* Glyphs, not words: the tracking that makes `now` read as a label makes a
   pause bar read as two separate marks. Slightly larger because ▶ and ❚❚ carry
   less ink than four letters at the same size. */
.timetravel-play { font-size: 14px; letter-spacing: 0; line-height: 1; }
.timetravel-speed { min-width: 2.2em; text-align: left; }

.timetravel-slider {
  appearance: none; -webkit-appearance: none;
  width: clamp(140px, 18vw, 230px); height: 14px;
  background: none; cursor: pointer;
}
.timetravel-slider::-webkit-slider-runnable-track { height: 2px; background: var(--line); border-radius: 2px; }
.timetravel-slider::-moz-range-track { height: 2px; background: var(--line); border-radius: 2px; }
.timetravel-slider::-webkit-slider-thumb {
  -webkit-appearance: none; width: 11px; height: 11px; border-radius: 50%;
  background: var(--ink); margin-top: -4.5px;
}
.timetravel-slider::-moz-range-thumb {
  width: 11px; height: 11px; border: 0; border-radius: 50%; background: var(--ink);
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.surface { position: relative; padding: 26vh clamp(20px, 5vw, 80px) 12vh; }
.surface-scene { display: none; }
.surface-copy h1 {
  font-size: clamp(34px, 6vw, 76px); font-weight: 700;
  /* 20ch + balance so the headline settles on two even lines instead of
     stranding "surface" alone on a third. */
  line-height: 1.02; letter-spacing: -.03em; max-width: 20ch;
  text-wrap: balance;
}
.surface-copy p { margin-top: 22px; font-size: clamp(16px, 2vw, 20px); color: var(--muted); }
.scroll-hint { margin-top: 40px; font: 400 11px var(--mono); letter-spacing: .2em; text-transform: uppercase; color: rgba(255,255,255,.5); }

.underground { position: relative; background: var(--soil); padding: 8vh clamp(20px, 5vw, 80px); }
.overlay { display: none; }

.pocket { max-width: 46ch; padding: 8vh 0; }
.pocket-label { font: 700 11px var(--mono); letter-spacing: .18em; text-transform: uppercase; color: var(--accent); margin-bottom: 14px; }
.pocket h2 { font-size: clamp(24px, 3.2vw, 40px); font-weight: 700; line-height: 1.06; letter-spacing: -.025em; }
.pocket-body { margin-top: 14px; font-size: 16px; line-height: 1.6; color: var(--muted); }

/* Media pockets need more room than a column of prose. 60ch still clears the
   tunnel: at the pocket's y the bore is near mid-page, leaving ~700px beside a
   7% inset. Revisit if legHeight or margin change. */
.pocket--wide { max-width: 60ch; }

.logo-grid {
  list-style: none; margin: 30px 0 0; padding: 0;
  /* auto-fill rather than a fixed count: the set grows as clients are added,
     and 27 marks in three columns is a nine-row wall. */
  display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 22px 28px; align-items: center;
}
/* Fixed-height cells so rows line up whatever each mark's aspect ratio is.
   --span widens the cell for lockups too wide to read at one column. */
.logo-grid li {
  grid-column: span var(--span, 1);
  display: flex; align-items: center; justify-content: center;
  height: 52px; padding: 0 8px;
}
/* The marquee's cells, built by clientwall.js on narrow screens. Sized here
   rather than in the module because everything else about how a mark is drawn
   is already in this file, and a size in JS is a size nobody finds. */
.logo-marquee { display: none; }
.wall--marquee .logo-grid { display: none; }
.wall--marquee .logo-marquee { display: grid; gap: 12px; margin-top: 26px; }

/* The row is the window, the track is the strip that slides behind it. Two
   copies of the marks live in every track, so the clip is what makes the wrap
   invisible rather than a gap appearing at one end.

   The cell's width and height and the track's gap are all written by
   clientwall.js, because the module sums them to work out how far a copy is
   and a number in two places is a number that will disagree with itself. What
   stays here is only what does not enter that arithmetic. */
.logo-row { overflow: hidden; }
.logo-track {
  list-style: none; margin: 0; padding: 0;
  display: flex; width: max-content;
  will-change: transform;
}
.logo-track li {
  flex: 0 0 auto; padding: 0;
  display: flex; align-items: center; justify-content: center;
}
/* Width from the cell, height from the module: `contain` then paints the mark
   at the size the cell was measured for, so it fills its box instead of
   floating in the middle of one. --y stays a transform because a few marks are
   centred off their own optical middle, and that is a correction to what you
   see rather than to how much room it takes. */
.logo-track img {
  display: block;
  width: 100%;
  object-fit: contain; object-position: center;
  transform: translateY(var(--y, 0));
  filter: brightness(0) invert(1); opacity: .55;
}

.logo-grid img {
  /* BOTH dimensions must be stated. 21 of these 27 SVGs carry only a viewBox
     and no width/height, so `auto` gives them no intrinsic size and they
     collapse to nothing. object-fit does the aspect work instead. */
  display: block; width: 100%; height: 100%;
  object-fit: contain; object-position: center;
  /* Optical balance cannot be derived: every file carries its own padding and
     stroke weight, so one shared box makes DHL look tiny beside Dior. --s is
     the per-mark correction, applied as a transform because `contain` may be
     bound by either dimension and a scale compensates for both. Nudge by eye.
     --y is the same idea vertically, for marks whose file centres them off the
     optical middle. Both are transforms, so neither disturbs the grid. */
  transform: translateY(var(--y, 0)) scale(var(--s, 1));
  /* The set is a mix of white and dark marks, so normalise rather than trust
     each file, and hold them back from competing with the copy. */
  filter: brightness(0) invert(1); opacity: .55;
}

.work-slider { margin-top: 30px; }
.work-track { list-style: none; margin: 0; padding: 0; position: relative; }
/* Default is every slide stacked, so without JS the work is still all there.
   slider.js adds .is-live, which is what collapses it to one at a time — keyed
   off the component, not .enhanced, so it survives reduced-motion too. */
.work-slide { margin-bottom: 26px; }
.work-slider.is-live .work-slide { display: none; margin-bottom: 0; }
.work-slider.is-live .work-slide.is-current { display: block; }
.work-shot {
  display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; object-position: top center;
  border-radius: 10px; border: 1px solid rgba(255,255,255,.12); background: #191512;
}
.work-link { display: block; text-decoration: none; color: inherit; }
.work-link:hover .work-shot { border-color: var(--accent); }
.work-link:hover .work-caption { color: var(--ink); }
.work-caption { margin-top: 14px; font-size: 16px; color: var(--muted); }
.work-ext { color: var(--accent); }
.work-controls { display: none; align-items: center; gap: 16px; margin-top: 18px; }
.work-slider.is-live .work-controls { display: flex; }
.work-arrow {
  width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
  border: 1px solid rgba(255,255,255,.22); background: transparent;
  color: inherit; font-size: 16px; line-height: 1;
}
.work-arrow:hover { border-color: var(--accent); color: var(--accent); }
.work-count { font: 400 11px var(--mono); letter-spacing: .18em; color: var(--muted); }

/* Top padding is small because the soil above it already supplies a gap:
   tailPadding below the tunnel floor plus the PIN_PX main.js adds to the
   underground. Raising this reopens the hole between floor and heading.

   Flat --soil, matching the unenhanced .underground above it, so the section
   boundary is invisible without JS. Enhanced, .underground is a gradient
   ending on a different colour entirely and tunnel.css overrides this. */
.burrow {
  text-align: center;
  padding: 5vh clamp(20px, 5vw, 80px) 14vh;
  background: var(--soil);
}
.burrow h2 { font-size: clamp(28px, 5vw, 56px); font-weight: 700; letter-spacing: -.03em; }
.burrow p { margin-top: 16px; color: var(--muted); max-width: 46ch; margin-inline: auto; }

.burrow-actions {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 14px; margin-top: 30px;
}
.button {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 15px 26px; border-radius: 8px;
  border: 1px solid var(--accent); background: var(--accent);
  color: #0B0B0A; font-weight: 700; font-size: 15px; text-decoration: none;
}
.button svg { width: 18px; height: 18px; flex: none; }
.button:hover { filter: brightness(1.08); }
/* Same weight of information, not the same weight of colour: two accent slabs
   side by side read as a decision to make rather than two ways to reach the
   same person. */
.button--ghost { background: transparent; border-color: rgba(255,255,255,.28); color: var(--ink); }
.button--ghost:hover { filter: none; border-color: var(--accent); color: var(--accent); }

.depth-readout { display: none; }

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

/* ------------------------------------------------------------------ narrow */

/* WHY 900 AND NOT 768. The header is three children on one row and the middle
   one is absolutely positioned at left:50%. The wordmark measures 268px at
   --logo-h 22, the five links about 250px, and the time control about 230px,
   so the centred child starts running into the other two at roughly 854px.
   This breakpoint is that number, rounded up so nothing sits on the edge of
   its own failure. It is deliberately NOT the 767 used for the carrot and the
   client wall: those break for their own reasons, at their own widths, and one
   shared breakpoint would be a coincidence dressed up as a system. */
@media (max-width: 899px) {
  .site-header {
    flex-wrap: wrap;
    row-gap: 12px;
    padding: 14px clamp(16px, 4vw, 28px);
  }

  .nav-toggle { display: block; }

  /* A panel rather than a row. It hangs off the header, which is the
     positioned ancestor, so top:100% is the underside of whatever height the
     header has ended up at — including the time control's row. */
  .site-nav {
    display: none;
    position: absolute; top: 100%; right: clamp(16px, 4vw, 28px);
    flex-direction: column; gap: 2px; text-align: right;
    padding: 10px 16px;
    border: 1px solid var(--line); border-radius: 10px;
    background: rgba(7,7,10,.92);
    -webkit-backdrop-filter: blur(6px);
            backdrop-filter: blur(6px);
  }
  .site-nav.open { display: flex; }
  /* 44px is the smallest target Apple and Google both publish, and these were
     sized for a cursor. The padding does the work rather than a font size, so
     the type stays the type. */
  .site-nav a { font-size: 15px; opacity: .9; padding: 11px 0; }

  /* Out of the absolute centre and onto a row of its own. `order` is doing
     something flex-wrap cannot: the time control is the SECOND child in source,
     so wrapping alone would leave it between the wordmark and the menu button
     and push the nav down instead. */
  .enhanced .timetravel {
    position: static; left: auto; transform: none;
    order: 3; width: 100%;
    justify-items: stretch;
    gap: 12px;
  }
  /* baseline is right when these sit in a line of type; once they are padded
     targets it puts them on a shared invisible line and the readout drifts. */
  .timetravel-row { align-items: center; gap: 18px; }
  .timetravel-now, .timetravel-play, .timetravel-speed {
    display: inline-flex; align-items: center; min-height: 44px;
  }
  /* clamp(140px, 18vw, 230px) gives 140px at 390 — 18vw is 70 — so the whole
     day would be 140 pixels wide, about six minutes per pixel. */
  .timetravel-slider { width: 100%; }
  .timetravel-slider::-webkit-slider-thumb { width: 16px; height: 16px; margin-top: -7px; }
  .timetravel-slider::-moz-range-thumb { width: 16px; height: 16px; }
}

/* 767 rather than 899, because this is a different failure with a different
   cause: the grid needs a 120px column and the pocket it sits in is 52vw. */
@media (max-width: 767px) {
  /* THE FALLBACK MATTERS AS MUCH AS THE MARQUEE HERE. A visitor who has asked
     for reduced motion never reaches enhance(), so clientwall.js never runs and
     they keep this grid — on the one screen where the old numbers made it 28
     rows tall. An ID because the JS writes left/right inline and .enhanced
     .pocket--wide would lose to nothing but would also catch the work slider,
     which nobody has complained about. */
  .enhanced #clients { max-width: none; }
  .logo-grid {
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    gap: 14px 16px;
    margin-top: 24px;
  }
  .logo-grid li { height: 42px; padding: 0 4px; }
}

/* The wordmark is 12.2 units wide per unit tall, so at --logo-h 22 it measures
   268px. Plus the MENU button that is about 50, that needs 318px of row, and a
   320px phone offers 288 inside the padding — so the button wraps to a line of
   its own and the header grows a third row. 18px brings the wordmark to 220 and
   it fits again. This is a fitting decision on small screens, not a revisit of
   the 22px that was validated on the desktop page. */
@media (max-width: 420px) {
  :root { --logo-h: 18px; }
}
