/* ==========================================================================
   Art In Motion, v4
   1. Tokens + font
   2. Reset + base
   3. Type roles
   4. Components: .btn, .sec-head, .media
   5. Reveal system
   6. Layout: intro, background wash, header, hero, sections
   7. Menu overlay, gala pages
   8. Reduced motion + QA
   ========================================================================== */

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

:root {
  color-scheme: dark;
  --black: #000000;
  --surface: #141414;
  --surface-2: #1e1e1e;
  --white: #ffffff;
  --muted: #8f8f96;
  --line: rgba(255, 255, 255, 0.10);
  --accent: #ff3da6;
  --accent-ink: #12000a;
  --max: 1280px;
  --pad: clamp(1.25rem, 5vw, 3.5rem);
  --gap: clamp(1rem, 2vw, 1.5rem);
  --section: clamp(5rem, 11vw, 9rem);
  --r-media: 14px;
  --r-tag: 8px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* motion tokens, v5. every new transition/animation uses these. */
  --e-out: cubic-bezier(0.16, 1, 0.3, 1);   /* the settle: strong start, long tail */
  --e-io: cubic-bezier(0.65, 0, 0.35, 1);   /* symmetrical, for hover swaps */
  --d-1: 620ms;   /* text lines */
  --d-2: 900ms;   /* images settling */
  --d-3: 1400ms;  /* large hero elements */
}

@font-face {
  font-family: 'Jakarta';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('fonts/jakarta-var.woff2') format('woff2');
}

/* ------------------------------------------------------- 2. Reset + base */

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

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

html.locked,
html.locked body { overflow: hidden; }

body {
  position: relative;   /* containing block for the scrolling background layers */
  margin: 0;
  background: var(--black);
  color: var(--white);
  font-family: Jakarta, 'Helvetica Neue', Arial, sans-serif;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

h1, h2, h3, p, figure, ul { margin: 0; }

a { color: inherit; }

button { font: inherit; }

[hidden] { display: none !important; }

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

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  padding: 0.85rem 1.25rem;
  background: var(--white);
  color: #111;
  text-decoration: none;
  transform: translateY(-120%);
}
.skip-link:focus-visible { transform: none; }

.wrap {
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* directional reveals translate horizontally, so sections clip on X only.
   overflow-x: clip leaves the Y axis visible and creates no scroll container. */
.sec {
  padding-block: var(--section);
  overflow-x: clip;
}

section[id] { scroll-margin-top: 6rem; }

/* ------------------------------------------------------- 3. Type roles */

.t-hero {
  font-size: clamp(2.4rem, 5.4vw, 4.6rem);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.02em;
}

.t-h2 {
  font-size: clamp(2rem, 4.2vw, 3.5rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.02em;
}

.t-h3 {
  font-size: clamp(1.35rem, 2.2vw, 2rem);
  font-weight: 700;
  line-height: 1.15;
}

.t-state {
  font-size: clamp(1.5rem, 3.1vw, 2.7rem);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.01em;
}

.t-kicker {
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.t-body {
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;
  color: var(--muted);
}

.t-tag {
  font-size: 0.85rem;
  font-weight: 500;
  line-height: 1.2;
}

.t-btn {
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ------------------------------------------------------- 4. Components */

/* .btn: pill with a circular arrow badge on the right edge */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.6rem 0.55rem 1.5rem;
  border: 0;
  border-radius: 999px;
  max-width: 100%;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 300ms var(--ease);
}

.btn-badge {
  position: relative;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  overflow: hidden;
}

/* inline SVG, not a font glyph: the arrow is optically centred at any size */
.btn-arrow,
.btn-arrow-2,
.btn-x {
  display: block;
  transition: transform var(--d-1) var(--e-io);
}

/* the hand-off: arrow one leaves right, arrow two takes its place from the
   left. 180% clears the 38px badge completely, so neither is visible at rest. */
.btn-arrow-2 {
  position: absolute;
  inset: 0;
  margin: auto;
  transform: translateX(-180%);
}

.btn:hover { transform: translateY(-2px); }
.btn:hover .btn-arrow,
.btn:focus-visible .btn-arrow { transform: translateX(180%); }
.btn:hover .btn-arrow-2,
.btn:focus-visible .btn-arrow-2 { transform: none; }

/* the close button keeps one X and turns instead of handing off */
.menu-close:hover .btn-x,
.menu-close:focus-visible .btn-x { transform: rotate(90deg); }

.btn-accent {
  background: var(--accent);
  color: var(--accent-ink);
}
.btn-accent .btn-badge {
  background: var(--accent-ink);
  color: var(--accent);
}

.btn-light {
  background: var(--white);
  color: #111;
}
.btn-light .btn-badge {
  background: #111;
  color: var(--white);
}

.btn-ghost {
  background: transparent;
  color: var(--white);
  border: 1px solid var(--line);
}
.btn-ghost .btn-badge {
  background: var(--white);
  color: #111;
}


/* .sec-head */
.sec-head {
  display: grid;
  gap: clamp(1.25rem, 2.5vw, 1.75rem);
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}

.sec-head .kicker { color: var(--white); }

.sec-head-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2rem;
  align-items: end;
}

.sec-head-left { max-width: 32rem; }
.sec-head-left .lede { margin-top: 1rem; }

.sec-head-row .btn {
  justify-self: end;
  align-self: end;
}

@media (max-width: 860px) {
  .sec-head-row {
    grid-template-columns: 1fr;
    gap: 1.75rem;
  }
  .sec-head-row .btn { justify-self: start; }
}

/* .media */
.media {
  display: block;
  overflow: hidden;
  border-radius: var(--r-media);
  background: var(--surface);
}

.media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* --------------------------------------------------- 5. Reveal system */

/* Things arrive differently depending on what they are, and the blur is the
   thread running through all of it rather than the whole idea.

     .rv     a block            rises and clears
     .rv-l   a left-hand thing  comes in from the left
     .rv-r   a right-hand thing comes in from the right
     .rv-d   a line in a stack  rises on a stagger

   Everything moves on transform and opacity, which the compositor handles;
   only the blur costs real work, so it stays small — smaller still on a
   phone. Once revealed a thing is done: the observer stops watching it, so
   nothing re-runs on the way back up. */

.js .rv,
.js .rv-l,
.js .rv-r,
.js .rv-d {
  opacity: 0;
  filter: blur(6px);
  transition:
    opacity 700ms var(--e-out),
    transform 820ms var(--e-out),
    filter 700ms var(--e-out);
}

.js .rv    { transform: translate3d(0, 26px, 0); }
.js .rv-l  { transform: translate3d(-38px, 0, 0); }
.js .rv-r  { transform: translate3d(38px, 0, 0); }
.js .rv-d  { transform: translate3d(0, 15px, 0); }

/* the stagger: each line follows the one above it */
.js .rv-d { transition-delay: calc(min(var(--i, 0), 12) * 65ms); }
.js .sec-head .rv-d { transition-delay: calc(min(var(--i, 0), 12) * 80ms); }

.js .rv.in,      .js .in .rv,
.js .rv-l.in,    .js .in .rv-l,
.js .rv-r.in,    .js .in .rv-r,
.js .rv-d.in,    .js .in .rv-d {
  opacity: 1;
  transform: none;
  filter: blur(0);
}

/* the section-head button keeps an instant hover, so it names its own
   transition rather than inheriting the reveal timing */
.js .sec-head .btn.rv-d {
  transition: opacity 700ms var(--e-out), transform 300ms var(--ease),
              filter 700ms var(--e-out);
}
.js .sec-head .btn.rv-d:hover,
.js .sec-head .btn.rv-d:focus-visible { transform: translateY(-2px); }

/* headlines pull into focus and settle back from a touch of scale, which
   is what makes them read as a lens rather than a slide */
.js :where([data-rv], .hero) :where(.t-hero, .t-h2, .t-state) {
  filter: blur(9px);
  opacity: 0;
  transform: scale(1.03);
  transform-origin: 50% 60%;
  transition: filter 900ms var(--e-out), opacity 700ms var(--e-out),
              transform 1000ms var(--e-out);
}
.js .in :where(.t-hero, .t-h2, .t-state),
.js :where([data-rv], .hero).in :where(.t-hero, .t-h2, .t-state) {
  filter: blur(0);
  opacity: 1;
  transform: none;
}

/* a photograph settles out of a small push, the way a camera lands on it */
.js .media img,
.js .media video:not(.card-loop) {
  transform: scale(1.05);
  transition: transform 1100ms var(--e-out);
}
.js .in .media img,
.js .media.in img,
.js .in .media video:not(.card-loop) {
  transform: none;
}

/* the gala cards arrive one after the other */
.js .gala-card.rv { transition-delay: calc(var(--i, 0) * 110ms); }

/* so do the Instagram tiles, on a quick diagonal ripple */
.js .ig-cell {
  opacity: 0;
  transform: translate3d(0, 14px, 0) scale(0.98);
  transition: opacity 520ms var(--e-out), transform 620ms var(--e-out);
}
.js .in .ig-cell,
.js .ig-cell.in { opacity: 1; transform: none; }
.js .ig-cell:nth-child(1)  { transition-delay: 0ms; }
.js .ig-cell:nth-child(2)  { transition-delay: 45ms; }
.js .ig-cell:nth-child(3)  { transition-delay: 90ms; }
.js .ig-cell:nth-child(4)  { transition-delay: 60ms; }
.js .ig-cell:nth-child(5)  { transition-delay: 105ms; }
.js .ig-cell:nth-child(6)  { transition-delay: 150ms; }
.js .ig-cell:nth-child(7)  { transition-delay: 120ms; }
.js .ig-cell:nth-child(8)  { transition-delay: 165ms; }
.js .ig-cell:nth-child(9)  { transition-delay: 210ms; }
.js .ig-cell:nth-child(10) { transition-delay: 180ms; }
.js .ig-cell:nth-child(11) { transition-delay: 225ms; }
.js .ig-cell:nth-child(12) { transition-delay: 270ms; }

/* the gallery wall comes up in a soft cascade across each row */
.js .gal-item.rv:nth-child(3n + 2) { transition-delay: 70ms; }
.js .gal-item.rv:nth-child(3n + 3) { transition-delay: 140ms; }

/* ---------------------------------------------------- lighter on a phone */

/* A phone gets the same choreography with less blur and less travel: the
   motion still reads, and the expensive part is dialled back. */
@media (max-width: 760px) {
  .js .rv, .js .rv-l, .js .rv-r, .js .rv-d { filter: blur(4px); }
  .js .rv   { transform: translate3d(0, 18px, 0); }
  .js .rv-l { transform: translate3d(-22px, 0, 0); }
  .js .rv-r { transform: translate3d(22px, 0, 0); }
  .js :where([data-rv], .hero) :where(.t-hero, .t-h2, .t-state) {
    filter: blur(5px);
    transform: scale(1.02);
  }
  .js .media img, .js .media video { transform: scale(1.03); }
}

/* 1. the word wrapper. The masked rise it used to carry is gone with the
   rest of the entrance animation; the element stays so the markup and the
   descender-safe line box are untouched. */
.w {
  display: inline-block;
  vertical-align: bottom;
  padding-bottom: 0.16em;
  margin-bottom: -0.16em;
}
.js .w > span { display: inline-block; }

/* 2. image settle. the logo lockups (gala blank card, summer panel) are
   excluded: they carry their own fixed opacity. :where() keeps the
   exclusion at zero specificity so the hover rules below still win. */
/* images do not fade or settle either: they are simply out of focus with
   the block they belong to. The hover scale below is untouched. */
.js .media img {
  transition: transform var(--d-2) var(--e-out);
}

/* --------------------------------------------- 6. Opening intro overlay */

/* scoped to the direct body child: `.intro` is also the id="introduction"
   section's class, and that section must not inherit any of this. */
body > .intro {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  background: var(--black);
}

.intro-mark {
  width: min(62vw, 460px);
  height: auto;
  opacity: 0.16;
  transform: translateY(8px) scale(0.992);
}
.js .intro-mark { transition: opacity 760ms var(--ease), transform 760ms var(--ease); }
body > .intro.lit .intro-mark { opacity: 1; transform: none; }

.js body > .intro { transition: transform 900ms cubic-bezier(0.76, 0, 0.24, 1); }
body > .intro.up { transform: translateY(-100%); }

/* the header wordmark rides in with the lift */
html.intro-run .brand { opacity: 0; }

/* ------------------------------------------------- 6a. Background wash */

.bg-wash {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(60vw 60vw at 82% 8%, rgba(255, 61, 166, 0.085), transparent 62%),
    radial-gradient(55vw 55vw at 10% 42%, rgba(120, 90, 255, 0.075), transparent 60%),
    radial-gradient(70vw 70vw at 60% 88%, rgba(0, 160, 220, 0.065), transparent 65%);
}

/* ------------------------------------------------------- 6b. Header */

/* 1px sentinel parked 60px down the document. The header goes solid the
   moment it stops intersecting, so no scroll listener is needed. */
.hdr-sentinel {
  position: absolute;
  top: 60px;
  left: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2rem);
  padding: 1rem var(--pad);
  background-color: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 400ms var(--ease), border-color 400ms var(--ease);
}

.site-header.is-stuck {
  /* 0.92 alone let magenta kickers read straight through the bar as the
     page scrolled under it. The blur diffuses whatever is behind so the
     header stays a surface rather than a window. */
  background-color: rgba(8, 7, 10, 0.82);
  backdrop-filter: blur(16px) saturate(1.15);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  border-bottom-color: var(--line);
}

/* no backdrop-filter support: fall back to an opaque bar */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header.is-stuck { background-color: rgba(8, 7, 10, 0.98); }
}

