/* ==========================================================================
   Yugantix — Design Portfolio

   Editorial portfolio aesthetic, after ottiliestandring.work: serif display
   and body type, warm paper, generous whitespace, work-first layout. DM Mono
   is reserved for labels and plate numbers; Yugantix blue is the only saturated
   accent. Drafting-table furniture (ruled grid, dimension lines) stays, but
   stepped back so typography carries the page.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  color-scheme: light;

  --font-display: "Fraunces", "Iowan Old Style", "Palatino Linotype", Georgia, serif;
  --font-body: "Newsreader", "Iowan Old Style", "Palatino Linotype", Georgia, serif;
  --font-mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --font: var(--font-body);

  /* Warm neutrals — paper has a slight cream so white feels intentional. */
  --paper: #f9f7f4;
  --paper-deep: #f1eeea;
  --surface: #fffcf8;
  --surface-sunk: #ebe7e1;

  --ink: #141312;
  --ink-soft: #3d3a36;
  --ink-muted: #6b6660;
  --ink-subtle: #9c9690;

  --line: #e0dbd4;
  --line-strong: #cfc9c0;

  --accent: #004bda;
  --accent-deep: #001f5c;
  --accent-soft: #e8eefb;

  --grid-ink: rgba(20, 19, 18, 0.045);
  --shadow-1: 0 1px 2px rgba(20, 19, 18, 0.04);
  --shadow-2: 0 2px 8px rgba(20, 19, 18, 0.04), 0 16px 40px rgba(20, 19, 18, 0.06);
  --shadow-3: 0 8px 24px rgba(20, 19, 18, 0.08), 0 32px 64px rgba(20, 19, 18, 0.1);

  --fs-micro: 0.6875rem;
  --fs-sm: 0.8125rem;
  --fs-base: 1.0625rem;
  --fs-lg: clamp(1.0625rem, 1rem + 0.28vw, 1.1875rem);
  --fs-xl: clamp(1.2rem, 1.05rem + 0.55vw, 1.5rem);
  --fs-2xl: clamp(1.45rem, 1.15rem + 1vw, 2rem);
  --fs-3xl: clamp(1.75rem, 1.3rem + 1.6vw, 2.75rem);
  --fs-4xl: clamp(2rem, 1.4rem + 2.2vw, 3.25rem);
  --fs-hero: clamp(2.35rem, 1.4rem + 4.2vw, 5rem);

  --shell: 1180px;
  --gutter: clamp(1.5rem, 5vw, 4rem);
  --section-y: clamp(4rem, 8vw, 7rem);
  --radius: 2px;
  --radius-lg: 4px;

  --ease: cubic-bezier(0.16, 0.84, 0.26, 1);
  --ease-in: cubic-bezier(0.4, 0, 0.7, 0.15);
}

[data-theme="dark"] {
  color-scheme: dark;

  --paper: #11100f;
  --paper-deep: #161514;
  --surface: #1c1b19;
  --surface-sunk: #242220;

  --ink: #f4f0ea;
  --ink-soft: #d4cec6;
  --ink-muted: #a8a29a;
  --ink-subtle: #7a746c;

  --line: #2a2825;
  --line-strong: #35322e;

  --accent: #5b8cff;
  --accent-deep: #a8c2ff;
  --accent-soft: #16224a;

  --grid-ink: rgba(244, 240, 234, 0.05);
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.3), 0 16px 40px rgba(0, 0, 0, 0.4);
  --shadow-3: 0 8px 24px rgba(0, 0, 0, 0.4), 0 32px 64px rgba(0, 0, 0, 0.5);
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */

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

* { margin: 0; }

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

body {
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: 1.72;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  transition: background 0.3s var(--ease), color 0.3s var(--ease);
}

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

/* width/height attributes are on every <img> to reserve space and avoid layout
   shift. Without height:auto the attribute height becomes the used height, so
   any image sized by CSS width alone (the loader and wipe marks) gets a
   200px-tall box. Anything that genuinely needs a fixed height sets it via a
   class, which outranks this. */
img { height: auto; }

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

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

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.025em;
  text-wrap: balance;
}

p { text-wrap: pretty; }

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

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

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

.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 50%;
  z-index: 500;
  padding: 0.6rem 1.1rem;
  font-size: var(--fs-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
  background: var(--accent);
  border-radius: var(--radius);
  transform: translate(-50%, -200%);
  transition: transform 0.2s var(--ease);
}

.skip-link:focus-visible { transform: translate(-50%, 0); }

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

/* --------------------------------------------------------------------------
   3. Type utilities
   -------------------------------------------------------------------------- */

.mono {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-subtle);
}

.section-label,
.rule-label > .mono,
h2.section-label {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 500;
  font-style: italic;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--ink-muted);
}

h2.section-label {
  line-height: 1.2;
  margin: 0;
}

.measure { max-width: 78ch; }

.section__intro { margin-bottom: 3rem; }

.lede {
  font-size: var(--fs-lg);
  line-height: 1.75;
  color: var(--ink-soft);
  max-width: 58ch;
}

.section { padding-block: var(--section-y); }

.section--sunk {
  background: var(--paper-deep);
  border-block: 1px solid var(--line);
}

