/* ============================================================
   Anna Chocholi — landing redesign
   Pure black base, bone/cream foreground, one dusty-rose accent
   ============================================================ */

:root {
  --bg:            #000000;
  --bg-soft:       #050505;
  --fg:            oklch(0.97 0 0);            /* pure warm-neutral white */
  --fg-dim:        oklch(0.72 0 0);            /* mid grey */
  --fg-faint:      oklch(0.57 0 0);            /* darker grey — 4.7:1 on black, the
                                                  lowest step that clears WCAG AA for the
                                                  10px labels that use it (0.48 was 3.2:1) */
  --rule:          oklch(0.22 0 0);
  --accent:        oklch(0.97 0 0);            /* was dusty rose — now white */
  --accent-dim:    oklch(0.60 0 0);            /* mid grey */

  /* display — high-contrast didone for the name only, feels dreamy/romantic and contrasts with the humanist sans */
  --display: "Bodoni Moda", "Didot", "Bodoni 72", "Playfair Display", Georgia, serif;
  --serif:   "Instrument Sans", "Inter Tight", -apple-system, "Helvetica Neue", Arial, sans-serif;
  --sans:    "Inter Tight", "Söhne", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  --mono:    "JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace;

  --nav-h: 72px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

html, body {
  max-width: 100%;
  overflow-x: clip;
}
/* Default image/video/iframe shrink behavior — does NOT apply to the
   hero backdrop, which is intentionally larger than the viewport for
   its slow-zoom cover effect (handled inside .hero with overflow:hidden). */
img:not(.hero__video),
video:not(.hero__video),
iframe:not(.hero__video),
svg:not(.hero__video) {
  max-width: 100%;
}

a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; }

/* ---------- NAV ---------- */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--nav-h);
  padding: 0 32px;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  z-index: 100;
  background: linear-gradient(180deg, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0) 100%);
  backdrop-filter: blur(2px);
}
.nav__mark {
  grid-column: 1;
  justify-self: start;
  font-family: var(--serif);
  font-weight: 300;
  font-style: italic;
  font-size: 18px;
  letter-spacing: 0.01em;
  color: var(--fg);
  display: flex;
  align-items: center;
}
.nav__logo {
  display: block;
  max-height: 40px;
  max-width: 220px;
  width: auto;
  height: auto;
  object-fit: contain;
}
.nav__mark em {
  font-style: inherit;
  color: inherit;
  font-weight: inherit;
}
.nav__links {
  grid-column: 2;
  justify-self: center;
  display: flex;
  gap: 28px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
}
.nav__link {
  color: var(--fg-dim);
  position: relative;
  padding: 4px 0;
  transition: color .2s ease;
  white-space: nowrap;
}
.nav__link:hover, .nav__link.active { color: var(--fg); }
.nav__link::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--fg);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .3s ease;
}
.nav__link:hover::after, .nav__link.active::after { transform: scaleX(1); }

.nav__right {
  display: flex;
  align-items: center;
  gap: 24px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
.nav__dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--fg);
  box-shadow: 0 0 10px rgba(255,255,255,0.5);
  animation: pulse 2.2s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { opacity: .55; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.25); }
}

/* ---------- HERO ---------- */
.hero {
  position: relative;
  /* 100vh is the *large* viewport on iOS: the hero runs taller than the visible
     area and the eyebrow, anchored to its bottom, hides under Safari's toolbar.
     dvh tracks the toolbar; vh stays as the fallback for browsers without it.
     min-height drops on short screens for the same reason — 720px forced the
     hero past the bottom of an iPhone SE (667px). */
  height: 100vh;
  height: 100dvh;
  min-height: 720px;
  width: 100%;
  overflow: hidden;
}
@media (max-height: 760px) {
  .hero { min-height: 0; }
}
.hero__canvas {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.hero__video-layer {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
.hero__zoom-wrap {
  position: absolute;
  inset: 0;
  transform: translateY(var(--video-offset-y, 0));
}
.hero__video {
  position: absolute;
  top: 50%; left: 50%;
  min-width: 100%;
  min-height: 100%;
  width: auto;
  height: auto;
  transform: translate(-50%, -50%);
  object-fit: cover;
}

/* Covers the player until real playback starts, so the landing screen is never
   black. It uses the same cover crop as the video, so the handover does not jump. */
.hero__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  background-size: cover;
  background-position: center;
  pointer-events: none;
  transition: opacity .9s ease;
}
.hero__poster.is-gone { opacity: 0; }

.hero__iframe {
  position: absolute;
  top: 50%; left: 50%;
  width: max(100vw, 177.78vh);
  height: max(56.25vw, 100vh);
  transform: translate(-50%, -50%);
  border: 0;
  pointer-events: none;
}

/* Zoom animation now applies ONLY to the hero name / logo */
.hero__name,
.hero__name-logo {
  animation: heroZoom var(--zoom-dur, 42s) ease-in-out infinite alternate;
  transform-origin: center center;
  will-change: transform;
}
@keyframes heroZoom {
  0%   { transform: scale(1.00); }
  100% { transform: scale(1.12); }
}

.hero__name-logo {
  display: block;
  margin: 0 auto;
  max-width: min(78vw, 860px);
  max-height: 38vh;
  width: auto;
  height: auto;
  object-fit: contain;
}

.hero__video-layer--overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at center, rgba(0,0,0,var(--vig-center, 0.15)) 0%, rgba(0,0,0,var(--vig-edge, 0.7)) 75%, rgba(0,0,0,0.92) 100%),
    linear-gradient(180deg, rgba(0,0,0,0.45) 0%, rgba(0,0,0,0) 28%, rgba(0,0,0,0) 70%, rgba(0,0,0,0.7) 100%);
  pointer-events: none;
}

.hero__content {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: 1fr auto;
  padding: calc(var(--nav-h) + 8px) 32px 48px;
  z-index: 5;
}

.hero__name-wrap {
  align-self: center;
  justify-self: center;
  width: 100%;
  max-width: 1400px;
  position: relative;
  text-align: center;
}

.hero__eyebrow {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 32px;
  text-align: center;
  display: inline-block;
  transform-origin: center center;
}

.hero__name {
  font-family: var(--sans);
  line-height: 1;
  color: var(--fg);
  display: block;
  position: relative;
}

.hero__name span,
.hero__name .chocholi {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
}

.hero__logo {
  display: block;
  margin: 0 auto;
  max-width: min(560px, 70vw);
  max-height: clamp(60px, 14vh, 160px);
  width: auto;
  height: auto;
  object-fit: contain;
}

.hero__tagline {
  margin-top: 32px;
  font-family: var(--sans);
  font-weight: 300;
  font-size: 14px;
  letter-spacing: 0.04em;
  color: var(--fg-dim);
  max-width: 520px;
  margin-left: auto;
  margin-right: auto;
  line-height: 1.5;
}

.hero__meta {
  align-self: end;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: end;
  gap: 24px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
.hero__meta-col { display: flex; flex-direction: column; gap: 6px; }
.hero__meta-col--right { text-align: right; }
.hero__meta strong { color: var(--fg-dim); font-weight: 400; }

.hero__scroll {
  justify-self: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  color: var(--fg-dim);
}
.hero__scroll-line {
  width: 1px;
  height: 44px;
  background: linear-gradient(180deg, transparent, var(--fg-dim), transparent);
  animation: scrollPulse 2.4s ease-in-out infinite;
}
@keyframes scrollPulse {
  0%   { transform: scaleY(0); transform-origin: top; }
  45%  { transform: scaleY(1); transform-origin: top; }
  55%  { transform: scaleY(1); transform-origin: bottom; }
  100% { transform: scaleY(0); transform-origin: bottom; }
}

/* ================== DIRECTION-SPECIFIC ================== */

/* D2 — Liquid Type: letters are spans with ripple transform, handled via JS */
body[data-dir="liquid"] .hero__name .letter {
  display: inline-block;
  will-change: transform, filter;
  transition: filter .4s ease;
}

/* D3 — Kinetic Slices: clip letters into horizontal bands */
.slice-stack {
  display: inline-block;
  position: relative;
  line-height: 0.95;
}
.slice {
  display: block;
  position: relative;
  overflow: hidden;
  transition: transform 0.6s cubic-bezier(.2,.8,.2,1);
  will-change: transform;
}
.slice-inner {
  display: block;
}
body[data-dir="slices"] .slice:nth-child(1) .slice-inner { transform: translateY(0); }

/* D4 — Ghost Reel: project titles ghost under the name */
.ghost-reel {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--accent-dim);
  opacity: 0;
  transition: opacity .5s ease;
  transform: translateY(80px);
}
body[data-dir="ghost"] .ghost-reel { opacity: 0.8; }

/* ---------- WORK GRID ---------- */
.section {
  position: relative;
  padding: 120px 32px;
  border-top: 1px solid var(--rule);
}
.section__head {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 48px;
  margin-bottom: 80px;
  max-width: 1600px;
  margin-left: auto;
  margin-right: auto;
}
.section__label {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--fg-faint);
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.section__label::before {
  content: "";
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--fg-dim);
  margin-top: 4px;
}
.section__title {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(32px, 4.5vw, 72px);
  line-height: 1.02;
  letter-spacing: -0.015em;
  margin: 0;
  color: var(--fg);
  max-width: 900px;
}
.section__title em { font-style: italic; font-weight: 200; color: var(--fg-dim); }
/* The About headline sits at the top of the text column, in an italic serif,
   closed by a thin rule. Font, colour, alignment and the nudge are editor-set,
   with the designed values as fallbacks. Size is here too but cannot win alone:
   the Global tab's title size is written with !important, so tweaks/server.js
   always generates the About size as its own !important rule. Keep the two
   clamps in step. */
#about .section__head {
  grid-template-columns: 1fr;
  gap: 22px;
  max-width: none;
  margin: 0 0 30px;
}
#about .section__title {
  font-family: var(--about-headline-font, "Newsreader", Georgia, serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(30px, 3vw, 46px);
  line-height: 1.16;
  letter-spacing: 0;
  /* Keeps a lone word off the last line at any column width. */
  text-wrap: pretty;
  color: var(--about-headline-color, #EFE9DE);
  text-align: var(--about-headline-align, start);
  margin-top: var(--about-headline-offset, 0px);
  max-width: none;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--about-line-color, #4A463F);
}
#about .section__title em { font-style: inherit; font-weight: inherit; color: inherit; }

.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 24px 24px;
  max-width: 1600px;
  margin: 0 auto;
}

.work {
  position: relative;
  overflow: hidden;
  background: var(--bg-soft);
  aspect-ratio: 16 / 9;
  transition: transform .7s cubic-bezier(.2,.8,.2,1);
}
/* Layout: editorial asymmetric */
.work--lg  { grid-column: span 8; aspect-ratio: 16 / 9; }
.work--md  { grid-column: span 4; aspect-ratio: 4 / 5;  }
.work--sm  { grid-column: span 4; aspect-ratio: 4 / 3;  }
.work--wd  { grid-column: span 6; aspect-ratio: 16 / 9; }

