/* ============================================================
   Self-hosted brand fonts
   ============================================================ */
@font-face {
  font-family: 'Gotham Bold';
  src: url('fonts/Gotham Bold Regular.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ============================================================
   ENDEAVOR FILMS — design tokens
   ============================================================ */
:root {
  /* palette */
  --ink:        #0e0e0f;   /* near-black — hero / nav / footer base */
  --ink-soft:   #1c1d1f;
  --paper:      #ffffff;   /* body background */
  --stone:      #f3f1ea;   /* warm neutral for alternating panels */
  --teal:       #3d6a80;   /* steel teal — wedge dividers, links (darkened from ref for AA) */
  --teal-deep:  #29465a;
  --sage:       #66704f;   /* olive sage — testimonials / footer (darkened from ref for AA) */
  --sage-deep:  #4e5640;
  --slate:      #3f6371;   /* deep slate-teal — footer gradient (darkened from ref for AA) */
  --lime:       #a9ba17;   /* single bold accent, used sparingly */
  --text:       #17181a;
  --text-soft:  rgba(23, 24, 26, 0.66);
  --text-on-dark: rgba(255, 255, 255, 0.94);
  --text-on-dark-soft: rgba(255, 255, 255, 0.68); /* only safe on --ink — huge luminance headroom */

  /* type */
  --font-display: 'Space Grotesk', 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Lato', 'Helvetica Neue', Arial, sans-serif;

  /* rhythm */
  --edge: clamp(1.5rem, 5vw, 4.5rem);
  --radius: 4px;

  /* shared by .hero, .section-chevron-hero and .films — the hero's
     bottom V-notch and the chevron's top notch have to stay the exact
     same depth to interlock, and Films' own min-height accounts for
     the chevron's footprint so [chevron + Films] together still fill
     one screen the same way Photography does on its own. One source
     of truth so a future resize can't desync them. */
  --hero-chev: 110px;
}

/* ============================================================
   Reset & base
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--text);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; display: block; }

a { color: inherit; text-decoration: none; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.08;
  margin: 0;
}

p { margin: 0; }

button {
  font-family: inherit;
  cursor: pointer;
  background: none;
  border: none;
  color: inherit;
}

ul { list-style: none; margin: 0; padding: 0; }

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--ink);
  color: var(--paper);
  padding: 0.75rem 1.25rem;
  z-index: 1000;
}
.skip-link:focus {
  left: var(--edge);
  top: 0.75rem;
}

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   Shared bits
   ============================================================ */
.eyebrow {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-on-dark-soft);
  margin: 0 0 1.1rem;
}
.eyebrow-dark { color: var(--teal-deep); }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 1rem 1.9rem;
  border-radius: 999px;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
  min-height: 44px;
}
.btn:hover { transform: translateY(-1px); }

.btn-outline-light {
  border: 1.5px solid rgba(255,255,255,0.55);
  color: var(--text-on-dark);
}
.btn-outline-light:hover {
  border-color: var(--teal);
  color: var(--teal);
}
.btn-play {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--text-on-dark);
  color: var(--ink);
}
.btn-accent {
  /* muted sage, not the site's lime accent — sits better with the
     palette than a bright chartreuse call-to-action would here */
  background: var(--sage);
  color: var(--text-on-dark);
}
.btn-accent:hover { background: var(--sage-deep); }

.section-head {
  max-width: 640px;
  margin: 0 0 3rem;
}
.section-head h2 {
  font-size: clamp(2.1rem, 4vw, 3rem);
  margin-bottom: 1rem;
}
.section-intro {
  font-size: 1.05rem;
  color: var(--text-soft);
  max-width: 56ch;
}

/* Standalone chevron divider — the faceted motif echoed from the mark.
   Used where a section can't clip itself (e.g. bridging two blocks that
   don't share a background). */
.section-chevron {
  --chev: 55px;
  position: relative;
  z-index: 6;
  height: calc(var(--chev) * 2);
  margin-top: calc(var(--chev) * -1);
  background: linear-gradient(180deg, var(--teal) 0%, var(--sage) 100%);
  clip-path: polygon(
    0 0,
    50% var(--chev),
    100% 0,
    100% var(--chev),
    50% calc(var(--chev) * 2),
    0 var(--chev)
  );
  pointer-events: none;
}
/* CTA divider: downward-V top over the white section above, flat bottom
   that butts directly into the dark CTA (no white gap into a dark block) */
.section-chevron-cta {
  background: linear-gradient(180deg, var(--sage) 0%, var(--slate) 100%);
  clip-path: polygon(0 0, 50% var(--chev), 100% 0, 100% 100%, 0 100%);
}
/* Hero divider: nests into the hero's clipped V the same way the old
   Mission section did, but it's just the bare chevron ribbon now — no
   heading/copy. Solid white (not a gradient) so it just closes the gap
   left by the hero's own V-notch and dissolves flush into the Films
   section below (same background color, no seam to blend). */
.section-chevron-hero {
  /* twice the base ribbon thickness, matched to .hero's own --chev */
  --chev: var(--hero-chev);
  margin-top: calc(var(--chev) * -1 - 1px);
  background: var(--paper);
}

/* ============================================================
   Header
   ============================================================ */
.site-header {
  /* --hdr goes 0→1 linearly with scroll position (set by script.js);
     opacity caps at 0.92 once fully scrolled in */
  --hdr: 0;
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 500;
  background: rgba(14, 14, 15, calc(var(--hdr) * 0.92));
  backdrop-filter: saturate(140%) blur(calc(var(--hdr) * 6px));
  box-shadow: 0 1px 0 rgba(255, 255, 255, calc(var(--hdr) * 0.06));
}
.header-inner {
  max-width: 1400px;
  margin: 0 auto;
  padding: 1.1rem var(--edge);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}
.brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.brand-logo {
  height: 54px;
  width: auto;
}

.main-nav { position: relative; }
.main-nav ul {
  display: flex;
  gap: 2.1rem;
}
.main-nav a {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-on-dark-soft);
  padding: 0.5rem 0.1rem;
  border-bottom: 1px solid transparent;
  transition: color 0.2s ease;
}
.main-nav a:hover,
.main-nav a:focus-visible,
.main-nav a.is-active { color: var(--text-on-dark); }

