/* ==========================================================================
   outder — design studio
   00. font swap
   01. tokens (dark + light)
   02. reset & base
   03. typography primitives
   04. layout primitives
   05. chrome (nav, theme toggle, burger, cursor)
   06. mobile menu
   07. hero + canvas
   08. clients marquee
   09. disciplines
   10. work grid
   11. contact block + footer
   12. contact modal + form
   13. motion / reveal
   14. responsive
   15. about page
   ========================================================================== */

/* === FONT SWAP: substituir por 29LT Bukra Wide === */
/* Outfit (Google Fonts, SIL OFL, livre para uso comercial) está aqui como
   stand-in do 29LT Bukra Wide, escolhido contra a referência: mesma largura
   geométrica, contadores circulares, 'g' de um andar com descendente direita.

   Um ficheiro só, porque a Outfit é variável — 32 KB para toda a gama 100-900,
   contra os ~80 KB que quatro estáticos custavam. Cada peso que o site pede
   (200, 300, 400, 700) é uma instância real do eixo `wght`, interpolada pelo
   motor de tipos: não há aqui nenhum negrito ou fino sintético.

   Quando o Bukra chegar, é este bloco e só este. Como vem em ficheiros
   estáticos, passa a ser um @font-face por peso; muda o font-family para
   'Bukra' e o --font-display lá em baixo apanha-o. */

@font-face {
  font-family: 'Outfit';
  src: url('../fonts/Outfit-variable.woff2') format('woff2-variations'),
       url('../fonts/Outfit-variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
/* === /FONT SWAP === */

/* ---------- 01. tokens -------------------------------------------------- */

/* The design system, in one file. There was a light build alongside this one
   while the three of you were choosing; it is gone. If a light theme ever
   comes back it does NOT get built by inverting these tokens — a straight
   inversion is exactly what made the first attempt look washed out. */

:root {
  --bg:            #0B0C0E;
  --bg-raise:      #131519;   /* sections that lift off the ground */
  --bg-raise-2:    #1A1D22;   /* cards */
  --bg-sunken:     #08090A;

  --ink:           #F2F2F0;
  --ink-soft:      #B4B7BC;
  --ink-mute:      #83878E;

  --line:          rgba(255, 255, 255, 0.10);
  --line-strong:   rgba(255, 255, 255, 0.22);

  --accent:        #00FFD5;   /* straight from the logo */
  --accent-ink:    #00FFD5;   /* accent where it carries text */
  --accent-glow:   rgba(0, 255, 213, 0.32);
  --accent-wash:   rgba(0, 255, 213, 0.06);
  --on-accent:     #04201D;

  --danger:        #FF6B6B;   /* the one error state; it only ever shows on dark */

  --shadow-card:   0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-lift:   0 24px 60px rgba(0, 0, 0, 0.55);
  --shadow-modal:  0 40px 120px rgba(0, 0, 0, 0.7);
  --scrim:         rgba(4, 5, 6, 0.72);

  --grain-op:      0.030;
  --grain-blend:   overlay;

  --canvas-dust:   #D6DCE4;   /* the constellation's ordinary points */

  /* The two dark grounds, named so the paper band can fade back into the
     right one. Deliberately outside anything .tone-light redeclares. */
  --ground:        #0B0C0E;
  --ground-raise:  #131519;


  color-scheme: dark;
}


:root {
  /* type */
  /* --font-display is the display face: the logo, both page headlines, the
     star labels and the header pill — the things that carry the brand.
     Swap it in one line when Bukra arrives. Section headings and body copy
     stay on --font-heading, so changing the face can never reflow the long
     text down the page. */
  --font-display: 'Outfit', 'Bukra', system-ui, sans-serif;
  --font-heading: 'Instrument Sans', 'Helvetica Neue', Arial, sans-serif;
  --font-body:    'Instrument Sans', 'Helvetica Neue', Arial, sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* fluid type scale. The two headlines are not in here: they carry their
     own clamps beside the rest of their type, because both are positioned
     against the viewport rather than flowing with the page. */
  --t-h2:      clamp(2rem, 4.6vw, 4rem);
  --t-h3:      clamp(1.3rem, 1.9vw, 1.7rem);
  --t-lead:    clamp(1rem, 1.35vw, 1.24rem);
  --t-body:    clamp(0.94rem, 1.02vw, 1.02rem);
  /* The floor used to be 0.62rem, which bottomed out at 9.9px on anything
     narrower than a 1516px viewport. Every micro-label in the site rides this
     token, so the whole family was illegible on a low-DPI screen. */
  --t-label:   clamp(0.7rem, 0.76vw, 0.72rem);

  /* rhythm */
  --gutter:    clamp(20px, 4.4vw, 72px);
  --section-y: clamp(56px, 5.6vw, 92px);
  --maxw:      1560px;
  --nav-h:     clamp(62px, 7vw, 84px);
  --radius:    14px;
  --radius-sm: 10px;

  /* motion */
  --ease:      cubic-bezier(0.22, 1, 0.36, 1);
  --ease-io:   cubic-bezier(0.65, 0, 0.35, 1);
  --dur:       0.62s;
}


/* ---------- 01b. the paper band ----------------------------------------- */

/* The site is not one ground, it is two. Dark carries the canvases, the work
   and the ask; paper carries the reading. A section with .tone-light
   redeclares the palette for its own subtree, so every component underneath
   — chips, rules, tags, cards — follows without knowing anything changed.

   It is deliberately NOT an inversion of the dark tokens. That was tried
   once and read washed out: pure white glared next to the near-black, and
   the teal, which only carries text because the ground is dark, fell to
   about 1.5:1 on it. So the ground is warm paper rather than white, the ink
   keeps the cool cast of the dark ground so the two feel like one family,
   and the accent splits in two — the bright teal stays for fills, a deeper
   one takes over wherever it has to be read. */

.tone-light,
.nav--hero.is-compact.on-paper {
  --bg:            #EFEDE6;   /* warm paper — a different material, not a flipped switch */
  --bg-raise:      #F5F3EE;   /* surfaces still lift toward the light, same as dark */
  --bg-raise-2:    #FFFFFF;
  --bg-sunken:     #E3DFD6;

  --ink:           #16181C;
  --ink-soft:      #454A52;
  --ink-mute:      #5D626B;   /* 5.2:1 on paper; #6E747E measured 4.0 and failed */

  --line:          rgba(22, 24, 28, 0.13);
  --line-strong:   rgba(22, 24, 28, 0.30);

  --accent:        #00FFD5;   /* still the fill: dark ink sits on it */
  --accent-ink:    #096D66;   /* but text needs a teal that holds on paper */
  --accent-glow:   rgba(9, 109, 102, 0.20);
  --accent-wash:   rgba(0, 255, 213, 0.16);
  --on-accent:     #04201D;

  --shadow-card:   0 1px 2px rgba(20, 22, 26, 0.08);
  --shadow-lift:   0 24px 60px rgba(20, 22, 26, 0.14);
}

.tone-light {
  background: var(--bg);
  color: var(--ink);
}

/* The band changes ground on a hard edge. No border and no blend: the
   colour change is the edge, and anything drawn across it — a hairline, a
   gradient — only softens the one moment the reader is meant to notice. */


/* ---------- 02. reset & base -------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;

  /* The modal's scroller was themed and the page's was not, so the one
     scrollbar a visitor actually drags was the browser's. Same treatment,
     hoisted to the document. It stays dark over the paper band because the
     page scrollbar belongs to the page, not to the section under it. */
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

/* WebKit ignores the two properties above, so the same bar again for Safari.
   The modal's own rules are more specific and still win inside it. */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border: 3px solid transparent;
  border-radius: 100px;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-mute); background-clip: padding-box; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: -0.004em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  transition: background 0.5s var(--ease), color 0.5s var(--ease);
}

