/* ══════════════════════════════════════════════════════════════════════════
   ★★ THE MOBILE TRIGGER — A VISIBLE WAY IN, ON EVERY PAGE (2026-10-01)

   ── THE REPORTED FAULT ──────────────────────────────────────────────────────
     "for mobile screen, the button for listen to page is hidden inside the
      burger menu. can we have a visible button all the time for each page
      for mobile."

   ★★ MEASURED AT 390x844, on /pricing/:
       hamburgerVisible : true       ← the burger is what the visitor sees
       menuDisplay      : 'none'     ← the menu is COLLAPSED inside it
       linkVisible      : false      ← the tour link is unreachable
       heroBtn          : false      ← and no hero button exists on that page
   **A PHONE HAD NO WAY TO START THE TOUR AT ALL.** The feature was effectively
   desktop-only, which is the opposite of what an audio tour is for.

   ★ WHY A FLOATING BUTTON AND NOT A MENU FIX. The menu is collapsed by the
   responsive shell on purpose; moving one item out of it would fight that
   design. A page-level control is also the honest shape for a feature that is
   ABOUT the page.

   ★★ IT IS PLACED BY MEASUREMENT, NOT GUESSED. The orb was measured at 390px:
       orb rect  left 324, top 778, right 376, bottom 830   (bottom-RIGHT)
   So this sits BOTTOM-LEFT (left: 14px), which cannot overlap it. This project
   has paid four times for elements whose positions were assumed, including the
   orb's own `right: 18px` vs its 46px centre.

   ⚠ AND IT IS HIDDEN ON THE TOUR PAGE ITSELF — a button that starts the tour on
   the page ABOUT the tour is noise. */
/* ⚠ SPECIFICITY, NOT ORDER (fixed 2026-10-01).
   MEASURED BUG: this rule first used the bare `.ct-aov-fab` selector, and the later
   `.ct-aov-trigger { display: inline-flex }` — SAME specificity — overrode it. The
   button rendered on DESKTOP at 1440x37px, invisibly covering the menu, and Playwright
   reported *"ct-aov-fab intercepts pointer events"* when clicking the menu link.
   ★ `.ct-aov-trigger.ct-aov-fab` (0,2,0) beats `.ct-aov-trigger` (0,1,0) whatever the
   order, so a future rule added above or below cannot re-show it on desktop. */
