/* =====================================================================
   Justis Peppin, portfolio
   Monochrome. Sections are told apart by tone, never by a rule.
   Glass surfaces float over a live WebGL field, so the background
   reads through everything at low strength and breathes at the seams.

   Motifs: the ink diamond, oversized index numerals, mask wipe reveals.
   ===================================================================== */

/* ------------------------------ tokens ------------------------------ */
:root {
  /* the tonal ladder. Alpha lets the field read through. */
  --canvas:  #fafaf8;
  --tone-0:  rgba(250, 250, 248, 0.64);   /* hero, lightest */
  --tone-1:  rgba(226, 226, 220, 0.86);   /* work, first grey step */
  --tone-2:  rgba(248, 248, 245, 0.70);   /* about, back to light */
  --tone-3:  rgba(190, 190, 183, 0.92);   /* contact, deepest grey, before the dark footer */
  --tone-dark: rgba(13, 13, 12, 0.90);    /* process, footer */

  /* ink */
  --ink:      #101010;
  --ink-soft: #35352f;
  --muted:    #6a6a62;
  --faint:    #9b9b92;
  --hair:     rgba(16, 16, 16, 0.09);

  /* monochrome: the accent role is played by ink, and flips on dark blocks */
  --accent:      #101010;
  --accent-deep: #000000;
  --accent-wash: rgba(16, 16, 16, 0.07);

  /* on dark */
  --on-dark:      #f4f4f0;
  --on-dark-mute: #8e8e86;
  --dark-hair:    rgba(244, 244, 240, 0.14);

  /* glass */
  --glass:        rgba(255, 255, 255, 0.56);
  --glass-strong: rgba(255, 255, 255, 0.74);
  --glass-edge:   rgba(255, 255, 255, 0.85);
  --glass-hair:   rgba(16, 16, 16, 0.07);
  --glass-dark:       rgba(28, 28, 26, 0.46);
  --glass-dark-edge:  rgba(255, 255, 255, 0.10);
  --blur: blur(18px) saturate(150%);
  --blur-lite: blur(10px) saturate(130%);

  --lift-1: 0 1px 2px rgba(16, 16, 16, 0.04), 0 10px 30px rgba(16, 16, 16, 0.07);
  --lift-2: 0 2px 6px rgba(16, 16, 16, 0.05), 0 26px 60px rgba(16, 16, 16, 0.13);
  --inner-hi: inset 0 1px 0 rgba(255, 255, 255, 0.9);

  /* fonts */
  --font-display: "Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --fs-mega:    clamp(2.9rem, 0.9rem + 10.5vw, 8.75rem);
  --fs-display: clamp(2.4rem, 1rem + 6vw, 5.5rem);
  --fs-h2:      clamp(1.9rem, 1.1rem + 3.6vw, 3.75rem);
  --fs-h3:      clamp(1.2rem, 1.05rem + 0.9vw, 1.75rem);
  --fs-lead:    clamp(1.05rem, 0.95rem + 0.55vw, 1.4rem);
  --fs-body:    clamp(1rem, 0.97rem + 0.16vw, 1.0625rem);
  --fs-meta:    0.6875rem;

  --lh-mega: 0.86;
  --lh-snug: 1.1;
  --lh-body: 1.65;
  --ls-mega: -0.045em;
  --ls-meta: 0.18em;

  --sp-1: 0.25rem; --sp-2: 0.5rem;  --sp-3: 0.75rem; --sp-4: 1rem;
  --sp-5: 1.5rem;  --sp-6: 2rem;    --sp-7: 3rem;    --sp-8: 4rem;
  --sp-9: 6rem;    --sp-10: 8rem;
  --section-y: clamp(3.5rem, 2.25rem + 5vw, 6.5rem);

  --r-sm: 3px; --r-md: 6px; --r-lg: 10px; --r-xl: 14px; --r-pill: 999px;

  --shell: 1280px;
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --ease: cubic-bezier(0.22, 0.68, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  /* a calmer settle for whole sections and cards arriving: barely overshoots
     rather than bouncing. --spring stays reserved for small, brief pops
     (the diamond, the index numeral, hover arrows) where a bounce reads as
     delight rather than wobble. */
  --settle: cubic-bezier(0.22, 1.04, 0.36, 1);
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 6rem;
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
body.is-locked { overflow: hidden; }

h1, h2, h3, h4, p, figure, blockquote, ol, ul, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg, canvas { display: block; max-width: 100%; }
img { height: auto; }
button { font: inherit; color: inherit; }
a { color: inherit; }

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


.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-4);
  z-index: 500;
  padding: var(--sp-3) var(--sp-5);
  background: var(--ink);
  color: var(--on-dark);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.875rem;
  border-radius: var(--r-md);
  transform: translateY(-250%);
  transition: transform 0.2s var(--ease);
}
.skip-link:focus { transform: translateY(0); }

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

/* =====================================================================
   The live field, fixed behind everything.
   ===================================================================== */
#field {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.6s var(--ease);
}
#field.is-live { opacity: 1; }
.page { position: relative; z-index: 1; }

/* =====================================================================
   Mask wipe reveal. The inner span slides out from behind the mask.
   ===================================================================== */
.mask {
  display: block;
  overflow: hidden;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
.mask__in {
  display: block;
  transform: translate3d(0, 106%, 0);
  transition: transform 1.05s var(--ease-out);
  transition-delay: calc(var(--reveal-i, 0) * 90ms);
}
.is-visible > .mask > .mask__in,
.is-visible.mask > .mask__in { transform: translate3d(0, 0, 0); }
.no-js .mask__in { transform: none; }

/* ------------------------------ shared type ------------------------------ */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--muted);
}
.diamond {
  width: 7px; height: 7px;
  flex: none;
  background: var(--accent);
  transform: rotate(45deg);
}

/* Availability indicator. The one place colour is allowed, because a status
   light reads as a status light and nothing else does that job. */
.status-dot {
  position: relative;
  width: 11px; height: 11px;
  flex: none;
  border-radius: 50%;
  background: #14c25e;
  box-shadow: 0 0 0 2px rgba(20, 194, 94, 0.28), 0 0 14px 3px rgba(20, 194, 94, 0.75);
}
/* a ping that expands and fades, the way a live indicator reads */
.status-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: #14c25e;
  animation: ping 2.2s var(--ease-out) infinite;
}
@keyframes ping {
  0%   { transform: scale(1);   opacity: 0.65; }
  70%  { transform: scale(2.8); opacity: 0; }
  100% { transform: scale(2.8); opacity: 0; }
}
.hero__avail { color: var(--ink); font-weight: 700; }

.h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-h2);
  line-height: var(--lh-snug);
  letter-spacing: -0.03em;
  text-wrap: balance;
}
.h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-h3);
  line-height: 1.18;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.lead { font-size: var(--fs-lead); line-height: 1.5; color: var(--ink-soft); max-width: 34ch; text-wrap: pretty; }
.prose p { color: var(--ink-soft); text-wrap: pretty; }
.prose p + p { margin-top: var(--sp-5); }
.prose strong { color: var(--ink); font-weight: 600; }

.text-link {
  text-decoration: none;
  background-image: linear-gradient(var(--ink), var(--ink));
  background-repeat: no-repeat;
  background-size: 100% 1px;
  background-position: 0 100%;
  transition: background-size 0.3s var(--ease-out);
}
.text-link:hover, .text-link:focus-visible { background-size: 100% 2px; }

