/* ============================================================
   Wendy Shapero — Photography Portfolio
   Inspired by spencergabor.work, tuned for photography.
   ============================================================ */

:root {
  --bg: #ffffff;
  --bg-alt: #f3f3f1;
  --ink: #0c0c0c;
  --muted: #6b6b6b;
  --ghost: #d4d4d2;
  /* Accent — used for hover states and links. Dark charcoal so the
     palette stays in the blacks-and-grays family Wendy asked for; the
     photos themselves carry all the color. */
  --accent: #2a2a2a;
  --rule: #ececec;
  /* Mid-gray for the CONTACT panel — matches jackdanielvo.com's
     contact section. Used by both .contact and body (body matches so
     the slide-up reveal blends seamlessly). */
  --contact-bg: #d8d8d8;
  --shadow: 0 30px 60px -25px rgba(0, 0, 0, 0.18), 0 6px 18px -12px rgba(0, 0, 0, 0.12);
  --radius: 14px;
  --max: 1400px;
  --pad-x: clamp(20px, 5vw, 80px);
  /* Editorial Helvetica display font for site-wide headers (WENDYPIX,
     FEATURED WORK, ALL SHOOTS, CONTACT). Helvetica regular at large
     sizes reads tall and architectural — same family as jackdanielvo.
     System font, no Google Fonts fetch needed. */
  --display-font: "Helvetica", "Arial", "Helvetica Neue", sans-serif;
  --serif-font: "Instrument Serif", Georgia, serif;
  --body-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

[data-theme="dark"] {
  --bg: #0c0c0c;
  --bg-alt: #161616;
  --ink: #f5f5f3;
  --muted: #9a9a9a;
  --ghost: #2a2a2a;
  --rule: #1f1f1f;
  --contact-bg: #1c1c1c;
  --shadow: 0 30px 60px -25px rgba(0, 0, 0, 0.6), 0 6px 18px -12px rgba(0, 0, 0, 0.5);
}

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

html {
  scroll-behavior: smooth;
  /* Mobile-page-motion lockdown.
     On iOS, overflow-x:hidden on body alone is NOT enough — the html
     element keeps its own scrollable axis and will still slide
     sideways when a finger pushes the frame. Setting overflow-x on
     html too actually traps stray horizontal motion. We use `clip`
     where supported (no extra scroll container, no breaking sticky
     positioning) and fall back to `hidden` via a duplicate value.
     overscroll-behavior-x:none stops the elastic horizontal bounce
     entirely. We deliberately leave Y alone so pull-to-refresh and
     normal vertical scrolling still feel right. */
  overflow-x: hidden;
  overflow-x: clip;
  overscroll-behavior-x: none;
  width: 100%;
}

body {
  margin: 0;
  /* The body's background is the CONTACT panel color so that as <main>
     scrolls up off the top, the fixed footer underneath blends seamlessly.
     Now matches the gray contact panel (jackdanielvo style). */
  background: var(--contact-bg);
  color: var(--ink);
  font-family: var(--body-font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  overflow-x: clip;
  overscroll-behavior-x: none;
  width: 100%;
  /* position:relative gives layout calculations a stable origin and
     fixes a Safari quirk where overflow-x:hidden on the html/body
     pair can leak when there are absolutely-positioned children
     (we do have them — the theme toggle, shoot view, etc.) */
  position: relative;
  transition: background 0.5s var(--ease), color 0.5s var(--ease);
}

/* Main sits ABOVE the fixed CONTACT footer, with its own opaque background.
   As you scroll, main slides up off the top of the viewport, progressively
   revealing the footer that's been pinned underneath the whole time. */
main {
  position: relative;
  z-index: 2;
  background: var(--bg);
}
.contact-spacer {
  /* Two viewports of runway. Combined with RANGE_VH = 2 in script.js,
     this aligns the reveal so the word is at scaleY(0) right when the
     contact panel first peeks out, and grows to scaleY(4.2) at full
     scroll. (Spacer height in vh must match RANGE_VH for the alignment.) */
  height: 200vh;
  height: 200dvh;
  pointer-events: none;
}

a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
a:hover { color: var(--accent); border-color: var(--accent); }

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

button { cursor: pointer; font-family: inherit; }

/* -------- Theme toggle -------- */
.theme-toggle {
  position: fixed;
  top: 22px;
  right: 22px;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--bg);
  color: var(--ink);
  display: grid;
  place-items: center;
  z-index: 200;
  transition: transform 0.4s var(--ease), background 0.3s var(--ease);
}
.theme-toggle:hover { transform: rotate(20deg); color: var(--accent); border-color: var(--accent); }

/* -------- Hero nav (home page only) --------
   "RATES.CALL.BOOK." row sitting just under the WENDYPIX wordmark.
   Visually a smaller-scale echo of the .contact-links treatment at
   the very bottom of the page — same Inter Black 900 weight, same
   tight letter-spacing and period punctuation, same plum / ink / teal
   color split. No scroll-driven scaleY here (that's reserved for the
   billboard footer); this row stays a constant size so it reads as
   navigation rather than a closing flourish. */
.hero-nav {
  font-family: var(--body-font);
  font-weight: 900;
  /* Sized to read as a clear secondary headline below WENDYPIX —
     ~1/3 the wordmark size at every viewport width. Capped so it
     can never crowd or overflow on huge displays. */
  font-size: clamp(28px, 5vw, 80px);
  line-height: 0.85;
  letter-spacing: -0.06em;
  white-space: nowrap;
  text-align: center;
  margin: clamp(8px, 2vh, 24px) 0 clamp(20px, 4vh, 48px);
  user-select: none;
}
.hero-nav__link {
  display: inline-block;
  text-decoration: none;
  border: none;
  transition: opacity 0.2s var(--ease);
}
.hero-nav__link:hover { opacity: 0.65; }
/* Same color treatment as the footer's contact-link triplet —
   plum / ink / teal — so the brand cues are consistent top and bottom. */
.hero-nav__link--rates { color: #b347b9; }
.hero-nav__link--call  { color: var(--ink); }
.hero-nav__link--book  { color: #1d5e62; }
[data-theme="dark"] .hero-nav__link--call { color: var(--ink); }
[data-theme="dark"] .hero-nav__link--book { color: #5fb1b6; }

/* On phones, drop a touch in size and tighten breathing room so the
   row sits cleanly between the wordmark and the hero photo cards. */
@media (max-width: 600px) {
  .hero-nav {
    font-size: clamp(28px, 9vw, 56px);
    letter-spacing: -0.05em;
    margin: clamp(4px, 1.5vh, 16px) 0 clamp(16px, 3vh, 32px);
  }
}

/* -------- Typography -------- */
.caps {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
  margin: 0;
}
.muted { color: var(--muted); font-weight: 500; }

.display {
  /* Heavy editorial display weight, matching the RATES headline on
     the /pricing page. Inter Black at -0.045em tracking + 0.85
     line-height reads as confident and architectural — same
     character at every section header on the site, so the home
     page and the pricing page feel like one design system.
     The WENDYPIX wordmark (.hero h1.display.brand) and the giant
     CONTACT mega (.contact-mega) both have their own font-family
     overrides further down, so they're unaffected by this change. */
  font-family: var(--body-font);
  font-weight: 900;
  letter-spacing: -0.045em;
  line-height: 0.85;
  margin: 0;
  text-transform: uppercase;
}

.kicker {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 24px 0;
  font-weight: 700;
}

/* -------- Layout -------- */
main { display: block; }
section { padding-left: var(--pad-x); padding-right: var(--pad-x); }

/* ============================================================
   HERO
   ============================================================ */
.hero {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: clamp(28px, 5vh, 60px);
  padding-bottom: clamp(40px, 8vh, 100px);
  position: relative;
  text-align: center;
}

.hero-meta { margin-bottom: clamp(28px, 6vh, 60px); }
.hero-meta p { line-height: 1.4; }
.hero-meta a { border-bottom: none; }
.hero-meta a:hover { color: var(--accent); }

.hero .display {
  font-size: clamp(64px, 13vw, 220px);
  margin-bottom: clamp(20px, 4vh, 60px);
}

/* The big WENDYPIX brand wordmark — Inter Black 900 to match the
   editorial display treatment used across the rest of the site
   (FEATURED WORK, ALL SHOOTS, the RATES headline on /pricing). The
   WENDY half stays in ink and the PIX half stays in plum, so the
   color identity reads even at the heavier weight. */
.hero h1.display.brand {
  font-family: var(--body-font);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: -0.045em;
  line-height: 0.82;
  color: var(--ink);
  font-size: clamp(64px, 13vw, 220px);
}
.hero h1.display { color: var(--ink); }
/* Two-tone wordmark: WENDY in ink, PIX in bright plum to give the brand
   a small color accent without breaking the grayscale rest-of-page. */
.hero h1.display.brand .brand-wendy { color: var(--ink); }
.hero h1.display.brand .brand-pix   { color: #b347b9; }

/* D-Y kerning fix. Inter Black gives D a near-flat right edge and Y a
   diagonal that converges low-center, so the optical space between
   them reads wider than every other letter pair in WENDYPIX even with
   the negative letter-spacing already applied. Wrap the Y in
   .brand-y (in HTML) and pull it back with a small negative margin
   so the gap matches the rest of the wordmark. Applies to both the
   big hero wordmark on index.html and the smaller .pricing__brand
   wordmark used on book / pricing / admin / book-success. */
.brand-y { margin-left: -0.06em; }
.hero h1.display {
  color: #8e3a8c;
}

/* Each letter is wrapped in a .char span (by JS) so we can stagger a
   gentle fade-in-from-below entrance. Subtle: ~70ms per letter, 1s ease-out
   per letter, runs once on load. */
.hero h1.display .char {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.4em);
  animation: title-char-in 1s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: calc(var(--i, 0) * 70ms + 200ms);
  /* Preserve word breaks: the JS marks spaces with .char--space */
}
.hero h1.display .char--space {
  width: 0.25em; /* default word break width */
}
@keyframes title-char-in {
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .hero h1.display .char {
    opacity: 1;
    transform: none;
    animation: none;
  }
}

.hero .sub {
  color: var(--ghost);
  font-size: clamp(34px, 7vw, 110px);
  margin-top: clamp(20px, 4vh, 60px);
}

/* Second line of the hero subtitle. Sits below PHOTOGRAPHER as a
   subordinate tagline — visibly smaller and recolored to brand
   teal so it reads as a secondary descriptor rather than a comma-
   separated continuation. Inline-block so margin-top works for a
   touch of breathing room above it (the parent's tight line-height
   0.85 from .display would otherwise stack the two lines too close
   given the size delta). */
.hero .sub__tag {
  display: inline-block;
  font-size: clamp(20px, 4.2vw, 64px);
  color: #1d5e62;
  margin-top: clamp(6px, 1.2vh, 18px);
  /* Override the parent .display letter-spacing (-0.045em). That tight
     tracking works at PHOTOGRAPHER's huge size but at ~60% scale the
     letters of PORTRAITS / MOMENTS run together and the spaces around
     the & collapse to nothing. Reset to neutral and add a touch of
     word-spacing so the three tokens read as separate words. */
  letter-spacing: 0;
  word-spacing: 0.12em;
}

.services {
  margin-top: clamp(36px, 6vh, 80px);
}
.services p { line-height: 1.7; }
/* Bump the 'Available for / Headshots · Corporate Portraits · …' block
   from the global .caps 11px up to the same size as the bold category
   labels under each hero card. Reads as part of the hero composition
   rather than a tiny disclaimer at the bottom of it. Letter-spacing is
   tightened slightly to match the hero card labels. */
.services .caps {
  font-size: clamp(14px, 1.4vw, 18px);
  letter-spacing: 0.12em;
}

/* ============================================================
   Hero card stack — Spencer-style continuous floating motion.
   Each card sits in flow as a row item; CSS variables --ox/--oy/--rot
   are updated every frame by JS to drive translate/rotate.
   ============================================================ */
.hero__mid {
  width: 100%;
  display: flex;
  justify-content: center;
  /* Top margin stays negative so the cards tuck up under the
     wordmark like before. Bottom margin is positive so the bolder
     category labels (which hang ~42px below each card) have room
     to sit before the PHOTOGRAPHER subtitle below. */
  margin: clamp(-40px, -4vh, -10px) 0 clamp(60px, 8vh, 90px) 0;
  position: relative;
  z-index: 1;
}
.hero__images {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(6px, 1vw, 18px);
  height: clamp(260px, 36vh, 440px);
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
}
.hero__image {
  position: relative;
  width: clamp(140px, 14.5vw, 220px);
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* Cards are real <button> elements now (tappable). Reset native button
     chrome so they look like the photo cards they are. */
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  /* On touch devices the tap-highlight overlay is distracting on top of
     the rotation transform. Suppress it; the active-state below gives a
     more on-brand response. */
  -webkit-tap-highlight-color: transparent;
}
.hero__image:active img {
  filter: brightness(1.06);
}
.hero__image img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  background: var(--bg-alt);
  /* Custom props the rAF loop writes to */
  --ox: 0px;
  --oy: 0px;
  --rot: 0deg;
  /* Modern translate/rotate properties layer cleanly with transforms */
  translate: var(--ox) var(--oy);
  rotate: calc(var(--base-rot, 0deg) + var(--rot));
  will-change: translate, rotate;
  /* Entrance fade */
  opacity: 0;
  animation: hero-card-in 1.1s var(--ease) forwards;
  animation-delay: calc(var(--idx, 0) * 120ms + 200ms);
}
.hero__image:nth-child(1) img { --base-rot: -14deg; --idx: 0; }
.hero__image:nth-child(2) img { --base-rot:  -6deg; --idx: 1; }
.hero__image:nth-child(3) img { --base-rot:   2deg; --idx: 2; transform: scale(1.04); }
.hero__image:nth-child(4) img { --base-rot:   8deg; --idx: 3; }
.hero__image:nth-child(5) img { --base-rot:  16deg; --idx: 4; }

/* Always-visible category label below each hero card. The card is
   now a category navigator (click → scroll to that category section)
   so visitors need to know which card leads where without hovering.
   Position is BELOW the card (bottom: -34px) rather than inside it —
   the cards' rotated images fill most of the card's box on shorter
   viewports and would otherwise overlap the label. The .hero__images
   container has padding-bottom (and .hero__mid drops its negative
   bottom margin) to make sure labels stay inside the section's flow
   instead of bleeding into the subtitle below. */
.hero__image__label {
  position: absolute;
  left: 50%;
  bottom: -42px;
  transform: translateX(-50%);
  font-family: var(--body-font);
  font-weight: 900;          /* Inter Black, matches WENDYPIX wordmark */
  font-size: clamp(14px, 1.4vw, 18px);
  letter-spacing: 0.12em;    /* tightened to compensate for larger size */
  line-height: 1.1;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;     /* clicks pass through to the parent button */
  transition: color 0.25s var(--ease), transform 0.25s var(--ease);
  z-index: 2;
}
.hero__image:hover .hero__image__label {
  color: #b347b9;           /* brand plum on hover */
  transform: translateX(-50%) translateY(-2px);
}
[data-theme="dark"] .hero__image__label {
  color: var(--ink);
}

@keyframes hero-card-in {
  from { opacity: 0; scale: 0.85; }
  to   { opacity: 1; scale: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero__image img { animation: none; opacity: 1; }
}

/* On phones the desktop layout (5 rotated cards in a fixed flex row) packs
   them all into ~380px and they overlap into one. Convert the row to a
   horizontal scroll-snap carousel: each card becomes a comfortable 56vw
   wide, the row scrolls under your finger, and snaps to whichever card
   is centered. Tapping still opens the shoot (or lightbox). */
@media (max-width: 640px) {
  /* Desktop uses a negative top margin to tuck the cards up under the
     WENDYPIX wordmark, but on mobile the RATES.CALL.BOOK. nav sits between
     the wordmark and the cards — that negative margin pulls the cards UP
     into the nav row and they overlap. Clear it on mobile so the cards
     sit cleanly below the nav with breathing room. */
  .hero__mid {
    margin-top: clamp(12px, 3vh, 28px);
  }
  .hero__images {
    overflow-x: auto;
    /* When one axis is non-visible (overflow-x: auto here), browsers
       silently treat the other as `auto` too — the spec calls this
       "implied overflow propagation". So the container clips
       vertically too, and the absolutely-positioned category labels
       at bottom: -34px got cropped off. Padding-bottom expands the
       container's interior so labels live INSIDE it instead of
       hanging into clipped territory. */
    overflow-y: visible;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 14px 22vw 74px;     /* extra bottom padding for bolder, larger category labels */
    gap: 18px;
    height: auto;
    max-width: none;
    /* Don't fight scroll with vertical motion */
    touch-action: pan-x;
  }
  .hero__images::-webkit-scrollbar { display: none; }

  .hero__image {
    width: 56vw;
    height: auto;
    flex-shrink: 0;
    scroll-snap-align: center;
  }
  .hero__image img {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 5;
  }

  /* Tone down the desktop rotations so cards read clearly in the swipe
     view rather than overlapping their neighbors when one's centered. */
  .hero__image:nth-child(1) img { --base-rot: -3deg; }
  .hero__image:nth-child(2) img { --base-rot: -1.5deg; }
  .hero__image:nth-child(3) img { --base-rot: 0deg; }
  .hero__image:nth-child(4) img { --base-rot: 1.5deg; }
  .hero__image:nth-child(5) img { --base-rot: 3deg; }
}

/* ============================================================
   ABOUT — two-column layout: portrait + bio
   ============================================================ */
.about {
  max-width: 1180px;
  margin: clamp(80px, 14vh, 180px) auto;
  display: grid;
  grid-template-columns: minmax(260px, 1fr) minmax(0, 1.4fr);
  gap: clamp(30px, 5vw, 80px);
  align-items: center;
  text-align: left;
}
.about__photo figure {
  margin: 0;
}
.about__photo-frame {
  width: 100%;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-alt);
  box-shadow: var(--shadow);
  position: relative;
}
.about__photo-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Friendly placeholder if wendy-portrait.jpg isn't present */
.about__photo-frame.missing {
  background:
    repeating-linear-gradient(45deg, var(--bg-alt) 0 12px, color-mix(in srgb, var(--bg-alt) 70%, var(--ghost)) 12px 24px);
}
.about__photo-frame.missing::after {
  content: "Drop  wendy-portrait.jpg  in this folder";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--body-font);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--muted);
  text-align: center;
  padding: 0 20px;
}
.about__caption {
  margin: 14px 0 0 0;
  color: var(--muted);
  font-size: 11px;
}
.about__copy { min-width: 0; }
.about .bio {
  font-family: var(--serif-font);
  font-size: clamp(22px, 2.2vw, 32px);
  font-style: italic;
  line-height: 1.35;
  margin: 0 0 24px 0;
  color: var(--ink);
}
.about .link {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
@media (max-width: 720px) {
  .about {
    grid-template-columns: 1fr;
    text-align: left;
  }
  .about__photo { max-width: 360px; }
}

/* ============================================================
   FEATURED WORK
   ============================================================ */
.featured {
  margin-top: clamp(80px, 12vh, 160px);
  padding-top: clamp(40px, 8vh, 100px);
  border-top: 1px solid var(--rule);
  text-align: center;
}
.featured .display {
  font-size: clamp(46px, 8vw, 110px);
}
.featured-sub {
  font-family: var(--serif-font);
  font-style: italic;
  color: var(--muted);
  font-size: 18px;
  margin: 16px 0 clamp(50px, 8vh, 110px) 0;
}

/* Category sections (WOMEN, MEN, KIDS & TEENS, LIFESTYLE, EVENTS) no
   longer render the .featured-sub paragraph below the headline — its
   bottom margin used to supply all the breathing room between the h2
   and the carousel below. Without it, the carousel hugs the headline.
   Put the gap on the h2 itself so the editorial rhythm comes back. */
.category-section .display {
  margin-bottom: clamp(48px, 7vh, 96px);
}

/* ------------------------------------------------------------
   Featured Work carousel — Spencer-style.
   Three tiles visible at a time:
     - flank-left (rotated, partially overlapped behind center)
     - active    (centered, larger, label pill below)
     - flank-right
   Tiles further out slide off-screen and fade. Click a flank to
   rotate it into focus; click the active tile to open the shoot.
   ------------------------------------------------------------ */
.featured-carousel {
  --tile-w: clamp(240px, 28vw, 420px);
  --tile-h: clamp(320px, 50vh, 540px);
  position: relative;
  /* When wrapped in .featured-carousel-wrap (flex container with arrow
     buttons either side), the carousel needs to flex-grow to absorb
     the remaining width — otherwise it'd collapse to content width. */
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  height: var(--tile-h);
  margin: 0 auto;
  /* Allow flanking tiles to extend past the section's content box. The
     containing section has horizontal padding, but the carousel itself
     should let tiles ride to the edges of the viewport. */
  outline: none;
  /* Touch / drag affordance. `cursor: grab` signals you can drag it on
     desktop. `touch-action: pan-y` tells the browser: vertical scroll
     stays with the page, horizontal motion is mine to handle (so swipes
     drive the carousel instead of scrolling the page sideways). */
  cursor: grab;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}
.featured-carousel:active { cursor: grabbing; }
.featured-carousel:focus-visible {
  /* Subtle keyboard-focus ring on the carousel container itself, since
     individual tiles can't easily host a ring while they're transformed. */
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: var(--radius);
}

.ftile {
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--tile-w);
  height: var(--tile-h);
  margin-left: calc(var(--tile-w) / -2);
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  /* Default: hidden / off-screen. State classes (.active, .flank-left,
     etc.) override these with the per-position transform + opacity. */
  opacity: 0;
  pointer-events: none;
  transform: translateX(0) scale(0.55) rotate(0deg);
  transform-origin: 50% 60%;
  transition:
    transform 0.75s cubic-bezier(0.22, 1, 0.36, 1),
    opacity   0.5s  var(--ease),
    box-shadow 0.5s var(--ease);
  will-change: transform, opacity;
}
.ftile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  background: var(--bg-alt);
}
.ftile__label {
  position: absolute;
  left: 50%;
  bottom: -28px;
  transform: translateX(-50%) translateY(8px);
  background: var(--ink);
  color: var(--bg);
  padding: 8px 18px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
  pointer-events: none;
}
[data-theme="dark"] .ftile__label {
  background: var(--bg-alt);
  color: var(--ink);
}

