/* ===================================================================
   DESIGN TOKENS
   -------------------------------------------------------------------
   Subject: a director / photographer / designer whose work spans
   motion, stills, and brand systems. The visual language borrows
   from the film-editing world — reel numbers, timecodes, slate tape —
   because that vernacular is native to the work, not decoration
   bolted onto a generic template.
=================================================================== */
:root{
  --blackout:#0B0B0C;
  --paper:#F3EFE4;
  --tape:#F80052;
  --smoke:#9A9484;
  --rule:#2A2A28;
  --scrim-top: rgba(11,11,12,.15);
  --scrim-bottom: rgba(11,11,12,.85);

  --display: "Anton", Impact, sans-serif;
  --mono: "Space Mono", "Courier New", monospace;
  --body: "Inter", -apple-system, sans-serif;

  --nav-h: 76px;
  --row-height: 90dvh; /* ← change this to shorten/lengthen every row, e.g. 80vh or 640px */
  /* Shared with .reel-content's own side padding below AND with the
     "Culture & Government" seal's overflow-safe positioning math (see
     .reel-headline-seal) — kept as one variable so both always agree on
     how much side gutter actually exists at any screen width. */
  --content-pad-x: clamp(20px,4vw,48px);
}

*{ box-sizing:border-box; }
html{ scroll-behavior:smooth; }
@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }
  *{ animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important; scroll-behavior:auto !important; }
}

body{
  margin:0;
  overflow-x:hidden;
  background:var(--blackout);
  color:var(--paper);
  font-family:var(--body);
  -webkit-font-smoothing:antialiased;
}

a{ color:inherit; text-decoration:none; }
img, video{ display:block; width:100%; height:100%; object-fit:cover; }

/* ---------------------------- NAV ---------------------------- */
.site-nav{
  position:fixed;
  top:0; left:0; right:0;
  height:var(--nav-h);
  z-index:50;
  display:flex; align-items:center; justify-content:space-between;
  padding:0 clamp(20px,4vw,48px);
  /* Always solid black — no longer fades to transparent at the top of
     the page. The first row's media is pushed down to sit flush below
     this bar (see <main>'s inline padding-top in index.html), so a
     transparent nav is no longer needed to "reveal" the hero video
     behind it. */
  background:var(--blackout);
  transition:border-color .35s ease;
  border-bottom:1px solid transparent;
}
.site-nav.scrolled{
  border-bottom-color:var(--rule);
}

.brand{
  font-family:var(--display);
  font-size:clamp(18px,2.4vw,24px);
  letter-spacing:.02em;
  display:flex; flex-direction:row; align-items:center; gap:11px;
  width:fit-content;
}

.brand .brand-mark{
  color:var(--tape);
  line-height:1;
}
.brand-tagline{
  font-family:var(--mono); font-size:12px; letter-spacing:.06em; text-transform:uppercase;
  line-height:1;
}

.nav-links{
  display:flex; align-items:center; gap:clamp(14px,2vw,28px);
  font-family:var(--mono); font-size:12px; letter-spacing:.06em; text-transform:uppercase;
}
.nav-links a{
  position:relative; padding:6px 2px; color:var(--paper); opacity:.8;
  transition:opacity .2s ease;
  /* Flex row so any link that carries an icon (see .nav-emoji) centers
     that icon against its own text first, rather than leaning on
     inline baseline math — baseline alignment is what threw "Photo
     Stream" out of line with plain-text links like "About / Contact"
     last time, since emoji glyphs carry their own baked-in vertical
     offset that varies by platform/font. */
  display:inline-flex;
  align-items:center;
}
.nav-links a:hover, .nav-links a:focus-visible{ opacity:1; }
.nav-links a::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:var(--tape); transform:scaleX(0); transform-origin:left;
  transition:transform .25s ease;
}
.nav-links a:hover::after, .nav-links a:focus-visible::after{ transform:scaleX(1); }

/* Camera emoji next to "Photo Stream" — sits in a fixed-size box that
   flex-centers the glyph itself (rather than sizing the emoji via
   font-size and leaning on line-height/baseline to place it), so its
   vertical position doesn't ride on emoji font metrics, which vary
   enough across platforms to visibly throw off baseline-based
   centering. Wiggles side to side around its own center, same
   "always-on decorative motion" treatment as .scroll-cue-corner's
   bounce elsewhere on the site. Respects the reduced-motion override
   at the top of this file automatically, since that collapses all
   animation-durations to near-zero. */
.nav-emoji{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:18px;
  height:18px;
  margin-bottom:6px;
  margin-right:10px;
  font-size:22px;
  line-height:1;
  transform-origin:50% 50%;
  animation:navEmojiWiggle 2s ease-in-out infinite;
}
@keyframes navEmojiWiggle{
  0%,100%{ transform:rotate(-18deg); }
  50%{ transform:rotate(18deg); }
}

/* "Photo Stream" label next to the camera emoji — visible on
   non-mobile by default (no rule needed here, it just inherits
   .nav-links a's normal mono/uppercase look). Hidden at the mobile
   breakpoint below so only the emoji itself shows there. */

.nav-toggle{ display:none; }
@media (max-width:640px){
  /* No hamburger/menu toggle exists in the markup, so hiding .nav-links
     here would remove all site navigation on mobile with no way to get
     it back. Keep the links visible and just tighten them to fit.

     "MEDIA" used to just disappear here (display:none on
     .brand-tagline) — it never should have, since that's the only
     place the tagline appeared at all on small screens. Instead the
     whole .brand lockup switches from a side-by-side row to a stacked
     column, with align-items:flex-start so "MEDIA CREATION" sits
     left-flush under "COZZOLINO" (per Tony's request — was
     align-items:flex-end/right-flush) instead of overlapping the nav
     links or floating off on its own. */
  .brand{
    flex-direction:column;
    align-items:flex-start;
    gap:2px;
  }
  .brand-tagline{
    font-size:10px;
  }
  /* Emoji-only on mobile — "Photo Stream" drops out entirely rather
     than shrinking, since the camera emoji alone already reads fine
     as a link at this size. */
  .nav-photo-text{
    display:none;
  }
  /* Small mobile: links go as small as they can read ("on point") and
     sit close together — the camera emoji next to "Photo Stream"
     already reads as its own visual break, so a tight gap here still
     keeps the two links looking like a single cluster rather than
     spread across the bar. */
  .nav-links{
    gap:1px;
    font-size:10px;
  }
}

/* --------------------------- REELS ---------------------------- */
.reel{
  position:relative;
  min-height:var(--row-height);
  display:flex;
  align-items:flex-end;
  overflow:hidden;
  isolation:isolate;
}

.reel-media{
  position:absolute; inset:0; z-index:0;
}
.reel-media img, .reel-media video{
  width:100%; height:100%; object-fit:cover;
}
/* A <picture> wrapper (used when a row swaps in a narrower mobile crop
   — see reel-7's mobileSrc in index.html) is inline by default and
   wouldn't otherwise fill .reel-media the way a bare <img> does; give
   it the same absolute-fill treatment so the <img> inside still has a
   definite box to size against. */
.reel-media picture{
  display:block; width:100%; height:100%;
}
/* Vimeo embeds are an <iframe>, which has no object-fit support, so
   it's oversized and centered instead — the standard "cover" trick
   for 16:9 embeds. */
.video-bg-wrap{
  position:absolute; inset:0; overflow:hidden;
}
.video-bg-wrap iframe{
  position:absolute; top:50%; left:50%;
  width:177.78vh; height:100vh; /* 16:9 sized off viewport height */
  min-width:100%; min-height:56.25vw; /* 16:9 sized off row width */
  transform:translate(-50%,-50%);
  border:0; pointer-events:none;
}
/* Slideshow — stacked images that crossfade on a timer */
.slideshow-wrap{
  position:absolute; inset:0;
}
.slideshow-slide{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  opacity:0;
  transition:opacity 1.2s ease;
}
.slideshow-slide.is-active{
  opacity:1;
}

/* Fallback look when no media file is supplied yet — a graded gradient
   that still reads as "designed" rather than a blank box. Swap any
   row's src in MEDIA_CONFIG (bottom of index.html) and this disappears
   beneath the real asset. */