body.is-locked {
  overflow: hidden;
  position: fixed;
  inset-inline: 0;
  width: 100%;
}

img, svg, canvas, video { display: block; max-width: 100%; }

/* an author `display` outranks the UA rule for [hidden], so anything with a
   display of its own keeps rendering — and a "closed" overlay goes on eating
   clicks meant for the page underneath. One rule settles it for every one. */
[hidden] { display: none !important; }

a { color: inherit; text-decoration: none; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

input, select, textarea { font: inherit; color: inherit; }

::selection { background: var(--accent); color: var(--on-accent); }

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 500;
  padding: 12px 18px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-mono);
  font-size: 0.75rem;
}

.skip:focus { left: 12px; top: 12px; }

/* Visible to a screen reader, to nobody else. The hero lost its paragraph,
   so this is where the sentence about the studio now lives. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* a fine film of grain — kills banding in the big gradients. Fixed and
   exactly viewport-sized: an oversized blended layer over a long page is a
   real compositing cost, and the tile repeats anyway. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: var(--grain-op);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  mix-blend-mode: var(--grain-blend);
}

/* ---------- 03. typography primitives ----------------------------------- */

/* The uppercase letterspaced mono micro-labels — .label, .idx, .tag,
   .chip__n, .project__disc, .field__label, the star tip — all sit at one
   size now. They used to hold seven values between 0.58rem and 0.66rem,
   which at that scale is not a hierarchy anybody can read: it is noise, and
   the smallest of them landed at 9.3px. One value, above the 11px floor. */

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 600;
  line-height: 0.94;
  letter-spacing: -0.045em;
  text-wrap: balance;
}

p { margin: 0; }

.h2 {
  font-size: var(--t-h2);
  line-height: 0.98;
  letter-spacing: -0.046em;
  text-transform: lowercase;
}

.lead {
  font-size: var(--t-lead);
  line-height: 1.52;
  color: var(--ink-soft);
  letter-spacing: -0.012em;
  max-width: 46ch;
}

.label {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--ink-mute);
}

.label--accent { color: var(--accent-ink); }
.accent { color: var(--accent-ink); }

.idx {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--ink-mute);
  transition: color var(--dur) var(--ease);
}

/* ---------- 04. layout primitives --------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  position: relative;
  z-index: 2;
  padding-block: var(--section-y);
}

.rule { border-top: 1px solid var(--line); }

.sec-head {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 9fr);
  gap: clamp(20px, 4vw, 64px);
  align-items: start;
  margin-bottom: clamp(40px, 6vw, 92px);
}

/* That margin is the gap between a section's head and the thing under it.
   In a section that is only a head — the studio page's first one — there is
   nothing under it, and the gap was being paid anyway: 87px of dead space
   on top of the section's own padding. */
.sec-head:last-child { margin-bottom: 0; }

/* And such a section is an intro, not a chapter: a heading and two
   paragraphs in the right-hand column with an empty column beside them. At
   full section padding the void around it reads as twice the number, because
   the left third is empty above and below the join as well. */
.section:has(> .wrap > .sec-head:only-child) {
  padding-block: calc(var(--section-y) * 0.7);
}

.sec-head__aside {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 0.5em;
}

.sec-head__body {
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2vw, 28px);
}

/* ---------- 05. chrome --------------------------------------------------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  border-bottom: 1px solid transparent;
  transition: background 0.45s var(--ease), backdrop-filter 0.45s var(--ease),
              border-color 0.45s var(--ease);
}

.nav.is-stuck {
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  border-bottom-color: var(--line);
}

/* --- the home header, in two states --- */
/* Over the hero it is chrome-less: no ground, no border, a 64px logo and a
   pill one size up. Once the hero is essentially gone (.is-compact, driven
   by an IntersectionObserver in main.js) it collapses onto a blurred ground
   with a 30px logo. One element, one set of links — the bar never leaves,
   only its weight changes. */
.nav--hero {
  transition: background 240ms ease, backdrop-filter 240ms ease,
              border-color 240ms ease;
}

.nav--hero,
.nav--hero.is-stuck {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
}

.nav--hero.is-compact,
.nav--hero.is-compact.is-stuck {
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--line);
}

/* A dark bar floating over the paper band read as a grey smear rather than
   as chrome, because 72% of a near-black over paper is not near-black. So
   the bar wears whichever palette it is standing on — navTone in main.js
   watches the light sections against the strip the header occupies. */
.nav--hero.is-compact.on-paper,
.nav--hero.is-compact.on-paper.is-stuck {
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  border-bottom-color: var(--line);
  color: var(--ink);
}

/* Over the hero the bar is furniture you cannot touch: only the things you
   can actually click take the pointer, so a star that drifts under the top
   band still hovers. Once it has a ground, it blocks like a bar should. */
.nav--hero:not(.is-compact) .nav__inner,
.nav--hero:not(.is-compact) .nav__links,
.nav--hero:not(.is-compact) .nav__actions { pointer-events: none; }

.nav--hero:not(.is-compact) .logo,
.nav--hero:not(.is-compact) .nav__link,
.nav--hero:not(.is-compact) .nav__cta,
.nav--hero:not(.is-compact) .burger { pointer-events: auto; }

/* --- the logo --- */
/* The lockup is the partners' artwork, not type set in a webfont: a 1-bit
   PNG used as a CSS mask, with currentColor painted through it. A mask and
   not an <img>, because the wordmark has to take the colour of whatever
   ground it is on — white over the dark hero, near-black over the paper
   band — and currentColor does not cross into an <img>'s own document.

   font-size stays the size lever so every rule that already tuned the logo
   still works. 0.81em is the height at which the new lockup's x-height
   lands within half a percent of the previous one's, so nothing in the
   header had to move — the mark is simply 11% narrower than before. */
.logo {
  --logo-h: 0.81em;
  display: inline-flex;
  align-items: center;
  flex: none;
  font-size: clamp(30px, 3.35vw, 64px);
  color: var(--ink);
}