/* --- Position states --- */
.ftile.active {
  transform: translateX(0) scale(1) rotate(-2deg);
  opacity: 1;
  pointer-events: auto;
  z-index: 3;
}
.ftile.active .ftile__label {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.ftile.flank-left {
  transform: translateX(-90%) scale(0.78) rotate(-9deg);
  opacity: 1;
  pointer-events: auto;
  z-index: 1;
}
.ftile.flank-right {
  transform: translateX(90%) scale(0.78) rotate(8deg);
  opacity: 1;
  pointer-events: auto;
  z-index: 1;
}
/* Far positions: slid further out, fading away. Kept in the DOM so the
   transition reads naturally as they move past the flank slots. */
.ftile.far-left {
  transform: translateX(-180%) scale(0.6) rotate(-13deg);
  opacity: 0;
  z-index: 0;
}
.ftile.far-right {
  transform: translateX(180%) scale(0.6) rotate(13deg);
  opacity: 0;
  z-index: 0;
}

/* Focal-tile hover lift — like the gallery tiles, just a touch bigger. */
.ftile.active:hover {
  transform: translateX(0) scale(1.025) rotate(-2deg);
}
.ftile.flank-left:hover {
  transform: translateX(-86%) scale(0.81) rotate(-7deg);
}
.ftile.flank-right:hover {
  transform: translateX(86%) scale(0.81) rotate(6deg);
}

/* ------------------------------------------------------------
   Carousel wrap + left/right arrow buttons.
   The wrap puts the carousel between two prominent arrow buttons.
   Vertical mouse-wheel stays with the page — these buttons (plus the
   dots below, drag, click-the-flank, and arrow keys) are how readers
   move between subjects. No more wheel-hijacking.
   ------------------------------------------------------------ */
.featured-carousel-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(8px, 1.4vw, 24px);
  width: 100%;
}
.featured-arrow {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: clamp(42px, 5vw, 56px);
  height: clamp(42px, 5vw, 56px);
  border-radius: 50%;
  border: 1.5px solid var(--rule);
  background: var(--bg);
  color: var(--ink);
  cursor: pointer;
  padding: 0;
  box-shadow: var(--shadow);
  transition:
    background 0.25s var(--ease),
    color 0.25s var(--ease),
    border-color 0.25s var(--ease),
    transform 0.25s var(--ease),
    opacity 0.25s var(--ease);
  /* Stack on top of the carousel's overflowing flank tiles so the
     button is always reachable. */
  position: relative;
  z-index: 3;
}
.featured-arrow:hover:not(:disabled) {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
  transform: scale(1.06);
}
.featured-arrow:active:not(:disabled) {
  transform: scale(0.96);
}
.featured-arrow:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.featured-arrow svg {
  display: block;
}