/* --------------------------------------------------------------------------
   4. Dimension line — a ruled span with tick marks at each end, the way a
   measurement is called out on a technical drawing. Used as the section
   marker throughout, which is what ties the whole site to the motif.
   -------------------------------------------------------------------------- */

.rule-label {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  margin-bottom: 2.5rem;
}

.dim {
  position: relative;
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--line-strong), transparent 92%);
}

.dim::before,
.dim::after { display: none; }

/* --------------------------------------------------------------------------
   5. Ruled paper backdrop
   -------------------------------------------------------------------------- */

.blueprint {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right, var(--grid-ink) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-ink) 1px, transparent 1px);
  background-size: 120px 120px;
  background-position: center;
  opacity: 0.55;
  -webkit-mask-image: radial-gradient(ellipse 90% 70% at 50% 20%, #000 8%, transparent 72%);
  mask-image: radial-gradient(ellipse 90% 70% at 50% 20%, #000 8%, transparent 72%);
}

@media (max-width: 640px) {
  .blueprint { background-size: 64px 64px; }}

/* --------------------------------------------------------------------------
   5b. Intro loader
   Shown once per session on first arrival. .is-loading is set on <html> by the
   head script before first paint; everything here is inert without it, so the
   loader can never strand a visitor on a blank page.
   -------------------------------------------------------------------------- */

.loader {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: none;
}

.is-loading .loader { display: block; }

/* The sheet lifts away rather than dissolving — same physical language as the
   page wipe, so the intro and the navigation read as one system. */
.loader__sheet {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--paper);
  overflow: hidden;
}

.loader[data-state="out"] .loader__sheet {
  animation: loader-lift 0.85s var(--ease-in) both;
}

.loader[data-state="out"] { pointer-events: none; }

@keyframes loader-lift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(0, -100%, 0); }
}

.loader[data-state="out"] .loader__stage {
  animation: loader-stage-out 0.3s ease-in both;
}

@keyframes loader-stage-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* Keep the page still underneath while the loader is up. */
.is-loading body { overflow: hidden; }