/* in monochrome the highlight is a wash plus a solid underline */
.mark {
  background-image: linear-gradient(var(--accent-wash), var(--accent-wash));
  background-repeat: no-repeat;
  background-size: 100% 0.44em;
  background-position: 0 76%;
  box-shadow: inset 0 -3px 0 var(--accent);
}

/* one word that breathes, to close the page on some warmth */
.glow-word {
  position: relative;
  display: inline-block;
  color: #0e9c4b;
  animation: breathe 3.6s var(--ease) infinite;
}
.glow-word::after {
  content: "";
  position: absolute;
  left: -0.1em; right: -0.1em; bottom: 0.05em;
  height: 0.14em;
  border-radius: var(--r-pill);
  background: #14c25e;
  opacity: 0.5;
}
@keyframes breathe {
  0%, 100% { text-shadow: 0 0 14px rgba(20, 194, 94, 0.26), 0 0 34px rgba(20, 194, 94, 0.10); }
  50%      { text-shadow: 0 0 22px rgba(20, 194, 94, 0.52), 0 0 58px rgba(20, 194, 94, 0.26); }
}

/* =====================================================================
   Sections. Tone carries the separation, and each one fades into its
   neighbour so there is never a visible seam. The field shows through
   strongest in those fade bands.
   ===================================================================== */
.section { padding-block: var(--section-y); position: relative; }
.section:focus { outline: none; }

.tone-0, .tone-1, .tone-2, .tone-3 { position: relative; }
.tone-0::before, .tone-1::before, .tone-2::before, .tone-3::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.tone-0::before {
  background: linear-gradient(to bottom, var(--tone-0) 0%, var(--tone-0) 86%, transparent 100%);
}
.tone-1::before {
  background: linear-gradient(to bottom, transparent 0%, var(--tone-1) 9%, var(--tone-1) 91%, transparent 100%);
}
.tone-2::before {
  background: linear-gradient(to bottom, transparent 0%, var(--tone-2) 9%, var(--tone-2) 91%, transparent 100%);
}
.tone-3::before {
  background: linear-gradient(to bottom, transparent 0%, var(--tone-3) 10%, var(--tone-3) 100%);
}

/* section header. No rule, the index numeral does the work. */
.section__head { display: grid; gap: var(--sp-5); margin-bottom: var(--sp-7); }
.section__index {
  font-family: var(--font-display);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: var(--ls-meta);
  color: var(--accent);
  opacity: 0.42;
}
.section__title { max-width: 20ch; }
.section__aside { align-self: end; }

/* ------------------------------ buttons ------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0.95rem 1.6rem;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  /* magnetic offset composes here, app.js only writes the two vars */
  transform: translate3d(var(--mag-x, 0px), var(--mag-y, 0px), 0);
  transition: transform 0.5s var(--ease-out), background-color 0.3s var(--ease),
              color 0.3s var(--ease), border-color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.btn.is-magnetic { transition: transform 0.12s linear, background-color 0.3s var(--ease),
              color 0.3s var(--ease), border-color 0.3s var(--ease), box-shadow 0.3s var(--ease); }
.btn svg { width: 16px; height: 16px; flex: none; }

.btn--accent { background: var(--ink); color: var(--on-dark); }
.btn--accent:hover, .btn--accent:focus-visible {
  background: var(--accent-deep);
  box-shadow: var(--lift-2);
}
.btn--ghost {
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  color: var(--ink);
  border-color: var(--glass-hair);
  box-shadow: var(--inner-hi);
}
.btn--ghost:hover, .btn--ghost:focus-visible { border-color: var(--ink); background: var(--glass-strong); }

/* ------------------------------ header ------------------------------ */
.site-header {
  position: sticky;
  top: var(--sp-4);
  z-index: 100;
  width: min(calc(100% - 2 * var(--sp-4)), calc(var(--shell) - var(--sp-9) * 2));
  margin-inline: auto;
  padding-block: var(--sp-4);
  background: rgba(250, 250, 248, 0.62);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-hair);
  border-radius: var(--r-pill);
  box-shadow: var(--lift-1);
  transition: background-color 0.4s var(--ease), box-shadow 0.4s var(--ease);
}
.site-header.is-scrolled {
  background: rgba(250, 250, 248, 0.82);
  box-shadow: var(--lift-2);
}
.site-header__inner { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-5); }

.wordmark {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
}

.nav { display: flex; align-items: center; gap: var(--sp-6); }
.nav__list { display: flex; align-items: center; gap: var(--sp-6); }
.nav__link {
  position: relative;
  padding: var(--sp-2) 0;
  font-family: var(--font-display);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.25s var(--ease);
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1.5px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease-out);
}
.nav__link:hover, .nav__link:focus-visible, .nav__link.is-active { color: var(--ink); }
.nav__link:hover::after, .nav__link.is-active::after { transform: scaleX(1); }
.nav__cta { padding: 0.65rem 1.25rem; }

