/* =========================================================================
   NOBP 504 — the living hero, on the home page
   -------------------------------------------------------------------------
   Everything here is scoped to `.hero--live`, which sits alongside `.hero` on
   the same element. nobp.css already styles `.hero`, `.hero-inner` and
   `.hero-logo` for the old static hero, and those rules are still needed by
   the other pages — so rather than editing them, this file overrides them for
   the one element that opts in. `.hero.hero--live` outranks `.hero`, so the
   overrides land without a single !important.

   Tokens (--gold, --cream, --font-display, --bass) all come from nobp.css.
   Nothing is redefined here that already exists there; a second definition is
   a second thing to keep in sync.
   ========================================================================= */

.hero.hero--live {
  position: relative;
  /* nobp.css gives .hero a min-height plus 170–190px of bottom padding to
     clear the old dock. The living hero clears the dock differently — the
     dock is a separate section below it now — so the height is stated
     outright and the padding is taken back. */
  min-height: 0;

  /* TWO DIFFERENT HEIGHTS, AND THE DIFFERENCE IS THE POINT.

     --hero-visible is the screenful: everything above the fold. `height` is
     the whole picture, which is now DELIBERATELY TALLER than that. Because
     the backdrop is object-fit:cover, a taller box shows more of the
     photograph's vertical range rather than blowing it up — so the street,
     the feet and the foreground come back into frame instead of being
     cropped away at the bottom. That extra strip lives below the fold and is
     revealed as you scroll, which is what makes the hero feel like a scene
     you are standing in rather than a banner.

     1.2 is as far as this can go before the reveal starts feeling like a
     second page. */
  --hero-visible: calc(100vh - var(--header-h));

  /* THE FRAME IS THE SHAPE OF THE PICTURE. 1672x941 — the artwork's own
     dimensions.

     This matters more than any height value, because the backdrop is
     object-fit:cover: the image scales until it covers the box, and whatever
     does not fit is cut off. Give it a box SHAPED differently from the
     picture and it crops — and it always crops the dimension the box is
     short of.

     The previous frame was 1440x989, a ratio of 1.46 against the picture's
     1.78. Cover scaled the image to fill that height, which made it 1757px
     wide inside a 1440px box, and quietly sliced 158px off each side — taking
     the DJ on his porch at the left edge and the dancer at the right with it.
     Making the hero TALLER, which is what I did to show "more picture", is
     precisely what widened that cut.

     Matching the ratio means cover has nothing left to remove: the whole
     photograph is on screen, edge to edge, every element in frame. */
  /* Width pinned explicitly. With `aspect-ratio` alone, a min-height that
     bites is free to solve the ratio the other way and widen the element
     past the viewport — which is exactly what put a horizontal scrollbar on
     a sideways phone. Fixing the width leaves height as the only thing the
     ratio can move. */
  width: 100%;
  aspect-ratio: 1672 / 941;
  height: auto;

  /* Guards, not the main event. The floor keeps a narrow desktop window from
     squeezing the hero below the height its content needs; the ceiling keeps
     a very wide monitor from pushing the button down out of reach. Both
     reintroduce a little cropping when they bite, which is the correct
     trade — an uncropped hero nobody can use is worse. */
  min-height: 540px;
  max-height: calc(var(--hero-visible) * 1.15);
  /* BOTH paddings, not just the bottom one.
     nobp.css gives .hero `padding-top: calc(header + 34px)` — 110px here — to
     clear the sticky header on the old hero. That padding shrinks the content
     box the inner is measured against AND pushes its origin down, so
     `--bounce-center: 68%` was landing at 72.3% of the hero: the percentage
     was of a box 110px shorter, offset 110px lower. The knob has to be a
     percentage of the frame the visitor sees, or it means nothing.

     The header does not need clearing here anyway — this hero's content is
     placed well below it, and the lock-up sets its own top. */
  padding-top: 0;
  padding-bottom: 0;
  overflow: hidden;
  background: #0a0a0a;
  isolation: isolate;
  display: block;
}

/* The backdrop mounts into this. Three tiers — video, sky-drift, flat — all
   the same artwork; backdrop.js picks by what the device will actually do. */
.hero--live .hero-stage {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden; background: #0a0a0a;
}
.hero--live .hero-stage img,
.hero--live .hero-stage video {
  pointer-events: none; user-select: none; transform-origin: center;
}

/* nobp.css paints its own ::before/::after washes over .hero for the static
   artwork. They are tuned for a different picture and stack with the ones
   below, so they are switched off for this variant. */