/* width is stated rather than left to aspect-ratio: inside a flex line an
   auto width resolved from a ratio is the one thing browsers still disagree
   about, and the logo is the last place to find that out. */
.logo__art {
  position: relative;
  display: block;
  height: var(--logo-h);
  width: calc(var(--logo-h) * 1981 / 288);
}

.logo__word {
  position: absolute;
  inset: 0;
  background-color: currentColor;
  -webkit-mask: url(../assets/wordmark.png) no-repeat center / 100% 100%;
          mask: url(../assets/wordmark.png) no-repeat center / 100% 100%;
}

/* The full stop is kept out of the mask and drawn as a real circle, so the
   one part of the lockup that carries the cyan stays sharp at any size and
   follows --accent instead of being baked into a bitmap. Measured on the
   artwork: centre at 994.5 / 194 of a 1981 x 288 lockup, diameter 53. It
   sits on the baseline — it is a full stop, not a floating dot.

   --accent-ink and not --accent: the dot is punctuation, so it takes the
   accent that is allowed to carry text. In dark they are the same #00FFD5;
   on paper the raw accent is a cyan so light the full stop disappears. */
.logo__dot {
  position: absolute;
  left: 50.202%;
  top: 67.361%;
  width: 2.675%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background-color: var(--accent-ink);
}

/* the footer's lockup: sized to sit with the footer's own type rather than
   to lead a page */
.logo--foot { font-size: clamp(22px, 2vw, 32px); }

/* the collapsed size. font-size is the only thing that moves, so the three
   parts keep their proportions and their shared baseline all the way down. */
.nav--hero .logo { transition: font-size 240ms ease; }
.nav--hero.is-compact .logo { font-size: 30px; }

/* The pill, one size up (+15% type, +15% padding) so it holds its own beside
   a 64px logo, and back to the standard size once the logo collapses. Weight
   400 is stated rather than inherited from .btn's 500, so the pill sits at a
   named point on the weight axis rather than wherever 500 lands beside the
   700 logo. */
.nav--hero .nav__cta,
.hero__cta {
  flex: none;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: calc(var(--t-label) * 1.15);
  padding: 14px 24px;
  transition: color 0.4s var(--ease), border-color 0.4s var(--ease),
              transform 0.5s var(--ease), box-shadow 0.4s var(--ease),
              font-size 240ms ease, padding 240ms ease;
}

.nav--hero.is-compact .nav__cta {
  font-size: var(--t-label);
  padding: 12px 21px;
}

@media (prefers-reduced-motion: reduce) {
  .nav--hero .nav__cta { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .nav--hero .logo { transition: none; }
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(16px, 3vw, 40px);
  height: var(--nav-h);
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(20px, 2.4vw, 40px);
  margin-inline: auto;
}

.nav__link {
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-soft);
  padding-block: 4px;
  transition: color 0.35s var(--ease);
}

.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.5s var(--ease);
}

.nav__link:hover { color: var(--ink); }
.nav__link:hover::after { transform: scaleX(1); transform-origin: left; }

.nav__actions {
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.2vw, 18px);
  flex: none;
}

/* --- buttons --- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 21px;
  border-radius: 100px;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  white-space: nowrap;
  overflow: hidden;
  isolation: isolate;
  transition: color 0.4s var(--ease), border-color 0.4s var(--ease),
              transform 0.5s var(--ease), box-shadow 0.4s var(--ease);
}

.btn--lg { padding: 15px 27px; }
.btn--block { width: 100%; }

/* the fill sweeps up from the bottom edge */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--accent);
  transform: translateY(101%);
  transition: transform 0.5s var(--ease);
}

.btn:hover::before { transform: translateY(0); }

.btn--ghost {
  border: 1px solid var(--line-strong);
  color: var(--ink);
}

.btn--ghost:hover { color: var(--on-accent); border-color: var(--accent); }

.btn--solid {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 0 0 0 var(--accent-glow);
}

.btn--solid::before { background: var(--ink); }
.btn--solid:hover { color: var(--bg); box-shadow: 0 8px 30px var(--accent-glow); }

.btn[disabled] { opacity: 0.5; pointer-events: none; }

.btn__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

/* --- burger (mobile only) --- */

.burger {
  display: none;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: 100px;
  position: relative;
  flex: none;
}

.burger span {
  position: absolute;
  left: 11px;
  width: 18px;
  height: 1.5px;
  background: var(--ink);
  border-radius: 2px;
  transition: transform 0.4s var(--ease), opacity 0.3s var(--ease);
}

.burger span:nth-child(1) { top: 16px; }
.burger span:nth-child(2) { top: 22px; }

.burger.is-open span:nth-child(1) { transform: translateY(3px) rotate(45deg); }
.burger.is-open span:nth-child(2) { transform: translateY(-3px) rotate(-45deg); }

/* --- custom cursor --- */

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 400;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 50%;
  /* the page is dark at one end and paper at the other, and a fixed ring
     cannot read a section's tokens — difference blending inverts it against
     whatever it happens to be over */
  mix-blend-mode: difference;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s var(--ease), width 0.4s var(--ease),
              height 0.4s var(--ease), margin 0.4s var(--ease),
              background 0.4s var(--ease), border-color 0.4s var(--ease);
  will-change: transform;
}

.cursor.is-on { opacity: 1; }

.cursor.is-hot {
  width: 52px;
  height: 52px;
  margin: -26px 0 0 -26px;
  background: var(--accent-wash);
  border-color: var(--accent);
}

@media (hover: none), (pointer: coarse) {
  .cursor { display: none; }
}

/* ---------- 06. mobile menu ---------------------------------------------- */

.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: var(--bg);
  padding: calc(var(--nav-h) + 24px) var(--gutter) calc(24px + env(safe-area-inset-bottom));
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  overflow-y: auto;
}

.mobile-menu.is-open { opacity: 1; }

.mobile-menu__inner {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  gap: 4px;
}

.mobile-menu__link {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding-block: clamp(14px, 3.4vw, 22px);
  border-bottom: 1px solid var(--line);
  font-size: clamp(1.7rem, 8vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.045em;
  text-transform: lowercase;
  color: var(--ink);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease),
              color 0.3s var(--ease);
}

.mobile-menu.is-open .mobile-menu__link { opacity: 1; transform: none; }
.mobile-menu.is-open .mobile-menu__link:nth-child(1) { transition-delay: 0.05s; }
.mobile-menu.is-open .mobile-menu__link:nth-child(2) { transition-delay: 0.11s; }
.mobile-menu.is-open .mobile-menu__link:nth-child(3) { transition-delay: 0.17s; }

.mobile-menu__link .idx { font-size: 0.7rem; color: var(--ink-mute); }
.mobile-menu__link:active { color: var(--accent-ink); }