.loader__grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, var(--grid-ink) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-ink) 1px, transparent 1px);
  background-size: 100px 100px;
  background-position: center;
  -webkit-mask-image: radial-gradient(ellipse 80% 70% at 50% 50%, #000 15%, transparent 75%);
  mask-image: radial-gradient(ellipse 80% 70% at 50% 50%, #000 15%, transparent 75%);
}

/* Registration marks, the way a print sheet is trimmed. */
.loader__crop {
  position: absolute;
  width: 22px;
  height: 22px;
  border: 0 solid var(--line-strong);
  animation: loader-rise 0.5s var(--ease) 0.1s both;
}

.loader__crop--tl { top: 28px; left: 28px; border-top-width: 1px; border-left-width: 1px; }
.loader__crop--tr { top: 28px; right: 28px; border-top-width: 1px; border-right-width: 1px; }
.loader__crop--bl { bottom: 28px; left: 28px; border-bottom-width: 1px; border-left-width: 1px; }
.loader__crop--br { bottom: 28px; right: 28px; border-bottom-width: 1px; border-right-width: 1px; }

@media (max-width: 640px) {
  .loader__crop { width: 14px; height: 14px; }
  .loader__crop--tl, .loader__crop--tr { top: 16px; }
  .loader__crop--bl, .loader__crop--br { bottom: 16px; }
  .loader__crop--tl, .loader__crop--bl { left: 16px; }
  .loader__crop--tr, .loader__crop--br { right: 16px; }}

.loader__stage {
  position: relative;
  display: grid;
  justify-items: center;
  gap: clamp(1rem, 2.6vw, 1.5rem);
  width: min(88vw, 460px);
  text-align: center;
}

.loader__mark {
  width: clamp(130px, 32vw, 200px);
  animation: loader-mark 0.7s var(--ease) both;
}

[data-theme="dark"] .loader__mark { filter: invert(1) brightness(1.9); }

/* Dimension line with the practice name set into it, the way a measurement is
   called out on a drawing. */
.loader__dim {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 0.85rem;
  width: 100%;
  animation: loader-rule 0.6s var(--ease) 0.16s both;
}

.loader__dim-rule {
  position: relative;
  height: 1px;
  background: color-mix(in srgb, var(--ink) 26%, transparent);
}

.loader__dim-rule::before,
.loader__dim-rule::after {
  content: "";
  position: absolute;
  top: -4px;
  width: 1px;
  height: 9px;
  background: color-mix(in srgb, var(--ink) 26%, transparent);
}

.loader__dim-rule::before { left: 0; }
.loader__dim-rule::after { right: 0; }
.loader__dim-rule:first-child::before { display: none; }
.loader__dim-rule:last-child::after { display: none; }

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

.loader__text {
  font-size: var(--fs-sm);
  line-height: 1.85;
  color: var(--ink-muted);
  max-width: 44ch;
  animation: loader-rise 0.6s var(--ease) 0.28s both;
}

/* Counter carries the weight — it is the only thing on screen that moves. */
.loader__meter {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  margin-top: 0.5rem;
  animation: loader-rise 0.6s var(--ease) 0.4s both;
}

.loader__count {
  font-size: clamp(2.1rem, 7vw, 3.1rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.loader__stage-name {
  color: var(--ink-subtle);
  text-align: right;
  transition: opacity 0.2s var(--ease);
}

.loader__bar {
  display: block;
  width: 100%;
  height: 2px;
  background: var(--line);
  overflow: hidden;
  animation: loader-rise 0.6s var(--ease) 0.46s both;
}

.loader__fill {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  /* Smooths the jump when `loaded` flips and the target leaps to 100. */
  transition: transform 0.25s var(--ease);
}

@keyframes loader-mark {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}

@keyframes loader-rule {
  from { opacity: 0; transform: scaleX(0.5); }
  to { opacity: 1; transform: none; }
}

@keyframes loader-rise {
  from { opacity: 0; transform: translateY(9px); }
  to { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   6. Navigation
   -------------------------------------------------------------------------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 90;
  background: color-mix(in srgb, var(--paper) 84%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s var(--ease);
}

.nav[data-scrolled="true"] { border-bottom-color: var(--line); }

.nav__inner {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  height: 72px;
}

.nav__brand {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin-right: auto;
}

.nav__logo { height: 20px; width: auto; }
[data-theme="dark"] .nav__logo { filter: invert(1) brightness(1.9); }

.nav__brand-sub {
  padding-left: 0.85rem;
  border-left: 1px solid var(--line-strong);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-style: italic;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--ink-muted);
}

.nav__links {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  list-style: none;
  padding: 0;
}

.nav__link {
  position: relative;
  display: block;
  padding: 0.45rem 0.9rem;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--ink-muted);
  transition: color 0.18s var(--ease);
}

.nav__link:hover { color: var(--ink); }
.nav__link[aria-current] { color: var(--ink); }

.nav__link[aria-current]::after {
  content: "";
  position: absolute;
  left: 0.85rem;
  right: 0.85rem;
  bottom: 0.05rem;
  height: 1px;
  background: var(--accent);
}

.nav__actions { display: flex; align-items: center; gap: 0.5rem; }

.icon-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.16s var(--ease), transform 0.16s var(--ease),
    border-color 0.16s var(--ease);
}

.icon-btn:hover { background: var(--surface-sunk); border-color: var(--line-strong); }
.icon-btn:active { transform: scale(0.92); }
.icon-btn svg { width: 15px; height: 15px; }

.nav__toggle { display: none; }

@media (max-width: 860px) {
  .nav__toggle { display: inline-flex; }

  .nav__links {
    position: fixed;
    inset: 72px 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.5rem var(--gutter) 1.5rem;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow-2);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s var(--ease), transform 0.2s var(--ease), visibility 0.2s;
  }

  .nav__links[data-open="true"] {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }

  .nav__link {
    padding: 0.95rem 0.25rem;
    font-size: var(--fs-sm);
    border-bottom: 1px solid var(--line);
  }

  .nav__link[aria-current]::after { display: none; }
  .nav__brand-sub { display: none; }}

/* --------------------------------------------------------------------------
   8. Hero
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  padding-block: clamp(4.5rem, 10vw, 8rem) clamp(2.5rem, 5vw, 4rem);
  overflow: hidden;
}

.hero__inner { position: relative; }

.hero__kicker {
  display: block;
  margin-bottom: 1.5rem;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-subtle);
}

.hero__kicker::before { display: none; }

.hero__title {
  font-size: var(--fs-hero);
  font-weight: 500;
  letter-spacing: -0.035em;
  line-height: 1.06;
  max-width: 14ch;
}

.hero__title em {
  font-style: italic;
  color: var(--ink);
}

.hero__body {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.75fr);
  gap: clamp(2.5rem, 6vw, 6rem);
  align-items: start;
  margin-top: clamp(2rem, 4vw, 3rem);
}

.hero__meta { display: grid; gap: 0; padding-top: 0.5rem; }

.hero__meta-row {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  gap: 1rem;
  padding-block: 0.9rem;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm);
}

.hero__meta-row dt {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-subtle);
}

.hero__meta-row:first-child { border-top: 1px solid var(--line); }
.hero__meta-row dd { color: var(--ink-muted); }

@media (max-width: 860px) {
  .hero__body { grid-template-columns: 1fr; align-items: start; }}

.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--line-strong);
  border-bottom: 1px solid var(--line-strong);
}

.stats__cell {
  padding: 2rem 1.25rem 2rem 0;
  border-right: 1px solid var(--line);
}

.stats__cell:last-child { border-right: 0; }

.stats__num {
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  font-weight: 500;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.stats__num sup {
  font-size: 0.4em;
  vertical-align: super;
  color: var(--accent);
  margin-left: 0.15em;
}

.stats__label {
  margin-top: 0.5rem;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-muted);
}

@media (max-width: 720px) {
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stats__cell:nth-child(2n) { border-right: 0; }
  .stats__cell:nth-child(-n + 2) { border-bottom: 1px solid var(--line); }}

/* --------------------------------------------------------------------------
   8b. Hero actions, marquee, project cards, CTA
   -------------------------------------------------------------------------- */

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: clamp(2rem, 3.5vw, 2.75rem);
}

/* Full-bleed strip of real archive thumbnails. Two rows travelling in
   opposite directions so the eye has something to catch on. The track is
   duplicated in the markup; translating exactly -50% therefore lands on an
   identical frame and the loop is invisible. */
/* The showcase owns the band's background and borders; the marquee is just
   the strip inside it. */
