/* =========================================================================
   ARC INDEX — Global Design System
   A cultural intelligence practice. Confident silence.
   ========================================================================= */

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Inter+Tight:wght@300;400;500&display=swap');

/* -------------------------------------------------------------------------
   0. Reset
   ------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, p, ul, ol, li, figure, figcaption, blockquote, dl, dd {
  margin: 0; padding: 0;
}
html { -webkit-text-size-adjust: 100%; }
body { min-height: 100vh; }
ul, ol { list-style: none; }
a { color: inherit; text-decoration: none; }
button, input, textarea { font: inherit; color: inherit; background: none; border: none; }
img, video { max-width: 100%; display: block; }
/* Casual-saving deterrents, not real protection — raises friction for
   drag-to-desktop and iOS's long-press "Save Image" menu. Does nothing
   against screenshots, dev tools, or a direct request to the image URL,
   which no CSS or JS can prevent. See theme.js for the matching
   right-click/dragstart handlers. */
img { -webkit-user-drag: none; user-drag: none; -webkit-touch-callout: none; }
button { cursor: pointer; }

/* -------------------------------------------------------------------------
   0.5 View Transitions — same-origin page navigations morph a project's
   hero image directly into the next page's hero instead of a hard cut;
   everything else on the page gets the browser's default cross-fade.
   Degrades to a plain navigation with zero visual difference on browsers
   that don't yet support the cross-document API.
   ------------------------------------------------------------------------- */
@view-transition {
  navigation: auto;
}

::view-transition-group(*) {
  animation-duration: 600ms;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* -------------------------------------------------------------------------
   1. Design Tokens
   ------------------------------------------------------------------------- */
:root {
  /* color */
  --bg: #080808;
  --fg: #F0ECE4;
  --fg-dim: rgba(255, 255, 255, 0.56);
  --fg-faint: rgba(255, 255, 255, 0.28);
  --fg-faint-2: rgba(255, 255, 255, 0.14);
  --surface: #101010;
  --surface-hover: #161616;
  --line: rgba(255, 255, 255, 0.12);
  --line-strong: rgba(255, 255, 255, 0.2);
  /* Blueprint sheets — fixed paper/ink, intentionally not theme-linked */
  --bp-paper: #F6F5F0;
  --bp-ink: #12110E;
  --bp-ink-dim: rgba(18, 17, 14, 0.62);
  --bp-ink-faint: rgba(18, 17, 14, 0.28);

  /* type */
  --font-sans: 'Inter', 'General Sans', -apple-system, sans-serif;
  --font-display: 'Inter Tight', 'Inter', sans-serif;
  --font-mono: 'SF Mono', 'JetBrains Mono', ui-monospace, monospace;

  --text-display: clamp(2.5rem, 6vw, 5.5rem);
  --text-h1: clamp(2rem, 4vw, 3.25rem);
  --text-section: clamp(1.25rem, 2vw, 1.75rem);
  --text-body-lg: clamp(1rem, 1.4vw, 1.25rem);
  --text-body: 1rem;
  --text-small: 0.875rem;
  --text-meta: 0.8125rem;
  --text-micro: 0.6875rem;

  /* motion */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast: 250ms;
  --dur-base: 400ms;
  --dur-slow: 600ms;

  /* layout */
  --sidebar-w: 28vw;
  --sidebar-min: 400px;
  --pad: 2rem;
  --pad-lg: 3rem;
  --radius: 14px;
  --radius-sm: 8px;
  --card-radius: var(--radius-sm);
  /* was the height reserved for the bottom dated-index dock strip, now
     removed — kept at 0 rather than deleting the variable outright,
     since .sidebar/.main/.stage below still reference it in calc() */
  --dock-h: 0px;

  --z-overlay: 100;
  --z-sidebar: 10;
}

@media (max-width: 1023px) {
  :root { --sidebar-min: 320px; --pad: 1.5rem; --pad-lg: 2rem; }
}

/* Light mode — off-white, not pure white; near-black text, not pure black.
   Same restraint principle as dark mode (§2), mirrored. */
html[data-theme="light"] {
  --bg: #FAF9F6;
  --fg: #0A0A0A;
  --fg-dim: rgba(10, 10, 10, 0.62);
  --fg-faint: rgba(10, 10, 10, 0.30);
  --fg-faint-2: rgba(10, 10, 10, 0.16);
  --surface: #EFEEEA;
  --surface-hover: #E2E0D9;
  --line: rgba(10, 10, 10, 0.12);
  --line-strong: rgba(10, 10, 10, 0.22);
}

/* -------------------------------------------------------------------------
   2. Base
   ------------------------------------------------------------------------- */
html {
  background: var(--bg);
  color-scheme: dark;
  transition: background-color var(--dur-slow) var(--ease);
  scrollbar-width: none;
  /* Belt-and-suspenders alongside body's overflow-x:hidden below — iOS
     Safari's horizontal rubber-band/swipe gesture can reveal overflow
     that body-only containment doesn't always catch. overscroll-behavior-x
     and touch-action then remove horizontal panning/bounce as a gesture
     entirely, so scrolling only ever moves the page up and down —
     pinch-zoom stays available for accessibility. */
  overflow-x: hidden;
  overscroll-behavior-x: none;
  touch-action: pan-y pinch-zoom;
}
html[data-theme="light"] { color-scheme: light; }

/* No page scrollbar anywhere on the site — scroll still works (wheel,
   trackpad, keyboard, PageUp/Down), it just never reserves or paints a
   gutter/thumb. Applies to the root scroller; .sidebar__projects and
   .strip__track already hide their own scrollbars entirely too. */
html::-webkit-scrollbar { display: none; width: 0; height: 0; }
body {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--text-body);
  line-height: 1.5;
  background: var(--bg);
  color: var(--fg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  overscroll-behavior-x: none;
  touch-action: pan-y pinch-zoom;
  position: relative;
  transition: background-color var(--dur-slow) var(--ease), color var(--dur-slow) var(--ease);
}

/* subtle print-matter grain — texture, not decoration */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 999;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 300; letter-spacing: -0.02em; }
p { max-width: 60ch; }

.tnum { font-variant-numeric: tabular-nums; }

::selection { background: var(--fg); color: var(--bg); }

a:focus-visible, button:focus-visible, input:focus-visible, [tabindex]:focus-visible {
  outline: 1px solid var(--fg);
  outline-offset: 3px;
}

body.overlay-open { overflow: hidden; }

/* text link primitive — underline/arrow, never a pill */
.link {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  color: var(--fg-dim);
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 2px;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.link:hover, .link:focus-visible { color: var(--fg); border-color: var(--fg); }
.link__arrow { display: inline-block; transition: transform var(--dur-fast) var(--ease); }
.link:hover .link__arrow, .link:focus-visible .link__arrow { transform: translateX(4px); }

.eyebrow {
  font-size: var(--text-meta);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

.meta {
  font-size: var(--text-meta);
  color: var(--fg-dim);
  letter-spacing: 0.02em;
}

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

/* -------------------------------------------------------------------------
   3. App Shell
   ------------------------------------------------------------------------- */
.shell {
  display: flex;
  min-height: 100vh;
  min-height: 100dvh;
}

/* -------------------------------------------------------------------------
   4. Sidebar
   ------------------------------------------------------------------------- */
.sidebar {
  position: fixed;
  top: 0; left: 0; bottom: var(--dock-h);
  width: var(--sidebar-w);
  min-width: var(--sidebar-min);
  max-width: 560px;
  height: calc(100vh - var(--dock-h));
  height: calc(100dvh - var(--dock-h));
  display: flex;
  flex-direction: column;
  padding: var(--pad);
  z-index: var(--z-sidebar);
  border-right: 1px solid var(--line);
  background: var(--bg);
}

.sidebar__utility {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
}

.sidebar__show-all {
  font-size: var(--text-meta);
  letter-spacing: 0.02em;
  color: var(--fg-dim);
  transition: color var(--dur-fast) var(--ease);
}
.sidebar__show-all:hover, .sidebar__show-all:focus-visible { color: var(--fg); }

.toggle {
  position: relative;
  width: 40px;
  height: 22px;
  border: none;
  background: transparent;
  flex-shrink: 0;
}
.toggle::before {
  content: '';
  position: absolute;
  top: 50%; left: 0; right: 0;
  height: 1px;
  background: var(--line-strong);
  transform: translateY(-50%);
}
.toggle__knob {
  position: absolute;
  top: 50%; left: 0;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--fg);
  transform: translate(0, -50%);
  transition: transform var(--dur-fast) var(--ease);
}
.toggle[aria-pressed="true"] .toggle__knob {
  transform: translate(32px, -50%);
}

.sidebar__wordmark {
  flex-shrink: 0;
  margin-top: clamp(1.75rem, 3.5vh, 2.625rem);
  margin-bottom: clamp(3rem, 6vh, 4.5rem);
}
.sidebar__wordmark img {
  width: min(100%, 200px);
  height: auto;
  margin: 0 auto;
  transition: filter var(--dur-slow) var(--ease);
}
html[data-theme="light"] .sidebar__wordmark img { filter: brightness(0); }
.sidebar__wordmark-text {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.5rem;
  letter-spacing: 0.08em;
}

.about-card {
  flex-shrink: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--card-radius);
  padding: 1.25rem 1.375rem;
  margin-bottom: 1.75rem;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
  text-align: left;
  width: 100%;
  display: block;
}
.about-card:hover, .about-card:focus-visible { background: var(--surface-hover); border-color: var(--line-strong); }
.about-card__label {
  font-size: var(--text-meta);
  color: var(--fg);
  margin-bottom: 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.4em;
}
.about-card__label .link__arrow { transition: transform var(--dur-fast) var(--ease); }
.about-card:hover .about-card__label .link__arrow { transform: translateX(4px); }
.about-card__text {
  font-size: var(--text-small);
  color: var(--fg-dim);
  line-height: 1.5;
}

/* Fills the rest of the sidebar's height instead of a fixed 210px box —
   all featured projects show at once, no internal scroll/fade-mask
   hinting at hidden entries below. overflow-y:auto stays only as a
   fallback for a genuinely short viewport, not the normal case. */
.sidebar__projects {
  flex: 1 1 auto;
  overflow-y: auto;
  margin: 0 -0.75rem;
  padding: 0 0.75rem;
  scrollbar-width: none;
}
.sidebar__projects::-webkit-scrollbar { display: none; width: 0; height: 0; }

.project-card {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 0.875rem;
  border-radius: var(--card-radius);
  transform: scale(1);
  transition: background var(--dur-base) var(--ease), transform var(--dur-base) var(--ease);
  cursor: pointer;
}
.project-card:hover, .project-card:focus-visible, .project-card.is-active {
  background: var(--surface-hover);
  transform: scale(1.015);
}
.project-card__thumb {
  flex-shrink: 0;
  width: 44px; height: 44px;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  overflow: hidden;
  color: var(--bg);
  line-height: 1.1;
  text-align: center;
}
.project-card__thumb--img img { width: 100%; height: 100%; object-fit: cover; object-position: 58% 46%; display: block; }
/* MELT + Agora's hero is a centered mark lockup, not an off-center photo — the
   shared 58%/46% crop above was tuned for the other case studies' imagery. */
.project-card[data-slug="melt-agora-records"] .project-card__thumb--img img { object-position: center; }
.project-card__body { min-width: 0; }
.project-card__name {
  font-size: var(--text-small);
  font-weight: 500;
  color: var(--fg);
  margin-bottom: 0.15rem;
  display: flex;
  align-items: center;
  gap: 0.5em;
}
/* shared small tag/label — hairline border, modest radius, never a pill
   (pill/stadium shape is reserved for commerce actions, see .pill-btn) */