.nav-toggle {
  display: none;
  align-items: center;
  gap: var(--sp-3);
  padding: 0.5rem 0.9rem;
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  border: 1px solid var(--glass-hair);
  border-radius: var(--r-pill);
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.nav-toggle__bars { display: grid; gap: 4px; width: 16px; }
.nav-toggle__bars span { height: 1.5px; background: var(--ink); transition: transform 0.35s var(--spring); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:first-child { transform: translateY(2.75px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:last-child { transform: translateY(-2.75px) rotate(-45deg); }

/* ------------------------------ hero ------------------------------ */
.hero {
  position: relative;
  padding-top: clamp(1.25rem, 0.75rem + 2vw, 2.5rem);
  padding-bottom: clamp(3rem, 1.75rem + 6vw, 7rem);
}
/* the first thing anyone on the site sees gets a quicker, no-blur entrance
   than the rest of the site, for the same reason a case study hero does:
   this is a first impression, not a considered scroll reveal. --settle
   barely overshoots rather than bouncing, so it reads as quick, not springy. */
.hero [data-reveal] {
  transition-duration: 0.55s;
  transition-timing-function: var(--settle);
}
.hero [data-reveal]:not(.is-visible) { filter: none; }
.hero__top {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  align-items: center;
  padding-bottom: var(--sp-5);
}
.hero__sep {
  width: 22px; height: 1px;
  flex: none;
  background: currentColor;
  opacity: 0.35;
}
.hero__loc { color: var(--muted); }
.hero__avail { color: var(--ink); }

.hero__name {
  font-family: var(--font-display);
  font-size: var(--fs-mega);
  font-weight: 800;
  line-height: var(--lh-mega);
  letter-spacing: var(--ls-mega);
  text-transform: uppercase;
  margin-top: clamp(1.5rem, 1rem + 3vw, 3.5rem);
}
.hero__name .line-2 .mask__in { padding-left: 0.09em; }

.hero__body { display: grid; gap: var(--sp-7); margin-top: clamp(1.25rem, 0.75rem + 2vw, 2.25rem); }
.hero__role {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: var(--sp-4);
}
.hero__value { font-size: var(--fs-lead); line-height: 1.45; color: var(--ink-soft); max-width: 34ch; margin-bottom: var(--sp-6); text-wrap: pretty; }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

.hero__media { position: relative; }
.hero__figure {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  aspect-ratio: 4 / 5;
  box-shadow: var(--lift-2), var(--inner-hi);
  padding: 6px;
}
.hero__figure img {
  width: 100%; height: 100%; object-fit: cover; border-radius: calc(var(--r-xl) - 7px);
  transition: transform 0.55s var(--ease-out);
}
/* the curtain only ever hides a sliver, never the whole frame: a portrait
   is the one thing on this page that must never look like it failed to
   load, even mid-transition or on a slow first paint */
.hero__figure[data-reveal] {
  clip-path: inset(0 0 0 0);
  transition: clip-path 0.55s var(--ease-out);
}
.hero__figure[data-reveal]:not(.is-visible) { clip-path: inset(0 0 14% 0); }
.hero__figure[data-reveal]:not(.is-visible) img { transform: scale(1.05); }

/* stat rail, glass panel instead of ruled boxes */

/* ------------------------------ work ------------------------------ */
/* a touch narrower than the shell it sits in, so the cards read a size
   smaller than a full-bleed grid without the section header above losing
   its own full measure */
.work__grid { display: grid; gap: var(--sp-6); align-items: stretch; max-width: 1120px; margin-inline: auto; }

.project {
  position: relative;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--lift-1), var(--inner-hi);
  transform: translate3d(var(--mag-x, 0px), var(--mag-y, 0px), 0);
  transition: transform 0.6s var(--ease-out), box-shadow 0.5s var(--ease), background-color 0.5s var(--ease);
}
.project.is-magnetic { transition: transform 0.14s linear, box-shadow 0.5s var(--ease), background-color 0.5s var(--ease); }
.project:hover, .project:focus-within { box-shadow: var(--lift-2), var(--inner-hi); background: var(--glass-strong); }

.project__media {
  position: relative; overflow: hidden; background: rgba(16,16,16,0.04); aspect-ratio: 4 / 3; perspective: 1200px;
  clip-path: inset(0 0 0 0);
  transition: clip-path 0.55s var(--ease-out);
}
/* --img-scale-enter is a second, independent multiplier so the entrance zoom
   and the hover zoom never fight over the same custom property: each is
   only ever set by its own trigger, and they compose in the transform. */
.project__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: rotateY(var(--tilt-y, 0deg)) rotateX(var(--tilt-x, 0deg)) scale(calc(var(--img-scale, 1.01) * var(--img-scale-enter, 1)));
  transition: transform 0.5s var(--ease-out);
}
/* keyed off the whole card's reveal state, since the media itself has no
   data-reveal of its own: the card arrives as one unit, the photo inside
   opens and settles at a slightly different rate. The curtain only ever
   hides a sliver, never the whole photo: even mid-transition or on a slow
   first paint this must never look like a broken image. */
.project[data-reveal]:not(.is-visible) .project__media { clip-path: inset(0 0 14% 0); }
.project[data-reveal]:not(.is-visible) .project__media img { --img-scale-enter: 1.05; }
.project:hover .project__media img, .project:focus-within .project__media img { --img-scale: 1.07; }

.project__cue {
  position: absolute;
  right: var(--sp-4);
  bottom: var(--sp-4);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.6rem 1.1rem;
  background: rgba(16, 16, 16, 0.82);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  color: var(--on-dark);
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-radius: var(--r-pill);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--spring);
}
.project__cue svg { width: 13px; height: 13px; }
.project:hover .project__cue, .project:focus-within .project__cue { opacity: 1; transform: translateY(0); }

.award {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-4);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.45rem 0.9rem;
  background: rgba(16, 16, 16, 0.86);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  color: var(--on-dark);
  font-family: var(--font-display);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-radius: var(--r-pill);
}

.project__body { display: flex; flex-direction: column; gap: var(--sp-4); padding: clamp(1.25rem, 1rem + 1.2vw, 2.1rem); flex: 1; }
.project__meta {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}
.project__title a {
  text-decoration: none;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-size: 0% 2px;
  background-position: 0 100%;
  transition: background-size 0.5s var(--ease-out);
}
.project:hover .project__title a, .project__title a:focus-visible { background-size: 100% 2px; }
.project__title a::after { content: ""; position: absolute; inset: 0; z-index: 2; }

.project__problem { color: var(--ink-soft); }
.project__role { padding-top: var(--sp-4); border-top: 1px solid var(--hair); font-size: 0.9375rem; color: var(--ink-soft); }
.project__role b {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.5;
  margin-bottom: var(--sp-2);
}

.tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: auto; padding-top: var(--sp-2); }
.tags li {
  padding: 0.3rem 0.7rem;
  background: rgba(255, 255, 255, 0.5);
  border: 1px solid var(--glass-hair);
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.project--featured .project__media { aspect-ratio: 16 / 9; }
.project--featured .project__problem { font-size: var(--fs-lead); line-height: 1.45; color: var(--ink); }

.pill-links { display: flex; flex-wrap: wrap; gap: var(--sp-2); position: relative; z-index: 3; }
.pill-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.5rem 0.95rem;
  border: 1px solid var(--ink);
  border-radius: var(--r-pill);
  background: var(--surface);
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-decoration: none;
  color: var(--ink);
  box-shadow: 0 1px 0 var(--ink);
  transform: translate3d(var(--mag-x, 0px), var(--mag-y, 0px), 0);
  transition: transform 0.5s var(--ease-out), color 0.25s var(--ease),
              background-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
.pill-link.is-magnetic { transition: transform 0.12s linear, color 0.25s var(--ease),
              background-color 0.25s var(--ease), box-shadow 0.25s var(--ease); }
.pill-link svg { width: 12px; height: 12px; transition: transform 0.3s var(--ease-out); }
.pill-link:hover, .pill-link:focus-visible {
  background: var(--ink);
  color: var(--on-dark);
  box-shadow: 0 3px 0 rgba(16, 16, 16, 0.35);
}
.pill-link:hover svg, .pill-link:focus-visible svg { transform: translate(2px, -2px); }
.pill-link:active { box-shadow: 0 0 0 var(--ink); }
/* ------------------------------ about ------------------------------ */
.about__grid { display: grid; gap: var(--sp-7); }
/* the offwork strip stays full width, only the prose is measured */
.about__copy > p { max-width: 56ch; }

.about__figure {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  aspect-ratio: 4 / 5;
  box-shadow: var(--lift-2), var(--inner-hi);
  padding: 6px;
  max-width: 320px;
}
.about__figure img {
  width: 100%; height: 100%; object-fit: cover; border-radius: calc(var(--r-xl) - 7px);
  transition: transform 0.55s var(--ease-out);
}
.about__figure[data-reveal] {
  clip-path: inset(0 0 0 0);
  transition: clip-path 0.55s var(--ease-out);
}
.about__figure[data-reveal]:not(.is-visible) { clip-path: inset(0 0 14% 0); }
.about__figure[data-reveal]:not(.is-visible) img { transform: scale(1.05); }

.offwork { display: grid; gap: var(--sp-4); margin-top: var(--sp-7); }
.offwork__item {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-3);
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  padding: var(--sp-5);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-md);
  box-shadow: var(--inner-hi);
  transform: translate3d(var(--mag-x, 0px), var(--mag-y, 0px), 0);
  transition: transform 0.55s var(--ease-out), background-color 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.offwork__item.is-magnetic { transition: transform 0.14s linear, background-color 0.35s var(--ease), box-shadow 0.35s var(--ease); }
.offwork__item:hover { background: var(--glass-strong); box-shadow: var(--lift-1), var(--inner-hi); }
.offwork__label {
  display: flex; align-items: center; gap: var(--sp-2);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 700;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  margin-bottom: var(--sp-3);
}
.offwork__text { font-size: 0.9375rem; color: var(--muted); line-height: 1.55; }
.offwork__more {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: auto;
  padding-top: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 700;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--faint);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease-out), color 0.3s var(--ease);
}
.offwork__more svg { width: 12px; height: 12px; transition: transform 0.35s var(--ease-out); }
.offwork__item:hover .offwork__more,
.offwork__item:focus-visible .offwork__more { opacity: 1; transform: translateY(0); color: var(--ink); }
.offwork__item:hover .offwork__more svg { transform: translateX(3px); }

