/* ============================================================
   samdesign.works — replica
   Tokens measured from the live Framer build.
   ============================================================ */

:root {
  color-scheme: dark;

  --bg:        #07080A;
  --fg:        #F5F6F8;
  --muted:     #9CA3AF;
  --prose:     #B0B6C2;

  --surface:   #0D0F13;                   /* raised panels (mobile menu) */
  --raise:     rgba(255, 255, 255, .06);  /* hover / nav pill fill */
  --raise-soft: rgba(255, 255, 255, .05);
  --line:      rgba(245, 246, 248, .12);
  --line-soft: rgba(245, 246, 248, .1);
  --line-strong: rgba(245, 246, 248, .28);

  --code-bg:   rgba(255, 255, 255, .1);
  --code-fg:   #E6EBF5;

  --accent:        rgba(0, 255, 174, .7);
  --accent-strong: rgba(0, 255, 174, 1);
  --accent-ring:   rgba(0, 255, 174, .18);

  --danger:      #FF6B6B;
  --danger-soft: rgba(255, 107, 107, .1);

  --dock-bg:         linear-gradient(#000000 0%, #141414 100%);
  --dock-seg:        linear-gradient(#151515 0%, #1F1F1F 100%);
  --dock-rim:        #2A292C;
  --dock-text:       #DAD9DC;
  --dock-text-on:    #FFFFFF;
  /* The press is a crossfade between a lit rim and a recessed one, so each
     shadow layer needs a zero-alpha twin to interpolate against. */
  --dock-hi:         #303030;
  --dock-hi-0:       rgba(48, 48, 48, 0);
  --dock-lo:         #161616;
  --dock-lo-0:       rgba(22, 22, 22, 0);
  --dock-press:      #0D0D0D;
  --dock-press-0:    rgba(13, 13, 13, 0);

  --grain-opacity: .75;
  --grain-invert: 0;

  --font-sans:    "Geist", "Geist Placeholder", sans-serif;
  --font-display: "Inter", "Inter Display", sans-serif;

  --col-narrow: 560px;   /* hero + works text */
  --col-media:  600px;   /* works media + chips */
  --col-wide:   800px;   /* about */
}

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

  --bg:        #F8F3EA;
  --fg:        #16130D;
  --muted:     #6B6559;
  --prose:     #46413A;

  --surface:   #FFFDF8;
  --raise:     rgba(22, 19, 13, .06);
  --raise-soft: rgba(22, 19, 13, .05);
  --line:      rgba(22, 19, 13, .14);
  --line-soft: rgba(22, 19, 13, .1);
  --line-strong: rgba(22, 19, 13, .3);

  --code-bg:   rgba(22, 19, 13, .07);
  --code-fg:   #221E15;

  /* The mint is illegible on white, so light mode uses a deeper green. */
  --accent:        #00875A;
  --accent-strong: #006E49;
  --accent-ring:   rgba(0, 135, 90, .16);

  --danger:      #C0392B;
  --danger-soft: rgba(192, 57, 43, .08);

  --dock-bg:         linear-gradient(#FFFDF8 0%, #EFE8DB 100%);
  --dock-seg:        linear-gradient(#FDFAF4 0%, #F2EBDE 100%);
  --dock-rim:        rgba(22, 19, 13, .16);
  --dock-text:       #46413A;
  --dock-text-on:    #16130D;
  --dock-hi:         rgba(255, 255, 255, .9);
  --dock-hi-0:       rgba(255, 255, 255, 0);
  --dock-lo:         rgba(22, 19, 13, .08);
  --dock-lo-0:       rgba(22, 19, 13, 0);
  --dock-press:      rgba(22, 19, 13, .22);
  --dock-press-0:    rgba(22, 19, 13, 0);

  --grain-opacity: .28;
  --grain-invert: 1;
}

/* Colour changes are eased so a theme switch reads as a transition, not a
   flash. Excluded from anything already animating its own transform. */
body, .chip, .btn-dark, .toc a, .mobile-menu, .dock-inner {
  transition: background-color .35s ease, color .35s ease, box-shadow .35s ease;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

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

/* Opening a deep link like /projects/x#section jumps natively, before any of
   our JS runs, so headings carry their own clearance for the fixed nav too. */
h1[id], h2[id], h3[id] { scroll-margin-top: 88px; }

/* Focus lands here after a soft navigation so the page change is announced.
   It's a scroll container, not a control — no ring. */
#main:focus { outline: none; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }
img, video { display: block; max-width: 100%; }
em { font-style: italic; }

/* ── Grain overlay ─────────────────────────────────────────── */
.grain {
  position: fixed;
  inset: 0;
  z-index: 0;
  opacity: var(--grain-opacity);
  pointer-events: none;
}
.grain img { width: 100%; height: 100%; object-fit: cover; filter: invert(var(--grain-invert)); }

/* ── Shared icon sizing ────────────────────────────────────── */
svg.ico-16, svg.ico-18, svg.ico-20 {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}
svg.ico-16 { width: 16px; height: 16px; }
svg.ico-18 { width: 18px; height: 18px; }
svg.ico-20 { width: 20px; height: 20px; }

/* ── Typography primitives ─────────────────────────────────── */
/* Inter Display is Inter at a display optical size — drive the opsz axis
   so glyph widths match the original rather than Inter's text-size metrics. */
.h-24 {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 500;
  line-height: 33.6px;
  letter-spacing: -.05px;
  font-variation-settings: "opsz" 32;
  color: var(--fg);
  display: inline-block;
}
.label-muted {
  font-size: 14px;
  line-height: 22.4px;
  letter-spacing: -.28px;
  color: var(--muted);
}

/* ============================================================
   NAV — fixed, 83px, 16px/120px, backdrop blur
   ============================================================ */
/* Root stacking order, which the mobile menu depends on:
     #main 1  <  dock 12  <  .menu-backdrop 15  <  .nav 20
   The menu lives inside .nav, and .nav is a stacking context of its own
   (z-index plus backdrop-filter), so no z-index on the menu itself can lift
   it past anything outside .nav — it is pinned to whatever .nav is. That's
   why .nav, not the menu, has to outrank the backdrop. Keeping .nav on top
   also leaves the burger clickable while the sheet is open, so the X still
   closes it. */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 20;
  height: 83px;
  padding: 16px 120px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  background: rgba(15, 15, 15, 0);
}

.nav-id { display: flex; align-items: center; gap: 8px; }

.avatar {
  width: 42px; height: 42px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }

.nav-id-text { display: flex; flex-direction: column; align-items: flex-start; }
.nav-name { font-size: 18px; line-height: 28.8px; letter-spacing: -.36px; color: var(--fg); }
.nav-role { font-size: 14px; line-height: 22.4px; letter-spacing: -.28px; color: var(--muted); }

.nav-right { display: flex; align-items: center; gap: 34px; }

/* Ghost buttons, grouped. Icon and label both inherit colour so they react to
   hover together; the fill sits just above the page background. */
.nav-links { position: relative; display: flex; align-items: center; gap: 2px; }

/* One pill for the whole group: it slides and resizes to whichever item is
   hovered, rather than each item fading its own background in and out. */
.nav-pill {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: var(--pill-w, 0);
  border-radius: 10px;
  background: var(--raise);
  opacity: 0;
  pointer-events: none;
  transform: translateX(var(--pill-x, 0));
  /* Only opacity animates while idle, so the pill snaps to its first target
     off-screen and fades in at full size instead of growing from zero. */
  transition: opacity .2s ease;
}
.nav-links.is-tracking .nav-pill {
  opacity: 1;
  transition:
    transform .3s cubic-bezier(.4, 0, .2, 1),
    width .3s cubic-bezier(.4, 0, .2, 1),
    opacity .2s ease;
}

.nav-links a {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: 10px;
  color: var(--muted);
  background: transparent;
  transition: color .22s ease;
}
.nav-links a svg { color: inherit; }
.nav-links p {
  font-size: 14px;
  line-height: 22.4px;
  letter-spacing: -.28px;
  color: inherit;
}
/* The pill supplies the fill; the link itself only brightens. */
.nav-links a:hover,
.nav-links a:focus-visible { color: var(--fg); }
.nav-links a:focus-visible { outline: none; }

/* Current page — a persistent fill under the sliding pill, plus a mint icon. */
.nav-links a.is-active { color: var(--fg); }
.nav-links a.is-active::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 10px;
  background: var(--raise-soft);
}
.nav-links a.is-active svg { color: var(--accent-strong); }

.nav-social { display: flex; align-items: center; gap: 8px; }
.nav-social a {
  width: 28px; height: 28px;
  padding: 4px;
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  color: var(--fg);
  transition: background .2s ease, color .2s ease;
}
.nav-social a:hover { background: var(--raise); }

/* ============================================================
   LAYOUT COLUMNS
   ============================================================ */
#main { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; }

.col-560 { width: var(--col-narrow); display: flex; flex-direction: column; align-items: flex-start; gap: 20px; }
.col-600 { width: var(--col-media);  display: flex; flex-direction: column; align-items: flex-start; gap: 20px; }
.col-800 { width: var(--col-wide);   display: flex; flex-direction: column; align-items: flex-start; gap: 16px; }

/* ============================================================
   HERO — padding-top 160
   ============================================================ */
.hero { width: 100%; padding: 160px 0 0; display: flex; justify-content: center; }

.hero-title {
  width: 100%;
  font-family: var(--font-sans);
  font-size: 28px;
  font-weight: 500;
  line-height: 39.2px;
  letter-spacing: -.28px;
  color: var(--fg);
}

.specialty-head { width: 100%; display: flex; align-items: center; gap: 10px; padding-right: 11px; }
.rule { flex: 1; height: 1px; background: var(--line); }

.specialty-list { width: 100%; display: flex; flex-direction: column; gap: 8px; }
.specialty-row { display: flex; align-items: center; gap: 8px; }
.specialty-row p {
  font-size: 14px;
  line-height: 22.4px;
  letter-spacing: -.28px;
  color: var(--fg);
}
.specialty-row strong { font-weight: 600; }
.specialty-row .dim { color: var(--muted); }
.specialty-row svg { color: var(--fg); }

.cta-row { display: flex; align-items: center; gap: 12px; padding: 12px 0 20px; }
.cta-row--about { padding: 0 0 20px; }

/* About page only: match the metal button's corners to View Resume's 12px
   rect instead of the hero's full pill. */
.cta-row--about .btn-metal-border,
.cta-row--about .btn-metal-face { border-radius: 12px; }

/* ============================================================
   BUTTONS
   ============================================================ */

/* — metallic "Send a message" — */
.btn-metal { display: block; width: 126px; height: 40px; }
.btn-metal-border {
  display: block;
  width: 126px; height: 39px;
  padding: .78px;
  border-radius: 61.2px;
  overflow: hidden;
  background: radial-gradient(29% 50% at 12.8% 37.8%, #9C9C9C 0%, #FFFFFF 100%);
  box-shadow: 0 3.12px 18px -2.92px rgba(0,0,0,.47);
}
.btn-metal-face {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%;
  border-radius: 61.2px;
  overflow: hidden;
  background: linear-gradient(0deg, #F2F2F2 0%, #D7D7D7 25%, #D7D7D7 57%, #F2F2F2 100%);
}
.btn-metal-noise {
  position: absolute; inset: 0;
  background-image: url("https://framerusercontent.com/images/wGAHOWhVswEtWkOKTJN6s2CW0.svg?width=96&height=96");
  background-size: 18.5px auto;
  background-repeat: repeat;
  opacity: .28;
  pointer-events: none;
}
.btn-metal-shine {
  position: absolute;
  top: 50%; left: -40%;
  width: 79px; height: 79px;
  transform: translateY(-50%);
  opacity: .52;
  background: linear-gradient(270deg, rgba(255,255,255,.7) 0%, rgba(255,255,255,.63) 33%, rgba(255,255,255,0) 100%);
  animation: shine 3.6s ease-in-out infinite;
  pointer-events: none;
}
@keyframes shine {
  0%, 100% { left: -45%; }
  50%      { left: 105%; }
}

/* text swap on hover */
.btn-swap {
  position: relative;
  display: block;
  height: 16px;
  overflow: hidden;
  z-index: 1;
}
.btn-swap p {
  font-size: 13px;
  font-weight: 500;
  line-height: 15.6px;
  letter-spacing: -.78px;
  color: #272B2D;
  transition: transform .32s cubic-bezier(.4,0,.2,1);
}
.btn-swap p:last-child { position: absolute; top: 0; left: 0; transform: translateY(100%); }
.btn-metal:hover .btn-swap p:first-child { transform: translateY(-100%); }
.btn-metal:hover .btn-swap p:last-child  { transform: translateY(0); }

/* — dark buttons — */
.btn-dark {
  background: var(--bg);
  color: var(--fg);
  font-size: 14px;
  line-height: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  box-shadow: inset 0 0 0 1px var(--line);
  transition: box-shadow .2s ease, background .2s ease;
}
.btn-dark:hover { box-shadow: inset 0 0 0 1px var(--line-strong); }

.btn-dark--pill,
.btn-dark--rect { font-family: var(--font-display); font-variation-settings: "opsz" 32; letter-spacing: -.18px; }
.btn-dark--pill { height: 40px; padding: 10px 20px; border-radius: 32px; }
.btn-dark--rect { height: 40px; padding: 10px 24px; border-radius: 12px; }
.btn-dark--wide {
  flex: 1 1 0;
  height: 40px;
  padding: 9px 18px 9px 16px;
  border-radius: 32px;
  gap: 8px;
}
.btn-dark--wide p { font-size: 14px; line-height: 22.4px; letter-spacing: -.28px; }
.dot-icon { width: 16px; height: 16px; color: var(--fg); display: flex; }

/* — silver "View More" — */
.btn-silver {
  flex: 1 1 0;
  height: 40px;
  border-radius: 32px;
  background: #F5F6F8;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
}
.btn-silver-face {
  width: calc(100% - 6px);
  height: 39px;
  border-radius: 30px;
  background: linear-gradient(129deg, #C1CBDB 0%, #BFBFBF 100%);
  display: flex; align-items: center; justify-content: center;
  transition: filter .2s ease;
}
.btn-silver:hover .btn-silver-face { filter: brightness(1.06); }
.btn-silver p {
  font-size: 14px;
  line-height: 22.4px;
  letter-spacing: -.28px;
  color: #07080A;
}

/* ============================================================
   WORKS — padding-top 64
   ============================================================ */
.works { width: 100%; padding: 64px 0 0; display: flex; justify-content: center; }

.works-head { width: 100%; padding: 0 32px 0 20px; }

.works-list { width: 100%; display: flex; flex-direction: column; gap: 40px; }

.work-card { width: 100%; display: block; }

.work-media {
  width: 100%;
  padding-bottom: 8px;
}
.work-media video {
  width: 600px;
  height: 360px;
  border-radius: 8px;
  object-fit: cover;
  transition: transform .5s cubic-bezier(.4,0,.2,1);
}
.work-card:hover .work-media video { transform: scale(1.012); }

.work-meta { width: 100%; padding: 0 20px; display: flex; align-items: flex-start; }
.work-meta > div { width: 560px; display: flex; flex-direction: column; gap: 4px; }
.work-title { font-size: 18px; line-height: 28.8px; letter-spacing: -.36px; color: var(--fg); }
.work-sub   { font-size: 14px; line-height: 22.4px; letter-spacing: -.28px; color: var(--muted); }

.works-cta { width: 100%; display: flex; align-items: center; gap: 12px; padding: 24px 0 0; }
/* equal halves — flex-basis:0 would split unevenly, since border-box padding
   is added on top of the basis before free space is shared out */
.works-cta > * { flex: 0 0 calc((100% - 12px) / 2); }

/* ============================================================
   BANDS — Clients / Tools / About (48px top, 40px bottom)
   ============================================================ */
.band { width: 100%; margin-top: 10px; padding: 48px 0 40px; display: flex; justify-content: center; }
.band .col-600 { gap: 16px; }

.band-head { width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.band-head--about { gap: 10px; }
.band-sub {
  font-size: 14px;
  line-height: 22.4px;
  letter-spacing: -.28px;
  color: var(--muted);
}
.band-sub strong { font-weight: 700; }
.about-sub {
  font-size: 14px;
  line-height: 22.4px;
  letter-spacing: -.28px;
  color: var(--muted);
}

/* ── chips ─────────────────────────────────────────────────── */
.chips {
  width: 100%;
  padding: 4px 0;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.chip {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  padding: 4px 8px;
  border-radius: 8px;
  background: var(--bg);
  transition: background .2s ease;
}
.chip:hover { background: var(--raise); }
.chip img { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; flex: none; }
.chip p { font-size: 14px; line-height: 22.4px; letter-spacing: -.28px; color: var(--muted); white-space: nowrap; }

/* ============================================================
   ABOUT
   ============================================================ */
.about-body { width: 100%; display: flex; align-items: flex-start; justify-content: center; gap: 32px; }

.photo-stack {
  position: relative;
  width: 320px;
  height: 412px;
  flex: none;
}
.photo-stack img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 21.97px;
  transition: transform .9s cubic-bezier(.4,0,.2,1), opacity .9s cubic-bezier(.4,0,.2,1);
}
.photo-stack img[data-depth="0"] { transform: scale(1);     opacity: 1;  z-index: 3; }
.photo-stack img[data-depth="1"] { transform: scale(.928);  opacity: .6; z-index: 2; }
.photo-stack img[data-depth="2"] { transform: scale(.856);  opacity: .3; z-index: 1; }

.about-copy { width: 448px; display: flex; flex-direction: column; align-items: flex-start; gap: 16px; }

.about-lead {
  width: 100%;
  font-size: 14px;
  font-weight: 600;
  line-height: 22.4px;
  letter-spacing: -.28px;
  color: var(--muted);
}
.about-paras { width: 100%; display: flex; flex-direction: column; gap: 16px; }
.about-paras p {
  font-size: 14px;
  font-weight: 500;
  line-height: 22.4px;
  letter-spacing: -.28px;
  color: var(--muted);
}

/* ============================================================
   FOOTER
   ============================================================ */
.footer { width: 100%; padding: 40px 120px 80px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.footer-inner { width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: 16px; }

.footer-links { display: flex; align-items: center; gap: 12px; }
.footer-links p { font-size: 14px; line-height: 22.4px; letter-spacing: -.28px; color: var(--muted); }
.footer-links a p { transition: color .2s ease; }
.footer-links a:hover p { color: var(--fg); }
.footer-links .slash { font-size: 16px; line-height: 19px; }

.copy { font-size: 14px; line-height: 22.4px; letter-spacing: -.28px; color: var(--muted); }

/* ============================================================
   DOCK — fixed bottom segmented control
   ============================================================ */
.dock {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  z-index: 12; /* above the page, below .menu-backdrop so it dims too */
}
.dock-inner {
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 2px;
  border-radius: 20px;
  background: var(--dock-bg);
  box-shadow: 0 1px 0 0 var(--dock-rim);
}
/* The segment is a physical key. At rest it sits proud of the dock: a lit top
   rim (--dock-hi) reads as the lip catching light. Pressed, that rim fades out
   entirely, a dark rim closes around it, and the whole key — label included —
   scales to .9 so it sinks into the well. Measured off the original, where the
   active segment rests at matrix(0.9, 0, 0, 0.9, 0, 0) with both lift shadows
   animated to alpha 0. The background gradient never changes; the lighting does. */
.dock-seg {
  width: 104px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: var(--dock-seg);
  /* Three layers in both states so box-shadow interpolates instead of snapping. */
  box-shadow:
    inset 0 1px 1px 1px var(--dock-hi),
    inset -1px -1px 4px 0 var(--dock-lo),
    inset 0 0 2px 1px var(--dock-press-0);
  transform: scale(1);
  transform-origin: 50% 50%;
  transition:
    transform .22s var(--ease-appear),
    box-shadow .22s var(--ease-appear);
  -webkit-tap-highlight-color: transparent;
}
.dock-seg:first-child { border-radius: 20px 8px 8px 20px; }
.dock-seg:last-child  { border-radius: 8px 20px 20px 8px; }
.dock-seg p {
  font-size: 12px;
  font-weight: 500;
  line-height: 14.4px;
  color: var(--dock-text);
  transition: color .22s var(--ease-appear);
}

/* Pressed: the page you're on, and any key under the cursor mid-click. */
.dock-seg.is-active,
.dock-seg.was-active,
.dock-seg:active {
  transform: scale(.9);
  box-shadow:
    inset 0 1px 1px 1px var(--dock-hi-0),
    inset -1px -1px 4px 0 var(--dock-lo-0),
    inset 0 0 2px 1px var(--dock-press);
}
.dock-seg.is-active p,
.dock-seg.was-active p,
.dock-seg:active p { color: var(--dock-text-on); }

/* Going down is a snap, coming up is a settle — matched to the original, where
   the rim collapsed within one frame of pointerdown but took ~150ms to return. */
.dock-seg:active { transition-duration: .07s; }

/* Navigation is a full page load, so the outgoing key has no chance to rise on
   its own. app.js paints the previous page's state for one frame, then drops
   these classes so the keys swap under the transition above. */
.dock-seg.is-active.not-yet {
  transform: scale(1);
  box-shadow:
    inset 0 1px 1px 1px var(--dock-hi),
    inset -1px -1px 4px 0 var(--dock-lo),
    inset 0 0 2px 1px var(--dock-press-0);
}
.dock-seg.is-active.not-yet p { color: var(--dock-text); }
.dock-inner.is-settling .dock-seg,
.dock-inner.is-settling .dock-seg p { transition: none; }

/* ============================================================
   SUB-PAGE SHELL — Works / Resources index
   ============================================================ */
.page {
  width: 100%;
  padding: 140px 0 80px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

.page-head { display: flex; flex-direction: column; align-items: center; gap: 16px; }
/* align-items: center only centers the title's own box, and an auto-width
   inline-block that wraps grows to fill the full available width (shrink-to-
   fit sizing) -- so on Resources, where the longer heading wraps to two
   lines on a narrow phone, the box was centered but its two lines still sat
   left-aligned inside it. Works/Contact share this markup and happen to fit
   on one line, where the bug is invisible but was still latent. */
.page-head .h-24 { text-align: center; }
.page-head-row { display: flex; align-items: center; gap: 8px; }
.page-lede {
  font-size: 18px;
  font-weight: 500;
  line-height: 28.8px;
  letter-spacing: -.36px;
  color: var(--muted);
}
.link-mint {
  font-size: 18px;
  font-weight: 500;
  line-height: 28.8px;
  letter-spacing: -.36px;
  color: var(--accent);
  transition: color .2s ease;
}
.link-mint:hover { color: var(--accent-strong); }

.grid-wrap { width: 1020px; max-width: 100%; padding-top: 12px; }
.grid {
  display: grid;
  grid-template-columns: repeat(3, 313.33px);
  grid-auto-rows: 1fr; /* every row as tall as its tallest card, as in the original */
  gap: 40px;
}

/* Cards are 320 wide inside 313px cells — they overhang, as in the original. */
.gcard { width: 320px; max-width: 100%; display: flex; flex-direction: column; }
.gcard-media video {
  width: 320px;
  max-width: 100%;
  height: 228px;
  border-radius: 8px;
  object-fit: cover;
  transition: transform .5s cubic-bezier(.4, 0, .2, 1);
}
.gcard--res .gcard-media video { height: 232px; }
.gcard:hover .gcard-media video { transform: scale(1.015); }

.gcard-meta { display: flex; align-items: flex-start; gap: 12px; padding: 16px 14px 0; }
.gcard--res .gcard-meta { padding-top: 15px; }
.gcard-logo { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; flex: none; }
.gcard-meta > div { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.gcard-title { font-size: 18px; line-height: 25.2px; letter-spacing: -.36px; color: var(--fg); }
.gcard-sub { font-size: 12px; line-height: 16.8px; letter-spacing: -.24px; color: var(--muted); }

/* ============================================================
   CASE STUDY (CMS detail page)
   ============================================================ */
.article {
  width: 100%;
  /* 100px, not 140: the back button takes the top of this space, which leaves
     the title 16px lower than it was (100 + 36 button + 20 gap = 156) rather
     than pushing it down by the whole 56px the button and its gap add up to. */
  padding: 100px 0 80px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Back button: sits in the 720px column, above the title. Quiet on purpose —
   muted until hovered, same hover fill as the nav links — since it's a way out,
   not part of the case study. Its own entrance rather than .reveal, so it
   doesn't push every block after it back by another cascade step. */
.back-wrap { width: 720px; margin-bottom: 20px; }
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  /* The hover fill needs room around the content, so the link reaches 10px past
     the column on the left and the chevron sits exactly where the title starts. */
  padding: 0 14px 0 10px;
  margin-left: -10px;
  border-radius: 10px;
  font-size: 14px;
  letter-spacing: -.28px;
  color: var(--muted);
  transition: color .2s ease, background .2s ease;
  animation: back-in .6s var(--ease-appear) .1s both;
}
/* The chevron's viewBox is cropped to the stroke itself (round caps included),
   so the arrow's own edge is the svg's edge. The shared 16px icon box leaves
   about 6px of empty space on the left, which is what made it look indented
   from the title even though the box was aligned. 5x9 is that 16px icon's scale. */
.back-link svg {
  width: 5px;
  height: 9px;
  flex: none;
  overflow: visible;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .2s ease;
}
.back-link:hover,
.back-link:focus-visible { color: var(--fg); background: var(--raise); }
.back-link:hover svg { transform: translateX(-2px); }
.back-link:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--accent-strong); }
@keyframes back-in { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .back-link { animation: none; } }

/* Head spans the 720 body column but wraps at 600, matching the original. */
.article-head { width: 720px; display: flex; flex-direction: column; gap: 8px; }
.article-title, .article-sub { max-width: 600px; }
.article-title {
  font-size: 28px;
  font-weight: 500;
  line-height: 39.2px;
  letter-spacing: -.28px;
  color: var(--fg);
}
.article-sub { font-size: 16px; line-height: 25.6px; letter-spacing: -.32px; color: var(--muted); }

.byline {
  width: 720px;
  margin-top: 28px;
  padding: 0 16px 0 8px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.byline-who { display: flex; align-items: center; gap: 8px; }
.byline-avatar { width: 28px; height: 28px; border-radius: 8px; object-fit: cover; }
.byline-name { font-size: 14px; line-height: 22.4px; letter-spacing: -.28px; color: var(--fg); }
.byline-date { font-size: 14px; line-height: 22.4px; letter-spacing: -.28px; color: var(--muted); }

.cover-wrap { width: 800px; margin-top: 48px; }
.cover { width: 800px; height: auto; border-radius: 12px; object-fit: contain; display: block; }

.rich {
  width: 720px;
  margin-top: 40px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.rich h1, .rich h2, .rich h3 {
  margin-top: 28px;
  font-family: var(--font-sans);
  font-weight: 400;
  color: var(--fg);
}
.rich h1 { font-size: 48px; line-height: 52.8px; letter-spacing: -1.92px; }
.rich h2 { font-size: 36px; line-height: 43.2px; letter-spacing: -1.44px; }
.rich h3 { font-size: 28px; line-height: 36.4px; letter-spacing: -1.12px; }
.rich > *:first-child { margin-top: 0; }

.rich p, .rich li {
  font-size: 18px;
  line-height: 28.8px;
  letter-spacing: -.36px;
  color: var(--prose);
  /* A bare URL or long unhyphenated word wouldn't otherwise find a break
     point at narrow widths and would push the line past the viewport. */
  overflow-wrap: anywhere;
}
.rich strong { font-weight: 700; color: var(--prose); }
.rich a { color: var(--accent); text-decoration: none; }
.rich a:hover { color: var(--accent-strong); }
.rich ul, .rich ol { padding-left: 26px; display: flex; flex-direction: column; gap: 8px; }
.rich li { list-style: disc; }
.rich ol li { list-style: decimal; }

.rich code {
  font-family: "Fragment Mono", ui-monospace, monospace;
  font-size: 18px;
  color: var(--code-fg);
  background: var(--code-bg);
  padding: 1.8px 3.6px;
  border-radius: 6px;
  overflow-wrap: anywhere;
}

.rich img, .rich video {
  width: 100%;
  height: auto;
  margin-top: 30px;
  border-radius: 8px;
  display: block;
}
/* Body screenshots specifically (not the cover, not the client-review shot's
   own surrounding chrome — those aren't UI to inspect closer) open larger on
   click, since case-study images are usually dense enough that the
   at-column-width version can't show the actual design clearly. */
.rich img { cursor: zoom-in; transition: filter .2s ease; }
.rich img:hover { filter: brightness(1.04); }

/* ============================================================
   EXPLORATION — a bento of silent looping clips
   ============================================================ */
/* The grid is built on a 16:10 cell: --row is a column's width / 1.6, so a 1x1
   tile and a 2x2 tile are both native 16:10 for an ordinary screen recording,
   1x2 is portrait for a phone recording, and 2x1 is a banner. Tiles say which
   they want (.bento-sm / -wide / -tall / -lg) and dense flow backfills gaps. */
.bento-wrap { padding-top: 12px; }
.bento {
  --cols: 4;
  --gap: 16px;
  --w: min(1080px, calc(100vw - 80px));
  --row: calc((var(--w) - (var(--cols) - 1) * var(--gap)) / var(--cols) / 1.6);
  display: grid;
  width: var(--w);
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  grid-auto-rows: var(--row);
  grid-auto-flow: dense;
  gap: var(--gap);
}
.bento-wide { grid-column: span 2; }
.bento-tall { grid-row: span 2; }
.bento-lg   { grid-column: span 2; grid-row: span 2; }

.bento-tile {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  background: var(--surface); /* what shows while a poster is still loading */
}
/* The hairline sits above the video, which would otherwise cover a border. */
.bento-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--line-soft);
  pointer-events: none;
}
.bento-tile video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: pointer;
  transition: transform .6s var(--ease-appear);
}
.bento-tile:hover video { transform: scale(1.03); }
.bento-tile { cursor: pointer; }
.bento-tile:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 3px; }

/* Title and date over the bottom edge, on a scrim just tall enough to read
   against a bright recording. Always visible: touch screens have no hover, and
   a title is half of what makes a clip worth stopping on. */
.bento-cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 44px 18px 16px;
  background: linear-gradient(to top, rgba(4, 4, 8, .62), rgba(4, 4, 8, 0));
  color: #F5F6F8;
  pointer-events: none; /* clicks fall through to the video underneath */
}
.bento-title {
  font-size: 15px;
  font-weight: 500;
  line-height: 20px;
  letter-spacing: -.3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bento-sub { font-size: 12px; line-height: 16px; letter-spacing: -.24px; opacity: .72; }

/* The draft state: quiet empty slots, so the layout can be judged before there
   is anything to put in it. */
.bento-empty { background: var(--raise-soft); }
@media (prefers-reduced-motion: reduce) { .bento-tile video { transition: none; } }
@media (max-width: 680px) {
  /* Still a bento on a phone, just two columns wide. */
  .bento { --cols: 2; --gap: 12px; --w: calc(100vw - 40px); }
  .bento-tile { border-radius: 14px; }
  .bento-cap { padding: 32px 12px 11px; }
  .bento-title { font-size: 13px; line-height: 18px; }
}
/* Four dock keys don't fit a phone at 104px each (4 x 104 = 423px against a
   390px screen), so they share the width. :has() keeps the three-key dock the
   exact size it's always been when there are no explorations to show. */
@media (max-width: 480px) {
  .dock-inner:has(.dock-seg:nth-child(4)) .dock-seg {
    width: min(84px, calc((100vw - 40px) / 4));
  }
}

/* ============================================================
   LIGHTBOX — click a case-study image to see it larger
   ============================================================ */
.lightbox[hidden] { display: none; }
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50; /* above .nav (20) and everything under it */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vh 5vw;
  background: rgba(4, 4, 8, .82);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: zoom-out;
  /* Not [hidden] the instant it closes — app.js removes .is-open first and
     only sets [hidden] once this transition actually finishes, so closing
     fades out instead of just vanishing the way an animation-on-enter-only
     approach would (that has nothing to play in reverse on the way out). */
  opacity: 0;
  transition: opacity .22s ease;
}
.lightbox.is-open { opacity: 1; }
[data-theme="light"] .lightbox { background: rgba(10, 9, 6, .82); }

.lightbox { flex-direction: column; gap: 14px; }
.lightbox-img,
.lightbox-video {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border-radius: 8px;
  box-shadow: 0 24px 64px -16px rgba(0, 0, 0, .6);
  cursor: default; /* the backdrop closes it; the image itself shouldn't invite another click */
  transform: scale(.97);
  transition: transform .22s var(--ease-appear);
}
.lightbox.is-open .lightbox-img,
.lightbox.is-open .lightbox-video { transform: scale(1); }
.lightbox-img[hidden], .lightbox-video[hidden], .lightbox-cap[hidden] { display: none; }
.lightbox-video { min-height: 0; max-height: calc(100% - 40px); cursor: pointer; background: #0b0b0d; }
.lightbox-cap { display: flex; align-items: baseline; gap: 10px; margin: 0; color: #F5F6F8; font-size: 15px; }
.lightbox-cap-title { font-weight: 500; }
.lightbox-cap-sub { color: rgba(245, 246, 248, .6); font-size: 13px; }

.lightbox-close {
  position: fixed;
  top: 20px;
  right: 20px;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: #F5F6F8;
  background: rgba(255, 255, 255, .08);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .16);
  cursor: pointer;
  transition: background .2s ease;
}
.lightbox-close:hover { background: rgba(255, 255, 255, .16); }
.lightbox-close svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; }

@media (prefers-reduced-motion: reduce) {
  .lightbox, .lightbox-img, .lightbox-video { transition: none; }
}

/* Full-width metallic CTA ("Try it out") — three stacked layers:
   rim, drop shadow, brushed face. */
.btn-cta {
  width: 100%;
  height: 40px;
  margin-top: 30px;
  border-radius: 32px;
  background: #F5F6F8;
  box-shadow:
    rgba(247, 250, 255, .6) 0 0 1px 0,
    rgba(5, 5, 5, .1) 0 0 1px 0,
    rgba(5, 5, 5, .04) 0 0 1px 0;
  display: block;
}
.btn-cta-drop {
  display: block;
  height: 100%;
  border-radius: 100px;
  box-shadow:
    rgba(18, 18, 18, .51) 0 6px 4px -3px,
    rgba(5, 5, 5, .25) 7px 15px 5px -3px;
}
.btn-cta-face {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 100%;
  padding: 0 73px;
  border-radius: 32px;
  overflow: hidden;
  background: linear-gradient(129deg, #B3ADAD 0%, #A4AFB0 100%);
  box-shadow:
    rgba(5, 5, 5, .13) 0 0 2px 0 inset,
    rgba(255, 255, 255, .25) 0 0 4px 4px inset,
    #FFFFFF 2px 3px 4px 0 inset,
    rgba(255, 255, 255, .25) 2px 2px 2px 0 inset,
    rgba(0, 0, 0, .25) 0 1px 2px 3px inset;
  transition: filter .2s ease;
}
.btn-cta:hover .btn-cta-face { filter: brightness(1.07); }
.btn-cta:active .btn-cta-face { filter: brightness(.96); }
.btn-cta p {
  position: relative;
  z-index: 1;
  font-size: 16px;
  line-height: 25.6px;
  letter-spacing: -.32px;
  color: #07080A;
}
.btn-cta-shine {
  position: absolute;
  top: 50%;
  left: -30%;
  width: 120px;
  height: 120px;
  transform: translateY(-50%);
  opacity: .45;
  pointer-events: none;
  background: linear-gradient(270deg, rgba(255, 255, 255, .8) 0%, rgba(255, 255, 255, .55) 35%, rgba(255, 255, 255, 0) 100%);
  animation: shine 4.2s ease-in-out infinite;
}

.pager {
  width: 720px;
  margin-top: 64px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
/* Same mint the original uses for its inline links. */
.pager-link {
  font-size: 18px;
  font-weight: 500;
  line-height: 28.8px;
  letter-spacing: -.36px;
  color: var(--accent);
  transition: color .2s ease;
}
.pager-link:hover { color: var(--accent-strong); }

/* ============================================================
   THEME TOGGLE
   ============================================================ */
.theme-toggle {
  width: 32px; height: 32px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--fg);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .22s ease, color .22s ease;
}
.theme-toggle:hover { background: var(--raise); }
.theme-toggle svg {
  width: 19px; height: 19px;
  /* the filled half swings round as the theme flips */
  transition: transform .45s cubic-bezier(.4, 0, .2, 1);
}
[data-theme="light"] .theme-toggle svg { transform: rotate(180deg); }

/* ============================================================
   HAMBURGER + MOBILE MENU
   ============================================================ */
.nav-burger {
  display: none;
  width: 40px; height: 40px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 5px;
  transition: background .22s ease;
}
.nav-burger:hover { background: var(--raise); }
.nav-burger span {
  display: block;
  width: 18px;
  height: 1.5px;
  background: var(--fg);
  border-radius: 2px;
  transition: transform .28s var(--ease-appear), opacity .2s ease;
}
.nav-burger[aria-expanded="true"] span:first-child { transform: translateY(3.25px) rotate(45deg); }
.nav-burger[aria-expanded="true"] span:last-child  { transform: translateY(-3.25px) rotate(-45deg); }

/* Dims the page behind the sheet and gives it something to sit on top of —
   without it the panel just floats over live content, which is most of why
   the old version read as a generic dropdown rather than a considered menu. */
.menu-backdrop[hidden] { display: none; }
.menu-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(4, 4, 8, .55);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  z-index: 15; /* dims the page and the dock; .nav sits above it — see .nav */
  animation: backdrop-in .22s ease;
}
[data-theme="light"] .menu-backdrop { background: rgba(22, 19, 13, .28); }
@keyframes backdrop-in {
  from { opacity: 0; }
}

.mobile-menu[hidden] { display: none; }  /* a class beats the UA [hidden] rule */
.mobile-menu {
  position: absolute;
  top: calc(100% - 4px);
  right: 16px;
  left: 16px;
  /* Deliberately no z-index: it would be scoped inside .nav's stacking
     context and could never clear the backdrop. .nav carries that job. */
  padding: 8px;
  border-radius: 22px;
  background: linear-gradient(180deg, var(--surface) 0%, var(--bg) 160%);
  /* Borrows the dock's vocabulary — a lit top rim plus a hairline edge — so
     the sheet reads as part of the same product rather than a stock dropdown. */
  box-shadow:
    inset 0 1px 0 0 var(--dock-hi),
    inset 0 0 0 1px var(--line-soft),
    0 28px 64px -18px rgba(0, 0, 0, .55);
  display: flex;
  flex-direction: column;
  transform-origin: top right;
  animation: menu-in .3s var(--ease-appear);
}
@keyframes menu-in {
  from { opacity: 0; transform: translateY(-10px) scale(.98); }
}

.mobile-menu-primary { display: flex; flex-direction: column; gap: 2px; }

.mm-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 14px;
  border-radius: 14px;
  font-size: 15px;
  font-weight: 500;
  color: var(--fg);
  transition: background .2s ease, color .2s ease;
}
.mm-link svg { color: var(--muted); transition: color .2s ease; }
.mm-link:hover { background: var(--raise); }
.mm-link.is-active {
  background: var(--raise-soft);
  color: var(--accent-strong);
}
.mm-link.is-active svg { color: var(--accent-strong); }
/* Echoes the desktop nav-pill's current-page dot without needing a second
   element to animate into place. */
.mm-link.is-active::after {
  content: "";
  width: 6px;
  height: 6px;
  margin-left: auto;
  border-radius: 50%;
  background: var(--accent-strong);
}

.mobile-menu-rule { height: 1px; margin: 6px 14px; background: var(--line-soft); }

.mobile-menu-label {
  padding: 8px 14px 2px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.mobile-menu-quick { display: flex; flex-direction: column; gap: 2px; }
.mobile-menu-quick a {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 14px;
  border-radius: 14px;
  font-size: 14px;
  color: var(--fg);
  transition: background .2s ease;
}
.mobile-menu-quick a svg { color: var(--muted); }
.mobile-menu-quick a:hover { background: var(--raise); }

/* The socials are hidden from the desktop-style bar below 680px (the name
   needs the room), so the mobile menu is their only way in short of a trip
   to the footer. */
.mobile-menu-social {
  display: flex;
  justify-content: space-between;
  padding: 4px 10px 6px;
}
.mobile-menu-social a {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--muted);
  transition: background .2s ease, color .2s ease;
}
.mobile-menu-social a:hover { background: var(--raise); color: var(--fg); }

/* Each row cascades in rather than the sheet landing as one flat slab —
   the same "fills in, doesn't just appear" language the rest of the site
   uses for its load animation. */
.mm-link, .mobile-menu-quick a {
  animation: mm-item-in .32s var(--ease-appear) backwards;
}
@keyframes mm-item-in {
  from { opacity: 0; transform: translateY(-6px); }
}
.mobile-menu-primary .mm-link:nth-child(1) { animation-delay: .02s; }
.mobile-menu-primary .mm-link:nth-child(2) { animation-delay: .05s; }
.mobile-menu-primary .mm-link:nth-child(3) { animation-delay: .08s; }
.mobile-menu-primary .mm-link:nth-child(4) { animation-delay: .11s; }
.mobile-menu-quick a:nth-child(1) { animation-delay: .15s; }
.mobile-menu-quick a:nth-child(2) { animation-delay: .18s; }

/* ============================================================
   CONTACT FORM
   ============================================================ */
/* The dock is fixed over the bottom of every page. Everywhere else that just
   covers more copy you can scroll past; here it would sit on the submit
   button, so this page ends well clear of it. */
.page--contact { padding-bottom: 140px; }

/* Single centred column — matches how every other page on the site centres
   one measure of content rather than splitting into a sidebar. */
.contact-wrap {
  width: 560px;
  max-width: 100%;
  margin-top: 28px;
}

/* No card: the form sits directly on the page, same ground as everything
   else, with no panel colour, border or shadow lifting it off. */
.contact-card { background: none; padding: 0; }
.contact-form { display: flex; flex-direction: column; gap: 18px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.field label {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -.26px;
  color: var(--fg);
}
.field-hint { margin-left: 6px; font-weight: 400; color: var(--muted); }

/* Transparent, so the page colour reads straight through — the only thing
   defining a field is the same hairline .btn-dark and .toc use elsewhere. */
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 12px 14px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--line);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 24px;
  transition: box-shadow .2s ease;
}
.field textarea { min-height: 142px; resize: vertical; }
.field input::placeholder,
.field textarea::placeholder { color: var(--muted); }

.field input:hover,
.field select:hover,
.field textarea:hover { box-shadow: inset 0 0 0 1px var(--line-strong); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  box-shadow: inset 0 0 0 1px var(--accent-strong), 0 0 0 3px var(--accent-ring);
}
/* Only after a failed submit — .invalid is set in JS, so the fields aren't
   scolding anyone mid-typing on a form they haven't tried to send yet. */
.field input.invalid,
.field textarea.invalid { box-shadow: inset 0 0 0 1px var(--danger); }

.select-wrap { position: relative; display: block; }
.select-wrap select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 38px;
  cursor: pointer;
}
.select-chev {
  position: absolute;
  top: 50%;
  right: 13px;
  width: 16px;
  height: 16px;
  margin-top: -8px;
  pointer-events: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--muted);
}