.mobile-menu__foot {
  margin-top: auto;
  padding-top: 32px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-items: center;
  opacity: 0;
  transition: opacity 0.5s var(--ease) 0.24s;
}

.mobile-menu.is-open .mobile-menu__foot { opacity: 1; }

/* ---------- 07. hero ----------------------------------------------------- */

/* Everything in the hero is absolutely positioned against this box: the
   canvas fills it, the header sits on top, the headline hangs off the
   bottom-left, the three labels are placed per-frame from JS. Nothing is in
   normal flow, so nothing can push anything else around. */
.hero {
  position: relative;
  min-height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

.hero__canvas {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(56% 60% at 68% 42%, var(--accent-wash), transparent 70%),
    radial-gradient(84% 78% at 50% 104%, var(--bg-sunken), transparent 62%);
  pointer-events: none;
}

/* --- headline, bottom-left, aligned to the logo by sharing .wrap --- */
.hero__bottom {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(48px, 12vh, 140px);
  z-index: 3;
  /* the headline must never eat a star's hover — only the button takes the
     pointer back */
  pointer-events: none;
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: clamp(38px, 4.4vw, 86px);
  line-height: 1.12;
  letter-spacing: -0.005em;
  text-transform: lowercase;
  text-wrap: wrap;
  color: #FFFFFF;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.9s var(--ease) 0.12s, transform 0.9s var(--ease) 0.12s;
}

.hero.is-in .hero-title { opacity: 1; transform: none; }

/* the phone's one button — the header up there only has room for the logo */
.hero__cta {
  display: none;
  margin-top: 28px;
  pointer-events: auto;
}

/* --- the three discipline labels --- */
/* Positioned every frame by constellation.js from the same screen
   coordinates it draws the stars with, so they cannot drift out of step on
   resize, on rotation, or between DPRs. */
.star-labels {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.9s var(--ease) 0.35s;
}

.hero.is-in .star-labels { opacity: 1; }

/* Held invisible until the loop has placed it. rAF does not run in a
   background tab, so without this a label would sit in the top-left corner
   until the tab is looked at. */
.star-label {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(13px, 1.45vw, 28px);
  line-height: 1;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(255, 255, 255, 0.30);
  /* never a hover target itself: the star underneath owns the pointer */
  pointer-events: none;
  transition: color 200ms ease;
  will-change: transform;
}

.star-label.is-placed { opacity: 1; }

.star-label.is-live { color: rgba(255, 255, 255, 0.90); }

/* The card that follows a hovered star. It used to name the discipline;
   it now previews an actual project, because "here is work" beats "here is
   a category" in the two seconds a visitor gives a homepage. */
.star-tip {
  position: absolute;
  z-index: 3;
  top: 0;
  left: 0;
  width: 250px;
  padding: 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--bg-raise) 96%, transparent);
  box-shadow: var(--shadow-lift);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -108%) scale(0.96);
  transform-origin: 50% 100%;
  transition: opacity 0.32s var(--ease), transform 0.32s var(--ease);
}

.star-tip.is-on { opacity: 1; transform: translate(-50%, -118%) scale(1); }

/* a star near the top of the hero would push its card up under the fixed
   nav, so the card flips and hangs below the star instead */
.star-tip--below { transform: translate(-50%, 8%) scale(0.96); transform-origin: 50% 0; }
.star-tip--below.is-on { transform: translate(-50%, 18%) scale(1); }

.star-tip__media {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 7px;
  overflow: hidden;
  background:
    radial-gradient(46% 58% at 24% 24%, var(--accent-glow), transparent 62%),
    radial-gradient(52% 52% at 78% 74%, rgba(96, 118, 190, 0.22), transparent 66%),
    linear-gradient(152deg, var(--bg-raise-2), var(--bg-sunken) 78%);
}

/* the hairline plate stands in until a real still is dropped in */
.star-tip__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, var(--line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--line) 1px, transparent 1px);
  background-size: 34px 34px;
  -webkit-mask-image: radial-gradient(72% 72% at 50% 50%, #000, transparent 84%);
          mask-image: radial-gradient(72% 72% at 50% 50%, #000, transparent 84%);
}

.star-tip__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.star-tip__media:has(img:not([hidden]))::after { display: none; }

.star-tip__body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 11px 5px 4px;
}

.star-tip__k {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent-ink);
}

.star-tip__t {
  font-size: 0.98rem;
  font-weight: 500;
  letter-spacing: -0.028em;
  color: var(--ink);
}

.star-tip__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 5px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

/* a card is too much furniture on a phone, where there is no hover anyway —
   tapping a star just takes you to the project */
@media (hover: none), (pointer: coarse) {
  .star-tip { display: none; }
}

/* ---------- 08. clients marquee ------------------------------------------ */

.clients {
  position: relative;
  z-index: 2;
  padding-block: clamp(22px, 2.8vw, 34px);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--bg-raise);
  overflow: hidden;
}

/* The label sits beside the strip rather than centred above it. Centred, it
   left a band of dead space that made the whole section read top-heavy. */
.clients__inner {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: clamp(22px, 3.5vw, 54px);
  /* No max-width here on purpose. The label still lines up with the site's
     content column, but the strip runs all the way to the real viewport edge
     so it can fade out into nothing instead of stopping at a container. */
  padding-left: max(var(--gutter), calc((100% - var(--maxw)) / 2 + var(--gutter)));
}

.clients__label {
  margin: 0;
  white-space: nowrap;
  line-height: 1.5;
  border-left: 1px solid var(--line);
  padding-left: clamp(14px, 1.6vw, 20px);
}

.marquee {
  position: relative;
  display: flex;
  width: 100%;
  overflow: hidden;
  /* Soft at both ends. The left fade is short — it only has to soften the
     hand-off from the label — while the right runs long, so a logo dissolves
     rather than getting sliced by the viewport edge. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 7%, #000 72%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 7%, #000 72%, transparent 100%);
}

/* One element moves, not each track. Animating every track separately meant
   a clone added later started its own animation from zero while the original
   had been running since page load — they drifted apart, and the drift is
   what opened that wide hole in the middle of the strip. */
.marquee__row {
  display: flex;
  flex: none;
  align-items: center;
  min-width: max-content;
  animation: marquee var(--marquee-dur, 40s) linear infinite;
  will-change: transform;
}

.marquee:hover .marquee__row { animation-play-state: paused; }

/* --marquee-shift is one track's measured width, so the row lands exactly
   where it started with the next copy in place. */
@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(-1 * var(--marquee-shift, 100%)), 0, 0); }
}

.marquee__track {
  display: flex;
  flex: none;
  align-items: center;
  min-width: max-content;
}

/* Each logo sits in a slot of identical width rather than being separated by
   a fixed gap. With artwork ranging from a 29px roundel to a 168px wordmark,
   an even gap gives a visibly uneven rhythm — even slots give an even one,
   and every mark ends up optically centred. */