/* ------------------------ process, the dark anchor ------------------------ */
.process {
  /* on dark the accent role flips, so every accent use inside inverts */
  --accent: #f4f4f0;
  --accent-wash: rgba(244, 244, 240, 0.1);
  background: var(--tone-dark);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid rgba(255, 255, 255, 0.07);
  color: var(--on-dark);
  border-radius: var(--r-xl);
  padding-block: clamp(2.5rem, 1.75rem + 3.5vw, 4.5rem);
  margin-block: var(--section-y);
  box-shadow: var(--lift-2);
  overflow: hidden;
}
.process .eyebrow, .process .lead { color: var(--on-dark-mute); }
.process .section__index { color: var(--on-dark); opacity: 0.45; }
.process .mark { box-shadow: inset 0 -3px 0 var(--on-dark); }

.steps { display: grid; gap: var(--sp-4); }
.step {
  padding: var(--sp-6);
  background: var(--glass-dark);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  border: 1px solid var(--glass-dark-edge);
  border-radius: var(--r-md);
  transform: translate3d(var(--mag-x, 0px), var(--mag-y, 0px), 0);
  transition: transform 0.55s var(--ease-out), background-color 0.4s var(--ease), border-color 0.4s var(--ease);
}
.step.is-magnetic { transition: transform 0.14s linear, background-color 0.4s var(--ease), border-color 0.4s var(--ease); }
.step:hover { background: rgba(45, 45, 42, 0.6); border-color: rgba(255, 255, 255, 0.2); }
.step__num {
  font-family: var(--font-display);
  font-size: 2.75rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
  color: transparent;
  -webkit-text-stroke: 1px rgba(244, 244, 240, 0.5);
  margin-bottom: var(--sp-4);
}
.step__title {
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin-bottom: var(--sp-3);
}
.step p { font-size: 0.9375rem; color: var(--on-dark-mute); line-height: 1.55; }

/* tools strip, closes the process section */
.toolbelt {
  margin-top: var(--sp-6);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--dark-hair);
}
.toolbelt__list { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.toolbelt__list li {
  padding: 0.4rem 0.85rem;
  background: var(--glass-dark);
  border: 1px solid var(--glass-dark-edge);
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--on-dark);
}

/* ------------------------------ contact ------------------------------ */
.contact__grid { display: grid; gap: var(--sp-6); }
.paths { display: grid; gap: var(--sp-4); }
.path {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  padding: var(--sp-6);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-md);
  box-shadow: var(--inner-hi);
  text-decoration: none;
  transform: translate3d(var(--mag-x, 0px), var(--mag-y, 0px), 0);
  transition: transform 0.55s var(--ease-out), box-shadow 0.4s var(--ease), background-color 0.4s var(--ease);
}
.path.is-magnetic { transition: transform 0.14s linear, box-shadow 0.4s var(--ease), background-color 0.4s var(--ease); }
.path:hover, .path:focus-visible { box-shadow: var(--lift-2), var(--inner-hi); background: var(--glass-strong); }

.path__title { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; }
.path__arrow {
  flex: none;
  width: 42px; height: 42px;
  display: grid;
  place-items: center;
  border: 1px solid var(--glass-hair);
  border-radius: 50%;
  transition: transform 0.35s var(--settle), background-color 0.35s var(--ease),
              color 0.35s var(--ease), border-color 0.35s var(--ease);
}
.path__arrow svg { width: 16px; height: 16px; }
.path:hover .path__arrow { background: var(--ink); border-color: var(--ink); color: var(--on-dark); transform: rotate(-45deg); }

.path--primary { background: rgba(16, 16, 16, 0.9); border-color: rgba(255,255,255,0.1); color: var(--on-dark); box-shadow: var(--lift-1); }
.path--primary .path--primary .path__arrow { border-color: rgba(244, 244, 240, 0.3); }
.path--primary:hover { background: rgba(0, 0, 0, 0.94); }
.path--primary:hover .path__arrow { background: var(--on-dark); color: var(--ink); border-color: var(--on-dark); }


/* ------------------------------ footer ------------------------------ */
.site-footer {
  --accent: #f4f4f0;
  position: relative;
  background: var(--tone-dark);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  color: var(--on-dark);
  padding-block: var(--sp-6);
  /* no top margin: any gap here shows raw body background as a light band
     between the contact tone and the dark footer */
  margin-top: 0;
}
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4) var(--sp-5);
}
.site-footer__name {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.to-top {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--on-dark-mute);
  transition: color 0.3s var(--ease);
}
.to-top__arrow {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border: 1px solid var(--dark-hair);
  border-radius: 50%;
  transition: transform 0.45s var(--spring), background-color 0.3s var(--ease),
              color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.to-top__arrow svg { width: 14px; height: 14px; }
.to-top:hover, .to-top:focus-visible { color: var(--on-dark); }
.to-top:hover .to-top__arrow {
  transform: translateY(-3px);
  background: var(--on-dark);
  color: var(--ink);
  border-color: var(--on-dark);
}

/* =====================================================================
   Case study pages

   Each case carries one desaturated hue, set by data-case on <body>, so the
   three studies are told apart on sight without the site leaving monochrome.
   The hue only ever appears as a rail, a numeral, or a wash, never as fill.
   The default is ink, so these components stay monochrome anywhere the
   attribute is absent.
   ===================================================================== */
:root {
  --case-ink:   var(--ink);
  --case-wash:  var(--accent-wash);
  --case-edge:  var(--hair);
}
[data-case="huskyadapt"] {
  --case-ink:   #106e5f;
  --case-wash:  rgba(16, 110, 95, 0.07);
  --case-edge:  rgba(16, 110, 95, 0.24);
}
[data-case="rvfb"] {
  --case-ink:   #82571a;
  --case-wash:  rgba(130, 87, 26, 0.07);
  --case-edge:  rgba(130, 87, 26, 0.26);
}
[data-case="worldcup"] {
  --case-ink:   #2e3fa0;
  --case-wash:  rgba(46, 63, 160, 0.065);
  --case-edge:  rgba(46, 63, 160, 0.24);
}

.case-hero { padding-top: clamp(2.25rem, 1.5rem + 3vw, 4rem); padding-bottom: var(--sp-6); }
.back-link {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  margin-bottom: var(--sp-6);
  transition: color 0.25s var(--ease), gap 0.25s var(--ease);
}
.back-link svg { width: 13px; height: 13px; }
.back-link:hover, .back-link:focus-visible { color: var(--ink); gap: var(--sp-3); }

.case-hero .eyebrow { color: var(--case-ink); }
/* the diamond pops in on a spring rather than fading with the rest of the
   eyebrow line, so the case's colour is the first thing to arrive */
.case-hero .diamond {
  background: var(--case-ink);
  transform: rotate(45deg) scale(0);
  transition: transform 0.65s var(--spring) 0.15s;
}
.case-hero .eyebrow.is-visible .diamond { transform: rotate(45deg) scale(1); }

.case-title {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 800;
  line-height: 0.98;
  letter-spacing: -0.038em;
  text-transform: uppercase;
  margin-block: var(--sp-4);
  max-width: 16ch;
  text-wrap: balance;
}
/* a colour accent draws in under the headline once the wipe finishes, so the
   case's hue lands with a beat of its own instead of only tinting text */
.case-title::after {
  content: "";
  display: block;
  width: 0;
  height: 3px;
  margin-top: var(--sp-4);
  background: var(--case-ink);
  transition: width 0.85s var(--ease-out) 0.55s;
}
.case-title.is-visible::after { width: 4.5rem; }
/* the case sub is the one lead-scale line on the page, so it gets the hue
   rule that every downstream block echoes at smaller weight */
.case-sub {
  font-size: var(--fs-lead);
  color: var(--ink-soft);
  max-width: 52ch;
  text-wrap: pretty;
  padding-left: var(--sp-5);
  border-left: 2px solid var(--case-edge);
}

.case-facts {
  display: grid;
  gap: 0;
  margin-top: var(--sp-6);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  border-top: 2px solid var(--case-edge);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1), var(--inner-hi);
  overflow: hidden;
}
.case-facts > div { padding: var(--sp-5); border-top: 1px solid var(--glass-hair); }
.case-facts > div:first-child { border-top: 0; }