.reel-media.placeholder{
  background:
    linear-gradient(160deg, var(--tint-a,#1c1a19), var(--tint-b,#0B0B0C) 70%);
}
.reel-media.placeholder::after{
  content:""; position:absolute; inset:0;
  background-image:radial-gradient(rgba(255,255,255,.04) 1px, transparent 1px);
  background-size:3px 3px;
  mix-blend-mode:overlay;
}

.reel-scrim{
  position:absolute; inset:0; z-index:1;
  background:linear-gradient(to top, var(--scrim-bottom) 0%, var(--scrim-top) 55%, transparent 100%);
}

.reel-frame-meta{
  position:absolute; top:calc(var(--nav-h) + 20px); left:clamp(20px,4vw,48px);
  z-index:2;
  font-family:var(--mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--paper);
  display:flex; align-items:center; gap:10px;
  /* Solid black bar behind the pink dot + label so the small mono text
     doesn't get lost against whatever photo/video is playing behind
     it. Width/height default to auto (the bar just hugs the dot+text)
     but both are real, overridable box dimensions — set a fixed size
     sitewide by changing the defaults here, or per-row with e.g.
     style="--frame-meta-w:220px; --frame-meta-h:32px;" on an
     individual .reel-frame-meta.

     Padding is split into top/bottom (--frame-meta-pad-y) and
     left/right (--frame-meta-pad-x) so each can be tuned separately —
     top/bottom defaults tighter (1px) than the sides (10px). Adjust
     either independently, sitewide here or per-row inline.

     Transparency is its own knob too: --frame-meta-alpha (0%–100%)
     controls only the bar's fill via color-mix(), not the dot/text
     riding on top of it — a plain CSS `opacity` on this element would
     have faded the label along with the bar, which isn't what we want.
     100% = fully solid (today's look); lower it for a more see-through
     bar, e.g. style="--frame-meta-alpha:55%;". */
  width:var(--frame-meta-w, auto);
  height:var(--frame-meta-h, auto);
  padding:var(--frame-meta-pad-y, 1px) var(--frame-meta-pad-x, 10px);
  background:color-mix(in srgb, var(--frame-meta-bg, var(--blackout)) var(--frame-meta-alpha, 100%), transparent);
}
.reel-frame-meta .dot{ width:4px; height:4px; border-radius:50%; background:var(--tape); flex:none; }

.reel-content{
  position:relative; z-index:2;
  width:100%;
  /* Top padding is 0 by default (every row starts flush with the top
     of its content area) but adjustable per-instance via
     --content-pad-top, e.g. style="--content-pad-top:80px;" on the
     .reel-content div — used by reel-7 to add breathing room above
     "Call me Tony". */
  padding:var(--content-pad-top, 0) var(--content-pad-x) clamp(64px,10vh,120px);
  max-width:1400px; margin:0 auto;
  display:flex; flex-direction:column; gap:22px;
  opacity:0;
  /* --content-shift-x nudges the whole headline/sub/button block
     sideways as one unit, on top of the reveal animation below — a
     negative value moves it left, a positive value moves it right.
     Adjustable per-instance via style="--content-shift-x:-40px;" on
     the .reel-content div (used on reel-2's "Culture & Government"
     block). Because it's a transform, it doesn't change the box's
     layout width or trigger any reflow/overflow of the row — it just
     slides the already-aligned block sideways, so the headline, the
     seal riding on it, .reel-sub, and .reel-cta all move together and
     stay lined up with each other exactly as before. */
  transform:translateY(28px) translateX(var(--content-shift-x, 0));
  transition:opacity .7s ease, transform .7s ease;
}
.reel.in-view .reel-content{ opacity:1; transform:translateY(0) translateX(var(--content-shift-x, 0)); }

.reel:nth-child(even) .reel-content{ align-items:flex-end; text-align:right; }

/* reel-7 (the merged "Call me Tony" sign-off) is pinned right-aligned
   here explicitly, rather than relying on .reel:nth-child(even) above
   (which right-aligns a row based purely on sibling position —
   removing an earlier row, like when "Who I've worked with" was
   deleted, can silently flip a later row's odd/even parity). An ID
   selector outranks the nth-child rule's specificity no matter what
   position reel-7 ends up in, so this stays correct even if rows are
   added or removed above it again.

   .footer-bottom is deliberately exempted from the shrink-and-align
   that the other children get (align-self:stretch keeps it full
   width) — its ::before divider line centers itself off this
   element's own box, so if it were left to shrink like its siblings
   the line would end up off-center instead of spanning the full
   page width. justify-content:flex-end pushes the copyright text to
   the right edge of that full-width bar instead, so the line stays
   correct while the text still reads right-aligned. */
#reel-7 .reel-content{ align-items:flex-end; text-align:right; }
#reel-7 .reel-sub{ margin-left:auto; }
/* justify-content:flex-start left-aligns the copyright line within
   .footer-bottom's own full-width bar — independent of the
   right-alignment everything above it in this section gets, since
   .footer-bottom is deliberately exempted from that shrink-and-align
   (see the comment above #reel-7 .reel-content). */
#reel-7 .footer-bottom{ align-self:stretch; justify-content:flex-start; }

/* Very slow "Ken Burns" pan on reel-7's photo (index_tony.jpg) —
   scoped to just this image, not every image sitewide. Scaled up
   slightly (1.08x) so the pan never reveals an edge, then drifted
   diagonally and back over a long, slow cycle. .reel already clips
   overflow, so the oversized/moving image never bleeds past the
   section. Automatically shortened to near-zero by the
   prefers-reduced-motion override at the top of this file. */
@keyframes reel7PhotoPan{
  0%{ transform:scale(1.08) translate(0,0); }
  100%{ transform:scale(1.08) translate(-2.5%,-2.5%); }
}
#reel-7 .reel-media img{
  animation:reel7PhotoPan 50s ease-in-out infinite alternate;
}

/* about.html's reel-7 ("Let's Talk") is left-aligned instead of the
   sitewide #reel-7 right-alignment used above — scoped with this extra
   .reel-left class (added only to about.html's markup) so index.html/
   photostream.html/culture-government.html's reel-7 rows (still showing
   "Call me Tony", still meant to read right-aligned) are untouched. An
   ID selector combined with a class on that same element ("#reel-7
   .reel-left") outranks the plain "#reel-7 .reel-content" rule above on
   specificity, so this wins without editing that shared rule. flex-start
   mirrors exactly how flex-end/text-align:right was set above — just
   pointed the other way. */
#reel-7.reel-left .reel-content{
  align-items:flex-start;
  text-align:left;
}

/* The "About" button in reel-7 uses a reversed color scheme from
   every other .reel-cta on the site (this section only — the base
   .reel-cta rule above is untouched, so buttons elsewhere keep their
   pink-fill/black-text look): black at rest, filling pink on hover,
   with the border staying pink throughout so the button still reads
   as a button against the black backdrop when it's not hovered. */
#reel-7 .reel-cta{
  background:var(--blackout);
  color:var(--tape);
  border-color:var(--tape);
}
#reel-7 .reel-cta:hover, #reel-7 .reel-cta:focus-visible{
  background:var(--tape);
  color:var(--blackout);
}

.reel-headline{
  font-family:var(--display);
  font-weight:400;
  text-transform:uppercase;
  line-height:.92;
  letter-spacing:.01em;
  /* --headline-base holds the original fluid size (unchanged) so both
     multipliers below stay derived from one source instead of two
     separately-maintained clamp()s drifting apart. Laptop/desktop
     (the default, i.e. anything wider than the 640px mobile
     breakpoint used elsewhere on this site — see .nav-links etc.)
     gets 1.25x that size; the mobile override further down goes to
     2x. */
  --headline-base:clamp(38px, 7vw, 96px);
  font-size:calc(var(--headline-base) * 1.25);
  max-width:18ch;
  margin:0;
  /* --headline-seal-gap / --headline-seal-gap-left pull the TEXT away
     from a headline's right or left edge, opening a gap between it and
     a seal image pinned to that same edge (.reel-headline-seal sits at
     right:0; .reel-headline-seal-left sits at left:0 — see both below).
     Because *{box-sizing:border-box} is set globally, adding padding
     here shrinks only the space text is allowed to flow into (so long
     headlines wrap a little sooner/tighter) without moving this box's
     own edges at all — the seal, and this headline's alignment with
     .reel-sub/.reel-cta below it, are both untouched. Both are 0 by
     default (no effect on headlines without a seal); set the one that
     matches your seal's side per-instance, e.g.
     style="--headline-seal-gap:2.4em;" for a right-side seal (used on
     index.html reel-2's "Culture & Government") or
     style="--headline-seal-gap-left:1.7em;" for a left-side seal (used
     on government.html's hero "Culture & Government") — in em units so
     the gap scales with the headline's own font-size instead of going
     stale at a fixed pixel width across breakpoints. Increase whichever
     one applies if the seal still covers text; decrease it if there's
     an unnecessarily large empty stripe between text and seal. */
  padding-right:var(--headline-seal-gap, 0);
  padding-left:var(--headline-seal-gap-left, 0);
  /* overflow-wrap/word-break are deliberately left at their normal
     (non-breaking) default — a word is never allowed to split mid-way
     onto two lines. The mobile rule below is what actually keeps
     headlines from overflowing; word-breaking was tried first and
     rejected (it split words like "ENTERTAINMENT" into "ENTERTAINME" /
     "NT", which reads worse than a slightly smaller headline). */
}
@media (max-width:640px){
  .reel-headline{
    /* A flat *2 multiplier on --headline-base was the earlier attempt
       here, but --headline-base bottoms out at a fixed 38px floor (its
       clamp() stops shrinking as the viewport narrows further), so on
       small phones the doubled size stayed a fixed 76px regardless of
       how little width was actually available — enough for long words
       like "ENTERTAINMENT"/"MOTION+EDITING" or "GOVERNMENT" to have
       nowhere to fit, which is what forced them to break mid-word.
       A pure vw value has no such floor — it keeps scaling down with
       the viewport all the way to the smallest phones, so long words
       always have room to wrap between words instead of within one.
       9vw was checked (headlessly, at 320–639px widths, against every
       headline on the site including the longest words) to comfortably
       fit without breaking; if a future headline is longer still and
       needs to wrap, tune this number down rather than reintroducing
       word-breaking above. */
    font-size:9vw;
  }
}

