/* ============================================================================
   Emily — site-guide widget
   Tokens from tbg.css only. No :root overrides, no new palette.
   Navy / electric / cyan. NO --emerald (it reads as a status-green in a UI
   surface and belongs to Aperture), NO --amber.
   ---------------------------------------------------------------------------- */

.emily {
  position: fixed;
  right: 20px; bottom: 20px;
  z-index: var(--z-widget);          /* 90 — always BELOW the contact modal at 100 */
  display: flex; flex-direction: column; align-items: flex-end; gap: 12px;
}

/* ---------- ORB ---------- */
.emily__orb {
  position: relative;
  width: 56px; height: 56px;
  border-radius: 50%;
  border: 1px solid var(--tint-cyan-50);
  background: radial-gradient(circle at 38% 34%, var(--tint-cyan-22), var(--void-3) 72%);
  box-shadow: var(--shadow-md);
  display: grid; place-items: center;
  padding: 0;
  transition: transform var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.emily__orb:hover { transform: translateY(-2px); border-color: var(--cyan); }
.emily__orb-core {
  width: 20px; height: 20px; border-radius: 50%;
  background: radial-gradient(circle, var(--cyan-2), var(--electric));
  box-shadow: var(--shadow-glow-cyan);
}
.emily__orb-ring {
  position: absolute; inset: -5px;
  border-radius: 50%;
  border: 1px solid var(--tint-cyan-22);
  animation: emily-breathe 3s ease-in-out infinite;
}
@keyframes emily-breathe {
  0%, 100% { opacity: .55; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.06); }
}
.emily__orb-cap {
  position: absolute; right: calc(100% + 10px); top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  font-family: var(--f-mono); font-size: 11px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2);
  background: var(--void-2);
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  padding: 6px 12px;
  opacity: 0;
  animation: emily-cap-in var(--dur-med) var(--ease-out-soft) 2.4s forwards;
}
@keyframes emily-cap-in { to { opacity: 1; } }
.emily[data-open="true"] .emily__orb-cap { display: none; }
.emily[data-open="true"] .emily__orb-ring { animation: none; opacity: 1; border-color: var(--cyan); }

/* ---------- PANEL ---------- */
.emily__panel {
  width: 360px; max-width: calc(100vw - 40px);
  background: var(--void-2);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  display: flex; flex-direction: column;
  order: -1;                          /* panel sits ABOVE the orb */
}
.emily__panel[hidden] { display: none; }

.emily__panel-head {
  display: flex; align-items: center; gap: 8px;
  padding: 14px 14px 12px 18px;
  border-bottom: 1px solid var(--rule);
}
.emily__panel-titles { flex: 1 1 auto; min-width: 0; }
.emily__panel-title { font-size: 15px; font-weight: 500; color: var(--ink); letter-spacing: -.01em; }
.emily__panel-sub {
  font-family: var(--f-mono); font-size: 10.5px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-4);
  margin-top: 2px;
}
.emily__icon-btn {
  width: 30px; height: 30px; flex: 0 0 auto;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  color: var(--ink-3);
  display: grid; place-items: center;
  font-size: 12px; line-height: 1;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.emily__icon-btn:hover { border-color: var(--rule-strong); color: var(--ink); }

/* ---------- VIDEO / POSTER ---------- */
.emily__video-wrap {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--void);
  border-bottom: 1px solid var(--rule);
}
.emily__video { width: 100%; height: 100%; object-fit: cover; display: block; }
.emily__video-fallback {
  position: absolute; inset: 0;
  display: none; place-items: center;
  background:
    radial-gradient(ellipse 60% 70% at 50% 55%, var(--tint-cyan-16), transparent 70%),
    var(--void);
}
.emily__fallback-orb {
  width: 46px; height: 46px; border-radius: 50%;
  background: radial-gradient(circle at 40% 36%, var(--cyan-2), var(--electric));
  box-shadow: var(--shadow-glow-cyan);
  animation: emily-breathe 3s ease-in-out infinite;
}
/* text-only mode: no clip file yet, or playback refused. Fully functional. */
.emily[data-video="off"] .emily__video { display: none; }
.emily[data-video="off"] .emily__video-fallback { display: grid; }

