/**
 * EEH Audio Tour — "Hear this page" (S-AUDIO-TOUR-UI, 2026-10-02)
 *
 * ── WHAT THIS SHEET STYLES ────────────────────────────────────────────────────
 *   1. `.eeh-aov-trigger`    the plain button (player binds the behaviour)
 *   2. `.eeh-aov-fab`        the floating button (printed on wp_footer)
 *   3. button STATES         writing / playing animations (see below)
 *
 * ── ★★ S-BUTTON-ONLY-STATUS (2026-10-06) — THE INLINE PANEL BLOCK WAS REMOVED ──
 * There used to be a `.eeh-aov` inline panel mounted after the hero, carrying
 * the title "♪ Hear this page" and a visible status word ("writing the tour…" /
 * "playing…"). The visitor asked for that LINE to be gone and for the feedback
 * to live ON the button instead. So the whole `.eeh-aov` / `.eeh-aov-head` /
 * `.eeh-aov-title` / `.eeh-aov-icon` / `.eeh-aov-status` block is deleted, and
 * the state ("writing" / "playing" / "error") is now a `data-eeh-aov-state`
 * attribute on every `.eeh-aov-trigger`, styled in section 3.
 *
 * ⚠ DO NOT RE-ADD `.eeh-aov` — nothing mounts it any more (`_mount()` was
 * removed from `eeh-audio-tour.js`). Re-adding the panel is re-adding the line
 * the visitor asked to remove.
 *
 * ── ⚠ IT CARRIES ITS OWN TOKENS AND DEPENDS ON NOTHING ────────────────────────
 * This is a PUBLIC sheet. It is never enqueued behind the admin cascade
 * (`.clinerules` §2), and it must NOT assume `:root` values from another sheet —
 * the same rule `eeh-header-footer.css` follows. Every colour is either a literal
 * here or has a literal fallback in `var(--x, #fallback)`.
 *
 * ── ⚠ THE TWO ELEMENTS MUST NOT COMPETE FOR THE SAME CORNER ───────────────────
 * The orb lives bottom-right. The floating tour button is BOTTOM-LEFT and shifts
 * up on a phone where the orb's own trigger occupies the floor. Two floating
 * elements in one corner is the obstruction a visitor already reported once.
 *
 * @package ExpressEntryHub
 */

/* ===================================================================== *
 *  1. THE TRIGGER (plain button — the player binds the behaviour)
 *
 * ⚠★ S-AOV-THEME (2026-10-11) — EVERY "BRAND" COLOUR HERE IS NOW A TOKEN.
 *
 * WHAT WAS WRONG: the whole sheet was hard-coded bronze (`rgba(205,133,63,…)`,
 * `#CD853F`), so the "Hear this page" button stayed bronze no matter which of the
 * orb's 10 palettes the admin chose. MEASURED: with `eeh_orb_theme` set to
 * `sapphire` and to `emerald`, the rendered border was `rgba(205,133,63,0.5)` both
 * times — identical, i.e. the setting changed nothing.
 *
 * ⚠ THE TOKENS ARE `--eeh-aov-*`, PUBLISHED BY `EEH_Guide::orb_theme_css()` FROM
 *   THE SAME PALETTE TABLE. This sheet owns its token names rather than borrowing
 *   `--eeh-orb-*`, because the two sheets are enqueued by different classes on
 *   different conditions and a borrowed token can be absent where this one
 *   renders. See the note on `$aov_map` for the full reasoning.
 *
 * ⚠ EVERY TOKEN KEEPS THE OLD LITERAL AS ITS FALLBACK, SO NOTHING CAN REGRESS.
 *   `var(--eeh-aov-edge, rgba(205,133,63,0.45))` is byte-identical to today's
 *   render on any page where the theme block is absent — a cached page, a DB
 *   reset, a PHP error, or an admin who never opened the Voice tab. The fallback
 *   IS the rollback.
 *
 * ⚠ THE ERROR COLOURS ARE DELIBERATELY NOT TOKENISED (see §3). Red there is a
 *   MEANING, not a brand — a Sapphire theme must not make a failure look healthy.
 * ===================================================================== */

.eeh-aov-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 10px;
  border: 1px solid var(--eeh-aov-edge, rgba(205, 133, 63, 0.45));
  /* The surface is the palette's own metal top, held far back so the inline
     button stays a button rather than a panel. */
  background: var(--eeh-aov-metal-top, rgba(205, 133, 63, 0.12));
  color: var(--eeh-aov-text, #f3f4f6);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  /*
   * ⚠ `touch-action: none` ONLY ON THE FLOATING VARIANT (see below). On an inline
   * button it would block the page scroll the visitor expects when their thumb
   * lands on it.
   */
  transition: background 0.15s ease, border-color 0.15s ease;
}

.eeh-aov-trigger:hover {
  background: var(--eeh-aov-metal-mid, rgba(205, 133, 63, 0.20));
  border-color: var(--eeh-aov-accent, rgba(205, 133, 63, 0.70));
}