/* Logo stacked above "PICKLEBALL TOURNAMENT" in reel-1 (the SMASH/
   Selkirk lockup), left-aligned to match the headline below it since
   it's a plain inline child of the same left-aligned .h1 — no extra
   alignment rule needed here.

   Sized in em, not px or %, and with no font-size of its own — so it
   inherits .reel-headline's font-size (the clamp() above) and scales
   at exactly the same rate the headline text does on resize, rather
   than tracking the viewport on its own separate curve. height:auto
   preserves the source PNG's own aspect ratio.

   display:block overrides this site's global "img{ width:100%;
   height:100%; object-fit:cover }" rule (used elsewhere for reel
   background photos, which doesn't apply to inline logo art) and
   also forces the following headline text onto its own new line
   without needing a <br>.

   margin-bottom is NEGATIVE on purpose — it pulls the headline text
   up underneath the logo's own bottom edge so the two overlap
   slightly (clipping the very top of "PICKLEBALL"), instead of
   leaving a gap between them. It's in em too, so the amount of
   overlap scales right along with the logo and the headline as the
   page resizes, instead of a fixed px overlap looking too small on a
   huge headline or too aggressive on a tiny one. */
.reel-headline-logo{
  display:block;
  width:5em;
  max-width:100%; /* keeps the logo from running past the edge on narrow phones now that mobile headlines scale up */
  height:auto;
  margin:0 0 -.14em;
  /* Overlapping normal-flow content paints in DOM order by default —
     since this <img> comes BEFORE the "PICKLEBALL TOURNAMENT" text in
     the markup, the text was painting on top of it in the overlap
     area, hiding the bottom of the logo behind the letters. Giving
     the logo its own stacking context (position:relative + a z-index)
     lifts it above ordinary in-flow text regardless of markup order,
     so the logo now sits in front of the headline instead of behind
     it. */
  position:relative;
  z-index:1;
  /* Nudges the logo down 5px from where it'd otherwise sit, without
     shifting the headline text below it (top offsets on a
     position:relative element move it visually but don't affect the
     normal-flow layout everything else is measured against). Fixed
     px here, not em, per request for a flat 5px move regardless of
     screen size. */
  top:2px;
}

/* Seal image on "Culture & Government" (index.html reel-2) — treated as
   part of the headline rather than a separate element beside it, per
   the reference image: it's the same height as the two-line headline,
   deliberately overlapping the tail end of the text, and its right
   edge lines up with the paragraph and the button below it — the
   headline+seal reads as one unit, not headline-plus-decoration.

   That last part is what makes this simple: .reel-sub and .reel-cta
   are already right-aligned to the row's right edge via
   .reel:nth-child(even) .reel-content{ align-items:flex-end }, and so
   is the headline itself (as a plain block child of that same flex
   column) — meaning the headline's own right edge ALREADY sits
   exactly where the paragraph and button's right edges do. So pinning
   the seal's right edge to the headline's right edge (right:0, on a
   position:relative headline) lines it up with all three automatically,
   with no extra math and no risk of running past the screen edge —
   it's the same edge every other right-aligned element on this row
   already uses safely. Overlapping the text is the intended look here,
   not a bug to design around like the earlier attempts assumed.

   height:100% matches the seal to the full headline box, on one line
   or two; width:auto derives the matching width from the image's own
   aspect ratio. There's deliberately no size cap this time — a wide
   seal only ever grows further into the (much longer) text to its
   left, never past the row's own right edge.

   .reel-headline-seal-left below is the mirror image of this, used on
   government.html's hero headline: same h/w mechanics, just pinned to
   left:0 instead of right:0, for a page whose headline is left-aligned
   by default (a .project-hero, not a .reel:nth-child(even) row) rather
   than right-aligned. Pair it with --headline-seal-gap-left (not
   --headline-seal-gap) on that headline so the text recedes away from
   the correct edge — see the comment above padding-right/padding-left
   on .reel-headline above. */
.reel-headline:has(.reel-headline-seal, .reel-headline-seal-left){
  position:relative;
}
.reel-headline-seal{
  position:absolute;
  top:0;
  right:0;
  height:100%;
  width:auto;
  display:block;
}
.reel-headline-seal-left{
  position:absolute;
  top:0;
  left:0;
  height:100%;
  width:auto;
  display:block;
}

.reel-sub{
  /* Space Mono per Tony's request (was var(--body), i.e. Inter) — this
     is the shared .reel-sub rule, so every "paragraph" line under a
     headline site-wide (index.html and every project page reusing
     .reel-sub, e.g. government.html's hero) picks up Space Mono, not
     just one page. */
  font-family:var(--mono);
  font-size:clamp(14px,1.6vw,17px);
  /* Was color:var(--paper) with opacity:.75 on the whole element — that
     dimmed-white look is now baked into the text color itself via
     color-mix() instead, specifically so the new background bar below
     can have its OWN, separate transparency. A blanket element opacity
     would have faded the black bar right along with the text, the same
     issue --frame-meta-alpha was built to avoid on .reel-frame-meta. */
  color:color-mix(in srgb, var(--paper) 75%, transparent);
  max-width:46ch;
  margin:0;
  /* Black bar behind the paragraph — same pattern as .reel-frame-meta
     above: width/height default to auto/fit-content (hugs the actual
     text instead of stretching to fill the row, which it otherwise
     would on the left-aligned rows where the parent's default
     align-items:stretch applies) but are real, overridable dimensions;
     padding splits into top/bottom vs left/right; --sub-alpha is the
     bar's own transparency knob via color-mix(), independent of the
     text color set above. Tune any of these sitewide here, or
     per-instance with e.g. style="--sub-alpha:55%;" on one .reel-sub. */
  width:var(--sub-w, fit-content);
  height:var(--sub-h, auto);
  padding:var(--sub-pad-y, 4px) var(--sub-pad-x, 8px);
  background:color-mix(in srgb, var(--sub-bg, var(--blackout)) var(--sub-alpha, 100%), transparent);
}
.reel:nth-child(even) .reel-sub{ margin-left:auto; }

/* ------------------------- REEL TAGS -------------------------
   Small pill-style labels under a headline, used in place of a single
   .reel-sub paragraph on rows where the copy is really just a short
   list of media types (e.g. "Photos", "Podcast", "Motion Design").
   Each tag gets its own solid black bar behind it — the same
   color-mix()-driven bar pattern .reel-frame-meta/.reel-sub/.crumb
   already use elsewhere on this site, just applied per-tag instead of
   to one block of text — and the whole group wraps onto new lines on
   its own as the row narrows, so tags always "stack" cleanly at any
   screen width without a separate mobile rule to maintain.

   TO EDIT: add/remove/relabel individual <li class="reel-tag"> items
   inside a <ul class="reel-tags"> freely — the layout adjusts
   automatically no matter how many tags a row has.

   --tag-gap controls the space between tags (both directions, since
   flex gap applies to wrapped rows too); --tag-pad-x/--tag-pad-y size
   each tag's own bar; --tag-bg/--tag-alpha work exactly like
   --sub-bg/--sub-alpha on .reel-sub above, tuneable sitewide here or
   per-instance with e.g. style="--tag-alpha:55%;" on one .reel-tags. */
.reel-tags{
  display:flex;
  flex-wrap:wrap;
  gap:var(--tag-gap, 10px);
  margin:0;
  padding:0;
  list-style:none;
}
.reel-tag{
  font-family:var(--mono);
  font-size:10px;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--paper);
  white-space:nowrap;
  padding:var(--tag-pad-y, 4px) var(--tag-pad-x, 8px);
  background:color-mix(in srgb, var(--tag-bg, var(--blackout)) var(--tag-alpha, 100%), transparent);
}
/* Right-aligned rows (see .reel:nth-child(even) .reel-content above)
   wrap their tags from the right edge inward too, so a wrapped group
   still reads flush-right against the headline/button above and
   below it instead of hugging the left edge of its own box. */
.reel:nth-child(even) .reel-tags{ justify-content:flex-end; }
@media (max-width:640px){
  .reel-tags{ gap:8px; }
}

.reel-cta{
  display:inline-flex; align-items:center; gap:12px;
  font-family:var(--mono); font-size:12px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--blackout); background:var(--tape);
  padding:14px 22px;
  border:1px solid var(--tape);
  width:fit-content;
  transition:background-color .2s ease, color .2s ease;
}
.reel-cta:hover, .reel-cta:focus-visible{
  background:transparent; color:var(--paper);
}

/* The ">" character used inside every CTA button, standardized across
   all reels so they all share one consistent arrow treatment. */
.arrow-icon{
  font-family:var(--mono);
  font-weight:700;
  line-height:1;
}