/* ---------- CAPTIONS (always on — never a toggle) ---------- */
.emily__captions {
  padding: 14px 18px;
  font-size: 15px; line-height: 1.5;
  color: var(--ink);
  min-height: 62px;
  border-bottom: 1px solid var(--rule);
}
.emily__captions[data-partial="true"] { color: var(--ink-3); font-style: italic; }
.emily__captions:empty::before { content: '…'; color: var(--ink-4); }

/* ---------- CHIPS ---------- */
.emily__chip-row {
  display: flex; gap: 8px;
  padding: 12px 18px;
  overflow-x: auto; scrollbar-width: none;
  border-bottom: 1px solid var(--rule);
}
.emily__chip-row::-webkit-scrollbar { display: none; }
.emily__chip-row[hidden] { display: none; }
.emily-chip {
  flex: 0 0 auto;
  padding: 7px 14px;
  background: transparent;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  font-size: 12.5px; color: var(--ink-2);
  white-space: nowrap;
  transition: all var(--dur-fast) var(--ease);
}
.emily-chip:hover { border-color: var(--electric); background: var(--tint-electric-08); color: var(--ink); }

/* ---------- FOOT ---------- */
.emily__panel-foot { display: flex; align-items: center; gap: 8px; padding: 12px 14px 14px 18px; }
.emily__form { flex: 1 1 auto; display: flex; gap: 6px; min-width: 0; }
.emily__input {
  flex: 1 1 auto; min-width: 0;
  background: var(--void-3);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 9px 12px;
  color: var(--ink);
  font-family: var(--f-body); font-size: 13.5px;
}
.emily__input:focus { outline: none; border-color: var(--electric); box-shadow: 0 0 0 3px var(--tint-electric-16); }
.emily__send, .emily__mic {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  color: var(--ink-3);
  display: grid; place-items: center;
  font-size: 13px;
  transition: all var(--dur-fast) var(--ease);
}
.emily__send:hover, .emily__mic:hover { border-color: var(--electric); color: var(--ink); }
.emily__mic[aria-pressed="true"] { border-color: var(--cyan); color: var(--cyan); box-shadow: 0 0 0 3px var(--tint-cyan-16); }
.emily__mic[hidden] { display: none; }
.emily__handoff {
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: var(--ink-4);
  font-size: 11.5px;
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 0 2px;
}
.emily__handoff:hover { color: var(--cyan); }

/* state cue that is NOT colour — a11y */
.emily[data-mode="listening"]   .emily__panel-sub::before,
.emily[data-mode="near-miss"]   .emily__panel-sub::before,
.emily[data-mode="no-match"]    .emily__panel-sub::before,
.emily[data-mode="handoff"]     .emily__panel-sub::before { margin-right: 6px; }
.emily[data-mode="listening"] .emily__panel-sub::before { content: '●'; color: var(--cyan); }
.emily[data-mode="near-miss"] .emily__panel-sub::before,
.emily[data-mode="no-match"]  .emily__panel-sub::before { content: '?'; }
.emily[data-mode="handoff"]   .emily__panel-sub::before { content: '→'; }

/* ---------- MOBILE — bottom sheet ---------- */
@media (max-width: 819px) {
  /* 88px keeps a full orb clear of a browser bottom bar and of any
     page-bottom Contact CTA */
  .emily { right: 16px; bottom: 88px; }
  .emily__panel {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    width: 100vw; max-width: 100vw;
    height: 78dvh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-left: 0; border-right: 0; border-bottom: 0;
  }
  .emily__video-wrap { flex: 0 0 auto; }
  .emily__captions { flex: 1 1 auto; overflow-y: auto; }
}
@media (max-width: 480px) {
  .emily__chip-row { flex-wrap: wrap; overflow-x: visible; }
}