@media (max-width: 640px) {
  .featured-arrow {
    /* On phones, anchor the arrows over the edges of the carousel
       so they don't steal width from the tiles. */
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 38px;
    height: 38px;
  }
  .featured-arrow--prev { left: 6px; }
  .featured-arrow--next { right: 6px; }
  .featured-carousel-wrap {
    position: relative;
    gap: 0;
  }
}

/* --- Dot indicators --- */
.featured-dots {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin: clamp(56px, 8vh, 96px) auto 0;
}
.featured-dots button {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: none;
  background: var(--ghost);
  padding: 0;
  cursor: pointer;
  transition: background 0.3s var(--ease), transform 0.3s var(--ease);
}
.featured-dots button:hover { background: var(--muted); }
.featured-dots button.on {
  background: var(--accent);
  transform: scale(1.25);
}

@media (prefers-reduced-motion: reduce) {
  .ftile { transition: opacity 0.3s linear; }
}

/* ============================================================
   ALL WORK
   ============================================================ */
.all-work {
  margin-top: clamp(100px, 16vh, 200px);
  padding-top: clamp(40px, 8vh, 100px);
  border-top: 1px solid var(--rule);
}
.all-work .display.secondary {
  font-size: clamp(40px, 7vw, 100px);
  text-align: center;
  color: var(--ghost);
  margin-bottom: clamp(40px, 6vh, 80px);
}

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  justify-content: center;
  margin-bottom: clamp(40px, 6vh, 80px);
}
.filter {
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 9px 18px;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--muted);
  transition: all 0.25s var(--ease);
}
.filter:hover { color: var(--ink); border-color: var(--ink); }
.filter.active {
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
}