/* Scroll cue on the hero row — thin line + bouncing down-arrow
   telling visitors there's more content below. */
.scroll-cue{
  position:absolute;
  left:50%;
  bottom:32px;
  z-index:2;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:8px;
  color:var(--paper);
  opacity:.7;
  transition:opacity .2s ease;
  animation:scrollCueBounce 2.2s ease-in-out infinite;
}
.scroll-cue:hover, .scroll-cue:focus-visible{ opacity:1; }
.scroll-cue-line{
  width:1px;
  height:36px;
  background:var(--paper);
  opacity:.5;
}
.scroll-cue-arrow{
  font-family:var(--mono);
  font-size:16px;
  line-height:1;
}
@keyframes scrollCueBounce{
  0%,100%{ transform:translate(-50%,0); }
  50%{ transform:translate(-50%,10px); }
}

/* Large corner scroll cue — a second, more attention-grabbing "there's
   more below" indicator for pages like entertainment.html that don't
   open on a full-bleed .reel hero to hang the standard .scroll-cue
   off of. Fixed to the viewport (not the page), bottom-center, so it
   stays visible for as long as there's more page below; the page's
   IntersectionObserver (see the script at the bottom of the HTML)
   adds .is-hidden the moment the footer heading first scrolls into
   view, since its job is done at that point. Purely decorative
   (aria-hidden in the markup) — pointer-events are off so it never
   intercepts clicks on whatever is underneath it. Size and opacity
   are adjustable per-instance via --corner-arrow-size /
   --corner-arrow-opacity, e.g. style="--corner-arrow-size:64px;" on
   the element, same pattern as the rest of this site's spacing knobs. */
.scroll-cue-corner{
  position:fixed;
  left:50%;
  bottom:clamp(24px,5vh,64px);
  z-index:30;
  font-family:var(--mono);
  font-size:var(--corner-arrow-size, clamp(48px,8vw,88px));
  line-height:1;
  color:var(--tape);
  opacity:var(--corner-arrow-opacity, 1.0);
  pointer-events:none;
  animation:cornerArrowBounce 2.4s ease-in-out infinite;
  transition:opacity .4s ease;
}
.scroll-cue-corner.is-hidden{
  opacity:0;
}
@keyframes cornerArrowBounce{
  0%,100%{ transform:translate(-50%,0); }
  50%{ transform:translate(-50%,14px); }
}

/* ------------------------- SCROLL REVEAL -------------------------
   Generic version of the fade-up-into-view treatment .reel-content
   uses on the homepage (there, JS toggles .in-view on the whole
   .reel once it's on screen). This is the same idea as a standalone
   utility class, so it can be dropped onto any headline, paragraph,
   label, or button/link on a page that doesn't have .reel rows —
   project pages (2d3dreel.html, entertainmentreel.html, etc). JS at
   the bottom of each page observes every .reveal element and adds
   .in-view once it scrolls into the viewport (unobserving after, so
   it only plays once). Respects the reduced-motion override at the
   top of this file automatically, since that collapses all
   transition-durations to near-zero. */
.reveal{
  opacity:0;
  transform:translateY(20px);
  transition:opacity .7s ease, transform .7s ease;
  /* Stagger multiple .reveal elements that land in view together by
     giving each one its own delay, e.g. style="--reveal-delay:.15s;" */
  transition-delay:var(--reveal-delay, 0s);
}
.reveal.in-view{
  opacity:1;
  transform:translateY(0);
}