.work__thumb {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  transition: transform 1.2s cubic-bezier(.2,.8,.2,1), filter .6s ease;
  filter: grayscale(0.15) contrast(1.02);
}
.work:hover .work__thumb {
  transform: scale(1.04);
  filter: grayscale(0) contrast(1.05);
}
.work__overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0) 45%, rgba(0,0,0,0.85) 100%);
  opacity: 0.85;
  transition: opacity .4s ease;
}
.work:hover .work__overlay { opacity: 0.55; }

.work__meta {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: 22px;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
  z-index: 2;
  pointer-events: none;
}
.work__title {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(18px, 1.6vw, 26px);
  line-height: 1.1;
  letter-spacing: -0.005em;
  color: var(--fg);
  max-width: 70%;
}
.work__client {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-top: 6px;
}
.work__tag {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-faint);
  white-space: nowrap;
  min-width: 0;
}
/* Once the card is a phone wide, a nowrap tag like
   "[ 01 / Direction, Concert Visuals ]" overshoots its row by ~90px. The page
   carries overflow-x: clip, so it was not clipped-but-scrollable — it was
   unreachable. Let it wrap instead. */
@media (max-width: 700px) {
  .work__meta { gap: 10px; }
  .work__tag {
    white-space: normal;
    text-align: right;
    letter-spacing: 0.16em;
    line-height: 1.5;
  }
}