.tag, .project-card__concept {
  font-family: 'SF Mono', 'JetBrains Mono', ui-monospace, monospace;
  font-size: var(--text-micro);
  color: var(--fg-dim);
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  padding: 1px 5px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  flex-shrink: 0;
}
.project-card__desc {
  font-size: var(--text-micro);
  color: var(--fg-dim);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* -------------------------------------------------------------------------
   5. Main / Visual Stage / Dated Strip
   ------------------------------------------------------------------------- */
.main {
  margin-left: var(--sidebar-w);
  width: calc(100% - var(--sidebar-w));
  min-width: calc(100% - 560px);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  padding-bottom: var(--dock-h);
  position: relative;
}
@media (min-width: 1024px) {
  .main { margin-left: max(var(--sidebar-w), var(--sidebar-min)); width: auto; flex: 1 1 auto; }
}

.stage {
  position: relative;
  flex: 1 1 auto;
  /* .stage is the only child of .main now, so this exactly fills the
     viewport (100vh - strip) with nothing to scroll to below it. */
  min-height: calc(100vh - var(--dock-h));
  min-height: calc(100dvh - var(--dock-h));
  overflow: hidden;
  background: var(--surface);
}

.stage__layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  cursor: pointer;
  /* Stacked layers have no z-index, so without this, later siblings in
     DOM order paint (and hit-test) above earlier ones regardless of
     which is actually visible — opacity:0 hides a layer but doesn't
     stop it from intercepting clicks meant for the one underneath it.
     Only the active layer should ever be clickable. */
  pointer-events: none;
}
.stage__layer.is-active { opacity: 1; pointer-events: auto; }
.stage__field {
  position: absolute;
  inset: -6%;
  animation: kenburns 32s ease-in-out infinite alternate;
}
.stage__field.is-static-safe { animation: none; }

.stage__heroimg { position: absolute; inset: 0; }
.stage__heroimg img,
.stage__heroimg video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Promotes the playing hero video to its own GPU compositing layer, so a
   scroll/gesture-driven repaint of the page around it doesn't have to
   repaint the video's own pixels too — one more layer of defense against
   the stall a wheel/trackpad gesture can otherwise cause (see the
   wheel/touchmove handling on #stage in app.js). */
.stage__heroimg video { transform: translateZ(0); will-change: transform; }
.stage__layer--hero-img .stage__index { display: none; }
.stage__layer--hero-img .stage__caption { display: none; }

/* Temma's stage layer reuses .tc-video__frame (see the case study's
   own video block below) for its controls bar and hover-fade styling —
   but as a full-bleed stage layer it needs to stay absolutely positioned
   edge-to-edge rather than adopt that class's own 16:9/max-height box
   model, hence the override immediately after it in source order. */
.stage__heroimg.tc-video__frame {
  position: absolute; inset: 0; width: 100%; height: 100%;
  aspect-ratio: auto; max-height: none;
}

/* Named to match each case study's hero <img>/<video> (set inline on
   that page) — identical view-transition-name on both sides is what
   tells the browser to morph this element into that one instead of
   cross-fading the whole page. Harmless on every other project pairing
   since names just won't match. Video-hero stage layers (any project
   with p.stageVideo in data.js) get vt-hero-{slug} set inline directly
   in stageLayerHTML in app.js, not here — image-hero layers still use
   this per-slug selector since their markup has no per-project inline
   style to hook. */
.stage__layer[data-slug="kwame-adusei"] .stage__heroimg img { view-transition-name: vt-hero-kwame-adusei; }

.stage__caption {
  position: absolute;
  right: clamp(1.5rem, 4vw, 4rem);
  bottom: clamp(1.5rem, 4vw, 4rem);
  left: clamp(1.5rem, 4vw, 4rem);
  z-index: 2;
}

.stage__wordmark {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.75rem, 4vw, 3.5rem);
  letter-spacing: -0.02em;
  line-height: 1.02;
  color: var(--bg);
  mix-blend-mode: normal;
  /* Project names are now stored in title case (assets/js/data.js) to
     match each case study's own heading — this display, unlike
     .project-card__name, is meant to stay all-caps, so it needs its
     own explicit transform now that the source text isn't. */
  text-transform: uppercase;
}

.stage__index {
  position: absolute;
  top: clamp(-2rem, -3vw, -1rem);
  right: clamp(-1rem, -2vw, 0.5rem);
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(8rem, 22vw, 18rem);
  line-height: 1;
  color: var(--bg);
  opacity: 0.18;
  letter-spacing: -0.03em;
  z-index: 1;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}

.stage__diagram { position: absolute; inset: 0; z-index: 1; opacity: 0.5; pointer-events: none; }

.stage__empty {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--fg-dim);
  font-size: var(--text-meta);
}

.stage__hint {
  margin-top: 0.6rem;
  font-size: var(--text-meta);
  color: var(--bg);
  opacity: 0.6;
}

/* scroll cue — bare double-chevron, no scrim/shadow/card chrome at all.
   Centered at the bottom of the hero (the standard placement for this
   pattern — bottom-right is conventionally reserved for back-to-top/chat
   affordances, not "keep scrolling" cues). The two chevrons glow via an
   opacity pulse, staggered so it reads as a downward flow — no position
   movement/bounce, which was rejected in an earlier pass. */
.stage__scroll-cue {
  position: absolute;
  left: 50%; bottom: 0;
  /* The scrubber/play/fullscreen controls that once shared this bottom
     strip are gone (case study hero videos are ambient-only now — just
     a mute/volume pill at the right edge, see .tc-video__controls), so
     the cue is free to sit flush at the bottom again, its original
     position. */
  transform: translateX(-50%);
  z-index: 3;
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 56px;
  background: none;
  border: none; padding: 0; margin: 0;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.94);
  transition: opacity var(--dur-slow) var(--ease), color var(--dur-slow) var(--ease);
}
.stage__scroll-cue.is-hidden { opacity: 0; pointer-events: none; }
/* flipped automatically when the active hero's bottom-center patch
   (sampled live via canvas in app.js) is bright enough that white
   chevrons would wash out against it */
.stage__scroll-cue.is-light { color: rgba(10, 10, 10, 0.82); }
.stage__scroll-chevron { opacity: 0.32; animation: stage-scroll-glow 1.8s ease-in-out infinite; }
.stage__scroll-chevron--2 { animation-delay: 0.3s; }
@keyframes stage-scroll-glow {
  0%, 100% { opacity: 0.32; }
  45% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .stage__scroll-chevron { animation: none; opacity: 0.85; }
}

@keyframes kenburns {
  from { transform: scale(1); }
  to { transform: scale(1.06); }
}

/* -------------------------------------------------------------------------
   6. Overlays (Index / About) — full-screen channel swap
   ------------------------------------------------------------------------- */
.overlay {
  position: fixed;
  inset: 0;
  left: 0;
  z-index: var(--z-overlay);
  background: var(--bg);
  opacity: 0;
  transform: scale(0.985);
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease), transform var(--dur-base) var(--ease);
  display: flex;
  flex-direction: column;
}
.overlay.is-open {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}
/* About overlay keeps the sidebar instrument panel visible beside it —
   the Index overlay is truly full-bleed and replaces the sidebar's column. */
@media (min-width: 768px) {
  .overlay--pane { left: max(var(--sidebar-w), var(--sidebar-min)); }
}

/* About panel content — each top-level section fades up into place in a
   quiet top-to-bottom cascade on open, instead of appearing all at once.
   Delay lives on the section itself (not gated behind .is-open) so the
   cascade replays correctly every time the panel reopens. */
.about__section {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 480ms var(--ease), transform 480ms var(--ease);
}
.overlay--pane.is-open .about__section { opacity: 1; transform: translateY(0); }
.about__section:nth-of-type(1) { transition-delay: 90ms; }
.about__section:nth-of-type(2) { transition-delay: 180ms; }
.about__section:nth-of-type(3) { transition-delay: 270ms; }
.about__section:nth-of-type(4) { transition-delay: 360ms; }

@media (prefers-reduced-motion: reduce) {
  .about__section {
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    transform: none !important;
  }
}

.overlay__search {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--line);
  padding: 0 var(--pad-lg);
  height: 56px;
}
.overlay__search-cursor { color: var(--fg-faint); margin-right: 0.6rem; font-weight: 300; }
.overlay__search-input {
  flex: 1;
  height: 100%;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1rem, 1.6vw, 1.25rem);
  color: var(--fg);
  letter-spacing: -0.01em;
  outline: none;
}
.overlay__search-input:focus, .overlay__search-input:focus-visible { outline: none; }
.overlay__search-input::placeholder { color: var(--fg-faint); }
.overlay__search-submit {
  display: flex; align-items: center; justify-content: center;
  height: 100%;
  color: var(--fg-dim);
  font-size: 1.2rem;
  padding: 0.5rem;
  transition: transform var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.overlay__search-submit:hover { color: var(--fg); transform: translateX(3px); }
.overlay__close {
  display: flex; align-items: center; justify-content: center;
  height: 100%;
  color: var(--fg-dim);
  font-size: 1.4rem;
  font-weight: 300;
  padding: 0.5rem 0 0.5rem 1.5rem;
  margin-left: 1rem;
  border-left: 1px solid var(--line);
  transition: color var(--dur-fast) var(--ease);
}
.overlay__close:hover, .overlay__close:focus-visible { color: var(--fg); }

.overlay__body {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
}

.overlay__filters {
  flex-shrink: 0;
  width: 220px;
  padding: 1.9rem var(--pad) var(--pad-lg) calc(var(--pad) + 1rem);
  overflow-y: auto;
  border-right: 1px solid var(--line);
}
.overlay__filter-item {
  position: relative;
  display: flex;
  align-items: baseline;
  width: 100%;
  text-align: left;
  font-size: var(--text-small);
  color: var(--fg-dim);
  padding: 0.6rem 0.25rem;
  margin-left: -0.75rem;
  padding-left: 0.75rem;
  border-radius: var(--radius-sm);
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.overlay__filter-item::before {
  content: '';
  position: absolute;
  left: -1rem; top: 50%;
  width: 2px; height: 0;
  background: var(--fg);
  transform: translateY(-50%);
  transition: height var(--dur-fast) var(--ease);
}
.overlay__filter-item:hover, .overlay__filter-item:focus-visible { color: var(--fg); background: var(--surface); }
.overlay__filter-item.is-active { color: var(--fg); font-weight: 500; }
.overlay__filter-item.is-active::before { height: 16px; }
.overlay__filter-label { flex: 1 1 auto; min-width: 0; }

.overlay__grid-wrap { flex: 1 1 auto; overflow-y: auto; }

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 23vw;
  grid-auto-flow: dense;
  gap: 2rem;
  padding: var(--pad-lg);
  padding-top: 2.5rem;
}

.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  opacity: 1;
  transform: scale(1);
  transition: opacity var(--dur-base) var(--ease), transform var(--dur-base) var(--ease);
}
.tile.is-hidden { opacity: 0; transform: scale(0.96); pointer-events: none; position: absolute; visibility: hidden; }

/* Full Index entrance — same top-to-bottom fade-up cascade as the About
   panel, but strictly one-shot: gated behind :not(.is-entered), which
   app.js adds to #overlay-index once the cascade finishes and removes
   again on close. Without this gate, every later search/category
   filter click would replay the same translateY delay on newly-shown
   tiles — a distracting "bump" instead of the plain, instant fade
   .tile.is-hidden already handles below. */
#overlay-index:not(.is-entered) .tile:not(.is-hidden) { opacity: 0; transform: translateY(14px); }
#overlay-index.is-open:not(.is-entered) .tile:not(.is-hidden) { opacity: 1; transform: translateY(0); }
#overlay-index:not(.is-entered) .tile:nth-child(1) { transition-delay: 60ms; }
#overlay-index:not(.is-entered) .tile:nth-child(2) { transition-delay: 115ms; }
#overlay-index:not(.is-entered) .tile:nth-child(3) { transition-delay: 170ms; }
#overlay-index:not(.is-entered) .tile:nth-child(4) { transition-delay: 225ms; }
#overlay-index:not(.is-entered) .tile:nth-child(5) { transition-delay: 280ms; }
#overlay-index:not(.is-entered) .tile:nth-child(6) { transition-delay: 335ms; }

@media (prefers-reduced-motion: reduce) {
  #overlay-index:not(.is-entered) .tile {
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    transform: none !important;
  }
}
.tile--sq { grid-column: span 2; grid-row: span 1; }
.tile--tall { grid-column: span 2; grid-row: span 2; }
.tile--wide { grid-column: span 4; grid-row: span 1; }

.tile__media {
  position: relative;
  flex: 1 1 auto;
  border-radius: var(--radius-sm);
  overflow: hidden;
  transition: filter var(--dur-fast) var(--ease);
}
.tile__media img { transition: transform 700ms cubic-bezier(0.16, 1, 0.3, 1); }
.tile:hover .tile__media, .tile:focus-visible .tile__media { filter: brightness(1.08); }
.tile:hover .tile__media img, .tile:focus-visible .tile__media img { transform: scale(1.045); }
@media (prefers-reduced-motion: reduce) {
  .tile__media img { transition: none; }
}
.tile__media--blueprint { background: var(--bp-paper); }
.tile__media--blueprint svg { width: 100%; height: 100%; display: block; }
.tile__media--blueprint img { width: 100%; height: 100%; display: block; object-fit: cover; }
.tile__media--photo img { width: 100%; height: 100%; display: block; object-fit: cover; }
/* Temma's wide tile uses the same kitchen island shot as the
   homepage hero, but the tile's box is much shorter and wider than the
   hero frame — the hero's own 68% bias crops too far down here, cutting
   off the sculptural bowl sitting on the island. A lighter bias keeps
   the whole bowl in frame. */