/* subtle per-row tint variety so the placeholder gradients aren't identical */
.reel:nth-child(1){ --tint-a:#241d19; --tint-b:#0B0B0C; }
.reel:nth-child(2){ --tint-a:#1a1d24; --tint-b:#0B0B0C; }
.reel:nth-child(3){ --tint-a:#231a1d; --tint-b:#0B0B0C; }
.reel:nth-child(4){ --tint-a:#1d2419; --tint-b:#0B0B0C; }
.reel:nth-child(5){ --tint-a:#191c24; --tint-b:#0B0B0C; }
.reel:nth-child(6){ --tint-a:#241923; --tint-b:#0B0B0C; }
.reel:nth-child(7){ --tint-a:#221d24; --tint-b:#0B0B0C; }

/* ---------------------------- FOOTER ---------------------------- */
.site-footer{
  position:relative;
  background:var(--blackout);
  border-top:1px solid var(--rule);
  padding:clamp(48px,8vw,96px) clamp(20px,4vw,48px) 32px;
}
.footer-grid{
  display:grid; gap:40px;
  grid-template-columns: 1.4fr 1fr 1fr;
  max-width:1400px; margin:0 auto;
}
/* Modifier for a .footer-grid holding just link columns (no brand
   column) — used when the footer is folded into a .reel's own
   content instead of standing alone, as on the homepage's merged
   "Call me Tony" / sign-off section. Declared before the responsive
   media query below (same specificity, source order decides) so the
   mobile 1-column collapse still wins at narrow widths. */
.footer-grid-2col{
  grid-template-columns:1fr 1fr;
}
@media (max-width:760px){
  .footer-grid{ grid-template-columns:1fr; }
}
.footer-brand .reel-headline{ font-size:clamp(28px,4vw,44px); margin-bottom:12px; }
.footer-col h3{
  font-family:var(--mono); font-size:11px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--smoke); margin:0 0 16px;
}
.footer-col ul{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:10px; }
.footer-col a{
  font-family:var(--body); font-size:14px; opacity:.85; transition:opacity .2s ease;
}
.footer-col a:hover, .footer-col a:focus-visible{ opacity:1; text-decoration:underline; text-underline-offset:3px; }

/* A .reel-cta button occasionally lands inside a .footer-col (e.g. the
   "Tony on Facebook" button in the homepage's merged sign-off section)
   — .footer-col a above sets its own font-family/font-size/opacity on
   every <a> inside it, and at equal-or-higher specificity than
   .reel-cta alone, so without this it would silently override the
   button's mono/uppercase treatment with the footer's plain link
   style. Re-assert .reel-cta's own look here. */
.footer-col .reel-cta{
  font-family:var(--mono);
  font-size:12px;
  opacity:1;
}
.footer-col .reel-cta:hover, .footer-col .reel-cta:focus-visible{
  opacity:1;
  text-decoration:none;
}

/* footer-bottom's border stretches the full viewport width via a
   full-bleed ::before, even though the row itself stays inside the
   1400px content column. */
.footer-bottom{
  position:relative;
  /* z-index:0 (on top of position:relative) makes .footer-bottom its
     own stacking context, so the -blackout::before backdrop's
     z-index:-1 below is guaranteed to land behind THIS element's own
     text on every device/browser — it no longer depends on how far
     up the tree a negative z-index happens to escape to, which is
     where mobile browsers and desktop ones can disagree. */
  z-index:0;
  max-width:1400px; margin:56px auto 0;
  padding-top:20px;
  display:flex; justify-content:space-between; flex-wrap:wrap; gap:12px;
  font-family:var(--mono); font-size:11px; color:var(--smoke); text-transform:uppercase; letter-spacing:.06em;
}
.footer-bottom::before{
  content:"";
  position:absolute;
  top:0;
  left:50%;
  width:100vw;
  transform:translateX(-50%);
  border-top:1px solid var(--rule);
}

/* Modifier for a .footer-bottom that sits directly on a photo/video
   background instead of an already-solid-black <footer> (e.g. the
   homepage's merged "Call me Tony" sign-off section, reel-7) — turns
   the existing full-bleed divider line into a solid black backdrop
   that runs from the line down through the rest of the section, so
   the copyright bar reads as a real footer strip instead of floating
   on the photo. The generous negative bottom value just needs to
   reach the section's bottom edge; the .reel it lives in clips
   overflow, so over-extending is harmless. No effect on pages where
   .footer-bottom already sits inside a solid-black <footer>.

   z-index:-1 keeps the copyright text readable: this ::before is
   position:absolute, and an absolutely positioned element paints ON
   TOP of ordinary in-flow content (like the &copy; text right next
   to it) by default — "before" only means first in the markup, not
   underneath visually. Once this pseudo-element gained a solid black
   background instead of just a thin line, that default stacking
   order was quietly painting the black backdrop over the text.
   Pinning it to -1 drops it behind .footer-bottom's own text (still
   above the photo/scrim further back, since those live in an outer
   stacking context of their own) — and because .footer-bottom itself
   now has z-index:0 (see above), that negative value is scoped to
   THIS element's own stacking context specifically, rather than
   escaping up to whichever ancestor happens to be the nearest one
   that establishes one. That escaping behavior is where different
   browsers (mobile Safari included) can disagree; pinning the
   context locally removes the ambiguity. */
.footer-bottom-blackout::before{
  background:var(--blackout);
  bottom:-200px;
  z-index:-1;
}

/* ------------------------- PROJECT PAGES -------------------------
   Shared by every project-template.html copy (photography.html,
   interactive.html, etc.) so a subpage always matches the index's
   look and feel — they all load this same stylesheet. */

/* "Back to Home" breadcrumb — sits in the same spot as .reel-frame-meta
   on the index, but is itself a link back to the homepage. */
.crumb{
  position:absolute; top:calc(var(--nav-h) + 20px); left:clamp(20px,4vw,48px);
  z-index:2;
  font-family:var(--mono); font-size:12px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--paper); opacity:.85;
  display:flex; align-items:center; gap:10px;
  transition:opacity .2s ease;
}
.crumb:hover, .crumb:focus-visible{ opacity:1; }
.crumb .dot{
  width:auto; height:auto; border-radius:0; background:none;
  color:var(--tape);
  font-family:var(--mono); font-size:14px; line-height:1;
}
/* Black bar behind the arrow + "Back to All Work" text — same pattern
   as .reel-frame-meta's bar: width/height default to auto (hugs the
   arrow+text) but are real, overridable box dimensions; padding splits
   top/bottom (--crumb-pad-y) vs left/right (--crumb-pad-x) so each can
   be tuned separately; --crumb-alpha is the bar's own transparency via
   color-mix(), independent of this element's existing .85 opacity
   (which fades the arrow/text, not the bar). Tune any of these sitewide
   here, or per-instance with e.g. style="--crumb-pad-y:8px;" on one
   <a class="crumb">.

   Scoped with :not(.crumb-inline) — a bar is only needed where the
   crumb overlays a photo (e.g. government.html's .project-hero, where
   this was requested); .crumb-inline sits in normal flow on the
   page's own solid black background (e.g. entertainment.html's
   .page-intro), where a black bar behind it would be invisible anyway
   and would just add unwanted padding. */
.crumb:not(.crumb-inline){
  width:var(--crumb-w, auto);
  height:var(--crumb-h, auto);
  padding:var(--crumb-pad-y, 4px) var(--crumb-pad-x, 10px);
  background:color-mix(in srgb, var(--crumb-bg, var(--blackout)) var(--crumb-alpha, 100%), transparent);
}
/* Modifier for using the same breadcrumb in normal document flow
   instead of overlaid on a full-bleed hero (e.g. above an inline
   video embed, where there's no .project-hero to anchor it to). */
.crumb-inline{ position:static; margin-bottom:24px; }

/* Page intro — headline + one-line description in normal flow (no
   full-bleed background video), used above an inline .video-embed. */
.page-intro{
  max-width:1400px; margin:0 auto;
  padding:calc(var(--nav-h) + clamp(32px,6vw,64px)) clamp(20px,4vw,48px) 0;
}
.page-intro .reel-headline{ margin-bottom:14px; }
.page-intro .reel-sub{ max-width:60ch; }

/* Standard responsive 16:9 embed (a real, interactive video — controls
   on, not the muted/looping background treatment used on the reels). */
.video-embed{
  position:relative;
  width:100%;
  aspect-ratio:16/9;
  background:var(--rule);
  overflow:hidden;
  /* Space above/below the embed. Override per-instance with e.g.
     style="--embed-pad-top:24px; --embed-pad-bottom:24px;" on the div.
     (Uses margin, not padding, so the 16:9 box itself never distorts.) */
  margin:var(--embed-pad-top, 0) 0 var(--embed-pad-bottom, 40px);
}
.video-embed iframe{
  position:absolute; inset:0;
  width:100%; height:100%;
  border:0;
}

/* Link below a video embed pointing to the live post on social media
   (e.g. "View on Instagram >"). Same small mono/uppercase treatment
   used for the site's other secondary links. */
.video-link{
  display:inline-flex; align-items:center; gap:6px;
  font-family:var(--mono); font-size:11px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--smoke);
  opacity:.85;
  transition:opacity .2s ease, color .2s ease;
  /* Space between the video above and this link. Override per-instance
     with e.g. style="--link-pad-top:16px;" on the <a>. */
  margin-top:var(--link-pad-top, 10px);
}
.video-link:hover, .video-link:focus-visible{ opacity:1; color:var(--paper); }

/* Spotify-branded variant of .video-link — Spotify's own brand green
   instead of the default smoke/paper treatment, 2px larger (13px vs
   the base 11px), always full-opacity rather than dimmed-until-hover,
   and paired with the .spotify-mark icon below instead of (or in
   addition to) the bare arrow-icon other .video-link instances use.
   Scoped to this modifier class — add class="video-link spotify" — so
   other .video-link instances (e.g. a future "View on Instagram" link)
   keep their default look untouched. */
.video-link.spotify{
  font-size:13px;
  color:#1DB954;
  opacity:1;
}
.video-link.spotify:hover, .video-link.spotify:focus-visible{
  color:#1ED760; /* Spotify's brighter hover green */
}
.spotify-mark{
  width:15px; height:15px;
  flex:none;
}

/* Project hero — a shorter one-off version of .reel (no scroll-cue,
   no data-row driven array; just the single HERO_MEDIA object at the
   bottom of the page). Reuses .reel-media / .reel-scrim / .reel-content
   / .reel-headline / .reel-sub / .reel-cta as-is. */
.project-hero{
  position:relative;
  min-height:60vh;
  display:flex;
  align-items:flex-end;
  overflow:hidden;
  isolation:isolate;
  --tint-a:#1c1a19; --tint-b:#0B0B0C;
}
.project-hero .reel-content{ opacity:1; transform:none; } /* no in-view reveal needed on a single hero */

/* Hero photo slideshow (the crossfading images inside .project-hero's
   .reel-media) keep the TOP of each source photo visible instead of
   the default object-fit:cover crop, which centers by default and was
   cutting subjects off across the top edge — e.g. reel-1's logo/net
   shots on pickleball-event.html. Scoped to .project-hero specifically
   so it doesn't touch the separate 3:2 .content-carousel photo grids
   further down the page (Action/Medalist Photography), which crop
   from dead-center on purpose. */
.project-hero .slideshow-slide{
  object-position:center top;
}

/* --------------------- PROJECT-HERO HEADLINE CLEARANCE ---------------------
   .project-hero's .reel-content (headline + any logo/seal image riding
   on it + .reel-tags) is flex-end aligned — it hugs the BOTTOM of the
   60vh hero, growing upward as its own content gets taller. At mobile
   and tablet/laptop widths the full-size headline+logo+tag combo (see
   pickleball-event.html's "PICKLEBALL TOURNAMENT" + logo + 5 tags) got
   tall enough to reach up past the top of that flex-end block and
   overlap the .crumb "Back to All Work" button, which is pinned near
   the top of the hero independently. Giving the hero itself extra
   height below doesn't change where the crumb sits — it just gives the
   bottom-anchored content block more room to occupy, which pushes the
   whole headline/logo/tags group further down the page, away from the
   crumb, without changing the crumb itself.

   Three ranges, worst overlap first:
   - Mobile S/M/L (<=640px): most extra height, since mobile viewports
     are proportionally shortest relative to their width and the
     headline runs its largest mobile size (see the 9vw .reel-headline
     rule above).
   - Tablet (641px-1024px): same issue, milder, so less extra height —
     paired with a flat 5% size reduction on the headline (its em-sized
     logo/seal shrinks right along with it) and the tags, so the block
     needs less room to begin with.
   - Laptop/Laptop Large (1025px-1440px): by this width, --headline-base
     has already hit its 96px clamp() ceiling (see .reel-headline
     above), so the plain headline text stops shrinking with viewport
     width on its own — and an em-sized logo like pickleball-event.html's
     index_smash-mk.png (~85% of the headline's own rendered height)
     rides right along, staying just as oversized. That combination was
     tall enough to poke up through the fixed site-nav bar (getting
     visually cropped by it) and still overlap the .crumb button below
     it — needs a bigger size cut than the tablet tier, PLUS reining in
     the logo's own em multiplier specifically (5em was tuned for a
     much smaller inherited font-size; at this tier's font-size that
     same multiplier is what was driving most of the extra height), on
     top of a much bigger height boost on the hero itself. Scoped to
     .project-hero specifically (rather than the plain .reel-headline /
     .reel-tag rules the tablet tier above uses) so this stronger
     laptop-tier cut doesn't also shrink index.html's or government.html's
     own headlines/tags at these widths, where this overlap problem
     doesn't exist.
   Anything above 1440px is treated as desktop, where the original
   60vh/full size already leaves enough clearance, so none of this
   applies. */
@media (max-width:640px){
  .project-hero{ min-height:calc(60vh + 140px); }
}
@media (min-width:641px) and (max-width:1024px){
  .project-hero{ min-height:calc(60vh + 80px); }
  .reel-headline{ font-size:calc(var(--headline-base) * 1.25 * 0.95); }
  .reel-tag{ font-size:9.5px; }
}
@media (min-width:1025px) and (max-width:1440px){
  .project-hero{ min-height:calc(60vh + 220px); }
  .project-hero .reel-headline{ font-size:calc(var(--headline-base) * 1.25 * 0.75); }
  .project-hero .reel-headline-logo{ width:3.5em; }
  .project-hero .reel-tag{ font-size:9px; }
}

/* ------------------------- CONTENT PHOTO CAROUSEL -------------------------
   A contained (not full-bleed) rotating photo carousel for inline body
   copy — used by government.html for its event-photography carousel,
   between the intro paragraph and the podcast video. Same crossfading
   .slideshow-wrap/.slideshow-slide mechanism the hero slideshow above
   uses, just boxed into a fixed aspect-ratio frame instead of filling
   the full viewport, with its own prev/next arrows + dot indicators
   (reusing the lightbox's button look — border + blackout fill, pink
   on hover — rather than inventing a new button style). TO ADJUST THE
   FRAME SHAPE: change the aspect-ratio value below. */
.content-carousel{
  position:relative;
  max-width:900px;
  aspect-ratio:3/2;
  overflow:hidden;
  isolation:isolate;
  /* Space above/below the carousel via the top/bottom values (override
     per-instance with e.g. style="--carousel-pad-top:24px;
     --carousel-pad-bottom:24px;" on the .content-carousel div, same as
     before). The left/right value is "auto" rather than a fixed 0 — since
     this box has a max-width (900px) that's narrower than its container
     on most screens, auto margins center it in the available width
     instead of leaving it flush against the left edge. Affects every
     .content-carousel on the site (Action Photography + Medalist
     Photography on pickleballevent.html, Event Photography on
     government.html) since they all share this one rule. */
  margin:var(--carousel-pad-top, 8px) auto var(--carousel-pad-bottom, 32px);
}
.content-carousel .slideshow-wrap{ position:absolute; inset:0; }
/* Vertical (portrait, e.g. a 2:3 photo) images dropped into this 3:2
   landscape frame are resized to fit the frame's HEIGHT instead of
   being cropped by the default object-fit:cover below — each page's
   buildContentCarousel() tags a portrait image with .is-vertical once
   it loads (comparing naturalHeight to naturalWidth), and this
   override switches just that image to object-fit:contain, so the
   full photo scales to the frame's height and letterboxes left/right
   (against this page's dark background) rather than losing its
   left/right edges. Landscape photos are untouched and keep filling
   the frame edge-to-edge via the default cover below. */
.content-carousel .slideshow-slide.is-vertical{
  object-fit:contain;
}
.content-carousel-prev, .content-carousel-next{
  position:absolute; top:50%; transform:translateY(-50%); z-index:3;
  border:1px solid var(--rule); background:var(--blackout); color:var(--paper);
  font-family:var(--mono); line-height:1;
  width:40px; height:40px; font-size:20px;
  display:flex; align-items:center; justify-content:center;
  cursor:pointer;
  transition:background-color .2s ease, color .2s ease, border-color .2s ease;
}
.content-carousel-prev:hover, .content-carousel-prev:focus-visible,
.content-carousel-next:hover, .content-carousel-next:focus-visible{
  background:var(--tape); color:var(--blackout); border-color:var(--tape);
}
.content-carousel-prev{ left:clamp(10px,2vw,20px); }
.content-carousel-next{ right:clamp(10px,2vw,20px); }
.content-carousel-dots{
  position:absolute; z-index:3; bottom:clamp(12px,2vh,20px); left:50%; transform:translateX(-50%);
  display:flex; gap:8px;
}
.content-carousel-dot{
  width:7px; height:7px; border-radius:50%;
  border:1px solid var(--paper); background:transparent; padding:0; cursor:pointer;
  opacity:.6; transition:opacity .2s ease, background-color .2s ease, border-color .2s ease;
}
.content-carousel-dot:hover, .content-carousel-dot:focus-visible{ opacity:1; }
.content-carousel-dot.is-active{
  background:var(--tape); border-color:var(--tape); opacity:1;
}
@media (max-width:640px){
  .content-carousel{ aspect-ratio:4/3; }
  .content-carousel-prev, .content-carousel-next{ width:34px; height:34px; font-size:16px; }
}

/* Stacked list of .video-link-styled links — e.g. below the photo
   carousel above, each one pointing to a different Facebook post or
   album. Same small mono/uppercase secondary-link look as everywhere
   else on the site, just several in a column instead of a single link
   under a video. */
.video-link-list{
  display:flex; flex-direction:column; gap:10px;
  /* Space above the list. Override per-instance with e.g.
     style="--link-list-pad-top:24px;" on the containing div. */
  margin-top:var(--link-list-pad-top, 4px);
}
.video-link-list .video-link{ margin-top:0; } /* the list's own gap handles spacing instead of each link's individual margin-top */

.project-meta{
  display:flex; flex-wrap:wrap; gap:28px;
  list-style:none; margin:0;
  /* Space above/below the whole list. Override per-instance with e.g.
     style="--meta-pad-top:24px; --meta-pad-bottom:24px;" on the <ul>. */
  padding:var(--meta-pad-top, 4px) 0 var(--meta-pad-bottom, 0);
}
.project-meta li{
  display:flex; align-items:baseline; gap:8px; /* label + value on one line */
  font-family:var(--mono); font-size:11px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--smoke);
}
.project-meta li strong{
  color:var(--paper); font-size:13px; letter-spacing:.01em; font-weight:600;
}

.project-body{
  max-width:1400px; margin:0 auto;
  /* --body-pad-top is the space between whatever comes before this
     section (e.g. the .page-intro paragraph) and its first child
     (e.g. the .video-embed). Override per-instance, e.g.
     style="--body-pad-top:16px;" on the <section class="project-body">. */
  padding:var(--body-pad-top, clamp(48px,8vw,96px)) clamp(20px,4vw,48px) clamp(48px,8vw,96px);
}
.project-body p{
  font-family:var(--body); font-size:clamp(15px,1.6vw,18px); line-height:1.7;
  color:var(--paper); opacity:.85; max-width:70ch; margin:0 0 24px;
}

.project-gallery{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(280px,1fr)); gap:16px;
  margin-top:16px;
}
.project-gallery figure{ margin:0; }
.project-gallery img, .project-gallery video{
  width:100%; aspect-ratio:4/3; object-fit:cover; display:block;
}
.project-gallery figcaption{
  font-family:var(--mono); font-size:11px; letter-spacing:.04em; color:var(--smoke);
  margin-top:8px;
}

/* ------------------------- PHOTO STREAM -------------------------
   Simple chronological photo blog grid, used by photostream.html.
   Every entry is a <figure class="photo-stream-item"> built by that
   page's own script from a plain array (photo file, date, one-line
   caption) — see the comment above the empty grid div in
   photostream.html for the three-step "how to add a photo" workflow.
   Reuses this site's existing type treatments (the small mono/
   uppercase look already used for dates elsewhere, like
   .project-meta, and .reel-sub's body-copy look for the caption)
   rather than introducing new type styles just for this page. */
.photo-stream-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(280px,1fr));
  gap:36px 24px;
}
.photo-stream-item{
  margin:0;
}
/* Order within each .photo-stream-item is date, then caption, then
   photo (see photostream.html's script) — so the date's margin below
   separates it from the caption, and the caption's margin below
   separates it from the photo. The photo itself is last, so it gets
   no bottom margin of its own (the grid's row gap already spaces it
   from the next entry). */