/* placeholder swatch fills for thumbs */
.work__placeholder {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 30% 30%, var(--p1, #3a2a22) 0%, transparent 60%),
    radial-gradient(ellipse at 70% 70%, var(--p2, #241814) 0%, transparent 60%),
    linear-gradient(135deg, var(--p3, #110b09) 0%, #000 100%);
  filter: blur(0.3px);
}
.work__grain {
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(0deg, rgba(255,255,255,0.02) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(255,255,255,0.015) 0 1px, transparent 1px 3px);
  mix-blend-mode: overlay;
  opacity: 0.6;
  pointer-events: none;
}

/* ---------- ABOUT ---------- */
/* Editorial: the portrait floats and the copy runs around it, rather than the
   two sitting in separate grid columns. A float is the only layout that lets
   line boxes shorten around a sibling — grid and flex both keep the text in
   its own column. */
:root {
  --about-portrait-width: 41%;
  --about-portrait-gap: 24px;
  /* Optical, not geometric: the copy's cap-height sits below its line box, so
     the photo's hard top edge has to drop to look level with it. */
  --about-portrait-top: 0px;
  --about-portrait-ratio: 4 / 5;
}
.about {
  display: grid;
  grid-template-columns: var(--about-portrait-width) minmax(0, 1fr);
  /* The label takes a short first row over the portrait. 1fr keeps it short:
     the text column spans both rows, and its extra height lands here. */
  grid-template-rows: auto 1fr;
  column-gap: var(--about-portrait-gap);
  align-items: start;
  max-width: 1600px;
  margin: 0 auto;
}
/* The portrait runs off the page edge and fades into the black. --about-bleed
   is the distance from .about to that edge: the section padding, or the centring
   margin once the layout reaches its 1600px cap. The fade works because the
   photo's own edges are already close to black. */
#about .section__label { grid-column: 1; grid-row: 1; margin-bottom: 26px; }
.about__portrait {
  --about-bleed: max(32px, (100vw - 1600px) / 2);
  grid-column: 1;
  grid-row: 2;
  margin-top: var(--about-portrait-top);
  margin-left: calc(-1 * var(--about-bleed));
  width: calc(100% + var(--about-bleed));
  aspect-ratio: var(--about-portrait-ratio);
  -webkit-mask-image:
    linear-gradient(to right, #000 calc(100% - var(--about-portrait-fade, 30%)), transparent 100%),
    linear-gradient(to bottom, #000 90%, transparent 100%);
  mask-image:
    linear-gradient(to right, #000 calc(100% - var(--about-portrait-fade, 30%)), transparent 100%),
    linear-gradient(to bottom, #000 90%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
  background:
    radial-gradient(ellipse at 40% 30%, #2a1d18 0%, transparent 70%),
    linear-gradient(160deg, #1a1210 0%, #000 100%);
  position: relative;
  overflow: hidden;
}
.about--portrait-right { grid-template-columns: minmax(0, 1fr) var(--about-portrait-width); }
.about--portrait-right .about__portrait {
  grid-column: 2;
  margin-left: 0;
  margin-right: calc(-1 * var(--about-bleed));
  -webkit-mask-image:
    linear-gradient(to left, #000 calc(100% - var(--about-portrait-fade, 30%)), transparent 100%),
    linear-gradient(to bottom, #000 90%, transparent 100%);
  mask-image:
    linear-gradient(to left, #000 calc(100% - var(--about-portrait-fade, 30%)), transparent 100%),
    linear-gradient(to bottom, #000 90%, transparent 100%);
}
#about .about--portrait-right .section__label { grid-column: 2; }
.about--portrait-right .about__col { grid-column: 1; }
/* The section ends with the portrait rather than with its usual padding. */
#about { padding-bottom: 0; }
.about__col { grid-column: 2; grid-row: 1 / span 2; min-width: 0; padding-bottom: 72px; }
.about__portrait::after {
  content: "[ portrait ]";
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  color: var(--fg-faint);
}
/* Size, colour and the vertical nudge are editor-set; the fallbacks in each
   var() are the designed values, so an untouched theme renders exactly as
   before. The nudge is a margin rather than a transform — translating would
   slide the copy out from under the wrap the floated portrait carved into it,
   leaving the indented opening lines misaligned with the picture. */
.about__copy {
  font-family: var(--about-copy-font, var(--serif));
  /* Regular weight and system smoothing on purpose. Light type with
     antialiased smoothing thins out on black until it is tiring to read,
     even at very high contrast. */
  font-weight: 400;
  -webkit-font-smoothing: auto;
  font-size: var(--about-copy-size, clamp(17px, 1.34vw, 21px));
  line-height: 1.58;
  text-align: var(--about-copy-align, start);
  color: var(--about-copy-color, #E6E0D6);
  margin-top: var(--about-copy-offset, 0px);
  columns: var(--about-copy-columns, 2);
  column-gap: clamp(28px, 3vw, 44px);
  column-rule: 1px solid var(--about-line-color, #4A463F);
}
.about__copy p { margin: 0 0 0.9em; }
.about__copy p:last-child { margin-bottom: 0; }
/* The first paragraph carries the page. The rest step back in colour rather
   than weight: Instrument Sans loads from 400, so anything lighter is not
   available, and thinner strokes are what made this text hard to read. */
.about__copy p:not(:first-child) { color: var(--about-rest-color, #BEB7AB); }
/* Emphasis changes the style only, so a marked phrase stays in its paragraph's tone. */
.about__copy em { font-style: italic; color: inherit; }
.about__copy strong { font-weight: 600; color: inherit; }
.about__list {
  margin-top: 36px;
  padding-top: 20px;
  border-top: 1px solid var(--about-line-color, #4A463F);
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.about__list h3 {
  margin: 0 0 8px;
  color: var(--fg-faint);
  font-weight: 400;
}
.about__list ul { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 4px 18px; }

/* ---------- ARTWORKS ---------- */
.art-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  max-width: 1600px;
  margin: 0 auto;
}
.art {
  aspect-ratio: 3 / 4;
  position: relative;
  overflow: hidden;
  background: var(--bg-soft);
}
.art .work__placeholder { filter: hue-rotate(var(--hr, 0deg)) blur(0.3px); }
.art__caption {
  position: absolute;
  left: 14px;
  bottom: 12px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-dim);
  z-index: 2;
}

/* ---------- CONTACT / FOOTER ---------- */
.contact {
  padding: 140px 32px 96px;
  border-top: 1px solid var(--rule);
  position: relative;
}
.contact__inner {
  max-width: 1400px;
  margin: 0 auto;
}
.contact__title {
  font-family: var(--serif);
  font-weight: 200;
  font-style: italic;
  font-size: clamp(56px, 10vw, 160px);
  line-height: 0.95;
  letter-spacing: -0.02em;
  color: var(--fg);
  margin: 0 0 60px;
}
.contact__title .accent { color: var(--fg-dim); font-style: italic; }

.contact__grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 48px;
  border-top: 1px solid var(--rule);
  padding-top: 48px;
}
.contact__block h3 {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0 0 16px;
  font-weight: 400;
}
.contact__email {
  font-family: var(--serif);
  font-weight: 300;
  font-style: italic;
  font-size: clamp(24px, 3vw, 44px);
  color: var(--fg);
  letter-spacing: -0.01em;
  display: inline-block;
  position: relative;
}
.contact__email::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 2px;
  height: 1px;
  background: var(--fg-dim);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s cubic-bezier(.2,.8,.2,1);
}
.contact__email:hover::after { transform: scaleX(1); }
.contact__block ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; font-family: var(--sans); font-size: 14px; color: var(--fg-dim); }
.contact__block ul a:hover { color: var(--fg); }

.footer-bar {
  margin-top: 80px;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

/* ---------- TWEAKS PANEL ---------- */
.tweaks {
  position: fixed;
  right: 20px;
  bottom: 20px;
  width: 340px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  background: rgba(12,10,10,0.94);
  border: 1px solid var(--rule);
  backdrop-filter: blur(14px);
  padding: 18px 18px 16px;
  z-index: 500;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  color: var(--fg);
  display: none;
}
.tweaks.open { display: block; }
.tweaks h5 {
  margin: 0 0 4px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--fg-faint);
  font-weight: 400;
  display: flex;
  justify-content: space-between;
}
.tweaks h5 + p {
  margin: 0 0 14px;
  color: var(--fg-dim);
  font-size: 10px;
  letter-spacing: 0.02em;
  line-height: 1.4;
}
.tweak-group { margin-bottom: 14px; }
.tweak-group-label {
  display: block;
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 6px;
}
.tweak-options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}
.tweak-opt {
  padding: 8px 10px;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--fg-dim);
  text-align: left;
  font-size: 10px;
  letter-spacing: 0.06em;
  transition: all .2s ease;
  line-height: 1.2;
}
.tweak-opt:hover { color: var(--fg); border-color: var(--fg-dim); }
.tweak-opt.active {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}

.tweak-input {
  width: 100%;
  margin-top: 6px;
  padding: 7px 9px;
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  outline: none;
  cursor: text;
}
.tweak-input:focus { border-color: var(--fg-dim); }

.tweak-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 2px;
  background: var(--rule);
  outline: none;
  cursor: pointer;
}
.tweak-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--fg);
  cursor: pointer;
}
.tweak-range::-moz-range-thumb {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--fg);
  border: 0;
  cursor: pointer;
}
.tweak-close {
  position: absolute;
  top: 10px; right: 10px;
  font-size: 14px;
  color: var(--fg-dim);
}

/* ---------- color swatch control ---------- */
.tweak-color-row {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  margin-top: 10px;
}
.tweak-color-row label {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.tweak-color {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 28px;
  border: 1px solid var(--rule);
  background: transparent;
  cursor: pointer;
  padding: 0;
}
.tweak-color::-webkit-color-swatch-wrapper { padding: 2px; }
.tweak-color::-webkit-color-swatch { border: 0; }
.tweak-color::-moz-color-swatch { border: 0; }
.tweak-color-row .tweak-opt { flex: 0 0 auto; }

/* ---------- tweaks tabs ---------- */
.tweak-tabs {
  display: flex;
  gap: 0;
  margin: 0 0 16px;
  border: 1px solid var(--rule);
}
.tweak-tab {
  flex: 1;
  padding: 8px 6px;
  background: transparent;
  color: var(--fg-dim);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  border: 0;
  border-right: 1px solid var(--rule);
  cursor: pointer;
  transition: all .2s ease;
}
.tweak-tab:last-child { border-right: 0; }
.tweak-tab:hover { color: var(--fg); }
.tweak-tab.active {
  background: var(--fg);
  color: var(--bg);
}

/* ---------- type-row ---------- */
.type-row {
  padding: 10px 0 14px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 12px;
}
.type-row:last-child { border-bottom: 0; }
.type-row__label {
  font-size: 9px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--fg);
  margin-bottom: 10px;
  font-weight: 500;
}
.type-row__sliders {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 10px;
}
.type-row__sliders label {
  display: block;
}
.type-row__sliders label > span {
  display: block;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 4px;
}
.type-row__sliders input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 2px;
  background: var(--rule);
  outline: none;
  cursor: pointer;
}
.type-row__sliders input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--fg);
  cursor: pointer;
}
.type-row__sliders input[type="range"]::-moz-range-thumb {
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--fg);
  border: 0;
  cursor: pointer;
}
.type-row__toggles {
  display: flex;
  gap: 4px;
}
.type-row__toggles .tweak-opt {
  flex: 1;
  text-align: center;
  padding: 6px 8px;
  font-size: 10px;
}

/* ---------- scroll reveal ---------- */
.reveal {
  opacity: 0;
  transform: translateY(36px);
  transition: opacity 1s ease, transform 1s cubic-bezier(.2,.8,.2,1);
}
.reveal.in { opacity: 1; transform: translateY(0); }

/* ---------- responsive ---------- */
@media (max-width: 980px) {
  .nav__links { gap: 18px; }
  .nav__right { display: none; }
  .hero__meta { grid-template-columns: 1fr; text-align: left !important; }
  .hero__meta-col--right { text-align: left; }
  .section { padding: 80px 20px; }
  .section__head { grid-template-columns: 1fr; gap: 20px; margin-bottom: 48px; }
  .grid { gap: 14px; }
  .work--lg, .work--md, .work--sm, .work--wd { grid-column: span 12; }
  :root { --about-portrait-width: 46%; --about-portrait-gap: 28px; }
  .about__list { grid-template-columns: 1fr; }
  .contact__grid { grid-template-columns: 1fr; gap: 32px; }
}


/* ============================================================
   Project Modal
   ============================================================ */
.modal-scrim {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .4s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
}
.modal-scrim.open {
  opacity: 1;
  pointer-events: auto;
}
.modal {
  position: relative;
  width: min(1240px, 100%);
  max-height: 92vh;
  background: #0a0a0a;
  border: 1px solid rgba(255,255,255,0.07);
  border-radius: 2px;
  overflow: hidden;
  display: grid;
  grid-template-rows: auto 1fr;
  transform: translateY(20px) scale(0.985);
  transition: transform .5s cubic-bezier(.2,.8,.2,1);
}
.modal-scrim.open .modal {
  transform: translateY(0) scale(1);
}
.modal__close {
  position: absolute;
  top: 14px;
  right: 16px;
  z-index: 4;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(0,0,0,0.6);
  border: 1px solid rgba(255,255,255,0.15);
  color: var(--fg);
  font-size: 18px;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background .2s ease, border-color .2s ease;
}
.modal__close:hover {
  background: rgba(255,255,255,0.08);
  border-color: rgba(255,255,255,0.3);
}

.modal__video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
  overflow: hidden;
}
.modal__video iframe,
.modal__video video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}
.modal__video-placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--fg-faint);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  background:
    radial-gradient(ellipse at center, var(--p1, #3a2a22) 0%, var(--p2, #1a120e) 50%, #000 100%);
}

.modal__body {
  overflow-y: auto;
  padding: 40px 48px 48px;
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 48px;
  align-items: start;
}
.modal__meta-label {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 18px;
}
.modal__title {
  font-family: var(--sans);
  font-weight: 300;
  font-size: 34px;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin: 0 0 14px;
}
.modal__title em {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 300;
}
.modal__client {
  font-family: var(--sans);
  font-style: italic;
  font-weight: 300;
  font-size: 16px;
  color: var(--fg-dim);
  margin-bottom: 28px;
}
.modal__tag-row {
  display: flex;
  gap: 6px 14px;
  flex-wrap: wrap;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
.modal__tag-row > * + *::before {
  content: "·";
  margin-right: 14px;
  color: rgba(255,255,255,0.2);
}

.modal__desc {
  font-family: var(--sans);
  font-weight: 400;
  -webkit-font-smoothing: auto;
  font-size: 16px;
  line-height: 1.6;
  color: #D0CABE;
  white-space: pre-wrap;
}
.modal__desc p + p {
  margin-top: 1em;
}

.modal__stills {
  grid-column: 1 / -1;
  margin-top: 8px;
}
.modal__stills-label {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--fg-faint);
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(255,255,255,0.08);
  margin-bottom: 20px;
}
.modal__stills-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
}
.modal__still {
  position: relative;
  aspect-ratio: 4 / 3;
  background: #151515;
  overflow: hidden;
  border-radius: 1px;
}
.modal__still img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.modal__still--empty {
  display: grid;
  place-items: center;
  color: rgba(255,255,255,0.22);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  border: 1px dashed rgba(255,255,255,0.08);
}

/* Work card — clearer click affordance */
.work { cursor: pointer; }
.work:focus-visible {
  outline: 1px solid rgba(255,255,255,0.4);
  outline-offset: 4px;
}

/* Tweaks — project editor */
.tweak-project-card {
  border: 1px solid rgba(255,255,255,0.08);
  background: rgba(255,255,255,0.02);
  padding: 14px;
  margin-bottom: 10px;
  border-radius: 2px;
}
.tweak-project-card__head {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.tweak-project-card__num { color: var(--fg-faint); }
.tweak-stills-preview {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.tweak-stills-preview__chip {
  position: relative;
  width: 46px;
  height: 34px;
  background: #222;
  border-radius: 1px;
  overflow: hidden;
}
.tweak-stills-preview__chip img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.tweak-stills-preview__chip button {
  position: absolute;
  top: 1px;
  right: 1px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: rgba(0,0,0,0.75);
  border: 0;
  color: #fff;
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.tweak-projects-scroll {
  max-height: 420px;
  overflow-y: auto;
  margin: 0 -4px;
  padding: 0 4px;
}

@media (max-width: 820px) {
  .modal { max-height: 96vh; }
  .modal__body {
    grid-template-columns: 1fr;
    gap: 28px;
    padding: 28px 22px 32px;
  }
  .modal__title { font-size: 26px; }
}


/* ============================================================
   PHASE 1 CORRECTIONS
   Surgical overrides + new feature styles, layered on top
   of the original Claude Design CSS above.
   ============================================================ */

/* ---------- NAV ---------- */
/* Designer feedback: ANNA CHOCHOLI uppercase, medium-bold, slightly larger.
   Keep the original 3-column grid layout otherwise unchanged. */
.nav__mark {
  font-family: var(--sans);
  font-weight: 500;
  font-style: normal;
  font-size: 21px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.nav__mark em { font-style: normal; }

/* Slightly larger desktop nav links — only a tiny size bump,
   no other design change. Original was 11px. */
.nav__links {
  font-size: 12px;
  gap: 30px;
}

/* Mobile narrow-viewport overrides for headline and eyebrow.
   Both sit in inline-block .chromatic-text wrappers; without these,
   the long uppercase eyebrow with wide letter-spacing and the contact
   headline at clamp() minimum push the page into horizontal overflow. */
@media (max-width: 500px) {
  .hero__eyebrow {
    font-size: clamp(11px, 3.2vw, 14px);
    letter-spacing: 0.18em;
  }
  .contact__title {
    font-size: clamp(36px, 11vw, 56px);
  }
  .contact { padding: 96px 20px 72px; }
}

/* ============================================================
   COMPACT NAV — hamburger + current-section label
   ------------------------------------------------------------
   The inline links stop fitting well before the old 720px
   threshold. .nav__links is white-space:nowrap inside an `auto`
   grid column measuring 815px, so between 721px and ~1010px the
   row ran off the right edge — and html/body carry
   overflow-x: clip, so "Instagram" was not merely clipped, it
   could not be scrolled to at all. Collapsing at 1010px is what
   makes iPad portrait reachable, not a stylistic choice.

   The stacked layout this replaces was worse than it looked: the
   editor writes `.nav__link { font-size: 22px !important }` at
   every width, which beat the 10px rule here, so six links wrapped
   onto three rows spanning 147px inside a 116px bar — 76px of
   them floating over the hero video with no scrim behind.
   ============================================================ */
.nav__menu-btn {
  display: none;
  grid-column: 3;
  justify-self: end;
  align-items: center;
  gap: 12px;
  /* 44px of target even though the burger glyph is 22x14. */
  min-height: 44px;
  padding: 4px 0 4px 14px;
  cursor: pointer;
}
/* The label is a span wearing .nav__link.active so it inherits the generated
   chromatic + glitch treatment. It must not inherit the link's underline —
   that reads as "tap me" on something that is only a status readout. */
.nav__current { padding: 0; }
.nav__current::after { display: none !important; }
/* Beats the generated `.nav__link { font-size: 22px !important }` on
   specificity: an important declaration still loses to a more specific
   important one in the same origin. The bar label is a mobile affordance and
   should not track a number tuned for the desktop nav — that leak is exactly
   what broke the old mobile layout. The menu links below do keep it. */
/* The vw term is a ceiling, not a size. The name, this label and the burger
   share one row — about 350px on a 390px phone — so a value that reads well on
   a tablet can put the name straight through the label on a handset. min()
   lets the editor's number apply wherever it fits and quietly shrinks it where
   it does not, instead of overlapping. The factors are set so the defaults
   (20 / 16) are untouched at 390px and above: 20/390 = 5.13vw, 16/390 =
   4.10vw, rounded up a little. */
.nav .nav__current.nav__link {
  font-size: min(var(--nav-label-size, 16px), 4.2vw) !important;
  letter-spacing: 0.12em;
  color: var(--fg);
  white-space: nowrap;
}
.nav__burger {
  position: relative;
  flex: 0 0 auto;
  width: 22px;
  height: 14px;
  color: var(--fg);
}
.nav__burger span {
  position: absolute;
  left: 0; right: 0;
  height: 1.5px;
  background: currentColor;
  transition: transform .34s cubic-bezier(.2,.8,.2,1), opacity .18s ease;
}
.nav__burger span:nth-child(1) { top: 0; }
.nav__burger span:nth-child(2) { top: 50%; margin-top: -0.75px; }
.nav__burger span:nth-child(3) { bottom: 0; }
.nav__burger.is-open span:nth-child(1) { transform: translateY(6.25px) rotate(45deg); }
.nav__burger.is-open span:nth-child(2) { opacity: 0; }
.nav__burger.is-open span:nth-child(3) { transform: translateY(-6.25px) rotate(-45deg); }

.nav__menu-btn:focus-visible {
  outline: 1px solid var(--fg);
  outline-offset: 6px;
}

@media (max-width: 1010px) {
  :root {
    --nav-h: 68px;
    /* Defaults. tweaks/server.js regenerates all three as one ladder, so a
       size changed in the editor moves the bar and the panel together. */
    --nav-mark-size: 20px;
    --nav-label-size: 16px;
    --nav-menu-link-size: 34px;
  }
  .nav {
    height: var(--nav-h);
    padding: 0 20px;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto;
    gap: 12px;
    align-items: center;
  }
  .nav__mark {
    grid-column: 1;
    justify-self: start;
    /* Capped like the label above, for the same row. */
    font-size: min(var(--nav-mark-size, 20px), 5.2vw);
    letter-spacing: 0.12em;
    min-width: 0;
  }
  /* Deliberately not clipped. An ellipsis guard here is worse than the problem
     it solves: the box would end at the last glyph, and the chromatic
     ::before/::after copies translate up to ~7px past it, so the red/grey
     fringe gets shaved off every render — and with box-sizing: border-box the
     guard also triggered an ellipsis on a name that fitted. "ANNA CHOCHOLI"
     leaves 20px of slack at 390px; a much longer one will visibly crowd the
     label, which is the cue to turn the size down in the editor. */
  .nav__links { display: none; }
  .nav__menu-btn { grid-column: 2; display: flex; }
}

/* Genuinely narrow phones only — 320 and 360. At 430 this step captured every
   current handset (390, 393, 402, 414, 428), so the smaller sizes were what
   nearly everyone saw and the larger ones never appeared outside a tablet.
   320px is what sets these numbers: mark, label and burger share 288px there. */
@media (max-width: 380px) {
  :root {
    --nav-mark-size: 16px;
    --nav-label-size: 13px;
    --nav-menu-link-size: 28px;
  }
  .nav { padding: 0 16px; }
  .nav__mark { letter-spacing: 0.1em; }
  .nav__menu-btn { gap: 10px; padding-left: 10px; }
}

/* ---------- the menu panel ---------- */
/* Sibling of .nav, not a child: .nav has backdrop-filter, and a filter makes
   the element a containing block for fixed descendants, which would pin this
   inside the bar. */
.nav-menu {
  position: fixed;
  inset: 0;
  z-index: 150;
  opacity: 0;
  visibility: hidden;
  transition: opacity .26s ease, visibility 0s linear .26s;
}
.nav-menu.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity .26s ease, visibility 0s;
}
/* Darkened and blurred, not opaque — the page still reads through it. */
.nav-menu__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.58);
  backdrop-filter: blur(22px) saturate(0.85);
  -webkit-backdrop-filter: blur(22px) saturate(0.85);
}
/* A panel hung under the bar, not a full-screen takeover: it opens from the
   burger and claims the corner it belongs to, leaving most of the blurred page
   visible around it. Height follows the links; it only scrolls if a short
   screen makes it. */
.nav-menu__panel {
  position: absolute;
  top: calc(var(--nav-h) - 4px);
  right: 8px;
  width: min(360px, calc(100vw - 20px));
  max-height: calc(100vh - var(--nav-h) - 20px);
  max-height: calc(100dvh - var(--nav-h) - 20px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  padding: 8px 14px 18px;
  /* Nothing at all. The blurred, darkened scrim behind is the entire
     background. The soft radial pool that used to sit here still read as a
     rectangle: a gradient reaching zero at 125% of the box from the top-right
     corner has not reached zero by the left or bottom edge, so it terminated
     on a straight line. Legibility over a bright artwork comes from the links'
     own text-shadow instead, which has no bounds to show. */
  background: none;
  border: 0;
  box-shadow: none;
  transform-origin: top right;
  transform: translateY(-10px) scale(0.96);
  opacity: 0;
  transition: transform .3s cubic-bezier(.2,.8,.2,1), opacity .22s ease;
}
.nav-menu.is-open .nav-menu__panel {
  transform: translateY(0) scale(1);
  opacity: 1;
}
.nav-menu__link {
  display: flex;
  align-items: center;
  /* Right-aligned: with the card gone there is no box edge to line up against,
     so the links hang from the burger they opened from. */
  justify-content: flex-end;
  text-align: right;
  width: 100%;
  /* Comfortably past the 44px minimum; these are the primary targets. */
  min-height: 56px;
  padding: 6px 4px;
  line-height: 1.05;
  /* Carries the legibility on its own now that the panel has no background:
     a tight shadow for edge definition, a wide one to darken whatever the
     blurred page puts behind a given letter. */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55), 0 2px 24px rgba(0, 0, 0, 0.8);
  /* Staggered entrance, keyed off the index the panel sets as --i. */
  opacity: 0;
  transform: translateX(10px);
  transition: opacity .3s ease, transform .3s cubic-bezier(.2,.8,.2,1);
}
.nav-menu.is-open .nav-menu__link {
  opacity: 1;
  transform: translateX(0);
  transition-delay: calc(70ms + var(--i, 0) * 40ms);
}
/* The underline is the nav's own active marker; inside the panel it should hug
   the text from the left rather than span the whole row. */
.nav-menu__link::after {
  left: auto;
  right: 4px;
  width: 2.2em;
  transform-origin: right;
}
/* The panel wants display type, not the desktop bar's size. Matching
   `.nav__link`'s specificity is not enough — the generated rule carries
   !important too and theme-overrides.css loads later, so it wins the tie.
   Three classes deep takes it. */
/* Capped against the panel it sits in rather than the row: the panel is
   min(360px, 100vw - 20px), so the ceiling is a vw term for phones and a flat
   42px beyond, where vw would let the type outgrow the 360px panel. */
.nav-menu__panel .nav-menu__link.nav__link {
  font-size: min(var(--nav-menu-link-size, 34px), 9.5vw, 42px) !important;
}
.nav-menu__panel .nav-menu__link--out.nav__link {
  font-size: calc(min(var(--nav-menu-link-size, 34px), 9.5vw, 42px) * 0.62) !important;
}
/* Set apart by size and weight rather than a rule across the panel — a
   hairline would put back the straight edge the card just lost. */
.nav-menu__link--out {
  color: var(--fg-dim);
  min-height: 48px;
  margin-top: 10px;
}
.nav-menu__link:focus-visible {
  outline: 1px solid var(--fg);
  outline-offset: -4px;
}

/* The butterfly layer carries an inline z-index from the editor (20 by
   default), which put it under the menu's 150 — she flew behind the blur and
   vanished. Lift her over the panel for as long as the menu is open, then let
   the configured value apply again: an !important rule beats a plain inline
   declaration. 160 clears the menu and still passes under the project modal
   (200) and the lightbox (220), which do want to cover her.

   Above the scrim in paint order also means she is not blurred or darkened by
   it — the point of the request. */
body.nav-menu-open #butterfly-layer { z-index: 160 !important; }

@media (prefers-reduced-motion: reduce) {
  .nav__burger span,
  .nav-menu,
  .nav-menu__panel,
  .nav-menu__link {
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }
  .nav-menu__link { transform: none; }
  .nav-menu.is-open .nav-menu__panel { transform: none; }
}

/* ---------- HERO eyebrow at lower edge ---------- */
/* Reposition the eyebrow to the lower third / lower edge as requested.
   Make it bigger, bolder. The original .hero__content / .hero__name-wrap
   structure remains intact for the video backdrop. */
.hero__eyebrow {
  font-family: var(--mono);
  font-size: clamp(14px, 1.5vw, 20px);
  letter-spacing: 0.32em;
  font-weight: 500;
  color: var(--fg);
  text-shadow: 0 2px 24px rgba(0,0,0,0.55);
  margin-bottom: 0;
}
.hero__eyebrow-anchor {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(48px, 9vh, 110px);
  text-align: center;
  z-index: 5;
  pointer-events: none;
  padding: 0 24px;
}

/* The eyebrow is too long for one line below this width, and a free wrap strands
   its last word. Each phrase takes its own line instead. */
.hero__eyebrow-stack { display: none; }
@media (max-width: 820px) {
  .hero__eyebrow-anchor--stacks .hero__eyebrow--line { display: none; }
  .hero__eyebrow-anchor--stacks .hero__eyebrow-stack { display: block; }
  .hero__eyebrow-row { line-height: 1.5; }
}

/* ---------- WORK CARDS — remove gradient overlay, support real images ---------- */
/* Cards are now <button> elements (a11y fix to avoid href="#" scroll-jump).
   Strip native button chrome so the visual treatment is unchanged. */
.work,
.art {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  display: block;
  width: 100%;
}
.work { background: var(--bg-soft); }
.art  { background: transparent; }
.work:focus-visible,
.art:focus-visible {
  outline: 2px solid rgba(255,255,255,0.35);
  outline-offset: 4px;
}
.work__overlay { display: none; }
.work__thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.2s cubic-bezier(.2,.8,.2,1), filter .6s ease;
  filter: grayscale(0.05) contrast(1.02);
}
.work:hover .work__thumb img {
  transform: scale(1.04);
  filter: grayscale(0) contrast(1.05);
}
/* Subtle text plate at the bottom strip — keeps title readable on bright frames */
.work::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 38%;
  background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.62) 100%);
  pointer-events: none;
  z-index: 1;
  transition: opacity .3s ease;
}
.work:hover::after { opacity: 0.85; }