.marquee__item {
  flex: 0 0 auto;
  width: clamp(148px, 13.5vw, 210px);
  height: clamp(34px, 3.2vw, 46px);
  display: flex;
  align-items: center;
  justify-content: center;
}

.marquee__item img {
  max-height: 62%;
  max-width: 78%;
  width: auto;
  height: auto;
  opacity: 0.45;
  filter: grayscale(1);
  transition: opacity 0.45s var(--ease), filter 0.45s var(--ease);
}

.marquee:hover .marquee__item img { opacity: 0.26; }
.marquee__item:hover img { opacity: 1; }

.marquee__item--tall img { max-height: 100%; }

@media (prefers-reduced-motion: reduce) {
  .marquee__row { animation: none; }
  .marquee { overflow-x: auto; }
}

/* ---------- 09. disciplines ---------------------------------------------- */

.disciplines__list { border-top: 1px solid var(--line); }

.discipline {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2.1fr) minmax(0, 2.7fr) minmax(0, 1.9fr);
  align-items: start;
  gap: clamp(18px, 3vw, 56px);
  padding-block: clamp(26px, 3.4vw, 50px);
  padding-inline: clamp(0px, 1.4vw, 20px);
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius-sm);
  isolation: isolate;
}

.discipline::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--accent-wash), transparent 62%);
  opacity: 0;
  transition: opacity 0.55s var(--ease);
}

.discipline::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.8s var(--ease);
}

.discipline:hover::before { opacity: 1; }
.discipline:hover::after  { transform: scaleX(1); }
.discipline:hover .idx    { color: var(--accent-ink); }
.discipline:hover .discipline__title { transform: translateX(10px); }

.discipline__title {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.035em;
  line-height: 1.1;
  text-transform: lowercase;
  transition: transform 0.6s var(--ease);
}

.discipline__body {
  color: var(--ink-soft);
  font-size: var(--t-body);
  line-height: 1.62;
  max-width: 52ch;
}

.discipline__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  justify-content: flex-end;
  align-items: flex-start;
  max-width: 340px;
  margin-left: auto;
}

.tag {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-mute);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 5px 11px;
  white-space: nowrap;
  transition: border-color 0.45s var(--ease), color 0.45s var(--ease);
}

.discipline:hover .tag { border-color: var(--line-strong); color: var(--ink-soft); }

/* ---------- 10. work grid ------------------------------------------------ */

.work__filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: clamp(24px, 3vw, 40px);
}

.work__filters-label { margin-right: 8px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 15px;
  border: 1px solid var(--line);
  border-radius: 100px;
  background: var(--bg-raise);
  font-size: 0.84rem;
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--ink-soft);
  white-space: nowrap;
  transition: color 0.35s var(--ease), border-color 0.35s var(--ease),
              background 0.35s var(--ease);
}

.chip:hover { color: var(--ink); border-color: var(--line-strong); }

.chip.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}

.chip__n {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  padding: 2px 6px;
  border-radius: 100px;
  background: var(--bg-sunken);
  color: var(--ink-mute);
}

.chip.is-on .chip__n {
  background: color-mix(in srgb, var(--bg) 22%, transparent);
  color: var(--bg);
}

.work__grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(12px, 1.4vw, 22px);
}

/* dimming the siblings of whatever is hovered is what makes the grid tactile */
.work__grid:hover .project { opacity: 0.4; }
.work__grid:hover .project:hover { opacity: 1; }

.project {
  position: relative;
  grid-column: span 6;
  display: block;
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}

.project--wide  { grid-column: span 8; }
.project--slim  { grid-column: span 4; }
.project--full  { grid-column: span 12; }

/* The frame keeps the dark ground even where the section around it is
   paper. Two reasons: what replaces these plates is real work — renders,
   film stills, photography — which is dark and rich, and on paper a white
   plate on a white section is not a plate at all, it is a hole. So the
   artwork reads as artwork on both grounds, and the caption underneath it
   is the part that follows the section. */
.project__frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--ground-raise);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-card);
  aspect-ratio: 4 / 3;
  isolation: isolate;
  transition: box-shadow 0.5s var(--ease), border-color 0.5s var(--ease);
}

.project:hover .project__frame {
  box-shadow: var(--shadow-lift);
  border-color: var(--line-strong);
}

.project--wide .project__frame { aspect-ratio: 16 / 11; }
.project--slim .project__frame { aspect-ratio: 3 / 4; }
.project--full .project__frame { aspect-ratio: 24 / 9; }

/* placeholder visual — replaced the moment an <img> or <video> lands inside */
.project__media {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(42% 55% at 22% 26%, var(--accent-glow), transparent 62%),
    radial-gradient(48% 48% at 78% 72%, rgba(120, 140, 255, 0.16), transparent 64%),
    linear-gradient(150deg, var(--ground-raise), var(--ground) 72%);
  transform: scale(1.02);
  transition: transform 1.05s var(--ease);
}

.project__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, var(--line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--line) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(75% 75% at 50% 50%, #000, transparent 82%);
          mask-image: radial-gradient(75% 75% at 50% 50%, #000, transparent 82%);
}

.project__media:has(img)::after,
.project__media:has(video)::after { display: none; }

.project:nth-child(2n) .project__media { filter: hue-rotate(-28deg); }
.project:nth-child(3n) .project__media { filter: hue-rotate(38deg); }
.project:nth-child(5n) .project__media { filter: hue-rotate(-64deg) saturate(0.85); }

.project__media img,
.project__media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.project:hover .project__media { transform: scale(1.06); }

.project__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 34%, rgba(6, 7, 8, 0.82) 100%);
  opacity: 0;
  transition: opacity 0.55s var(--ease);
}

.project:hover .project__frame::after { opacity: 1; }

.project__tagline {
  position: absolute;
  z-index: 2;
  left: clamp(16px, 1.6vw, 26px);
  right: clamp(16px, 1.6vw, 26px);
  bottom: clamp(16px, 1.6vw, 24px);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.5s var(--ease), transform 0.55s var(--ease);
}

.project:hover .project__tagline { opacity: 1; transform: none; }

/* this copy sits on the dark scrim over the artwork, not on the page, so it
   stays light in both themes */
.project__brief {
  font-size: 0.86rem;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.82);
  max-width: 34ch;
}

.project__view {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #00FFD5;
  flex: none;
}

.project__cap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-top: 14px;
}

.project__name {
  font-size: clamp(0.96rem, 1.1vw, 1.1rem);
  font-weight: 500;
  letter-spacing: -0.028em;
  text-transform: lowercase;
  transition: color 0.4s var(--ease);
}

.project:hover .project__name { color: var(--accent-ink); }

.project__disc {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-mute);
  text-align: right;
  flex: none;
}

/* When a star sends you here, the project it pointed at announces itself —
   otherwise you land in a grid with no idea which one you asked for. */
.project.is-cued .project__frame {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), var(--shadow-lift);
}