.ct-aov-trigger.ct-aov-fab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  position: fixed;
  /* ⚠ BOTTOM-LEFT. The orb is bottom-right (measured left: 324 of 390). */
  left: 14px;
  /*
   * ══ ★★ S-ORB-AOV-ALIGN (ported 2026-10-24 from EEH) — 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 = 18px  (--ct-orb-inset)
   *   fab  bottom = 14px        <- 4px lower
   * Both are pinned by their BOTTOM edge, so they reported different baselines and
   * read as two unrelated floating things rather than one pair.
   *
   * ── ★ ONE PUBLISHED BASELINE, NOT TWO HARDCODED NUMBERS ─────────────────────
   * This sheet must NOT read `--ct-orb-inset`: its own contract is that it "carries
   * its own tokens and depends on nothing", and the two sheets are enqueued on
   * DIFFERENT conditions (`ct-orb.css` only from `enqueue_orb_v2()`). Assuming the
   * other sheet's `:root` value would work on orb pages and silently fall back to a
   * wrong literal everywhere else.
   *
   * So the shared value is PUBLISHED under a name this sheet owns:
   * `--ct-aov-baseline` is emitted from PHP next to the palette, from the same
   * number as `--ct-orb-inset`. The fallback below is this rule's previous literal,
   * so a page with no emitted token renders exactly as it always has.
   *
   * ⚠ 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. That is also what
   * "the same line" literally means for two elements pinned to the same edge.
   */
  bottom: var(--ct-aov-baseline, 14px);
  z-index: 2147483000;       /* above page content; below nothing we own */
  padding: 10px 15px 10px 13px;
  border-radius: 999px;
  font: inherit;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  /* ★★ SEE-THROUGH, BUT VISIBLE — the visitor's own words. A 55% ink fill over
     the page reads as glass: the page shows through, the label stays legible.
     ⚠ 88% (the old mobile value) was opaque; 45% or less lost the label on a
     busy photograph. 55% was the balance point. */
  color: #f4f7ff;
  background: color-mix(in srgb, #0b1020 55%, transparent);
  border: 1px solid color-mix(in srgb, #ffffff 38%, transparent);
  box-shadow: 0 4px 16px color-mix(in srgb, #000000 40%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  /* ⚠ Clears the iOS home indicator, so the tap target is never under the bar. */
  margin-bottom: env(safe-area-inset-bottom, 0px);
  /* ⚠ TOUCH GESTURE OWNERSHIP. The drag handler uses Pointer Events and calls
     `preventDefault()` on move; `touch-action: none` stops the browser from
     scrolling the page under the finger while it is being dragged. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  /* ⚠ NO transition on transform here — a drag must track the finger 1:1. */
  transition: background .16s ease, border-color .16s ease;
}

/* A drag is in progress: dim slightly and show the grab cursor so the visitor
   can see the button is following their finger. */
.ct-aov-trigger.ct-aov-fab[data-ct-aov-dragging="1"] {
  cursor: grabbing;
  opacity: .9;
}

/*
 * ══ ★★ S-TOUR-ORB-PRIORITY (ported 2026-10-24 from EEH) — THE ORB WINS ══════
 *
 * "when orb is clicked open, the hear this page button disappears, and when orb
 *  is close it shows. so the two do not interfere. one is on, other is closed."
 *
 * ── 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 button TAPPABLE underneath. The visitor asked for it
 * to be inactive, which is a state, not a stacking order.
 *
 * ⚠ `display: none` IS THE ONE PROPERTY THAT CANNOT BE OVERRIDDEN. The orb's own
 * `_peek()` records the same reasoning: 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-ct-aov-quiet`, WRITTEN BY `CTAudioOverview._quiet()` — ONE
 * writer, in the same `data-ct-aov-*` vocabulary as `data-ct-aov-state`. The JS
 * decides WHO wins; the CSS only reflects it. No inline styles, no second source
 * of truth.
 *
 * ⚠ DESKTOP TOO, DELIBERATELY. The expanded bar is her taking over just as the
 * phone stage is, so the button stands down there as well.
 *
 * ⚠ PLACED AFTER THE DRAG RULES so a future rule added above cannot re-show it.
 */
.ct-aov-trigger.ct-aov-fab[data-ct-aov-quiet="1"] {
  display: none;
}

.ct-aov-trigger.ct-aov-fab:hover,
.ct-aov-trigger.ct-aov-fab:focus-visible {
  background: color-mix(in srgb, #0b1020 72%, transparent);
  border-color: color-mix(in srgb, #ffffff 62%, transparent);
}

.ct-aov-trigger.ct-aov-fab:active { transform: translateY(1px); }

/* The icon is decorative; the label carries the meaning for screen readers. */
.ct-aov-fab-icon { font-size: 15px; }

/* ⚠ VERY NARROW PHONES: the label can crowd the orb's corner. Below 380px the
   label steps down a size rather than the button growing. */
@media (max-width: 380px) {
  .ct-aov-trigger.ct-aov-fab { font-size: 12px; padding: 9px 13px 9px 11px; }
}

/* ---------------------------------------------------------------------------
 * S-BUTTON-ONLY-STATUS (2026-10-16) - THE INLINE PANEL BLOCK WAS REMOVED
 * ---------------------------------------------------------------------------
 *
 * WHAT WAS HERE: a `.ct-aov` inline panel was mounted after the hero, carrying
 * the title "Hear this page" and a visible status word ("writing the tour..." /
 * "playing..."). So the visitor saw TWO things: the floating button AND this line.
 *
 * WHY THE WHOLE BLOCK IS DELETED (ported from EEH `S-BUTTON-ONLY-STATUS`):
 *   "i prefer not to see this line, instead there can be an animation in the
 *    hear this page button."
 *
 * THE DECISION: the state ("writing" / "playing" / "error") is now a
 * `data-ct-aov-state` attribute on every `.ct-aov-trigger`, styled below in
 * section "BUTTON STATES". The `.ct-aov` / `.ct-aov-head` / `.ct-aov-title` /
 * `.ct-aov-icon` / `.ct-aov-status` block AND its `@media (max-width:480px)`
 * full-screen stage are all REMOVED - not hidden. The accessibility live region
 * survives as a visually-hidden span inside each trigger (see `_mountLive()`).
 *
 * DO NOT RE-ADD `.ct-aov` - nothing mounts it any more (`_mount()` was removed
 * from `ct-audio-overview.js`). Re-adding the panel is re-adding the line the
 * visitor asked to remove.
 * --------------------------------------------------------------------------- */



/* ── THE TRIGGER BUTTON (hero + the tour page) ────────────────────────────────
 *
 * ★ IT IS A GHOST, DELIBERATELY — A SECONDARY CONTROL, NOT A THIRD CTA.
 * The hero's primary pair ("Start Free Trial" / "Browse Famous People") is the
 * money path and must not be diluted. The visitor asked for a play control on the
 * hero; making it a PEER of "Start Free Trial" would have changed the page's job.
 * A ghost reads as an invitation: here is something else you can do.
 *
 * ⚠ IT USES FALLBACKABLE TOKENS. The tour renders on pages WITHOUT the orb (the AI
 * Site Guide has none), so `--ct-orb-*` may be undefined; and a hardcoded light
 * colour would vanish on the Ivory palette's near-black ink. */
.ct-aov-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  /* Transparent fill + a bright hairline: present, but clearly not the CTA. */
  background: color-mix(in srgb, #ffffff 8%, transparent);
  color: inherit;
  border: 1px solid color-mix(in srgb, #ffffff 34%, transparent);
  border-radius: 999px;
  padding: 10px 18px 10px 15px;
  transition: background .16s ease, border-color .16s ease, transform .12s ease;
}

.ct-aov-trigger:hover,
.ct-aov-trigger:focus-visible {
  background: color-mix(in srgb, #ffffff 16%, transparent);
  border-color: color-mix(in srgb, #ffffff 55%, transparent);
  outline: none;
}

/* A visible ring, because the button is reachable by keyboard and a ghost fill
   alone is easy to lose against a photo hero. */
.ct-aov-trigger:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, #ffffff 30%, transparent);
}

.ct-aov-trigger:active { transform: translateY(1px); }

.ct-aov-trigger-icon { font-size: 15px; }

/* On the tour's OWN page the section supplies the text colour, so the button
   follows it rather than assuming a dark hero behind it. */
.ct-aov-page .ct-aov-trigger {
  background: color-mix(in srgb, currentColor 7%, transparent);
  border-color: color-mix(in srgb, currentColor 30%, transparent);
}
.ct-aov-page .ct-aov-trigger:hover,
.ct-aov-page .ct-aov-trigger:focus-visible {
  background: color-mix(in srgb, currentColor 14%, transparent);
  border-color: color-mix(in srgb, currentColor 48%, transparent);
}

/* ── THE TOUR PAGE'S OWN TYPOGRAPHY (2026-10-02) ─────────────────────────────
   ⚠ THESE REPLACE INLINE STYLES. The page previously carried `style="font-size:17px"`
   and per-heading inline margins, which is why it did NOT match `/about/`, `/faq/`
   etc. — the visitor saw a page that looked like a different site. Classes let the
   page inherit the theme's type scale like every other page, so a palette change
   or a font-size change reaches it too. */
.ct-aov-page-cta {
  margin: 22px 0 26px;
}
.ct-aov-lede {
  font-size: 17px;
  line-height: 1.65;
}
.ct-aov-h2 {
  font-size: 20px;
  margin: 26px 0 10px;
}
/* The FIRST h2 follows a paragraph, not another heading — same gap as the rest. */
.ct-aov-h2:first-of-type {
  margin-top: 26px;
}
.ct-aov-list {
  line-height: 1.7;
  padding-left: 20px;
}

/* ══════════════════════════════════════════════════════════════════════════
   ★★ THE CAPTION AND ITS [CC] TOGGLE ARE GONE (2026-10-02)

   ── WHAT WAS HERE ───────────────────────────────────────────────────────────
   On 2026-10-01 the caption was COLLAPSED BY DEFAULT and revealed by a `[CC]`
   button, so the tour was listen-only unless the visitor asked for the words. A
   spoken page with no text alternative excluded deaf and hard-of-hearing visitors,
   so the transcript was one tap away rather than absent.

   ── WHY THE WHOLE BLOCK IS DELETED ──────────────────────────────────────────
     "i see, ♪ Hear this page / CC / Stop / × … this is not needed, as user can
      use browser base cc, remove this option at all, but we can click the same
      hear this page button to stop the conversation."

   ★ THE DECISION: the tour is PURE AUDIO. The visitor judged that the browser/OS
   caption tooling covers the text case, and the floating button now toggles
   play/stop. So the caption element, the `[CC]` toggle, the `data-ct-aov-cc`
   collapse rules and the `.ct-aov-transcript` slot are all REMOVED — not hidden.

   ⚠ A REMOVED RULE MUST NOT BE LEFT BEHIND. Keeping `.ct-aov:not([data-ct-aov-cc=
   "open"]) .ct-aov-caption { display:none }` would style an element that no longer
   exists and would read, to the next reader, as if the caption were still shipping.
   ══════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════════
   ★★ BUTTON STATES — THE ANIMATION THAT REPLACES THE STATUS LINE (2026-10-16)

   ── WHAT THIS IS (ported from EEH `S-BUTTON-ONLY-STATUS`) ───────────────────
   The visitor asked not to see the "♪ Hear this page / playing…" line. The
   feedback it carried now lives ON the button, driven by `data-ct-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.
 */
.ct-aov-trigger[data-ct-aov-state="writing"] .ct-aov-trigger-icon,
.ct-aov-trigger[data-ct-aov-state="writing"] .ct-aov-fab-icon {
  display: inline-block;
  animation: ct-aov-writing 1.6s ease-in-out infinite;
}

@keyframes ct-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: `color: transparent` hides the glyph, and the
 * three bars are the icon's background gradient slices that scale vertically.
 * Pure CSS, no extra markup.
 *
 * ⚠ 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. The three bars run the same
 * keyframe at different sizes so the group reads as a waveform.
 */
.ct-aov-trigger[data-ct-aov-state="playing"] .ct-aov-trigger-icon,
.ct-aov-trigger[data-ct-aov-state="playing"] .ct-aov-fab-icon {
  display: inline-block;
  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: ct-aov-eq 0.9s ease-in-out infinite;
}

@keyframes ct-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.
 *
 * ★ THE ERROR COLOUR IS NOT A THEME TOKEN. Red here is a MEANING, not a brand —
 * an Ivory theme must not make a failure look healthy.
 */
.ct-aov-trigger[data-ct-aov-state="error"] {
  border-color: rgba(248, 113, 113, 0.85);
  background: rgba(248, 113, 113, 0.18);
}

.ct-aov-trigger[data-ct-aov-state="error"] .ct-aov-trigger-icon,
.ct-aov-trigger[data-ct-aov-state="error"] .ct-aov-fab-icon {
  display: inline-block;
  color: #fca5a5;
  animation: ct-aov-shake 0.4s ease-in-out 1;
}

@keyframes ct-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).
 */
.ct-aov-live.ct-aov-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;
}

/*
 * ★★ REDUCED MOTION — EVERY ANIMATED SURFACE IS NAMED HERE.
 * Naming only some of them would have left a visitor with "reduce motion" set
 * still watching a pulsing or shaking control.
 *
 * ⚠ REMOVING THE ANIMATION MUST NEVER REMOVE THE STATE. The `playing` state
 * keeps an unmistakable, motionless cue: the paper-white icon.
 */
@media (prefers-reduced-motion: reduce) {
  .ct-aov-trigger[data-ct-aov-state="writing"] .ct-aov-trigger-icon,
  .ct-aov-trigger[data-ct-aov-state="writing"] .ct-aov-fab-icon,
  .ct-aov-trigger[data-ct-aov-state="playing"] .ct-aov-trigger-icon,
  .ct-aov-trigger[data-ct-aov-state="playing"] .ct-aov-fab-icon,
  .ct-aov-trigger[data-ct-aov-state="error"] .ct-aov-trigger-icon,
  .ct-aov-trigger[data-ct-aov-state="error"] .ct-aov-fab-icon {
    animation: none;
  }
  /* Keep an unmistakable, motionless "playing" cue: the paper-white icon. */
  .ct-aov-trigger[data-ct-aov-state="playing"] .ct-aov-trigger-icon,
  .ct-aov-trigger[data-ct-aov-state="playing"] .ct-aov-fab-icon {
    color: #fff;
    background-image: none;
    width: auto;
    height: auto;
  }
}