/* The hero is the one thing every visitor sees on load rather than on
   scroll, so it earns a quicker entrance than the plain fade and slide used
   further down the page. That system is untouched below the hero. No blur
   here either: blur suits the quieter reveals further down, not the first
   thing a visitor sees. */
.case-hero [data-reveal] {
  transition-duration: 0.55s;
  transition-timing-function: var(--settle);
}
.case-hero [data-reveal]:not(.is-visible) { filter: none; }
.case-hero .case-facts [data-reveal]:not(.is-visible) {
  transform: perspective(1400px) rotateX(4deg) translate3d(0, 16px, 0) scale(0.97);
}

.case-facts dt {
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 700;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--case-ink);
  opacity: 0.75;
  margin-bottom: var(--sp-2);
}
.case-facts dd { font-size: 0.9375rem; color: var(--ink-soft); }

.figure {
  margin-block: var(--sp-6);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--lift-1), var(--inner-hi);
  clip-path: inset(0 0 0 0);
  transition: border-color 0.4s var(--ease), box-shadow 0.4s var(--ease), clip-path 0.55s var(--ease-out);
}
.figure:hover { border-color: var(--case-edge); box-shadow: var(--lift-2), var(--inner-hi); }
/* --fig-hover and --fig-enter are independent multipliers, so the hover
   zoom and the entrance zoom never have to fight over one property */
.figure img {
  width: 100%; display: block;
  transform: scale(calc(var(--fig-hover, 1) * var(--fig-enter, 1)));
  transition: transform 0.5s var(--ease-out);
}
.figure:hover img { --fig-hover: 1.025; }
/* the frame opens like a shutter while the photo inside settles from a
   slight zoom, so a figure arrives as two layers instead of one flat fade.
   The curtain only ever hides a sliver, never the whole photo: even
   mid-transition or on a slow first paint this must never look broken. */
.figure[data-reveal]:not(.is-visible) { clip-path: inset(0 0 14% 0); }
.figure[data-reveal]:not(.is-visible) img { --fig-enter: 1.05; }
.figure figcaption {
  padding: var(--sp-3) var(--sp-5);
  background: rgba(255, 255, 255, 0.45);
  border-top: 1px solid var(--glass-hair);
  font-family: var(--font-display);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.figure-pair { display: grid; gap: var(--sp-4); margin-block: var(--sp-6); }
.figure-pair .figure { margin: 0; }

/* ---------------------------------------------------------------------
   The case block.

   Everything in a block hangs off one content column, so a heading and the
   copy beneath it share a left edge. The index numeral is the only thing
   that lives in the rail, which is what makes the numeral read as a marker
   for the block rather than as a competing column of its own.
   --------------------------------------------------------------------- */
/* Matches the shell's full content width, same as the photos beside it.
   Went through two narrower measures first (68ch, then 60rem) trying to
   protect paragraph line length, but both left every bullet list, decision
   log, quote box, and deliverables list looking stranded in empty space
   next to a full width photo, which read as broken rather than deliberate
   regardless of how readable the prose itself was. bullets/decision-log/
   quote/deliverables/callout are siblings of .case-block__body, not
   children of it, so they inherit this full width directly; the paragraph
   text alone keeps its own narrower reading measure below, since going
   uniform there too would push lines well past a comfortable length with
   nothing gained visually (prose wasn't what read as stranded). */
.case-block {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  max-width: 100%;
  padding-block: var(--sp-6);
}
.case-block__head { display: grid; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.case-block__body { max-width: 60rem; }
.case-block__body + .case-block__body { margin-top: var(--sp-5); }

/* the block heading, dropped a full step off the landing page's h2 so a case
   study reads as a document rather than a deck of title slides */
.case-block .h2 {
  font-size: clamp(1.45rem, 1.15rem + 1.3vw, 2.125rem);
  line-height: 1.12;
  letter-spacing: -0.022em;
  max-width: 24ch;
}
.case-block .section__index {
  font-size: var(--fs-meta);
  color: var(--case-ink);
  opacity: 1;
}
/* the lead paragraph of a block carries more weight than the copy after it,
   which is the depth the flat all-one-size version was missing */
.case-block__head + .case-block__body > p:first-child {
  font-size: 1.0625rem;
  color: var(--ink);
  font-weight: 500;
}

/* every non-prose element in a block is inset behind a hue rail, so a list,
   a log, or a quote all read as belonging to the heading above them */
.case-block > .bullets,
.case-block > .deliverables,
.case-block > .decision-log,
.case-block > .quote,
.case-block > .callout,
.case-block > .metrics { margin-top: var(--sp-5); }

/* width: fit-content so the card wraps its own stat + sentence instead of
   stretching to the full content column: a short "53.8%" and one line of
   text inside a column-wide box left a few hundred pixels of dead air on
   the right, which is what read as off centre. */
.callout {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: center;
  width: fit-content;
  max-width: 100%;
  padding: var(--sp-5) clamp(1.25rem, 1rem + 1.5vw, 2rem);
  background: var(--glass-strong);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  border-left: 3px solid var(--case-ink);
  border-radius: var(--r-md);
  box-shadow: var(--lift-1), var(--inner-hi);
  margin-block: var(--sp-5);
}
/* specificity matched to `.callout p` below, which would otherwise win */
.callout .callout__stat {
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 1.5rem + 3vw, 3.75rem);
  font-weight: 800;
  line-height: 0.9;
  letter-spacing: -0.045em;
  color: var(--case-ink);
}
.callout p { color: var(--ink-soft); max-width: 44ch; }

/* Visible placeholder for a fact only Justis can supply, distinct from the
   SWAP IN image comments so a guess never ships as finished copy by accident. */
.tk { font-style: italic; color: var(--muted); }
.tk::before { content: "TK: "; font-style: normal; font-weight: 700; color: var(--faint); }

/* Bullets as bounded rows. The old version let a bold label run inline into
   its own description, so the eye had to re-find where each item began. Each
   item is now one block with the label sitting directly above the sentence
   it governs. */
.bullets {
  display: grid;
  gap: 1px;
  margin-top: var(--sp-5);
  background: var(--glass-hair);
  border: 1px solid var(--glass-edge);
  border-left: 2px solid var(--case-edge);
  border-radius: var(--r-md);
  overflow: hidden;
}
.bullets li {
  display: grid;
  gap: var(--sp-1);
  padding: var(--sp-4) var(--sp-5);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  color: var(--ink-soft);
  font-size: 0.9375rem;
  transition: background-color 0.3s var(--ease), transform 0.3s var(--ease);
}
.bullets li:hover { background: var(--glass-strong); transform: translate3d(3px, 0, 0); }
.bullets li strong {
  display: block;
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--case-ink);
}