/* sliding active-section indicator (desktop only) */
.nav-indicator {
  position: absolute;
  bottom: -2px;
  left: 0;
  width: 0;
  height: 2px;
  /* the blue used on the left-of-center "Justin" diamond, not the lime
     accent — a deliberate, separate choice for this indicator */
  background: var(--teal);
  border-radius: 2px;
  opacity: 0;
  transform: translateX(0);
  transition: transform 0.55s cubic-bezier(0.65, 0, 0.35, 1),
              width 0.55s cubic-bezier(0.65, 0, 0.35, 1),
              opacity 0.3s ease;
  pointer-events: none;
  will-change: transform, width;
}
.nav-indicator.is-visible { opacity: 1; }

/* "Contact" is a plain nav link like the rest now — no special-cased
   color, so it behaves identically (soft by default, bright on
   hover/active) instead of always sitting brighter than its siblings. */

.header-right {
  display: flex;
  align-items: center;
  gap: 1.4rem;
}
.social-row {
  display: flex;
  gap: 0.9rem;
}
.social-row a {
  color: var(--text-on-dark-soft);
  transition: color 0.2s ease;
}
.social-row a:hover { color: var(--text-on-dark); }

.menu-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
}
.menu-toggle span {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--text-on-dark);
  transition: transform 0.25s ease, opacity 0.25s ease;
}

/* ============================================================
   Hero
   ============================================================ */
.hero {
  /* matched to .section-chevron-hero's own --chev below — these two
     have to stay equal or the interlocking V notches misalign */
  --chev: var(--hero-chev);
  position: relative;
  min-height: 100svh;
  background: var(--ink);
  color: var(--text-on-dark);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* bottom edge dips to a downward V so the chevron ribbon below masks
     the rectangular video corners */
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--chev)),
    50% 100%,
    0 calc(100% - var(--chev))
  );
}

.hero-video {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
  background: var(--ink);
}
.hero-video video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(100deg, rgba(8,9,10,0.88) 0%, rgba(8,9,10,0.6) 42%, rgba(8,9,10,0.32) 68%, rgba(8,9,10,0.55) 100%),
    linear-gradient(0deg, rgba(8,9,10,0.5) 0%, rgba(8,9,10,0) 30%, rgba(8,9,10,0) 75%, rgba(8,9,10,0.6) 100%);
}

.hero-grain {
  position: absolute;
  inset: 0;
  z-index: 1;
  opacity: 0.3;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.35'/%3E%3C/svg%3E");
  mix-blend-mode: overlay;
}

.hero-inner {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 1400px;
  margin: 0 auto;
  width: 100%;
  padding: 7.5rem var(--edge) 1rem;
}

.hero-copy { max-width: 620px; }
.hero-copy-centered {
  max-width: 800px;
  margin: 0 auto;
  text-align: center;
}
/* same highlight treatment as the Mission section's buttons */
.hero-copy-centered .btn-outline-light:hover {
  border-color: var(--teal-deep);
  color: var(--text-on-dark);
  background: var(--teal-deep);
}

.hero h1 {
  font-family: 'Gotham Bold', 'Helvetica Neue', Arial, sans-serif;
  font-weight: 700;
  letter-spacing: -0.015em;
  margin: 0 0 2rem;
}
.eyebrow.hero-eyebrow-large {
  font-family: 'Gotham Bold', 'Helvetica Neue', Arial, sans-serif;
  font-weight: 700;
  letter-spacing: 0.08em;
  font-size: clamp(2.4rem, 5.2vw, 4.2rem);
  color: var(--text-on-dark);
  margin: 0 0 0.5rem;
  /* opens larger (scale, not font-size, so it's GPU-composited and stays
     fluid at every viewport width) and settles to its real size at 1.5s */
  transform-origin: center;
  animation: hero-name-intro 0.7s cubic-bezier(0.65, 0, 0.35, 1) 1.5s both;
}
.hero h1.hero-heading-small {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0.01em;
  font-size: clamp(1.2rem, 2.4vw, 1.7rem);
  margin: 0 auto 2rem;
  max-width: 44ch;
  /* stays invisible until the name has settled, then fades in */
  opacity: 0;
  animation: hero-fade-in 0.6s ease-out 1.5s both;
}
@keyframes hero-name-intro {
  from { transform: scale(1.35); }
  to { transform: scale(1); }
}
@keyframes hero-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
#watch-reel-btn {
  opacity: 0;
  animation: hero-fade-in 0.6s ease-out 1.5s both;
}

/* buttons carried over from the old Mission section, now anchored at
   the very bottom of the hero, outside the vertically-centered copy */
.hero-mission-actions {
  position: relative;
  z-index: 2;
  flex-shrink: 0;
  padding: 0 var(--edge) clamp(2.25rem, 6vh, 3.5rem);
}
.mission-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
}
/* these two buttons highlight in the same blue as the carousel dots,
   not the site's lime accent — scoped here so the hero/CTA buttons
   elsewhere keep their usual hover color */
.mission-actions .btn-outline-light:hover {
  border-color: var(--teal-deep);
  color: var(--text-on-dark);
  background: var(--teal-deep);
}

/* ============================================================
   Films
   ============================================================ */
.films {
  /* Matches Photography's own recipe (fill one viewport, center the
     content in it) rather than centering the *scroll position* on
     Films itself — that was tried and, on a section this short,
     scrolled up far enough to reveal the hero's background video
     above the chevron. Accounting for the chevron's own footprint
     here means [[chevron] + [Films]] together still fill one screen,
     so a plain anchor jump lands the same way Photography's does. */
  /* The negative-margin trick previously used to close the gap only
     pulled the content block up within this box — since justify-
     content:center still divides free space evenly around whatever
     the block's new (shrunken-by-margin) height comes out to, that
     shift added the exact same amount of space back below, and the
     section stopped reading as centered. Actually shrinking the box
     itself (instead of faking it with margins) lets a plain
     justify-content:center keep the gap symmetric while both gaps
     get smaller together. */
  min-height: calc(100svh - var(--hero-chev) - 10rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* matches Photography's own top/bottom padding — same recipe, same
     rhythm, since the two now sit right next to each other in flow */
  padding: 2.75rem var(--edge);
  /* The fixed .site-header floats over this section once scrolled to
     (it isn't in normal flow, so it doesn't push content down on its
     own). With the gap above already tightened, centered content was
     landing partly behind it. Reserving the header's real measured
     height as top padding means justify-content:center now centers
     within the space actually visible below the header, not the full
     box — the top gap still reads as "closed," just measured from
     where the header ends instead of from the true top edge. */
  padding-top: calc(var(--header-h, 96px) + 1.5rem);
  /* raised from 1400px so the wider carousel below has room — the
     section-head/section-intro elements have their own 640px/56ch caps,
     so this only actually widens the tabs row and the carousel */
  max-width: 1650px;
  margin: 0 auto;
  width: 100%;
}
.films .section-head { margin-left: auto; margin-right: auto; text-align: center; }
.films .tabs { justify-content: center; }

.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
  border-bottom: 1px solid #e4e1d6;
  margin-bottom: 2.5rem;
}
.tab {
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-soft);
  padding: 0 0 1rem;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 0.2s ease, border-color 0.2s ease;
  min-height: 44px;
}
.tab:hover { color: var(--text); }
.tab.is-active {
  color: var(--teal-deep);
  border-color: var(--teal);
}

