/* ================================================================= *
 |  SHARED AUDIO PLAYER  —  .fr-audio  (Phase 2, 2026-07-26)
 |
 |  The site's one audio control. Built for /radio-ads (five 2008 radio spots)
 |  and extracted here when /scavenger-hunt needed the same thing for the Broyles
 |  phone message — the payoff of the entire hunt, and until then a bare
 |  <audio controls> concatenated into a description paragraph.
 |
 |  Markup: resources/views/partials/audio-player.blade.php
 |  Behaviour: public/js/fringe-audio.js
 |  Data: App\Support\AudioClip (duration + precomputed waveform peaks)
 |
 |  Scoped to the COMPONENT (.fr-audio), not to a page skin, so it travels. It
 |  does read the Massive Dynamic custom properties (--md-accent, --md-ink,
 |  --md-muted, --md-line, --md-display), so a page using it wants `md-skin` —
 |  every page that currently does. Fallbacks are declared below so it degrades
 |  to something legible rather than transparent-on-transparent if one ever
 |  doesn't.
 |
 |  WHY A CUSTOM PLAYER AT ALL. Native <audio controls> is the single most
 |  unstyled element on the web platform: drawn differently by every browser,
 |  identical between clips, happy to play over each other, and silent about a
 |  clip's length until it has been fetched. What this adds:
 |    - one clip at a time, across the whole page
 |    - a waveform scrubber drawn from precomputed peaks, so a set of clips stops
 |      looking alike before anything is pressed
 |    - a stated duration, up front, with no reflow when metadata lands
 |    - a real keyboard contract on the scrubber, which a div does not get free
 * ================================================================= */