.hero.hero--live::before { content: none; }

/* Legibility wash, kept off the middle of the frame so the party stays
   bright. This is the lightened pass — the earlier values drained the
   daylight out of a sunny block party and read as gloomy. */
.hero.hero--live::after {
  content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    linear-gradient(to bottom, rgba(5,5,5,0.58) 0%, rgba(5,5,5,0.22) 16%,
                    rgba(5,5,5,0.07) 30%, rgba(5,5,5,0) 42%),
    linear-gradient(to top, rgba(5,5,5,0.78) 0%, rgba(5,5,5,0.18) 24%, rgba(5,5,5,0) 44%);
}

/* ═══ THE ONE KNOB ═══════════════════════════════════════════════════════
   --bounce-center is where the middle of the Bounce Button sits, measured
   down the hero. Everything else in the hero is arranged around it, so this
   is the single number to change when the button needs to move.

   80% sits it level with the knees of the front row — low in the frame, a
   little clear of the bottom edge. It covers the weak stretch of generated
   crowd behind them without the call to action riding over anybody's face,
   which is what the higher value did. Raise the number and the flaw
   reappears below it; lower it much further and the button meets the dock.

   Tuned against the landscape artwork; the portrait crop needs its own value
   and gets one in the phone block further down.
   ═══════════════════════════════════════════════════════════════════════ */
.hero--live { --bounce-center: 0.808; }

/* THE BUTTON'S SIZE LIVES ON :root, NOT ON THE HERO.
   Two elements outside the hero need to read it: the stack, to seat the
   button on the picture's bottom edge, and the player dock BELOW the hero,
   which has to reserve room for the part that overhangs. The dock is the
   hero's sibling, so a variable declared on .hero--live never reaches it —
   the calc silently fell back and the dock landed on top of the button. */
:root { --bounce-size: clamp(140px, 17vw, 240px); }

.hero--live .hero-inner {
  /* Block, not flex. The two groups are placed against the frame rather than
     flowed one after the other, because the button answers to a position in
     the photograph and the lock-up answers to the top of the screen. */
  position: relative; z-index: 3;
  display: block;
  width: 100%; height: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding: 0 var(--gutter);
  text-align: center;
}

.hero--live .hero-lockup {
  position: absolute;
  left: var(--gutter); right: var(--gutter);
  /* Clears the sticky header itself now that the hero no longer pads for it,
     plus a little air. Sitting higher also opens the gap between the headline
     and the call to action, which were reading as one four-line block. */
  top: calc(var(--header-h) + clamp(6px, 1.5vh, 22px));
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(8px, 1.2vh, 16px);
}

/* Centred on the BUTTON, not on the button-plus-its-caption.
   The caption is lifted out of flow (bottom: 100% below), so this wrapper is
   exactly as tall as the button itself — which makes translateY(-50%) put the
   button's own middle on --bounce-center. Leave the caption in flow and the
   group centres instead, quietly dropping the button by half the caption's
   height and making the knob lie about where it points. */
.hero--live .hero-bounce-stack {
  position: absolute;
  left: 50%;
  /* MEASURED DOWN THE SCREENFUL, NOT DOWN THE PICTURE.
     Now that the picture is taller than the screen, a percentage of the
     picture would drop the button below the fold — the one place it must
     never be, since it is the only control on the page. Measuring down
     --hero-visible instead pins it near the bottom of what the visitor can
     actually see, whatever their screen, while the picture keeps running on
     underneath it. */
  /* Measured down whichever is SHORTER: the screenful, or the picture itself.
     Those come apart in both directions — a tall monitor makes the picture
     shorter than the screen, a small laptop makes it taller — and the button
     has to stay inside both. Against the screenful alone it can fall below
     the fold; against the picture alone it can slide off the bottom of the
     artwork onto the section beneath. min() of the two is the only value that
     is always in frame and always in view. */
  /* KNEE HEIGHT OF THE TWO CROUCHING MEN, MEASURED OFF THE ARTWORK.

     In the new photograph the man in black at the left and the man in the
     white tee at the right are both dropped into a low crouch, knees almost
     on the ground, leaning in toward the middle of the frame. Their knees sit
     at y≈760 of the picture's 941 — 80.8% down. Putting the button there, dead
     centre, lands it exactly between them at the height they are bent toward,
     so the two of them read as crouching around it rather than beside it.

     This is why the knob is a fraction of the PICTURE and not of the screen:
     it is pinned to something in the photograph, so it stays on their knees
     at every window size. The second term is the guard that keeps the
     button's own bottom edge inside the frame on a short window. */
  top: min(
    calc(min(var(--hero-visible), 100%) * var(--bounce-center)),
    calc(min(var(--hero-visible), 100%) - var(--bounce-size) / 2 - 14px)
  );
  transform: translate(-50%, -50%);
  width: max-content; max-width: calc(100% - var(--gutter) * 2);
  display: flex; flex-direction: column; align-items: center;
}
/* THE HALO.
   A soft pool of black behind the button, sized to it, fading out well before
   its edge. Its job is to quiet the crowd immediately around the button so
   the gold reads cleanly and the busiest part of the generated background
   stops competing.

   Deliberately on the STACK, not on .bounce: the button is running the idle
   bounce animation, and a shadow that hops along with it reads as a sticker
   sliding around rather than an object sitting in a scene. Anchored here it
   stays put while the button moves against it.

   closest-side keeps the gradient circular in a non-square box, and the stops
   are weighted to die out early — this is a local pool, not the full-frame
   scrim that made the hero look gloomy the first time round. */