/* Uniform grid — every tile is the same 4:5 portrait, like the hero cards.
   No more masonry; the photos themselves do the talking. */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(14px, 2vw, 28px);
  max-width: 1500px;
  margin: 0 auto;
}
.grid .tile {
  aspect-ratio: 4 / 5;
  border-radius: var(--radius);
  overflow: hidden;
  position: relative;
  cursor: pointer;
  background: var(--bg-alt);
  border: none;
  padding: 0;
  /* Reveal-on-scroll: just a fade — the continuous bob already gives
     the tile life, so we don't need a translate-up entrance. */
  opacity: 0;
  /* Continuous gentle bob, staggered per-tile via --i (set in JS). The
     negative animation-delay scatters each tile's start phase across
     the cycle so the grid breathes instead of bobbing in lockstep —
     same idea as the hero cards' multi-card wander, just calmer. */
  animation: tile-float 7s ease-in-out calc(var(--i, 0) * -310ms) infinite;
  /* The bob uses `transform`; the hover lift uses individual translate/
     rotate/scale properties so the two layers compose without fighting. */
  transition: opacity 0.7s var(--ease),
              translate 0.55s cubic-bezier(0.34, 1.56, 0.64, 1),
              rotate    0.55s cubic-bezier(0.34, 1.56, 0.64, 1),
              scale     0.55s cubic-bezier(0.34, 1.56, 0.64, 1),
              box-shadow 0.5s var(--ease);
  will-change: transform, translate, rotate, scale;
}
.grid .tile.in { opacity: 1; }
.grid .tile.hidden { display: none; }
.grid .tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: scale 1.2s var(--ease);
}
.grid .tile:hover {
  z-index: 2;
  /* Springy lift + slight rotate, echoing the rubbery feel of the hero
     cards. --hover-rot is set per-tile in JS so adjacent tiles tilt
     in different directions, mimicking the hero stack's varied base-rot. */
  translate: 0 -10px;
  rotate: var(--hover-rot, -1.2deg);
  scale: 1.025;
  box-shadow: var(--shadow);
}
.grid .tile:hover img { scale: 1.05; }
.grid .tile::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.35));
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  pointer-events: none;
}
.grid .tile:hover::after { opacity: 1; }
.grid .tile .cat {
  position: absolute;
  bottom: 14px; left: 14px;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 700;
  color: #fff;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.grid .tile .view {
  position: absolute;
  bottom: 14px; right: 14px;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 700;
  color: #fff;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.4s var(--ease) 60ms, transform 0.4s var(--ease) 60ms;
}
.grid .tile:hover .cat,
.grid .tile:hover .view { opacity: 1; transform: translateY(0); }