/* ---------------------------------------------------------------------
   Deliverables. A shipped-artifact inventory rather than a bulleted list:
   every row carries a number, a name, and the spec that would appear on a
   handoff sheet, so the section reads as production work.
   --------------------------------------------------------------------- */
.deliverables { display: grid; gap: var(--sp-3); margin-top: var(--sp-5); }
.deliverable {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
  padding: var(--sp-4) var(--sp-5);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-md);
  box-shadow: var(--lift-1), var(--inner-hi);
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease), border-color 0.35s var(--ease);
}
.deliverable:hover {
  transform: translateY(-2px);
  box-shadow: var(--lift-2), var(--inner-hi);
  border-color: var(--case-edge);
}
.deliverable__index {
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  line-height: 1;
  padding: 0.5em 0.65em;
  color: var(--case-ink);
  background: var(--case-wash);
  border: 1px solid var(--case-edge);
  border-radius: var(--r-sm);
}
.deliverable__name {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.012em;
  color: var(--ink);
}
.deliverable__spec {
  margin-top: 0.35em;
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--case-ink);
  opacity: 0.85;
}
.deliverable__note { margin-top: var(--sp-2); font-size: 0.9375rem; color: var(--ink-soft); }

/* Participant quote. Fills the block's own measure (see the comment on
   .case-block's max-width) and hangs an oversized quote mark in its own
   rail so the space that mark occupies reads as structure, not leftover
   width. */
.quote {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
  margin-block: var(--sp-6);
  padding: var(--sp-5) clamp(1.25rem, 1rem + 1.5vw, 2rem);
  background: var(--case-wash);
  border: 1px solid var(--case-edge);
  border-left: 3px solid var(--case-ink);
  border-radius: var(--r-md);
}
/* placed explicitly: the mark holds the rail across both rows so the quote
   and its attribution stay in one column instead of the cite wrapping back
   under the mark and starving the text of width */
.quote::before {
  content: "\201C";
  grid-column: 1;
  grid-row: 1 / span 2;
  font-family: var(--font-display);
  font-size: 3rem;
  font-weight: 800;
  line-height: 0.78;
  color: var(--case-ink);
  opacity: 0.42;
}
.quote p {
  grid-column: 2;
  grid-row: 1;
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 0.95rem + 0.95vw, 1.625rem);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.012em;
  color: var(--ink);
}
.quote__cite {
  display: block;
  grid-column: 2;
  grid-row: 2;
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--case-edge);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--case-ink);
  opacity: 0.9;
}

/* Metric row. Two to four stats, reusing the callout's numeral scale at a
   smaller size since these sit alongside prose rather than standing alone. */
.metrics {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6) var(--sp-5);
  margin-block: var(--sp-6);
  padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1), var(--inner-hi);
}
.metric { flex: 1 1 130px; }
.metric__stat {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 1.2rem + 2.4vw, 2.75rem);
  font-weight: 800;
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: var(--case-ink);
}
.metric__label { margin-top: var(--sp-2); font-size: 0.875rem; color: var(--muted); }

/* Decision log. Rows of dt/dd rather than a table, so it degrades to
   stacked cards on a phone instead of a table forced to scroll sideways.
   Each row is numbered by counter so the log reads as a sequence of calls. */
.decision-log { display: grid; gap: var(--sp-3); margin-block: var(--sp-5); counter-reset: decision; }
.decision-log__row {
  position: relative;
  counter-increment: decision;
  padding: var(--sp-4) var(--sp-5);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  border: 1px solid var(--glass-edge);
  border-top: 2px solid var(--case-edge);
  border-radius: var(--r-md);
  box-shadow: var(--lift-1), var(--inner-hi);
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.decision-log__row:hover { transform: translateY(-2px); box-shadow: var(--lift-2), var(--inner-hi); }
.decision-log__row::before {
  content: "Decision " counter(decision, decimal-leading-zero);
  display: block;
  margin-bottom: var(--sp-4);
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--case-ink);
}
.decision-log__row dl { display: grid; gap: var(--sp-4); }
.decision-log__row dt {
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 700;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: var(--sp-1);
}
.decision-log__row dd { color: var(--ink-soft); font-size: 0.9375rem; }

/* Before/after modifier on the existing figure pair. Same layout, just a
   labeled chip pinned over each frame. */
.figure-pair--compare .figure { position: relative; }
.figure__tag {
  position: absolute;
  top: var(--sp-4);
  left: var(--sp-4);
  z-index: 2;
  padding: 0.3em 0.85em;
  background: var(--glass-strong);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 700;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--ink);
}

/* Next project. A spotlight tracks the pointer (JS writes --mx/--my as
   percentages), and the arrow travels further than the generic .path
   treatment so this specific link reads as the more inviting exit off a
   case study. The magnetic pull is the same plain translate every other
   card on the site uses: an added rotate on top of it read as too much
   movement for a card this size. */
.next-project {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  padding: clamp(1.75rem, 1rem + 3vw, 3.5rem);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-xl);
  box-shadow: var(--lift-1), var(--inner-hi);
  text-decoration: none;
  margin-bottom: 0;
  transform: translate3d(var(--mag-x, 0px), var(--mag-y, 0px), 0);
  transition: transform 0.55s var(--ease-out), box-shadow 0.5s var(--ease), border-color 0.5s var(--ease);
}
.next-project.is-magnetic { transition: transform 0.14s linear, box-shadow 0.5s var(--ease), border-color 0.5s var(--ease); }
.next-project > span { position: relative; z-index: 1; }
.next-project::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(480px circle at var(--mx, 50%) var(--my, 30%), var(--case-wash), transparent 65%);
  opacity: 0;
  transition: opacity 0.5s var(--ease);
}
.next-project:hover::before, .next-project:focus-visible::before { opacity: 1; }
.next-project:hover, .next-project:focus-visible {
  border-color: var(--case-edge);
  box-shadow: var(--lift-2), 0 30px 55px -22px var(--case-wash);
}
.next-project:hover .path__arrow, .next-project:focus-visible .path__arrow {
  background: var(--case-ink);
  border-color: var(--case-ink);
  color: var(--on-dark);
  transform: translate3d(6px, 0, 0) rotate(-45deg) scale(1.08);
}
.next-project__label {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 700;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--case-ink);
  margin-bottom: var(--sp-3);
}
.next-project__label::before {
  content: "";
  width: 16px;
  height: 1.5px;
  background: var(--case-ink);
  transition: width 0.4s var(--ease);
}
.next-project:hover .next-project__label::before,
.next-project:focus-visible .next-project__label::before { width: 30px; }
.next-project__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 700;
  letter-spacing: -0.02em;
  display: inline-block;
  transition: transform 0.35s var(--settle);
}
.next-project:hover .next-project__title,
.next-project:focus-visible .next-project__title { transform: translate3d(6px, 0, 0); }

/* the trailing space belongs inside the tone, otherwise the footer sits on a
   band of raw body background instead of against the section colour.
   padding-top matches the ~64px figure/block rhythm used all the way up the
   page, so the run into "next project" gets the same room as every other
   transition instead of half of it. */
.case-tail { padding-top: var(--sp-6); padding-bottom: var(--section-y); }