.showcase {
  background: var(--paper-deep);
  border-block: 1px solid var(--line);
  padding-bottom: clamp(3rem, 6vw, 5rem);
}

.showcase__foot { padding-top: clamp(3rem, 6vw, 5rem); }
.showcase__foot .rule-label { margin-bottom: 2rem; }

.marquee {
  display: grid;
  gap: clamp(0.5rem, 1.2vw, 0.85rem);
  padding-block: clamp(0.85rem, 2vw, 1.5rem);
  overflow: hidden;
}

.marquee__row { overflow: hidden; }

.marquee__track {
  display: flex;
  gap: clamp(0.5rem, 1.2vw, 0.85rem);
  width: max-content;
  animation: marquee 68s linear infinite;
}

.marquee__row[data-direction="right"] .marquee__track {
  animation-direction: reverse;
}

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

@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-50%, 0, 0); }
}

.marquee__cell {
  flex: none;
  width: clamp(130px, 16vw, 210px);
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border: none;
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.marquee__cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(0.35) contrast(1.02);
  opacity: 0.88;
  transition: filter 0.5s var(--ease), opacity 0.5s var(--ease), transform 0.6s var(--ease);
}

.marquee__cell:hover img {
  filter: none;
  opacity: 1;
  transform: scale(1.03);
}

/* Project cards — the homepage shows work, not just a list of names. */
.projects {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
}

.project.is-wide { grid-column: span 4; }
.project.is-narrow { grid-column: span 2; }
.project.is-half { grid-column: span 3; }

.project__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.7s var(--ease);
}

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

.project {
  grid-column: span 3;
  display: grid;
  grid-template-rows: auto 1fr;
  align-content: start;
  gap: 1.1rem;
  padding: 0;
  border: none;
  background: transparent;
  transition: transform 0.45s var(--ease);
}

.project:hover {
  border-color: transparent;
  transform: translateY(-4px);
  box-shadow: none;
}

.project__media {
  display: block;
  height: clamp(220px, 28vw, 360px);
  overflow: hidden;
  background: var(--surface-sunk);
  box-shadow: var(--shadow-2);
}

.project__body { display: grid; gap: 0.4rem; padding-inline: 0.15rem; }

.project__num {
  font-family: var(--font-mono);
  color: var(--ink-subtle);
}

.project__title {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.project__sector {
  font-size: var(--fs-sm);
  font-style: italic;
  color: var(--ink-muted);
}

.project__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.35rem;
  padding-top: 0;
  border-top: none;
}

.project__foot .mono {
  font-size: 0.625rem;
  color: var(--ink-subtle);
}

.project__arrow {
  width: 15px;
  height: 15px;
  opacity: 0.3;
  transition: transform 0.32s var(--ease), opacity 0.32s var(--ease);
}

.project:hover .project__arrow { transform: translate(3px, -3px); opacity: 1; }

@media (max-width: 900px) {
  .projects { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .project,
  .project.is-wide,
  .project.is-narrow,
  .project.is-half { grid-column: span 1; }}

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

/* Closing call to action */
.cta {
  position: relative;
  overflow: hidden;
  padding-block: clamp(3.5rem, 7vw, 6rem);
  border-top: 1px solid var(--line-strong);
}

.cta__inner { position: relative; }

.cta__kicker {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-subtle);
  margin-bottom: 1.5rem;
}

.cta__title {
  font-family: var(--font-display);
  font-size: var(--fs-4xl);
  letter-spacing: -0.03em;
  max-width: 16ch;
}

.cta__body {
  font-size: var(--fs-lg);
  line-height: 1.8;
  color: var(--ink-muted);
  max-width: 52ch;
  margin-top: 1.25rem;
}

.cta__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem;
  margin-top: 2rem;
}

.cta__mail {
  font-size: var(--fs-sm);
  color: var(--ink-muted);
  box-shadow: inset 0 -1px 0 var(--line-strong);
  transition: color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}

.cta__mail:hover { color: var(--accent); box-shadow: inset 0 -1px 0 var(--accent); }

/* Discipline cards became links, so they need the same affordances */
.card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: auto;
  padding-top: 1.5rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line);
}

.card__foot .mono { color: var(--ink-subtle); }

/* --------------------------------------------------------------------------
   10. Cards
   -------------------------------------------------------------------------- */

.cards {
  position: relative;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr));
  border-top: 1px solid var(--line-strong);
  border-left: 1px solid var(--line);
}

.cards::after {
  content: "";
  position: absolute;
  inset: 0;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  pointer-events: none;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(1.6rem, 3vw, 2.5rem);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  transition: background 0.24s var(--ease);
}

.card:hover { background: var(--paper-deep); }

.card__num {
  display: block;
  margin-bottom: 2rem;
  color: var(--accent);
}

.card__title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  margin-bottom: 0.8rem;
  letter-spacing: -0.02em;
}

.card__body { font-size: var(--fs-sm); color: var(--ink-muted); line-height: 1.8; }

/* Bounds the sticky client rail. Sticky is clamped to its parent box, so the
   rail lets go after the last gallery instead of riding the studio and contact
   bands to the bottom of the page. Layout-neutral — do not delete as "unused". */
.galleries { display: flow-root; }

/* Sticky client rail over the galleries */
.jump {
  position: sticky;
  top: 72px;
  z-index: 80;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  border-block: 1px solid var(--line);
}