/* Subtle continuous floating motion. Echoes the hero cards' wander but
   is much calmer (a few px translate, fractional-degree rotate) — the
   gallery is supporting cast, not the lead. */
@keyframes tile-float {
  0%   { transform: translate(0px, 0px)   rotate(0deg);    }
  25%  { transform: translate(3px, -5px)  rotate(0.55deg); }
  50%  { transform: translate(-3px, 2px)  rotate(-0.5deg); }
  75%  { transform: translate(-2px, -3px) rotate(0.35deg); }
  100% { transform: translate(0px, 0px)   rotate(0deg);    }
}
@media (prefers-reduced-motion: reduce) {
  .grid .tile { animation: none; }
}

@media (max-width: 1024px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
  /* Pull the carousel tile down a notch on tablet so flanks still read. */
  .featured-carousel {
    --tile-w: clamp(220px, 36vw, 360px);
    --tile-h: clamp(300px, 56vw, 460px);
  }
}
@media (max-width: 600px) {
  .grid { grid-template-columns: 1fr; }
  /* On phones, the flanks tuck right against the active tile's edges. */
  .featured-carousel {
    --tile-w: clamp(200px, 60vw, 280px);
    --tile-h: clamp(260px, 80vw, 360px);
  }
  .ftile.flank-left  { transform: translateX(-78%) scale(0.7) rotate(-8deg); }
  .ftile.flank-right { transform: translateX(78%)  scale(0.7) rotate(7deg); }
  .card-stack { height: 60vw; }
  .card { width: 38vw; }
}