.brand {
  display: inline-flex;
  align-items: center;
  margin-right: auto;
  text-decoration: none;
  transition: opacity 500ms var(--ease);
}
.brand img {
  display: block;
  height: 38px;
  width: auto;
}

.hdr-nav {
  display: flex;
  align-items: center;
  gap: clamp(1.1rem, 2.2vw, 2rem);
}

.hdr-link {
  color: var(--white);
  text-decoration: none;
  transition: opacity 300ms var(--ease);
}
.hdr-link:hover { opacity: 0.62; }

/* underline wipe: in from the left, out to the right */
.hdr-nav a,
.footer-links a { position: relative; }
.hdr-nav a::after,
.footer-links a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--d-1) var(--e-out);
}
.hdr-nav a:hover::after,
.hdr-nav a:focus-visible::after,
.footer-links a:hover::after,
.footer-links a:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left;
}

.hdr-cta {
  flex: 0 0 auto;
  padding: 0.45rem 0.5rem 0.45rem 1.15rem;
}
.hdr-cta .btn-badge {
  width: 32px;
  height: 32px;
}

.menu-btn {
  display: none;
  align-items: center;
  gap: 0.7rem;
  padding: 0.5rem 0;
  background: none;
  border: 0;
  color: var(--white);
  cursor: pointer;
}

@media (max-width: 899px) {
  .hdr-nav,
  .hdr-cta { display: none; }
  .menu-btn { display: inline-flex; }
}

.burger {
  display: grid;
  gap: 6px;
}
.burger i {
  display: block;
  width: 20px;
  height: 1.5px;
  background: var(--white);
}

/* ------------------------------------------------------- 6a. Hero */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-top: 7rem;
  overflow: hidden;
}

/* the room: camera flashes blooming out of the dark, under the type */
.hero-flashes {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 98%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 98%);
}
.hero-flashes span {
  position: absolute;
  border-radius: 50%;
  filter: blur(38px);
  animation: flash-pulse 9s ease-in-out infinite alternate;
}
.hero-flashes span:nth-child(1) {
  left: 12%; top: 34%; width: 190px; height: 190px;
  background: rgba(255, 61, 166, .20);
  animation-duration: 7s;
  animation-delay: 0s;
}
.hero-flashes span:nth-child(2) {
  left: 68%; top: 22%; width: 150px; height: 150px;
  background: rgba(120, 200, 255, .16);
  animation-duration: 9s;
  animation-delay: 1.2s;
}
.hero-flashes span:nth-child(3) {
  left: 44%; top: 58%; width: 220px; height: 220px;
  background: rgba(255, 255, 255, .10);
  animation-duration: 11s;
  animation-delay: 2.4s;
}
.hero-flashes span:nth-child(4) {
  left: 82%; top: 64%; width: 130px; height: 130px;
  background: rgba(160, 120, 255, .16);
  animation-duration: 8s;
  animation-delay: .6s;
}
.hero-flashes span:nth-child(5) {
  left: 26%; top: 72%; width: 120px; height: 120px;
  background: rgba(255, 200, 120, .12);
  animation-duration: 10s;
  animation-delay: 3s;
}

@keyframes flash-pulse {
  from { opacity: .55; transform: scale(.94); }
  to { opacity: 1; transform: scale(1.06); }
}



.hero-inner {
  position: relative;
  z-index: 3;
  width: 100%;
  padding-bottom: clamp(5rem, 12vh, 8rem);
}

.hero-kicker { color: var(--white); }

.hero-title {
  max-width: 18ch;
  margin-top: clamp(1rem, 2vh, 1.5rem);
}

.hero-cta { margin-top: clamp(1.75rem, 4vh, 2.5rem); }

.hero-scroll {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(1.5rem, 4vh, 2.5rem);
  z-index: 3;
  pointer-events: none;
}
.hero-scroll-inner {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--white);
  opacity: 0.85;
}
.hero-scroll-arrow {
  font-size: 0.9rem;
  line-height: 1;
}
@media (max-height: 699px) {
  .hero-scroll { display: none; }
}

/* narrow viewports: the credit wraps, so give it its own lane under the
   scroll cue instead of letting the two collide */
@media (max-width: 700px) {
  .hero-scroll { bottom: calc(clamp(1.5rem, 4vh, 2.5rem) + 2.2rem); }
}

/* --------------------------------------------------- 6b. Introduction */

.intro-inner .kicker { color: var(--white); }

.intro-state {
  max-width: 54rem;
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
}

.intro-cta { margin-top: clamp(2rem, 4vw, 3rem); }

/* ------------------------------------------------------- 6c. Galas */

.gala-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
@media (max-width: 1024px) {
  .gala-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 699px) {
  .gala-grid { grid-template-columns: 1fr; }
}

.gala-card {
  display: block;
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  color: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
/* no per-card stagger: the row comes into focus as one plane */

.gala-media { aspect-ratio: 4 / 5; }
/* the card block is staggered, so its image settles on the same beat */
.js .gala-card .media img { transition-delay: calc(var(--i, 0) * 90ms); }
.js .gala-card:hover .gala-media img,
.js .gala-card:focus-visible .gala-media img {
  transform: scale(1.04);
  transition-delay: 0ms;
}

.media.gala-blank {
  display: grid;
  place-items: center;
  background: radial-gradient(120% 100% at 50% 30%, #1d1a22, #0d0c10 70%);
}
.media.gala-blank img {
  width: min(52%, 220px);
  height: auto;
  object-fit: contain;
  opacity: 0.62;
}

.gala-title {
  display: block;
  margin-top: 1.25rem;
  color: var(--white);
}
.js .gala-title { transition: transform var(--d-1) var(--e-out); }
.gala-card:hover .gala-title,
.gala-card:focus-visible .gala-title { transform: translateX(6px); }


/* -------------------------------------------------- 6e. What we produce */

.prod-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: center;
  margin-bottom: clamp(3rem, 6vw, 5rem);
}
.prod-row:last-child { margin-bottom: 0; }

.prod-media {
  aspect-ratio: 3 / 2;
  max-height: 420px;
}

.prod-row .prod-media { order: 1; }
.prod-row .prod-copy { order: 2; }
.prod-row--flip .prod-media { order: 2; }
.prod-row--flip .prod-copy { order: 1; }

.prod-body { margin-top: 1rem; }

@media (max-width: 860px) {
  .prod-row { grid-template-columns: 1fr; }
  .prod-row .prod-media,
  .prod-row--flip .prod-media { order: 1; }
  .prod-row .prod-copy,
  .prod-row--flip .prod-copy { order: 2; }
}

/* ------------------------------------------------------- 6f. Instagram */

/* the real grid: 3 across at every width, near flush, one rounded block.
   measured live on the profile: tiles are 292 x 389, a 3:4 portrait, with
   a 1px gutter. they are not squares. */
/* ------------------------------------------------------- 6g. Inquiry */

.sec-head--nobtn .sec-head-row { grid-template-columns: 1fr; }

.form-card {
  background: var(--surface);
  border-radius: var(--r-media);
  padding: clamp(1.5rem, 4vw, 3rem);
}

.field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap);
}
@media (max-width: 700px) {
  .field-grid { grid-template-columns: 1fr; }
}

.field {
  display: grid;
  gap: 0.5rem;
  min-width: 0;
}
.field--full { grid-column: 1 / -1; }

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

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.9rem 1rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--white);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.4;
}

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

.field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 1.15rem center, right 0.85rem center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 2.5rem;
}
.field select option {
  background: var(--surface);
  color: var(--white);
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible { border-color: var(--accent); }

.form-actions { justify-self: start; }

.form-status:empty { display: none; }
.form-status { margin-top: 1.25rem; }

/* ------------------------------------------------------- 6h. Footer */

.site-footer {
  padding-block: clamp(3rem, 6vw, 5rem);
  text-align: center;
}

.footer-badge {
  display: block;
  width: 120px;
  height: auto;
  margin-inline: auto;
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.5rem;
  margin-top: 2rem;
}
.footer-link {
  color: var(--white);
  text-decoration: none;
  transition: opacity 300ms var(--ease);
}
.footer-link:hover { opacity: 0.65; }

.footer-legal { margin-top: 2rem; }
.footer-line { font-size: 0.85rem; }

/* ------------------------------------------------- 7a. Menu overlay */

.menu {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: rgba(0, 0, 0, 0.96);
  opacity: 0;
  transition: opacity 300ms var(--ease);
  overflow-y: auto;
}
.menu.open { opacity: 1; }

.menu-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: flex-end;
  padding: 1.25rem var(--pad);
}

.menu-close {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0;
  background: none;
  border: 0;
  color: var(--white);
  cursor: pointer;
}
.menu-close-badge {
  width: 28px;
  height: 28px;
  background: var(--white);
  color: #111;
}

.menu-nav {
  display: grid;
  gap: 0.35rem;
  width: 100%;
  padding-block: 6rem;
}

.menu-link {
  display: block;
  padding: 0.2rem 0;
  color: var(--white);
  text-decoration: none;
}
.menu-link:hover { color: var(--accent); }

/* the close button lands last */
.js .menu-close {
  opacity: 0;
  transition: opacity var(--d-1) var(--e-out);
  transition-delay: 260ms;
}
.js .menu.open .menu-close { opacity: 1; }

/* ------------------------------------------------ 7b. Gala pages */

/* each gala is its own page now. the head block clears the fixed header,
   then the masonry wall runs to the footer. tokens only, no new scale. */
.gala-page {
  padding-bottom: var(--section);
  overflow-x: clip;   /* reveals translate on X, same guard as .sec */
}

.page-head { padding-top: clamp(8rem, 16vh, 12rem); }

.page-back { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }

/* the hand-off runs LEFT on the back link: arrow one leaves to the left and
   arrow two arrives from the right, the mirror of every forward button. */