.tile[data-slug="temma"] .tile__media--photo img { object-position: center 48%; }

.tile__index {
  position: absolute;
  bottom: -0.4rem; right: -0.2rem;
  font-family: var(--font-display);
  font-size: clamp(3rem, 8vw, 6rem);
  color: var(--bg);
  opacity: 0.2;
  font-weight: 300;
  z-index: 1;
}
.tile__wordmark {
  position: absolute; left: 1rem; bottom: 1rem; right: 1rem;
  z-index: 2;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(0.95rem, 1.6vw, 1.4rem);
  color: var(--bg);
  line-height: 1.05;
  /* See .stage__wordmark — project names are stored in title case now,
     but this Full Index grid display stays all-caps by design. */
  text-transform: uppercase;
}
.tile__foot {
  padding-top: 1rem;
  flex-shrink: 0;
}
.tile__title { font-size: var(--text-small); color: var(--fg); font-weight: 500; display: flex; align-items: center; gap: 0.5em; }
.tile__desc { font-size: var(--text-micro); color: var(--fg-dim); margin-top: 0.35rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

.grid__empty {
  padding: 4rem var(--pad-lg);
  color: var(--fg-dim);
  font-size: var(--text-small);
  grid-column: 1 / -1;
}

/* -------------------------------------------------------------------------
   7. About
   ------------------------------------------------------------------------- */
.about {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
  overflow: hidden;
}
.about__center {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 0 0 6rem;
}

.about__hero {
  height: 46vh;
  min-height: 320px;
  position: relative;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
}
.about__hero-field { position: absolute; inset: 0; }
.about__hero-caption {
  position: absolute; left: var(--pad-lg); bottom: 1.5rem;
  font-size: var(--text-meta); color: var(--bg); opacity: 0.7;
}

.about__section {
  padding: 3.5rem var(--pad-lg) 0;
  max-width: 1100px;
}
.about__label { font-size: var(--text-meta); color: var(--fg-dim); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 1.25rem; }

.about__statement { max-width: 68ch; }
.about__statement p { font-size: var(--text-body-lg); line-height: 1.55; color: var(--fg); margin-bottom: 1.1rem; }
.about__statement p:last-child { margin-bottom: 0; }

/* Horizontal row, spanning the same width as the founder card below it —
   two columns (General Inquiries / Social) separated by a hairline,
   framed top+bottom as one unit. Columns size to their own content
   rather than stretching to fill the row, so the pair reads as a
   compact, deliberate pairing instead of two overstretched halves. */
.contact-pairs--row {
  display: flex; flex-wrap: nowrap;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  margin-top: 2.5rem; padding: 1.75rem 0;
}
.contact-pairs--row .contact-pairs__item { flex: 0 1 auto; min-width: 0; padding: 0 2rem; }
.contact-pairs--row .contact-pairs__item:first-child { padding-left: 0; }
.contact-pairs--row .contact-pairs__item:last-child { padding-right: 0; }
.contact-pairs--row .contact-pairs__item + .contact-pairs__item { border-left: 1px solid var(--line); }
.contact-pairs__label { display: block; font-size: var(--text-meta); color: var(--fg-dim); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.4rem; }
.contact-pairs__value { font-size: var(--text-small); color: var(--fg); overflow-wrap: anywhere; }
.contact-pairs__value-row { display: flex; align-items: center; gap: 0.5rem; }
.contact-pairs__icon { width: 20px; height: 20px; flex-shrink: 0; color: var(--fg-dim); }

.team-grid { display: grid; grid-template-columns: 1fr; }
.team-card { display: flex; flex-wrap: wrap; align-items: flex-start; }
.team-card__body { flex: 1 1 300px; }
.team-card__name { font-family: var(--font-display); font-size: var(--text-section); font-weight: 400; margin-bottom: 4px; }
.team-card__role { font-size: var(--text-meta); color: var(--fg); margin-bottom: 10px; letter-spacing: 0.02em; }
.team-card__bio { font-size: var(--text-small); color: var(--fg-dim); line-height: 1.6; max-width: none; margin-bottom: 1rem; }
.team-card__bio:last-child { margin-bottom: 0; }

.team-card__bio-toggle {
  display: inline-flex; align-items: center; gap: 0.4em;
  font: inherit; font-size: var(--text-small); color: var(--fg-dim);
  background: none; border: none; border-bottom: 1px solid var(--line-strong);
  padding: 0 0 2px; cursor: pointer; margin-bottom: 10px;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.team-card__bio-toggle:hover, .team-card__bio-toggle:focus-visible { color: var(--fg); border-color: var(--fg); }
.team-card__bio-toggle .link__arrow { display: inline-block; transition: transform var(--dur-fast) var(--ease); }
.team-card__bio-toggle:hover .link__arrow, .team-card__bio-toggle:focus-visible .link__arrow { transform: translateX(4px); }

.team-card__bio-full { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-base) var(--ease); }
.team-card__bio-full.is-expanded { grid-template-rows: 1fr; }
.team-card__bio-full-inner { overflow: hidden; min-height: 0; opacity: 0; transition: opacity var(--dur-fast) var(--ease); }
.team-card__bio-full.is-expanded .team-card__bio-full-inner { opacity: 1; transition-delay: 100ms; }
.team-card__bio-full-inner > .team-card__bio:first-child { margin-top: 0; }
.team-card__tagline {
  font-style: italic; font-size: var(--text-small); color: var(--fg-dim);
  letter-spacing: 0.04em; margin-bottom: 1rem;
}
.team-card__portfolio-link { margin-top: 0; }
#team-bio-full.is-expanded ~ .team-card__portfolio-link { margin-top: 1rem; }

.offerings__tabs { display: flex; flex-wrap: wrap; gap: 0 1.75rem; margin-bottom: 1.5rem; }
.offerings__tab {
  font-family: inherit; font-size: var(--text-small); color: var(--fg-dim); background: none; border: none;
  padding: 0.4rem 0; position: relative; cursor: pointer; transition: color var(--dur-fast) var(--ease);
}
.offerings__tab:hover, .offerings__tab:focus-visible { color: var(--fg); }
.offerings__tab.is-primary { color: var(--fg); }
.offerings__tab.is-primary::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--fg);
}
.about__section p.body-copy { font-size: var(--text-body); color: var(--fg-dim); line-height: 1.6; max-width: 68ch; }
.offerings__detail {
  border: 1px solid var(--line); border-radius: var(--radius); padding: 1.75rem 2rem;
}
.offerings__detail #offerings-detail-label { display: block; margin-bottom: 0.75rem; }
.offerings__detail p.body-copy { max-width: none; margin: 0; }

.client-filter { display: flex; flex-wrap: wrap; gap: 0 1.5rem; margin-bottom: 1.5rem; border-bottom: 1px solid var(--line); padding-bottom: 1.25rem; }
.client-filter button { font-size: var(--text-small); color: var(--fg-dim); padding: 0.3rem 0; }
.client-filter button.is-active { color: var(--fg); }
.client-index { columns: 2; column-gap: 3rem; }
@media (max-width: 767px) { .client-index { columns: 1; } }
.client-index li { break-inside: avoid; padding: 0.55rem 0; font-size: var(--text-small); color: var(--fg); border-top: 1px solid var(--line); }

.event-item { padding: 1rem 0; border-top: 1px solid var(--line); }
.event-item:last-child { border-bottom: 1px solid var(--line); }
.event-item__title { font-size: var(--text-small); color: var(--fg); margin-bottom: 0.3rem; }
.event-item__meta { font-size: var(--text-micro); color: var(--fg-dim); }