/* ---------- REDUCED MOTION ---------- */
@media (prefers-reduced-motion: reduce) {
  .emily__orb-ring, .emily__fallback-orb { animation: none; opacity: 1; transform: none; }
  .emily__orb-cap { animation: none; opacity: 1; }
  .emily__orb:hover { transform: none; }
}

/* ---------- FORCED COLORS ---------- */
@media (forced-colors: active) {
  .emily__panel, .emily__orb, .emily-chip { border-color: currentColor; }
}

/* ============================================================================
   P5 — AMBASSADOR UPGRADE (2026-09-03)
   Guided tour, boardroom demo, and the avatar presentation that replaces the
   placeholder orb. Class names are Simon's registry (P2c §11.2) verbatim.
   Tokens only — nothing below introduces a colour, radius, shadow or duration
   that tbg.css does not already define. Navy / electric / cyan. No gold.
   Every rule here is ADDITIVE: no shipped selector above is redefined.
   ---------------------------------------------------------------------------- */

/* ---------- VIDEO HOOD — still + video + vignette in one stacking context ---- */
.emily__video-hood {
  position: relative;
  aspect-ratio: 4 / 5;
  background: var(--void);
  border-bottom: 1px solid var(--rule);
  overflow: hidden;
}
/* The hood owns the aspect now; the shipped wrap fills it rather than
   imposing a second, conflicting ratio. */
.emily__video-hood .emily__video-wrap {
  position: absolute; inset: 0;
  aspect-ratio: auto;
  border-bottom: 0;
  background: transparent;
}
.emily__still {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  opacity: 1;
  transition: opacity var(--dur-med) var(--ease-out-soft);
}
/* She is speaking: the video is the presentation, the still steps behind it. */
.emily[data-vstate="speaking"] .emily__still { opacity: 0; }
/* FIX 2026-09-07: hide the still whenever the video is truly playing (tour path) */
.emily.emily--playing .emily__still { opacity: 0 !important; }

.emily__video-vignette {
  position: absolute; inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 40px var(--tint-cyan-16);
  transition: box-shadow var(--dur-med) var(--ease);
}
.emily[data-vstate="speaking"]  .emily__video-vignette { box-shadow: inset 0 0 40px var(--tint-cyan-08); }
.emily[data-vstate="listening"] .emily__video-vignette { box-shadow: inset 0 0 44px var(--tint-cyan-22); }
.emily[data-vstate="thinking"]  .emily__video-vignette { box-shadow: inset 0 0 40px var(--tint-electric-16); }

/* ---------- CAPTION CUES ---------- */
/* Weight only — never a colour change, so the highlight survives forced
   colours and does not cost contrast. */
.emily__cue { transition: opacity var(--dur-fast) var(--ease); opacity: .68; }
.emily__cue--active { opacity: 1; font-weight: 500; }