/* ============================================================
   CONTACT — fixed under main, revealed as main scrolls past
   ============================================================ */
.contact {
  position: fixed;
  inset: auto 0 0 0;            /* pin to bottom of viewport */
  /* Use 100dvh on browsers that support it; fall back to 100vh. */
  height: 100vh;
  height: 100dvh;
  z-index: 1;
  /* Mid-gray panel matching jackdanielvo.com — the giant white
     CONTACT word reads against this. Body uses the same gray below
     so the slide-up reveal is seamless. */
  background: var(--contact-bg);
  overflow: hidden;
  /* Tight horizontal padding so the giant word can run nearly to the
     viewport edges, like jackdanielvo. */
  padding: 0 clamp(10px, 2vw, 30px);
}
.contact-inner {
  position: relative;
  width: 100%;
  height: 100%;
}
.contact-mega {
  /* Matches jackdanielvo.com's CONTACT exactly — Helvetica Neue Black
     (weight 900), tight tracking, white-on-gray. Helvetica Neue is
     listed first so macOS picks up its "Black" cut; Arial Black is
     the Windows fallback that has comparable weight. */
  position: absolute;
  left: 50%;
  bottom: clamp(220px, 32vh, 360px);
  margin: 0;
  font-family: "Helvetica Neue", "Helvetica", "Arial Black", Arial, sans-serif;
  font-weight: 900;
  /* Helvetica Neue Black caps are ~0.78em wide; with -0.05em tracking
     applied between 7 letters the word's effective width is ≈5.11em.
     A min(18vw, 360px) cap keeps the word inside the viewport at
     every desktop width even with .contact's horizontal padding. */
  font-size: min(18vw, 360px);
  line-height: 0.85;
  color: var(--bg);
  letter-spacing: -0.05em;
  user-select: none;
  white-space: nowrap;
  /* scaleY: --reveal is the 0–1 scroll progress set by JS on :root.
     ×4.2 for the giant word's dramatic stretch — same magnitude
     jackdanielvo uses. transform-origin: center bottom anchors the
     bottom edge so only the top sweeps upward as the word grows. */
  transform: translateX(-50%) scaleY(calc(var(--reveal, 0) * 4.2));
  transform-origin: center bottom;
  will-change: transform;
}
[data-theme="dark"] .contact-mega {
  color: var(--bg-alt);
  -webkit-text-stroke: 1px var(--ghost);
}

/* Row of three big bold words: RATES. CALL. BOOK. ============
   They butt edge-to-edge (no spaces in HTML between the <a>s), so
   visual separation comes purely from each word's color — and the
   trailing period on each (which inherits the link's color, so the
   period belongs to the word it follows). The row shares the
   scroll-reveal animation but with a much smaller peak (1.4) so the
   words remain readable rather than stretched. */
.contact-links {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(80px, 12vh, 150px);
  text-align: center;
  font-family: var(--body-font);
  font-weight: 900;
  /* Sized to fit the viewport at all desktop widths. The row is
     "RATES.CALL.BOOK." — 16 characters of Inter Black, where each
     uppercase glyph is ~0.69em wide, periods are 0.29em wide, and
     -0.08em letter-spacing tightens by 1.28em. Net effective width
     is ≈8.6em. Parent .contact has up to 120px of horizontal
     padding, so available width at a 1500px viewport is ~1380px.
     A 10vw cap puts the row at ≈86vw of viewport — comfortably
     inside the available space at every desktop width. */
  font-size: min(10vw, 180px);
  line-height: 0.85;
  letter-spacing: -0.08em;
  white-space: nowrap;
  /* scaleY tied to the same --reveal — smaller peak (1.3) so the row
     reaches natural-ish size + a touch of stretch by the time the
     user is at the bottom of scroll, without colliding with the
     stretched CONTACT word above. */
  transform: scaleY(calc(var(--reveal, 0) * 1.3));
  transform-origin: center bottom;
  will-change: transform;
  user-select: none;
}

/* On narrow screens, drop to a vertical stack — each word on its own
   line, still big and bold. The whole stack still scaleY-animates
   together. We also retune the giant CONTACT word's animation here:
   on mobile the natural font-size is much smaller (capped to viewport
   width), so the same 4.2× scaleY peak that's dramatic on desktop
   barely covers the upper third of a phone screen. Bumping the peak
   to 8× and pulling the bottom anchor up to 40vh fills the upper
   viewport with the stretched word — same "billboard" effect as
   desktop, just calibrated for portrait phones. */