.back-link .btn-arrow-2 { transform: translateX(180%); }
.back-link:hover .btn-arrow,
.back-link:focus-visible .btn-arrow { transform: translateX(-180%); }
.back-link:hover .btn-arrow-2,
.back-link:focus-visible .btn-arrow-2 { transform: none; }

.page-head .kicker { color: var(--muted); }
.page-title { margin-top: 1rem; }
.page-lede {
  max-width: 40rem;
  margin-top: 1.25rem;
}

/* masonry. columns of natural heights, so frames never line up into rows
   and every photograph keeps its own shape instead of being boxed to a
   common width. multicol is used rather than grid because grid masonry is
   not yet supported broadly, and this needs no JS and cannot thrash. */
.ov-gallery {
  --gal-gap: clamp(0.7rem, 1.3vw, 1.1rem);
  columns: 3;
  column-gap: var(--gal-gap);
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.gal-item {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  display: block;
  width: 100%;
  margin: 0 0 var(--gal-gap);
}

@media (max-width: 1024px) {
  .ov-gallery { columns: 2; }
}
@media (max-width: 620px) {
  .ov-gallery { columns: 1; }
}

/* The archive is ~80% identical 2:3 portraits, and masonry with one shared
   ratio is just a grid. So the frames are cropped on a repeating rhythm of
   shapes, which is what gives the wall its stagger. Faces sit high in these
   photographs, so every crop is anchored above centre. */
.gal-item { aspect-ratio: 2 / 3; }
.gal-item:nth-child(6n + 2) { aspect-ratio: 1 / 1; }
.gal-item:nth-child(6n + 3) { aspect-ratio: 4 / 5; }
.gal-item:nth-child(6n + 4) { aspect-ratio: 3 / 4; }
.gal-item:nth-child(6n + 5) { aspect-ratio: 1 / 1; }
.gal-item:nth-child(6n + 6) { aspect-ratio: 5 / 7; }

/* landscapes keep a landscape box whatever slot they land in, so a wide
   frame is never squeezed into a tall one */
.gal-item.is-wide,
.gal-item.is-wide:nth-child(n) { aspect-ratio: 3 / 2; }

.gal-item img { object-position: 50% 28%; }
.gal-item.is-wide img { object-position: 50% 45%; }

/* neighbours must not fire in unison. a 3-step cycle reads as a cascade
   down the wall rather than a single block appearing. */
.gal-item { --i: 0; }
.gal-item:nth-child(3n+2) { --i: 1; }
.gal-item:nth-child(3n+3) { --i: 2; }
.js .gal-item,
.js .gal-item img { transition-delay: calc(var(--i, 0) * 80ms); }

.summer-panel {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 1.5rem;
  min-height: min(55svh, 460px);
  padding: clamp(2rem, 5vw, 3.5rem);
  margin-top: clamp(2.5rem, 5vw, 4rem);
  background: var(--surface);
  color: var(--muted);
}
.summer-panel img {
  width: min(52%, 260px);
  height: auto;
  object-fit: contain;
  opacity: 0.3;
}

/* ------------------------------------------- 8. Reduced motion + QA */

html.qa { scroll-behavior: auto; }

.qa .rv,
.qa .rv-l,
.qa .rv-r,
.qa .rv-d,
.qa .t-hero,
.qa .t-h2,
.qa .t-state,
.qa .w > span,
.qa .menu-link,
.qa .media:not(:where(.gala-blank, .summer-panel)) img,
.qa .menu-close {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
  filter: none !important;
}
.qa .sec-head .btn.rv-d {
  opacity: 1 !important;
  animation: none !important;
}
.qa .hero-zoom {
  animation: none !important;
  transform: none !important;
}
.qa .brand { opacity: 1 !important; }

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

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

  .rv,
  .rv-l,
  .rv-r,
  .rv-d,
  .w > span,
  .menu-link,
  .media:not(:where(.gala-blank, .summer-panel)) img,
  .menu-close {
    opacity: 1 !important;
    transform: none !important;
  }

  .sec-head .btn.rv-d {
    opacity: 1 !important;
    animation: none !important;
  }

  .hero-zoom {
    animation: none !important;
    transform: none !important;
  }
  .brand { opacity: 1 !important; }

  /* no hand-off: the second arrow is a decorative duplicate, so it goes */
  .btn-arrow-2 { display: none !important; }

  /* the intro never runs under reduced motion; blooms hold a static glow */
  .hero-flashes span {
    animation: none !important;
    opacity: .8;
    transform: none !important;
  }

  .btn:hover,
  .btn:hover .btn-arrow,
  .btn:focus-visible .btn-arrow,
  .menu-close:hover .btn-x,
  .menu-close:focus-visible .btn-x,
  .gala-card:hover .gala-media img,
  .gala-card:focus-visible .gala-media img,
  .gala-card:hover .gala-title,
  .gala-card:focus-visible .gala-title,
  .ig-tile:hover img,
  .ig-tile:focus-visible img { transform: none !important; }
}

/* -------------------------------------- art direction: keep faces in frame */

.gala-media:not(.gala-blank) img { object-position: 50% 18%; }
.media.gala-blank img { object-position: 50% 50%; }

.prod-media--1 img { object-position: 50% 45%; }
.prod-media--2 img { object-position: 50% 42%; }   /* the booth sits right of frame */

/* ------------------------------------------- ambient nightclub light, v8
   The light field scrolls WITH the document rather than being pinned to
   the viewport, so you travel through the lighting instead of dragging it
   along. The layer is as tall as the page and the blooms are spread down
   its whole length, which means every section sits in its own colour.
   Cheap to run: radial gradients moving on transform and opacity only. */

.ambient {
  position: absolute;
  inset: 0;                 /* spans the full document, not the viewport */
  z-index: -2;
  pointer-events: none;
  overflow: hidden;
}

/* two parallax depths. the blooms themselves are already animating their
   own transform, so the cursor offset is written here instead: writing it
   onto `.glow` would be overwritten by the running keyframes. the layer
   keeps `mix-blend-mode: screen` so grouping the blooms does not change
   how the colours combine (screen is commutative, so the split into two
   layers reads the same as the flat stack did). */
.orbit {
  position: absolute;
  inset: 0;
  mix-blend-mode: screen;
  will-change: transform;
}

.glow {
  position: absolute;
  display: block;
  border-radius: 50%;
  mix-blend-mode: screen;
}

/* spread down the page. vertical positions are percentages of the document,
   so they stay evenly distributed however long the page grows. */
.g1  { width: 72vw; height: 72vw; left: -20vw; top: 1%;
       background: radial-gradient(circle, rgba(255,61,166,0.34) 0%, rgba(255,61,166,0) 66%);
       animation: drift-a 41s ease-in-out infinite alternate; }
.g2  { width: 64vw; height: 64vw; right: -18vw; top: 10%;
       background: radial-gradient(circle, rgba(64,196,255,0.30) 0%, rgba(64,196,255,0) 66%);
       animation: drift-b 47s ease-in-out infinite alternate; }
.g3  { width: 80vw; height: 56vw; left: 4vw; top: 20%;
       background: radial-gradient(ellipse, rgba(138,92,255,0.30) 0%, rgba(138,92,255,0) 68%);
       animation: drift-c 53s ease-in-out infinite alternate; }
.g4  { width: 56vw; height: 56vw; right: 2vw; top: 30%;
       background: radial-gradient(circle, rgba(61,220,132,0.22) 0%, rgba(61,220,132,0) 64%);
       animation: drift-d 59s ease-in-out infinite alternate; }
.g5  { width: 66vw; height: 46vw; left: -8vw; top: 40%;
       background: radial-gradient(ellipse, rgba(255,150,60,0.20) 0%, rgba(255,150,60,0) 68%);
       animation: drift-e 67s ease-in-out infinite alternate; }
.g6  { width: 60vw; height: 60vw; right: -12vw; top: 50%;
       background: radial-gradient(circle, rgba(255,61,166,0.28) 0%, rgba(255,61,166,0) 66%);
       animation: drift-f 73s ease-in-out infinite alternate; }
.g7  { width: 70vw; height: 50vw; left: 2vw; top: 60%;
       background: radial-gradient(ellipse, rgba(64,196,255,0.26) 0%, rgba(64,196,255,0) 68%);
       animation: drift-c 61s ease-in-out infinite alternate; }
.g8  { width: 54vw; height: 54vw; right: 6vw; top: 70%;
       background: radial-gradient(circle, rgba(138,92,255,0.28) 0%, rgba(138,92,255,0) 64%);
       animation: drift-b 43s ease-in-out infinite alternate; }
.g9  { width: 76vw; height: 54vw; left: -10vw; top: 80%;
       background: radial-gradient(ellipse, rgba(255,61,166,0.24) 0%, rgba(255,61,166,0) 68%);
       animation: drift-a 71s ease-in-out infinite alternate; }
.g10 { width: 58vw; height: 58vw; right: -6vw; top: 90%;
       background: radial-gradient(circle, rgba(64,196,255,0.22) 0%, rgba(64,196,255,0) 66%);
       animation: drift-e 79s ease-in-out infinite alternate; }

/* each bloom travels its own path and breathes on its own clock. the
   periods are coprime, so the combined field never visibly repeats. */
@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); opacity: 0.55; }
  to   { transform: translate3d(14vw, 6vh, 0) scale(1.20); opacity: 1; }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.12); opacity: 1; }
  to   { transform: translate3d(-12vw, 9vh, 0) scale(0.92); opacity: 0.45; }
}
@keyframes drift-c {
  from { transform: translate3d(0, 0, 0) scale(1); opacity: 0.5; }
  to   { transform: translate3d(11vw, -8vh, 0) scale(1.18); opacity: 0.95; }
}
@keyframes drift-d {
  from { transform: translate3d(0, 0, 0) scale(1.05); opacity: 0.9; }
  to   { transform: translate3d(-15vw, -6vh, 0) scale(0.9); opacity: 0.35; }
}
@keyframes drift-e {
  from { transform: translate3d(0, 0, 0) scale(0.95); opacity: 0.4; }
  to   { transform: translate3d(17vw, 5vh, 0) scale(1.24); opacity: 0.85; }
}
@keyframes drift-f {
  from { transform: translate3d(0, 0, 0) scale(1); opacity: 0.30; }
  to   { transform: translate3d(-9vw, 8vh, 0) scale(1.28); opacity: 0.7; }
}

@media (prefers-reduced-motion: reduce) {
  .glow { animation: none; opacity: 0.6; }
}

/* ==========================================================================
   v6, refinements taken from the design studies. Only what survived review:
   accent kickers, a real neon glow on the primary action, one emphasised
   phrase per statement, a photograph bleeding under the inquiry, and a
   footer that carries the mark properly. Deliberately NOT taken: 01/02/03
   number stamps and the serif, both of which would break the type system.
   ========================================================================== */

/* the kicker is the smallest place accent colour can live without shouting */
.sec-head .kicker,
.intro .kicker { color: var(--accent); }

/* the primary action now reads as lit rather than filled */
.btn-accent {
  box-shadow: 0 0 0 1px rgba(255, 61, 166, 0.35),
              0 6px 26px -6px rgba(255, 61, 166, 0.55),
              0 0 44px -12px rgba(255, 61, 166, 0.65);
  transition: transform 300ms var(--ease), box-shadow 400ms var(--ease);
}
.btn-accent:hover,
.btn-accent:focus-visible {
  box-shadow: 0 0 0 1px rgba(255, 61, 166, 0.5),
              0 10px 34px -6px rgba(255, 61, 166, 0.7),
              0 0 60px -10px rgba(255, 61, 166, 0.8);
}

/* one phrase per statement carries the colour. never a whole line. */
.em > span { color: var(--accent); }

/* the inquiry reads as head-left over a room, form-right. the photograph
   lives under the left column where the type is sparse, never under the
   form, so contrast is never in question. */