.film-carousel {
  /* The ACTIVE CARD (not the carousel box) is the thing that should read
     as 70% of the page: card = 82% of the rail (see .film-card below),
     and — now that the arrows live below the rail instead of flanking it
     (.film-controls) — the rail IS the carousel's width, with nothing
     else sharing the row to subtract. So carousel width = 70% / 0.82,
     which nets the card out to exactly 70% of .films' content box once
     the 82% is applied. If that 0.82 ratio below ever changes, this
     divisor must change with it — they're two halves of one equation. */
  width: calc(70% / 0.82);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 1.5rem;
}
.film-controls {
  display: flex;
  justify-content: center;
  gap: 1rem;
}
.film-controls .slide-arrow {
  border-color: rgba(23, 24, 26, 0.18);
  color: var(--text);
}
.film-controls .slide-arrow:hover { border-color: var(--teal); color: var(--teal-deep); }

.film-rail {
  position: relative;
  width: 100%;
  min-width: 0;
  overflow: hidden;
  /* horizontal drag is captured by the swipe handler in script.js;
     pan-y keeps vertical page scroll working under a touch drag that
     starts on the rail instead of the swipe capturing it too. */
  touch-action: pan-y;
}
/* Fades the peeking neighbor cards into the page at each edge. This is
   a pair of solid overlays painted over the rail (not a mask-image
   gradient on the rail itself) — mask-image here was unreliable across
   browsers: it rendered asymmetrically and would only repaint one side
   correctly after an unrelated hover forced a reflow. Plain overlays
   have no such repaint dependency.
   Multi-stop (not a flat 2-stop linear fade): a straight opacity ramp
   reads as an abrupt wall right at the outer edge because the eye is
   far more sensitive to alpha change near full-opacity than near
   transparent. Easing through intermediate stops removes that "hard
   edge" look and — combined with the wider zone below — keeps more of
   each peeking card visible through partial opacity instead of fully
   hidden under solid paper near the rail's edge. */
.film-rail::before,
.film-rail::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(40px, 11%, 110px);
  z-index: 3;
  pointer-events: none;
}
.film-rail::before {
  left: 0;
  background: linear-gradient(
    90deg,
    var(--paper) 0%,
    rgba(255, 255, 255, 0.85) 20%,
    rgba(255, 255, 255, 0.45) 55%,
    rgba(255, 255, 255, 0) 100%
  );
}
.film-rail::after {
  right: 0;
  background: linear-gradient(
    270deg,
    var(--paper) 0%,
    rgba(255, 255, 255, 0.85) 20%,
    rgba(255, 255, 255, 0.45) 55%,
    rgba(255, 255, 255, 0) 100%
  );
}
.film-track {
  position: relative;
  display: flex;
  gap: 1.25rem;
  will-change: transform;
  transition: transform 0.55s cubic-bezier(0.65, 0, 0.35, 1);
}
.film-card {
  /* the active card reads at 82% of the rail's width — paired with
     .film-carousel's width:calc(70%/0.82) above so the card itself
     nets out to exactly 70% of the page; the remaining 18% is what
     lets the previous/next cards peek in at the sides */
  flex: 0 0 82%;
  min-width: 0;
  position: relative;
  /* close to a native 1920x1080 frame, so it reads as video rather
     than a photo tile */
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  color: var(--text-on-dark);
  background-size: cover;
  background-position: center;
  /* explicit even though `cover` alone should prevent visible tiling —
     script.js used to set the placeholder via the `background`
     shorthand inline, which silently resets background-repeat (and
     background-size!) to their initial values for that element,
     overriding these two lines entirely. Harmless for a gradient (it
     has no intrinsic size to tile), but it meant a real photo dropped
     in later via backgroundImage rendered at native size and tiled —
     the "repeats in a 4-grid" bug. Fixed at the source (that inline
     style is background-image-only now, a longhand, so it can't
     clobber these), but keeping this explicit is cheap insurance. */
  background-repeat: no-repeat;
  isolation: isolate;
}
.film-card[data-video-id] { cursor: pointer; }
.film-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,0.78) 100%);
  z-index: 1;
}
.film-card-body {
  position: relative;
  z-index: 2;
  padding: 0.9rem 1rem;
  width: 100%;
}
.film-card-tag {
  display: block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-on-dark-soft);
  margin-bottom: 0.3rem;
}
.film-card-title { font-family: var(--font-display); font-size: 1rem; font-weight: 600; }
.film-card-play {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: rgba(255,255,255,0.16);
  border: 1px solid rgba(255,255,255,0.55);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-on-dark);
  backdrop-filter: blur(1px);
  transition: transform 0.2s ease, background 0.2s ease;
}
.film-card[data-video-id]:hover .film-card-play,
.film-card[data-video-id]:focus-visible .film-card-play {
  transform: translate(-50%, -50%) scale(1.08);
  background: rgba(255,255,255,0.28);
}

/* ============================================================
   Photography
   ============================================================ */
.photography {
  /* fills one viewport when jumped to from the nav — same recipe as
     Films just above it, same top/bottom padding for a matching rhythm */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 2.75rem var(--edge);
  max-width: 1400px;
  margin: 0 auto;
  width: 100%;
}
.photography .section-head { margin-left: auto; margin-right: auto; text-align: center; margin-bottom: 1.75rem; }
.photography .tabs { justify-content: center; margin-bottom: 1.75rem; }

/* Fixed 3-across × 2-tall gallery (not a carousel) — photoData is
   trimmed to 6 items/tab to fit exactly, no empty cells. Sized as a
   percentage of .photography's own content box (same pattern as
   .film-carousel) rather than an svh-capped cell size, so thumbnails
   read larger and scale with the page instead of being squeezed to
   fit one screen height — this section can now run taller than one
   viewport on a nav jump, which is an accepted tradeoff for bigger
   thumbnails at a page-relative width. */