.merch-card { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.merch-card__img { height: 160px; position: relative; }
.merch-card__body { padding: 1.1rem 1.25rem; }
.merch-card__name { font-size: var(--text-small); font-weight: 500; margin-bottom: 0.2rem; }
.merch-card__price { font-size: var(--text-meta); color: var(--fg-dim); margin-bottom: 1rem; }
.pill-btn {
  display: inline-block;
  background: var(--fg);
  color: var(--bg);
  font-size: var(--text-meta);
  font-weight: 500;
  padding: 0.6rem 1.25rem;
  border-radius: 999px;
  transition: opacity var(--dur-fast) var(--ease);
}
.pill-btn:hover, .pill-btn:focus-visible { opacity: 0.82; }

/* -------------------------------------------------------------------------
   8. Standalone pages (project / dispatch / journal / contact) shell
   ------------------------------------------------------------------------- */
.page {
  min-height: 100vh;
  min-height: 100dvh;
}
.page__nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.5rem var(--pad-lg);
  opacity: 0.6;
}
.page__nav-back { font-size: var(--text-meta); color: #FFFFFF; display: flex; align-items: center; gap: 0.5em; transition: color var(--dur-slow) var(--ease); }
.page__nav-back .link__arrow { display: inline-block; transform: rotate(180deg); transition: transform var(--dur-fast) var(--ease); }
.page__nav-back:hover .link__arrow { transform: rotate(180deg) translateX(4px); }
.page__nav-word { width: 120px; height: auto; display: block; transition: filter var(--dur-slow) var(--ease); }
html[data-theme="light"] .page__nav-back { color: #0A0A0A; }
html[data-theme="light"] .page__nav-word { filter: brightness(0); }

/* On light-background inserts within an otherwise-dark page (e.g. the
   Temma map, a warm-paper document treatment) the fixed nav needs
   to flip dark too, or white-on-white becomes illegible. Toggled by
   scroll position — see initNavAdaptiveColor in util.js. */
.page__nav.is-on-light .page__nav-back { color: #0A0A0A; }
.page__nav.is-on-light .page__nav-word { filter: brightness(0); }

/* Mirror case for light-mode theme: the page background is light so the
   nav defaults dark (above), but the photo/video hero at the top of every
   case study is still visually dark media — same adaptive check, opposite
   direction, overriding the light-theme default back to white while the
   nav overlaps that hero. */
html[data-theme="light"] .page__nav.is-on-dark .page__nav-back { color: #FFFFFF; }
html[data-theme="light"] .page__nav.is-on-dark .page__nav-word { filter: none; }

/* Tim Vo's standalone portfolio — text wordmark in place of the ARC
   INDEX image mark, same nav shell/weight as every other page. */
.tv-wordmark { background: none; border: none; padding: 0; cursor: pointer; line-height: 0; }
.tv-wordmark__img { height: 11px; width: auto; display: block; }
html[data-theme="light"] .tv-wordmark__img { filter: brightness(0); }

/* Email, bottom-right — same distance from the bottom/right edges as
   .tv-wordmark is from the top/left (.page__nav's own 1.5rem / --pad-lg
   padding), just anchored to the opposite corner instead of sitting in
   the nav bar itself. */
.tv-email-corner {
  position: fixed; right: var(--pad-lg); bottom: 1.5rem; z-index: 50;
  font-size: var(--text-meta); font-weight: 600; color: #FFFFFF; opacity: 0.6;
  text-decoration: none; transition: opacity var(--dur-slow) var(--ease);
}
.tv-email-corner:hover, .tv-email-corner:focus-visible { opacity: 1; }
html[data-theme="light"] .tv-email-corner { color: #0A0A0A; }

/* Photo/Film nav tabs — jump links, not a filter: the page is one
   continuous scroll either way, these just smooth-scroll to the
   matching section (see initNavTabs in timvo.js). Same type scale as
   .tv-wordmark, sitting in the nav's own right-hand slot. */
.tv-nav-tabs { display: flex; align-items: center; gap: 0.5em; pointer-events: auto; }
.tv-nav-tab {
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--font-sans); font-size: var(--text-meta); font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: #FFFFFF; opacity: 0.36;
  transition: opacity var(--dur-fast) var(--ease);
}
.tv-nav-tab:hover, .tv-nav-tab:focus-visible, .tv-nav-tab.is-active { opacity: 1; }
.tv-nav-tab__divider { font-size: var(--text-meta); color: #FFFFFF; opacity: 0.18; }
/* .page__nav's own opacity: 0.6 (shared, site-wide) would otherwise
   dim the TIM VO wordmark along with everything else in the nav — the
   user wants that wordmark pure white specifically. Undo it just for
   Tim Vo's pages and bake the equivalent dimming directly into
   .tv-nav-tab/.tv-nav-tab__divider's own opacity above (0.6*0.6=0.36,
   0.6*0.3=0.18) so they read exactly as before; only the wordmark
   actually changes. */
.tv-nav { opacity: 1; }

/* Social, bottom-left — same distance from the bottom/left edges as
   .tv-wordmark is from the top/left, so all four corners (wordmark,
   nav tabs, social, email) share one consistent margin. */
.tv-social-corner {
  position: fixed; left: var(--pad-lg); bottom: 1.5rem; z-index: 50;
  display: flex; align-items: center; gap: 1rem;
}
.tv-social-corner a { color: #FFFFFF; opacity: 0.6; display: flex; transition: opacity var(--dur-fast) var(--ease); }
.tv-social-corner a:hover, .tv-social-corner a:focus-visible { opacity: 1; }
.tv-social-corner svg { display: block; }
html[data-theme="light"] .tv-social-corner a { color: #0A0A0A; }
html[data-theme="light"] .tv-nav-tab { color: #0A0A0A; }
html[data-theme="light"] .tv-nav-tab__divider { color: #0A0A0A; }

/* Adaptive corners — each of the four fixed corner elements (wordmark,
   nav tabs, social icons, email) samples the actual pixel luminance of
   whatever photo or video is scrolled beneath it and flips to near-
   black whenever that's a light surface, white otherwise — see
   initAdaptiveCorners in timvo.js. A quick color/filter transition
   (not the slower opacity one above) so it keeps up with scroll speed. */
.tv-wordmark__img,
.tv-nav-tab, .tv-nav-tab__divider,
.tv-social-corner a,
.tv-email-corner { transition: color 120ms linear, filter 120ms linear, opacity var(--dur-fast) var(--ease); }
.tv-wordmark.is-on-light .tv-wordmark__img,
.tv-nav-tabs.is-on-light .tv-nav-tab, .tv-nav-tabs.is-on-light .tv-nav-tab__divider,
.tv-social-corner.is-on-light a,
.tv-email-corner.is-on-light { color: #0A0A0A; filter: brightness(0); }

/* Photography & Videography archive — same tc-excerpt → view-toggle →
   masonry grid structure as every case study page, not a page-specific
   layout. #page-root just adds clearance for the fixed nav, since this
   page (unlike the case studies) has no hero image above the excerpt. */
.ph-empty { font-size: var(--text-small); color: var(--fg-dim); padding: 3rem var(--pad-lg) 4rem; border-top: 1px solid var(--line); max-width: 52ch; margin: 0 auto; }

/* Contact page entrance — a slow, quiet rise-and-fade on load, each
   element lagging the one before it by a beat (see the inline
   transition-delay values in timvo-contact.html) so the page arrives
   as one unhurried gesture rather than all at once. The curve
   (cubic-bezier(0.19,1,0.22,1), "expo-out") front-loads nearly all of
   the motion into the first third of the duration then spends the
   rest settling almost imperceptibly -- the restrained, unhurried
   feel this calls for, not a bounce or a snap. JS (timvo-contact.js)
   adds .is-in after the first paint; see initEntrance(). */
.tv-contact-fade { opacity: 0; transform: translateY(14px); transition: opacity 1300ms cubic-bezier(0.19, 1, 0.22, 1), transform 1300ms cubic-bezier(0.19, 1, 0.22, 1); }
.tv-contact-fade.is-in { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .tv-contact-fade { transition: opacity 200ms linear; transform: none; }
}

/* Contact page — same near-black background, white text, and corner
   styling as the rest of the Tim Vo site (no light-theme override):
   name/bio + contact block on the left, square portrait on the right,
   world clocks along the bottom. Fixed pixel values throughout (no vh
   centering, no vw-scaled sizing) so the composition stays pinned in
   place — expanding the window just reveals more background, it never
   re-centers or rescales the content. */
.tv-contact {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  padding: 90px var(--pad-lg) 64px;
  max-width: 1400px;
  box-sizing: border-box;
}
/* The text block centers in the space ABOVE the clock row (auto
   margins on a flex child pull it to the middle of whatever room is
   left), rather than being centered as one group together with the
   clocks -- that grouped approach was pulling the actual bio text
   below true center, since the clock row's own height was part of
   what got balanced. Clocks stay put near the bottom, footer-style. */
.tv-contact__grid { display: flex; justify-content: space-between; align-items: flex-start; gap: 80px; flex-wrap: wrap; margin-top: auto; margin-bottom: auto; }
.tv-contact__text { max-width: 100%; }
.tv-contact__intro { font-size: 14px; font-weight: 400; line-height: 1.6; letter-spacing: 0.08em; text-transform: uppercase; color: #E8E6E1; margin: 0 0 3rem; }
.tv-contact__block { margin-bottom: 2.25rem; }
.tv-contact__block:last-child { margin-bottom: 0; }
.tv-contact__label { font-family: var(--font-sans); font-size: var(--text-meta); font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg); margin-bottom: 0.4rem; }
.tv-contact__value { font-size: var(--text-small); letter-spacing: 0.02em; color: var(--fg-dim); display: inline-block; }
.tv-contact__value:hover, .tv-contact__value:focus-visible { color: var(--fg); text-decoration: underline; }

.tv-contact__photo { width: 230px; aspect-ratio: 1 / 1; flex-shrink: 0; box-sizing: border-box; }
.tv-contact__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tv-contact__photo.is-empty { background: rgba(255, 255, 255, 0.06); }
.tv-contact__photo.is-empty img { display: none; }

.tv-contact__clocks { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 24px 48px; }
.tv-contact__clock { display: flex; align-items: baseline; gap: 0.6rem; font-size: var(--text-meta); letter-spacing: 0.04em; }
.tv-contact__clock-city { font-family: var(--font-sans); font-weight: 600; letter-spacing: 0.1em; color: var(--fg); }
.tv-contact__clock-time { color: var(--fg-dim); font-variant-numeric: tabular-nums; }

/* Mobile: one city per row instead of wrapping into two ragged
   columns. A fixed-width city label (sized to fit "LOS ANGELES", the
   longest name here) means every time value starts at the same x
   position — reads as a clean, aligned column rather than six
   independent baseline-paired labels. */
@media (max-width: 640px) {
  .tv-contact__clocks { flex-direction: column; gap: 10px; }
  .tv-contact__clock-city { min-width: 108px; }
}

/* Hero rapid-cycle — same .tc-video__frame box (16:9, max-height 88vh)
   every case study opens on, but instead of one fixed image/video, two
   stacked layers crossfade through every photo in the archive on a
   timer (see initHeroCycle in photography.js). */
.ph-hero-section { display: flex; justify-content: center; padding: clamp(3.5rem, 8vw, 5.5rem) var(--pad-lg) 0; }
/* padding-bottom intentionally left at .tc-excerpt's own default —
   matches the case study pages' excerpt-to-toggle rhythm exactly now
   that this page has the same .tc-view-toggle row they do. */
.ph-hero-section + .tc-excerpt { padding-top: clamp(3rem, 6vw, 4.5rem); }
.ph-hero {
  position: relative;
  width: 100%; max-width: 520px;
  aspect-ratio: 3 / 4; max-height: 76vh;
  background: var(--bg);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
/* object-fit: contain, not cover — the archive mixes portrait and
   landscape shots, and cropping every one to fill a fixed 3:4 box was
   cutting into the actual subject. Letterboxing onto the frame's own
   --bg instead keeps every photo shown whole. */
.ph-hero__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: contain;
  opacity: 0;
  transition: opacity 35ms linear;
}
.ph-hero__img.is-active { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .ph-hero__img { transition: none; }
}

/* Mobile only, Tim Vo's page only: the hero rapid-cycle sits inside a
   full-screen frame and is vertically centered in it, so the first
   screen a visitor sees is just the slideshow — the social icons and
   email (fixed to the screen's own corners) land on that same plain
   background instead of overlapping the gallery section starting
   underneath. Desktop keeps the original top-anchored layout.
   100svh (not dvh) deliberately — dvh resizes as the browser's address
   bar shows/hides during scroll, reflowing this section and everything
   below it (including the Film nav target) mid-scroll and producing a
   visible stutter when jumping to it. svh stays fixed. */
@media (max-width: 767px) {
  .tv-page .ph-hero-section { min-height: 100svh; align-items: center; padding-top: 0; padding-bottom: 0; }
}

/* project hero */
.p-hero {
  position: relative;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
}
.p-hero__field { position: absolute; inset: 0; }
.p-hero__index { display: none; }
.p-hero__content { position: relative; z-index: 2; padding: 0 var(--pad-lg) 4rem; width: 100%; }
.p-hero__category { font-size: var(--text-meta); color: var(--bg); opacity: 0.7; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 1rem; display: flex; gap: 0.75rem; align-items: center; }
.p-hero__title { font-family: var(--font-display); font-weight: 300; font-size: clamp(2.5rem, 7vw, 6.5rem); line-height: 0.98; letter-spacing: -0.02em; color: var(--bg); max-width: 16ch; }
.p-hero__scroll { display: none; }

.p-body {
  max-width: 1400px;
  margin: 0 auto;
  padding: clamp(4rem, 8vw, 8rem) var(--pad-lg);
}
.p-brief {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 3rem;
  padding-bottom: clamp(4rem, 8vw, 7rem);
  border-bottom: 1px solid var(--line);
  margin-bottom: clamp(4rem, 8vw, 7rem);
}
@media (max-width: 900px) { .p-brief { grid-template-columns: 1fr; gap: 2.5rem; } }
.p-brief__block .eyebrow { margin-bottom: 1rem; display: block; }
.p-brief__block p { font-size: var(--text-body); color: var(--fg-dim); line-height: 1.6; }

.p-media { margin-top: clamp(3rem, 6vw, 5rem); margin-bottom: clamp(3rem, 6vw, 5rem); }
.p-media:first-child { margin-top: 0; }
.p-media__frame { position: relative; border-radius: var(--radius-sm); overflow: hidden; }
.cs-video { width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }
.p-media--full .p-media__frame { aspect-ratio: 16/9; }
.p-media--pair { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
.p-media--pair .p-media__frame { aspect-ratio: 4/5; }
@media (max-width: 767px) { .p-media--pair { grid-template-columns: 1fr; } }
.p-media__caption { margin-top: 0.75rem; font-size: var(--text-meta); color: var(--fg-dim); }

.p-diagram { position: absolute; inset: 0; opacity: 0.6; pointer-events: none; }

.p-credits {
  border-top: 1px solid var(--line);
  padding-top: clamp(2.5rem, 5vw, 4rem);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 2rem;
}
.p-credits__item .eyebrow { display: block; margin-bottom: 0.5rem; }
.p-credits__item div { font-size: var(--text-small); color: var(--fg); }

.p-nextprev {
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.p-nextprev a {
  padding: 2.5rem var(--pad-lg);
  display: block;
  transition: background var(--dur-fast) var(--ease);
}
.p-nextprev a:hover { background: var(--surface); }
.p-nextprev a:first-child { border-right: 1px solid var(--line); }
.p-nextprev .eyebrow { margin-bottom: 0.6rem; display: block; }
.p-nextprev__title { font-family: var(--font-display); font-size: clamp(1.25rem, 2.5vw, 1.75rem); font-weight: 300; }

/* reveal */
.reveal { opacity: 0; transform: translateY(20px); transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease); }
.reveal.is-visible { opacity: 1; transform: translateY(0); }
/* Gallery cells fade in without the slide-up — in a masonry grid, cells
   in different columns cross the reveal threshold at slightly different
   scroll moments, so the translateY here made neighboring images look
   momentarily out of alignment with each other mid-scroll. The grid's
   own positions are unaffected; only the transition motion changes. */
.tc-gallery-cell.reveal { transform: none; }
/* Tim Vo's page only — the same slow, premium ease-out used for the
   Contact page's entrance (see .tv-contact-fade), so selecting Photo
   gives the gallery the same unhurried arrival feel instead of the
   site's standard --dur-slow/--ease. See replayGalleryReveal() in
   timvo.js, which re-triggers this fade every time Photo is clicked,
   not just the first time the grid scrolls into view. */
.tv-page .tc-gallery-cell.reveal { transition: opacity 900ms cubic-bezier(0.19, 1, 0.22, 1); }

/* -------------------------------------------------------------------------
   Rich case study template (.cs-) — reusable layout for flagship projects.
   Sits on the same dark canvas + token system as the rest of the site;
   diagrams/technical drawings are shown in light "exhibit" frames since
   they're authored on white, rather than forcing a dark-mode filter.
   ------------------------------------------------------------------------- */
.cs-hero {
  display: flex; flex-direction: column; align-items: center;
  position: relative;
  padding: clamp(4rem, 9vh, 6rem) var(--pad-lg) clamp(1.5rem, 3vh, 2.5rem);
  text-align: center;
}
.cs-hero__text {
  width: 100%;
  display: flex; flex-direction: column; align-items: center;
  margin-top: clamp(3rem, 5.25vw, 5.5rem);
}
.cs-hero__index { display: none; }
.cs-hero__art {
  position: relative; z-index: 1;
  width: 100%; max-width: min(820px, 86vw);
  border-radius: var(--radius-sm);
  overflow: hidden;
  box-shadow: 0 50px 100px -30px rgba(0, 0, 0, 0.65);
}
.cs-hero__art img { width: 100%; display: block; }
.cs-hero__title {
  position: relative; z-index: 1;
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.65rem, 3.2vw, 2.75rem); line-height: 1.08; letter-spacing: -0.005em;
  color: var(--fg);
  max-width: 20ch;
}
.cs-hero__meta { position: relative; z-index: 1; margin-top: 1.5rem; color: var(--fg-dim); font-size: var(--text-small); white-space: nowrap; }
.cs-hero__scroll { display: none; }

.cs-body { max-width: 1320px; margin: 0 auto; padding: 0 var(--pad-lg); }
.cs-hero + .cs-body { margin-top: 0; }

.cs-recap {
  margin-top: clamp(3.5rem, 7vw, 6rem);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
  padding: clamp(1.5rem, 3vw, 2.5rem) clamp(1.5rem, 4vw, 3rem);
}
.cs-recap__row {
  display: grid; grid-template-columns: 160px 1fr; gap: 1.5rem 2rem;
  padding: 1.25rem 0; border-top: 1px solid var(--line);
  align-items: baseline;
}
.cs-recap__row:first-child { border-top: none; padding-top: 0; }
.cs-recap__row--block { align-items: start; }
.cs-recap__value { font-size: var(--text-body); color: var(--fg); }
.cs-recap__tags { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.cs-recap__specs { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem 2rem; }
.cs-recap__specs > div { display: flex; flex-direction: column; gap: 0.35rem; }
.cs-recap__specs-k { font-size: var(--text-micro); letter-spacing: 0.03em; text-transform: uppercase; color: var(--fg-faint); }
.cs-recap__specs-v { font-size: var(--text-small); color: var(--fg); line-height: 1.4; }
@media (max-width: 480px) { .cs-recap__specs { grid-template-columns: 1fr; } }
.cs-recap__tag {
  font-size: var(--text-micro); letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--fg-dim); border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 0.3rem 0.85rem;
}
.cs-recap__block h4 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-section); color: var(--fg); margin-bottom: 0.6rem; }
.cs-recap__block p { font-size: var(--text-small); color: var(--fg-dim); line-height: 1.7; max-width: 62ch; }
@media (max-width: 640px) { .cs-recap__row { grid-template-columns: 1fr; gap: 0.5rem; } }

.cs-thesis { padding: clamp(5rem, 11vw, 9rem) 0; text-align: center; border-bottom: 1px solid var(--line); }
.cs-thesis__q {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.75rem, 4.5vw, 3.25rem);
  line-height: 1.15; letter-spacing: -0.01em;
  max-width: 20ch; margin: 0 auto;
  color: var(--fg);
}

.cs-section { padding: clamp(3.5rem, 7vw, 6rem) 0; border-bottom: 1px solid var(--line); }
.cs-section__head { margin-bottom: clamp(2rem, 4vw, 3rem); }
.cs-section__title { font-family: var(--font-display); font-weight: 300; font-size: var(--text-h1); color: var(--fg); }
.cs-section__lede { font-size: var(--text-body-lg); color: var(--fg-dim); max-width: 62ch; line-height: 1.6; margin-top: 1rem; }

.cs-cols-2 { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 4rem); margin-top: clamp(2rem, 4vw, 3rem); }
.cs-cols-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(2rem, 4vw, 3rem); margin-top: clamp(2rem, 4vw, 3rem); }
@media (max-width: 900px) { .cs-cols-2, .cs-cols-3 { grid-template-columns: 1fr; gap: 2.5rem; } }

/* standalone .cs-col (from a blocks[] array item, not a .cs-cols grid child)
   needs its own top spacing — grid children get it from .cs-cols-2/3 instead */
.cs-section > .cs-col { margin-top: clamp(2rem, 4vw, 3rem); }
.cs-col h3 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-section); color: var(--fg); margin-bottom: 1rem; }
.cs-col p { font-size: var(--text-body); color: var(--fg-dim); line-height: 1.7; }
.cs-col__role { color: var(--fg-dim); font-size: var(--text-small); margin-bottom: 1rem; }
.cs-col ul { margin-top: 0.5rem; }
.cs-col li { font-size: var(--text-small); color: var(--fg-dim); padding: 0.65rem 0; border-top: 1px solid var(--line); }
.cs-col li:first-child { border-top: none; }
.cs-note { margin-top: clamp(2rem, 4vw, 3rem); font-size: var(--text-body); color: var(--fg-dim); line-height: 1.7; max-width: 72ch; }

.cs-gallery { display: grid; gap: 1.25rem; margin-top: clamp(2rem, 4vw, 3rem); }
.cs-gallery--2 { grid-template-columns: 1fr 1fr; }
.cs-gallery--3 { grid-template-columns: 1fr 1fr 1fr; }
.cs-gallery--4 { grid-template-columns: 1fr 1fr 1fr 1fr; }
/* --6: for source images too low-res to stretch across 4 columns without
   softening — smaller tiles keep them close to native resolution. */
.cs-gallery--6 { grid-template-columns: repeat(6, 1fr); }
@media (max-width: 900px) { .cs-gallery--3, .cs-gallery--4, .cs-gallery--6 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 600px) { .cs-gallery--2, .cs-gallery--3, .cs-gallery--4, .cs-gallery--6 { grid-template-columns: 1fr; } }
.cs-gallery figure { margin: 0; }
.cs-gallery img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius-sm); display: block; aspect-ratio: 4 / 5; }
.cs-gallery figcaption { margin-top: 0.75rem; font-size: var(--text-meta); color: var(--fg-dim); line-height: 1.4; }

/* Option A — full-width uncropped plates, stacked */
.cs-plates { display: grid; gap: clamp(2rem, 4vw, 3rem); margin-top: clamp(2rem, 4vw, 3rem); }
/* --cols3: constrains plates to a 3-up grid instead of full-width stacking,
   for source images too low-res to stretch edge-to-edge without softening. */
.cs-plates--cols3 { grid-template-columns: 1fr 1fr 1fr; }
@media (max-width: 900px) { .cs-plates--cols3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 600px) { .cs-plates--cols3 { grid-template-columns: 1fr; } }
.cs-plates figure { margin: 0; }
.cs-plates img { width: 100%; height: auto; display: block; border-radius: var(--radius-sm); }
.cs-plates figcaption { margin-top: 0.85rem; font-size: var(--text-meta); color: var(--fg-dim); line-height: 1.4; }

/* Option B — smaller, paired side by side, uncropped (natural ratio) */
.cs-pair { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.25rem, 3vw, 2rem); margin-top: clamp(2rem, 4vw, 3rem); }
.cs-pair figure { margin: 0; }
.cs-pair img { width: 100%; height: auto; display: block; border-radius: var(--radius-sm); }
.cs-pair figcaption { margin-top: 0.75rem; font-size: var(--text-meta); color: var(--fg-dim); line-height: 1.4; }
@media (max-width: 720px) {
  .cs-pair { grid-template-columns: 1fr; }
}