.hero--live .hero-bounce-stack::before {
  content: '';
  position: absolute; z-index: -1; pointer-events: none;
  left: 50%; top: 50%;
  width: 250%; height: 250%;
  transform: translate(-50%, -50%);
  /* Dialled back per Tee: enough to lift the gold off the crowd, not enough
     to read as a dark disc painted on the photograph. */
  background: radial-gradient(closest-side,
    rgba(5, 5, 5, 0.44) 0%,
    rgba(5, 5, 5, 0.30) 34%,
    rgba(5, 5, 5, 0.13) 56%,
    rgba(5, 5, 5, 0.04) 76%,
    rgba(5, 5, 5, 0) 100%);
}

.hero--live .hero-bounce-text {
  position: absolute;
  bottom: 100%;
  left: 50%; transform: translateX(-50%);
  width: max-content; max-width: 92vw;
  margin-bottom: clamp(8px, 1.4vh, 16px);
}
/* The margin that used to separate it from the headline above is gone now
   that it is positioned rather than flowed. */
.hero--live .hero-bounce-stack .bounce { margin-top: 0; }

/* THE DOCK STOPS OVERLAPPING THIS HERO.
   nobp.css pulls .player-dock up by -110px (-128px on wide screens) so it
   straddles the seam under the old hero. That hero is left-aligned with
   nothing in its lower middle, so the overlap costs it nothing.

   This one is centre-stacked and bottom-heavy — logo, headline, button, call
   to action — and the call to action is the last thing in the column, which
   put it exactly where the dock lands. Reserving padding for the overlap was
   the obvious fix and it was the wrong one: it takes the space out of the
   same box the content has to fit in, so on a 1440x700 laptop the content
   simply outgrew what was left and the cue line went under the dock anyway.

   Not overlapping removes the conflict instead of budgeting around it. The
   dock reads as its own band under the hero, which suits a page whose hero
   already has a play control in it — the two no longer visually compete for
   the same seam. */
.hero--live + .player-dock { margin-top: 20px; }

/* A whisper of separation behind the lock-up, not a scrim. */
.hero--live .hero-inner::before {
  content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(ellipse 44% 50% at 50% 47%,
    rgba(5,5,5,0.30) 0%, rgba(5,5,5,0.20) 40%,
    rgba(5,5,5,0.08) 66%, rgba(5,5,5,0) 84%);
}

/* nobp.css anchors the home-page logo to the left, for a left-aligned hero.
   This one is centred, so the origin has to move with it or the bass pulse
   pushes the artwork sideways instead of breathing in place. */
.hero--live .hero-logo {
  display: block;
  /* A touch larger than before (34vw/510 -> 37vw/560). The lock-up now has
     the top of the frame to itself, so it can take the room. */
  width: clamp(260px, 37vw, 560px);
  height: auto;
  margin: 0 auto;
  max-width: 100%;
  transform-origin: center;
  transform: scale(calc(1 + var(--bass) * 0.014));
  filter: drop-shadow(0 3px 10px rgba(0, 0, 0, 0.7));
}