.project.is-cued .project__name { color: var(--accent-ink); }

@media (prefers-reduced-motion: no-preference) {
  .project.is-cued .project__frame { animation: cue 1.9s var(--ease); }
  @keyframes cue {
    0%, 100% { box-shadow: 0 0 0 1px var(--accent), var(--shadow-lift); }
    50%      { box-shadow: 0 0 0 5px var(--accent-wash), var(--shadow-lift); }
  }
}

.work__more {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-top: clamp(32px, 4vw, 60px);
  padding-top: clamp(22px, 2.6vw, 34px);
  border-top: 1px solid var(--line);
}

/* ---------- 11. contact block + footer ----------------------------------- */

.contact {
  position: relative;
  z-index: 2;
  padding-block: clamp(72px, 8.5vw, 140px) clamp(44px, 6vw, 76px);
  border-top: 1px solid var(--line);
  overflow: hidden;
}


.contact::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -46%;
  width: min(1100px, 130vw);
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: radial-gradient(circle, var(--accent-wash), transparent 62%);
  pointer-events: none;
}

.contact__inner {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: clamp(20px, 2.8vw, 36px);
}

.contact__title {
  font-size: clamp(2.4rem, 7.4vw, 6.6rem);
  line-height: 0.9;
  letter-spacing: -0.054em;
  text-transform: lowercase;
}

.contact__lead { text-align: center; max-width: 52ch; }

.contact__cta {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

.footer {
  position: relative;
  z-index: 2;
  padding-block: clamp(26px, 3vw, 40px);
  border-top: 1px solid var(--line);
}

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 18px 32px;
}

.footer__set {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 26px;
  align-items: center;
}

.footer__link {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-mute);
  transition: color 0.4s var(--ease);
}

.footer__link:hover { color: var(--accent-ink); }

/* ---------- 12. contact modal + form ------------------------------------- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(12px, 3vw, 40px);
}

.modal__scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}

.modal.is-open .modal__scrim { opacity: 1; }

/* The native bar sat on the panel's rounded edge and broke the corner.
   Thin, inset, and painted from the theme so it reads as part of the panel. */
.modal__panel {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
  scrollbar-gutter: stable;
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 760px;
  max-height: min(92svh, 900px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: clamp(26px, 4vw, 48px);
  border: 1px solid var(--line);
  border-radius: clamp(14px, 2vw, 22px);
  background: var(--bg-raise);
  box-shadow: var(--shadow-modal);
  opacity: 0;
  transform: translateY(22px) scale(0.98);
  transition: opacity 0.45s var(--ease), transform 0.5s var(--ease);
}

.modal.is-open .modal__panel { opacity: 1; transform: none; }

.modal__panel::-webkit-scrollbar { width: 10px; }
.modal__panel::-webkit-scrollbar-track { background: transparent; }

.modal__panel::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 100px;
  /* a transparent border clipped to the padding box insets the thumb, so it
     floats clear of the panel edge instead of sitting on the corner */
  border: 3px solid transparent;
  background-clip: padding-box;
}

.modal__panel::-webkit-scrollbar-thumb:hover { background: var(--ink-mute); background-clip: padding-box; }

.modal__close {
  position: absolute;
  top: clamp(14px, 2vw, 22px);
  right: clamp(14px, 2vw, 22px);
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--ink-soft);
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease),
              transform 0.4s var(--ease);
}

.modal__close:hover {
  color: var(--ink);
  border-color: var(--line-strong);
  transform: rotate(90deg);
}

.modal__head {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: clamp(24px, 3vw, 34px);
  padding-right: 44px;
}

.modal__title {
  font-size: clamp(1.7rem, 4vw, 2.7rem);
  line-height: 0.98;
  letter-spacing: -0.048em;
  text-transform: lowercase;
}

.modal__intro {
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 46ch;
}

/* --- form --- */

.form { display: flex; flex-direction: column; gap: clamp(16px, 2vw, 22px); }

.form__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(12px, 1.6vw, 20px);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.field__label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-mute);
  padding: 0;
}

.field__label span { color: var(--accent-ink); }

.field__input {
  width: 100%;
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink);
  caret-color: var(--accent);
  font-size: 0.96rem;
  letter-spacing: -0.012em;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease),
              box-shadow 0.3s var(--ease);
  appearance: none;
}

.field__input::placeholder { color: var(--ink-mute); }
.field__input:hover { border-color: var(--line-strong); }

.field__input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}

.field__input--area { resize: vertical; min-height: 108px; line-height: 1.55; }

.field__select { position: relative; }

.field__caret {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--ink-mute);
  font-size: 0.8rem;
}

.field__error {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--danger);
}

.field__error:empty { display: none; }

.field.has-error .field__input { border-color: var(--danger); }

.chip-set { display: flex; flex-wrap: wrap; gap: 8px; }

.chip--check {
  position: relative;
  cursor: pointer;
  user-select: none;
  padding: 10px 16px;
  background: var(--bg);
}

.chip--check input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.chip--check:hover { border-color: var(--line-strong); color: var(--ink); }

.chip--check:has(input:checked) {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.chip--check:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  padding-top: 4px;
}

.form__note {
  font-size: 0.78rem;
  color: var(--ink-mute);
  max-width: 34ch;
}

.form__status {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  line-height: 1.6;
  color: var(--ink-soft);
}

.form__status:empty { display: none; }
.form__status.is-bad { color: #FF6B6B; }

.modal__done {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  padding-block: clamp(16px, 3vw, 32px);
}

.modal__done-mark { color: var(--accent-ink); }

.modal__done-title {
  font-size: clamp(1.6rem, 3.4vw, 2.3rem);
  letter-spacing: -0.045em;
  text-transform: lowercase;
}

/* --- touch targets ------------------------------------------------------- */
/* Sizes tuned against a cursor leave the tap area under the 44px a finger
   needs: the burger and the pill both land at 40, the filter chips at 38.
   Keyed off the pointer rather than the viewport, so a phone gets the bigger
   hit box and a mouse sees nothing move. Only the box grows — padding, type
   and colour are untouched. */
@media (pointer: coarse) {
  .btn,
  .chip { min-height: 44px; }

  .burger {
    width: 44px;
    height: 44px;
  }

  /* The two lockups are links — home, and back to top — and at the header's
     30px floor the artwork is 24px tall, 18px in the footer. The padding
     grows the hit box and the matching negative margin gives the space back
     to the layout, so the mark does not move by a pixel in either place. */
  .logo {
    padding: 13px 8px;
    margin: -13px -8px;
  }
}


/* ---------- 13. motion / reveal ------------------------------------------ */

.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.95s var(--ease), transform 0.95s var(--ease);
}

.reveal.is-in { opacity: 1; transform: none; }

.reveal-group > * {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.85s var(--ease), transform 0.85s var(--ease);
}