.cs-reversal { margin-top: clamp(2rem, 4vw, 3rem); border-top: 1px solid var(--line); }
.cs-reversal__row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 1.5rem;
  padding: 1.1rem 0; border-bottom: 1px solid var(--line);
}
.cs-reversal__tag {
  flex: 0 0 9.5rem;
  font-size: var(--text-meta); letter-spacing: 0.04em; text-transform: uppercase; color: var(--fg-faint);
}
.cs-reversal__row--active .cs-reversal__tag { color: var(--fg-dim); }
.cs-reversal__chain { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; font-size: var(--text-small); color: var(--fg-faint); }
.cs-reversal__row--active .cs-reversal__chain { color: var(--fg-dim); }
.cs-reversal__arrow, .cs-reversal__dot { color: var(--fg-faint); }
.cs-reversal__hub { color: var(--fg); font-family: var(--font-display); font-weight: 400; font-size: var(--text-body); }
@media (max-width: 620px) { .cs-reversal__tag { flex-basis: 100%; } }

.cs-layers {
  display: flex; flex-direction: column;
  margin-top: clamp(2rem, 4vw, 3rem);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  overflow: hidden;
}
.cs-layers__band {
  padding: 0.95rem 1.25rem;
  font-family: var(--font-display); font-weight: 300; font-size: var(--text-section);
  color: var(--fg);
  background: rgba(255, 255, 255, calc(0.025 + var(--i) * 0.018));
  border-top: 1px solid var(--line);
}
.cs-layers__band:first-child { border-top: none; }

.cs-framework__row, .cs-zone {
  display: grid; grid-template-columns: 200px 1fr; gap: 2rem;
  padding: clamp(1.5rem, 3vw, 2rem) 0;
  border-top: 1px solid var(--line);
}
.cs-framework__row:last-child, .cs-zone:last-child { border-bottom: none; }
.cs-framework, .cs-zones { border-bottom: 1px solid transparent; margin-top: clamp(2rem, 4vw, 3rem); }
.cs-framework__label, .cs-zone__label {
  font-family: var(--font-display); font-weight: 300; font-size: var(--text-section); color: var(--fg);
}
.cs-zone__label small { display: block; font-family: var(--font-sans); font-weight: 400; font-size: var(--text-meta); color: var(--fg-faint); letter-spacing: 0.04em; text-transform: uppercase; margin-bottom: 0.35rem; }
.cs-framework__body h4, .cs-zone__body h4 { font-size: var(--text-body); font-weight: 500; color: var(--fg); margin-bottom: 0.5rem; }
.cs-framework__body p, .cs-zone__body p { font-size: var(--text-small); color: var(--fg-dim); line-height: 1.65; max-width: 60ch; }
@media (max-width: 700px) { .cs-framework__row, .cs-zone { grid-template-columns: 1fr; gap: 0.75rem; } }