@media (max-width: 900px) {
  /* Compact contact: ditch the fixed-position scroll-reveal panel
     entirely on phones, tablets in portrait, and phones in landscape.
     The 100vh fixed panel forced a huge gray rectangle behind the
     words regardless of where we positioned them, since the words
     can't fill that much screen without scaleY tricks the user
     doesn't want. Instead, contact becomes a normal-flow block at
     the end of the page — the panel only takes the height it
     actually needs to hold the words plus modest padding. The 900px
     breakpoint covers iPhones (any orientation), small tablets, and
     a comfortable margin past 640px so landscape phones get the
     compact layout too. */
  body {
    background: var(--bg);
  }
  .contact-spacer { display: none; }
  .contact {
    position: static;
    height: auto;
    z-index: auto;
    padding: clamp(60px, 10vh, 120px) 6vw clamp(40px, 6vh, 80px);
  }
  .contact-inner {
    height: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: clamp(16px, 3vh, 32px);
  }
  .contact-mega,
  .contact-links,
  .copyright {
    position: static;
    transform: none;
    top: auto;
    bottom: auto;
    left: auto;
    right: auto;
  }
  .contact-mega {
    /* No --reveal-driven opacity on mobile. The JS still publishes
       --reveal based on the scroll-runway pattern that no longer
       applies in this layout, so it would leave the word at opacity
       0 for most of the page. Default opacity (1) means the word
       is just visible when the user scrolls to the contact section
       in normal flow — same way any other section appears. */
    font-size: min(18vw, 200px);
    line-height: 0.85;
  }
  .contact-links {
    font-size: min(15vw, 80px);
    line-height: 1.0;
    letter-spacing: -0.05em;
    white-space: normal;
  }
  /* Higher specificity (.contact .contact-link, .contact .copyright)
     because the BASE .contact-link and .copyright rules sit AFTER
     this @media block in source order. At equal specificity (0,1,0)
     the later rule wins, so without these compound selectors the
     base `display: inline-block` and `position: absolute` would clobber
     the mobile values, making the words flow inline and the copyright
     overlap them. Specificity 0,2,0 here beats the base. */
  .contact .contact-link {
    display: block;
  }
  .contact .copyright {
    position: static;
    bottom: auto;
    left: auto;
    right: auto;
    margin: 0;
  }
}
.contact-link {
  display: inline-block;
  padding: 0;
  border: none;
  text-decoration: none;
  transition: opacity 0.2s var(--ease), transform 0.3s var(--ease);
}
.contact-link:hover { opacity: 0.65; }
/* Three different colors carry the word breaks. RATES is the brand
   plum; CALL is the page ink; BOOK is a mid-warm gray so it sits
   between the other two without competing. */