.reveal-group.is-in > * { opacity: 1; transform: none; }
.reveal-group.is-in > *:nth-child(1) { transition-delay: 0.02s; }
.reveal-group.is-in > *:nth-child(2) { transition-delay: 0.09s; }
.reveal-group.is-in > *:nth-child(3) { transition-delay: 0.16s; }
.reveal-group.is-in > *:nth-child(4) { transition-delay: 0.23s; }
.reveal-group.is-in > *:nth-child(5) { transition-delay: 0.30s; }
.reveal-group.is-in > *:nth-child(6) { transition-delay: 0.37s; }
.reveal-group.is-in > *:nth-child(7) { transition-delay: 0.44s; }

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal-group > *,
  .hero-title,
  .star-labels,
  .mobile-menu__link,
  .mobile-menu__foot {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .grain { display: none; }
  .modal__panel, .modal__scrim { transition: none; }
}

/* ---------- 15. about page ----------------------------------------------- */

/* This page opens the way the home page does: a canvas behind, the type
   hung off the bottom-left on the header's own gutter, and the bar overhead
   with nothing under it. Not the full 100svh of the hero — the constellation
   is the home page's signature and this scene should not compete with it —
   but the same shape. */

.page-head {
  position: relative;
  z-index: 2;
  min-height: min(86svh, 940px);
  padding-top: calc(var(--nav-h) + clamp(48px, 9vh, 120px));
  padding-bottom: clamp(48px, 12vh, 140px);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: clamp(18px, 2.4vw, 30px);
}

.page-head .wrap { display: flex; flex-direction: column; gap: clamp(18px, 2.4vw, 30px); }

/* the same face and weight as the home headline, one step down because this
   one runs to three lines */
.page-head__title {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: clamp(34px, 3.7vw, 72px);
  line-height: 1.12;
  letter-spacing: -0.005em;
  text-transform: lowercase;
  text-wrap: wrap;
  color: #FFFFFF;
}

.page-head__lead { max-width: 48ch; }

/* --- founders --- */

.founders,
.steps {
  list-style: none;
  margin: 0;
  padding: 0;
}

.founders { border-top: 1px solid var(--line); }

.founder {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr);
  gap: clamp(16px, 3vw, 44px);
  padding-block: clamp(26px, 3.4vw, 46px);
  border-bottom: 1px solid var(--line);
}

.founder .idx { padding-top: 0.35em; }

.founder__name {
  font-size: clamp(1.3rem, 2.1vw, 1.85rem);
  font-weight: 500;
  letter-spacing: -0.038em;
  line-height: 1.05;
}

.founder__disc {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--accent-ink);
  margin-top: 8px;
}

.founder__note {
  color: var(--ink-soft);
  line-height: 1.62;
  max-width: 46ch;
}

@media (max-width: 820px) {
}

@media (max-width: 560px) {
  .founder { grid-template-columns: 1fr; gap: 10px; }
}

/* --- process --- */

.steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.step {
  background: var(--bg-raise);
  padding: clamp(20px, 2.2vw, 30px);
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: background 0.45s var(--ease);
}

.step:hover { background: var(--bg-raise-2); }

.step__name {
  font-size: clamp(1.15rem, 1.7vw, 1.5rem);
  font-weight: 500;
  letter-spacing: -0.035em;
  text-transform: lowercase;
}

.step__body { color: var(--ink-soft); font-size: 0.95rem; line-height: 1.58; flex: 1; }