/* ---------- CHIPS AT THE LARGER SCALE ---------- */
.emily-chip--primary {
  border-color: var(--electric);
  background: var(--tint-electric-08);
  color: var(--ink);
}
.emily-chip--primary:hover { background: var(--tint-electric-16); border-color: var(--electric-2); }
.emily__chip-row--overflow {
  -webkit-mask-image: linear-gradient(to right, #000 88%, transparent 100%);
          mask-image: linear-gradient(to right, #000 88%, transparent 100%);
}

/* ---------- TOUR BAR ---------- */
.emily__tour-bar {
  padding: 10px 18px 8px;
  border-bottom: 1px solid var(--rule);
  background: var(--void-3);
}
.emily__tour-bar[hidden] { display: none; }
.emily__tour-dots { display: flex; gap: 5px; align-items: center; }
.emily__tour-dot {
  width: 100%; height: 3px;
  flex: 1 1 auto;
  border-radius: 999px;
  background: var(--rule-strong);
  transition: background var(--dur-fast) var(--ease);
}
.emily__tour-dot--visited { background: var(--tint-cyan-50); }
.emily__tour-dot--current { background: var(--cyan); box-shadow: var(--shadow-glow-cyan); }
.emily__tour-label {
  margin-top: 7px;
  font-family: var(--f-mono); font-size: 10.5px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-4);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------- TOUR CONTROLS (mutually exclusive with the chip row) ---------- */
.emily__tour-controls {
  display: flex; gap: 8px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--rule);
}
.emily__tour-controls[hidden] { display: none; }
.emily__tour-btn {
  flex: 1 1 auto;
  padding: 8px 10px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  color: var(--ink-3);
  font-family: var(--f-mono); font-size: 11px;
  letter-spacing: .12em; text-transform: uppercase;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.emily__tour-btn:hover { border-color: var(--electric); color: var(--ink); }

/* ---------- PAGE-LEVEL STOP RIBBON ---------- */
.emily__stop-ribbon {
  position: absolute; left: 0; top: 0;
  display: flex; align-items: center; gap: 10px;
  padding: 6px 12px 6px 0;
  pointer-events: none;
  animation: emily-ribbon-in var(--dur-med) var(--ease-out-soft);
}
.emily__stop-ribbon-mark {
  width: 3px; height: 22px;
  border-radius: 999px;
  background: var(--cyan);
  box-shadow: var(--shadow-glow-cyan);
}
.emily__stop-ribbon-label {
  font-family: var(--f-mono); font-size: 10px;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--cyan);
}
@keyframes emily-ribbon-in { from { opacity: 0; } to { opacity: 1; } }

/* ---------- BOARDROOM DEMO TETHER (boardroom.html only) ---------- */
.emily__demo-tether {
  position: fixed;
  right: 46px; bottom: 96px;
  width: 1px; height: 22vh;
  z-index: var(--z-widget);
  pointer-events: none;
  background: linear-gradient(to top, var(--cyan), transparent);
  opacity: .55;
  animation: emily-tether-in var(--dur-slow) var(--ease-out-soft);
}
@keyframes emily-tether-in { from { opacity: 0; } to { opacity: .55; } }

/* ---------- PANEL SIZE VARIANTS ---------- */
.emily--tour .emily__panel,
.emily--demo .emily__panel { width: 384px; }
.emily--watch .emily__panel { width: 480px; }
/* The demo narrates a console that sits to the right; she moves left so she is
   not standing on top of the thing she is describing. */
@media (min-width: 1100px) {
  .emily--demo { right: auto; left: 20px; align-items: flex-start; }
  .emily--demo .emily__demo-tether { right: auto; left: 46px; }
}

/* ---------- MOBILE ---------- */
@media (max-width: 819px) {
  /* A 4:5 portrait plus a tour bar plus controls does not fit a phone
     alongside the caption; cap the hood so the caption keeps the room. */
  .emily__video-hood { aspect-ratio: 5 / 4; max-height: 34dvh; }
  .emily--demo { right: 16px; left: auto; align-items: flex-end; }
  .emily__demo-tether { display: none; }
  .emily__tour-label { font-size: 10px; }
}

/* ---------- REDUCED MOTION ---------- */
@media (prefers-reduced-motion: reduce) {
  .emily__still,
  .emily__video-vignette,
  .emily__cue { transition: none; }
  .emily__stop-ribbon,
  .emily__demo-tether { animation: none; }
}

/* ---------- FORCED COLORS ---------- */
@media (forced-colors: active) {
  .emily__tour-btn,
  .emily__video-hood { border-color: currentColor; }
  .emily__tour-dot--current { background: Highlight; }
  .emily__stop-ribbon-mark { background: Highlight; }
  .emily__cue--active { font-weight: 700; }
}

/* ---------- DOCK (the default corner presentation) ---------- */
.emily--dock .emily__panel { width: 384px; }

/* ---------- OPEN / CLOSE TRANSITION ----------
   Transient only: emily.js removes this class within --dur-med, so a panel can
   never be left mid-fade by a later state change. */
.emily--transitioning .emily__panel {
  animation: emily-panel-in var(--dur-med) var(--ease-out-soft);
}
@keyframes emily-panel-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .emily--transitioning .emily__panel { animation: none; }
}