/* ------------------------------ 404 ------------------------------ */
.notfound {
  min-height: calc(100dvh - 12rem);
  display: grid;
  align-items: center;
  padding-block: clamp(4rem, 3rem + 6vw, 9rem);
}
.notfound__code {
  font-family: var(--font-display);
  font-size: clamp(6rem, 3rem + 18vw, 16rem);
  font-weight: 800;
  line-height: 0.84;
  letter-spacing: -0.055em;
  margin-block: var(--sp-4) var(--sp-5);
}
.notfound__lead {
  font-size: var(--fs-lead);
  color: var(--ink-soft);
  max-width: 42ch;
  margin-bottom: var(--sp-7);
}
.notfound__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-bottom: var(--sp-8); }
.notfound__index { padding-top: var(--sp-6); border-top: 1px solid var(--hair); }
.notfound__index-label {
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 700;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: var(--sp-4);
}
.notfound__index ul { display: grid; gap: var(--sp-3); }
.notfound__index a {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 700;
  letter-spacing: -0.02em;
  text-decoration: none;
  background-image: linear-gradient(var(--ink), var(--ink));
  background-repeat: no-repeat;
  background-size: 0% 2px;
  background-position: 0 100%;
  transition: background-size 0.4s var(--ease-out);
}
.notfound__index a:hover, .notfound__index a:focus-visible { background-size: 100% 2px; }

@media (min-width: 720px) {
  .notfound__index ul { grid-template-columns: repeat(3, 1fr); gap: var(--sp-5); }
}

/* =====================================================================
   Reveal system: "focus pull"

   Everything hidden lives under :not(.is-visible) rather than everything
   visible living under .is-visible. The reason is specificity: an element
   that is both data-reveal and something else with its own permanent
   transform (a magnetic link, a card whose hover state moves it) needs to
   fall back to THAT rule once revealed, not have a hardcoded
   translate3d(0,0,0) outrank it forever. Writing only the hidden state
   means "revealed" is just "nothing here overriding you any more," so
   whatever the element's own resting transform is takes back over
   correctly, magnetic pull and all.
   ===================================================================== */
[data-reveal] {
  transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out), filter 0.6s var(--ease-out);
  transition-delay: calc(var(--reveal-i, 0) * 55ms);
}
[data-reveal]:not(.is-visible) {
  opacity: 0;
  filter: blur(5px);
  transform: translate3d(0, 14px, 0) scale(0.985);
}
/* an element that only wipes should not also blur or slide, the mask does the work */
[data-reveal].has-mask:not(.is-visible) { opacity: 1; filter: none; transform: none; }

/* Cards read as physical objects rather than lines of text, so they lean
   in with real depth (rotateX) instead of just sliding up flat. Each of
   these already owns its resting transform elsewhere (magnetic drift, or
   none at all), which is what it falls back to once revealed. Kept subtle:
   a long perspective and a couple of degrees reads as weight settling, a
   short perspective and a steep angle reads as sliding on ice. */
.project[data-reveal]:not(.is-visible),
.step[data-reveal]:not(.is-visible),
.offwork__item[data-reveal]:not(.is-visible),
.deliverable[data-reveal]:not(.is-visible),
.next-project[data-reveal]:not(.is-visible) {
  filter: blur(4px);
  transform: perspective(1600px) rotateX(3deg) translate3d(0, 14px, 0) scale(0.99);
}

/* The index numeral is its own recurring motif (see the diamond), so it
   gets a crisp spring pop instead of the soft focus pull everything else
   uses. Blur reads as muddy on a small bold mark at this size. */
.section__index[data-reveal] {
  transition: opacity 0.6s var(--spring), transform 0.6s var(--spring);
  transition-delay: calc(var(--reveal-i, 0) * 90ms);
}
.section__index[data-reveal]:not(.is-visible) {
  opacity: 0;
  filter: none;
  transform: scale(0.4) rotate(-14deg);
}

.no-js [data-reveal] { opacity: 1 !important; filter: none !important; transform: none !important; clip-path: none !important; }

/* ============================ breakpoints ============================ */
@media (max-width: 880px) {
  .nav-toggle { display: inline-flex; position: relative; z-index: 2; }
  .nav {
    position: fixed;
    inset: 0 0 auto 0;
    width: min(calc(100% - 2 * var(--sp-4)), calc(var(--shell) - var(--sp-9) * 2));
    margin-inline: auto;
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-5);
    padding: 6.5rem var(--gutter) var(--sp-7);
    background: rgba(250, 250, 248, 0.92);
    -webkit-backdrop-filter: var(--blur);
    backdrop-filter: var(--blur);
    border: 1px solid var(--glass-hair);
    border-radius: var(--r-xl);
    box-shadow: var(--lift-2);
    max-height: 100dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    transform: translate3d(0, -100%, 0);
    visibility: hidden;
    transition: transform 0.5s var(--ease-out), visibility 0.5s;
  }
  .nav.is-open { transform: translate3d(0, 0, 0); visibility: visible; }
  .nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav__list li + li { border-top: 1px solid var(--glass-hair); }
  .nav__link { display: block; padding: var(--sp-4) 0; font-size: 1rem; letter-spacing: 0.08em; }
  .nav__link::after { display: none; }
  .nav__cta { justify-content: center; }
}

@media (min-width: 560px) {
      .offwork { grid-template-columns: repeat(2, 1fr); }
  .figure-pair { grid-template-columns: 1fr 1fr; }
  .case-facts { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 720px) {
  .steps { grid-template-columns: repeat(2, 1fr); }
  }

@media (min-width: 900px) {
  .hero__body {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.62fr);
    align-items: end;
    gap: clamp(2rem, 1rem + 4vw, 4.5rem);
  }
  .hero__media { margin-top: -14vw; }
  .hero__figure { max-width: 380px; margin-left: auto; }

  .decision-log__row dl { grid-template-columns: 1fr 1.3fr 1.3fr; gap: var(--sp-6); }

  .about__grid { grid-template-columns: minmax(0, 0.56fr) minmax(0, 1.44fr); align-items: center; gap: clamp(2rem, 1rem + 3.5vw, 4rem); }
  .about__media { position: sticky; top: 6rem; }

  .section__head { grid-template-columns: auto minmax(0, 1.1fr) minmax(0, 0.9fr); gap: var(--sp-6); align-items: start; }
  .section__index { padding-top: 0.6rem; }

  .case-facts { grid-template-columns: repeat(4, 1fr); }
  .case-facts > div { border-top: 0; border-left: 1px solid var(--glass-hair); }
  .case-facts > div:first-child { border-left: 0; }
  .deliverable { grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-5); }
  .offwork { grid-template-columns: repeat(3, 1fr); }
}

@media (min-width: 1000px) {
  .work__grid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-7); }
  .project--featured { grid-column: 1 / -1; flex-direction: row; align-items: stretch; }
  .project--featured .project__media { flex: 0 0 56%; aspect-ratio: auto; }
  .project--featured .project__body { padding: clamp(1.75rem, 1rem + 2vw, 2.9rem); justify-content: center; }
  .steps { grid-template-columns: repeat(4, 1fr); gap: var(--sp-4); }
}