/* Designer requirement: every Motion Art preview card is 16:9.
   Cards still flow in the existing 12-col grid; sizes vary only by
   column span to keep editorial rhythm — but ALL aspect ratios are 16:9. */
.grid { grid-auto-flow: dense; }

.work,
.work.ratio-wide,
.work.ratio-square,
.work.ratio-vertical,
.work.is-feature {
  grid-column: span 6;
  aspect-ratio: 16 / 9;
}

/* Odd card count = a half-empty last row. Give the final card the whole
   width so the grid always ends flush. 21:9 keeps it from towering over
   the 16:9 cards above it. */
@media (min-width: 821px) {
  .grid > .work:last-child:nth-child(odd) {
    grid-column: span 12;
    aspect-ratio: 21 / 9;
  }
}

@media (max-width: 820px) {
  .work,
  .work.ratio-wide,
  .work.ratio-square,
  .work.ratio-vertical,
  .work.is-feature { grid-column: span 12; }
}

/* ---------- DRAWER TOGGLE (Explore more / Collapse) ---------- */
/* The drawer items now flow inside the main grid rather than a second
   container below it, so there is no separate panel to slide open; the
   per-card stagger reveal covers the transition. */

.drawer-toggle {
  display: flex;
  justify-content: center;
  margin-top: 56px;
}
.drawer-toggle__btn {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 14px 28px;
  border: 1px solid var(--rule);
  /* corner-shape only lands on newer engines; the radius applies on its own
     everywhere else, so the button softens rather than snapping back to a
     hard rectangle. */
  border-radius: var(--drawer-radius, 8px);
  corner-shape: var(--drawer-corner-shape, bevel);
  background: transparent;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--fg-dim);
  transition: color .25s ease, border-color .25s ease, background .25s ease;
  cursor: pointer;
}
.drawer-toggle__btn:hover {
  color: var(--fg);
  border-color: var(--fg-dim);
  background: rgba(255,255,255,0.02);
}
.drawer-toggle__chev {
  display: inline-block;
  font-size: 14px;
  line-height: 1;
}