.jump__inner {
  display: flex;
  gap: 1.5rem;
  overflow-x: auto;
  scrollbar-width: none;
  padding-block: 0.9rem;
}

.jump__inner::-webkit-scrollbar { display: none; }

.jump__link {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  white-space: nowrap;
  font-size: var(--fs-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
  transition: color 0.18s var(--ease);
}

.jump__link:hover,
.jump__link[aria-current="true"] { color: var(--ink); }

.jump__link[aria-current="true"] { box-shadow: inset 0 -1px 0 var(--accent); }

.jump__n {
  font-size: 0.625rem;
  color: var(--ink-subtle);
  font-variant-numeric: tabular-nums;
}

/* Sections inside one engagement. Same language as the client rail above it,
   but in flow rather than sticky — two sticky bars would stack. */
.typerail {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.25rem;
}

.typerail__link {
  display: inline-flex;
  align-items: baseline;
  gap: 0.45rem;
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--line);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease),
    background 0.18s var(--ease);
}

.typerail__link:hover {
  color: var(--ink);
  border-color: var(--line-strong);
  background: var(--paper-deep);
}

.typerail__n {
  font-size: 0.625rem;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* Featured piece — one item shown large to break a long section up */
.feature {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 13rem;
  gap: clamp(1rem, 2.5vw, 2rem);
  align-items: start;
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
  padding-bottom: clamp(1.25rem, 2.5vw, 2rem);
  border-bottom: 1px solid var(--line);
}

.feature .tile { margin-bottom: 0; cursor: zoom-in; }

.feature__note {
  display: grid;
  gap: 0.6rem;
  align-content: start;
  padding-top: 0.35rem;
}

.feature__label {
  font-size: var(--fs-sm);
  color: var(--ink-muted);
  line-height: 1.7;
}

@media (max-width: 720px) {
  .feature { grid-template-columns: 1fr; }
  .feature__note { padding-top: 0; }}

@media (max-width: 860px) {
  .jump { top: 66px; }}

/* One rhythm for the whole archive.

   Column count used to be derived per set — auto-fill against a per-ratio
   minimum for uniform sets, a capped count for mixed ones — so a page of
   sections stepped between four, five and three columns depending on what
   happened to be in each. On a sheet meant to read as one archive, that is
   the thing the eye notices. Every set now runs the same ladder and only the
   shape of the frames inside it changes. */
.tile-grid,
.masonry,
.carousel {
  --cols: 4;
  --grid-gap: clamp(0.75rem, 1.6vw, 1.25rem);
}

@media (max-width: 1100px) {
  .tile-grid, .masonry, .carousel { --cols: 3; }}

@media (max-width: 760px) {
  .tile-grid, .masonry, .carousel { --cols: 2; }}

@media (max-width: 520px) {
  .tile-grid, .masonry { --cols: 1; }}

/* Mixed-ratio sets: masonry, because one shared row height would crop them. */
.masonry {
  columns: var(--cols);
  column-gap: var(--grid-gap);
}

/* Uniform-ratio sets: a real grid, so items flow left-to-right and fill every
   track. --ar is the set's own aspect, so nothing is cropped. */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--grid-gap);
  align-items: start;
}

/* Short sets. One, two or three plates in a four-column row leave the rest of
   it empty, which reads as a mistake rather than as a short set — so they take
   more of the row instead. Only at the four-column step: below it the ladder
   has already narrowed and the plates are wide enough. */
@media (min-width: 1101px) {
  .tile-grid[data-set="1"] > .tile,
  .tile-grid[data-set="2"] > .tile { grid-column: span 2; }

  .tile-grid[data-set="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  .masonry[data-set="1"],
  .masonry[data-set="2"] { columns: 2; }

  .masonry[data-set="3"] { columns: 3; }}

.tile-grid .tile { margin-bottom: 0; }

.tile-grid .tile__frame { aspect-ratio: var(--ar, 1); }

.tile-grid .tile__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tile {
  position: relative;
  display: block;
  width: 100%;
  break-inside: avoid;
  margin-bottom: clamp(0.75rem, 1.6vw, 1.25rem);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--surface);
  cursor: zoom-in;
  text-align: left;
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease),
    border-color 0.3s var(--ease);
}

/* The artwork is clipped, the caption plate under it is not.

   `width: 100%` is load-bearing, not tidiness. With `aspect-ratio` set and the
   width left auto, a `max-height` makes the browser shrink the *width* to keep
   the ratio — the frame pulls in from its own tile and leaves a gap down the
   right with the caption plate still full width. A definite width means
   max-height can only ever clip the height, which is the whole intent. */
.tile__frame {
  position: relative;
  display: block;
  width: 100%;
  overflow: hidden;
}

/* A 1:6 infographic left at full height buries the twelve pieces after it.
   Tall plates are cropped to the top — the part that carries the headline —
   and fade out at the cut so it reads as "continues", not "ends". The real
   dimensions stay in the gauge, and the viewer still opens the whole piece.

   The threshold sits at 2.2:1, above every standard social format and below
   every long-form infographic. A 9:16 reel is a whole format, not a scroll
   with more underneath it, and cropping one says the opposite. */