.photo-stream-date{
  display:block;
  font-family:var(--mono);
  font-size:10px;
  letter-spacing:.05em;
  text-transform:uppercase;
  color:var(--smoke);
  margin-bottom:0;
}
/* Every property below — not just margin — was silently losing to
   .project-body p's rule (declared earlier in this file, for ordinary
   body copy): this caption is a <p> living inside .project-body, and
   ".project-body p" (a class + an element selector) beats a plain
   class selector like ".photo-stream-caption" on CSS specificity, no
   matter what values were set here or which rule came later in the
   file. That's the actual reason font-size changes here had zero
   visible effect — the fix I made earlier only re-declared margin at
   higher specificity, which patched the spacing but left font-size,
   line-height, and color still losing the same fight. The
   .photo-stream-date rule below never had this problem since a <time>
   isn't a <p>, so ".project-body p" never matched it — that's why the
   date's own font-size DID work for you. Qualifying the selector with
   the .photo-stream-item ancestor class raises the specificity enough
   to win on every property at once, so this is now the one real place
   that controls how the caption looks. */
.photo-stream-item .photo-stream-caption{
  font-family:var(--body);
  font-size:13px;
  line-height:2.0;
  color:var(--paper);
  opacity:.85;
  margin:0;
}
/* Wrapper around an entry's photo(s) — one <img> for a normal single-
   photo entry, several for a grouped entry (see the "TO GROUP SEVERAL
   PHOTOS" comment in photostream.html). Plain block by default so a
   single photo behaves exactly as before; .photo-stream-photos-multi
   below switches it to a small grid only when there's more than one
   image in the entry. */