/* ============================================================================
   FRAMING FIX — 2026-09-03 (Joe: "she gets cutoff at the top ... make it all
   fit properly in the window ... maybe show less of Emily's lower body")
   TWO causes, both real, both fixed here. Additive only — nothing above is
   redefined, and tbg.css is untouched.

   CAUSE 1 — the panel had NO height bound on desktop. Hood 4:5 at 360px wide
   is 450px, plus head + captions + chips + foot is ~665px of panel. The panel
   is bottom-anchored, so on a short window it grew off the TOP of the viewport
   and the first thing clipped was her head.

   CAUSE 2 — the clips are 1536x1024 LANDSCAPE, framed head-to-waist with only
   ~4% headroom above her hair. Any centred crop therefore eats her scalp
   before it eats anything else. The media is now TOP-ANCHORED, so every crop
   is taken off the BOTTOM. Her head can no longer be cut, at any window size.
   ---------------------------------------------------------------------------- */

/* --- 2 — top-anchored, oversized. Applies at every width. -------------------
   width:150% zooms past `cover` into a real head-and-shoulders (a plain cover
   fit cannot crop tighter than the source framing, which is head-to-waist).
   Chosen by rendering the 1.3x / 1.5x / 1.8x crops from a real clip frame and
   comparing them; 1.5x keeps visible headroom, 1.8x did not. */
.emily__video-hood .emily__video,
.emily__video-hood .emily__still {
  position: absolute;
  top: 0; left: 50%;
  width: 150%;
  height: auto;
  min-height: 100%;
  transform: translateX(-50%);
  object-fit: cover;
  object-position: 50% 0;
}