.tile--tall .tile__frame,
.feature .tile__frame {
  -webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent 99%);
  mask-image: linear-gradient(to bottom, #000 72%, transparent 99%);
}

.tile--tall .tile__frame { max-height: 380px; }

/* The featured plate is the section's opening image; past two thirds of the
   viewport it stops being an opening and becomes the section. */
.feature .tile__frame { max-height: min(62vh, 560px); }

.tile--tall .tile__frame img,
.feature .tile__frame img { object-position: top center; }

.tile:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-2);
  border-color: var(--line-strong);
}

.tile img { width: 100%; height: auto; background: var(--surface-sunk); }

/* Caption plate. A contact sheet captions under the frame, not on hover: a
   tile that only names itself to a mouse names itself to nobody on a phone. */
.tile__meta {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-top: 1px solid var(--line);
  background: var(--paper-deep);
  transition: background 0.3s var(--ease);
}

.tile:hover .tile__meta { background: var(--surface-sunk); }

.tile__num {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  flex: none;
}

.tile__label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-muted);
}

/* Every plate knows its own pixel size, and a design portfolio is one of the
   few places where that is worth printing. It takes the meta bar's third cell
   because the kind is already said by the play icon and the deck badge —
   repeating "Still" on 130 plates said nothing. */
.tile__gauge {
  flex: none;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Below this the three cells cannot share a line without truncating to
   nothing, so the size drops out and the plate number carries the identity. */
@media (max-width: 560px) {
  .tile__gauge { display: none; }}

.tile[hidden] { display: none; }

.tile__play {
  position: absolute;
  top: 0.7rem;
  right: 0.7rem;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: rgba(17, 18, 18, 0.66);
  backdrop-filter: blur(6px);
  color: #fff;
}

.tile__play svg { width: 11px; height: 11px; }

.tile__badge {
  position: absolute;
  top: 0.7rem;
  left: 0.7rem;
  padding: 0.2rem 0.5rem;
  background: rgba(17, 18, 18, 0.66);
  backdrop-filter: blur(6px);
  color: #fff;
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.spec {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  border-top: 1px solid var(--line-strong);
  border-left: 1px solid var(--line);
}

.spec__cell {
  padding: 1.2rem 1.25rem;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.spec__value { margin-top: 0.5rem; font-size: var(--fs-base); }

.case-section {
  padding-block: clamp(2rem, 3.5vw, 3.25rem);
  scroll-margin-top: 130px;
}

.case-section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem;
  padding-bottom: 1.1rem;
  margin-bottom: 2.25rem;
  border-bottom: 1px solid var(--line);
}

.case-section__title {
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  letter-spacing: -0.03em;
}

.carousel {
  display: grid;
  grid-auto-flow: column;
  /* A slide is a grid cell, so a set scrolling past four slides lines up with
     the four-column sets above and below it. 1.5rem covers the dashed frame's
     own padding, which is inside the 100%. */
  grid-auto-columns: calc(
    (100% - (var(--cols) - 1) * var(--grid-gap) - 1.5rem) / var(--cols)
  );
  gap: var(--grid-gap);
  margin-bottom: clamp(0.75rem, 1.6vw, 1.25rem);
  padding: 0.75rem;
  border: 1px dashed var(--line-strong);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}

.carousel > .tile { margin-bottom: 0; scroll-snap-align: start; }

.carousel-label {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin-bottom: 0.7rem;
}

/* --------------------------------------------------------------------------
   13. Lightbox
   -------------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background: color-mix(in srgb, var(--paper) 97%, transparent);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.28s var(--ease), visibility 0.28s;
}

.lightbox[data-open="true"] { opacity: 1; visibility: visible; }

.lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem var(--gutter);
  border-bottom: 1px solid var(--line);
}

.lightbox__stage {
  display: grid;
  place-items: center;
  padding: clamp(1rem, 3vw, 2rem) var(--gutter);
  overflow: auto;
  min-height: 0;
}

.lightbox__stage img,
.lightbox__stage video {
  max-width: min(100%, 1100px);
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  box-shadow: var(--shadow-3);
  transform: scale(0.97);
  opacity: 0;
  transition: transform 0.36s var(--ease), opacity 0.36s var(--ease);
}

.lightbox[data-open="true"] .lightbox__stage img,
.lightbox[data-open="true"] .lightbox__stage video {
  transform: scale(1);
  opacity: 1;
}

.lightbox__foot {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 1rem var(--gutter);
  border-top: 1px solid var(--line);
}

.lightbox__hint {
  position: absolute;
  right: var(--gutter);
  color: var(--ink-subtle);
}

@media (max-width: 720px) {
  .lightbox__hint { display: none; }}

.lightbox__counter {
  font-size: var(--fs-micro);
  letter-spacing: 0.14em;
  color: var(--ink-muted);
  min-width: 6rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   14. Contact
   -------------------------------------------------------------------------- */

.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
}

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

.contact-list { display: grid; margin-top: 2rem; border-top: 1px solid var(--line-strong); }

.contact-item {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  gap: 1rem;
  align-items: baseline;
  padding-block: 1.15rem;
  border-bottom: 1px solid var(--line);
}

.contact-item a { font-size: var(--fs-lg); transition: color 0.18s var(--ease); }
.contact-item a:hover { color: var(--accent); }

.form { display: grid; gap: 1.35rem; }

.field { display: grid; gap: 0.5rem; }

.field label {
  font-size: var(--fs-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.field input,
.field textarea,
.field select {
  padding: 0.8rem 0.9rem;
  font-size: var(--fs-base);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 0;
  transition: border-color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}

.field input:focus,
.field textarea:focus,
.field select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.field textarea { resize: vertical; min-height: 8rem; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.9rem 1.65rem;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  letter-spacing: 0.02em;
  text-transform: none;
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s var(--ease), transform 0.16s var(--ease),
    box-shadow 0.2s var(--ease);
}

.btn:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--accent) 26%, transparent);
}