.photo-rail {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  width: 90%;
  margin: 0 auto;
}
.photo-card {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  color: var(--text-on-dark);
  /* longhand, not the `background` shorthand — this card can also carry
     a real <img> cover (.photo-card-media) painted on top; keeping this
     the image-only longhand avoids the shorthand-resets-size/repeat
     gotcha documented in CLAUDE.md if a background-image swap is ever
     added here later the way film-card's placeholder-then-real-thumbnail
     swap works. */
  background-image: linear-gradient(155deg, #4a4a4a 0%, #1c1c1c 130%);
  cursor: pointer;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.photo-card:hover,
.photo-card:focus-visible {
  transform: scale(1.035);
  box-shadow: 0 12px 28px rgba(0,0,0,0.28);
}
/* Real cover photo, when a gallery has one — sits between the gradient
   placeholder (behind it) and the ::after darkening overlay + body text
   (both above it, via the stacking rules below). No z-index needed here:
   it's earlier in source order than ::after, and .photo-card-body's
   explicit z-index already lifts it above any auto-stacked sibling. */
.photo-card-media {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photo-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,0.8) 100%);
}
.photo-card-body {
  position: relative;
  z-index: 1;
  padding: 1.1rem 1.2rem;
  width: 100%;
}
.photo-card-tag {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-on-dark-soft);
  margin-bottom: 0.3rem;
}
.photo-card-title {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
}

/* ============================================================
   Testimonials / Clients — the whole section is the chevron
   ============================================================ */
.testimonials {
  --chev: 55px;
  position: relative;
  /* fallback shown when the reel is absent (reduced motion / load) */
  background: linear-gradient(155deg, var(--sage) 0%, var(--sage-deep) 100%);
  color: var(--text-on-dark);
  padding: calc(var(--chev) + 4rem) var(--edge) calc(var(--chev) + 3rem);
  overflow: hidden;
  clip-path: polygon(
    0 0,
    50% var(--chev),
    100% 0,
    100% calc(100% - var(--chev)),
    50% 100%,
    0 calc(100% - var(--chev))
  );
}
/* looping reel plays behind the section, dimmed by the sage overlay */
.testimonials-video {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: var(--ink);
}
.testimonials-video video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  /* darken the footage so white copy stays legible under the sage overlay */
  filter: brightness(0.5);
}
.testimonials-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(155deg, var(--sage) 0%, var(--sage-deep) 100%);
  opacity: 0.85;
}
.testimonials-inner {
  position: relative;
  z-index: 2;
}
/* mirrors the studio section head: eyebrow + big display line, centered */
.testimonials-head {
  margin: 0 auto 2.5rem;
  text-align: center;
}
.testimonials-head h2 { color: var(--text-on-dark); }

.testimonial-slider {
  max-width: 900px;
  margin: 0 auto 3.5rem;
  display: flex;
  align-items: center;
  gap: 1.5rem;
}
.slide-arrow {
  flex-shrink: 0;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.4);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-on-dark);
  transition: border-color 0.2s ease, color 0.2s ease;
}
.slide-arrow:hover { border-color: var(--teal); color: var(--teal); }

/* height is fixed in px by script.js to whichever testimonial is
   tallest, so switching slides never resizes the track (and therefore
   never resizes the chevron the whole section is clipped to) */
.testimonial-track {
  position: relative;
  flex: 1;
  height: 220px;
  overflow: hidden;
  touch-action: pan-y;
  transition: height 0.35s ease;
}
.testimonial {
  position: absolute;
  inset: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  opacity: 0;
  transform: translateX(100%);
  transition: transform 0.55s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.4s ease;
}
.testimonial.is-active { opacity: 1; transform: translateX(0); }
.testimonial p {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 2vw, 1.4rem);
  font-weight: 500;
  line-height: 1.5;
  margin-bottom: 1.25rem;
}
.testimonial cite {
  font-style: normal;
  font-size: 0.9rem;
  font-weight: 300;
  letter-spacing: 0.04em;
  color: var(--text-on-dark);
}

/* infinite horizontal scroll: the track holds the logo list twice back
   to back, and animates exactly -50% (the width of one full copy) — the
   instant the first copy scrolls out of view the second is sitting in
   its exact starting spot, so the loop is seamless */
.client-marquee {
  max-width: 1200px;
  margin: 0 auto;
  padding-top: 2.75rem;
  border-top: 1px solid rgba(255,255,255,0.22);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 6%, #000 94%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 6%, #000 94%, transparent 100%);
}
.client-marquee-track {
  /* fallback only — a plain -50% is mathematically wrong here (see
     below) and script.js overwrites this with the exact measured value
     once logos have laid out */
  --marquee-shift: -50%;
  display: flex;
  align-items: center;
  gap: 2.75rem;
  width: max-content;
  padding: 0.25rem 0;
  animation: client-marquee-scroll 38s linear infinite;
}
/* Each set is its own flex group so the loop shift can be measured
   directly. With one flat 40-item row, translateX(-50%) assumes the
   midpoint of the total width lands exactly on the seam between set A
   and set B — but with an odd count of gaps (39, for 40 items) across
   the whole row, that midpoint is off by half a gap, which is the
   visible "jump" at the loop point. Splitting into two explicit groups
   lets script.js measure set B's real starting offset and animate to
   that exact pixel value instead of assuming it. */
.client-marquee-set {
  display: flex;
  align-items: center;
  gap: 2.75rem;
  flex-shrink: 0;
}
/* pause to actually read a name — cheap, and the loop math holds
   regardless of when it's paused since the shift is a fixed value */
.client-marquee:hover .client-marquee-track { animation-play-state: paused; }
@keyframes client-marquee-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(var(--marquee-shift)); }
}
.client-logo {
  height: 34px;
  width: auto;
  max-width: 150px;
  object-fit: contain;
  opacity: 0.9;
  flex-shrink: 0;
}

/* ============================================================
   Studio / Who we are
   ============================================================ */