.inquiry { position: relative; overflow: hidden; }

.inquiry-cols {
  display: grid;
  grid-template-columns: 0.8fr 1fr;
  gap: clamp(2rem, 4vw, 4.5rem);
  align-items: start;
}
.inquiry-cols .sec-head { margin-bottom: 0; }
.inquiry-cols .sec-head-left { max-width: none; }

@media (max-width: 1020px) {
  .inquiry-cols { grid-template-columns: 1fr; gap: clamp(2rem, 5vw, 3rem); }
}

.inquiry-glow {
  position: absolute;
  left: 0;
  bottom: 0;
  width: min(50%, 620px);
  aspect-ratio: 4 / 5;
  z-index: 0;
  pointer-events: none;
  opacity: 0.8;
  /* one mask only. two composited layers cancel at the shared corner and
     the whole thing disappears, which is exactly what happened first. */
  /* The vertical radius (60%) and the centre (50%) are chosen so the mask
     is already fully transparent at BOTH horizontal edges of the element:
     the distance to either edge is 50%, which lands at 83% of the gradient,
     past the transparent stop. Anything still carrying alpha at the edge
     gets sliced into a straight line by the section's overflow clip. */
  -webkit-mask-image: radial-gradient(86% 60% at 28% 50%, #000 18%, rgba(0,0,0,0.5) 50%, transparent 82%);
  mask-image: radial-gradient(86% 60% at 28% 50%, #000 18%, rgba(0,0,0,0.5) 50%, transparent 82%);
}
.inquiry-glow img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 44% 40%;
  display: block;
  /* sunk under the type: it is a room, not a photograph on show */
  filter: brightness(1.18) contrast(1.02) saturate(0.85);
}
.inquiry > .wrap { position: relative; z-index: 1; }

@media (max-width: 1020px) {
  .inquiry-glow { width: min(80%, 520px); opacity: 0.3; }
}

/* footer: the mark gets room, and one lit hairline separates it from the
   words. the dots between links are the only decoration and they earn it
   by replacing three separate borders. */
.footer-badge { width: clamp(150px, 17vw, 210px); }

.rule,
.footer-rule {
  width: min(560px, 78%);
  height: 1px;
  margin: clamp(2rem, 4vw, 3rem) auto 0;
  background: linear-gradient(90deg, transparent, var(--line) 22%, var(--line) 78%, transparent);
  position: relative;
}
.rule::after,
.footer-rule::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 84px;
  height: 1px;
  transform: translate(-50%, -50%);
  background: var(--accent);
  box-shadow: 0 0 12px 1px rgba(255, 61, 166, 0.75);
}

.footer-legal { margin-top: clamp(1.75rem, 3vw, 2.5rem); }
.footer-line--lead {
  font-size: clamp(1rem, 1.6vw, 1.25rem);
  color: var(--white);
  max-width: 26ch;
  margin-inline: auto;
  line-height: 1.45;
}
.footer-line--legal { margin-top: 1.25rem; font-size: 0.82rem; }

.footer-links { gap: clamp(1rem, 2vw, 1.75rem); align-items: center; }
.footer-links li { display: contents; }
.footer-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  flex: 0 0 auto;
}
@media (max-width: 620px) { .footer-dot { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .btn-accent { transition: none; }
}

/* ==========================================================================
   v7. The room, the cursor, and the seams.
   1. liquid cursor canvas   2. nightclub hero (no photograph)
   3. seamless sections      4. centred introduction, pulled into focus
   5. galas without pills    6. produce hover      7. opening swipe
   ========================================================================== */

/* ----------------------------------------------- 1. liquid cursor canvas */

/* The fluid sim paints light. `screen` over a black page means it adds to
   whatever is beneath and never hides it, so it can sit above the content
   without touching legibility. It never takes a pointer event. */
#fluid {
  position: fixed;
  inset: 0;
  /* BEHIND the content. A negative index puts the canvas above the two
     background layers (-2 and -1, and it is later in the DOM than the
     wash) but under everything in normal flow, so the trail lights the
     room without ever washing over a button, a photograph or the type. */
  z-index: -1;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0.66;
}

/* no canvas on touch or for reduced motion: the script bails, and this
   makes sure the element can never occupy space in the meantime */
@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) {
  #fluid { display: none; }
}

/* ------------------------------------------- 2. the hero without a photo */

/* the hero is now the room itself: black, with the bloom field showing
   through, flashes going off, and dust in the beam. */
.hero {
  min-height: 100svh;
  padding-top: 7rem;
}

/* a deep well behind the hero so the type always has ground under it,
   fading out completely before the next section starts */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(120% 78% at 50% 108%, rgba(255, 61, 166, 0.16) 0%, transparent 62%),
    radial-gradient(90% 62% at 14% 12%, rgba(60, 150, 255, 0.14) 0%, transparent 68%),
    radial-gradient(80% 60% at 88% 22%, rgba(160, 90, 255, 0.13) 0%, transparent 70%),
    linear-gradient(to bottom, rgba(0, 0, 0, 0.55) 0%, transparent 34%);
  /* .hero clips its children, so a gradient that is still carrying colour
     at the bottom edge gets sliced into a hard line across the page. The
     well is masked out well before that edge, and the page-wide bloom
     carries on underneath, so the hero has no bottom at all. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 46%, rgba(0,0,0,0.45) 76%, transparent 97%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 46%, rgba(0,0,0,0.45) 76%, transparent 97%);
}

.hero-inner,
.hero-scroll { position: relative; z-index: 3; }

/* more flashes, and they now fire against black rather than over a photo,
   so they carry the whole mood. three extra positions. */
.hero-flashes span:nth-child(6) {
  left: 6%; top: 12%; width: 160px; height: 160px;
  background: rgba(80, 220, 255, .14);
  animation-duration: 12s;
  animation-delay: 1.8s;
}
.hero-flashes span:nth-child(7) {
  left: 56%; top: 8%; width: 200px; height: 200px;
  background: rgba(255, 61, 166, .13);
  animation-duration: 13s;
  animation-delay: 4.2s;
}
.hero-flashes span:nth-child(8) {
  left: 34%; top: 30%; width: 110px; height: 110px;
  background: rgba(255, 255, 255, .13);
  animation-duration: 6s;
  animation-delay: 5.1s;
}

/* dust in the light. eighteen motes on long coprime drifts so the field
   never visibly loops. transform + opacity only, so it composites. */
.hero-dust {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
}
.hero-dust i {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.55);
  filter: blur(0.4px);
  animation: dust-drift linear infinite;
  will-change: transform, opacity;
}

@keyframes dust-drift {
  0%   { transform: translate3d(0, 0, 0) scale(0.7); opacity: 0; }
  12%  { opacity: 0.7; }
  80%  { opacity: 0.5; }
  100% { transform: translate3d(var(--dx, 30px), -120px, 0) scale(1.1); opacity: 0; }
}

.hero-dust i:nth-child(1)  { left:  6%; top: 78%; --dx:  26px; animation-duration: 17s; animation-delay: -2s;  }
.hero-dust i:nth-child(2)  { left: 14%; top: 88%; --dx: -18px; animation-duration: 23s; animation-delay: -7s;  }
.hero-dust i:nth-child(3)  { left: 22%; top: 70%; --dx:  34px; animation-duration: 19s; animation-delay: -11s; }
.hero-dust i:nth-child(4)  { left: 31%; top: 92%; --dx: -28px; animation-duration: 29s; animation-delay: -3s;  }
.hero-dust i:nth-child(5)  { left: 39%; top: 66%; --dx:  16px; animation-duration: 21s; animation-delay: -15s; }
.hero-dust i:nth-child(6)  { left: 47%; top: 84%; --dx: -36px; animation-duration: 31s; animation-delay: -9s;  }
.hero-dust i:nth-child(7)  { left: 55%; top: 74%; --dx:  22px; animation-duration: 18s; animation-delay: -19s; }
.hero-dust i:nth-child(8)  { left: 63%; top: 90%; --dx: -14px; animation-duration: 27s; animation-delay: -5s;  }
.hero-dust i:nth-child(9)  { left: 71%; top: 68%; --dx:  30px; animation-duration: 23s; animation-delay: -13s; }
.hero-dust i:nth-child(10) { left: 79%; top: 86%; --dx: -24px; animation-duration: 33s; animation-delay: -1s;  }
.hero-dust i:nth-child(11) { left: 87%; top: 76%; --dx:  18px; animation-duration: 19s; animation-delay: -17s; }
.hero-dust i:nth-child(12) { left: 94%; top: 94%; --dx: -32px; animation-duration: 29s; animation-delay: -6s;  }
.hero-dust i:nth-child(13) { left: 10%; top: 60%; --dx:  20px; animation-duration: 37s; animation-delay: -21s; }
.hero-dust i:nth-child(14) { left: 27%; top: 56%; --dx: -22px; animation-duration: 31s; animation-delay: -25s; }
.hero-dust i:nth-child(15) { left: 44%; top: 52%; --dx:  28px; animation-duration: 41s; animation-delay: -8s;  }
.hero-dust i:nth-child(16) { left: 61%; top: 58%; --dx: -16px; animation-duration: 35s; animation-delay: -27s; }
.hero-dust i:nth-child(17) { left: 76%; top: 54%; --dx:  24px; animation-duration: 43s; animation-delay: -12s; }
.hero-dust i:nth-child(18) { left: 90%; top: 62%; --dx: -30px; animation-duration: 39s; animation-delay: -23s; }

/* --------------------------------------------------- 3. seamless seams */

/* Nothing draws a line between sections. The header only gains its rule
   once it is stuck over content, and even then it is the header's edge,
   not a section break. */
.sec + .sec,
.hero + .sec,
.site-footer { border-top: 0; }

/* the hero hands off to the section below through a shared fade rather
   than an edge: the top of the next section is already dark. */
.hero + .sec { margin-top: calc(var(--section) * -0.5); padding-top: calc(var(--section) * 1.5); }

/* ------------------------------ 4. centred introduction, pulled to focus */

.intro--centred .wrap { text-align: center; }

/* 22ch broke the statement over seven lines. A display sentence reads better
   on a wider measure; this holds it to four or five. */
.intro--centred .t-state {
  max-width: 34ch;
  margin-inline: auto;
}

@media (max-width: 620px) {
  .intro--centred .t-state { max-width: 26ch; }
}

/* on a narrow phone the display size is what forces the stack, not the measure */
@media (max-width: 430px) {
  .intro--centred .t-state { font-size: 1.35rem; max-width: none; }
}

.intro--centred .kicker { margin-bottom: clamp(1.25rem, 2.5vw, 2rem); }

.intro--centred .intro-cta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.85rem;
  margin-top: clamp(2rem, 4vw, 3rem);
}

/* The introduction is pulled into focus by the same reveal system as
   everything else now. It used to run on `animation-timeline: view()`,
   which only Chrome implements, so Safari and Firefox saw no focus pull
   at all. One mechanism, every browser, and reversible on the way back
   up like the rest of the site. */

/* ------------------------------------------------- 5. galas, no pills */

/* the year and the city live in the name now. the only thing left is the
   one honest note about missing photography, set as a quiet caption. */
.gala-note {
  display: block;
  margin-top: 0.5rem;
  color: var(--muted);
  font-size: 0.8rem;
}

/* ------------------------------------------------- 6. produce hover */

/* hovering a row lights its heading and its pills. the whole row is the
   target so the pills answer even when the pointer is on the words. */
.prod-row .t-h3 {
  transition: color 320ms var(--e-io), background-color 320ms var(--e-io),
              border-color 320ms var(--e-io);
}
.js .prod-row:hover .t-h3 { color: var(--accent); }
/* the pill under the pointer goes the whole way */

/* ------------------------------------------------ 7. the opening swipe */