.hero--live .hero-title {
  margin: 0;
  font-family: var(--font-display); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; line-height: 1.08;
  font-size: clamp(17px, 2.5vw, 30px);
  color: var(--cream);
  text-shadow: 0 1px 2px rgba(0,0,0,0.92), 0 2px 12px rgba(0,0,0,0.7);
  text-wrap: balance;
}
.hero--live .hero-title .gold {
  display: block; font-size: 0.66em;
  color: #d8cfbd; letter-spacing: 0.09em;
}

/* ── the bounce button ──────────────────────────────────────────────────── */
.bounce {
  /* Nudged up from 15vw/210 — bigger covers more of the background, which
     is the point, without becoming the only thing on the page. */
  --size: var(--bounce-size);
  margin-top: clamp(8px, 2.2vh, 26px);
  position: relative;
  width: var(--size); height: var(--size);
  border: 0; padding: 0; background: transparent;
  border-radius: 50%;
  touch-action: manipulation;
  transform-origin: 50% 100%;   /* it squashes against the ground, not its middle */
  transform: scale(calc(1 + var(--bass) * 0.018));
  transition: transform 160ms cubic-bezier(.2, .8, .2, 1), filter 160ms ease;
}
.bounce img { width: 100%; height: 100%; display: block; }
.bounce [data-bounce-rings],
.bounce [data-bounce-arc] { position: absolute; inset: 0; pointer-events: none; }

.bounce:hover { transform: translateY(-4px) scale(1.015); filter: brightness(1.05); }
.bounce[data-pressing] { transform: translateY(6px) scaleY(.94) scaleX(.99); }
.bounce[data-state="playing"] {
  filter: drop-shadow(0 0 calc(18px + var(--bass) * 26px)
                      rgba(244, 202, 98, calc(0.32 + var(--bass) * 0.33)));
}
/* Muted, not dead. The button stays pressable when the station cannot be
   reached, so it must not look like a disabled control — and it returns to
   full colour by itself a few seconds later. */
.bounce[data-state="off-air"] { filter: saturate(.55) brightness(.72); }
.bounce:focus-visible { outline: 3px solid var(--gold-bright); outline-offset: 10px; }

/* The Bounce Button bounces — motion that says "press me" because it is
   already doing the thing pressing it would do. Crouch, launch, land, settle;
   the squash is what makes it read as weight rather than a sliding sticker.

   It owns `transform` outright while idle, which is only safe because the
   bass is zero at rest — the station is not playing, by definition. The
   moment it plays, the animation stops and the bass scale takes the wheel. */
.bounce[data-state="rest"] {
  animation: bounce-cta 2.6s cubic-bezier(.3, .7, .4, 1) infinite;
}
.bounce:hover, .bounce[data-pressing] { animation: none; }
@keyframes bounce-cta {
  0%, 58%, 100% { transform: translateY(0) scale(1, 1); }
  64%  { transform: translateY(2%)   scale(1.05, 0.93); }   /* crouch */
  73%  { transform: translateY(-17%) scale(0.97, 1.05); }   /* launch */
  82%  { transform: translateY(0)    scale(1.06, 0.92); }   /* land */
  88%  { transform: translateY(-5%)  scale(0.99, 1.02); }   /* small rebound */
  94%  { transform: translateY(0)    scale(1.02, 0.98); }   /* settle */
}

[data-bounce-rings] { animation: ring-breathe 1.15s ease-in-out infinite; }
[data-bounce-arc] { animation: arc-spin .9s linear infinite; transform-origin: 50% 50%; }
@keyframes ring-breathe {
  50% { transform: scale(calc(1.012 + var(--bass) * 0.025)); opacity: .75; }
}
@keyframes arc-spin { to { transform: rotate(360deg); } }