[data-theme="dark"] .btn,
[data-theme="dark"] .btn:hover { color: #0f1116; }

.btn:active { transform: translateY(1px); }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--line-strong);
  border-radius: 999px;
}

.btn--ghost:hover {
  background: var(--surface-sunk);
  color: var(--ink);
  border-color: var(--ink);
  box-shadow: none;
}

[data-theme="dark"] .btn--ghost,
[data-theme="dark"] .btn--ghost:hover { color: var(--ink); }

.btn svg { width: 14px; height: 14px; }

.form__note { font-size: var(--fs-sm); color: var(--ink-muted); }

/* --------------------------------------------------------------------------
   16. Motion
   -------------------------------------------------------------------------- */

@keyframes band-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

/* Hover affordance on the whole tile, not just the image */
.tile__frame img {
  transition: transform 0.5s var(--ease), opacity 0.3s var(--ease);
}

.tile:hover .tile__frame img { transform: scale(1.025); }

/* --------------------------------------------------------------------------
   17. Scroll reveal
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   18. Hero WebGL sheet
   A drafting sheet rendered in three.js: the same ruled grid the CSS
   backdrop paints, given depth and a slow swell. It is decoration and is
   treated as such — the CSS .blueprint stays underneath and simply dims when
   the canvas reports itself ready, so a blocked CDN, an old GPU or a
   reduced-motion visitor loses nothing but a gradient of polish.
   -------------------------------------------------------------------------- */

.hero__gl {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.4s var(--ease);
}

.hero__gl canvas { display: block; width: 100%; height: 100%; }

/* Weighted to the lower right, away from the headline. The sheet is a
   backdrop and should never compete with the first line of type. */
.hero__gl {
  -webkit-mask-image: radial-gradient(ellipse 96% 88% at 62% 78%, #000 10%, transparent 76%);
  mask-image: radial-gradient(ellipse 96% 88% at 62% 78%, #000 10%, transparent 76%);
}

.hero[data-gl="ready"] .hero__gl { opacity: 1; }

/* Both layers are the same grid, so showing them at full strength together
   reads as a printing error. The flat one steps back instead. */
.blueprint { transition: opacity 1.4s var(--ease); }
.hero[data-gl="ready"] .blueprint { opacity: 0.18; }

.hero__inner { z-index: 1; }

/* --------------------------------------------------------------------------
   19. Reading furniture
   One page means the usual signals — a page count, a footer to land on — are
   gone. These three put them back: how far through you are, what a tile will
   do, and a way back to the top.
   -------------------------------------------------------------------------- */

/* Scroll progress: a rule that fills as the page is read, sitting on the
   nav's own bottom border rather than adding a second line. */
.progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  overflow: hidden;
  pointer-events: none;
}

.progress__fill {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(var(--progress, 0));
  transform-origin: left center;
}

/* Cursor badge. Hover-capable pointers only — on touch it would be a ghost
   chasing taps, and the tiles already open on tap. */
.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 900;
  display: none;
  place-items: center;
  width: 66px;
  height: 66px;
  margin: -33px 0 0 -33px;
  border: 1px solid var(--accent);
  border-radius: 50%;
  background: color-mix(in srgb, var(--paper) 62%, transparent);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  color: var(--accent);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  pointer-events: none;
  opacity: 0;
  scale: 0.55;
  transition: opacity 0.28s var(--ease), scale 0.28s var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .cursor { display: grid; }}

.cursor[data-visible="true"] { opacity: 1; scale: 1; }

/* .mono would otherwise paint the label grey over the accent ring. */
.cursor .mono { color: inherit; }

/* Back to top. With no footer this is the only way out of a 172-piece page
   short of flicking, so it earns its fixed position. */