.photo-stream-photos{
  display:block;
}
/* Grouped-photo entries (more than one photo in a single
   .photo-stream-item — see PHOTOS array entries with an array for
   "src" in photostream.html) used to sit in a CSS grid with equal-
   WIDTH columns and height:auto per photo (see .photo-stream-photos
   img below). Since height:auto derives a photo's rendered height
   from its own aspect ratio at that fixed column width, a vertical
   (portrait) photo came out noticeably TALLER than a horizontal
   (landscape) photo sitting right next to it at the same width —
   the row read as uneven/choppy instead of lining up.

   Switched to a flexbox "justified row" instead: every photo in the
   group is scaled to one shared HEIGHT (--photo-row-h) and its WIDTH
   is left free to follow that photo's own aspect ratio (see the
   width:auto/height:100% override on the img two rules down), so a
   tall portrait photo renders narrower and a wide landscape photo
   renders wider, but both sit at the exact same height — which is
   what actually makes them "match" visually. flex-wrap lets the row
   break onto new lines once photos stop fitting, the same way the
   old grid did.

   --photo-row-h defaults to a fluid clamp() so the shared row height
   itself scales down on narrow phones instead of staying fixed and
   forcing lots of narrow strips per row; override sitewide by
   changing the default here, or per-entry with e.g.
   style="--photo-row-h:200px;" on one .photo-stream-photos-multi. */
.photo-stream-photos-multi{
  display:flex;
  flex-wrap:wrap;
  align-items:flex-start;
  gap:10px;
  --photo-row-h: clamp(140px, 22vw, 260px);
}

/* height:auto explicitly overrides this site's global "img, video{
   height:100% }" rule (declared near the top of this file, meant for
   images inside fixed-height boxes like .reel-media). .photo-stream-item
   has no explicit height of its own, so that inherited height:100%
   had nothing real to resolve against — depending on the browser,
   that can make the image collapse to zero height in the page's
   actual layout flow while still painting at its full, aspect-ratio
   size, which is what was letting content further down the page (like
   the "Back to All Work" link) render as if it were sitting on top of
   the photo instead of below it. Setting height:auto here makes the
   image reserve real, correct space in the flow on every browser, so
   nothing after it can overlap.

   No fixed aspect-ratio and no object-fit:cover here on purpose — a
   fixed box + cover crops whatever doesn't match that shape, which
   hides part of the photo. width:100% + height:auto (with no aspect-
   ratio forcing a particular box) shows the image at its own true
   proportions, scaled to fit the column, at every screen size — a
   portrait photo stays tall, a landscape photo stays wide, and
   nothing is ever cropped out of frame. A grouped, multi-photo entry
   still gets height:auto per image — .photo-stream-photos-multi's grid
   columns are what constrain their width, same as this stylesheet's
   other auto-fit grids.

   Every photo is wrapped in a .photo-stream-photo-btn (a real <button>,
   so it's clickable AND keyboard-reachable — see photostream.html's
   script) rather than the <img> sitting directly in .photo-stream-photos,
   so the spacing/last-child rules below target the button, not the img
   itself. */
.photo-stream-photo-btn{
  display:block;
  width:100%;
  margin:0 0 10px;
  border:0;
  background:none;
  padding:0;
  text-align:left;
  cursor:pointer;
  transition:opacity .2s ease;
}
/* Single-photo entries get a small bottom margin removed by :last-child
   so nothing changes from before; a multi-photo entry's grid gap
   (above, on .photo-stream-photos-multi) handles spacing instead, so
   its buttons get no margin of their own. */
.photo-stream-photo-btn:last-child{
  margin-bottom:0;
}
.photo-stream-photos-multi .photo-stream-photo-btn{
  margin-bottom:0;
  /* Overrides the plain .photo-stream-photo-btn rule above (width:100%,
     sized for a single full-width photo) — inside a group, each button
     is only as wide as its own photo ends up being at the shared row
     height (--photo-row-h, set on .photo-stream-photos-multi above),
     not the full column width. max-width:100% is a safety cap so one
     unusually wide/panoramic photo can't push past the row and force
     horizontal scrolling if it doesn't wrap in time. */
  width:auto;
  max-width:100%;
  height:var(--photo-row-h);
  flex:0 0 auto;
}
.photo-stream-photo-btn:hover,
.photo-stream-photo-btn:focus-visible{
  opacity:.82;
}
.photo-stream-photos img{
  width:100%;
  height:auto;
  display:block;
}
/* Higher specificity than .photo-stream-photos img above (two classes
   deep instead of one), so this wins for grouped photos only: each
   image fills its button's fixed height (100%) with width left auto
   to follow the photo's own aspect ratio — this is what actually
   produces the matching row height between portrait and landscape
   photos, per the comment on .photo-stream-photos-multi above.
   object-fit:cover crops only as much as max-width:100% ever forces
   (an oversize panorama hitting the row's width cap), same crop
   behavior already used sitewide for other fixed-box media. */
.photo-stream-photos-multi .photo-stream-photo-btn img{
  width:auto;
  max-width:100%;
  height:100%;
  object-fit:cover;
}

/* "See more photos >" link at the end of an entry, once a unique URL
   is dropped into that entry's "link" field (see the TO ADD A NEW
   PHOTO comment and PHOTOS array in photostream.html) — reuses the
   exact small mono/uppercase treatment .video-link uses elsewhere on
   the site for the same kind of "go see the original post" link,
   rather than inventing a new link style just for this page. Entries
   with no "link" set simply skip this element entirely (see the
   `if (photo.link)` check in photostream.html's script), so nothing
   changes for them. */
.photo-stream-more-link{
  display:inline-flex; align-items:center; gap:6px;
  font-family:var(--mono); font-size:11px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--smoke);
  opacity:.85;
  transition:opacity .2s ease, color .2s ease;
  margin-top:12px;
}
.photo-stream-more-link:hover, .photo-stream-more-link:focus-visible{
  opacity:1; color:var(--paper);
}

/* Prev/next + back-to-home bar under the gallery, above the shared footer */
.project-footer-nav{
  max-width:1400px; margin:0 auto;
  padding:32px clamp(20px,4vw,48px) 0;
  display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:16px;
  border-top:1px solid var(--rule);
  font-family:var(--mono); font-size:12px; letter-spacing:.06em; text-transform:uppercase;
}
.project-footer-nav a{ opacity:.8; transition:opacity .2s ease; }
.project-footer-nav a:hover, .project-footer-nav a:focus-visible{ opacity:1; }

/* Row of embedded social posts below a project hero (Instagram/TikTok/
   Facebook's own embed widgets) — used on project pages like
   2d3d-reel.html. Reuses .project-body for the outer padding. */
.social-row-heading{
  font-family:var(--mono); font-size:12px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--smoke);
  /* Space above/below the heading. Override per-instance with e.g.
     style="--heading-pad-top:60px; --heading-pad-bottom:24px;" on the <h2>. */
  margin:var(--heading-pad-top, 0) 0 var(--heading-pad-bottom, 24px);
}
.social-row{
  display:grid;
  /* minmax(300px,1fr) alone forces every column to be AT LEAST 300px
     wide even when the page's own content column is narrower than
     that (small phones — anything under ~340px once the page's side
     padding is subtracted). The grid can't shrink a track below its
     minimum, so on those screens the stacked .png would spill past
     the page's right-hand margin instead of staying flush with it —
     it looked "off-center" because it wasn't respecting the same
     edges every other element on the page does. min(300px, 100%)
     lets the minimum give way to the container's own width once the
     container gets narrower than 300px, so the image always sits
     flush with the page's normal left/right edges, at every width. */
  grid-template-columns:repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap:24px;
  align-items:start;
}
/* A plain static image dropped into a .social-row column (e.g. two
   branding/promo .pngs side by side) instead of a .video-embed. Just
   scales to the column's full width at the image's own natural aspect
   ratio — nothing is cropped — so logos, flyers, or any other .png
   shape all work without needing a fixed aspect-ratio box. */
.social-image{
  display:block;
  width:100%;
  height:auto;
}

/* Modifier for about.html's bio photo specifically (paired with
   .social-image above for the actual image scaling) — lets you dial
   in how much the paragraph above it overlaps the photo's top edge,
   instead of a fixed plain gap. --photo-gap is a literal margin-top
   value: positive = a gap; negative = overlap, pulling the photo up
   under the paragraph's bottom edge.

   z-index:-1 is what actually makes the overlap read as "text over
   photo" rather than the reverse: without it, the photo — coming
   later in the HTML than the paragraph above it — would simply paint
   on top of that paragraph's text wherever the negative --photo-gap
   makes them overlap, hiding it. Pushing the photo itself behind
   instead means ANY text near it (this or any other paragraph) stays
   readable automatically, with no extra positioning needed on the
   text side. */
.about-photo{
  position:relative;
  z-index:-1;
  margin:var(--photo-gap, 8px) 0 32px;
}
/* --photo-gap is set per breakpoint below rather than once inline on
   the <img>, because a fixed px overlap doesn't hold up across screen
   sizes here: .social-image scales the photo to 100% of the content
   column's width with height:auto, so the photo itself gets a LOT
   taller at wider viewports (a phone-width photo vs. a 1400px-wide
   laptop one) while the bio text's own height barely changes (it's
   capped at max-width:70ch). The same fixed overlap that looks right
   on a short mobile photo barely registers against a much taller
   laptop one — and was found to be too much at the narrowest "Mobile
   S" width and too little from tablet up. (Inline style would also
   have blocked these media queries outright — inline declarations
   always beat external stylesheet rules regardless of specificity, so
   the per-instance override on the <img> itself has been removed;
   this is now the only place --photo-gap is set.)

   TO ADJUST ANY ONE SIZE: change that range's px value below — the
   four ranges don't overlap, so each is independent of the others. */