/* no logo on open. a black panel lifts off the page and that is all.
   SCOPED to the direct body child: `.intro` is also the class on the
   id="introduction" section, and an unscoped rule here turns that whole
   section into a fixed black panel. */
body > .intro {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--black);
  transform: translateY(0);
  transition: transform 900ms var(--e-out);
}
body > .intro.up { transform: translateY(-100%); }

/* ------------------------------------------------------- footer credit */

.footer-credit {
  display: block;
  margin-top: 0.6rem;
  font-size: 0.7rem;
  color: rgba(255, 255, 255, 0.34);
}
.footer-credit a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.footer-credit a:hover { color: rgba(255, 255, 255, 0.7); }

/* --------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  .hero-dust i { animation: none; opacity: 0.35; }
  body > .intro { transition: none; }
}

/* ------------------------------- headline focus pull, site wide */

/* Headlines are not .rv elements: their words ride the masked line reveal
   instead. So the blur goes on the heading as a single block, which is
   also what keeps it readable, a per-word blur inside `.w` would be cut
   off by that element's own overflow clip. `.t-h3` is deliberately absent:
   every h3 on the site already sits inside an .rv/.rv-l/.rv-r block and
   would otherwise be blurred twice. */
.js :where([data-rv], .hero) :where(.t-hero, .t-h2, .t-state) {
  filter: blur(9px);
  transition: filter 700ms cubic-bezier(0.33, 1, 0.68, 1);
}
.js .in :where(.t-hero, .t-h2, .t-state) { filter: blur(0); }
.js .hero.in :where(.t-hero, .t-h2, .t-state) { filter: blur(0); }
.js [data-rv].in :where(.t-hero, .t-h2, .t-state) { filter: blur(0); }

@media (prefers-reduced-motion: reduce) {
  .js .rv, .js .rv-l, .js .rv-r, .js .rv-d,
  .js :where([data-rv], .hero) :where(.t-hero, .t-h2, .t-state) {
    filter: none;
    transition: none;
  }
}

/* ==========================================================================
   Instagram, as a profile. Same construction as the Cinematic Frames and
   VibesByNeo social sections: centred head, a profile card carrying the
   avatar, handle and counts, then a full-bleed grid of real posts. Rebuilt
   on Art In Motion's own tokens rather than copied wholesale.
   ========================================================================== */

.ig-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1rem;
  margin-bottom: clamp(1.75rem, 3vw, 2.4rem);
}
.ig-head .lede { max-width: 42ch; }

/* the profile card */
.ig-profile {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(1rem, 3vw, 2.2rem);
  flex-wrap: wrap;
  max-width: 620px;
  margin: 0 auto clamp(2rem, 3.4vw, 2.8rem);
  padding: clamp(1.1rem, 2.4vw, 1.5rem) clamp(1.3rem, 3vw, 2rem);
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--line);
}

/* the ring stays Instagram's own gradient: it is what makes the card read
   as a profile at a glance. The mark inside it is ours. */
.igp-avatar {
  flex-shrink: 0;
  width: 88px;
  height: 88px;
  border-radius: 50%;
  padding: 3px;
  background: conic-gradient(from 25deg, #f9ce34, #ee2a7b, #6228d7, #f9ce34);
}
.igp-avatar img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: contain;
  background: var(--black);
  border: 3px solid var(--black);
  padding: 6px;
}

.igp-body {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  text-align: left;
  min-width: 0;
  flex: 1 1 auto;
}

.igp-top {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
}
.igp-name { font-weight: 700; font-size: 1.05rem; color: var(--white); }

.igp-real { font-weight: 700; font-size: 0.92rem; color: var(--white); margin: 0; }
.igp-cat { font-size: 0.8rem; color: var(--muted); margin: 0; }
.igp-bio { font-size: 0.85rem; line-height: 1.5; color: var(--white); margin: 0; }
.igp-link {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  width: fit-content;
}
.igp-link:hover { text-decoration: underline; text-underline-offset: 3px; }
.igp-handle { font-size: 0.85rem; color: var(--muted); }

.igp-stats {
  display: flex;
  gap: clamp(1.1rem, 2.5vw, 1.9rem);
  margin-top: 0.1rem;
}
.igp-stats div { display: flex; flex-direction: column; line-height: 1.2; }
.igp-stats b { font-weight: 700; font-size: 1.1rem; color: var(--white); }
.igp-stats span {
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* brand accent rather than Instagram blue: every other action on this site
   is the same magenta, and a blue button here would be the only one */
.igp-follow {
  flex-shrink: 0;
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: 10px;
  padding: 0.6rem 1.45rem;
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-decoration: none;
  box-shadow: 0 0 0 1px rgba(255, 61, 166, 0.35),
              0 6px 26px -6px rgba(255, 61, 166, 0.55);
  transition: transform 250ms var(--ease), box-shadow 300ms var(--ease);
}
.igp-follow:hover {
  transform: translateY(-1px);
  box-shadow: 0 0 0 1px rgba(255, 61, 166, 0.5),
              0 10px 34px -6px rgba(255, 61, 166, 0.75);
}

/* the grid runs full bleed, outside the wrap */
.ig-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: clamp(4px, 0.45vw, 7px);
}

.ig-cell {
  position: relative;
  aspect-ratio: 3 / 4;     /* the real tile shape, measured off the profile */
  overflow: hidden;
  border-radius: 4px;
  background: var(--surface);
  display: block;
}
.ig-cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 1s var(--e-out);
}
.js .ig-cell:hover img { transform: scale(1.07); }

.ig-cell::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: opacity 300ms var(--ease);
}
.ig-cell::before {
  content: "";
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 50%;
  width: 26px;
  height: 26px;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.82);
  transition: opacity 350ms var(--ease), transform 350ms var(--ease);
  background: var(--accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='black' d='M12 2.16c3.2 0 3.58.01 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.43.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s-.01 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.43.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58-.01-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.43-.36-1.06-.41-2.23C2.17 15.58 2.16 15.2 2.16 12s.01-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.43-.16 1.06-.36 2.23-.41C8.42 2.17 8.8 2.16 12 2.16zm0 4.38a5.46 5.46 0 1 0 0 10.92 5.46 5.46 0 0 0 0-10.92zm0 1.62a3.84 3.84 0 1 1 0 7.68 3.84 3.84 0 0 1 0-7.68zm5.65-2.9a1.28 1.28 0 1 0 0 2.56 1.28 1.28 0 0 0 0-2.56z'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='black' d='M12 2.16c3.2 0 3.58.01 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.43.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s-.01 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.43.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58-.01-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.43-.36-1.06-.41-2.23C2.17 15.58 2.16 15.2 2.16 12s.01-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.43-.16 1.06-.36 2.23-.41C8.42 2.17 8.8 2.16 12 2.16zm0 4.38a5.46 5.46 0 1 0 0 10.92 5.46 5.46 0 0 0 0-10.92zm0 1.62a3.84 3.84 0 1 1 0 7.68 3.84 3.84 0 0 1 0-7.68zm5.65-2.9a1.28 1.28 0 1 0 0 2.56 1.28 1.28 0 0 0 0-2.56z'/%3E%3C/svg%3E") center/contain no-repeat;
}
.js .ig-cell:hover::after { opacity: 1; }
.js .ig-cell:hover::before { opacity: 1; transform: translate(-50%, -50%) scale(1); }

@media (max-width: 1020px) {
  .ig-grid { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 640px) {
  .ig-grid { grid-template-columns: repeat(3, 1fr); }
  .ig-profile { flex-direction: column; }
  .ig-profile .igp-body { align-items: center; text-align: center; }
  .ig-profile .igp-stats { justify-content: center; }
  .ig-profile .igp-follow {
    margin-left: 0;
    padding: 0.8rem 2rem;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* ==========================================================================
   The cursor. A dot on the pointer, a ring easing behind it, and a state
   read from whatever is underneath. This layer is ABOVE the page — the
   fluid is below it — so there is always feedback, including over the
   photographs and over the form where the fluid is occluded.
   ========================================================================== */

/* the native arrow only goes away once the script is running and has
   confirmed a fine pointer */
html.has-cursor,
html.has-cursor a,
html.has-cursor button,
html.has-cursor label,
html.has-cursor input,
html.has-cursor select,
html.has-cursor textarea,
html.has-cursor [role="button"] { cursor: none; }

.cur-dot,
.cur-ring {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 200;          /* over everything, including the header and menu */
  pointer-events: none;  /* must never eat a click */
  opacity: 0;
  transition: opacity 220ms var(--ease);
  will-change: transform;
}

html.cursor-live .cur-dot,
html.cursor-live .cur-ring { opacity: 1; }

.cur-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--white);
  transition: opacity 220ms var(--ease), width 260ms var(--e-out),
              height 260ms var(--e-out), border-radius 260ms var(--e-out);
}

.cur-ring {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.55);
  background: transparent;
  transition: opacity 220ms var(--ease), width 320ms var(--e-out),
              height 320ms var(--e-out), background-color 320ms var(--e-out),
              border-color 320ms var(--e-out);
}

/* anything clickable: the ring opens and takes the brand colour */
.cur-ring.is-on {
  width: 54px;
  height: 54px;
  border-color: rgba(255, 61, 166, 0.85);
  background: rgba(255, 61, 166, 0.14);
}

/* a photograph or a card says what it does */
.cur-ring.is-label {
  width: 84px;
  height: 84px;
  border-color: rgba(255, 61, 166, 0.9);
  background: rgba(255, 61, 166, 0.18);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.cur-label {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--white);
  opacity: 0;
  transform: translateY(3px);
  transition: opacity 240ms var(--e-out), transform 240ms var(--e-out);
}
.cur-ring.is-label .cur-label { opacity: 1; transform: none; }

/* over a text field the dot becomes a caret and the ring gets out of the way */
.cur-ring.is-text {
  width: 0;
  height: 0;
  border-color: transparent;
  background: transparent;
}
html.cursor-live .cur-ring.is-text ~ .cur-dot,
.cur-ring.is-text + .cur-dot {
  width: 2px;
  height: 22px;
  border-radius: 1px;
}

/* the press */
.cur-ring.is-down { transform-origin: 50% 50%; border-color: var(--accent); }

/* the dot hides inside the larger states so the ring reads cleanly */
.cur-ring.is-label ~ .cur-dot { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .cur-dot, .cur-ring, .cur-label { transition: none; }
}

/* a coarse pointer never gets any of this */
@media (hover: none), (pointer: coarse) {
  .cur-dot, .cur-ring { display: none; }
  html.has-cursor, html.has-cursor * { cursor: auto; }
}

/* a photograph with nothing to open: the ring opens a little, stays white */
.cur-ring.is-media {
  width: 48px;
  height: 48px;
  border-color: rgba(255, 255, 255, 0.75);
  background: rgba(255, 255, 255, 0.06);
}

/* the new lockup is a wide two-line mark, so both places it appears are
   sized off its own proportions rather than the old round badge's */
.brand img { height: 34px; }
.footer-badge { width: clamp(190px, 22vw, 280px); }

/* ---------------------------------------- the lit rule, used more widely */

/* the hairline with a glowing centre segment, lifted out of the footer so
   the centred sections can use it too. It is a punctuation mark inside a
   section, never a divider between two of them — a line across a section
   boundary is the seam Giovani asked to be rid of. */
.rule { margin-block: clamp(1.75rem, 3.4vw, 2.75rem) clamp(1.5rem, 3vw, 2.25rem); }

.intro--centred .rule { margin-block: clamp(2rem, 3.6vw, 3rem) 0; }
.thanks .rule { margin-block: clamp(1.75rem, 3.2vw, 2.5rem) clamp(1.75rem, 3.2vw, 2.5rem); }
.ig-head .rule { width: min(420px, 70%); margin-block: 0.5rem 0; }