.fr-audio {
    /* the shared player reads these; every value has a fallback so the component
       still renders correctly on a page without the Massive Dynamic skin */
    --fr-audio-accent: var(--md-accent, #0056b7);
    --fr-audio-ink: var(--md-ink, #2a3540);
    --fr-audio-muted: var(--md-muted, #6c757d);
    --fr-audio-rest: #b9cbdc;
}


/* ================================================================= *
 |  NATIVE PLAYER  <->  CUSTOM PLAYER
 |
 |  The native <audio controls> is what the markup ships and what a reader with
 |  no JavaScript gets. `fr-js` is set on <html> by js/spoiler-settings.js, which
 |  is a synchronous <head> script, so this swap happens before first paint: no
 |  flash of native controls, and no flash of a custom UI that isn't wired up yet.
 |
 |  js/fringe-audio.js re-shows the natives if its own init throws
 |  (`.fr-audio-fallback` on <body>).
 * ================================================================= */
.fr-audio__ui { display: none }

.fr-audio__native {
    display: block;
    width: 100%;
    max-width: 30rem;
}

.fr-js .fr-audio__ui { display: grid }
.fr-js .fr-audio__native { display: none }

/* init failed — hand the page back to the browser rather than leave a dead
   control where the audio IS the content. The flag is on <body> so it also
   reaches a "play all" toolbar, which is not inside any one row.
   This works only because fringe-audio.js never removes the `controls`
   attribute: Chrome's UA stylesheet hides `audio:not([controls])`, so a stripped
   player could not be un-hidden here. See the note in the script. */
.fr-js .fr-audio-fallback .fr-audio__ui { display: none }
.fr-js .fr-audio-fallback .fr-audio__native { display: block }
.fr-js .fr-audio-fallback .fr-audio-toolbar { display: none }


/* ================================================================= *
 |  THE ROW
 |
 |  GRID, not flex: the button sits beside a two-row stack (head line, then
 |  waveform) at desktop and beside a ONE-row stack at narrow widths, with the
 |  waveform dropping to a full-bleed row of its own. A grid expresses both with
 |  the same three children and no extra wrapper — the `minmax(0, 1fr)` second
 |  track is what lets the waveform shrink instead of forcing the row wider.
 * ================================================================= */
.fr-audio__ui {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    column-gap: 1rem;
    row-gap: .45rem;
    padding: .85rem .35rem;
    border-radius: 6px;
    transition: background-color .14s ease;
}
.fr-audio.is-playing .fr-audio__ui {
    background: color-mix(in srgb, var(--fr-audio-accent) 6%, transparent);
}

.fr-audio__play { grid-column: 1; grid-row: 1 / span 2 }
.fr-audio__head { grid-column: 2; grid-row: 1 }
.fr-audio__wave { grid-column: 2; grid-row: 2 }


/* ================================================================= *
 |  PLAY BUTTON
 * ================================================================= */
.fr-audio__play {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 46px; height: 46px;
    padding: 0;
    border: 1px solid var(--fr-audio-accent);
    border-radius: 50%;
    background: #fff;
    color: var(--fr-audio-accent);
    font-size: 1rem;
    cursor: pointer;
    transition: background-color .14s ease, color .14s ease, box-shadow .14s ease;
}
.fr-audio__play:hover,
.fr-audio__play:focus-visible {
    background: var(--fr-audio-accent);
    color: #fff;
    box-shadow: 0 2px 4px rgba(42, 53, 64, .16), 0 8px 18px rgba(42, 53, 64, .22);
    outline: none;
}
/* the playing row's button stays filled, so which clip is running is readable
   from the control itself and not only from the moving waveform */
.fr-audio.is-playing .fr-audio__play {
    background: var(--fr-audio-accent);
    color: #fff;
}
/* the pause glyph is two bars and reads narrower than the play triangle; nudge
   it back so the icon doesn't appear to shift inside the circle */
.fr-audio__play .fa-play { margin-left: 2px }

/* PLAY <-> PAUSE. Both glyphs are in the markup and CSS picks one, because the
   script cannot safely touch them: Font Awesome's SVG-with-JS build REPLACES
   each <i> with a new <svg>, so any element the script captured at init is
   detached by the time it would be restyled, and an <svg>'s `className` is a
   read-only SVGAnimatedString besides. FA copies the original classes onto the
   <svg> it emits, so these hooks survive the swap; `inline-block` matches what
   FA's own injected `.svg-inline--fa` rule sets, and out-specifies it either way.

   The state lives on the ROW, not the button: `is-playing` is added/removed by
   the audio element's own `play`/`pause`/`ended` events, so the glyph tracks
   real playback rather than what the last click intended — a clip that ends, or
   that the browser refuses to start, resets the icon on its own.

   This bug shipped once and was invisible in testing: the in-app browser pane
   never runs FA's rAF-driven sweep, so there every <i> stays an <i> and a script
   that rewrote the icon appeared to work. */
.fr-audio__icon--pause { display: none }
.fr-audio.is-playing .fr-audio__icon--play { display: none }
.fr-audio.is-playing .fr-audio__icon--pause { display: inline-block }


/* ================================================================= *
 |  HEAD LINE  —  optional number, name, elapsed / duration
 * ================================================================= */
.fr-audio__head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: .15rem .7rem;
}

.fr-audio__n {
    font-family: var(--md-display, inherit);
    font-size: .68rem;
    font-weight: 600;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--fr-audio-accent);
}

/* Explicit, because a bare heading falls through to the legacy Freelancer rules
   (Montserrat 700, 24-28px) and would out-weigh the 22.4px section header above
   it — the sub-heading inversion fixed across /episode on 2026-07-24. */
.fr-audio__name {
    margin: 0;
    font-family: var(--md-display, inherit);
    font-size: 1.15rem;
    font-weight: 600;
    line-height: 1.3;
    color: var(--fr-audio-ink);
}

/* pushed to the right rail so several elapsed readouts line up down a page */
.fr-audio__time {
    margin-left: auto;
    font-family: var(--md-display, inherit);
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: .06em;
    color: var(--fr-audio-muted);
}
.fr-audio__sep { margin: 0 .3rem; opacity: .6 }
.fr-audio.is-playing .fr-audio__time [data-fr-audio-elapsed] { color: var(--fr-audio-accent) }


/* ================================================================= *
 |  THE WAVEFORM  —  the scrubber, and what makes a set of clips look different
 |
 |  Two identical layers of bars; the played one is clipped from the right by
 |  --fr-audio-pos (0-100%), which js/fringe-audio.js writes on a rAF loop while a
 |  clip is running. `clip-path: inset()` doesn't affect layout, so the two layers
 |  stay in register at every width without measuring anything.
 |
 |  Why not one layer plus a wash over the unplayed part (1 element instead of
 |  128): the ground behind these sections is the skin's sky GRADIENT, fading from
 |  #cfe2f2 to white over the first 1500px, so no flat wash colour is correct down
 |  the whole page. Clipping a duplicate is exact and ground-independent.
 * ================================================================= */
.fr-audio__wave {
    position: relative;
    height: 40px;
    cursor: pointer;
    /* clicking the bars must seek to where you clicked, so the pointer target is
       the whole strip, including the gaps between bars */
    touch-action: pan-y;
}
.fr-audio__wave:focus-visible {
    outline: 2px solid var(--fr-audio-accent);
    outline-offset: 3px;
    border-radius: 3px;
}

.fr-audio__wave-layer {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    gap: 1px;
    pointer-events: none;
}

.fr-audio__wave-layer i {
    flex: 1 1 0;
    min-width: 1px;
    border-radius: 1px;
    background: currentColor;
}

/* unplayed: the skin's own hairline colour, a shade darkened so a 6%-tall bar is
   still visible against white */
.fr-audio__wave-layer--base { color: var(--fr-audio-rest) }

.fr-audio__wave-layer--play {
    color: var(--fr-audio-accent);
    clip-path: inset(0 calc(100% - var(--fr-audio-pos, 0%)) 0 0);
}

/* the head of the played region — a hairline at the playhead, so position is
   readable even where the waveform is quiet and the two colours meet at a 6% bar */
.fr-audio__wave::after {
    content: "";
    position: absolute;
    top: 0; bottom: 0;
    left: var(--fr-audio-pos, 0%);
    width: 1px;
    background: var(--fr-audio-accent);
    opacity: 0;
    transition: opacity .14s ease;
    pointer-events: none;
}
.fr-audio.is-active .fr-audio__wave::after { opacity: .55 }

/* hover previews that clicking seeks: the unplayed bars warm toward the accent */
.fr-audio__wave:hover .fr-audio__wave-layer--base {
    color: color-mix(in srgb, var(--fr-audio-accent) 34%, var(--fr-audio-rest));
}


/* ================================================================= *
 |  TOOLBAR  —  "play all", for a page whose clips are a set
 |
 |  Optional: only /radio-ads renders one. Hidden without JS, where it would be a
 |  button that does nothing.
 * ================================================================= */
.fr-audio-toolbar {
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem 1.1rem;
    margin-bottom: 1.6rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--md-line, #dde3ea);
}
.fr-js .fr-audio-toolbar { display: flex }

.fr-audio-playall {
    display: inline-flex;
    align-items: center;
    gap: .55rem;
    padding: .5rem 1.1rem;
    border: 1px solid var(--md-accent, #0056b7);
    border-radius: 999px;
    background: var(--md-accent, #0056b7);
    color: #fff;
    font-family: var(--md-display, inherit);
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background-color .14s ease, box-shadow .14s ease;
}
.fr-audio-playall:hover,
.fr-audio-playall:focus-visible {
    box-shadow: 0 2px 4px rgba(42, 53, 64, .16), 0 8px 20px rgba(42, 53, 64, .24);
    outline: none;
}
.fr-audio-playall i { font-size: .8em }

.fr-audio-toolbar__note {
    margin: 0;
    font-family: var(--md-display, inherit);
    font-size: .78rem;
    letter-spacing: .06em;
    color: var(--md-muted, #6c757d);
}

/* same CSS-only glyph swap as the row button, for the same Font Awesome reason */
.fr-audio-playall__icon--stop { display: none }
.fr-audio-playall.is-running .fr-audio-playall__icon--play { display: none }
.fr-audio-playall.is-running .fr-audio-playall__icon--stop { display: inline-block }


/* ================================================================= *
 |  NARROW
 |
 |  Below sm the row keeps the play button beside the head line and drops the
 |  waveform onto its own full-width line — at 375px a waveform sharing the row
 |  with a 46px button and a time readout is ~210px, which is both unreadable as a
 |  shape and a poor scrub target.
 * ================================================================= */
@media (max-width: 575.98px) {
    .fr-audio__ui {
        column-gap: .8rem;
        row-gap: .6rem;
        padding: .85rem .1rem;
    }
    .fr-audio__play { grid-row: 1; width: 42px; height: 42px }
    .fr-audio__wave { grid-column: 1 / -1; height: 34px }
    .fr-audio__name { font-size: 1.05rem }
}

@media (prefers-reduced-motion: reduce) {
    .fr-audio__ui,
    .fr-audio__play,
    .fr-audio-playall,
    .fr-audio__wave::after { transition: none }
}