@media (max-width:374px){
  /* Mobile S (~320px) — was overlapping into the subject's head at
     the old flat -80px, so pulled back to less overlap. */
  .about-photo{ --photo-gap:-40px; }
}
@media (min-width:375px) and (max-width:640px){
  /* Mobile M/L (~375-640px) — the -80px that was tuned and confirmed
     "perfect" at this size. */
  .about-photo{ --photo-gap:-80px; }
}
@media (min-width:641px) and (max-width:1024px){
  /* Tablet — same breakpoint range used for .project-hero's own
     tablet tier above. Bigger overlap to keep pace with the taller
     photo at this width. */
  .about-photo{ --photo-gap:-90px; }
}
@media (min-width:1025px){
  /* Laptop, Laptop L, and desktop beyond — same lower bound as
     .project-hero's laptop tier above. Biggest overlap, for the same
     reason: the photo is at its tallest here. */
  .about-photo{ --photo-gap:-210px; }
}
.social-embed{
  display:flex; justify-content:center;
  min-height:400px;
}

/* Modifier for .social-row when a page has more than one video row
   that should share the exact same column width (e.g. a 3-up square
   row and a 2-up vertical row below it) — a fixed 3-track grid
   instead of .social-row's normal auto-fit, so a row's column width
   isn't driven by how many items are in THAT particular row. A row
   with fewer than 3 items just leaves trailing columns empty rather
   than stretching to fill. Collapses to one column on narrow screens,
   same as .social-row's own auto-fit behavior did. */
.video-grid-3up{
  grid-template-columns:repeat(3, minmax(0,1fr));
}
@media (max-width:900px){
  .video-grid-3up{ grid-template-columns:1fr; }
}

/* Modifier for a .social-row holding a single lone item that should
   still sit at the same column width as a normal 2-up row above/below
   it (e.g. an odd fifth image after two paired rows) — a fixed
   2-track grid instead of .social-row's normal auto-fit, so the lone
   item doesn't stretch to fill the full row width; the second column
   is simply left empty. Collapses to one column at the same ~624px
   breakpoint .social-row's own auto-fit already collapses at, so it
   keeps matching the paired rows above it at every screen size. */
.social-row-2up{
  grid-template-columns:repeat(2, minmax(0,1fr));
}
@media (max-width:624px){
  .social-row-2up{ grid-template-columns:1fr; }
}

/* --------------------------- VENUE ROW ---------------------------
   Photo + video side by side (used for pickleballevent.html's "Venue"
   section, right below the Branding/Flyer row) — each cell is FORCED
   to a true 16:9 frame via aspect-ratio, rather than the row's earlier
   drafts, which either left the photo at its own native ratio (so it
   didn't match the video's height) or cropped both to fill an
   arbitrary shared height (which kept them matched but didn't hold
   either one to an actual 16:9 shape).

   .venue-row is a plain 2-column grid (1fr 1fr — adjust the ratio
   here, e.g. 3fr 2fr, for a wider photo or video) with NO explicit
   row height at all: since both columns are the same WIDTH and both
   children share the same aspect-ratio:16/9, they land on the exact
   same HEIGHT automatically (same ratio + same width = same height) —
   no shared-height variable, no cover-crop math, no per-breakpoint
   height override needed the way the row's earlier drafts required.
     - .venue-photo: aspect-ratio:16/9 + object-fit:cover crops only
       what a real photo's own ratio doesn't already fit into 16:9 —
       same crop-to-fill treatment used sitewide for other fixed-ratio
       photos (.reel-media img, .project-gallery img, etc.).
     - .venue-video-embed: aspect-ratio:16/9 on the wrapper, iframe
       filling it via inset:0/100% — since the box IS 16:9 (matching a
       standard Vimeo embed's own native ratio), the video needs no
       cropping or oversizing trick to fill it cleanly, unlike the
       arbitrary-ratio box an earlier draft of this row used.
   Because aspect-ratio holds at ANY width, this also needs no special
   handling once the row collapses to a single column below — each
   cell simply becomes 100% of the (narrower) column width and its
   height follows the same 16:9 ratio down with it. */
.venue-row{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:24px;
  margin-top:16px;
}
.venue-photo{
  display:block;
  width:100%;
  aspect-ratio:16/9;
  object-fit:cover;
}
.venue-video-embed{
  position:relative;
  width:100%;
  aspect-ratio:16/9;
  overflow:hidden;
  background:var(--rule);
}
.venue-video-embed iframe{
  position:absolute; inset:0;
  width:100%; height:100%;
  border:0;
}
/* Below 640px (this site's usual mobile breakpoint), the two cells
   stack into a single column instead of squeezing both into narrow
   side-by-side slivers — each still holds its own 16:9 shape, just at
   the column's full (now un-halved) width. */
@media (max-width:640px){
  .venue-row{
    grid-template-columns:1fr;
    gap:16px;
  }
}

/* --------------------------- LIGHTBOX ---------------------------
   Full-size photo viewer, opened by clicking (or Enter/Space-ing) any
   photo built by photostream.html's PHOTOS array. Static markup at
   the bottom of the page's HTML, hidden by default; the script there
   toggles .is-open. Sits above everything else — the fixed nav is
   z-index:50 and the corner scroll cue is z-index:30 — since it needs
   to cover the whole viewport including both of those. */
.lightbox{
  position:fixed; inset:0; z-index:200;
  display:flex; align-items:center; justify-content:center;
  opacity:0; visibility:hidden;
  transition:opacity .25s ease, visibility 0s linear .25s;
}
.lightbox.is-open{
  opacity:1; visibility:visible;
  transition:opacity .25s ease;
}
.lightbox-backdrop{
  position:absolute; inset:0;
  background:rgba(11,11,12,.94);
}
.lightbox-figure{
  position:relative; z-index:1;
  margin:0;
  max-width:92vw;
  display:flex; flex-direction:column; align-items:center;
  gap:14px;
  /* Keeps the close/prev/next buttons (positioned relative to the
     .lightbox viewport, not this figure) clear of a tall image. */
  padding:0 clamp(56px,8vw,96px);
}
.lightbox-img{
  display:block;
  max-width:100%;
  max-height:78vh;
  width:auto; height:auto;
  object-fit:contain;
  box-shadow:0 20px 60px rgba(0,0,0,.5);
}
/* Figcaption wraps both the date and the caption text as one block,
   stacked the same top-to-bottom order (date, then caption) as each
   entry on the page itself — see .photo-stream-date/.photo-stream-caption
   above, which this reuses the look of rather than inventing new type
   styles just for the lightbox. */
.lightbox-caption{
  display:flex; flex-direction:column; align-items:center; gap:6px;
  max-width:70ch; text-align:center;
}
.lightbox-date{
  display:block;
  font-family:var(--mono); font-size:11px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--smoke);
}
.lightbox-caption-text{
  font-family:var(--body); font-size:14px; line-height:1.5;
  color:var(--paper); opacity:.85;
}
.lightbox-close, .lightbox-prev, .lightbox-next{
  position:absolute; z-index:2;
  border:1px solid var(--rule); background:var(--blackout); color:var(--paper);
  font-family:var(--mono); line-height:1;
  display:flex; align-items:center; justify-content:center;
  cursor:pointer;
  transition:background-color .2s ease, color .2s ease, border-color .2s ease;
}
.lightbox-close:hover, .lightbox-close:focus-visible,
.lightbox-prev:hover, .lightbox-prev:focus-visible,
.lightbox-next:hover, .lightbox-next:focus-visible{
  background:var(--tape); color:var(--blackout); border-color:var(--tape);
}
.lightbox-close{
  top:clamp(16px,3vw,32px); right:clamp(16px,3vw,32px);
  width:40px; height:40px; font-size:22px;
}
.lightbox-prev, .lightbox-next{
  top:50%; transform:translateY(-50%);
  width:48px; height:48px; font-size:26px;
}
.lightbox-prev{ left:clamp(8px,2vw,24px); }
.lightbox-next{ right:clamp(8px,2vw,24px); }
/* Hidden entirely (JS adds this on open) when the page only has one
   photo total, rather than leaving Prev/Next arrows that go nowhere. */
.lightbox.lightbox-single .lightbox-prev,
.lightbox.lightbox-single .lightbox-next{
  display:none;
}
@media (max-width:640px){
  .lightbox-prev, .lightbox-next{ width:36px; height:36px; font-size:20px; }
  .lightbox-figure{ padding:0 clamp(44px,12vw,64px); }
}
/* Set on <body> while the lightbox is open, so the page underneath
   can't scroll behind the overlay. */
.lightbox-lock{
  overflow:hidden;
}

.skip-link{
  position:absolute; left:-999px; top:0; background:var(--tape); color:var(--blackout);
  padding:10px 16px; z-index:100; font-family:var(--mono); font-size:12px;
}
.skip-link:focus{ left:20px; }

:focus-visible{ outline:2px solid var(--tape); outline-offset:3px; }