/* --- 1 — desktop: bind the panel to the window and let the caption yield. --- */
@media (min-width: 820px) {
  /* orb 56 + gap 12 + bottom 20 = 88 of furniture, plus a 32px top margin. */
  .emily__panel { max-height: calc(100dvh - 120px); }

  /* 3:2 returns ~210px to the panel vs the old 4:5. The hood may shrink as a
     last resort — and because the media is top-anchored, shrinking it only
     ever trims her torso. */
  .emily__video-hood { aspect-ratio: 3 / 2; max-height: 32dvh; flex: 0 1 auto; min-height: 120px; }

  /* The captions are what gives way when the window is short, not the video. */
  .emily__captions { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
}

/* ============================================================================
   COMPACT SPEAKING WINDOW — Deliverable B, 2026-09-07
   (Joe: "her window blocks the content while she talks")

   Problem, measured: on the tour the panel is 384px wide and most of a short
   viewport tall. While she narrates a section, she is standing on the section.

   THE GATE IS THE TOUR, NOT THE CLIP. The first cut of this keyed the shrink to
   `emily--playing`, which reads well and measured badly: that class is dropped
   on the video's `ended` event, so across the 1200ms hold between stops the
   panel sprang back to 384px — 37 expand/collapse cycles per spine walk, and it
   was at FULL width during exactly the moment the page smooth-scrolls to the
   next section, i.e. obstructing the content this feature exists to reveal.
   Keying on `data-tour="active"` instead means she is small for the whole
   narration and grows only when the visitor asks.

   Three conditions gate the shrink, all of them in the selector:
     .emily--tour              a tour, not a Q&A answer — a visitor who ASKED a
                               question is looking at her, not past her, and the
                               boardroom demo (.emily--demo) is excluded the same
                               way
     [data-tour="active"]      not paused, and not the completion clip
     :not(:hover):not(:focus-within):not(.emily--pinned)
                               the visitor has not asked for her back

   `emily--playing` is still set by emily.js and is still the single source of
   truth for "she is genuinely speaking" — it now drives only the still-hide
   (activating the previously-dead rule at L272 as a declarative guard behind the
   inline one), which is what it was always for.

   Everything here is ADDITIVE. No shipped selector above is redefined — checked
   mechanically, not by eye — and no colour, radius, duration or easing is
   introduced that tbg.css/tokens.css does not already define.
   ---------------------------------------------------------------------------- */

@media (min-width: 820px) {

  /* Both directions animate, so the transition lives on the always-true half of
     the pair rather than on the compact state alone.
     `[data-tour]` is not decoration: emily.js sets that attribute for the whole
     life of a tour and removes it on exit, so it selects exactly the right
     states — and it keeps this off `.emily--tour .emily__panel`, which is a
     SHIPPED selector at L382. Reusing that one to hang a new property on would
     have broken this file's own additive-only contract on a technicality, and a
     contract that is only mostly kept is not one the next reader can trust. */
  .emily--tour[data-tour] .emily__panel {
    transition: width var(--dur-med) var(--ease-out-soft);
  }
  .emily--tour[data-tour] .emily__video-hood {
    transition: aspect-ratio var(--dur-med) var(--ease-out-soft);
  }

  /* ---- the shrunk state ---------------------------------------------------- */
  /* WIDTH TRACKS THE GUTTER, 2026-09-08. A FLAT width cannot clear the content
     column, because the column is fixed (`--maxw`, centred) and the free space
     beside it is therefore a function of the viewport: (100vw - --maxw)/2 +
     --pad-x. Measured on the live preview, a flat 216px still occluded real
     glyphs — 70px of them at 1440px, on 5 of the 6 spine pages (worst cases:
     the hero kicker on `/` and the "What it produces" list on /capabilities).
     That was invisible to a check that measures container edges, because a
     container's right edge is padding; only leaf elements that paint their own
     text reveal it.

     So the width is the gutter, floored and capped:
       floor 180px  the threshold this file already set below for a talking head
                    still reading as a person; below it we would be trading a
                    face for a few pixels of text
       cap   216px  the size tuned in P3 — extra gutter on a wide screen is not
                    a reason to grow her back toward blocking anything
       -20px        `.emily` sits at right: 20px (L10)

     Fallbacks live INSIDE the var()s deliberately: a var() that fails to
     substitute is invalid at computed-value time, which resolves to unset and
     does NOT fall back to an earlier declaration — so a two-declaration guard
     would silently lose compact mode entirely rather than degrade. */
  .emily--tour[data-tour="active"]:not(:hover):not(:focus-within):not(.emily--pinned) .emily__panel {
    width: clamp(180px, calc((100vw - var(--maxw, 1240px)) / 2 + var(--pad-x, 2.25rem) - 20px), 216px);
    cursor: pointer;     /* not a hint to click — a hint that clicking changes this */
  }

  /* A 3:2 hood at 216px is a 144px-tall face — below the ~180px where a talking
     head stops reading as a person. A square keeps the face large while shedding
     the height the 384px panel was spending on it. The media inside is already
     150%-wide and top-anchored (`object-position: 50% 0`), so a squarer crop
     trims torso, never head. */
  .emily--tour[data-tour="active"]:not(:hover):not(:focus-within):not(.emily--pinned) .emily__video-hood {
    aspect-ratio: 1 / 1;
    max-height: none;
    min-height: 0;
    flex: 0 0 auto;
  }

  /* The caption is the one thing that must survive the shrink — it is her second
     channel and on a muted tab it is her ONLY channel. Clamped, never removed,
     so role="status" aria-live="polite" keeps announcing every stop. */
  .emily--tour[data-tour="active"]:not(:hover):not(:focus-within):not(.emily--pinned) .emily__captions {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }

  /* Hidden with display:none, never opacity: a control a keyboard user can still
     tab to but cannot see is worse than no control. display:none takes it out of
     the tab order, so :focus-within can never be reached from inside a subtree
     this rule is hiding. Verified by walking the real tab order, not by reading
     the rule. */
  .emily--tour[data-tour="active"]:not(:hover):not(:focus-within):not(.emily--pinned) .emily__panel-foot,
  .emily--tour[data-tour="active"]:not(:hover):not(:focus-within):not(.emily--pinned) .emily__chip-row,
  .emily--tour[data-tour="active"]:not(:hover):not(:focus-within):not(.emily--pinned) .emily__panel-sub,
  .emily--tour[data-tour="active"]:not(:hover):not(:focus-within):not(.emily--pinned) .emily__tour-label {
    display: none;
  }

  /* Kept: mute, close, Pause, Skip and the progress dots — everything a visitor
     reaches for mid-narration. Exit is the one destructive control and it is one
     hover away, so it is the one that yields the room.
     Targeted by its OWN class, not by :last-child. The positional version was
     correct today and would have silently hidden the WRONG control the day
     anyone appended a fourth tour button. */
  .emily--tour[data-tour="active"]:not(:hover):not(:focus-within):not(.emily--pinned) .emily__tour-btn--exit {
    display: none;
  }
}

/* A visitor who asked for less motion asked for less of this too. The panel
   still shrinks — that is layout, and it is the point of the feature — it just
   arrives there without the slide. */
@media (prefers-reduced-motion: reduce) {
  .emily--tour[data-tour] .emily__panel,
  .emily--tour[data-tour] .emily__video-hood { transition: none; }
}

/* FREEZE-HARDEN 2026-09-08 — GPU-composite the clip video (and its hood) onto its
   own layer so page scroll/hover repaints never stall its frame painting. Audio
   decodes on its own thread; a non-composited <video> repaints on the main thread
   and so freezes on cursor movement / scroll while the sound keeps going. This
   isolates her paint. No visual change — position/size are unaffected by translateZ. */
.emily__video,
.emily__video-hood {
  transform: translateZ(0);
  will-change: transform;
  backface-visibility: hidden;
}

/* ===== IDLE-COVER-FIX 2026-09-08 =========================================
   THE perpetual "she stops moving but audio keeps going" bug: the tour clip
   keeps playing (audio + lips) but the state machine can leave data-vstate at
   "idle", so emily-idle.js holds the .emily__idle loop at opacity:1 OVER the
   talking video -- the visitor sees the near-static idle loop and hears the
   clip audio. emily--playing is the trusted, video-event-driven "she is
   genuinely speaking" flag (added on the clip video playing event, removed on
   pause/ended/emptied). Gate the idle layer on it so a playing clip is NEVER
   covered by idle. !important overrides emily-idle.js inline opacity. */
.emily.emily--playing .emily__idle { opacity: 0 !important; }

/* ===== VIDEO-SHOW-FIX 2026-09-08 (companion to IDLE-COVER-FIX) ===========
   Same root cause, other half: mid-clip the state machine can leave
   data-video="off", and emily.css:120 then sets .emily__video{display:none}
   -- so with the idle layer now correctly hidden too, a genuinely-playing
   clip rendered as a BLACK hood (the video was decoding but display:none).
   emily--playing is the trusted <video>-event-driven "she is speaking" flag;
   force her real video visible (and the fallback orb hidden) whenever it is
   set, so a playing clip is always shown regardless of the data-video label. */
.emily.emily--playing .emily__video { display: block !important; }
.emily.emily--playing .emily__video-fallback { display: none !important; }

/* ===== TRANSITION-ORB-FIX 2026-09-08 =====================================
   Side-effect of the two fixes above: between tour clips (emily--playing off,
   data-video="off") the fallback placeholder orb (.emily__video-fallback, a
   blue dot) briefly shows in the gap the stuck idle overlay used to hide. In
   a tour we always have the calm still poster to cover that gap, so suppress
   the placeholder orb while a tour is active -- the still carries the hold, no
   blue-dot flash. Only affects the guided tour, not 1:1 usage. */
.emily--tour[data-tour="active"] .emily__video-fallback { display: none !important; }

/* ===== TRANSITION-BLUR-FIX 2026-09-08 (Joe) ==============================
   During the tour, the idle loop cross-fades in/out between clips over 560ms
   (--emily-idle-in), and because that loop is MOVING, the blend read as a
   "blur through multiframe" at every stop-to-stop transition. Make the idle
   switch INSTANTLY during the active tour: it still sits between clips, but
   appears/disappears as a clean hard cut instead of a blended smear -- so the
   handover is seamless. Scoped to the active tour only; 1:1 idle unchanged.
   One-line revert: delete this rule. */
.emily--tour[data-tour="active"] .emily__idle { transition: none !important; }