/* ------------------------------------------- the pink button turns white */

/* On hover the accent button flips to white rather than running the arrow
   hand-off. The arrow swap is explicitly cancelled here: it belongs to the
   light and ghost buttons, which keep it. */
.btn-accent {
  transition: transform 300ms var(--ease), box-shadow 400ms var(--ease),
              background-color 320ms var(--e-io), color 320ms var(--e-io);
}
.btn-accent:hover,
.btn-accent:focus-visible {
  background: var(--white);
  color: #111;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.4),
              0 8px 30px -6px rgba(255, 61, 166, 0.5),
              0 0 52px -14px rgba(255, 61, 166, 0.6);
}
.btn-accent:hover .btn-badge,
.btn-accent:focus-visible .btn-badge {
  background: #111;
  color: var(--white);
}
.btn-accent .btn-badge {
  transition: background-color 320ms var(--e-io), color 320ms var(--e-io);
}

/* the arrows hold still on this button: the colour flip is the whole move */
.btn-accent:hover .btn-arrow,
.btn-accent:focus-visible .btn-arrow { transform: none; }
.btn-accent:hover .btn-arrow-2,
.btn-accent:focus-visible .btn-arrow-2 { transform: translateX(-180%); }

/* the Instagram Follow button is a pink button too, so it flips the same way */
.igp-follow {
  transition: transform 250ms var(--ease), box-shadow 300ms var(--ease),
              background-color 320ms var(--e-io), color 320ms var(--e-io);
}
.igp-follow:hover {
  background: var(--white);
  color: #111;
}

/* ------------------------------------------------ the thank you page */

.thanks {
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding-block: clamp(6rem, 14vh, 10rem);
}
.thanks-inner { max-width: 44rem; margin-inline: auto; }
.thanks-mark {
  display: block;
  width: clamp(200px, 26vw, 320px);
  height: auto;
  margin: 0 auto clamp(2rem, 4vw, 3rem);
}
.thanks .t-h2 { margin-bottom: 1.25rem; }
.thanks .lede { max-width: 34ch; margin-inline: auto; }
.thanks-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.85rem;
  margin-top: clamp(2rem, 4vw, 3rem);
}

/* ==========================================================================
   Gala film: the silent loop behind a card, and the neon viewer on the page.
   ========================================================================== */

/* the loop sits exactly where the card photograph did */
.gala-media video.card-loop {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* a touch past cover so no edge of the source can ever read as a bar */
  transform: scale(1.04);
  transition: transform var(--d-2) var(--e-out);
}
.js .gala-card:hover .gala-media video.card-loop,
.js .gala-card:focus-visible .gala-media video.card-loop { transform: scale(1.08); }

/* ------------------------------------------------------- the viewer */

.film {
  --played: 0%;
  position: relative;
  margin: clamp(2rem, 4vw, 3rem) 0 clamp(2.5rem, 5vw, 4rem);
  border-radius: 16px;
  overflow: hidden;
  background: #000;
  /* the neon frame: a magenta hairline that actually throws light */
  box-shadow: 0 0 0 1px rgba(255, 61, 166, 0.55),
              0 0 34px -6px rgba(255, 61, 166, 0.55),
              0 0 90px -30px rgba(255, 61, 166, 0.8);
}

.film video {
  display: block;
  width: 100%;
  height: auto;
  cursor: pointer;
}

/* the big centre button, gone once the film is running */
.film-big {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border: 0;
  background: rgba(0, 0, 0, 0.35);
  color: var(--white);
  cursor: pointer;
  transition: opacity 320ms var(--ease);
}
.film.is-playing .film-big { opacity: 0; pointer-events: none; }

.film-big span {
  display: grid;
  place-items: center;
  width: clamp(62px, 8vw, 88px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 0 0 1px rgba(255, 61, 166, 0.5),
              0 10px 40px -8px rgba(255, 61, 166, 0.9);
  transition: transform 300ms var(--e-out);
}
.film-big:hover span { transform: scale(1.06); }

/* the control bar */
.film-bar {
  display: flex;
  align-items: center;
  gap: clamp(0.7rem, 1.6vw, 1.1rem);
  padding: 0.8rem clamp(0.85rem, 2vw, 1.25rem);
  background: linear-gradient(to top, rgba(0, 0, 0, 0.92), rgba(0, 0, 0, 0.55));
}

.film-btn {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.05);
  color: var(--white);
  cursor: pointer;
  transition: background-color 260ms var(--e-io), border-color 260ms var(--e-io),
              color 260ms var(--e-io);
}
.film-btn:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* only one of the two glyphs shows at a time */
.film .i-pause, .film.is-playing .i-play { display: none; }
.film.is-playing .i-pause { display: block; }
.film .i-muted { display: none; }
.film.is-muted .i-muted { display: block; }
.film.is-muted .i-sound { display: none; }

.film-time {
  flex: 0 0 auto;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  letter-spacing: 0.02em;
}
.film-time b { color: var(--white); font-weight: 600; }

/* the scrubber. The played portion is painted from --played, which the
   script updates, so the fill tracks without a second element. */
.film-seek {
  flex: 1 1 auto;
  min-width: 0;
  appearance: none;
  -webkit-appearance: none;
  height: 22px;
  background: transparent;
  cursor: pointer;
}
.film-seek::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(to right, var(--accent) var(--played), rgba(255, 255, 255, 0.18) var(--played));
}
.film-seek::-moz-range-track {
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(to right, var(--accent) var(--played), rgba(255, 255, 255, 0.18) var(--played));
}
.film-seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 13px;
  height: 13px;
  margin-top: -5px;
  border: 0;
  border-radius: 50%;
  background: var(--white);
  box-shadow: 0 0 10px 1px rgba(255, 61, 166, 0.9);
}
.film-seek::-moz-range-thumb {
  width: 13px;
  height: 13px;
  border: 0;
  border-radius: 50%;
  background: var(--white);
  box-shadow: 0 0 10px 1px rgba(255, 61, 166, 0.9);
}
.film-seek:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.film-caption {
  margin-top: 0.9rem;
  font-size: 0.8rem;
  color: var(--muted);
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .gala-media video.card-loop { transform: none; }
}

/* ------------------------------------------------ mobile: the film bar */

/* On a phone the four controls in one row leave the scrubber about 130px
   wide, which is not enough to scrub a two minute film. The slider takes
   its own full-width row above the buttons instead. */
@media (max-width: 560px) {
  .film-bar {
    flex-wrap: wrap;
    row-gap: 0.6rem;
    padding-bottom: 0.9rem;
  }
  .film-seek {
    order: -1;
    flex: 1 0 100%;
  }
  .film-mute { margin-left: auto; }
  .film-time { font-size: 0.78rem; }
}

/* the poster now matches the film's own shape, so the box never jumps when
   metadata arrives; height:auto keeps that aspect from the attributes */
.film video { aspect-ratio: auto; }

/* ------------------------------------------------------- line breaking */

/* No orphans. A headline must never drop a single short word onto a line
   of its own ("Orlando Summer Gala" / "2022"). `balance` evens the line
   lengths of a headline; `pretty` only guards the last line, which is the
   right treatment for running copy. */
.t-hero,
.t-h2,
.t-h3,
.t-state,
.page-title,
.gala-title,
.igp-name,
.footer-line--lead {
  text-wrap: balance;
}

.lede,
.page-lede,
.prod-body,
.film-caption,
.igp-bio,
.t-body {
  text-wrap: pretty;
}

/* ==========================================================================
   Mobile: headlines and their subheadlines sit centred.
   On a phone the measure is narrow enough that left-aligned heads read as
   ragged against the centred sections that were already there; centring
   the whole column makes one rhythm down the page.
   ========================================================================== */

@media (max-width: 640px) {

  /* the hero */
  .hero-copy { text-align: center; }
  .hero-title { margin-inline: auto; }
  .hero-cta { display: flex; justify-content: center; }
  .hero-scroll-inner { justify-content: center; }

  /* every section head: kicker, headline, lede, and its action */
  .sec-head { text-align: center; }
  .sec-head-row { justify-items: center; }
  .sec-head-left { max-width: none; margin-inline: auto; }
  .sec-head-row .btn { justify-self: center; }

  /* gala and film page heads */
  .page-head { text-align: center; }
  .page-title,
  .page-lede { margin-inline: auto; }
  .page-back { display: flex; justify-content: center; }

  /* what we produce */
  .prod-copy { text-align: center; }
  .prod-body { margin-inline: auto; }

  /* the gala cards */
  .gala-card { text-align: center; }
  .gala-title { margin-inline: auto; }

  /* the inquiry column above the form */
  .inquiry-cols .sec-head { text-align: center; }

  /* the film caption and Instagram head are centred already */
}

/* ==========================================================================
   v9. Mobile pass two.
   ========================================================================== */

/* ------------------------------------------- the mark, with MOTION on it */

/* the header carries the full lockup now, so it is sized off the lockup's
   own 1.86:1 rather than a script-only height */
.brand img {
  height: auto;
  width: clamp(104px, 13vw, 132px);
}
.site-header { padding-block: 0.75rem; }

/* ------------------------------------------------------ the lit rules */

/* the glow on the centre segment carries further */
.rule::after,
.footer-rule::after {
  width: 110px;
  box-shadow: 0 0 18px 2px rgba(255, 61, 166, 0.9),
              0 0 42px 6px rgba(255, 61, 166, 0.45);
}

/* --------------------------------------------------------- the menu */

.menu {
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}

.menu-nav {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  width: 100%;
}

.menu-link {
  position: relative;
  display: block;
  width: min(520px, 100%);
  padding-block: clamp(1.1rem, 3.2vw, 1.9rem);
  text-align: center;
  /* the opening: each line rises and clears as the overlay settles */
  opacity: 0;
  transform: translateY(16px);
  filter: blur(8px);
  transition: opacity 620ms var(--e-out), transform 620ms var(--e-out),
              filter 700ms var(--e-out), color 300ms var(--e-io);
  transition-delay: calc(var(--i, 0) * 70ms + 120ms);
}
.menu.open .menu-link { opacity: 1; transform: none; filter: blur(0); }

/* the divider between items: a hairline that lights at its centre, the
   same punctuation the rest of the site uses */
.menu-link + .menu-link::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(340px, 76%);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line) 26%, var(--line) 74%, transparent);
}
.menu-link + .menu-link::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 54px;
  height: 1px;
  background: var(--accent);
  opacity: 0.55;
  box-shadow: 0 0 12px 1px rgba(255, 61, 166, 0.75);
}

/* -------------------------------------------- Instagram, phone shaped */

/* On a phone the profile card and the grid read as one screen, the way a
   real profile does: a single bordered block, the tiles flush against each
   other with hairlines instead of gaps. Same construction as the
   VibesByNeo section. */
@media (max-width: 760px) {
  .ig-head { margin-bottom: clamp(1.25rem, 3vw, 1.6rem); }
  .ig-head .rule { display: none; }

  .ig-profile {
    max-width: none;
    margin: 0 var(--pad);
    border-radius: 16px 16px 0 0;
    border-bottom: 0;
    padding: 1.1rem 1.15rem 1.25rem;
    gap: 1rem;
  }

  .ig-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0;
    max-width: none;
    margin: 0 var(--pad);
    border: 1px solid var(--line);
    border-top: 0;
    border-radius: 0 0 16px 16px;
    overflow: hidden;
    background: var(--surface);
  }

  .ig-cell {
    border-radius: 0;
    aspect-ratio: 1 / 1;
    border-right: 1px solid rgba(255, 255, 255, 0.07);
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  }
  .ig-cell:nth-child(2n) { border-right: 0; }
  .ig-cell:nth-last-child(-n + 2) { border-bottom: 0; }
}