.totop {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 80;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--line-strong);
  background: color-mix(in srgb, var(--paper) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  color: var(--ink-muted);
  font-size: var(--fs-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0;
  translate: 0 14px;
  pointer-events: none;
  transition: opacity 0.35s var(--ease), translate 0.35s var(--ease),
    color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.totop[data-visible="true"] { opacity: 1; translate: 0; pointer-events: auto; }
.totop:hover { color: var(--ink); border-color: var(--accent); }
.totop svg { width: 12px; height: 12px; }

/* The page has to end on something. One rule and one line — a plate stamp,
   not a footer: no navigation, no link columns, nothing to get lost in. */
.signoff {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin-top: clamp(3rem, 6vw, 5rem);
  padding-top: 1.25rem;
  border-top: 1px solid var(--line-strong);
}

.signoff .dim { flex: 1; }

@media (max-width: 720px) {
  .signoff { flex-direction: column; align-items: flex-start; gap: 0.75rem; }
  .signoff .dim { display: none; }}

/* Two columns of prose where there is room, one where there is not. The
   column minimum is the readable measure, not a fraction of the shell. */
.prose-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
  gap: clamp(1.5rem, 3vw, 3.5rem) clamp(2rem, 5vw, 5rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}

.prose-block .case-section__title { margin-bottom: 0.85rem; }
.prose-block .card__title { margin-bottom: 0.6rem; }

/* --------------------------------------------------------------------------
   20. Registration marks on hover
   The corner ticks a printer would crop to. They say "this is a plate you can
   open" without adding a label to 172 tiles.
   -------------------------------------------------------------------------- */

.tile__frame::before,
.tile__frame::after {
  content: "";
  position: absolute;
  z-index: 2;
  width: 13px;
  height: 13px;
  border: 1px solid var(--accent);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s var(--ease), inset 0.3s var(--ease);
}

.tile__frame::before { top: 6px; left: 6px; border-right: 0; border-bottom: 0; }
.tile__frame::after { bottom: 6px; right: 6px; border-left: 0; border-top: 0; }

.tile:hover .tile__frame::before,
.tile:hover .tile__frame::after,
.tile:focus-visible .tile__frame::before,
.tile:focus-visible .tile__frame::after { opacity: 0.9; }

/* Magnetic buttons lean a few pixels toward the pointer. `translate` rather
   than `transform`, because .btn:active already owns transform. */
.btn { transition: translate 0.25s var(--ease), background 0.2s var(--ease),
  color 0.2s var(--ease), border-color 0.2s var(--ease); }

/* --------------------------------------------------------------------------
   21. Hero entrance
   Gated on :not(.is-loading) so it starts when the intro sheet lifts rather
   than playing out of sight behind it. On a repeat visit the loader never
   runs, the selector matches at parse time, and it plays on load.
   -------------------------------------------------------------------------- */

@keyframes hero-rise {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: none; }
}

.js-reveal:not(.is-loading) .hero__kicker { animation: hero-rise 0.8s var(--ease) both; }
.js-reveal:not(.is-loading) .hero__title { animation: hero-rise 0.9s var(--ease) 0.09s both; }
.js-reveal:not(.is-loading) .hero__body { animation: hero-rise 0.9s var(--ease) 0.18s both; }
.js-reveal:not(.is-loading) .hero__actions { animation: hero-rise 0.9s var(--ease) 0.27s both; }

/* --------------------------------------------------------------------------
   23. Contact-sheet counter
   Bottom left, opposite the back-to-top control: which engagement you are in,
   and which plate of 172 you have reached. It is the page number a single
   scrolling document does not otherwise have.
   -------------------------------------------------------------------------- */

.plate {
  position: fixed;
  left: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 80;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  width: min(24rem, calc(100vw - 9rem));
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--line-strong);
  background: color-mix(in srgb, var(--paper) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  opacity: 0;
  translate: 0 14px;
  pointer-events: none;
  transition: opacity 0.35s var(--ease), translate 0.35s var(--ease);
}

.plate[data-visible="true"] { opacity: 1; translate: 0; }

.plate .dim { flex: 1; }

.plate__count {
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.plate__count b { font-weight: 400; color: var(--accent); }

@media (max-width: 560px) {
  .plate { display: none; }}

/* --------------------------------------------------------------------------
   24. Marquee inertia
   The strip already loops on its own; scrolling leans on it. --vel is set in
   JS from scroll velocity and decays back to zero, so the rows skew into the
   direction of travel and settle — the archive reacts to being read.
   -------------------------------------------------------------------------- */

.marquee__row {
  transform: skewX(calc(var(--vel, 0) * -1.4deg));
  translate: calc(var(--vel, 0) * -22px) 0;
  transition: transform 0.5s var(--ease), translate 0.5s var(--ease);
}

/* --------------------------------------------------------------------------
   25. Theme wipe
   The new theme is pulled down over the old one like a fresh sheet. Uses the
   View Transition API where it exists; everywhere else the swap is instant,
   which is what it always was.
   -------------------------------------------------------------------------- */

::view-transition-old(root) { animation: none; }

::view-transition-new(root) {
  animation: theme-sheet 0.6s var(--ease) both;
}

@keyframes theme-sheet {
  from { clip-path: inset(0 0 100% 0); }
  to { clip-path: inset(0 0 0 0); }
}

/* Word-level hero entrance. The whole-title animation in section 21 stays as
   the fallback; .is-split is only set once JS has actually done the split, so
   a failure leaves the simpler version running rather than nothing. */
.hero__title .word { display: inline-block; }

.js-reveal:not(.is-loading) .hero__title.is-split { animation: none; }

.js-reveal:not(.is-loading) .hero__title.is-split .word {
  animation: hero-rise 0.85s var(--ease) both;
  animation-delay: calc(var(--d, 0) * 42ms);
}

/* Scoped to .js-reveal, which the inline head script adds before first
   paint. Without it the content is simply visible — hiding it in the
   stylesheet would mean a JS failure silently eats whole sections. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

.js-reveal [data-reveal][data-revealed="true"] { opacity: 1; transform: none; }

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

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .js-reveal [data-reveal] { opacity: 1; transform: none; }
  .marquee__track { animation: none; }
  .marquee__row { transform: none; translate: none; }
  .cursor { display: none; }}