/* Blur is expensive on phones. Trade it for more opaque surfaces. */
@media (pointer: coarse), (max-width: 600px) {
  * { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
  :root {
    --glass: rgba(255, 255, 255, 0.82);
    --glass-strong: rgba(255, 255, 255, 0.92);
    --glass-dark: rgba(32, 32, 30, 0.82);
  }
  /* .nav's own background isn't tokenized through --glass, and without blur
     no amount of translucency fully hides the large bold hero type behind
     the open menu's links (even 0.97 alpha still ghosts through). Once blur
     is off there's no aesthetic reason left to stay translucent here, so
     go fully opaque instead of chasing a higher alpha. */
  .nav { background: var(--canvas); }
}

/* ------------------------------ motion opt out ------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  [data-reveal] { opacity: 1 !important; transform: none !important; filter: none !important; clip-path: none !important; }
  .glow-word, .status-dot::after { animation: none !important; }
  .mask__in { transform: none !important; }
  .mask { overflow: visible; }
  #field { display: none; }
  .project__media, .figure, .hero__figure, .about__figure { clip-path: none !important; }
  .project__media img { --tilt-x: 0deg; --tilt-y: 0deg; --img-scale-enter: 1 !important; }
  .figure img { --fig-enter: 1 !important; }
  .hero__media { margin-top: 0 !important; }
  .btn, .project, .path, .offwork__item, .step, .pill-link, .next-project {
    --mag-x: 0px; --mag-y: 0px;
  }
}


/* =====================================================================
   Off the clock dialogs
   Opened by click or keyboard, not hover: a hover-only panel is
   unreachable by keyboard, impossible on touch, and fires by accident
   on the way past. Hover gives the affordance, the click opens it.
   ===================================================================== */
.modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  padding: var(--gutter);
}
.modal[hidden] { display: none; }

.modal__scrim {
  position: absolute;
  inset: 0;
  background: rgba(16, 16, 16, 0.42);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
}
.modal.is-open .modal__scrim { opacity: 1; }

.modal__panel {
  position: relative;
  width: min(100%, 760px);
  max-height: min(86vh, 860px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: clamp(1.5rem, 1rem + 2vw, 2.75rem);
  background: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-xl);
  box-shadow: var(--lift-2), var(--inner-hi);
  opacity: 0;
  transform: translate3d(0, 16px, 0) scale(0.985);
  transition: opacity 0.4s var(--ease-out), transform 0.45s var(--ease-out);
}
.modal.is-open .modal__panel { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }

.modal__close {
  position: absolute;
  top: var(--sp-4);
  right: var(--sp-4);
  display: grid;
  place-items: center;
  width: 36px; height: 36px;
  background: transparent;
  border: 1px solid var(--glass-hair);
  border-radius: 50%;
  cursor: pointer;
  transition: background-color 0.25s var(--ease), color 0.25s var(--ease),
              border-color 0.25s var(--ease), transform 0.35s var(--spring);
}
.modal__close svg { width: 15px; height: 15px; }
.modal__close:hover, .modal__close:focus-visible {
  background: var(--ink);
  color: var(--on-dark);
  border-color: var(--ink);
  transform: rotate(90deg);
}

.modal__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--sp-3);
}
.modal__title {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.4rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin-bottom: var(--sp-4);
}
.modal__body { color: var(--ink-soft); max-width: 52ch; }

.modal__gallery {
  /* --row-h caps how tall a row can get. Five of the six shots are portrait,
     and uncapped they push the dialog into a scrollbar. Tied to viewport
     height so it also holds on a short laptop screen. */
  --row-h: clamp(150px, 30dvh, 250px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
  align-items: flex-start;
}
/* Justified gallery. Each shot grows in proportion to its own aspect ratio
   (stamped as --ar by tools/wire-images.sh), so portrait and landscape photos
   sitting in the same row land on a matching height and nothing gets cropped
   to force a shape it was never shot in. */
.modal__shot {
  flex: var(--ar, 1.333) 1 0;
  /* min-width beats max-width in CSS, so a floor here would let narrow
     portraits escape the row cap and break the shared height */
  min-width: 0;
  /* height is width / aspect, so bounding width bounds height without cropping */
  max-width: calc(var(--ar, 1.333) * var(--row-h));
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--surface-2);
  border: 1px solid var(--glass-hair);
}
.modal__shot img { display: block; width: 100%; height: auto; }
.modal__shot figcaption {
  display: flex;
  align-items: center;
  padding: var(--sp-2) var(--sp-4);
  font-family: var(--font-display);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

@media (max-width: 619px) {
  /* one per row on small screens, where a justified pair gets too narrow */
  .modal__shot { flex-basis: 100%; max-width: 100%; }
  .modal__gallery { --row-h: clamp(200px, 42dvh, 340px); }
}

/* =========================================================================
   Lightbox
   Any real photo or artifact image can be enlarged. Built at runtime by
   app.js, which wraps each image in a button so it stays a real, focusable
   control rather than a div with a click handler bolted on. Reuses the off
   the clock dialog's structure (scrim, focus trap, Escape, reduced motion)
   but goes dark and edge to edge, since the point here is the image, not a
   card of copy around it.
   ========================================================================= */
.figure__zoom {
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}
.figure__zoom:focus-visible {
  outline: 2px solid var(--case-ink, var(--ink));
  outline-offset: -3px;
  border-radius: inherit;
}
.figure__zoom-cue {
  position: absolute;
  right: var(--sp-4);
  bottom: var(--sp-4);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.55rem 1rem;
  background: rgba(16, 16, 16, 0.82);
  -webkit-backdrop-filter: var(--blur-lite);
  backdrop-filter: var(--blur-lite);
  color: var(--on-dark);
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-radius: var(--r-pill);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--spring);
  pointer-events: none;
}
.figure__zoom-cue svg { width: 12px; height: 12px; }
.figure:hover .figure__zoom-cue, .figure:focus-within .figure__zoom-cue,
.hero__figure:hover .figure__zoom-cue, .hero__figure:focus-within .figure__zoom-cue,
.about__figure:hover .figure__zoom-cue, .about__figure:focus-within .figure__zoom-cue {
  opacity: 1;
  transform: translateY(0);
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: grid;
  place-items: center;
  padding: var(--gutter);
}
.lightbox[hidden] { display: none; }

.lightbox__scrim {
  position: absolute;
  inset: 0;
  background: rgba(8, 8, 8, 0.88);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
}
.lightbox.is-open .lightbox__scrim { opacity: 1; }

.lightbox__frame {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 94vw;
  opacity: 0;
  transform: translate3d(0, 14px, 0) scale(0.985);
  transition: opacity 0.4s var(--ease-out), transform 0.45s var(--ease-out);
}
.lightbox.is-open .lightbox__frame { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }

.lightbox__img-wrap {
  position: relative;
  line-height: 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: rgba(16, 16, 16, 0.5);
  box-shadow: var(--lift-2);
}
.lightbox__img {
  display: block;
  max-width: min(94vw, 1400px);
  max-height: 78vh;
  width: auto;
  height: auto;
}
.lightbox__caption {
  margin-top: var(--sp-4);
  padding: 0 var(--sp-2);
  max-width: 62ch;
  color: var(--on-dark-mute);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-align: center;
}
.lightbox__caption[hidden] { display: none; }

.lightbox__close {
  position: absolute;
  top: var(--sp-4);
  right: var(--sp-4);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  background: rgba(16, 16, 16, 0.55);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 50%;
  color: var(--on-dark);
  cursor: pointer;
  transition: background-color 0.25s var(--ease), color 0.25s var(--ease),
              border-color 0.25s var(--ease), transform 0.35s var(--spring);
}
.lightbox__close svg { width: 15px; height: 15px; }
.lightbox__close:hover, .lightbox__close:focus-visible {
  background: var(--on-dark);
  color: var(--ink);
  border-color: var(--on-dark);
  transform: rotate(90deg);
}

@media (max-width: 619px) {
  .lightbox__img { max-height: 66vh; }
}

@media (prefers-reduced-motion: reduce) {
  .modal__scrim, .modal__panel { opacity: 1 !important; transform: none !important; }
  .offwork__more { opacity: 1 !important; transform: none !important; }
  .lightbox__scrim, .lightbox__frame { opacity: 1 !important; transform: none !important; }
  .figure__zoom-cue { transition: opacity 0.2s linear !important; transform: none !important; }
}