/* ---------- PROJECT MODAL — scrolling video + text + stills ---------- */
/* Override the original .modal that used grid-template-rows: auto 1fr.
   The new modal is a single scrolling container so the video scrolls
   with content (per designer feedback: not sticky/fixed). */
.modal {
  display: flex;
  flex-direction: column;
  grid-template-rows: none;
}
.modal__scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.modal__top {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 36px;
  padding: 56px 48px 24px;
  align-items: start;
}
.modal__top .modal__video {
  aspect-ratio: 16 / 9;
}
.modal__copy {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.modal__copy .modal__title {
  margin: 0 0 10px;
}
.modal__main {
  display: flex;
  flex-direction: column;
  gap: 22px;
  min-width: 0;
}
.modal__credits {
  padding-top: 14px;
  border-top: 1px solid rgba(255,255,255,0.08);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.5;
}
.modal__credits-label {
  color: var(--fg-faint);
  letter-spacing: 0.24em;
  margin-bottom: 12px;
}
.modal__credits-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px 28px;
}
.modal__credits-role { display: block; color: var(--fg-faint); }
.modal__credits-name { display: block; color: #D0CABE; }
/* Additional videos (when a project has more than one) — sit between
   the main top block and the stills grid. */
.modal__videos {
  padding: 0 48px 12px;
}
.modal__videos .modal__stills-label {
  border-top: 1px solid rgba(255,255,255,0.08);
  padding-top: 24px;
  margin-bottom: 16px;
}
.modal__videos-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
}
.modal__video--secondary {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  background: #050505;
}
.modal__video--secondary iframe,
.modal__video--secondary video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.modal__video-caption {
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  color: var(--fg-dim);
  text-transform: uppercase;
}
.modal__top--no-video {
  grid-template-columns: 1fr !important;
}

.modal__stills {
  padding: 0 48px 56px;
}
.modal__stills-label {
  border-top: 1px solid rgba(255,255,255,0.08);
  padding-top: 24px;
  border-bottom: 0;
}

/* Stills redistribute: clicked still expands, others rewrap.
   Designer requirement: max 3 columns on desktop. */