@media (max-width: 640px) {
  /* the card stacks and centres */
  .ig-profile { flex-direction: column; align-items: center; text-align: center; }
  .igp-body { align-items: center; text-align: center; }
  .igp-top { justify-content: center; }
  .igp-stats { justify-content: center; gap: 1.5rem; }
  .igp-link { margin-inline: auto; }
  .igp-follow { margin-left: 0; }
}

/* ------------------------------------------------ mobile: actions centred */

@media (max-width: 640px) {
  /* two calls to action stay side by side rather than stacking */
  .hero-cta {
    display: flex;
    flex-wrap: nowrap;
    justify-content: center;
    gap: 0.6rem;
  }
  .hero-cta .btn {
    padding-left: 1rem;
    gap: 0.5rem;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
  }
  .hero-cta .btn-badge { width: 32px; height: 32px; }

  /* the inquiry's own action centres with everything else */
  .form-actions { display: flex; justify-content: center; }
}

/* --------------------------------- mobile: the hero sits on the screen */

@media (max-width: 640px) {
  /* centred in the viewport on arrival rather than pinned to the bottom */
  .hero {
    justify-content: center;
    padding-top: 5rem;
    padding-bottom: 3rem;
  }
}

/* ------------------------------------------- mobile: gentler reveals */

/* the focus pull is a whisper on a phone: a small blur over a short
   distance, so scrolling never feels like it is fighting the page */
@media (max-width: 760px) {
  .js .rv,
  .js .rv-l,
  .js .rv-r,
  .js .rv-d {
    filter: blur(4px);
    transition: filter 560ms cubic-bezier(0.33, 1, 0.68, 1);
  }
  .js :where([data-rv], .hero) :where(.t-hero, .t-h2, .t-state) {
    filter: blur(5px);
    transition: filter 560ms cubic-bezier(0.33, 1, 0.68, 1);
  }
}

/* ------------------------------- background: calmer, and alive on touch */

/* the blooms drift more slowly and travel less far, so the field reads as
   light in a room rather than lava */
.glow { animation-duration: 68s; }

/* Without a pointer there is no parallax, so on touch the two depth layers
   get their own slow counter-drift. That is the only thing that was making
   the background feel alive, and phones were getting none of it. */
@media (hover: none), (pointer: coarse) {
  .orbit-far {
    animation: orbit-far-drift 54s ease-in-out infinite alternate;
    will-change: transform;
  }
  .orbit-near {
    animation: orbit-near-drift 43s ease-in-out infinite alternate;
    will-change: transform;
  }
}

@keyframes orbit-far-drift {
  from { transform: translate3d(-14px, -8px, 0); }
  to   { transform: translate3d(16px, 10px, 0); }
}
@keyframes orbit-near-drift {
  from { transform: translate3d(26px, 14px, 0); }
  to   { transform: translate3d(-28px, -12px, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .orbit-far, .orbit-near { animation: none; }
  .menu-link { transition: none; }
}

/* the two hero actions must hold one line each on a narrow screen */
@media (max-width: 640px) {
  .hero-cta .btn { white-space: nowrap; font-size: 0.68rem; padding-left: 0.85rem; }
  .hero-cta .btn > span:first-child { white-space: nowrap; }
}
@media (max-width: 380px) {
  .hero-cta { gap: 0.45rem; }
  .hero-cta .btn { font-size: 0.62rem; padding-left: 0.7rem; gap: 0.4rem; }
  .hero-cta .btn-badge { width: 28px; height: 28px; }
}

/* the menu is a page, not a scrim: nothing of the hero reads through it */
.menu {
  background:
    radial-gradient(90% 60% at 50% 12%, rgba(255, 61, 166, 0.10), transparent 70%),
    radial-gradient(80% 55% at 50% 100%, rgba(60, 150, 255, 0.08), transparent 72%),
    #000;
}

/* the gala frames are a card on a phone, not a full-bleed block */
@media (max-width: 640px) {
  .gala-card { max-width: 300px; margin-inline: auto; }
  .gala-media { aspect-ratio: 4 / 5; }
  .gala-title { font-size: clamp(1.15rem, 5.2vw, 1.45rem); }
  .gala-grid { gap: clamp(2rem, 7vw, 2.75rem); }
}

/* ==========================================================================
   v10. The gala row, the profile card, and the room behind the inquiry.
   ========================================================================== */

/* two galas in a three-column track sat left with a hole beside them */
.gala-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  max-width: 760px;
  margin-inline: auto;
}
@media (max-width: 1024px) {
  .gala-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ------------------------------------------------- the profile card */

/* Instagram's own arrangement: the mark, then the meta, then Follow pushed
   to the far edge. The bio and the link are gone — they turned the card
   into a tall ragged column with dead space beside it. */
.ig-profile {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(1rem, 2.6vw, 2rem);
  max-width: 620px;
  padding: clamp(1.1rem, 2.2vw, 1.4rem) clamp(1.2rem, 2.6vw, 1.6rem);
}

.igp-body { gap: 0.5rem; }

.igp-name { font-size: 1.02rem; letter-spacing: -0.01em; }

.igp-stats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: clamp(1.1rem, 2.4vw, 1.9rem);
}
.igp-stats li { display: flex; align-items: baseline; gap: 0.35rem; }
.igp-stats b { font-size: 0.96rem; }
.igp-stats span { font-size: 0.7rem; letter-spacing: 0.06em; }

.igp-real { font-size: 0.86rem; font-weight: 600; color: var(--white); }
.igp-cat { display: inline; font-weight: 400; color: var(--muted); }

.igp-follow { padding: 0.55rem 1.25rem; font-size: 0.82rem; }

@media (max-width: 640px) {
  .ig-profile {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
    gap: 0.9rem;
  }
  .igp-stats { justify-content: center; }
}

/* The accent button throws a ~26px glow past its own edge, so an inline
   word-space between the two actions reads as them touching. They get a
   real gap that clears the light. */
.hero-cta,
.thanks-actions,
.intro-cta {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1rem, 1.8vw, 1.5rem);
  align-items: center;
}
@media (max-width: 640px) {
  .hero-cta { gap: 0.8rem; }
}
@media (max-width: 380px) {
  .hero-cta { gap: 0.6rem; }
}

/* ------------------------------------------- every button flips on hover */

/* The ghost and light buttons still ran the arrow hand-off while the accent
   button flipped colour, so the row behaved two different ways. They all
   flip now: accent goes white, ghost and light go magenta. */
.btn-ghost,
.btn-light {
  transition: transform 300ms var(--ease), background-color 320ms var(--e-io),
              color 320ms var(--e-io), border-color 320ms var(--e-io),
              box-shadow 380ms var(--ease);
}
.btn-ghost .btn-badge,
.btn-light .btn-badge {
  transition: background-color 320ms var(--e-io), color 320ms var(--e-io);
}

.btn-ghost:hover,
.btn-ghost:focus-visible,
.btn-light:hover,
.btn-light:focus-visible {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(255, 61, 166, 0.45),
              0 8px 30px -8px rgba(255, 61, 166, 0.7);
}
.btn-ghost:hover .btn-badge,
.btn-ghost:focus-visible .btn-badge,
.btn-light:hover .btn-badge,
.btn-light:focus-visible .btn-badge {
  background: var(--accent-ink);
  color: var(--accent);
}

/* the arrows hold still on all of them: the colour flip is the whole move */
.btn:hover .btn-arrow,
.btn:focus-visible .btn-arrow { transform: none; }
.btn:hover .btn-arrow-2,
.btn:focus-visible .btn-arrow-2 { transform: translateX(-180%); }
/* the menu close keeps its turn */
.menu-close:hover .btn-x,
.menu-close:focus-visible .btn-x { transform: rotate(90deg); }

/* --------------------------------------------- statements carry weight */

/* the big statements sit heavier, and the phrase that carries the colour
   is heavier still and actually lit rather than just tinted */
.t-state { font-weight: 600; }

.em > span,
.igp-link {
  font-weight: 700;
  color: var(--accent);
  text-shadow: 0 0 18px rgba(255, 61, 166, 0.55),
               0 0 46px rgba(255, 61, 166, 0.3);
}

/* the same lit treatment wherever accent type appears at size */
.kicker,
.t-h2 .em > span,
.t-hero .em > span {
  text-shadow: 0 0 16px rgba(255, 61, 166, 0.45),
               0 0 40px rgba(255, 61, 166, 0.22);
}

@media (prefers-reduced-motion: reduce) {
  .btn-ghost, .btn-light { transition: none; }
}

/* ==========================================================================
   v11. The gala cards: the title lives on the film.
   ========================================================================== */

/* the section reads centred, like the introduction and Instagram either
   side of it */
.galas .sec-head { text-align: center; }
.galas .sec-head-row { justify-items: center; }
.galas .sec-head-left { max-width: 42rem; margin-inline: auto; }
.galas .sec-head-row .btn { justify-self: center; }

/* smaller cards */
.gala-grid { max-width: 640px; }

.gala-card {
  border-radius: var(--r-media);
  overflow: hidden;
  transition: transform 420ms var(--e-out), box-shadow 420ms var(--e-out);
}

.gala-media { position: relative; overflow: hidden; }

/* the film sits back so the name on top of it stays readable */
.gala-media video.card-loop,
.gala-media img {
  filter: brightness(0.48) saturate(0.95);
  transition: filter 420ms var(--e-out), transform 620ms var(--e-out);
}

/* a little weight at the centre so the type never floats on a bright frame */
.gala-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(78% 58% at 50% 50%, rgba(0,0,0,0.5), transparent 78%);
  pointer-events: none;
}

.gala-label {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: 0.9rem;
  text-align: center;
  pointer-events: none;
}

/* one line, always */
.gala-title {
  display: block;
  white-space: nowrap;
  font-size: clamp(0.9rem, 1.35vw, 1.12rem);
  letter-spacing: -0.01em;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.75);
}

/* hover: the card grows a little and the night brightens */
.js .gala-card:hover,
.js .gala-card:focus-visible {
  transform: scale(1.035);
  box-shadow: 0 0 0 1px rgba(255, 61, 166, 0.35),
              0 26px 60px -24px rgba(0, 0, 0, 0.9),
              0 0 46px -14px rgba(255, 61, 166, 0.45);
}
.js .gala-card:hover .gala-media video.card-loop,
.js .gala-card:hover .gala-media img,
.js .gala-card:focus-visible .gala-media video.card-loop {
  filter: brightness(0.82) saturate(1.02);
  transform: scale(1.04);
}

@media (max-width: 640px) {
  .gala-card { max-width: 268px; }
  .gala-grid { gap: clamp(1.25rem, 5vw, 1.75rem); max-width: none; }
  .gala-title { font-size: 0.9rem; }
}

@media (prefers-reduced-motion: reduce) {
  .js .gala-card:hover { transform: none; }
  .gala-media video.card-loop, .gala-media img { transition: none; }
}

/* ---------------------------------------- the profile card, final spacing */

/* Only the handle, the counts and Follow remain. The Follow button was
   sitting hard against "FOLLOWING", so the column gap is now wide enough
   to read as a separate control rather than part of the sentence. */
.ig-profile {
  gap: clamp(1.25rem, 3vw, 2.5rem);
  padding: clamp(1.35rem, 2.6vw, 1.75rem) clamp(1.5rem, 3vw, 2rem);
  max-width: 660px;
}

.igp-body { gap: 0.85rem; }

.igp-name { font-size: 1.08rem; }

.igp-stats { gap: clamp(1.25rem, 2.8vw, 2.1rem); }
.igp-stats li { gap: 0.4rem; }

/* the button keeps clear air on its left, and its glow no longer crowds
   the counts */
.igp-follow {
  margin-left: clamp(0.5rem, 1.5vw, 1.25rem);
  padding: 0.6rem 1.5rem;
}