.eeh-aov-trigger:focus-visible {
  outline: 2px solid var(--eeh-aov-accent, #CD853F);
  outline-offset: 2px;
}

/*
 * ★★ S-AOV-MOBILE (2026-10-08) — THE ICON GETS A REAL BOX.
 *
 * ⚠ THIS IS THE FIX THAT MAKES THE EXISTING ANIMATION LEGIBLE. All three state
 * animations in §3 target THIS span, but it was a bare inline `<span>` with no
 * width, height or display — so it had no box of its own. The `playing`
 * equaliser sets `width/height: 16px` and three `background-size` slices, and
 * with no box to sit in those bars rendered as an ambiguous smear rather than a
 * waveform. The visitor's report — "like an icon, hard to see, hard to
 * understand" — is this, measured.
 *
 * ⚠ `inline-flex` CENTRES the glyph in the box rather than relying on the
 * font's own metrics, so the ♪ sits optically centred in every state and the
 * equaliser's bars are flush to the box's edges. `flex: 0 0 auto` keeps the
 * button's own `gap` in charge of spacing — the icon must not stretch when the
 * label is long.
 *
 * ⚠ THE 18px BOX IS THE DESKTOP VALUE TOO. It is set on the BASE selector, not
 * inside a media query, because the equaliser bug was present on desktop as
 * well; only the pill SIZING around it is mobile-specific (§3b).
 */
.eeh-aov-trigger-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  /* ★ S-ORB-TRIO-3 — the icon tracks the 14.5px label. Left at 15px it would be
     the SMALLEST element in a pill whose text grew 26%, so it reads as a speck
     beside the words rather than as the thing the eye lands on first. */
  width: 17px;
  height: 17px;
  border-radius: 4px;
  font-size: 14px;
  line-height: 1;
  color: var(--eeh-aov-accent, #CD853F);
}

/*
 * ⚠ THE PLAYING STATE CHANGES BOTH THE ICON AND THE LABEL. The button is a
 * toggle, and a visitor who cannot tell whether it is running will press it
 * twice and stop the tour they just started.
 */
.eeh-aov-trigger--playing {
  border-color: var(--eeh-aov-accent, rgba(205, 133, 63, 0.85));
  background: var(--eeh-aov-metal-mid, rgba(205, 133, 63, 0.26));
}

.eeh-aov-trigger--playing .eeh-aov-trigger-icon {
  color: #fff;
}

/* ===================================================================== *
 *  3. BUTTON STATES — the animation that replaces the status line
 *
 *  ★★ S-BUTTON-ONLY-STATUS (2026-10-06). The visitor asked not to see the
 *  "♪ Hear this page / playing…" line. The feedback it carried now lives ON
 *  the button, driven by `data-eeh-aov-state` which `_panel()` sets:
 *
 *    · writing  — the tour is being written (a few seconds). A gentle pulse on
 *                 the ♪ icon, so the press is acknowledged immediately.
 *    · playing  — the tour is speaking. The ♪ icon becomes three equaliser
 *                 bars that never stop moving, so a visitor can see it is live
 *                 without reading anything.
 *    · error    — the tour could not be written. A red tint + one shake.
 *
 *  ⚠ ALL THREE ARE DISABLED UNDER `prefers-reduced-motion`. A pulsing or
 *  shaking control is exactly the motion that triggers discomfort; the colour
 *  changes alone still convey the state, so nothing is lost.
 * ===================================================================== */

/*
 * ⚠ THE WRITING PULSE. Slow and low-contrast by design — this is a public
 * page's reading path, and the visitor pressed the button on purpose, so the
 * acknowledgement should be noticed, not shouted. 1.6s is quick enough to feel
 * responsive while being far calmer than a spinner.
 */
.eeh-aov-trigger[data-eeh-aov-state="writing"] .eeh-aov-trigger-icon {
  animation: eeh-aov-writing 1.6s ease-in-out infinite;
}

@keyframes eeh-aov-writing {
  0%, 100% { opacity: 0.55; transform: scale(0.92); }
  50%      { opacity: 1;    transform: scale(1.06); }
}

/*
 * ★★ THE PLAYING EQUALISER. The ♪ glyph is HIDDEN and replaced by three bars
 * built from the same element via `box-shadow`-free pseudo-elements on the
 * icon span. Pure CSS, no extra markup: `font-size: 0` hides the glyph, and the
 * three bars are the icon's background gradient slices that scale vertically.
 *
 * ⚠ WHY THREE BARS AND NOT A SPINNER. A spinner says "loading"; bars say
 * "sound is playing". The visitor has just asked to HEAR the page, so the
 * metaphor has to be audio, not a throbber. Each bar runs the same keyframe at
 * a different delay so the group reads as a waveform.
 */
.eeh-aov-trigger[data-eeh-aov-state="playing"] .eeh-aov-trigger-icon {
  color: transparent;          /* hide the ♪ glyph, keep the box */
  background-image:
    linear-gradient(#fff, #fff),
    linear-gradient(#fff, #fff),
    linear-gradient(#fff, #fff);
  background-repeat: no-repeat;
  background-size: 3px 60%, 3px 100%, 3px 45%;
  background-position: left center, center center, right center;
  width: 16px;
  height: 16px;
  animation: eeh-aov-eq 0.9s ease-in-out infinite;
}

@keyframes eeh-aov-eq {
  0%, 100% { background-size: 3px 35%, 3px 90%, 3px 50%; }
  25%      { background-size: 3px 95%, 3px 40%, 3px 80%; }
  50%      { background-size: 3px 55%, 3px 100%, 3px 30%; }
  75%      { background-size: 3px 80%, 3px 50%, 3px 95%; }
}

/*
 * ⚠ THE ERROR STATE. A brief red tint + a single shake is enough to say
 * "that did not work" without a sentence. The shake runs ONCE (not infinite) so
 * a persistent failure does not rhythmically nag the visitor.
 */
.eeh-aov-trigger[data-eeh-aov-state="error"] {
  border-color: rgba(248, 113, 113, 0.85);
  background: rgba(248, 113, 113, 0.18);
}

.eeh-aov-trigger[data-eeh-aov-state="error"] .eeh-aov-trigger-icon {
  color: #fca5a5;
  animation: eeh-aov-shake 0.4s ease-in-out 1;
}

@keyframes eeh-aov-shake {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-3px); }
  40%      { transform: translateX(3px); }
  60%      { transform: translateX(-2px); }
  80%      { transform: translateX(2px); }
}

/*
 * ⚠ THE SCREEN-READER LIVE REGION. The visible status line is gone, so the
 * spoken state lives in this visually-hidden span inside each trigger. Standard
 * `.screen-reader-text` technique: clipped to 1px, never `display:none` (which
 * some screen readers skip for live regions).
 */
.eeh-aov-live.eeh-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .eeh-aov-trigger[data-eeh-aov-state="writing"] .eeh-aov-trigger-icon,
  .eeh-aov-trigger[data-eeh-aov-state="playing"] .eeh-aov-trigger-icon,
  .eeh-aov-trigger[data-eeh-aov-state="error"] .eeh-aov-trigger-icon {
    animation: none;
  }
  /*
   * ★ S-AOV-MOBILE (2026-10-08) — THE NEW MOTION OBEYS THE SAME SWITCH.
   * The halo (§3c) and the phone equaliser are added by this pass, and every
   * animated surface must be named here or it silently ignores the visitor's
   * preference. Naming only the desktop keyframes would have left a phone with
   * "reduce motion" set still watching a pulsing glow.
   */
  .eeh-aov-fab,
  .eeh-aov-fab[data-eeh-aov-state="playing"],
  .eeh-aov-fab[data-eeh-aov-state="writing"],
  .eeh-aov-fab[data-eeh-aov-state="playing"] .eeh-aov-trigger-icon {
    animation: none;
  }
  /* Keep an unmistakable, motionless "playing" cue: the paper-white icon. */
  .eeh-aov-trigger[data-eeh-aov-state="playing"] .eeh-aov-trigger-icon {
    color: #fff;
    background-image: none;
    width: auto;
    height: auto;
  }
  /*
   * ⚠ AND THE PHONE PILL STILL SAYS IT IS PLAYING WITHOUT MOTION. With the halo
   * gone the state rests entirely on colour, so the border and surface keep the
   * accent lift the animated state would otherwise have carried. Removing the
   * animation must never remove the STATE.
   */
  .eeh-aov-fab[data-eeh-aov-state="playing"],
  .eeh-aov-fab[data-eeh-aov-state="writing"] {
    /* ★ S-AOV-THEME — same palette tokens as the base playing rule above; this
       block only exists because the halo animation is suppressed here, so the
       state must rest on colour alone. The tokens keep the two in step. */
    border-color: var(--eeh-aov-accent, rgba(var(--color-accent-rgb, 205, 133, 63), 0.95));
    background: var(--eeh-aov-ink, rgba(43, 33, 20, 0.96));
  }
}


/* ===================================================================== *
 *
 *  §3c. THE BREATHING HALO (S-AOV-MOBILE, 2026-10-08)
 *
 *  ★★ A PHONE HAS NO HOVER, SO THE ONLY "IT IS LIVE" CUE IS MOTION.
 *
 *  On desktop a visitor can hover the button and feel it respond. A phone
 *  visitor gets no such acknowledgement — and because the FAB is draggable the
 *  first thing they do is touch it, at which point the drag arms and the press
 *  may be swallowed. The halo answers "she heard you" with no text at all,
 *  which is what the visitor asked for when the status line was removed.
 *
 *  ⚠⚠ IT ANIMATES `box-shadow`, NEVER `transform`. THIS IS LOAD-BEARING.
 *  `_bindDrag()` writes `el.style.transform = "translate(x,y)"` on every
 *  pointermove (`eeh-audio-tour.js:759`). A `transform` keyframe on this same
 *  element would be overridden by that inline style mid-animation, and worse,
 *  a transform-based pulse would make the button drift under the finger —
 *  the "the drag is broken" failure. `box-shadow` is the one property that
 *  animates a glow without touching the geometry the drag owns.
 *
 *  ⚠ IT IS ON THE FAB ONLY, NOT ON `.eeh-aov-hero-slot .eeh-aov-trigger`. The
 *  hero button is in the page's reading path, where a pulsing glow would be a
 *  distraction; the FAB is a control that has asked for attention by being
 *  pressed.
 * ===================================================================== */

/*
 * ⚠ MOBILE ONLY (≤782px), matching §3b. The desktop FAB already has a hover
 * response and a stronger border in the playing state; adding a permanent glow
 * there would make a page's corner ripple while a tour plays, which is more
 * than desktop needs. The phone is where the acknowledgement is missing.
 */
@media (max-width: 782px) {
  .eeh-aov-fab[data-eeh-aov-state="playing"],
  .eeh-aov-fab[data-eeh-aov-state="writing"] {
    animation: eeh-aov-halo 2.4s ease-in-out infinite;
  }
}

/*
 * ⚠ SLOW — 2.4s. This is a public page a visitor is reading while the tour
 * speaks. A fast pulse competes with the audio; a slow one reads as breathing,
 * which is the correct metaphor for a guide who is present and talking. The
 * halo never reaches full opacity so it stays a glow rather than a flash.
 *
 * ★ S-AOV-THEME — THE HALO READS `--eeh-aov-accent-rgb`, A TRIPLET, NOT A COLOUR.
 * A `box-shadow` cannot take a hex where `rgba()` expects three numbers, so the
 * palette's accent is also published as `R, G, B` for exactly this purpose. The
 * fallback chain keeps the site's own `--color-accent-rgb` (and then bronze) for
 * a page with no theme block, so the halo is unchanged there. Without the triplet
 * the halo would have stayed bronze while the button's border turned sapphire —
 * a half-themed control, which is worse than an unthemed one.
 */
@keyframes eeh-aov-halo {
  0%, 100% {
    box-shadow:
      0 8px 22px rgba(0, 0, 0, 0.44),
      0 0 0 0 rgba(var(--eeh-aov-accent-rgb, var(--color-accent-rgb, 205, 133, 63)), 0.42);
  }
  50% {
    box-shadow:
      0 8px 22px rgba(0, 0, 0, 0.44),
      0 0 0 7px rgba(var(--eeh-aov-accent-rgb, var(--color-accent-rgb, 205, 133, 63)), 0);
  }
}

/* ===================================================================== *
 *  3. THE FLOATING BUTTON
 * ===================================================================== */

/*
 * ★★ BOTTOM-LEFT, NOT BOTTOM-RIGHT.
 *
 * ⚠ THE ORB OWNS THE BOTTOM-RIGHT CORNER. Put this there and the two float over
 * one another — the exact "they obstruct my page" complaint the orb's own drag
 * feature was added to solve. Left is empty, so both can coexist.
 *
 * ⚠ IT IS ANCHORED BY `left`/`bottom`, NEVER BY `top`. The drag applies a
 * `transform` offset on top of this anchor (see `_bindDrag`), so a viewport
 * resize can never strand it off-screen and there is no layout thrash mid-drag.
 */
.eeh-aov-fab {
  position: fixed;
  left: 14px;
  /*
   * ══ ★★ S-ORB-AOV-ALIGN (2026-10-11) — THE SHARED BASELINE ══════════════════
   *
   * "we need to make the two, the hear this page, and orb hostess in the same
   *  size and line … first bring the orb hostess in the same line or align with
   *  hear this page button"
   *
   * ── WHAT WAS WRONG (measured, not eyeballed) ──────────────────────────────
   *   orb  bottom = 882   h = 56
   *   fab  bottom = 886   h = 42     <- 4px lower, 14px shorter
   * At 1440px the two reported DIFFERENT baselines and visibly different weights,
   * so they read as two unrelated floating things rather than one pair.
   *
   * ── ★ ONE PUBLISHED BASELINE, NOT TWO HARDCODED NUMBERS ────────────────────
   * The orb's anchor is `--eeh-orb-inset` (18px) + its own diameter. This sheet
   * must NOT read those tokens: its own header records the contract — "it carries
   * its own tokens and depends on nothing … it must NOT assume `:root` values from
   * another sheet" — and the two sheets are enqueued by different classes on
   * different conditions (see the `$aov_map` note in `class-guide.php`).
   *
   * So the shared value is PUBLISHED under a name this sheet owns, exactly as the
   * palette is: `--eeh-aov-baseline` is the orb's bottom inset, emitted alongside
   * the colours. The fallback below is this rule's previous literal, so a page
   * with no theme block renders byte-identically to before.
   *
   * ⚠ WHY `bottom` AND NOT `height`-BASED CENTRING. Both elements are anchored by
   *   their BOTTOM edge, so equalising the bottom edges is the one correction that
   *   survives every screen size without a second measurement. It is also what
   *   "the same line" literally means for two elements pinned to the same edge.
   */
  bottom: var(--eeh-aov-baseline, 18px);
  z-index: 9998;             /* just under the orb (9999) — the guest, not the host */
  display: inline-flex;
  align-items: center;
  /* ★ S-AOV-SHRINK — 8px -> 6px, scaled with the box so the icon and label keep
     their proportion inside the smaller pill. */
  gap: 6px;
  /* ★ MATCHED HEIGHT — set explicitly so the pill and the orb read as ONE SIZE.
   *
   * ⚠ WHY AN EXPLICIT HEIGHT AND NOT JUST BIGGER PADDING. The pill's height is
   *   derived today: `padding` + the 13px line box + the 1px border, which is how
   *   it landed on 42px against the orb's 56px. Deriving one and pinning the other
   *   guarantees they drift the moment either font-size or padding is touched, so
   *   the height is PINNED and the padding only has to keep the content inside it.
   *
   * ⚠ `--eeh-aov-pill-h` IS PUBLISHED, NOT HARDCODED HERE. Same contract as the
   *   baseline: this sheet owns the token and the emitter fills it from the orb's
   *   own diameter band, so "the same size" stays true if the orb is ever resized
   *   rather than being true only for today's 56px.
   *
   * ⚠ THE FALLBACK IS 56px — THE ORB'S MEASURED DIAMETER AT DESKTOP. With no
   *   published token (cached page, theme block absent) the pill is still the same
   *   height as the orb, which is the property being asked for; only the coupling
   *   to a future resize is lost.
   *
   * ⚠ AND `box-sizing` IS FORCED, because a pinned height plus padding means the
   *   two interpretations differ by the border width — a 2px discrepancy that would
   *   quietly defeat the whole exercise.
   */
  box-sizing: border-box;
  /*
   * ★★ S-AOV-SHRINK (2026-10-11) — 20% SMALLER: 56px -> 44.8px (56 x 0.80).
   *
   * The owner: "now the hear this page button is too big, shrink 20% from original
   * size, and make the text get fit in this space."
   *
   * ⚠ EVERYTHING THAT LIVES INSIDE THE BOX SHRINKS WITH IT, OR THE BOX IS SIMPLY
   *   SMALLER AND THE TEXT IS STILL TOO BIG FOR IT. A pinned height plus an
   *   unscaled font is how you get a clipped or vertically-cramped label — the
   *   "make the text get fit" half of the request is satisfied by scaling the
   *   padding and the icon, not by hoping.
   *
   * ⚠ THE FONT HAS A FLOOR OF 11.5px AND IS **NOT** SCALED TO 10.4px (13 x 0.80),
   *   WHICH WAS THE OPERATOR'S EXPLICIT CHOICE when the tradeoff was put to them.
   *   10.4px is below the point where a UI label stops being comfortably readable;
   *   11.5px keeps the button honestly 20% smaller while the text stays legible.
   *   The consequence is that the label is a larger FRACTION of a smaller box, so
   *   the horizontal padding below is slightly tighter than a pure 0.80 scale to
   *   buy that space back.
   */
  min-height: var(--eeh-aov-pill-h, 38.4px);
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid var(--eeh-aov-edge, rgba(205, 133, 63, 0.50));
  /* ★ S-AOV-THEME — the FAB's own surface. `--eeh-aov-ink` is the palette's panel
     base, so the button reads as a small piece of the same instrument as the orb
     rather than as an unrelated dark pill. The old literal is the fallback. */
  background: var(--eeh-aov-ink, rgba(24, 26, 32, 0.92));
  color: var(--eeh-aov-text, #f3f4f6);
  /* ★ S-ORB-TRIO-3 — 11.5 -> 14.5px. The 11.5px floor bought legibility inside a
     44.8px box; the box is now 38.4px and is SHARED with the pill, so both labels
     carry the SAME 14.5px and the pair reads as one control set. Vertical padding
     drops 6 -> 4px to keep that text clear of the border in the shorter box. */
  font-size: 14.5px;
  line-height: 1;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
  /* ⚠ The drag must own the gesture, so the browser must not scroll the page. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}

/*
 * ★★ THE QUIET STATE — THE BUTTON STANDS DOWN WHILE THE ORB IS OPEN.
 * (S-TOUR-ORB-PRIORITY, 2026-10-09)
 *
 * ── THE VISITOR'S REPORT ──────────────────────────────────────────────────────
 * On a phone, with the tour talking, opening the orb left the "Hear this page"
 * button floating OVER the orb's full-screen caption window. On a phone the orb
 * is a full-screen stage (`inset: 0`, `eeh-orb.css:1883`), so the FAB's
 * bottom-left placement — chosen to dodge the orb's bottom-RIGHT corner on
 * desktop — no longer separates them: the stage covers the whole viewport.
 *
 * ── WHY A STATE ATTRIBUTE AND NOT A Z-INDEX BUMP ──────────────────────────────
 * A higher z-index would not fix it; it would only decide which of the two draws
 * on top, while leaving the FAB TAPPABLE. The visitor asked for it to be
 * INACTIVE — *"only when orb is reduced to be quiet, then clicking on hear this
 * page button will make it activated"* — which is a state, not a stacking order.
 *
 * ⚠ `display: none` IS THE ONE PROPERTY THAT CANNOT BE OVERRIDDEN. The same
 * reasoning the orb's own `_peek()` records: an `opacity: 0` or `visibility:
 * hidden` control still reserves space and can be revealed by a later rule. This
 * is the only property that guarantees the button can NEVER sit beside (or
 * under) an open orb.
 *
 * ⚠ IT KEYS OFF `data-eeh-aov-quiet`, WRITTEN BY `EEHAudioTour._quiet()` — one
 * writer, in the same `data-eeh-aov-*` vocabulary as `data-eeh-aov-state`. The
 * JS decides WHO wins; the CSS only reflects it. No inline styles, no second
 * source of truth.
 *
 * ⚠ DESKTOP TOO, DELIBERATELY. The visitor asked for "the same behaviour for the
 * full screen pc" — the expanded bar is her taking over just as the phone stage
 * is, so the button stands down there as well.
 */
.eeh-aov-fab[data-eeh-aov-quiet="1"] {
  display: none;
}

.eeh-aov-fab:hover {
  border-color: var(--eeh-aov-accent, rgba(205, 133, 63, 0.85));
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.45);
}

/*
 * ⚠ THE DRAGGING STATE LIFTS IT AND KILLS TRANSITIONS. A transition on
 * `transform` would lag the button behind the finger, which reads as the drag
 * being broken rather than smooth.
 */
.eeh-aov-fab[data-eeh-aov-dragging="1"] {
  transition: none;
  cursor: grabbing;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.5);
}

/*
 * ★★ S-AOV-MOBILE (2026-10-08) — REPLACES A DEAD RULE.
 *
 * ⚠ WHAT WAS HERE AND WHY IT WAS DEAD. The old rule styled
 * `.eeh-aov-fab.eeh-aov-trigger--playing`. That class has NOT been set since
 * S-BUTTON-ONLY-STATUS (2026-10-06) moved the play state onto the
 * `data-eeh-aov-state` attribute — `_pressed()` no longer writes `--playing`
 * on the trigger at all, so the selector matched nothing and the FAB's
 * "playing" appearance came from the base `.eeh-aov-trigger--playing` rule
 * alone. A rule that can never match is worse than no rule: it tells the next
 * reader the FAB has a playing treatment, and it does not.
 *
 * ★ THE LIVE SELECTOR IS THE `data-eeh-aov-state` ONE, which `_panel()` sets on
 * every trigger (`eeh-audio-tour.js:328`). Both the border and the surface lift
 * so a visitor can tell at a glance that the tour is running — the FAB is the
 * tour's ONLY visitor surface, so its state must be unmissable.
 */
.eeh-aov-fab[data-eeh-aov-state="playing"],
.eeh-aov-fab[data-eeh-aov-state="writing"] {
  /* ★ S-AOV-THEME — the running state reads the PALETTE's accent, not the site's
     `--color-accent-rgb`. The old value is kept as the fallback, so a page with
     no theme block renders exactly as before. The playing state is the FAB's only
     state signal, so it must follow the same palette the button's resting border
     does — otherwise a Sapphire site shows a sapphire border and a bronze glow. */
  border-color: var(--eeh-aov-accent, rgba(var(--color-accent-rgb, 205, 133, 63), 0.95));
  background: var(--eeh-aov-ink, rgba(43, 33, 20, 0.96));
}

/* Never print the floating button on a page that is not a normal front-end page. */
.admin-bar .eeh-aov-fab {
  bottom: 46px;             /* clear the WP admin bar on a phone/tablet */
}

@media (max-width: 782px) {
  /*
   * ★★ S-AOV-MOBILE (2026-10-08) — THE PHONE GETS A LABELLED PILL, NOT A DOT.
   *
   * ── THE BUG THIS REPLACES ─────────────────────────────────────────────────
   * The previous rule here did two things at ≤480px:
   *     .eeh-aov-fab .eeh-aov-trigger-label { display: none; }
   *     .eeh-aov-fab { padding: 12px; border-radius: 50%; }
   * i.e. it DELETED the words and shrank the control to a ~44px circle whose
   * only content was a 15px ♪ glyph. The visitor's report is exact:
   * "the hear this page button is not there but like an icon, hard to see, hard
   * to understand". A music note in a plain circle does not say "hear this
   * page" — it says nothing.
   *
   * ★ THE LABEL IS THE FEATURE. The whole point of this control is that a
   * visitor understands it without being told. Removing the words removes the
   * only thing that carries that meaning, so the label now SURVIVES on a phone
   * and the pill is sized to fit it.
   *
   * ── WHY ≤782px AND NOT ≤480px ─────────────────────────────────────────────
   * 782px is WordPress's own admin-bar breakpoint, and it is the width at which
   * this sheet ALREADY shifts the FAB (`bottom: 64px`) to clear the taller
   * admin bar. Keying the pill's look to the same band means one breakpoint
   * governs the whole mobile arrangement instead of two that can drift apart.
   *
   * ── WHY THE SURFACE IS STRONGER HERE THAN ON DESKTOP ──────────────────────
   * On desktop the FAB floats in the page margin where a subtle pill reads
   * fine. On a phone it sits ON the content — and it is draggable, so the
   * visitor can park it over ANY background, light or dark. A faint tint would
   * vanish against one of them. So the phone pill gets an opaque-ish surface,
   * a stronger border and a deeper shadow: it must be legible over whatever it
   * is dragged onto, not only over a dark page.
   *
   * ⚠ THE ACCENT IS THE THEME'S, WITH A LITERAL FALLBACK. This sheet is public
   * and "depends on nothing" (see the header contract) — every colour here is
   * either literal or has a literal fallback, which is exactly what
   * `var(--color-accent-rgb, 205,133,63)` is. The live theme is `amber-slate`
   * whose accent is `197,138,32`, so hardcoding the old bronze `#CD853F` would
   * render a colour the theme never chose — most visible here, on the phone,
   * where the pill sits directly against the page.
   */
  .eeh-aov-fab {
    /* ★ S-ORB-AOV-ALIGN — the phone baseline and height, MEASURED from the orb.
       ⚠ The previous value here was 64px, taken from a comment claiming the orb
       also shifts to 64px on a phone. It does not: the ≤480px block sets
       `--eeh-orb-inset: 12px`, so the pill was sitting 50px BELOW the orb — the
       two furthest apart on the smallest screen. The probe measured it. */
    bottom: var(--eeh-aov-baseline-phone, 24px);
    /* ★ S-AOV-SHRINK — the phone pill shrinks with the desktop one: 52 x 0.80 =
       41.6px. ⚠ IT IS NOW BELOW THE 44px TAP-TARGET GUIDELINE, which is called out
       here rather than silently accepted: the owner asked for a 20% reduction and
       the phone pill shares the -20% rule. The FAB is a convenience duplicate of a
       control the orb also offers, and its label is still a full-width target, so
       the practical loss is small — but it IS a deviation from the guideline and it
       is recorded as one. */
    min-height: var(--eeh-aov-pill-h-phone, 38.4px);
    left: 10px;
    /*
     * ⚠ ROOM FOR A THUMB. The former `10px 14px` assumed a 52px box; scaled to the
     * 41.6px box it became `7px 11px`. ★ S-ORB-TRIO-3 raises the font to 14.5px to
     * match the peek pill, so the vertical padding comes DOWN to 4px to fit the
     * taller text into the 38.4px box — the same trade the desktop rule makes.
     */
    padding: 4px 11px;
    gap: 6px;
    font-size: 14.5px;
    border-width: 1px;
    /* ★ S-AOV-THEME — the phone pill keeps its own sizing but takes the palette's
       colours, so the FAB does not change brand between breakpoints. */
    border-color: var(--eeh-aov-accent, rgba(var(--color-accent-rgb, 205, 133, 63), 0.72));
    background: var(--eeh-aov-ink, rgba(24, 26, 32, 0.94));
    color: var(--eeh-aov-text, #f8fafc);
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.44);
    /*
     * ⚠ IT MUST NOT OUTGROW A NARROW PHONE. The label is fixed copy ("Hear this
     * page"), but the label is backend-renameable, so a long replacement could
     * push the pill past the viewport. The cap plus the ellipsis keeps it on
     * screen for any wording rather than for the current string only.
     */
    max-width: calc(100vw - 20px);
  }

  .eeh-aov-fab .eeh-aov-trigger-label {
    display: inline;
    max-width: 60vw;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* ⚠ THE ICON LARGER ON A PHONE, because the pill is and the glyph is what
     the eye lands on first. The equaliser scales with the box automatically —
     its bars are `background-size` percentages, not fixed pixels. */
  .eeh-aov-fab .eeh-aov-trigger-icon {
    width: 20px;
    height: 20px;
    font-size: 17px;
  }

  /* ⚠ THE EQUALISER BARS THICKEN WITH THE BOX. The base rule draws 3px bars;
     at 20px on a phone that reads thin, so the bars are re-declared at 4px.
     Both the `background-size` and the keyframe therefore need the phone
     override — a size change without matching keyframes would snap back to 3px
     on the first animation frame. */
  .eeh-aov-fab[data-eeh-aov-state="playing"] .eeh-aov-trigger-icon {
    background-size: 4px 60%, 4px 100%, 4px 45%;
    animation: eeh-aov-eq-mobile 0.9s ease-in-out infinite;
  }
}

/*
 * ★★ THE PHONE EQUALISER — same waveform, thicker bars.
 * Kept as its own keyframe set (rather than editing the desktop one) so the
 * desktop bars stay 3px. Nothing else differs: the delays and the group rhythm
 * are identical, so the two breakpoints read as the SAME animation at two
 * sizes rather than two different ones.
 */
@keyframes eeh-aov-eq-mobile {
  0%, 100% { background-size: 4px 35%, 4px 90%, 4px 50%; }
  25%      { background-size: 4px 95%, 4px 40%, 4px 80%; }
  50%      { background-size: 4px 55%, 4px 100%, 4px 30%; }
  75%      { background-size: 4px 80%, 4px 50%, 4px 95%; }
}

@media (max-width: 480px) {
  /*
   * ⚠ THE SMALL PHONE KEEPS THE LABEL — THIS RULE USED TO DELETE IT.
   * S-AOV-MOBILE (2026-10-08). The old block here hid the label and turned the
   * pill into a bare 44px circle; that is the defect being fixed, so what
   * remains is only a tightening of the spacing. The label is the feature.
   */
  .eeh-aov-fab {
    padding: 9px 12px;
    font-size: 12.5px;
    gap: 8px;
  }

  .eeh-aov-fab .eeh-aov-trigger-label {
    max-width: 56vw;
  }
}

/*
 * ⚠ THE DEAD `.eeh-aov` INLINE-PANEL RULES ARE GONE.
 * S-BUTTON-ONLY-STATUS deleted the panel block itself, and this block was a
 * leftover that still styled `.eeh-aov` — an element nothing mounts any more
 * (`_mount()` was removed from the JS). Re-adding it would be re-adding the
 * status line the visitor asked to be removed. See the header contract.
 */

/* ===================================================================== *
 *  4. THE TOUR'S OWN PAGE (page-hear-this-page.php)
 * ===================================================================== */

.eeh-aov-page .eeh-aov-lede {
  font-size: 16px;
  color: rgba(255, 255, 255, 0.88);
}

.eeh-aov-page-cta {
  margin: 20px 0 28px;
}

.eeh-aov-h2 {
  margin: 26px 0 8px;
  font-size: 18px;
  font-weight: 600;
  color: #fff;
}

.eeh-aov-list {
  margin: 0 0 16px 20px;
  padding: 0;
  list-style: disc;
}

.eeh-aov-list li {
  margin-bottom: 8px;
  color: rgba(255, 255, 255, 0.80);
}

@media (max-width: 480px) {
  .eeh-aov-page .eeh-aov-lede { font-size: 15px; }
  .eeh-aov-h2 { font-size: 16px; }
}

/* ===================================================================== *
 *  4. THE HERO SLOT + THE MENU LINK (S-TOUR-CONTROLS, 2026-10-05)
 *
 *  ★ CT PARITY (`onHero` / `inMenu`). The floating pill stays bottom-left on
 *  every page; these are the two MORE-DISCOVERABLE placements. They reuse the
 *  same `.eeh-aov-trigger` button, so there is no second control to keep in
 *  step — only two houses for it.
 * ===================================================================== */

/*
 * ⚠ THE HERO SLOT IS IN FLOW, NOT FLOATING. It is inserted after the page's own
 * heading, so it must behave like the page's furniture (its own line, its own
 * spacing) rather than like a fixed control. `inline-flex` + a small margin is
 * all it takes; the button inside already owns its own look.
 */
.eeh-aov-hero-slot {
  display: flex;
  margin: 10px 0 6px;
}

/*
 * ⚠ THE HERO BUTTON IS WIDER-SET THAN THE FLOATING ONE. In a hero there is no
 * dark page behind it to gauge against, so it carries a slightly stronger border
 * and a touch more padding to read as a deliberate call to action rather than a
 * stray pill. Colour tokens are literal with fallbacks — this is a public sheet
 * and must not inherit from the admin cascade (`.clinerules` §2).
 */
.eeh-aov-hero-slot .eeh-aov-trigger {
  padding: 10px 18px;
  font-size: 14px;
  border-width: 1px;
  /* ★ S-AOV-THEME — the in-hero variant is the SAME button in a different slot,
     so it must follow the same palette. Fallbacks are its previous literals. */
  border-color: var(--eeh-aov-accent, rgba(205, 133, 63, 0.75));
  background: var(--eeh-aov-ink, rgba(24, 26, 32, 0.92));
  color: var(--eeh-aov-text, #f3f4f6);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.30);
}

.eeh-aov-hero-slot .eeh-aov-trigger:hover {
  border-color: var(--eeh-aov-accent-lit, rgba(205, 133, 63, 1));
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.40);
}

/*
 * ⚠ THE MENU LINK CARRIES NO EXTRA CHROME. It must look exactly like every other
 * menu item — a differently-styled one reads as a different KIND of link, which
 * is the opposite of discoverable. Only the ♪ glyph distinguishes it, and that
 * glyph is inside the label span, so a theme's own menu styling applies intact.
 */
.eeh-aov-menu-item > a {
  white-space: nowrap;
}