.modal__stills-grid {
  grid-template-columns: repeat(3, 1fr);
  grid-auto-flow: dense;
}
.modal__still {
  cursor: pointer;
  transition: box-shadow .35s ease;
}
.modal__still img { transition: transform .6s cubic-bezier(.2,.8,.2,1); }
.modal__still:hover img { transform: scale(1.03); }
.modal__still.is-expanded {
  grid-column: 1 / -1;
  aspect-ratio: 16 / 9;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.18);
  z-index: 2;
}
.modal__still.is-expanded img { object-fit: contain; background: #050505; }

@media (max-width: 820px) {
  .modal__top {
    grid-template-columns: 1fr;
    gap: 22px;
    padding: 56px 22px 22px;
  }
  /* The column dissolves so the credits land after the text, not before it. */
  .modal__main { display: contents; }
  .modal__top .modal__video { order: 1; }
  .modal__copy { order: 2; }
  .modal__credits { order: 3; }
  .modal__credits-list { grid-template-columns: 1fr; }
  .modal__stills { padding: 0 22px 36px; }
  .modal__stills-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .modal__still.is-expanded { aspect-ratio: 16 / 9; }
  .modal__videos { padding: 0 22px 12px; }
}

/* ---------- ARTWORKS — masonry (CSS columns) ---------- */
/* Override original 4-col grid with CSS columns to respect native ratios. */
.art-grid {
  display: flex;
  align-items: flex-start;
  /* Two variables, deliberately. They started as one, and before that as two
     literals that had silently drifted apart — but the same number in both
     directions does not read as the same amount of space. Stacked down a
     column the eye follows the gap and it looks bigger than the identical
     gutter beside it, so the row gap is set tighter than the column gap
     rather than equal to it. */
  gap: var(--art-grid-gap);
  --cols: 4;
  --art-grid-gap: 16px;
  --art-grid-row-gap: 16px;
}
.art-grid__col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--art-grid-row-gap);
}
.art-grid .art {
  display: block;
  position: relative;
  width: 100%;
  margin: 0;
  aspect-ratio: auto;
  overflow: hidden;
  cursor: pointer;
}
.art__media {
  position: relative;
  width: 100%;
  overflow: hidden;
}
.art__media img {
  width: 100%;
  height: auto;
  display: block;
  transition: transform 1.2s cubic-bezier(.2,.8,.2,1);
}
.art:hover .art__media img { transform: scale(1.03); }
.art__placeholder {
  width: 100%;
  aspect-ratio: 1 / 1;
  background:
    radial-gradient(ellipse at 35% 40%, #1a1814 0%, transparent 65%),
    linear-gradient(160deg, #141210 0%, #050505 100%);
}

/* Caption on lower edge — small dark plate (no big gradient). */
.art__caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 12px 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  pointer-events: none;
  z-index: 2;
  /* override original */
  font-family: var(--serif);
  font-size: 14px;
  letter-spacing: -0.005em;
  text-transform: none;
  color: var(--fg);
}
.art__caption-title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 14px;
  line-height: 1.25;
  color: var(--fg);
}
.art__caption-sub {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

/* At two columns on a phone a card is ~171px wide, and the caption plate was
   built for a 380px one: full titles wrapped to three lines and swallowed up
   to 55% of the artwork. Scale the plate with the card and clamp the title. */
@media (max-width: 560px) {
  .art__caption { padding: 7px 8px 8px; gap: 2px; }
  .art__caption-title {
    font-size: 11px;
    line-height: 1.2;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .art__caption-sub {
    font-size: 8px;
    letter-spacing: 0.12em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* Two columns hold all the way down. Dropping to one at 500px put a single
   image on each screen and ran the collapsed section to ~7 viewport heights,
   which read as the images being flung far apart — while the BTS grid
   immediately below stayed a tight 2-up mosaic. The gutter tightens with the
   column width so the pair sits at roughly BTS's 170px/10px on a phone.
   These are defaults; tweaks/server.js regenerates the whole ladder. */
@media (max-width: 1100px) { .art-grid { --cols: 3; } }
@media (max-width: 820px)  { .art-grid { --cols: 2; --art-grid-gap: 12px; --art-grid-row-gap: 10px; } }
@media (max-width: 560px)  { .art-grid { --cols: 2; --art-grid-gap: 8px;  --art-grid-row-gap: 3px; } }

/* ---------- ARTWORK LIGHTBOX ---------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 220;
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lightbox.open { opacity: 1; pointer-events: auto; }
.lightbox__stage {
  position: relative;
  width: min(96vw, 1500px);
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 28px;
}
.lightbox__media {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
}
.lightbox__media img {
  max-width: 100%;
  max-height: 78vh;
  object-fit: contain;
  display: block;
}
.lightbox__placeholder {
  width: min(60vw, 600px);
  height: min(60vh, 600px);
  display: grid;
  place-items: center;
  background: radial-gradient(ellipse at 35% 40%, #1a1814 0%, transparent 65%), #050505;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(20px, 3vw, 36px);
  text-align: center;
  color: var(--fg-dim);
  padding: 32px;
}
.lightbox__meta {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
}
.lightbox__title {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(18px, 1.8vw, 26px);
  color: var(--fg);
  letter-spacing: -0.01em;
}
.lightbox__sub {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.lightbox__count {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.30em;
  color: var(--fg-faint);
  margin-top: 6px;
}
.lightbox__close,
.lightbox__nav {
  position: absolute;
  top: 50%;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.18);
  background: rgba(0,0,0,0.5);
  color: var(--fg);
  font-size: 26px;
  cursor: pointer;
  display: grid;
  place-items: center;
  z-index: 5;
  transition: background .2s ease, border-color .2s ease;
}
.lightbox__close { top: 24px; right: 24px; transform: none; font-size: 22px; }
.lightbox__nav { transform: translateY(-50%); }
.lightbox__nav--prev { left: 24px; }
.lightbox__nav--next { right: 24px; }
.lightbox__close:hover, .lightbox__nav:hover {
  background: rgba(255,255,255,0.08);
  border-color: rgba(255,255,255,0.35);
}
@media (max-width: 820px) {
  .lightbox__stage { padding: 56px 16px; gap: 14px; }
  .lightbox__media img { max-height: 64vh; }
  .lightbox__close { top: 14px; right: 14px; width: 40px; height: 40px; font-size: 18px; }
  .lightbox__nav { width: 40px; height: 40px; font-size: 22px; }
  .lightbox__nav--prev { left: 8px; }
  .lightbox__nav--next { right: 8px; }
}

/* ---------- CONTACT title — original short copy "Let's make / something dreamt." */
/* Original sizing preserved; intentionally large Bodoni statement. */

/* ---------- About / portrait fallback ---------- */
/* A real portrait is provided, so the "[ portrait ]" placeholder label is
   suppressed. Size lives in --about-portrait-width. */
.about__portrait::after { content: none; }
.about__portrait img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ============================================================
   Chromatic aberration text — inert base.
   The pseudo-element layers exist on every targeted element but
   stay invisible until theme-overrides.css enables them via
   per-element rules (animation + opacity > 0).
   ============================================================ */
.chromatic-text {
  position: relative;
  display: inline-block;
  max-width: 100%;
  overflow-wrap: break-word;
}
.chromatic-text::before,
.chromatic-text::after {
  content: attr(data-text);
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
  opacity: 0;
  color: inherit;
  font: inherit;
  font-style: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  white-space: inherit;
  overflow-wrap: inherit;
}

/* ============================================================
   BEHIND THE SCENES — film strip + process grid
   ------------------------------------------------------------
   Every --bts-* value below is a default. tweaks/server.js writes
   the same names into theme-overrides.css, which loads after this
   file and therefore wins. Anything that also needs to change
   responsively is expressed as a *multiplier* of the themed value
   (--bts-frame-scale), because a media query here would lose to
   the generated :root block.
   ============================================================ */
:root {
  --bts-frame-h: 220px;
  --bts-frame-ratio: 3 / 2;
  --bts-frame-aspect: 1.5;
  --bts-frame-gap: 10px;
  --bts-frame-scale: 1;
  --bts-sprocket-size: 12px;
  --bts-sprockets-per-frame: 8;
  --bts-sprocket-gutter: 20px;
  --bts-film-base: #141414;
  --bts-sprocket-color: #000000;
  --bts-edge-fade: 10%;

  --bts-overlay-opacity: 0.22;
  --bts-grain-size: 180px;
  --bts-grain-seconds: 0.8s;
  --bts-halftone-size: 3px;
  --bts-photo-sat: 0.55;
  --bts-photo-con: 1.12;
  --bts-photo-bri: 1;
  --bts-photo-sepia: 0.18;
  --bts-photo-grade:
    saturate(var(--bts-photo-sat))
    contrast(var(--bts-photo-con))
    brightness(var(--bts-photo-bri))
    sepia(var(--bts-photo-sepia));
  --bts-vignette: 0.35;
  --bts-tint: #f0e8da;
  --bts-tint-opacity: 0.06;
  --bts-paper-inset: 0px;
  --bts-paper-color: #0a0a0a;
  --bts-frame-bg: #0d0d0d;
  --bts-border-w: 1px;
  --bts-border-color: #1c1c1c;
  --bts-grid-gap: 16px;

  /* Derived. The pitch is what one stop-motion step has to land on: a frame
     plus its gap. Frames carry margin-right rather than the track carrying
     `gap`, so each half of the track is exactly frames x pitch wide and
     translating -50% rejoins the loop on a frame edge. */
  --bts-frame-hh: calc(var(--bts-frame-h) * var(--bts-frame-scale));
  --bts-frame-w: calc(var(--bts-frame-hh) * var(--bts-frame-aspect));
  --bts-pitch: calc(var(--bts-frame-w) + var(--bts-frame-gap));
  --bts-sprocket-pitch: calc(var(--bts-frame-hh) / var(--bts-sprockets-per-frame));
}

/* ---------- shared analogue treatment ---------- */
/* isolate: the paper layers must blend with the photograph only. Without it
   they blend with the film base behind, and the strip and the grid end up
   looking like two different processes. */
.bts-photo {
  position: relative;
  display: block;
  width: 100%;
  overflow: hidden;
  isolation: isolate;
  background: var(--bts-frame-bg);
  border: var(--bts-paper-inset) solid var(--bts-paper-color);
  box-shadow: 0 0 0 var(--bts-border-w) var(--bts-border-color);
}
.bts-photo__img {
  display: block;
  width: 100%;
  height: auto;
  transition: transform 1.2s cubic-bezier(.2,.8,.2,1);
}
/* The analogue treatment belongs to the film strip. The grid shows the
   photographs as shot — grade, grain, tint and vignette all stop at
   .bts-frame. tweaks/server.js can hand them back to the grid on request. */
.bts-frame .bts-photo__img {
  /* Grade the photograph, then blend paper on top. Moving this filter to the
     wrapper would grade the overlays too — a different, muddier picture. */
  filter: var(--bts-photo-grade);
}
.bts-photo__placeholder {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    repeating-linear-gradient(135deg,
      rgba(255,255,255,0.030) 0 1px, transparent 1px 9px),
    linear-gradient(158deg, #181510 0%, #0b0b0b 62%, #060606 100%);
}
.bts-photo__slug {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.28em;
  color: var(--fg-faint);
  opacity: 0.8;
}

.bts-photo__tint,
.bts-photo__halftone,
.bts-photo__grain,
.bts-photo__vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  display: none;
}
.bts-frame .bts-photo__tint,
.bts-frame .bts-photo__vignette { display: block; }
.bts-photo__tint {
  background: var(--bts-tint);
  opacity: var(--bts-tint-opacity);
  mix-blend-mode: soft-light;
}
.bts-photo__halftone {
  background-image: radial-gradient(circle at 50% 50%,
    rgba(255,255,255,0.55) 0 28%, transparent 30%);
  background-size: var(--bts-halftone-size) var(--bts-halftone-size);
  opacity: var(--bts-overlay-opacity);
  mix-blend-mode: overlay;
}
.bts-photo__grain {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.6'/%3E%3C/svg%3E");
  background-size: var(--bts-grain-size) var(--bts-grain-size);
  opacity: var(--bts-overlay-opacity);
  mix-blend-mode: overlay;
}
.bts-photo__vignette {
  background: radial-gradient(125% 105% at 50% 50%,
    transparent 42%, rgba(0,0,0,var(--bts-vignette)) 100%);
}

[data-bts-overlay="grain"]    .bts-frame .bts-photo__grain,
[data-bts-overlay="both"]     .bts-frame .bts-photo__grain    { display: block; }
[data-bts-overlay="halftone"] .bts-frame .bts-photo__halftone,
[data-bts-overlay="both"]     .bts-frame .bts-photo__halftone { display: block; }

/* Grain boil — eight scattered offsets held flat by steps(1, end), so the
   noise resettles rather than drifting diagonally the way a linear sweep
   quantised by steps(n) would. */
[data-bts-boil="on"] .bts-photo__grain {
  animation: btsGrainBoil var(--bts-grain-seconds) steps(1, end) infinite;
}
@keyframes btsGrainBoil {
  0%    { background-position:   0px    0px; }
  12.5% { background-position: -41px   23px; }
  25%   { background-position:  17px  -53px; }
  37.5% { background-position: -73px  -11px; }
  50%   { background-position:  59px   37px; }
  62.5% { background-position: -19px   67px; }
  75%   { background-position:  83px  -29px; }
  87.5% { background-position: -61px  -71px; }
  100%  { background-position:   0px    0px; }
}

/* ---------- module sub-headings ---------- */
.bts-module-head {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--fg-faint);
  max-width: 1600px;
  margin: 0 auto 22px;
}
.bts-grid + .drawer-toggle { margin-top: 48px; }
.bts-strip-wrap + .bts-module-head { margin-top: 84px; }

/* ---------- the film strip ---------- */
/* Full-bleed: the strip runs past the section gutter so it reads as film
   passing through the page rather than a boxed carousel. */
.bts-strip-wrap {
  margin-left: -32px;
  margin-right: -32px;
}
.bts-strip-wrap .bts-module-head {
  padding-left: 32px;
  padding-right: 32px;
}
.bts-strip {
  position: relative;
  overflow: hidden;
  background: var(--bts-film-base);
  padding: var(--bts-sprocket-gutter) 0;
  border-top: 1px solid #000;
  border-bottom: 1px solid #000;
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0, #000 var(--bts-edge-fade),
    #000 calc(100% - var(--bts-edge-fade)), transparent 100%);
  mask-image: linear-gradient(90deg,
    transparent 0, #000 var(--bts-edge-fade),
    #000 calc(100% - var(--bts-edge-fade)), transparent 100%);
}
.bts-strip__track {
  position: relative;
  display: flex;
  width: max-content;
  will-change: transform;
  /* Both the duration and the timing function come from JS: the step count is
     frames x steps-per-frame, and frames is a property of the data. Passing the
     whole timing function as one variable is what lets the strip switch
     between a stepped pan and a smooth one without a second rule. */
  animation: btsStripPan var(--bts-strip-duration, 40s)
             var(--bts-strip-timing, linear) infinite;
}
.bts-strip--reverse .bts-strip__track { animation-direction: reverse; }
.bts-strip--pause:hover .bts-strip__track,
.bts-strip--pause:focus-within .bts-strip__track { animation-play-state: paused; }
@keyframes btsStripPan {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
.bts-strip__half { display: flex; }

/* Sprocket holes live on the track, not the strip, so they travel with the
   film. Their period is the frame pitch, so the -50% loop lands them back in
   register. */
.bts-strip--sprockets .bts-frame::before,
.bts-strip--sprockets .bts-frame::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: var(--bts-sprocket-gutter);
  background-image: linear-gradient(90deg,
    transparent 0 calc(50% - var(--bts-sprocket-size) / 2),
    var(--bts-sprocket-color) calc(50% - var(--bts-sprocket-size) / 2)
      calc(50% + var(--bts-sprocket-size) / 2),
    transparent calc(50% + var(--bts-sprocket-size) / 2) 100%);
  background-size: var(--bts-sprocket-pitch) calc(var(--bts-sprocket-size) * 0.72);
  background-repeat: repeat-x;
  background-position: 0 center;
  pointer-events: none;
}
.bts-strip--sprockets .bts-frame::before { top: calc(var(--bts-sprocket-gutter) * -1); }
.bts-strip--sprockets .bts-frame::after  { bottom: calc(var(--bts-sprocket-gutter) * -1); }

/* --frame-aspect and --frame-ratio come from the item, so a landscape and an
   upright photo sit side by side at the same height with different widths. */
.bts-frame {
  position: relative;
  flex: 0 0 auto;
  width: calc(var(--bts-frame-hh) * var(--frame-aspect, var(--bts-frame-aspect)));
  margin-right: var(--bts-frame-gap);
}
.bts-frame .bts-photo {
  height: var(--bts-frame-hh);
  aspect-ratio: var(--frame-ratio, var(--bts-frame-ratio));
}
.bts-frame .bts-photo__img {
  height: 100%;
  object-fit: cover;
}
.bts-frame__caption {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-top: 8px;
  padding: 0 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- the process / backstage grid ---------- */
/* Explicit columns, not CSS columns. CSS columns fill column one to the bottom
   before starting column two, so the order set in the editor reads as vertical
   runs rather than left to right — and adding items reflows every column, which
   is why the grid used to rearrange itself when the drawer opened. Here each
   item is dealt to a column by its index, so expanding only appends. */
.bts-grid {
  display: flex;
  align-items: flex-start;
  gap: var(--bts-grid-gap);
  max-width: 1600px;
  margin: 0 auto;
  --cols: 5;
  /* Falls back to the column gap so a theme written before this variable
     existed renders exactly as it did. */
  --bts-grid-row-gap: var(--bts-grid-gap);
}
.bts-grid__col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--bts-grid-row-gap);
}
/* Same reasoning as the artworks grid: at phone width the vertical runs read
   longer than the identical gutter beside them. */
@media (max-width: 700px) {
  .bts-grid { --bts-grid-row-gap: 4px; }
}
.bts-item {
  display: block;
  position: relative;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
/* Nothing in the grid opens or zooms any more: a photo is a photo, and a video
   plays where it sits. Only the video tiles are interactive. */
.bts-item { cursor: default; }
.bts-item__media { position: relative; overflow: hidden; }
.bts-item--preview { cursor: pointer; }
.bts-item:focus-visible { outline: 1px solid var(--fg-dim); outline-offset: 3px; }

.bts-item__play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 42px; height: 42px;
  border: 1px solid rgba(255,255,255,0.55);
  border-radius: 50%;
  background: rgba(0,0,0,0.28);
  cursor: pointer;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  z-index: 3;
  pointer-events: none;
  transition: background .25s ease, border-color .25s ease;
}
.bts-item__play::after {
  content: "";
  position: absolute;
  left: 52%; top: 50%;
  transform: translate(-50%, -50%);
  border-style: solid;
  border-width: 6px 0 6px 10px;
  border-color: transparent transparent transparent rgba(255,255,255,0.9);
}
/* Hidden while it runs — but it returns on pause, so a click always has
   something to aim at. The whole tile takes the tap, which is why the mark itself
   ignores the pointer; it stays a real button for the keyboard. */
.bts-item--running .bts-item__play,
.bts-item--running:hover .bts-item__play { opacity: 0; }
.bts-item--playing:not(.bts-item--running) .bts-item__play {
  animation: btsLoading 1s ease-in-out infinite alternate;
}
@keyframes btsLoading { from { opacity: 1; } to { opacity: 0.35; } }
.bts-item:hover .bts-item__play {
  background: rgba(0,0,0,0.5);
  border-color: rgba(255,255,255,0.85);
}

.bts-item__caption {
  padding: 7px 1px 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.bts-item__title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 11px;
  line-height: 1.3;
  color: var(--fg-dim);
}
.bts-item--empty .bts-item__title { color: var(--fg-dim); }
.bts-item__sub {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

/* Video in the lightbox keeps a 16:9 stage instead of shrink-wrapping an
   image the way the artwork lightbox does. */
.lightbox__media--video {
  width: min(1200px, 90vw);
  aspect-ratio: 16 / 9;
}
.lightbox__media--video iframe,
.lightbox__media--video video {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

@media (max-width: 1000px) {
  :root { --bts-frame-scale: 0.82; }
}
@media (max-width: 980px) {
  .bts-strip-wrap { margin-left: -20px; margin-right: -20px; }
  .bts-strip-wrap .bts-module-head { padding-left: 20px; padding-right: 20px; }
}
@media (max-width: 700px) {
  :root { --bts-frame-scale: 0.62; }
  .bts-item__play { width: 34px; height: 34px; }
  .bts-item__play::after { border-width: 5px 0 5px 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .bts-strip:not(.bts-strip--motion-always) .bts-strip__track { animation-name: none; }
  .bts-strip:not(.bts-strip--motion-always) .bts-photo__grain { animation-name: none; }
  .bts-item:hover .bts-photo__img { transform: none; }
}

/* ============================================================
   SUMMON — the circle in Contact that calls the butterfly
   ------------------------------------------------------------
   Same convention as the BTS block: these are defaults, and
   tweaks/server.js writes the same names into theme-overrides.css,
   which loads later and wins. Responsive changes go through
   --summon-scale so a media query here does not lose to the
   generated :root block.
   ============================================================ */
:root {
  --summon-size: 180px;
  --summon-scale: 1;
  /* The drawn box. A cap in vw is layered on for narrow screens so the circle
     keeps shrinking with the viewport instead of holding a fixed px size —
     the themed --summon-size still sets the ceiling. */
  --summon-box: calc(var(--summon-size) * var(--summon-scale));
  --summon-left: 73.5%;
  --summon-top: 82px;
  --summon-stroke: 1.5px;
  --summon-color: var(--fg);
  --summon-opacity: 0.85;
  --summon-hover-opacity: 1;
  --summon-label-tracking: 0.18em;
  --summon-label-color: var(--fg-dim);
  --summon-label-opacity: 0.75;
  --summon-label-hover-opacity: 1;
  --summon-held-fill: #ffffff;
  --summon-held-glow: drop-shadow(0 0 7px rgba(255,255,255,0.765))
                      drop-shadow(0 0 34px rgba(255,255,255,0.468));
  /* Same numbers, as a shadow of the shape rather than of the ink — see the
     note on .summon__ring. */
  --summon-ring-glow: 0 0 7px rgba(255,255,255,0.765),
                      0 0 34px rgba(255,255,255,0.468),
                      inset 0 0 7px rgba(255,255,255,0.765);
}

/* The contact inner box is the positioning context. It has no other
   absolutely-positioned children, so this is safe to add here. */
.contact__inner { position: relative; }

.summon {
  position: absolute;
  left: var(--summon-left);
  top: var(--summon-top);
  width: var(--summon-box);
  height: var(--summon-box);
  transform: translate(-50%, -50%);
  z-index: 3;
}
.summon__hit {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: 50%;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Without this a drag on a touch screen scrolls the page instead of
     moving the lure, and pointermove never arrives. */
  touch-action: none;
}
.summon__hit:focus-visible { outline: 1px solid var(--fg-dim); outline-offset: 6px; }
/* The ring carries the same glow as the ball you drag out of it, so the circle
   reads as lit rather than drawn and asks to be touched. Same controls, same
   numbers — the Summon panel's glow settings generate both.

   A box-shadow, not the ball's filter, and the difference matters. Filters
   blur the element's own alpha: the ball is a filled 190px disc with ink to
   spare, but the ring is a 1.5px stroke, and spreading that across a 34px blur
   leaves a peak alpha under one percent. It computed perfectly and was
   invisible — which is exactly how it shipped the first time. A box-shadow
   blurs the border-box shape instead, so it keeps its stated alpha at the
   edge; outer and inset together light both sides of the stroke. */
.summon__ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: var(--summon-stroke) solid var(--summon-color);
  opacity: var(--summon-opacity);
  pointer-events: none;
  box-shadow: var(--summon-ring-glow);
  transition: opacity .3s ease, transform .45s cubic-bezier(.2,.8,.2,1),
              box-shadow .3s ease;
}
.summon__hit:hover ~ .summon__text,
.summon__hit:focus-visible ~ .summon__text {
  opacity: var(--summon-label-hover-opacity);
}
/* The ring is a child of the button, not a sibling — the label is the
   sibling. */
.summon__hit:hover .summon__ring,
.summon__hit:focus-visible .summon__ring {
  opacity: var(--summon-hover-opacity);
  transform: scale(1.03);
}
/* While held, the anchored ring gives way to the one in the cursor. */
.summon[data-summon-held] .summon__ring { opacity: 0; transform: scale(0.94); }
.summon[data-summon-held] .summon__text { opacity: 0; }

/* The type runs around the ring on two arcs. Its font-size comes from the
   element's inline style in viewBox units, so it is a proportion of the circle
   and scales with it. */
.summon__text {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  opacity: var(--summon-label-opacity);
  pointer-events: none;
  transition: opacity .3s ease;
}
.summon__text text {
  font-family: var(--mono);
  font-weight: 400;
  letter-spacing: var(--summon-label-tracking);
  text-transform: uppercase;
  fill: var(--summon-label-color);
}

/* The travelling copy. Outer element positions, inner one sizes — see the
   note in summon.js about why this is not a transform: scale. */
.summon-held {
  position: fixed;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity .18s ease;
}
.summon-held.is-on { opacity: 1; }
/* Held, it is a filled glowing dot rather than the hairline ring it sits in
   the page as. The glow is a filter rather than a box-shadow so it matches the
   butterfly's, which has to be a filter to follow the sprite's alpha. */
.summon-held__ring {
  position: absolute;
  left: 0;
  top: 0;
  box-sizing: border-box;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: var(--summon-stroke) solid var(--summon-held-fill);
  background: var(--summon-held-fill);
  filter: var(--summon-held-glow);
}

/* Reduced motion: the circle stays as a mark in the layout, it just does not
   summon anything. summon.js sets the attribute and skips its listeners. */
.summon[data-summon-inert] .summon__hit { cursor: default; }
.summon[data-summon-inert] .summon__text { display: none; }

@media (max-width: 1240px) {
  :root { --summon-scale: 0.82; --summon-left: 77%; --summon-top: 70px; }
}
@media (max-width: 1000px) {
  :root { --summon-box: min(calc(var(--summon-size) * var(--summon-scale)), 40vw); }
  .summon {
    /* relative, not static: the hit area and ring are absolute children, and
       with no positioned ancestor here they resolve against .contact__inner
       and stretch across the whole section. */
    position: relative;
    left: auto;
    top: auto;
    transform: none;
    margin: 8px auto 56px;
  }
  :root { --summon-scale: 0.86; }
}
@media (max-width: 560px) {
  :root {
    --summon-scale: 0.7;
    --summon-box: min(calc(var(--summon-size) * var(--summon-scale)), 34vw);
  }
  .summon { margin: 4px auto 40px; }
}

/* ============================================================
   CONTACT ACCENT — the write block
   ============================================================ */
:root {
  --contact-accent: #f0b45a;
}

/* ---------- the footer, now its own landmark ---------- */
/* It used to sit inside .contact__inner and inherit that width. Behind the
   Scenes now comes after Contact, so the footer had to move out to stay at the
   bottom of the page — and bring the width with it. */
.site-footer {
  padding: 0 32px 96px;
  border-top: 1px solid var(--rule);
}
.site-footer__inner {
  max-width: 1400px;
  margin: 0 auto;
}
.site-footer .footer-bar {
  margin-top: 0;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
}

/* ---------- the closing contact coda ---------- */
/* Behind the Scenes is the last section, so the page used to end on a
   copyright line. This puts the address back within reach of the bottom. */
.site-footer__coda {
  padding: 72px 0 40px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 18px;
}
.site-footer__label {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0;
}
/* Smaller than the one in Contact — a reprise, not a second headline. */
.site-footer__email {
  font-family: var(--serif);
  font-weight: 300;
  font-style: italic;
  font-size: clamp(22px, 2.4vw, 34px);
  letter-spacing: -0.01em;
  color: var(--fg);
  position: relative;
  display: inline-block;
}
.site-footer__email::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 2px;
  height: 1px;
  background: var(--fg-dim);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s cubic-bezier(.2,.8,.2,1);
}
.site-footer__email:hover::after { transform: scaleX(1); }
.site-footer__links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.site-footer__links a:hover { color: var(--fg); }

@media (max-width: 640px) {
  .site-footer__coda { padding: 48px 0 32px; gap: 14px; }
  .site-footer__links { gap: 4px 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .site-footer__email::after { transition: none !important; }
}

@media (max-width: 980px) {
  .site-footer { padding: 0 20px 72px; }
}
@media (max-width: 640px) {
  .site-footer .footer-bar { flex-direction: column; gap: 8px; }
}

/* Two text columns need room beside the portrait; below this they get too
   narrow to read. Then the portrait and text stack. */
@media (max-width: 1100px) {
  .about__copy { columns: 1; }
}
@media (max-width: 980px) {
  .about__portrait { --about-bleed: 20px; }
}
@media (max-width: 820px) {
  .about,
  .about--portrait-right { grid-template-columns: 1fr; grid-template-rows: none; }
  /* The column dissolves so the headline can come before the portrait. */
  .about__col { display: contents; }
  #about .section__label,
  #about .about--portrait-right .section__label { grid-column: auto; grid-row: auto; order: 0; margin-bottom: 20px; }
  #about .section__head { order: 1; }
  .about__portrait,
  .about--portrait-right .about__portrait {
    order: 2;
    grid-column: auto;
    grid-row: auto;
    width: 100%;
    margin: 0 0 28px;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .about__copy { order: 3; }
  .about__list { order: 4; margin-bottom: 72px; }
}

/* BTS grid — video previews itself on hover */
.bts-item__preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .35s ease;
  pointer-events: none;
  z-index: 1;
}
.bts-item--preview:focus-visible .bts-item__preview,
.bts-item--running .bts-item__preview {
  opacity: 1;
}
/* The play mark is an affordance for the still; once the clip is running it
   is just something in the way. */
.bts-item--preview:focus-visible .bts-item__play {
  opacity: 0;
  transition: opacity .25s ease;
}
/* Hover only where it is real. On a phone :hover sticks after a tap, which would
   leave a stopped clip covering its still. A touch screen plays on tap instead;
   the clip fetches nothing until then, as preload is none. */
@media (hover: hover) {
  .bts-item--preview:hover .bts-item__preview { opacity: 1; }
  .bts-item--preview:hover .bts-item__play {
    opacity: 0;
    transition: opacity .25s ease;
  }
}


/* ============================================================
   ACCESSIBILITY
   ------------------------------------------------------------
   Added after an audit of the site at phone widths. Nothing here
   changes the visual design at rest — every rule is either
   off-screen, keyboard-only, or scoped to coarse pointers.
   ============================================================ */

/* Off-screen but still announced. Not `display: none`, which would take it out
   of the accessibility tree along with everything else. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- skip link ---------- */
/* First tab stop on the page. Hidden until focused, so it costs the design
   nothing and saves a keyboard user six nav stops on every section. */
.skip-link {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 300;
  padding: 12px 18px;
  background: #000;
  border: 1px solid var(--fg);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  transform: translateY(-200%);
  transition: transform .2s ease;
}
.skip-link:focus { transform: translateY(0); }
/* <main> only takes focus as the skip link's destination; it should not paint
   a ring around the whole page when it does. */
main:focus { outline: none; }

/* ---------- visible focus ---------- */
/* The stylesheet had ten :focus rules and none covered the nav, the drawer
   toggles, the contact links or the lightbox controls — a keyboard user could
   not see where they were standing anywhere in the header or footer. */
.nav__link:focus-visible,
.nav__mark:focus-visible,
.drawer-toggle__btn:focus-visible,
.contact__email:focus-visible,
.contact__block a:focus-visible,
.site-footer a:focus-visible,
.lightbox__close:focus-visible,
.lightbox__nav:focus-visible,
.modal__close:focus-visible {
  outline: 1px solid var(--fg);
  outline-offset: 4px;
}
.skip-link:focus-visible { outline: 1px solid var(--fg); outline-offset: 2px; }

/* ---------- touch targets ---------- */
/* Measured at 390px: the footer's social links were 17px tall, the contact
   email 30px and the BTS play buttons 34px, against a 44px minimum. Scoped to
   coarse pointers so the desktop rhythm is untouched. */
@media (pointer: coarse) {
  .contact__email {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  .contact__block ul li { margin: 0; }
  .contact__block ul a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  .site-footer a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  .bts-item__play { width: 44px; height: 44px; }
  .bts-item__play::after { border-width: 6px 0 6px 10px; }
  /* The mark is 16px of text; the tappable area is what has to be 44px, not
     the glyph. Padding keeps the type where the composition puts it. */
  .nav__mark {
    min-height: 44px;
    padding: 6px 0;
  }
  .modal__close { width: 44px; height: 44px; }
}
.skip-link { min-height: 44px; }

/* ---------- reduced motion ---------- */
/* The existing block covered the film strip and the chromatic text only. The
   reveal animation — a 36px translate on every card on the page — and the
   hero's slow zoom both ran regardless. */
@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .hero__video,
  .hero__iframe,
  .hero__video-layer img { animation: none !important; }
  .art:hover .art__media img,
  .work:hover .work__media img { transform: none !important; }
  .nav__link::after,
  .nav-menu__link::after { transition: none !important; }
  .skip-link { transition: none !important; }
}

/* ---------- pausing the film strip on touch ---------- */
/* The strip pans indefinitely and its only stop was :hover / :focus-within,
   neither of which a touch screen has — so on a phone there was no way to stop
   it at all (WCAG 2.2.2). The control only appears where hover cannot help. */
.bts-strip__pause { display: none; }
@media (hover: none) {
  .bts-strip-wrap { position: relative; }
  .bts-strip__pause {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    padding: 6px 14px;
    margin-top: 10px;
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--fg-dim);
    border: 1px solid var(--rule);
    background: rgba(0, 0, 0, 0.4);
  }
  .bts-strip__pause:focus-visible {
    outline: 1px solid var(--fg);
    outline-offset: 3px;
  }
  .bts-strip--paused .bts-strip__track { animation-play-state: paused !important; }
}


/* ============================================================
   THE CYCLING CONTACT HEADLINE
   ------------------------------------------------------------
   The second line rotates through a pool of phrases. The two
   copies — outgoing and incoming — are stacked in one grid cell
   and animate at the same time, so there is never a moment with
   nothing on screen. A single element fading out and back in
   left a visible beat of empty space, which is what this
   replaces.

   The grid cell takes the height of the taller phrase, so a
   longer line does not clip and no fixed height has to be
   guessed at.

   Animations rather than transitions: an animation plays on
   mount, so the incoming copy needs no second class toggled a
   frame later — which would depend on requestAnimationFrame,
   frozen in a background tab.

   --headline-cycle-dur is written by tweaks/server.js.
   ============================================================ */
.contact__title-line { display: block; }

.headline-cycle { display: grid; }
.headline-phrase { grid-area: 1 / 1; }

/* Nothing animates at rest or on first paint — only while a change is in
   flight. Otherwise the headline would play its entrance on every load, on top
   of the section's own reveal. */

/* ---- morph: defocuses and swells through the change ---- */
[data-headline-transition="morph"].is-changing .headline-phrase {
  animation: headlineMorphIn var(--headline-cycle-dur, 0.9s) cubic-bezier(.2,.8,.2,1) both;
}
[data-headline-transition="morph"].is-changing .headline-phrase.is-leaving {
  animation: headlineMorphOut var(--headline-cycle-dur, 0.9s) cubic-bezier(.2,.8,.2,1) both;
}
@keyframes headlineMorphIn {
  from { opacity: 0; filter: blur(18px); transform: scale(1.06); }
  to   { opacity: 1; filter: blur(0);    transform: scale(1); }
}
@keyframes headlineMorphOut {
  from { opacity: 1; filter: blur(0);    transform: scale(1); }
  to   { opacity: 0; filter: blur(18px); transform: scale(0.96); }
}

/* ---- fall: the new line drops in from above as the old one falls away ----
   Both copies travel the same direction, which is what makes it read as one
   movement rather than two things passing each other. */
[data-headline-transition="fall"].is-changing .headline-phrase {
  animation: headlineFallIn var(--headline-cycle-dur, 0.9s) cubic-bezier(.2,.8,.2,1) both;
}
[data-headline-transition="fall"].is-changing .headline-phrase.is-leaving {
  animation: headlineFallOut var(--headline-cycle-dur, 0.9s) cubic-bezier(.2,.8,.2,1) both;
}
@keyframes headlineFallIn {
  from { opacity: 0; transform: translateY(-0.42em); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes headlineFallOut {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(0.42em); }
}

/* ---- glitch: stepped, in the language of the nav ---- */
[data-headline-transition="glitch"].is-changing .headline-phrase {
  animation: headlineGlitchIn var(--headline-cycle-dur, 0.9s) steps(1, end) both;
}
[data-headline-transition="glitch"].is-changing .headline-phrase.is-leaving {
  animation: headlineGlitchOut var(--headline-cycle-dur, 0.9s) steps(1, end) both;
}
@keyframes headlineGlitchIn {
  0%   { opacity: 0;    transform: translate(4px, 0) skewX(-10deg); }
  25%  { opacity: 0.7;  transform: translate(-3px, 1px) skewX(7deg); }
  50%  { opacity: 0.35; transform: translate(2px, -1px) skewX(-4deg); }
  75%  { opacity: 0.9;  transform: translate(-1px, 0) skewX(2deg); }
  100% { opacity: 1;    transform: translate(0,0) skewX(0deg); }
}
@keyframes headlineGlitchOut {
  0%   { opacity: 1;    transform: translate(0,0) skewX(0deg); }
  25%  { opacity: 0.6;  transform: translate(-4px, 0) skewX(9deg); }
  50%  { opacity: 0.8;  transform: translate(3px, 1px) skewX(-6deg); }
  75%  { opacity: 0.25; transform: translate(-2px, 0) skewX(4deg); }
  100% { opacity: 0;    transform: translate(0,0) skewX(0deg); }
}

/* ---- fade: a plain crossfade, still overlapping ---- */
[data-headline-transition="fade"].is-changing .headline-phrase {
  animation: headlineFadeIn var(--headline-cycle-dur, 0.9s) ease both;
}
[data-headline-transition="fade"].is-changing .headline-phrase.is-leaving {
  animation: headlineFadeOut var(--headline-cycle-dur, 0.9s) ease both;
}
@keyframes headlineFadeIn  { from { opacity: 0; } to { opacity: 1; } }
@keyframes headlineFadeOut { from { opacity: 1; } to { opacity: 0; } }

/* The runtime does not cycle at all under reduced motion, so there is nothing
   here to stop. This is the belt to that braces. */
@media (prefers-reduced-motion: reduce) {
  .headline-phrase { animation: none !important; opacity: 1 !important;
                     transform: none !important; filter: none !important; }
  .headline-phrase.is-leaving { display: none !important; }
}