.bounce-name {
  margin: 0;
  font-family: var(--font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.16em;
  font-size: clamp(15px, 1.9vw, 23px);
  color: var(--gold-bright);
  /* A four-way hard halo, not a soft glow: the button sits over a crowd, and
     a soft shadow alone lets gold text disappear into gold trim behind it. */
  text-shadow:
    0 1px 1px rgba(0,0,0,0.95), 0 -1px 1px rgba(0,0,0,0.85),
    1px 0 1px rgba(0,0,0,0.85), -1px 0 1px rgba(0,0,0,0.85),
    0 2px 10px rgba(0,0,0,0.75);
}
/* The coined name is for the campaign; this line is what tells a first-time
   visitor what the object actually does. */
.bounce-cue {
  margin: 2px 0 0;
  font-family: var(--font-ui); font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.22em;
  font-size: clamp(10px, 1.05vw, 12px);
  color: #ded5c4;
  text-shadow: 0 1px 1px rgba(0,0,0,0.95), 0 2px 9px rgba(0,0,0,0.8);
}

@media (max-width: 767px) {
  :root { --bounce-size: clamp(128px, 40vw, 190px); }


  /* The portrait crop is a different photograph: the front row sits lower in
     a 9:16 frame than in the wide one, so the desktop value would land too
     high. 82% keeps the whole group — button and the HIT THE BOUNCE BUTTON
     line above it — below the faces of the front row, which is the thing
     that made the earlier position look wrong on a phone. */
  .hero--live { --bounce-center: 0.89; }

  /* Bigger and higher on a phone, which is what "up in the air" asks for —
     the lock-up rides near the top of the frame with the sky behind it
     instead of sitting in the canopy.

     It still has to clear the sticky header. Written as a bare 10px this
     tucked the top of NEW ORLEANS underneath the header and sliced the
     lettering off — higher is only better until it is behind something. */
  .hero--live .hero-logo { width: clamp(280px, 76vw, 460px); }
  .hero--live .hero-lockup {
    top: calc(var(--header-h) + clamp(2px, 0.8vh, 12px));
  }
}

/* THE SHAPE FOLLOWS THE ORIENTATION, NOT THE WIDTH.
   backdrop.js chooses between the wide artwork and the tall one with
   `(orientation: portrait)`. This has to ask the same question, or the two
   disagree: an iPad held upright is 834px wide, which is past the 767px phone
   breakpoint, so the CSS was framing the tall photograph in the wide
   picture's shape and throwing away 1434px of it.

   Keying both off orientation means the frame is always the shape of the
   picture that is actually in it. */
@media (orientation: portrait) {
  .hero.hero--live {
    aspect-ratio: 909 / 1730;
    min-height: 480px;
    /* Tighter than the landscape ceiling. A 9:17 picture at tablet width is
       far taller than any screen, so this always bites — and letting it run
       past the fold would bury the button rather than reveal anything. */
    max-height: var(--hero-visible);
  }
}

/* SHORT SCREENS SIZE BY HEIGHT, NOT WIDTH.
   The lock-up and the button were both sized off viewport WIDTH, which is
   the wrong axis for the thing that actually runs out. On a 1440x700 laptop
   the hero is 624px tall, the dock eats 128 of that, and the content came to
   565px in a 453px box — so it overflowed by a clear 112px and the call to
   action disappeared behind the dock. Widening the padding could not fix
   that; there was no room to give.

   Capping both against vh is what makes it fit: the two tall elements give
   up height exactly when height is what is scarce. The width clamps still
   win on any normal screen, so a 900px-tall display renders identically to
   before — this only engages where it has to. */
@media (max-height: 860px) {
  /* Matches the new base size (37vw/560), not the old one it was written
     against — otherwise a short wide screen quietly served the smaller logo
     and the growth only showed on tall displays. */
  .hero--live .hero-logo { width: min(clamp(260px, 37vw, 560px), 42vh); }
  :root { --bounce-size: min(clamp(140px, 17vw, 240px), 26vh); }
}
@media (max-height: 860px) and (max-width: 767px) {
  :root { --bounce-size: min(clamp(128px, 40vw, 190px), 24vh); }
}

/* A PHONE TURNED SIDEWAYS HAS NO ROOM FOR ALL OF IT.
   The screenful there is about 330px tall, and the honest arithmetic does not
   close: logo, headline, caption and a button low in the frame need roughly
   400px of stacked height. Shrinking everything until it fits gives a hero of
   tiny illegible pieces, which is worse than dropping one.

   So the headline goes. The logo already says who this is, and what a visitor
   needs in a sideways phone is the station's name and something to press —
   "Born in the 504" is the line that can wait for them to turn back. */
@media (max-height: 520px) and (orientation: landscape) {
  .hero--live .hero-title { display: none; }
  .hero--live .hero-logo { width: min(clamp(200px, 26vw, 420px), 24vh); }
  :root { --bounce-size: min(clamp(110px, 13vw, 190px), 26vh); }
  .hero--live { --bounce-center: 0.82; }
  .hero--live .hero-lockup { top: calc(var(--header-h) + 2px); gap: 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .bounce, .hero--live .hero-logo { transform: none !important; }
  .bounce[data-state="rest"] { animation: none; }
  [data-bounce-rings], [data-bounce-arc] { animation: none; }
}