/* Bot bait — offscreen rather than display:none, which some bots skip. */
.form-honey {
  position: absolute !important;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.form-status {
  margin-top: -4px;
  padding: 11px 14px;
  border-radius: 10px;
  font-size: 13px;
  line-height: 20px;
  color: var(--danger);
  background: var(--danger-soft);
}
.form-status[hidden] { display: none; }

.form-submit {
  width: 100%;
  height: 48px;
  margin-top: 2px;
  border: 0;
  border-radius: 14px;
  background: var(--fg);
  color: var(--bg);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.2px;
  cursor: pointer;
  transition: transform .18s var(--ease-appear), filter .2s ease, opacity .2s ease;
}
.form-submit:hover { filter: brightness(.9); }
.form-submit:active { transform: scale(.985); }
.form-submit[disabled] { opacity: .55; cursor: progress; transform: none; filter: none; }

.form-fine {
  font-size: 12px;
  line-height: 18px;
  text-align: center;
  color: var(--muted);
}

/* ── Success ── */
.form-done[hidden] { display: none; }
.form-done {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 28px 8px 8px;
  animation: mm-item-in .4s var(--ease-appear);
}
.form-done-mark {
  width: 48px;
  height: 48px;
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--accent-strong);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.form-done-mark svg {
  width: 22px; height: 22px;
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.form-done-title {
  font-family: var(--font-display);
  font-size: 20px;
  font-variation-settings: "opsz" 32;
  color: var(--fg);
}
.form-done-body {
  max-width: 320px;
  font-size: 14px;
  line-height: 22.4px;
  color: var(--prose);
}
.form-done .btn-dark { margin-top: 14px; height: 40px; padding: 0 20px; border-radius: 10px; }

.form-fine a { color: var(--accent); transition: color .2s ease; }
.form-fine a:hover { color: var(--accent-strong); }

/* ============================================================
   MARQUEE (About)
   ============================================================ */
.marquee {
  width: 100%;
  margin-top: 64px;
  overflow: hidden;
  /* Chrome needs the size/repeat longhands alongside the prefixed image, or the
     mask tiles and clips the strip. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}
.marquee-track {
  display: flex;
  width: max-content;
  animation: marquee 78s linear infinite;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-set { display: flex; gap: 16px; padding-right: 16px; }
.marquee-set img,
.marquee-set video {
  width: 280px;
  height: 340px;
  object-fit: cover;
  border-radius: 16px;
  flex: none;
  background: var(--surface); /* shows while a video's first frame decodes */
}
@keyframes marquee {
  to { transform: translateX(-50%); }
}
/* .marquee also carries .reveal for its load-in fade, whose settled state
   (.reveal.is-in) sets opacity: 1 at 2-class specificity. This 3-class
   selector outranks it unconditionally, so the strip still fades in but
   settles at 80% rather than fully opaque. */
.marquee.reveal.is-in { opacity: .8; }

/* ============================================================
   CASE-STUDY SIDEBAR (table of contents)
   ============================================================ */
.toc {
  position: fixed;
  top: 50%;
  left: 40px;
  transform: translateY(-50%);
  width: 200px;
  max-height: 70vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 5;
}
.toc-label {
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: .7;
  padding: 0 10px 8px;
}
.toc a {
  display: block;
  padding: 6px 10px;
  border-left: 1.5px solid var(--line-soft);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
  transition: color .2s ease, border-color .2s ease, background .2s ease;
  border-radius: 0 6px 6px 0;
}
.toc a[data-level="h2"] span,
.toc a[data-level="h3"] span { padding-left: 10px; display: block; }
.toc a:hover { color: var(--fg); background: var(--raise); }
.toc a.is-current {
  color: var(--fg);
  border-left-color: var(--accent);
}

/* ============================================================
   SCROLL REVEAL
   ============================================================ */
/* Framer's appear motion: content rises 150px from opacity 0.001, the nav
   drops in from -60px. Expo-out settles fast then eases, which is what gives
   the original its weight. */
:root { --ease-appear: cubic-bezier(.16, 1, .3, 1); }

/* Blocks settle mostly in place — the original barely travels. */
.reveal {
  opacity: 0;
  transform: translateY(40px);
  transition:
    opacity 1.7s var(--ease-appear),
    transform 1.7s var(--ease-appear);
  transition-delay: var(--stagger, 0ms);
  will-change: opacity, transform;
}
.reveal.is-in { opacity: 1; transform: none; }

/* ── Line-by-line reveal (hero) ──────────────────────────────
   Each rendered line rises out of its own clipping box, one after the next.
   Lines already revealed hold their position — only the incoming one moves. */
/* Hidden until the split runs, otherwise the raw text paints first and then
   vanishes as the line boxes take over. */
.text-reveal { opacity: 0; }
.text-reveal.is-split { opacity: 1; }

.text-reveal .line {
  display: block;
  overflow: hidden;
  padding-bottom: .12em;   /* descenders would otherwise clip */
  margin-bottom: -.12em;
}
.text-reveal .line-inner {
  display: block;
  opacity: 0;
  transform: translateY(105%);
  transition:
    opacity 1.7s var(--ease-appear),
    transform 1.7s var(--ease-appear);
  transition-delay: calc(var(--stagger, 0ms) + var(--li, 0) * 220ms);
  will-change: opacity, transform;
}
.text-reveal.is-in .line-inner { opacity: 1; transform: none; }

/* ── Glitch (page + section headers) ─────────────────────────
   Characters scramble and resolve; a chromatic split rides along and decays. */
/* The split is animated on text-shadow, not transform — .reveal owns transform
   and the two would fight if the glitch fired before the rise finished. */
.scramble.is-glitching {
  animation: glitch-split .44s steps(2, end) infinite; /* half speed of the original .22s */
}
@keyframes glitch-split {
  0%, 100% {
    text-shadow: 1.6px 0 rgba(255, 0, 92, .5), -1.6px 0 rgba(0, 255, 214, .5);
  }
  50% {
    text-shadow: -1.2px 0 rgba(255, 0, 92, .38), 1.2px 0 rgba(0, 255, 214, .38);
  }
}

/* Grid cards cascade rather than landing all at once. */
.grid .reveal:nth-child(3n + 2) { --stagger: 80ms; }
.grid .reveal:nth-child(3n + 3) { --stagger: 160ms; }

/* Body copy rises a shorter distance — 150px per paragraph reads as jitter. */
.rich > .reveal { transform: translateY(40px); transition-duration: .75s; }
.rich > img.reveal, .rich > video.reveal { transform: translateY(60px); }
.rich > .reveal.is-in { transform: none; }

.nav {
  opacity: 0;
  transform: translateY(-60px);
  animation: nav-drop .9s var(--ease-appear) .05s forwards;
}
@keyframes nav-drop {
  to { opacity: 1; transform: none; }
}

.dock {
  animation: dock-rise 1s var(--ease-appear) .35s backwards;
}
@keyframes dock-rise {
  from { opacity: 0; transform: translate(-50%, 90px); }
  to   { opacity: 1; transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .text-reveal, .text-reveal .line-inner { opacity: 1; transform: none; transition: none; }
  .scramble.is-glitching { text-shadow: none; animation: none; }
  .nav { opacity: 1; transform: none; animation: none; }
  .dock { animation: none; }
  .btn-metal-shine, .btn-cta-shine { animation: none; }
  .mobile-menu, .menu-backdrop, .mm-link, .mobile-menu-quick a { animation: none; opacity: 1; transform: none; }
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 1200px) {
  .nav { padding: 16px 40px; }
  .footer { padding: 40px 40px 80px; }
  .footer-inner { width: 100%; }
}

@media (max-width: 1040px) {
  .col-800 { width: 100%; max-width: 600px; }
  .about-body { flex-direction: column; align-items: flex-start; gap: 24px; }
  .about-copy { width: 100%; }
}

/* The sidebar needs the margin beside the 720px column to sit in. */
@media (max-width: 1240px) {
  .toc { display: none; }
}

@media (max-width: 900px) {
  .nav-links { display: none; } /* Contact lives in here too; the mobile menu carries its own entry */
  .nav-role { display: none; }

  /* nav-links/nav-social are hidden, so .nav-right only wraps the theme
     toggle here. margin-left:auto on the burger absorbs the row's free
     space (overriding .nav's justify-content: space-between for this pair),
     pushing burger+toggle to the right edge together; the fixed 12px margin
     sets the gap between them, independent of that. */
  .nav-burger { display: flex; order: 1; margin-left: auto; }
  .nav-right { order: 2; gap: 0; margin-left: 12px; }
}

@media (max-width: 680px) {
  /* Socials live in the footer too — dropping them here gives the name room. */
  .nav { padding: 12px 20px; height: 72px; }
  .nav-social { display: none; }
  .nav-name { font-size: 15px; line-height: 1.3; white-space: nowrap; }
  .avatar { width: 34px; height: 34px; }
  .hero { padding-top: 120px; }
  .col-560, .col-600 { width: 100%; max-width: 100%; padding: 0 20px; }
  /* .col-800 (About) deliberately excluded here — it sits inside .page, which
     already applies this same 20px below, so including it too was doubling
     the side padding to 40px on About specifically, the one page where these
     two wrappers stack. Every other page using .page (Works/Resources/
     Contact) only has the single wrapper, so this was never visible there. */
  .col-800 { width: 100%; max-width: 100%; }
  .hero-title { font-size: 24px; line-height: 33.6px; }
  .works-head { padding: 0; }
  .work-media video { width: 100%; height: auto; aspect-ratio: 5 / 3; }
  .work-meta { padding: 0; }
  .work-meta > div { width: 100%; }
  .works-cta { flex-direction: column; align-items: stretch; }
  .btn-silver, .btn-dark--wide { width: 100%; }
  .about-lead { font-size: 22px; line-height: 30.8px; }
  .footer { padding: 40px 20px 120px; }
  .footer-links { flex-wrap: wrap; gap: 8px; }
  .photo-stack { width: 100%; max-width: 320px; }
  .marquee { margin-top: 40px; }
  .marquee-set img, .marquee-set video { width: 190px; height: 232px; }
  .article-head, .byline, .rich, .pager, .cover-wrap, .back-wrap { width: 100%; padding-left: 20px; padding-right: 20px; }
  .cover { width: 100%; }
  .rich code { font-size: 15px; }

  /* Works / Resources: the fixed 1020px three-column grid was the same width
     at every viewport, so on mobile it just got clipped by body's
     overflow-x: hidden — cards 2 and 3 rendered off-screen with no way to
     reach them. One column, full width, fixed-width cards traded for fluid. */
  .page { padding-left: 20px; padding-right: 20px; }
  .page-head { width: 100%; }
  .page-head-row { flex-wrap: wrap; justify-content: center; text-align: center; row-gap: 4px; }
  .grid-wrap { width: 100%; }
  .grid { grid-template-columns: 1fr; gap: 28px; }
  .gcard { width: 100%; }
  .gcard-media video { width: 100%; }

  /* Name/email and need/timeline stop sharing a row — at this width each half
     is too narrow to type a real email address into. */
  .contact-wrap { margin-top: 20px; }
  .field-row { grid-template-columns: 1fr; gap: 18px; }
  /* 16px keeps iOS from zooming the viewport when a field takes focus. */
  .field input, .field select, .field textarea { font-size: 16px; }
}

/* ============================================================
   TWO-COLUMN GRID (Works, Resources)
   Replaces the original three columns on desktop. It sits last so it wins
   over the three-column rules and the 3n stagger higher up; those base rules
   still matter on phones, which take their tile height from them. Phones
   already get one column from the 680px rules, so this is desktop only.
   ============================================================ */
@media (min-width: 681px) {
  /* Fluid rather than the old fixed 1020px, which overflowed anywhere between
     a phone and ~1100px wide. */
  .grid-wrap { width: min(960px, calc(100vw - 80px)); }
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 56px 40px; }
  .gcard { width: 100%; }
  /* Same proportions as before (320x228 / 320x232), just scaling with the cell
     instead of being fixed pixels. */
  .gcard-media video { width: 100%; height: auto; aspect-ratio: 320 / 228; }
  .gcard--res .gcard-media video { aspect-ratio: 320 / 232; }
  .gcard-title { font-size: 20px; line-height: 28px; letter-spacing: -.4px; }
  .gcard-sub { font-size: 13px; line-height: 18.2px; }
  /* Two columns, so the cascade staggers pairs instead of triples. */
  .grid .reveal:nth-child(n) { --stagger: 0ms; }
  .grid .reveal:nth-child(2n) { --stagger: 100ms; }
}