.cs-diagrams { margin-top: clamp(2rem, 4vw, 3rem); display: grid; gap: clamp(2rem, 4vw, 3rem); }
.cs-diagram { display: block; margin-top: clamp(2rem, 4vw, 3rem); }
.cs-diagram__frame { background: #efedea; border-radius: var(--radius-sm); padding: clamp(1rem, 3vw, 2rem); overflow: hidden; }
.cs-diagram__frame img { width: 100%; height: auto; display: block; }
.cs-diagram__caption { margin-top: 0.9rem; font-size: var(--text-meta); color: var(--fg-dim); }

/* Blueprint sheets are fixed white-paper / black-ink — deliberately NOT
   theme-adaptive. A technical drawing doesn't switch to dark mode. */
.cs-diagram__frame--blueprint {
  background: var(--bp-paper);
  border: 1px solid var(--bp-ink-faint);
  padding: clamp(1.25rem, 3vw, 2.25rem);
}
.cs-diagram__frame--blueprint svg { width: 100%; height: auto; display: block; }
.cs-bp-line { stroke: var(--bp-ink-dim); }
.cs-bp-line-strong { stroke: var(--bp-ink); }
.cs-bp-line-faint { stroke: var(--bp-ink-faint); }
.cs-bp-fill { fill: var(--bp-ink); }
.cs-bp-label {
  font-family: var(--font-display);
  fill: var(--bp-ink-dim);
  font-size: 13px;
  letter-spacing: 0.04em;
}
.cs-bp-label--strong { fill: var(--bp-ink); font-weight: 500; }
.cs-bp-label--amber { fill: var(--bp-ink); font-weight: 500; }
.cs-bp-eyebrow {
  font-family: var(--font-display);
  fill: var(--bp-ink-faint);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.cs-quote { padding: clamp(4rem, 9vw, 7.5rem) 0; text-align: center; }
.cs-quote p {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.5rem, 3.5vw, 2.5rem); line-height: 1.3;
  color: var(--fg); max-width: 30ch; margin: 0 auto;
}

.cs-disclaimer { padding: 2rem 0 clamp(4rem, 8vw, 6rem); }
.cs-disclaimer p { font-size: var(--text-micro); color: var(--fg-faint); line-height: 1.7; max-width: 72ch; }

/* small labeled color-swatch list — campaign-internal palette documentation,
   not a site theme control; kept flat/hairline per the no-shadow rule */
.cs-swatches { display: flex; flex-wrap: wrap; gap: 1.5rem 2rem; margin-top: clamp(2rem, 4vw, 3rem); }
.cs-swatch { display: flex; align-items: center; gap: 0.6rem; }
.cs-swatch__dot { width: 14px; height: 14px; border-radius: 50%; border: 1px solid var(--line-strong); flex-shrink: 0; }
.cs-swatch__name { font-size: var(--text-small); color: var(--fg-dim); }

/* dispatch */
.d-header {
  padding: clamp(7rem, 14vw, 10rem) var(--pad-lg) clamp(3rem, 6vw, 5rem);
  max-width: 900px;
}
.d-header__dateline { font-size: var(--text-meta); color: var(--fg-dim); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 1.5rem; display: flex; gap: 1rem; }
.d-header__title { font-family: var(--font-display); font-weight: 300; font-size: clamp(2rem, 5vw, 3.5rem); line-height: 1.05; letter-spacing: -0.02em; }
.d-body { max-width: 700px; padding: 0 var(--pad-lg) clamp(5rem, 10vw, 8rem); }
.d-body p { font-size: var(--text-body-lg); line-height: 1.65; color: var(--fg); margin-bottom: 1.5rem; }
.d-body .source-link { font-size: var(--text-small); color: var(--fg-dim); border-bottom: 1px solid var(--line-strong); }
.d-body .source-link:hover { color: var(--fg); border-color: var(--fg); }
.d-attribution { font-size: var(--text-small); color: var(--fg-faint); margin-top: -0.75rem; }
.d-byline { font-size: var(--text-small); color: var(--fg-dim); margin-top: 2.5rem; padding-top: 2rem; border-top: 1px solid var(--line); }

/* newsletter signup — reusable partial, dropped in at the close of Journal
   entries. Reuses the site's existing mono-label (.i-field label) and
   hairline-underline input treatment (.i-field input) rather than
   inventing a new pattern. */
.newsletter { max-width: 700px; margin-top: 3rem; padding-top: 2.25rem; border-top: 1px solid var(--line); }
.newsletter__eyebrow {
  font-family: var(--font-mono); font-size: var(--text-micro); letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--fg-dim); margin-bottom: 0.85rem;
}
.newsletter__line { font-size: var(--text-body-lg); line-height: 1.5; color: var(--fg); margin-bottom: 1.5rem; }
.newsletter__form { display: flex; align-items: flex-end; gap: 1.5rem; }
.newsletter__form[hidden] { display: none; }
.newsletter__input {
  flex: 1 1 auto; min-width: 0; background: transparent; border: none; border-bottom: 1px solid var(--line-strong);
  color: var(--fg); font-family: var(--font-sans); font-size: var(--text-body); padding: 0.55rem 0;
  transition: border-color var(--dur-fast) var(--ease);
}
.newsletter__input:focus { outline: none; border-color: var(--fg); }
.newsletter__input::placeholder { color: var(--fg-faint); }
.newsletter__submit {
  display: inline-flex; align-items: center; gap: 0.4em; flex-shrink: 0;
  font: inherit; font-size: var(--text-small); color: var(--fg-dim);
  background: none; border: none; border-bottom: 1px solid var(--line-strong);
  padding: 0 0 calc(0.55rem + 1px); cursor: pointer;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.newsletter__submit:hover, .newsletter__submit:focus-visible { color: var(--fg); border-color: var(--fg); }
.newsletter__submit:disabled { cursor: default; opacity: 0.6; }
.newsletter__submit .link__arrow { display: inline-block; transition: transform var(--dur-fast) var(--ease); }
.newsletter__submit:hover .link__arrow, .newsletter__submit:focus-visible .link__arrow { transform: translateX(4px); }
.newsletter__status { font-size: var(--text-body); color: var(--fg-dim); }
.newsletter__status.is-error { color: var(--fg); }
@media (max-width: 560px) {
  .newsletter__form { flex-direction: column; align-items: stretch; gap: 1.25rem; }
  .newsletter__submit { align-self: flex-start; }
}

.d-hero { max-width: 900px; margin: 0 auto; padding: 0 var(--pad-lg) clamp(3rem, 6vw, 5rem); }
.d-hero img { width: 100%; border-radius: var(--radius); display: block; }
.d-hero--pending {
  aspect-ratio: 4/3; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center;
}
.d-hero--pending span { font-size: var(--text-meta); color: var(--fg-faint); text-transform: uppercase; letter-spacing: 0.04em; }

/* journal archive */
.j-list { max-width: 1100px; margin: 0 auto; padding: clamp(7rem, 12vw, 9rem) var(--pad-lg) 6rem; }
.j-year { font-size: var(--text-meta); color: var(--fg-dim); text-transform: uppercase; letter-spacing: 0.06em; margin: 3rem 0 0.5rem; }
.j-year:first-child { margin-top: 0; }
.j-item {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: 2rem;
  padding: 1.5rem 0;
  border-top: 1px solid var(--line);
  align-items: baseline;
  transition: opacity var(--dur-fast) var(--ease);
}
@media (max-width: 640px) { .j-item { grid-template-columns: 1fr; gap: 0.5rem; } }
.j-item__date { font-size: var(--text-meta); color: var(--fg-dim); }
.j-item__title { font-size: var(--text-body-lg); font-weight: 400; }
.j-item__title:hover { color: var(--fg-dim); }

/* contact */
.c-page { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; justify-content: center; padding: 6rem var(--pad-lg); }
.c-mail { font-family: var(--font-display); font-weight: 300; font-size: clamp(2.25rem, 8vw, 6rem); letter-spacing: -0.02em; line-height: 1.05; margin-bottom: 2.5rem; word-break: break-word; }
.c-mail:hover { color: var(--fg-dim); }
.c-rows { display: flex; flex-wrap: wrap; gap: 2.5rem; }
.c-row { }
.c-row .contact-pairs__label { margin-bottom: 0.5rem; }
.c-row .contact-pairs__value { font-size: 1.1rem; }
.c-start-project { margin-top: 3rem; }

/* project inquiry overlay + form — full-bleed (no sidebar on contact.html,
   unlike .overlay--pane on the homepage), same .overlay base as the About
   panel. No backend on this static site: submit composes a formatted
   mailto: and opens it in the visitor's own mail client — see
   assets/js/data.js ABOUT.inquiry for the upgrade-path note. */
.inquiry-overlay__body { flex: 1 1 auto; overflow-y: auto; padding: clamp(2rem, 5vw, 4rem) var(--pad-lg) 4rem; }
.inquiry-form { max-width: 640px; margin: 0 auto; }
.inquiry-form__title { font-family: var(--font-display); font-weight: 300; font-size: clamp(1.5rem, 3.5vw, 2rem); margin-bottom: 0.75rem; }
.inquiry-form__lede { color: var(--fg-dim); font-size: var(--text-body); line-height: 1.6; max-width: 60ch; margin-bottom: 2.5rem; }

.i-field { margin-bottom: 1.75rem; }
.i-field:last-of-type { margin-bottom: 0; }
.i-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
.i-field label {
  display: block; font-family: var(--font-mono); font-size: var(--text-micro);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--fg-dim); margin-bottom: 0.6rem;
}
.i-field label .i-req { color: var(--fg-faint); text-transform: none; letter-spacing: 0; margin-left: 0.3rem; }
.i-field input, .i-field textarea, .i-field select {
  width: 100%; background: transparent; border: none; border-bottom: 1px solid var(--line-strong);
  color: var(--fg); font-family: var(--font-sans); font-size: var(--text-body); padding: 0.55rem 0;
  transition: border-color var(--dur-fast) var(--ease);
}
.i-field input:focus, .i-field textarea:focus, .i-field select:focus { outline: none; border-color: var(--fg-dim); }
.i-field input::placeholder, .i-field textarea::placeholder { color: var(--fg-faint); }
.i-field textarea { resize: vertical; min-height: 80px; font-family: inherit; }
.i-field select { appearance: none; -webkit-appearance: none; cursor: pointer; }
.i-chips { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.i-chip {
  font-family: var(--font-mono); font-size: var(--text-micro); letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--fg-dim); background: none; border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 0.4rem 0.9rem; cursor: pointer; transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.i-chip.is-on { color: var(--fg); border-color: var(--fg-dim); }
.i-hint { font-size: var(--text-small); color: var(--fg-faint); margin-top: 0.5rem; line-height: 1.5; }
.i-submit-row { margin-top: 2.5rem; display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap; }
.i-submit {
  font-family: var(--font-mono); font-size: var(--text-small); letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--bg); background: var(--fg); border: 1px solid var(--fg); border-radius: 999px;
  padding: 0.75rem 1.75rem; cursor: pointer; transition: opacity var(--dur-fast) var(--ease);
}
.i-submit:hover { opacity: 0.85; }
.i-submit-note { font-size: var(--text-small); color: var(--fg-faint); }

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

/* -------------------------------------------------------------------------
   10. Responsive — tablet / mobile
   ------------------------------------------------------------------------- */
@media (max-width: 1023px) {
  .sidebar { width: 35vw; }
  .grid { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 34vw; }
  .tile--sq, .tile--tall, .tile--wide { grid-column: span 2; }
  .tile--tall { grid-row: span 2; }
  .about { flex-direction: column; overflow-y: auto; }
  .about__center { overflow-y: visible; padding-bottom: 0; }
}

@media (max-width: 767px) {
  .shell { display: block; }
  .sidebar {
    position: static;
    width: 100%; min-width: 0; max-width: none;
    margin: 0;
    height: auto;
    border-right: none;
    border-bottom: 1px solid var(--line);
  }
  .sidebar__projects { max-height: 40vh; }
  /* min-height reset too — .main's base 100vh floor is what made sense
     when .stage always stretched to fill it; now that .stage hugs the
     video's own 16:9 height instead, keeping that floor would leave a
     blank gap below the video (and reintroduce scroll past it). */
  .main { margin-left: 0; width: 100%; min-height: 0; }
  /* 16:9, matching every homepage video hero's native ratio exactly —
     the box IS the video's shape here, so object-fit:cover (still set
     on .stage__heroimg video) never has to crop or zoom to fill it,
     unlike the desktop stage which stretches to fill the viewport
     regardless of the video's own proportions. flex-grow is reset to 0
     too — otherwise .main's flex column stretches .stage to fill the
     remaining 100vh regardless of the aspect-ratio set above. */
  .stage { flex: 0 0 auto; min-height: 0; aspect-ratio: 16 / 9; }

  .overlay { left: 0; }
  .overlay__body { flex-direction: column; overflow-y: auto; }
  .overlay__filters {
    width: 100%;
    border-right: none;
    border-bottom: 1px solid var(--line);
    display: flex;
    gap: 1.5rem;
    overflow-x: auto;
    padding: 1rem var(--pad);
    white-space: nowrap;
  }
  .overlay__filter-item { width: auto; margin-left: 0; padding: 0.35rem 0; }
  .overlay__filter-item::before { left: 0; top: auto; bottom: -2px; width: 0; height: 2px; transform: none; }
  .overlay__filter-item.is-active::before { width: 100%; height: 2px; }
  .grid { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 42vw; padding: 1.25rem; }
  .tile--sq { grid-column: span 1; grid-row: span 1; }
  .tile--tall { grid-column: span 1; grid-row: span 2; }
  .tile--wide { grid-column: span 2; grid-row: span 1; }

  .about__hero { height: 32vh; }
  .about__section { padding: 2.5rem 1.5rem 0; }
  .contact-pairs--row { flex-direction: column; padding: 0.5rem 0; }
  .contact-pairs--row .contact-pairs__item { padding: 1.25rem 0; border-left: none !important; }
  .contact-pairs--row .contact-pairs__item:first-child { padding-top: 1.25rem; }
  .contact-pairs--row .contact-pairs__item:last-child { padding-bottom: 1.25rem; }
  .contact-pairs--row .contact-pairs__item + .contact-pairs__item { border-top: 1px solid var(--line); }
  .p-hero__content { padding: 0 1.5rem 3rem; }
  .p-body { padding: 3rem 1.5rem; }
  .p-nextprev { grid-template-columns: 1fr; }
  .p-nextprev a:first-child { border-right: none; border-bottom: 1px solid var(--line); }
  .j-item { grid-template-columns: 1fr; }
  .c-page { padding: 5rem 1.5rem; }
}