.studio {
  /* shared by the section-head width calc below and the diamond grid,
     so the text column can be sized to match the grid exactly */
  --diamond: clamp(90px, 24vw, 240px);
  padding: 6rem var(--edge) 7rem;
  max-width: 1400px;
  margin: 0 auto;
  text-align: center;
}
.studio .section-head {
  margin-left: auto;
  margin-right: auto;
  text-align: center;
  /* as wide as the 5-diamond row itself. The row's true rendered width
     (outer vertex of d1 to outer vertex of d5) is 5.791·diamond — see the
     touching-geometry derivation above .diamond-grid below, which is what
     this coefficient comes from (not a round/eyeballed number). */
  max-width: calc(var(--diamond) * 5.791);
}
.studio .section-head h2 { line-height: 1.16; padding-top: 0.12em; }
.studio .section-intro { margin-left: auto; margin-right: auto; }
.studio-mission {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  /* no max-width of its own — the wider section-head above already
     caps it, and letting it fill that width means fewer line-wraps
     per paragraph, i.e. less vertical space overall */
  max-width: none;
}
.studio-mission-lead {
  color: var(--text);
  font-weight: 700;
  margin-top: 0.5rem;
}

.diamond-grid {
  /* Grid with every diamond stacked in the SAME single cell (grid-area:1/1
     below) rather than flex — a normal flex/grid ROW gives each item its
     own native slot, so any translateX on top of that STACKS with the
     layout's own automatic per-item spacing instead of replacing it (this
     is exactly what happened when this was `display:flex`: each diamond's
     computed translateX was the FULL required offset, added on top of the
     row's own native 1×diamond-per-item spacing, roughly doubling every
     step and pushing d1/d5 off-screen). With every diamond in the same
     cell, all 5 start from the identical base position, so translateX/Y
     below are the ONLY source of position — no native spacing to
     double-count. */
  display: grid;
  /* justify-content centers the single grid TRACK within the container's
     leftover width (equivalent to flex's old justify-content:center for
     "center the whole row on the page"); place-items centers each diamond
     within that shared track/cell. Both are needed — they control
     different things (track position vs. item-within-track position). */
  justify-content: center;
  place-items: center;
  /* d1/d5 are now the highest tiles (raised 0.64·diamond above the row
     baseline, see the cascade below) — clearance above has to cover
     that full rise plus their own rotated-corner poke (≈0.21·S) */
  margin-top: calc(var(--diamond) * 0.85 + 2.5rem);
  /* d3 is now the lowest tile (sits at the row baseline, unshifted) —
     only its own corner poke needs clearing below */
  margin-bottom: calc(var(--diamond) * 0.21);
}
.diamond {
  /* Without this, grid's default auto-flow puts each diamond in its own
     new ROW (no columns/placement defined), stacking them vertically by
     layout before translateY is even applied — that's what produced the
     diagonal-descending row instead of the intended valley shape. Forcing
     all 5 into the exact same cell is what makes translateX/Y the ONLY
     source of position (the actual point of switching to grid). */
  grid-area: 1 / 1;
  width: var(--diamond);
  height: var(--diamond);
  padding: 0;
  transform: rotate(45deg);
  cursor: pointer;
  /* ease-in-out (not plain ease) so both directions — growing in on
     hover and shrinking back out on mouseleave/moving to a neighbor —
     ease smoothly at both ends instead of snapping at the start */
  transition: transform 0.5s ease-in-out;
}
/* TOUCHING GEOMETRY — read this before changing any position/scale number
   below. Each diamond is a square of side `--diamond` (call it S) rotated
   45°. Two rotated squares of scale k1,k2 (sizes k1·S, k2·S), whose centers
   are offset by a FIXED vertical distance m·S (m=0.32, the cascade step)
   and an unknown horizontal distance x·S, touch with exactly zero overlap
   and zero gap when x = (√2/2)·(k1+k2) − m. (Derivation: rotating into the
   square's own unrotated coordinate frame turns "touch a 45°-rotated
   square" into "touch an axis-aligned square," where the classic
   center-to-center touching distance is just the sum of the two half-
   sides — projecting that back through the 45° rotation is where the
   √2/2 and the −m come from.) Every X position and every hover-state scale
   pairing below is this formula evaluated for the relevant k1,k2 — none of
   these decimals are eyeballed. At rest (k1=k2=1): step = √2 − 0.32 =
   1.0942. On hover, only ONE diamond is ever scaled to 1.12 (grown) at a
   time, the rest to 0.92 (shrunk), so only two step sizes ever occur:
   0.92+0.92 → 0.9811 (two shrunk diamonds, pulled slightly CLOSER than
   resting spacing — they're both smaller, so less distance is needed to
   still touch), and 0.92+1.12 → 1.1225 (a shrunk diamond next to the grown
   one, pushed slightly FARTHER than resting spacing). This is why, e.g.,
   hovering d3 pulls d1 and d5 visibly closer to center rather than further
   away — counterintuitive if you're picturing "pushing," but it's what
   zero-overlap-always requires: the grown diamond only needs *its own*
   neighbors pushed back, while every pair of shrunk diamonds needs LESS
   room than they used at full size. translateY (the symmetric cascade,
   each step out from center rises 0.32·diamond) never changes with hover —
   only translateX and scale do. */