.step__out {
  color: var(--accent-ink);
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

@media (max-width: 900px) { .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .steps { grid-template-columns: 1fr; } }

/* ---------- 14. responsive ----------------------------------------------- */

/* Big desktop monitors. Above ~1800px the content column stops growing while
   the viewport keeps going, so the hero drains into a wide empty band with the
   type stranded in one corner. The column grows with the screen, the type
   scale gets a second stage, and the hero stops being pinned to the full
   viewport height — on a 32" that alone was most of the emptiness. */
@media (min-width: 1800px) {
  :root {
    --maxw:      1840px;
    --gutter:    clamp(72px, 5vw, 110px);
    --t-h2:      clamp(4rem, 3.4vw, 5.2rem);
    --t-lead:    clamp(1.24rem, 1vw, 1.5rem);
    --t-body:    clamp(1.02rem, 0.82vw, 1.15rem);
    --t-label:   clamp(0.72rem, 0.56vw, 0.84rem);
    --section-y: clamp(92px, 4.8vw, 120px);
  }

  /* a tall monitor does not need a full-height hero: capping it brings the
     work section into reach instead of parking it below a screen of nothing */
  .hero { min-height: min(100svh, 1000px); }
}

/* Ultrawide. The constellation is sized off the hero's height, so past a
   point it stops keeping up with the width and drifts away from the type. */
@media (min-width: 2400px) {
  :root { --maxw: 2100px; }
}

@media (max-width: 1100px) {
  .discipline {
    grid-template-columns: auto minmax(0, 1fr);
    row-gap: 14px;
  }
  .discipline__body { grid-column: 2; }
  .discipline__tags {
    grid-column: 2;
    justify-content: flex-start;
    max-width: none;
    margin-left: 0;
  }
}

/* the point where the nav loses its inline links */
@media (max-width: 960px) {
  .nav__links { display: none; }
  .burger { display: block; }
  .nav__cta { display: none; }
}

/* On the pages with a canvas head, the pill stays down to the phone
   breakpoint: below 960 the links are already behind the burger, and the
   one that takes over further down (.hero__cta) has not appeared yet. */
@media (min-width: 769px) and (max-width: 960px) {
  .nav--hero .nav__cta { display: inline-flex; }
}

@media (max-width: 900px) {
  .sec-head { grid-template-columns: 1fr; gap: 16px; }
  .sec-head__aside { flex-direction: row; align-items: center; gap: 14px; padding-top: 0; }

  .project,
  .project--wide,
  .project--slim,
  .project--full { grid-column: span 12; }

  .project__frame,
  .project--wide .project__frame,
  .project--slim .project__frame,
  .project--full .project__frame { aspect-ratio: 4 / 3; }

  /* no hover on touch, so the label and scrim ride visible */
  .project__tagline { opacity: 1; transform: none; }
  .project__frame::after { opacity: 1; }
  .work__grid:hover .project { opacity: 1; }
}

/* Phones. The header has room for the logo at its 30px floor and nothing
   else, so the hero's one button moves down beside the headline. 32px of
   padding on both, so the headline still starts exactly under the "o" of
   the logo.
   No type sizes here: both clamps already bottom out where this breakpoint
   wants them — the headline at 38px and the labels at 13px — and pinning
   them again would only add a step at the boundary. */
@media (max-width: 768px) {
  .nav--hero .nav__inner,
  .hero__bottom { padding-inline: 32px; }

  .hero__cta { display: inline-flex; }
}

@media (max-width: 720px) {
  .discipline { grid-template-columns: 1fr; padding-inline: 0; }
  .discipline__body,
  .discipline__tags { grid-column: 1; }

  .clients__inner {
    grid-template-columns: 1fr;
    gap: 16px;
    padding-inline: var(--gutter);
  }
  .clients__label {
    border-left: 0;
    padding-left: 0;
    text-align: center;
  }
  .clients__label br { display: none; }
  .marquee {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 10%, #000 90%, transparent);
            mask-image: linear-gradient(to right, transparent, #000 10%, #000 90%, transparent);
  }

  .work__filters { gap: 6px; }
  .work__filters-label { width: 100%; margin: 0 0 4px; }
  .chip { padding: 8px 13px; font-size: 0.8rem; }

  .work__more { flex-direction: column; align-items: flex-start; }
  .footer__inner { flex-direction: column; align-items: flex-start; }

  .form__row { grid-template-columns: 1fr; }
  .form__foot { flex-direction: column; align-items: stretch; }
  .form__foot .btn { width: 100%; }

  /* on a phone the modal is a sheet that rises from the bottom edge */
  .modal { padding: 0; align-items: flex-end; }
  .modal__panel {
    max-width: none;
    max-height: 94svh;
    border-radius: 20px 20px 0 0;
    border-bottom: 0;
    padding-bottom: calc(28px + env(safe-area-inset-bottom));
  }
}

@media (max-width: 400px) {
  .contact__cta { width: 100%; flex-direction: column; }
  .contact__cta .btn { width: 100%; }
}

/* ---------- 15. added: nav state, labs, founders, studio scene ----------- */

/* --- where you are ---
   The underline already existed as a hover affordance; the active page just
   holds it open and takes the accent. One rule covers both cases: the anchor
   page (about.html carries aria-current="page" in the markup) and the home
   page sections, which main.js marks with aria-current="true" as you scroll. */
.nav__link[aria-current] {
  color: var(--accent-ink);
}
.nav__link[aria-current]::after {
  transform: scaleX(1);
  transform-origin: left;
}
.mobile-menu__link[aria-current] { color: var(--accent-ink); }
.mobile-menu__link[aria-current] .idx { color: var(--accent-ink); opacity: 0.75; }

/* --- labs ---
   Numbered on purpose. Nine loose experiments read as nine loose images;
   L/001–L/009 reads as a body of research. */
.labs__list {
  list-style: none;
  margin: clamp(40px, 5vw, 76px) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.lab {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: clamp(24px, 2.6vw, 38px);
  background: var(--bg);
  transition: background 0.5s var(--ease);
}

.lab:hover { background: var(--accent-wash); }

.lab__idx {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: 0.16em;
  color: var(--accent-ink);
}

.lab__title {
  font-size: clamp(1.15rem, 1.7vw, 1.5rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.1;
}

.lab__body {
  color: var(--ink-soft);
  line-height: 1.6;
  flex: 1;
}

.lab__meta {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--ink-soft);
  opacity: 0.72;
  padding-top: 4px;
}

@media (max-width: 900px) { .labs__list { grid-template-columns: 1fr; } }

/* --- founders, rebuilt ---
   Three identical columns, read left to right with nothing to rank them:
   same width, same photo, same type. The old layout had an index number and
   a two-column body, which put Daniel first and made the order look like a
   hierarchy. */
.founders {
  border-top: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 52px);
  margin-top: clamp(40px, 5vw, 76px);
}

.founder {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding-block: 0;
  border-bottom: 0;
  border-top: 1px solid var(--line);
  padding-top: clamp(18px, 1.8vw, 26px);
}

/* the photographs are already the brand: a silhouette against a lit disc,
   which is the same shape as a star in the hero. No filter, no frame. */
/* The frame matches the photograph's own 1200x896, and object-fit is
   contain rather than cover, so the lit disc can never be clipped at the
   sides — cropping it turns a star into a wall. The plate behind is black
   because the photograph's ground is black: any rounding gap is invisible. */
.founder__shot {
  position: relative;
  aspect-ratio: 1200 / 896;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  margin-bottom: clamp(18px, 2vw, 26px);
}

.founder__shot img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  transition: opacity 0.7s var(--ease), filter 0.7s var(--ease);
  opacity: 0.92;
}

/* no zoom on hover — scaling a contained image would crop the disc, which is
   the one thing this frame exists to protect. The light comes up instead. */
.founder:hover .founder__shot img { opacity: 1; filter: brightness(1.08); }

.founder__name { margin: 0; }
.founder__disc { margin-top: 10px; }
.founder__note { margin-top: 14px; max-width: none; }

@media (max-width: 860px) {
  .founders { grid-template-columns: 1fr; gap: clamp(30px, 6vw, 44px); }
}

/* --- the studio scene sits behind the page head, like the hero canvas ---
   overflow:hidden matters: the desk legs run below the scene's centre, and
   without it they draw straight through the rule into the next section. */
.page-head { position: relative; overflow: hidden; }

.page-head__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  z-index: 0;
}

.page-head .wrap { position: relative; z-index: 1; }

/* --- footer: a phone-sized target, and the muted address line --- */
/* --- the address line ---
   Its own row under the footer's link sets rather than another chip in them:
   a postal address wrapped into a row of uppercase mono nav links reads as a
   broken link, and at 720px it was pushing the set onto three lines. */
.footer__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 32px;
  margin-top: clamp(20px, 2.4vw, 32px);
  padding-top: clamp(16px, 1.8vw, 24px);
  border-top: 1px solid var(--line);
}

.footer__addr {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  font-style: normal;
  line-height: 1.7;
  color: var(--ink-soft);
  opacity: 0.72;
}

.footer__addr--link {
  opacity: 1;
  text-transform: lowercase;
  letter-spacing: 0.08em;
  transition: color 0.4s var(--ease);
}

.footer__addr--link:hover { color: var(--accent-ink); }

.footer__br { display: none; }

@media (max-width: 560px) {
  .footer__legal { flex-direction: column; align-items: flex-start; gap: 12px; }
  .footer__br { display: block; }
  .footer__addr--link { min-height: 44px; display: inline-flex; align-items: center; }
}

@media (max-width: 720px) {
  .footer__set { gap: 2px; }
  .footer__link {
    padding-block: 11px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}

/* --- reveals: arm on the first pixel, not at 8% ---
   On a tall block (the fit/not-a-fit grid on a phone) an 8% threshold means
   the trigger only arms once you are well inside it, and a 0.95s fade after
   that leaves a fast scroller looking at an empty screen. */
@media (hover: none), (pointer: coarse) {
  .reveal,
  .reveal-group > * {
    transition-duration: 0.6s;
  }
  .reveal-group.is-in > *:nth-child(n) { transition-delay: 0s; }
  .reveal-group.is-in > *:nth-child(2) { transition-delay: 0.05s; }
  .reveal-group.is-in > *:nth-child(3) { transition-delay: 0.10s; }
}