/* -------------------------------------------------------------------------
   11. Reduced motion
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .stage__field { animation: none; }
}

/* -------------------------------------------------------------------------
   12. Temma — editorial visual essay, modeled on Aman's own
   property-page pattern: hero video -> excerpt -> grid.
   Restraint over spectacle: near-black/warm-white, no card chrome, no
   forced crops. Full arc: hero video -> excerpt -> full grid (all 32
   images, one unified set, lightbox on click) -> location -> closing
   beat. The video is the page's opening frame — no separate cover.
   ------------------------------------------------------------------------- */
.tc-body { background: var(--bg); color: var(--fg); }

.tc-progress { position: fixed; top: 0; left: 0; width: 2px; height: 100vh; background: var(--line); z-index: 50; }
.tc-progress__fill { width: 100%; height: 0%; background: var(--fg-dim); }

/* Video reserve — full-bleed 16:9 slot, isolated by generous top/bottom
   padding so it reads as a deliberate pause. */
.tc-video { }
/* max-height caps the frame on wide-but-short viewports (ultrawide
   monitors, maximized wide windows) — at true 16:9 a very wide frame
   can grow taller than the viewport itself, pushing the controls bar
   below the fold. Capped, the footage still fills edge-to-edge via
   object-fit:cover on the <video>, just cropped a little top/bottom
   instead of letterboxed. */
/* Pure black, not var(--surface) — no poster/placeholder image on either
   the case study or homepage video, so this is genuinely the only thing
   visible until the first frame decodes and paints over it. On the
   homepage this also covers the project's own brand-tinted
   .stage__field sitting behind it. */
.tc-video__frame { position: relative; width: 100%; aspect-ratio: 16 / 9; max-height: 88vh; overflow: hidden; background: #000; }
/* In fullscreen the frame should fill the whole screen, not stay capped
   by the 88vh scroll-layout constraint above. Only the case study's
   frame has a fullscreen trigger — the homepage's stripped-down pill
   doesn't — so this is unreachable there regardless. */
.tc-video__frame:fullscreen, .tc-video__frame:-webkit-full-screen {
  width: 100vw; height: 100vh; max-height: none; aspect-ratio: auto;
}
.tc-video__frame img, .tc-video__frame video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Case-study page heroes fill the entire first screen, same footprint
   as the homepage stage — scoped to the opening .tc-video section's
   own frame only, not every .tc-video__frame on the site. */
.tc-video .tc-video__frame { aspect-ratio: auto; max-height: none; height: 100vh; height: 100dvh; }

/* On a phone that full-height box crops a landscape property film hard
   against a tall, narrow viewport. Below the same breakpoint the
   homepage's own stage switches to (see .stage in the mobile media
   query), the hero drops back to that identical 16:9 box instead —
   same horizontal format, same size logic. Needs to come after the
   rule above: equal specificity, so source order decides, and this
   must win on mobile. */
@media (max-width: 767px) {
  .tc-video .tc-video__frame { aspect-ratio: 16 / 9; max-height: none; height: auto; }
  /* The "scroll to continue" chevron only makes sense pointing at a
     full-screen hero with more content hidden below the fold — now
     that the hero is a modest 16:9 box on mobile with the excerpt
     already visible right underneath it, the cue is redundant there.
     Desktop keeps it untouched. */
  .tc-video .stage__scroll-cue { display: none; }
}

/* Controls — a single mute + volume pill, bottom-right, on every hero
   video (homepage stage and case study heroes alike). Hidden by
   default, faded in while the cursor is over/near the frame (or via
   keyboard focus) and shown briefly on load so first-time visitors
   notice it exists — see initVideoControlsVisibility in temma.js. */
.tc-video__controls {
  position: absolute; right: 0; bottom: 0;
  padding: 0 clamp(1.25rem, 3.5vw, 2rem) 0.85rem 0;
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-base) var(--ease);
}
.tc-video__frame.is-controls-active .tc-video__controls,
.tc-video__frame:focus-within .tc-video__controls {
  opacity: 1; pointer-events: auto;
}

.tc-video__mute {
  flex: none; width: 22px; height: 22px; border-radius: 50%; border: none;
  background: none; display: flex; align-items: center; justify-content: center;
  color: #F0ECE4; cursor: pointer; opacity: 0.85; transition: opacity var(--dur-fast) var(--ease);
}
.tc-video__mute:hover, .tc-video__mute:focus-visible { opacity: 1; }

/* Volume — always visible (not hover-revealed on its own) so it's
   reachable on touch devices too, not just mouse. Native
   <input type="range">, restyled to match the site's thin-line-plus-dot
   language rather than OS chrome. Carries its own backdrop since it's
   the only thing in the controls group. */
.tc-video__volume {
  display: flex; align-items: center; gap: 0.6rem; flex: none;
  background: rgba(8, 8, 8, 0.4);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border-radius: 999px;
  padding: 0.4rem 0.85rem;
}
.tc-video__volume-slider {
  width: 60px; -webkit-appearance: none; appearance: none; background: transparent; height: 16px;
}
.tc-video__volume-slider::-webkit-slider-runnable-track {
  height: 2px; background: rgba(240, 236, 228, 0.3);
}
.tc-video__volume-slider::-webkit-slider-thumb {
  -webkit-appearance: none; width: 8px; height: 8px; border-radius: 50%;
  background: #F0ECE4; margin-top: -3px; cursor: pointer;
}
.tc-video__volume-slider::-moz-range-track { height: 2px; background: rgba(240, 236, 228, 0.3); border: none; }
.tc-video__volume-slider::-moz-range-thumb { width: 8px; height: 8px; border-radius: 50%; background: #F0ECE4; border: none; cursor: pointer; }

@media (max-width: 640px) {
  .tc-video__volume-slider { width: 40px; }
}

/* Excerpt — centered intro block between the hero video and the grid,
   Aman's own pattern: small-caps location, title, short paragraph. Copy
   is a placeholder until real case-study text is written. */
.tc-excerpt { max-width: 1040px; margin: 0 auto; padding: clamp(4.5rem, 10vw, 7.5rem) var(--pad-lg) clamp(3.5rem, 8vw, 6rem); text-align: center; }
.tc-excerpt__location {
  font-size: var(--text-meta); text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-dim);
}
.tc-excerpt__title {
  margin-top: 0.75rem; font-family: var(--font-display); font-weight: 300;
  font-size: var(--text-h1); letter-spacing: -0.01em; color: var(--fg);
}
.tc-excerpt__body {
  margin-top: 1.5rem; max-width: 80ch; margin-left: auto; margin-right: auto;
  font-size: var(--text-body); line-height: 1.55; color: var(--fg-dim);
}

/* Gallery — one unified, unbroken 32-image sequence. No section labels;
   the cut between images carries the rhythm on its own. Masonry via CSS
   columns, true to each image's natural aspect ratio (no forced crops),
   generous gutters, zero card chrome. Each cell fades in individually on
   scroll (rather than the whole grid at once) — the grid is many
   viewport-heights tall, so a single reveal trigger on the whole thing
   can never reach its intersection threshold; per-cell reveal has no
   such ceiling. */
.tc-gallery-section { padding: 0 var(--pad-lg) clamp(3rem, 7vw, 5rem); scroll-margin-top: 90px; }
#tv-film-section { scroll-margin-top: 90px; }
.tc-grid-view { max-width: 1500px; margin: 0 auto; }

/* Grid / Single view toggle — Aman's own pattern: two quiet icon+label
   pairs, the active one carrying a thin underline. Purely a display
   switch on the same 32-image sequence below (see .tc-gallery-section
   [data-view="single"]), no separate markup or data per mode. */
.tc-view-toggle { max-width: 1500px; margin: 0 auto; padding: 0 var(--pad-lg) clamp(2rem, 4vw, 3rem); display: flex; justify-content: center; gap: 2.25rem; }
.tc-view-toggle__btn { display: flex; align-items: center; gap: 0.6rem; color: var(--fg-faint); transition: color var(--dur-fast) var(--ease); }
.tc-view-toggle__btn svg { flex-shrink: 0; display: block; }
.tc-view-toggle__btn span { font-size: var(--text-small); position: relative; padding-bottom: 4px; }
.tc-view-toggle__btn:hover, .tc-view-toggle__btn:focus-visible { color: var(--fg-dim); }
.tc-view-toggle__btn.is-active { color: var(--fg); }
.tc-view-toggle__btn.is-active span::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--fg); }

.tc-gallery-grid { column-count: 3; column-gap: clamp(1.5rem, 3vw, 2.5rem); transition: opacity 220ms var(--ease); }
.tc-gallery-section.is-switching .tc-gallery-grid,
.tc-gallery-section.is-switching .tc-slideshow { opacity: 0; }
.tc-gallery-cell {
  position: relative; overflow: hidden;
  break-inside: avoid; margin-bottom: clamp(1.5rem, 3vw, 2.5rem); cursor: pointer;
}
.tc-gallery-cell img { width: 100%; height: auto; display: block; transition: transform 700ms cubic-bezier(0.16, 1, 0.3, 1); }
.tc-gallery-cell:hover img, .tc-gallery-cell:focus-visible img { transform: scale(1.045); }
@media (prefers-reduced-motion: reduce) {
  .tc-gallery-cell img { transition: none; }
}
@media (max-width: 900px) { .tc-gallery-grid { column-count: 2; } }
@media (max-width: 560px) { .tc-gallery-grid { column-count: 1; } }

/* Single view: a proper slideshow — one image at a time, letterboxed
   full within a fixed dark frame (never cropped, unlike the cover-fit
   hero frame, since these images span both portrait macro shots and
   wide landscape rooms), with the same prev/next arrows as the
   lightbox for visual consistency. Grid stays in the DOM underneath —
   .tc-gallery-section[data-view] just toggles which one is visible. */
.tc-gallery-section:not([data-view="single"]) .tc-slideshow { display: none; }
.tc-gallery-section[data-view="single"] .tc-grid-view { display: none; }
.tc-slideshow { max-width: 1400px; margin: 0 auto; transition: opacity 220ms var(--ease); }
/* .tc-slideshow__row is the positioning context for the arrows — sized
   to the frame alone (arrows are absolute, out of flow), with side
   padding reserved so the buttons sit in the page's own black, clear of
   the frame's own overflow:hidden edge, never overlapping the photo the
   way the fullscreen lightbox's arrows deliberately do. */