.d1 { background: linear-gradient(135deg, #2b2b2b, #4a4a4a); transform: translateX(calc(var(--diamond) * -2.1884)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg); }
.d2 { background: linear-gradient(135deg, var(--teal), var(--teal-deep)); transform: translateX(calc(var(--diamond) * -1.0942)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg); }
.d3 { background: var(--ink); }
.d4 { background: linear-gradient(135deg, var(--sage), var(--sage-deep)); transform: translateX(calc(var(--diamond) * 1.0942)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg); }

/* Hover/focus: grow the hovered diamond, shrink every other one, and
   reposition ALL FIVE along X (see the touching-geometry comment above)
   so every adjacent pair keeps touching with zero overlap/zero gap at the
   new sizes. translateY never changes. One rule per (trigger, target)
   pair — 25 total (5 self + 20 cross) — because the required X position
   for a given diamond depends on which side of the hovered one it's on
   and how many shrunk-shrunk vs. shrunk-grown seams are between it and
   center, which differs for every trigger. IMPORTANT: because every
   diamond shares one grid cell (see grid-area:1/1 above), translateX:0
   means "the shared cell's center," NOT "this diamond's own resting
   spot" — d1's resting spot is -2.1884·diamond, not 0. So a diamond's
   OWN hover rule (the "self, grows" one) must still carry its own resting
   translateX, not omit it — omitting it silently teleports that diamond
   to center on hover instead of growing in place. (This is also why the
   other 20 "someone else is hovered" values below aren't the same numbers
   as the plain touching-step table in the comment above .d1: each set is
   that relative table shifted by the hovered diamond's own resting X, so
   the whole fanned-out row is anchored on the hovered diamond staying
   where it already was, not on the grid's shared center point.) Uses
   :has() so no JS is needed to track which diamond is hovered/focused. */
#diamond-grid:has(.d1:hover, .d1:focus-visible) .d1 {
  transform: translateX(calc(var(--diamond) * -2.1884)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(1.12);
}
#diamond-grid:has(.d1:hover, .d1:focus-visible) .d2 {
  transform: translateX(calc(var(--diamond) * -1.0659)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d1:hover, .d1:focus-visible) .d3 {
  transform: translateX(calc(var(--diamond) * -0.0849)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d1:hover, .d1:focus-visible) .d4 {
  transform: translateX(calc(var(--diamond) * 0.8962)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d1:hover, .d1:focus-visible) .d5 {
  transform: translateX(calc(var(--diamond) * 1.8773)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(0.92);
}

#diamond-grid:has(.d2:hover, .d2:focus-visible) .d1 {
  transform: translateX(calc(var(--diamond) * -2.2167)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d2:hover, .d2:focus-visible) .d2 {
  transform: translateX(calc(var(--diamond) * -1.0942)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(1.12);
}
#diamond-grid:has(.d2:hover, .d2:focus-visible) .d3 {
  transform: translateX(calc(var(--diamond) * 0.0283)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d2:hover, .d2:focus-visible) .d4 {
  transform: translateX(calc(var(--diamond) * 1.0094)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d2:hover, .d2:focus-visible) .d5 {
  transform: translateX(calc(var(--diamond) * 1.9904)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(0.92);
}

#diamond-grid:has(.d3:hover, .d3:focus-visible) .d1 {
  transform: translateX(calc(var(--diamond) * -2.1036)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d3:hover, .d3:focus-visible) .d2 {
  transform: translateX(calc(var(--diamond) * -1.1225)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d3:hover, .d3:focus-visible) .d3 {
  transform: rotate(45deg) scale(1.12);
}
#diamond-grid:has(.d3:hover, .d3:focus-visible) .d4 {
  transform: translateX(calc(var(--diamond) * 1.1225)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d3:hover, .d3:focus-visible) .d5 {
  transform: translateX(calc(var(--diamond) * 2.1036)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(0.92);
}

#diamond-grid:has(.d4:hover, .d4:focus-visible) .d1 {
  transform: translateX(calc(var(--diamond) * -1.9904)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d4:hover, .d4:focus-visible) .d2 {
  transform: translateX(calc(var(--diamond) * -1.0094)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d4:hover, .d4:focus-visible) .d3 {
  transform: translateX(calc(var(--diamond) * -0.0283)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d4:hover, .d4:focus-visible) .d4 {
  transform: translateX(calc(var(--diamond) * 1.0942)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(1.12);
}
#diamond-grid:has(.d4:hover, .d4:focus-visible) .d5 {
  transform: translateX(calc(var(--diamond) * 2.2167)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(0.92);
}

#diamond-grid:has(.d5:hover, .d5:focus-visible) .d1 {
  transform: translateX(calc(var(--diamond) * -1.8773)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d5:hover, .d5:focus-visible) .d2 {
  transform: translateX(calc(var(--diamond) * -0.8962)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d5:hover, .d5:focus-visible) .d3 {
  transform: translateX(calc(var(--diamond) * 0.0849)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d5:hover, .d5:focus-visible) .d4 {
  transform: translateX(calc(var(--diamond) * 1.0659)) translateY(calc(var(--diamond) * -0.32)) rotate(45deg) scale(0.92);
}
#diamond-grid:has(.d5:hover, .d5:focus-visible) .d5 {
  transform: translateX(calc(var(--diamond) * 2.1884)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg) scale(1.12);
}

/* photo diamond: clip to the (unrotated, local) square first, then an
   img exactly filling that square is counter-rotated + scaled up around
   its own center so it reads upright and fully covers the diamond with
   no gaps at the corners. Scale (not oversized width/height percentages)
   is used deliberately — it's applied around the element's own center
   regardless of how the percentages resolve, so coverage is guaranteed
   symmetric on all four sides. */
.diamond-photo {
  position: relative;
  overflow: hidden;
}
.diamond-photo-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* lower Y % = crop window shifts UP the source photo (more headroom
     kept above); lower X % = crop window shifts LEFT the source photo
     (more kept on the left, less cropped off the right). Tuned per
     photo below, since each headshot's framing differs. */
  object-position: 50% 50%;
  transform-origin: center;
  /* scale(1.5) > sqrt(2) ≈ 1.414, the minimum needed for a square to
     fully cover itself after a 45° rotation */
  transform: rotate(-45deg) scale(1.5);
}
.diamond-photo-img-d1 { object-position: 70% 30%; } /* Mizzen */
.diamond-photo-img-d2 { object-position: 50% 20%; } /* Justin */
.d3 .diamond-photo-img { object-position: 30% -5%; } /* Whit Wales */
/* Zeb: 10% more zoom than the shared 1.5 base, crop shifted down 5% */
.diamond-photo-img-d4 { object-position: 50% 45%; transform: rotate(-45deg) scale(1.65); }
.diamond-photo-img-d5 { object-position: 50% -9%; } /* Colin Gosselin */
.d5 {
  background: var(--ink);
  transform: translateX(calc(var(--diamond) * 2.1884)) translateY(calc(var(--diamond) * -0.64)) rotate(45deg);
}

/* ============================================================
   CTA band + contact form
   ============================================================ */
.cta-band {
  /* top edge is a flat, uniform slate so the sage→slate chevron above
     blends into it with no visible seam */
  background: linear-gradient(180deg, var(--slate) 0%, var(--ink) 90%);
  color: var(--text-on-dark);
  padding: 3rem var(--edge);
  text-align: center;
  /* contact form + footer together fill the last viewport exactly;
     --footer-h is measured by script.js */
  min-height: calc(100svh - var(--footer-h, 185px));
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.cta-inner { max-width: 640px; margin: 0 auto; width: 100%; }
/* heading/eyebrow now just use the site-wide .section-head recipe (same
   as Studio's "Who we are" / "A small crew, on purpose.") instead of a
   bespoke Gotham Bold treatment, so Contact reads like every other
   section instead of a one-off */
.cta-band .section-head { margin-left: auto; margin-right: auto; }

.contact-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem 1.5rem;
  text-align: left;
}
.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.form-field-wide { grid-column: 1 / -1; }
.form-field label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-on-dark);
}
.form-field input,
.form-field select,
.form-field textarea {
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--text-on-dark);
  background: rgba(255,255,255,0.07);
  border: 1px solid rgba(255,255,255,0.28);
  border-radius: var(--radius);
  padding: 0.8rem 0.9rem;
  min-height: 44px;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}
.form-field input::placeholder,
.form-field textarea::placeholder { color: rgba(255,255,255,0.38); }
.form-field input:hover,
.form-field select:hover,
.form-field textarea:hover { border-color: rgba(255,255,255,0.45); }
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  border-color: var(--teal);
  background: rgba(255,255,255,0.1);
  outline: none;
}
.form-field textarea { resize: vertical; min-height: 140px; }
.form-field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ffffff' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.85rem center;
  background-size: 16px;
  padding-right: 2.4rem;
}
.form-field select:required:invalid { color: rgba(255,255,255,0.38); }
.form-field select option { color: var(--text); }
/* the "Other" org-type text field is only editable once its dropdown
   sibling is set to Other — see the org-type change handler in
   script.js */
.form-field input:disabled,
.form-field select:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background: rgba(255,255,255,0.03);
}
.form-status {
  grid-column: 1 / -1;
  font-size: 0.9rem;
  color: var(--teal);
  min-height: 1.2em;
}
.contact-form .btn-accent {
  grid-column: 1 / -1;
  justify-self: center;
}

/* ============================================================
   Footer
   ============================================================ */
.site-footer {
  background: linear-gradient(180deg, var(--sage) 0%, var(--slate) 100%);
  color: var(--text-on-dark);
  padding: 1.75rem var(--edge) 1.25rem;
}
.footer-inner {
  max-width: 700px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
}
.footer-social a { color: var(--text-on-dark); transition: color 0.2s ease; }
.footer-social a:hover { color: var(--teal); }
.footer-email, .footer-phone {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 500;
  color: var(--text-on-dark);
}
.footer-email:hover, .footer-phone:hover { color: var(--teal); }
.footer-mark {
  opacity: 0.9;
  margin-top: 0.5rem;
  width: 36px;
  height: auto;
}
.footer-copy {
  font-size: 0.78rem;
  color: var(--text-on-dark);
  margin-top: 0;
}

/* ============================================================
   Reel modal
   ============================================================ */
.reel-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}
.reel-modal[hidden] { display: none; }
.reel-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 9, 10, 0.88);
}
.reel-modal-dialog {
  --reel-ratio: 1.7778; /* 16/9 fallback until the real oEmbed ratio loads */
  position: relative;
  z-index: 1;
  /* sized to fit an 80vw × 80vh box while preserving the video's own
     aspect ratio — never cropped, never letterboxed */
  width: min(80vw, calc(80vh * var(--reel-ratio)));
}
.reel-modal-close {
  position: absolute;
  top: -1.25rem;
  right: -1.25rem;
  z-index: 2;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(14, 14, 15, 0.65);
  color: var(--text-on-dark);
  border: 1px solid rgba(255,255,255,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.reel-modal-close:hover { border-color: var(--teal); color: var(--teal); }
.reel-embed {
  position: relative;
  width: 100%;
  aspect-ratio: var(--reel-ratio);
  background: transparent;
  border-radius: var(--radius);
  overflow: hidden;
}
.reel-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
}

/* ============================================================
   Photo lightbox
   ============================================================ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}
.lightbox[hidden] { display: none; }
.lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 9, 10, 0.92);
}
.lightbox-dialog {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1040px;
  display: flex;
  align-items: center;
  gap: 1rem;
}
.lightbox-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 2;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(255,255,255,0.08);
  color: var(--text-on-dark);
  border: 1px solid rgba(255,255,255,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.lightbox-close:hover { border-color: var(--teal); color: var(--teal); }
.lightbox-arrow {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.4);
  color: var(--text-on-dark);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.lightbox-arrow:hover { border-color: var(--teal); color: var(--teal); }
/* the base rule's `display: flex` otherwise outranks the browser's
   default [hidden]{display:none} (equal specificity, author styles win) */
.lightbox-arrow[hidden] { display: none; }
.lightbox-stage {
  flex: 1;
  min-width: 0;
}
.lightbox-image {
  width: 100%;
  aspect-ratio: 3/2;
  max-height: 72vh;
  border-radius: var(--radius);
  background: linear-gradient(155deg, #4a4a4a 0%, #1c1c1c 130%);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--text-on-dark-soft);
}
/* Real-photo mode (a gallery with actual images, e.g. Five Boro Bike
   Tour) — same "agnostic stage + per-photo frame" pattern as
   .team-lightbox-image/.team-lightbox-frame: a transparent, height-bound
   stage hosting one frame per photo, each sized to ITS OWN aspect ratio
   via --img-ratio (set from the loaded image's natural size in script.js)
   rather than every photo being forced into one fixed 3:2 box. */
.lightbox-image--photo {
  aspect-ratio: auto;
  height: min(72vh, 640px);
  background: transparent;
  position: relative;
  overflow: hidden;
}
.lightbox-frame {
  --img-ratio: 1.5;
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(100%, calc(var(--img-ratio) * min(72vh, 640px)));
  aspect-ratio: var(--img-ratio);
  border-radius: var(--radius);
  overflow: hidden;
  opacity: 1;
  transform: translate(-50%, -50%) translateX(0);
  transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.5s ease;
}
.lightbox-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.lightbox-caption {
  text-align: center;
  margin-top: 1rem;
  font-size: 0.85rem;
  color: var(--text-on-dark);
}

/* ============================================================
   Team lightbox (Who We Are gallery)
   ============================================================ */
.team-lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}
.team-lightbox[hidden] { display: none; }
.team-lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 9, 10, 0.92);
}
.team-lightbox-count {
  position: absolute;
  top: 1rem;
  left: 1rem;
  z-index: 2;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  color: var(--text-on-dark);
}
.team-lightbox-dialog {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 900px;
  display: flex;
  align-items: center;
  gap: 1rem;
}
.team-lightbox-stage { flex: 1; min-width: 0; }
/* A plain, photo-agnostic viewport — it no longer takes on any one
   photo's shape. Each photo gets its own "frame" (below) sized to its
   own ratio, so the box and the image inside it move as one joined
   element during a transition instead of the image sliding around
   inside a container that separately snaps to a new shape. */
.team-lightbox-image {
  /* an invisible bounding stage, not a visible box — it exists only to
     size the modal and clip the sideways slide motion. Its own height
     (min(70vh, 620px)) is the largest space any frame is ever allowed
     to use; every frame below is sized to fit entirely inside both that
     height AND the stage's width, so nothing is ever cropped. */
  position: relative;
  width: 100%;
  height: min(70vh, 620px);
  background: transparent;
  overflow: hidden;
}
.team-lightbox-frame {
  /* plain number (not "W/H"); script.js sets this per-photo from its
     natural size, so the frame's own shape matches that photo exactly */
  --img-ratio: 1.333;
  position: absolute;
  top: 50%;
  left: 50%;
  /* "contain" math, not a clamp on one axis alone: this is the width the
     frame would need at full stage height, capped by the stage's own
     width — whichever is smaller wins, and height always follows via
     aspect-ratio, so both dimensions shrink together and the full photo
     is always visible, never cropped */
  width: min(100%, calc(var(--img-ratio) * min(70vh, 620px)));
  aspect-ratio: var(--img-ratio);
  border-radius: var(--radius);
  overflow: hidden;
  opacity: 1;
  transform: translate(-50%, -50%) translateX(0);
  transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.5s ease;
}
.team-lightbox-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.team-lightbox-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* a slim footer strip, not a panel — hard cap + internal scroll so it
     can never grow past this however long a bio gets, and can't creep
     toward the arrow buttons */
  max-height: 24%;
  overflow-y: auto;
  /* scroll still works via wheel/touch/drag — just no visible track/thumb */
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding: 0.75rem 1.5rem 0.65rem;
  background: rgba(8, 9, 10, 0.62);
  backdrop-filter: blur(3px);
  text-align: center;
  /* the prev/next arrows sit outside this box entirely (siblings in the
     dialog's flex row), so pointer-events can stay enabled here — needed
     for the internal scroll (overflow-y: auto above) to actually work */
  touch-action: pan-y;
}
.team-lightbox-caption::-webkit-scrollbar { display: none; }
.team-lightbox-caption[hidden] { display: none; }
.team-lightbox-name {
  font-family: var(--font-body);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.85rem;
  color: var(--text-on-dark);
  margin: 0 0 0.15rem;
}
.team-lightbox-role {
  font-family: var(--font-body);
  font-weight: 300;
  letter-spacing: 0.04em;
  font-size: 0.75rem;
  color: var(--text-on-dark);
  margin: 0 0 0.35rem;
}
.team-lightbox-role:empty { display: none; margin: 0; }
.team-lightbox-bio {
  font-size: 0.76rem;
  line-height: 1.42;
  color: var(--text-on-dark);
  /* use the full width of whatever frame the current photo has, rather
     than an arbitrary character cap — more chars per line means fewer
     lines, which is what actually avoids needing to scroll */
  max-width: 100%;
  margin: 0 auto;
}
.team-lightbox-bio:empty { display: none; margin: 0; }

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 860px) {
  .main-nav {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(320px, 82vw);
    background: var(--ink);
    padding: 6.5rem 2rem 2rem;
    transform: translateX(100%);
    transition: transform 0.3s ease;
    z-index: 400;
  }
  .main-nav.is-open { transform: translateX(0); }
  .main-nav ul { flex-direction: column; gap: 1.5rem; }
  .main-nav a { font-size: 0.95rem; display: inline-block; }
  .main-nav a.is-active { border-bottom-color: var(--teal); }
  .nav-indicator { display: none; }
  .menu-toggle { display: flex; }
  .header-right .social-row { display: none; }
  /* mobile keeps a solid header at all times, and MUST explicitly reset
     backdrop-filter to none (not just omit it) — backdrop-filter makes an
     element the containing block for its own position:fixed descendants,
     and #main-nav is a position:fixed DESCENDANT of .site-header at this
     breakpoint. Omitting the property here doesn't clear it: the desktop
     rule's backdrop-filter (blur(calc(var(--hdr) * 6px))) still cascades
     in, and even blur(0px) at --hdr:0 counts as a non-"none" value for
     this purpose. With any non-none backdrop-filter present, #main-nav's
     inset:0 0 0 auto resolved against the slim header bar's own ~136px
     height instead of the viewport, collapsing the slide-in panel down to
     the header's height — its background painted correctly within that
     collapsed box, but the actual link list (taller than 136px, nothing
     clips it) overflowed straight past it onto bare hero content below,
     which is what read as "transparent." The header is already a solid
     92%-opacity background on mobile, so the blur was doing very little
     visually anyway — removing it costs nothing. */
  .site-header { background: rgba(14,14,15,0.92); backdrop-filter: none; }

  .menu-toggle.is-active span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .menu-toggle.is-active span:nth-child(2) { opacity: 0; }
  .menu-toggle.is-active span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
}

@media (max-width: 640px) {
  .hero-inner { padding-top: 6.5rem; }
  .films, .photography, .studio { padding-left: var(--edge); padding-right: var(--edge); }
  .testimonial-slider { flex-direction: row; gap: 0.75rem; }
  /* the arrows now sit below the rail (.film-controls), not flanking it,
     so the width:calc(70%/0.82) formula on .film-carousel needs no
     mobile override — it already nets the card out to 70% of the page
     at any breakpoint without anything else in the row to subtract. */
  .film-carousel { gap: 1rem; }
  .film-card-play { width: 36px; height: 36px; }
  .film-card-play svg { width: 13px; height: 13px; }
  .slide-arrow { width: 38px; height: 38px; }
  .reel-modal-close { top: 0.6rem; right: 0.6rem; }
  .photo-rail { grid-template-columns: repeat(2, 1fr); width: 90%; }
  .cta-band { padding: 2.5rem var(--edge); }
  .contact-form { grid-template-columns: 1fr; }
  .footer-inner { gap: 0.9rem; }
  .lightbox-dialog { gap: 0.5rem; }
  .lightbox-arrow { width: 34px; height: 34px; }
  .lightbox-image { font-size: 0.9rem; }
  .studio { --diamond: clamp(48px, 13vw, 90px); }
  .team-lightbox-dialog { gap: 0.5rem; }
  .hero, .testimonials, .section-chevron { --chev: 34px; }
  /* .films' min-height calc reads --hero-chev directly (it has no
     .section-chevron-hero class of its own to override), so it needs
     updating here too or it'd assume the desktop-sized chevron */
  :root { --hero-chev: 34px; }
  .client-logo { height: 26px; }
}

@media (max-width: 400px) {
  :root { --edge: 1.25rem; }
}