.contact-link--rates { color: #b347b9; }              /* brand plum */
.contact-link--call  { color: var(--ink); }
/* BOOK: deep teal — a split-complement to the brand plum. Sits next
   to plum cleanly on the color wheel (purple ↔ teal), reads with
   similar visual weight to ink, and adds a third distinct hue so
   all three words separate clearly. Light teal in dark mode. */
.contact-link--book  { color: #1d5e62; }
[data-theme="dark"] .contact-link--call  { color: var(--ink); }
[data-theme="dark"] .contact-link--book  { color: #5fb1b6; }

.copyright {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(24px, 4vh, 44px);
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
}

/* When the shoot view is open, the fixed contact shouldn't peek through. */
body.shoot-open .contact { display: none; }

/* ============================================================
   SHOOT view — full-screen takeover, magazine-style layout
   ============================================================ */
.shoot {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 600;
  /* The new single-photo layout fits the viewport — no scroll needed.
     overscroll-behavior keeps stray gestures from chaining up to the
     locked body (defense in depth alongside the position:fixed lock). */
  overflow: hidden;
  overscroll-behavior: contain;
  display: none;
  flex-direction: column;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
.shoot.open {
  display: flex;
  opacity: 1;
  transform: translateY(0);
}
.shoot__bar {
  /* Anchored at the top of the takeover. Close button on the left,
     centered breadcrumb + counter, and an empty spacer on the right
     so the breadcrumb stays visually centered now that the arrow
     buttons are no longer in the bar. */
  display: grid;
  grid-template-columns: 38px 1fr 38px;
  align-items: center;
  gap: 24px;
  padding: 18px clamp(20px, 4vw, 50px);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--rule);
  z-index: 5;
  flex: 0 0 auto;
}
.shoot__bar-spacer { display: block; }
.shoot__close {
  background: transparent;
  border: 1px solid var(--rule);
  width: 38px; height: 38px;
  border-radius: 50%;
  font-size: 22px;
  color: var(--ink);
  display: grid;
  place-items: center;
  transition: all 0.25s var(--ease);
}
/* The X glyph is the only thing shown on desktop. The "Close" word label
   only appears at mobile widths (see media query below), where the button
   morphs into a labeled solid pill so it can't be missed against varied
   photo backgrounds. */
.shoot__close-x { display: block; line-height: 1; }
.shoot__close-label { display: none; }
.shoot__close:hover { color: var(--accent); border-color: var(--accent); }
.shoot__close:hover .shoot__close-x { transform: rotate(90deg); transition: transform 0.25s var(--ease); }
.shoot__crumb { margin: 0; text-align: center; color: var(--muted); }
.shoot__crumb span[data-role="num"] { color: var(--ink); }
.shoot__nav { display: flex; gap: 6px; justify-self: end; }
.shoot__nav button {
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 9px 16px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: all 0.25s var(--ease);
}
.shoot__nav button:hover { color: var(--ink); border-color: var(--ink); }
.shoot__nav .caps { display: inline; }

/* Stage: holds the photo body plus the two floating side arrows. It's
   the flex child that fills the space between bar and foot. */
.shoot__stage {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  width: 100%;
}
.shoot__body {
  /* Photo container — fills the stage. Default align-items:stretch
     lets the .shoot__page inside grow to full height, giving the
     photos inside a definite parent to size against. */
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  padding: clamp(16px, 3vh, 36px) clamp(14px, 3vw, 50px);
}
/* The "page" is the row of (up to) 3 photos shown together. The flex
   children share width via flex:1 1 0; cross-axis defaults to stretch
   so each .shoot__photo fills the page's height. */
.shoot__page {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: clamp(10px, 1.4vw, 22px);
  width: 100%;
}
.shoot__photo {
  /* Each photo gets an equal share of the row, capped at 1 / --photo-cap.
     --photo-cap is set by JS to min(perPage, totalPhotos), so:
       - 3+ photos on desktop  → cap=3 → each photo max 1/3 width
       - 2-photo shoot         → cap=2 → each photo max 1/2 width
       - 1-photo shoot         → cap=1 → photo can fill row
       - phones (perPage=1)    → cap=1 → single photo fills row
     The gap math subtracts (cap-1) gaps' worth of width before dividing. */
  margin: 0;
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  max-width: calc(
    (100% - clamp(10px, 1.4vw, 22px) * (var(--photo-cap, 3) - 1))
    / var(--photo-cap, 3)
  );
  display: flex;
  align-items: center;
  justify-content: center;
  /* Staggered fade-in (delays applied inline in JS per child) */
  opacity: 0;
  transform: translateY(12px);
  animation: shoot-photo-in 0.5s var(--ease) forwards;
}
.shoot__photo img {
  display: block;
  /* Constrained by the figure's (now definite) box. width/height auto
     keeps the image's natural aspect ratio, so portrait portraits
     and landscape photos both fit fully — no cropping, no squashing.
     The calc() fallback bounds the img to the viewport minus measured
     chrome (~80px bar + ~80px foot + ~40px body padding ≈ 200px) so
     that even if the flex parent doesn't resolve to a definite size
     in some browser, the img is still capped. */
  max-width: 100%;
  max-height: min(100%, calc(100vh - 200px));
  width: auto;
  height: auto;
  object-fit: contain;
  box-shadow: var(--shadow);
  background: var(--bg-alt);
  user-select: none;
  -webkit-user-drag: none;
}
@keyframes shoot-photo-in {
  to { opacity: 1; transform: translateY(0); }
}

/* ------------------------------------------------------------
   Big floating side arrows — Prev / Next photo navigation.
   These overlay the photo area at the left and right edges so the
   way to flip through photos is immediately obvious.
   ------------------------------------------------------------ */
.shoot__side-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: clamp(46px, 5.5vw, 64px);
  height: clamp(46px, 5.5vw, 64px);
  border-radius: 50%;
  border: 1.5px solid var(--rule);
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  box-shadow: var(--shadow);
  transition:
    background 0.25s var(--ease),
    color 0.25s var(--ease),
    border-color 0.25s var(--ease),
    transform 0.25s var(--ease);
  z-index: 10;       /* float above the photo */
}
.shoot__side-arrow--prev { left: clamp(10px, 2vw, 32px); }
.shoot__side-arrow--next { right: clamp(10px, 2vw, 32px); }
.shoot__side-arrow:hover {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
  transform: translateY(-50%) scale(1.06);
}
.shoot__side-arrow:active {
  transform: translateY(-50%) scale(0.96);
}
.shoot__side-arrow svg { display: block; }

/* Photo counter pill in the breadcrumb — "3 of 8" */
.shoot__counter {
  display: inline-block;
  margin-left: 12px;
  padding: 3px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  color: var(--ink);
  font-weight: 700;
  letter-spacing: 0.06em;
  font-size: 11px;
}

.shoot__foot {
  /* Compact — the photo gets the screen, the foot is just a thin
     courtesy line linking to booking. */
  padding: 14px 20px;
  text-align: center;
  border-top: 1px solid var(--rule);
  flex: 0 0 auto;
  font-size: 12px;
}
.shoot__foot a {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

@media (max-width: 640px) {
  /* Tight bar on phones — keep the breadcrumb visible but compact.
     The close button widens into a labeled solid pill (see .shoot__close
     overrides below) so the column allowance has to grow accordingly,
     and the spacer mirrors it so the crumb stays visually centered. */
  .shoot__bar { grid-template-columns: auto 1fr auto; gap: 10px; padding: 12px 14px; }
  .shoot__crumb { font-size: 11px; }
  .shoot__counter { font-size: 10px; padding: 2px 8px; margin-left: 8px; }
  /* PROMINENT MOBILE CLOSE — users were missing the tiny X on phones.
     Solid filled pill with both an X glyph AND the word "Close",
     bright accent color, a touch shadow for separation against bright
     photos. Sized for a generous 44pt+ touch target. */
  .shoot__close {
    width: auto; height: auto;
    min-height: 44px;
    border-radius: 999px;
    padding: 10px 18px 10px 14px;
    background: var(--accent);
    border: 2px solid var(--accent);
    color: #fff;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 16px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.22);
  }
  .shoot__close-x {
    font-size: 22px;
    line-height: 1;
    font-weight: 400;
  }
  .shoot__close-label {
    display: inline;
    font-family: "Inter", system-ui, sans-serif;
    font-weight: 800;
    font-size: 13px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1;
  }
  .shoot__bar-spacer { display: none; }
  /* Same one-photo-fits-viewport behaviour on phones — touch swipe is
     the primary nav, but the side arrows stay visible (just smaller). */
  .shoot__body { padding: 12px; }
  .shoot__side-arrow {
    width: 40px;
    height: 40px;
  }
  .shoot__side-arrow--prev { left: 6px; }
  .shoot__side-arrow--next { right: 6px; }
  .shoot__side-arrow svg { width: 20px; height: 20px; }
}

/* When the shoot is open, lock body scroll */
body.shoot-open { overflow: hidden; }

/* ============================================================
   LIGHTBOX — fullscreen single-photo view, used when a hero card is
   URL-pinned (no shoot to open into). Tap or click to dismiss.
   ============================================================ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 700;
  background: rgba(0, 0, 0, 0.93);
  display: grid;
  place-items: center;
  padding: clamp(20px, 5vw, 60px);
  opacity: 0;
  transition: opacity 0.25s var(--ease);
  cursor: zoom-out;
}
.lightbox.open { opacity: 1; }
.lightbox img {
  /* `max-width: 100%` doesn't constrain inside an auto-sized grid cell,
     so peg the cap to the viewport directly (minus the lightbox's
     padding so we never bleed past the edges). */
  max-width: calc(100vw - clamp(40px, 10vw, 120px));
  max-height: calc(100vh - clamp(40px, 10vw, 120px));
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 0;
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.7);
  display: block;
}
.lightbox__close {
  position: absolute;
  top: clamp(16px, 3vw, 28px);
  right: clamp(16px, 3vw, 28px);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.3);
  font-size: 24px;
  cursor: pointer;
  display: grid;
  place-items: center;
  line-height: 1;
  transition: background 0.2s var(--ease), transform 0.2s var(--ease);
}
.lightbox__close:hover {
  background: rgba(255, 255, 255, 0.22);
  transform: rotate(90deg);
}

/* ============================================================
   Reveal-on-scroll
   ============================================================ */
.reveal { opacity: 0; transform: translateY(30px); transition: opacity 0.8s var(--ease), transform 0.8s var(--ease); }
.reveal.in { opacity: 1; transform: translateY(0); }