.tc-slideshow__row { position: relative; padding: 0 56px; }
.tc-slideshow__frame {
  position: relative;
  height: min(76vh, 820px);
  background: var(--bg);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.tc-slideshow__img { width: 100%; height: 100%; object-fit: contain; display: block; }
.tc-slideshow__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; color: var(--fg-dim); cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.tc-slideshow__nav:hover, .tc-slideshow__nav:focus-visible { color: var(--fg); }
.tc-slideshow__nav--prev { left: 0; }
.tc-slideshow__nav--next { right: 0; }
@media (max-width: 640px) {
  .tc-slideshow__row { padding: 0 32px; }
  .tc-slideshow__nav { width: 28px; height: 28px; }
}
.tc-slideshow__counter {
  text-align: center;
  margin-top: 1rem;
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: 0.04em;
  color: var(--fg-faint);
}
@media (prefers-reduced-motion: reduce) {
  .tc-gallery-grid { transition: none; }
}

/* Location — Leaflet on a warm paper/ink basemap, the same fixed
   light treatment as the blueprint diagram sheets elsewhere on the site
   (deliberately NOT theme-adaptive — a map reads as a document, not a
   photo, so it sits as a warm-white/grey inset against the page's dark
   ground rather than matching it). Free/keyless CARTO tiles, nudged
   warmer with a CSS filter since Positron's default is cool grey-blue.
   Pin and distance callouts are custom divIcons styled to the site's
   own type system, not any library default. */
.tc-map {
  --tc-map-accent: #1A00FF;
  --tc-map-paper: #DFDACD;
  --tc-map-paper-hover: #D3CDBE;
  max-width: 1500px; margin: 0 auto; aspect-ratio: 2.1 / 1; min-height: 320px;
  background: var(--tc-map-paper); border: 1px solid var(--bp-ink-faint);
  /* Leaflet sizes its internal canvas off the container's dimensions at
     init time, which can be measured wrong if the container was still
     `hidden` (0x0) or mid-layout at that instant — invalidateSize() in
     initLocationMap corrects the map's own rendering, but this is the
     hard backstop: whatever size Leaflet momentarily computes, it can
     never push the page itself wider than the viewport. */
  overflow: hidden;
}
/* Taller and squarer on narrow phones — the desktop 2.1:1 ratio reads
   as a thin, cramped strip once the width that ratio depends on is
   only ~340px instead of up to 1500px. */
@media (max-width: 767px) {
  .tc-map { aspect-ratio: 1 / 1; min-height: 340px; }
}
.tc-map .leaflet-container { background: var(--tc-map-paper); font-family: var(--font-sans); }
.tc-map .leaflet-tile-pane { filter: sepia(0.25) saturate(0.5) brightness(0.86) contrast(1.02); }
/* Leaflet positions raster tiles with sub-pixel CSS transforms, which
   browsers can round inconsistently between adjacent tiles — showing as
   a faint seam line at tile-row boundaries. A transparent outline forces
   the browser to anti-alias each tile's edge with a hairline of bleed,
   which hides the gap without visibly changing anything. */
.tc-map .leaflet-tile { outline: 1px solid transparent; }
.tc-map .leaflet-div-icon { background: transparent; border: none; }
.tc-map .leaflet-control-zoom { border: none; box-shadow: none; margin: 12px; }
.tc-map .leaflet-control-zoom a {
  width: 26px; height: 26px; line-height: 24px; background: var(--tc-map-paper); color: var(--bp-ink-dim);
  border: 1px solid var(--bp-ink-faint); border-radius: 0; font-weight: 400;
}
.tc-map .leaflet-control-zoom a:first-child { border-bottom: none; }
.tc-map .leaflet-control-zoom a:hover { background: var(--tc-map-paper-hover); color: var(--bp-ink); }
.tc-map .leaflet-control-attribution {
  background: rgba(223, 218, 205, 0.85); color: var(--bp-ink-faint); font-size: 10px; padding: 2px 6px;
}
.tc-map .leaflet-control-attribution a { color: var(--bp-ink-dim); }

.tc-map-pin { display: flex; align-items: center; gap: 0.5rem; white-space: nowrap; transform: translate(-4px, -10px); }
.tc-map-pin__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--bp-ink); flex: none; box-shadow: 0 0 0 4px rgba(18, 17, 14, 0.12); }
.tc-map-pin__label {
  font-size: var(--text-small); font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--bp-ink);
}

.tc-map-note { display: flex; align-items: center; gap: 0.4rem; white-space: nowrap; transform: translate(-3px, -8px); color: var(--tc-map-accent); }
.tc-map-note__icon { flex: none; display: flex; }
.tc-map-note__dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; display: block; }
.tc-map-note__text { font-size: var(--text-micro); font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; }

/* Same .tc-map treatment, reused inside the narrower About Us panel.
   .tc-map's own min-height forces aspect-ratio to widen the box past
   100% when the container is narrower than min-height * ratio (672px
   at 2.1:1) — the About panel's column is narrower than that, so an
   explicit width pins it to 100% and min-height:0 lets height follow
   the ratio down instead of pushing width out past the border. */
.about__section .about-map {
  margin-top: 2rem;
  width: 100%;
  min-height: 0;
}
@media (max-width: 767px) {
  .about__section .about-map { aspect-ratio: 1 / 1; min-height: 0; }
}

/* Film — Tim Vo's portfolio only. A short, stacked reel list (not a
   dense grid): each piece gets its own 16:9 frame with the ORIGINAL
   full video control interface (play/pause, a scrubbable track, mute,
   volume) — exactly as it was on this site's own case-study hero
   videos before being pared down to an ambient-only mute/volume pill
   there (see .tc-video__controls above). Click-to-play since these
   are real cuts meant to be watched deliberately, not autoplay loops. */
.tv-film-heading { max-width: 1040px; margin: 0 auto; padding: clamp(3rem, 6vw, 4.5rem) var(--pad-lg) clamp(2rem, 4vw, 2.75rem); text-align: center; }
.tv-film-heading__eyebrow { font-size: var(--text-meta); text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-dim); }
.tv-film-heading__title { margin-top: 0.75rem; font-family: var(--font-display); font-weight: 300; font-size: var(--text-h1); letter-spacing: -0.01em; color: var(--fg); }
.tv-film-section { max-width: 760px; margin: 0 auto; padding: 0 var(--pad-lg) clamp(4rem, 8vw, 6rem); display: flex; flex-direction: column; gap: clamp(2.5rem, 5vw, 3.5rem); }
.tv-film-item__frame { position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; overflow: hidden; }
/* Opus and Noir are both native 1440x1080 (4:3) — the shared 16:9
   frame above was pillarboxing them with visible black bars on each
   side. Kwame Adusei is natively 16:9 already and keeps the default. */
#frame-opus, #frame-noir { aspect-ratio: 4 / 3; }
.tv-film-item__frame video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; cursor: pointer; }
.tv-video__center-play {
  position: absolute; top: 50%; left: 50%; z-index: 2;
  width: 64px; height: 64px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(8, 8, 8, 0.32); border: 1px solid rgba(255, 255, 255, 0.5);
  color: #FFFFFF; backdrop-filter: blur(2px);
  cursor: pointer; transform: translate(-50%, -50%);
  transition: opacity var(--dur-base) var(--ease), transform var(--dur-base) var(--ease), background var(--dur-fast) var(--ease);
}
.tv-video__center-play svg { margin-left: 3px; }
.tv-video__center-play:hover, .tv-video__center-play:focus-visible { background: rgba(8, 8, 8, 0.48); }
.tv-video__center-play.is-hidden { opacity: 0; transform: translate(-50%, -50%) scale(0.92); pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .tv-video__center-play { transition: opacity var(--dur-fast) linear; }
}
.tv-film-item__meta { display: flex; justify-content: space-between; align-items: baseline; margin-top: 1.5rem; gap: 1rem; }
.tv-film-item__title { font-family: var(--font-display); font-weight: 600; font-size: var(--text-meta); letter-spacing: 0.18em; text-transform: uppercase; color: var(--fg); }
.tv-film-item__year { font-size: var(--text-meta); color: var(--fg-faint); flex-shrink: 0; }
.tv-film-item__desc { margin-top: 0.85rem; }
.tv-film-item__desc p { font-size: var(--text-small); line-height: 1.6; color: var(--fg-dim); margin: 0 0 0.6em; }
.tv-film-item__desc p:last-child { margin-bottom: 0; }

.tv-video__controls {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  display: flex; align-items: center; gap: 0.85rem;
  padding: clamp(1.5rem, 4vw, 2.25rem) clamp(1rem, 3vw, 1.75rem) clamp(0.9rem, 2.5vw, 1.4rem);
  background: linear-gradient(180deg, rgba(8, 8, 8, 0) 0%, rgba(8, 8, 8, 0.55) 100%);
  opacity: 0; pointer-events: none; transition: opacity var(--dur-base) var(--ease);
}
.tv-film-item__frame.is-controls-active .tv-video__controls { opacity: 1; pointer-events: auto; }
.tv-video__play {
  flex: none; width: 22px; height: 22px; border-radius: 50%; border: none;
  background: none; display: flex; align-items: center; justify-content: center;
  color: #F0ECE4; cursor: pointer; opacity: 0.85; transition: opacity var(--dur-fast) var(--ease);
}
.tv-video__play:hover, .tv-video__play:focus-visible { opacity: 1; }
.tv-video__track { flex: 1 1 auto; position: relative; height: 16px; display: flex; align-items: center; cursor: pointer; }
.tv-video__track::before { content: ''; position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); height: 2px; background: rgba(240, 236, 228, 0.3); }
.tv-video__track-fill { position: absolute; left: 0; top: 50%; transform: translateY(-50%); height: 2px; width: 0%; background: #F0ECE4; }
.tv-video__track-fill::after { content: ''; position: absolute; right: -4px; top: 50%; transform: translate(50%, -50%); width: 8px; height: 8px; border-radius: 50%; background: #F0ECE4; opacity: 0; transition: opacity var(--dur-fast) var(--ease); }
.tv-video__track:hover .tv-video__track-fill::after,
.tv-video__track:focus-visible .tv-video__track-fill::after,
.tv-video__track.is-scrubbing .tv-video__track-fill::after { opacity: 1; }
/* Expand-to-fullscreen: mobile only (desktop already reads the full
   frame comfortably at its native size, and mouse users have no need
   for a cinematic-widescreen escape hatch). Sized for an easy tap --
   bigger hit area than play/mute since it's a deliberate, one-off
   action rather than something scrubbed repeatedly mid-watch. */
.tv-video__expand {
  display: none; flex: none; width: 40px; height: 40px; margin: -9px -6px -9px 0;
  border-radius: 50%; border: none; background: none;
  align-items: center; justify-content: center;
  color: #F0ECE4; cursor: pointer; opacity: 0.85; transition: opacity var(--dur-fast) var(--ease);
}
.tv-video__expand:hover, .tv-video__expand:focus-visible { opacity: 1; }
@media (max-width: 767px) {
  .tv-video__expand { display: flex; }
}
/* In fullscreen the frame becomes the whole viewport -- release it
   from its normal 16:9 / 4:3 box (see #frame-opus/#frame-noir above)
   so the video fills the screen edge-to-edge instead of staying
   letterboxed to its in-page aspect ratio. */
.tv-film-item__frame:fullscreen,
.tv-film-item__frame:-webkit-full-screen {
  width: 100vw; height: 100vh; aspect-ratio: auto; background: #000;
}
.tv-video__volume { display: flex; align-items: center; gap: 0.6rem; flex: none; }
.tv-video__volume-slider { width: 60px; -webkit-appearance: none; appearance: none; background: transparent; height: 16px; }
.tv-video__volume-slider::-webkit-slider-runnable-track { height: 2px; background: rgba(240, 236, 228, 0.3); }
.tv-video__volume-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 8px; height: 8px; border-radius: 50%; background: #F0ECE4; margin-top: -3px; cursor: pointer; }
.tv-video__volume-slider::-moz-range-track { height: 2px; background: rgba(240, 236, 228, 0.3); border: none; }
.tv-video__volume-slider::-moz-range-thumb { width: 8px; height: 8px; border-radius: 50%; background: #F0ECE4; border: none; cursor: pointer; }
/* Mobile: drop the volume slider entirely -- a 40-60px drag target
   for fine-grained volume isn't a comfortable touch interaction, and
   the mute button alone covers the real need there. */
@media (max-width: 767px) { .tv-video__volume-slider { display: none; } }

/* Return to top — the page's real last word, so scroll ends on a
   deliberate gesture rather than just stopping. */
.tc-return {
  display: flex; justify-content: center; padding: 0 var(--pad-lg) clamp(4rem, 9vw, 6rem);
}
.tc-return__btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0.5rem 0.75rem; color: #FFFFFF; opacity: 0.6;
  background: none; border: none; cursor: pointer;
  transition: opacity var(--dur-fast) var(--ease);
}
.tc-return__btn:hover, .tc-return__btn:focus-visible { opacity: 1; }
.tc-return__btn svg { display: block; transition: transform var(--dur-fast) var(--ease); }
.tc-return__btn:hover svg, .tc-return__btn:focus-visible svg { transform: translateY(-2px); }

/* Lightbox — single-image detail view, minimal chrome, opened from any
   grid image with prev/next paging across the full 32-image set. */
.tc-lightbox {
  position: fixed; inset: 0; z-index: var(--z-overlay);
  background: rgba(8, 8, 8, 0.96);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; visibility: hidden; transition: opacity var(--dur-base) var(--ease);
}
.tc-lightbox.is-open { opacity: 1; visibility: visible; }
.tc-lightbox__img { max-width: min(90vw, 1600px); max-height: 88vh; width: auto; height: auto; object-fit: contain; display: block; }
.tc-lightbox__close {
  position: absolute; top: clamp(0.5rem, 1.5vw, 1rem); right: clamp(1rem, 3vw, 2rem);
  width: 36px; height: 36px; display: flex; align-items: center; justify-content: center;
  background: none; border: none; color: var(--fg-dim); font-size: 1.5rem; line-height: 1; cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.tc-lightbox__close:hover, .tc-lightbox__close:focus-visible { color: var(--fg); }
.tc-lightbox__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
  background: none; border: none; color: var(--fg-dim); cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.tc-lightbox__nav:hover, .tc-lightbox__nav:focus-visible { color: var(--fg); }
.tc-lightbox__nav--prev { left: clamp(0.5rem, 2vw, 1.5rem); }
.tc-lightbox__nav--next { right: clamp(0.5rem, 2vw, 1.5rem); }

@media (max-width: 700px) {
  .tc-progress { display: none; }
}