@media (max-width: 640px) {
  .ig-profile { gap: 1rem; padding: 1.25rem 1.1rem 1.4rem; }
  .igp-follow { margin-left: 0; }
  .igp-body { gap: 0.7rem; }
}

/* six pills per service need room to wrap without crowding the copy */
.prod-body { max-width: 42ch; }
@media (max-width: 640px) {
  .prod-body { max-width: 40ch; }
}

/* ==========================================================================
   v12. The mark travels. Above the fold is the logo and nothing else; as you
   scroll it shrinks and docks into the centre of the header.
   ========================================================================== */

/* ------------------------------------------------- the header, three up */

.site-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: clamp(0.75rem, 2vw, 1.5rem);
}
.hdr-side { display: flex; align-items: center; gap: clamp(0.75rem, 2vw, 1.25rem); }
.hdr-left { justify-content: flex-start; }
.hdr-right { justify-content: flex-end; }

/* the brand is the docking slot. On the home page it holds space and stays
   invisible, because the travelling mark lands exactly on top of it. */
.brand { justify-self: center; margin-right: 0; }
.brand img { width: clamp(104px, 13vw, 132px); height: auto; }

html.has-morph .brand img { opacity: 0; }
html.has-morph .brand { pointer-events: none; }

/* -------------------------------------------------- the travelling mark */

.hero { justify-content: center; align-items: center; }

/* reserves the mark's footprint so the hero has real height */
.hero-mark-slot {
  width: min(62vw, 640px);
  aspect-ratio: 620 / 334;
  max-height: 46svh;
}

.morph-mark {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 45;              /* over the header bar, under the menu */
  width: min(62vw, 640px);
  display: block;
  transform-origin: 50% 50%;
  will-change: transform;
  /* placed by script; hidden until it has been placed once so it can never
     flash at 0,0 */
  opacity: 0;
  transition: opacity 300ms var(--ease);
  pointer-events: auto;
}
html.morph-ready .morph-mark { opacity: 1; }
.morph-mark img { display: block; width: 100%; height: auto; }

/* while the menu is open the mark gets out of the way */
html.locked .morph-mark { opacity: 0; pointer-events: none; }

@media (max-width: 860px) {
  .hero-mark-slot,
  .morph-mark { width: min(80vw, 460px); }
  /* the nav goes, but its column must stay or the grid re-flows and the
     centred mark stops being centred */
  .hdr-left .hdr-nav { display: none; }
  .site-header { grid-template-columns: 1fr auto 1fr; }
}

/* no script, no morph: the header mark simply shows and the hero holds the
   lockup at rest so the page is never markless */
html:not(.has-morph) .hero-mark-slot { display: none; }

@media (prefers-reduced-motion: reduce) {
  .morph-mark { transition: none; }
}

/* ------------------------------------------------ the mark, a size down */

.hero-mark-slot,
.morph-mark { width: min(52vw, 520px); }

@media (max-width: 860px) {
  .hero-mark-slot,
  .morph-mark { width: min(68vw, 380px); }
}

/* ------------------------------------------------------ the scroll cue */

.hero-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(1.5rem, 4vh, 2.75rem);
  z-index: 3;
  display: grid;
  justify-items: center;
  gap: 0.5rem;
  color: var(--muted);
  text-decoration: none;
  /* it belongs to the hero, so it leaves as the hero does */
  transform: translateX(-50%);
  opacity: var(--cue-opacity, 1);
  transition: color 300ms var(--e-io);
}
.hero-cue:hover { color: var(--white); }

.hero-cue-line {
  display: block;
  width: 1px;
  height: clamp(30px, 5vh, 46px);
  background: linear-gradient(to bottom, transparent, var(--line) 30%, rgba(255, 61, 166, 0.7));
}

.hero-cue-arrow { display: block; animation: cue-nudge 2.4s var(--e-io) infinite; }

@keyframes cue-nudge {
  0%, 100% { transform: translateY(0); opacity: 0.55; }
  50%      { transform: translateY(4px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-cue-arrow { animation: none; }
}

/* ------------------------------------------- low power / saver friendly */

/* iOS Low Power Mode refuses to autoplay video and throttles timers. There
   is no battery API to ask, so the site infers it: if a muted card loop is
   refused, the page is marked and everything expensive stands down. The
   poster frame is already in place, so the cards simply become stills. */
html.power-save .glow,
html.power-save .hero-dust i,
html.power-save .orbit-far,
html.power-save .orbit-near,
html.power-save .hero-flashes span,
html.power-save .hero-cue-arrow { animation: none !important; }

html.power-save #fluid { display: none; }

html.power-save .hero-dust { opacity: 0.35; }
html.power-save .glow { opacity: 0.55; }

/* the reveal still happens, just cheaper: no blur to rasterise */
html.power-save .js .rv,
html.power-save .js .rv-l,
html.power-save .js .rv-r,
html.power-save .js .rv-d,
html.power-save .js :where([data-rv], .hero) :where(.t-hero, .t-h2, .t-state) {
  filter: none !important;
  transition: opacity 400ms var(--e-out);
}

/* the cards sit two-up on a phone, so the label has to fit a narrow card */
@media (max-width: 640px) {
  .gala-card { max-width: none; }
  .gala-grid { gap: clamp(0.7rem, 3vw, 1rem); }
  .gala-title { font-size: clamp(0.72rem, 3.4vw, 0.95rem); }
  .gala-label { padding: 0.6rem; }
}
@media (max-width: 380px) {
  .gala-title { font-size: 0.7rem; }
}

/* ------------------------------------- Instagram: card and grid as one */

/* The grid is full bleed and insets itself by --pad. The card lives inside
   `.wrap`, which ALREADY applies that padding, so giving it the same margin
   indented it twice and the two edges never lined up. The card takes no
   inline margin of its own; the wrap positions it and the grid matches. */
@media (max-width: 760px) {
  .ig-profile {
    margin-inline: 0;
    margin-bottom: 0;
    max-width: none;
    width: auto;
  }
  .ig-grid { margin-inline: var(--pad); }
}

/* ==========================================================================
   v13. Mobile: a still mark, a header that gets out of the way.
   ========================================================================== */

/* ------------------------------------------------ the skip link, properly */

/* It was absolutely positioned and merely translated out of view, so an
   overscroll at the top of the page dragged it into sight. Fixed to the
   viewport and faded out, it cannot be revealed by rubber-banding — and it
   still appears for a keyboard, which is the whole point of it. */
.skip-link {
  position: fixed;
  transform: translateY(-200%);
  opacity: 0;
  pointer-events: none;
  transition: transform 180ms var(--ease), opacity 180ms var(--ease);
}
.skip-link:focus,
.skip-link:focus-visible {
  transform: none;
  opacity: 1;
  pointer-events: auto;
}

/* --------------------------------------------------- the hero mark, still */

.hero-mark-slot { display: grid; place-items: center; }
.hero-mark-still { width: 100%; height: auto; display: block; }

/* when the travelling mark is running, the still one is redundant */
html.has-morph .hero-mark-still { display: none; }

/* and when it is not, there is no travelling mark to show */
html:not(.has-morph) .morph-mark { display: none; }
html:not(.has-morph) .hero-mark-slot { display: grid; }

/* ------------------------------------------------- the mobile header */

/* the header mark is absent until the hero has gone, so the logo is never
   on screen twice */
html.mobile-header .brand img {
  opacity: 0;
  transition: opacity 320ms var(--ease);
}
html.mobile-header.past-hero .brand img { opacity: 1; }

/* out of the way going down, back the moment you go up */
html.mobile-header .site-header {
  transition: transform 340ms var(--e-out), background-color 400ms var(--ease);
  will-change: transform;
}
html.mobile-header.header-away .site-header { transform: translateY(-100%); }

/* the menu overlay sits above it either way */
html.locked .site-header { transform: none !important; }

@media (prefers-reduced-motion: reduce) {
  html.mobile-header .site-header { transition: none; }
  .skip-link { transition: none; }
}

/* ---------------------------------------- mobile: the two actions pair up */

/* They belong side by side, so on a narrow screen the pair shrinks rather
   than stacking. The labels never wrap; the button gets smaller instead. */
@media (max-width: 640px) {
  .intro-cta {
    display: flex;
    flex-wrap: nowrap;
    justify-content: center;
    gap: 0.7rem;
  }
  .intro-cta .btn {
    white-space: nowrap;
    font-size: 0.68rem;
    padding: 0.5rem 0.5rem 0.5rem 0.9rem;
    gap: 0.45rem;
    letter-spacing: 0.05em;
  }
  .intro-cta .btn-badge { width: 30px; height: 30px; }

  /* SEND INQUIRY is a grid item pinned to the start of the form, so
     centring inside it did nothing. The item itself has to fill the row. */
  .form-actions {
    justify-self: stretch;
    display: flex;
    justify-content: center;
  }
}

@media (max-width: 380px) {
  .intro-cta { gap: 0.5rem; }
  .intro-cta .btn { font-size: 0.6rem; padding-left: 0.7rem; gap: 0.35rem; }
  .intro-cta .btn-badge { width: 26px; height: 26px; }
}

/* -------------------------------------------- the background settles first */

/* The blooms are positioned as percentages of the DOCUMENT, and the document
   height moves several times while a page loads — fonts swap, lazy images
   arrive, video posters size themselves. Every one of those reflows nudges
   all ten blooms, which reads as a flicker in the first second.

   So the field is held invisible until the page has settled, then faded in.
   The repositioning still happens; it just happens where nobody can see it. */
.ambient,
.bg-wash {
  opacity: 0;
  transition: opacity 900ms var(--e-out);
}
html.bg-ready .ambient,
html.bg-ready .bg-wash { opacity: 1; }

/* no script: never leave the page black */
html:not(.js) .ambient,
html:not(.js) .bg-wash { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .ambient, .bg-wash { transition: none; }
}

/* ==========================================================================
   layout: cta-04 — banner strip, filled-band variant.
   A hairline above and below is the pattern's other option, but a full-width
   rule between sections is exactly the seam this site had removed, so the
   band carries its own border instead. Anatomy and values are the same ones
   the Instagram profile card already uses.
   ========================================================================== */

.booth {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(1.25rem, 3vw, 2.5rem);
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding: clamp(1.35rem, 2.6vw, 1.85rem) clamp(1.5rem, 3vw, 2.25rem);
  border: 1px solid var(--line);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.03);
}

.booth-copy { display: grid; gap: 0.4rem; min-width: 0; }
.booth-kicker { color: var(--accent); }
.booth-line { text-wrap: balance; }
.booth-note { max-width: 46ch; }

.booth-cta { flex: 0 0 auto; }

/* MOBILE: stacks, CTA full width (the pattern's stated mobile rule) */
@media (max-width: 760px) {
  .booth {
    flex-direction: column;
    align-items: stretch;
    text-align: center;
    gap: 1.25rem;
  }
  .booth-copy { justify-items: center; }
  .booth-note { margin-inline: auto; }
  .booth-cta { justify-content: center; width: 100%; }
}

/* A full-width button centres the LABEL+BADGE group, which leaves the label
   itself sitting left of centre by half the badge. Pinning the badge and
   letting the label span the whole button puts the words back on the axis.
   Full-width buttons ONLY: on a content-sized pill, taking the badge out of
   flow just lets the pill shrink underneath it and the label runs behind. */
@media (max-width: 760px) {
  .booth-cta {
    position: relative;
    padding-left: 0.6rem;
    /* the badge no longer contributes height once it is pinned, so the pill
       would collapse to the label and let the circle hang over its edge */
    min-height: calc(38px + 1.1rem);
  }
  .booth-cta > span:first-child {
    flex: 1 1 auto;
    text-align: center;
  }
  .booth-cta .btn-badge {
    position: absolute;
    right: 0.6rem;
    top: 50%;
    margin-top: -19px;
  }
}
