/* =============================================================
   wynn-portal.css — Central modal overlay for Wynn chat
   =============================================================
   QP variant: same dark scheme as the rest of QP, but the chat
   modal uses BLUE accents (not the CB-style gold). User instantly
   reads "this is the chat — different surface from the page."
   Per Rick 2026-05-08: "I want the free chat window to carry the
   same aesthetic as QP not CB.. but I also want it visually
   different, same scheme but majorly different."

   Color tokens lifted from QP's shell:
     --twg-blue:        #3478ff
     --twg-blue-hi:     #5a93ff
     --twg-blue-glow:   rgba(58, 130, 255, 0.45)
     --twg-blue-soft:   rgba(58, 130, 255, 0.12)

   Mounting: each QP page <link>s this near the top of the head.
   ============================================================= */

/* Backdrop + modal container */
.wynn-portal-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.24s cubic-bezier(.22,.61,.36,1);
}
.wynn-portal-overlay[data-visible="true"] {
  opacity: 1;
  pointer-events: auto;
}
.wynn-portal-overlay__backdrop {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at center,
      rgba(8, 12, 24, 0.55) 0%,
      rgba(4, 6, 14, 0.78) 60%,
      rgba(0, 2, 8, 0.92) 100%);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* Modal — BLUE-rim panel, scales in from the orb's origin point.
   Origin is set inline by JS via captureOrbOrigin() so the modal
   appears to GROW OUT OF the orb the user just tapped. On close,
   it shrinks back to that same point — visual continuity. */
.wynn-portal-overlay__modal {
  position: relative;
  width: min(560px, 92vw);
  /* dvh = "dynamic viewport height" — automatically shrinks when
     the mobile soft keyboard opens (iOS Safari, Chrome Android,
     all modern browsers). Old vh units leave the modal anchored
     at full viewport height while the keyboard pushes everything
     up, which is what was making the header scroll out of sight
     when the user tapped the textarea. dvh keeps the modal sized
     to the actual visible area, and the flex column inside keeps
     the header pinned at the top regardless of how short the
     modal gets. Falls back gracefully to vh on browsers that
     don't support dvh. */
  height: min(720px, 86dvh);
  display: flex;
  flex-direction: column;
  background:
    linear-gradient(180deg, rgba(8, 14, 28, 0.97) 0%, rgba(2, 6, 14, 0.99) 100%);
  border: 1px solid rgba(58, 130, 255, 0.55);
  border-radius: 18px;
  box-shadow:
    0 -1px 0 rgba(120, 170, 255, 0.30) inset,
    0 28px 80px -16px rgba(0, 0, 0, 0.65),
    0 0 0 1px rgba(58, 130, 255, 0.12),
    0 0 80px -12px rgba(58, 130, 255, 0.32);
  overflow: hidden;
  transform: scale(0.06);
  opacity: 0;
  transform-origin: 50% 50%;     /* JS overrides per-open from orb rect */
  /* Asymmetric easing — different curves for open vs close.
     Open: spring-overshoot ~5% then settle (feels alive, lands).
     Close: slower deep-ease back into the orb (feels reverent,
     deliberate, like the conversation is gracefully retracting).
     The .leaving state uses a different transition tuned for the
     longer collapse so it's not jarring. */
  transition:
    transform 0.52s cubic-bezier(.34, 1.45, .64, 1),
    opacity   0.32s cubic-bezier(.22, .61, .36, 1);
}
.wynn-portal-overlay[data-visible="true"] .wynn-portal-overlay__modal {
  transform: scale(1);
  opacity: 1;
}
/* Closing — slower, deep-ease retraction. data-visible="false"
   (set on overlay) triggers the reverse transition with these
   tuned values: 750ms with a strong ease-in so the modal floats
   back into the orb instead of snapping. */
.wynn-portal-overlay[data-visible="false"] .wynn-portal-overlay__modal {
  transition:
    transform 0.75s cubic-bezier(.55, .06, .68, .19),
    opacity   0.55s cubic-bezier(.55, .06, .68, .19);
}

/* Desktop-only chat sizing — TEXT-FIRST scale lift. Tuned to
   match what 150% browser zoom looked like, applied at native
   100% so older homeowners reading on a laptop don't have to
   reach for ctrl+plus to read a window quote. The text size is
   the most important thing here — modal dimensions grow only
   to give the bigger text room to breathe.
   Scoped to (min-width: 641px) so mobile keeps its own sizes.
   Per Rick 2026-05-08: "make the chat and chat text larger on
   DESKTOP ONLY" + "the main thing elderly people will struggle
   with" is text size. */
@media (min-width: 641px) {
  /* Modal — wider + taller to give the bigger text breathing
     room. Caps at 820 × 920 so it doesn't dominate a 4K display
     but is generous on a typical laptop. */
  .wynn-portal-overlay__modal {
    width: min(820px, 92vw);
    height: min(920px, 88dvh);
  }
  /* Header — wordmark + role text + orb portrait all jump
     significantly. The brand mark needs to hold visual weight
     against 22px conversation bubbles below it. */
  .wynn-portal__header {
    padding: 22px 26px 18px;
    gap: 18px;
  }
  .wynn-portal__header-orb {
    flex: 0 0 72px;
    width: 72px;
    height: 72px;
  }
  .wynn-portal__wordmark {
    height: 36px;
    max-width: 200px;
  }
  .wynn-portal__header-name {
    font-size: calc(22px * var(--qp-text-scale));
  }
  .wynn-portal__header-role {
    font-size: calc(16px * var(--qp-text-scale));
  }
  .wynn-portal__close,
  .wynn-portal__voice {
    flex: 0 0 48px;
    width: 48px;
    height: 48px;
  }
  /* Message bubbles — THE LIFT THAT MATTERS. 14px → 22px is the
     real fix. Reads as a real conversation, not a UI label.
     Padding and radius bump proportionally so 22px copy doesn't
     feel cramped against the bubble edge. */
  .wynn-portal__log {
    padding: 28px 26px;
    gap: 16px;
  }
  .wynn-msg .bubble {
    font-size: calc(22px * var(--qp-text-scale));
    line-height: 1.5;
    padding: 16px 22px;
    border-radius: 18px;
  }
  /* Reply chips — bigger, more thumb-friendly, more readable. */
  .wynn-chip {
    font-size: calc(18px * var(--qp-text-scale));
    padding: 10px 18px;
  }
  /* Input row + textarea — 22px matches the bubble copy so the
     user types at the same scale they read. */
  .wynn-portal__input-row {
    padding: 20px 24px 22px;
    gap: 14px;
  }
  .wynn-portal__input {
    font-size: 22px;
    line-height: 1.45;
    padding: 16px 20px;
    min-height: 60px;
    max-height: 180px;
    border-radius: 14px;
  }
  .wynn-portal__send {
    flex: 0 0 56px;
    width: 56px;
    height: 56px;
    border-radius: 14px;
  }
  /* Thinking pill — sized so it doesn't disappear next to
     the bigger bubbles. */
  .wynn-thinking {
    font-size: calc(18px * var(--qp-text-scale));
    padding: 10px 20px;
  }
  /* "Wynn is thinking" dots scale to match the bigger pill. */
  .wynn-thinking__dots span {
    width: 5px;
    height: 5px;
  }
  .wynn-portal__chips {
    padding: 0 26px 8px;
    gap: 8px;
  }
  /* Typing-caret on assistant bubbles — bigger so it's
     visible against the larger text. */
  .wynn-msg .bubble[data-typing="true"]::after {
    width: 8px;
    height: 18px;
  }
}

/* Mobile — slightly larger relative footprint, keyboard-safe top
   anchor. dvh on the modal height + dvh on the overlay padding
   means when the soft keyboard opens, both shrink in sync. The
   modal stays anchored to the top of the visible viewport and
   the header never scrolls out of sight.
   ALSO bumps every chat-text size up so the conversation is
   actually readable on a phone held at arm's length — desktop
   sizing was tuned for a laptop screen, mobile readers need
   meaningfully bigger type. Per Rick 2026-05-08: "make the
   Wynn Chat and Wynn Chat text larger (ONLY ON MOBILE)". */
@media (max-width: 640px) {
  .wynn-portal-overlay {
    align-items: flex-start;
    padding-top: 5dvh;
  }
  .wynn-portal-overlay__modal {
    width: 96vw;
    height: 88dvh;
    border-radius: 16px;
  }
  /* Header — wordmark + role text + orb portrait all bump up so
     the brand mark reads as authoritative on a phone, not a
     squinty afterthought. */
  .wynn-portal__header {
    padding: 16px 18px 14px;
    gap: 14px;
  }
  .wynn-portal__header-orb {
    flex: 0 0 52px;
    width: 52px;
    height: 52px;
  }
  .wynn-portal__wordmark {
    height: 24px;
    max-width: 140px;
  }
  .wynn-portal__header-name {
    font-size: calc(17px * var(--qp-text-scale));
  }
  .wynn-portal__header-role {
    font-size: calc(13.5px * var(--qp-text-scale));
  }
  .wynn-portal__close,
  .wynn-portal__voice {
    flex: 0 0 40px;
    width: 40px;
    height: 40px;
  }
  /* Message bubbles — 14px → 16px lifts the conversation above
     "small print" territory on a phone screen. Padding bumps
     proportionally so the bubbles don't feel cramped around
     the bigger type. */
  .wynn-portal__log {
    padding: 18px 18px;
    gap: 12px;
  }
  .wynn-msg .bubble {
    font-size: calc(16px * var(--qp-text-scale));
    line-height: 1.5;
    padding: 12px 16px;
    border-radius: 16px;
    max-width: 88%;
  }
  /* Quick-reply chips — easier to tap with a thumb. */
  .wynn-chip {
    font-size: calc(13.5px * var(--qp-text-scale));
    padding: 8px 14px;
  }
  /* Input row — 16px font on the textarea is the magic iOS size
     that prevents the browser from auto-zooming the page when
     the user taps to type. Send button bumps up for thumb size. */
  .wynn-portal__input-row {
    padding: 14px 16px 16px;
    gap: 10px;
  }
  .wynn-portal__input {
    font-size: 16px;
    line-height: 1.45;
    padding: 12px 14px;
    min-height: 48px;
    max-height: 140px;
    border-radius: 12px;
  }
  .wynn-portal__send {
    flex: 0 0 48px;
    width: 48px;
    height: 48px;
    border-radius: 12px;
  }
  /* Thinking pill — bigger phrase + dots so the "Wynn is
     thinking" beat doesn't disappear into the larger bubbles. */
  .wynn-thinking {
    font-size: calc(14px * var(--qp-text-scale));
    padding: 7px 16px;
  }
  /* Greeting / first message visibility — slightly more breathing
     room above the first bubble so the wordmark doesn't crash
     into it. */
  .wynn-portal__chips {
    padding: 0 18px 6px;
  }
}

/* HANDLE BAR — universal mobile-app gesture cue at the top of
   the modal. Looks like the iOS bottom-sheet handle. Visual
   permission slip that says "this surface is draggable."
   Tap = dismiss. Touch-drag = swipe-to-dismiss (wired in JS).
   Sized generously so a thumb can reliably hit it. */
.wynn-portal__handle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* Tall hit target for thumbs — the visible bar is just 4px,
     but the button extends 14px above + below so the gesture
     never misses. */
  padding: 10px 0 6px;
  background: transparent;
  border: 0;
  cursor: pointer;
  /* Never let the handle compress when the modal shrinks
     (e.g. mobile keyboard opens). It must stay visible at the
     top of the modal so the user always sees the dismiss
     affordance. */
  flex: 0 0 auto;
  /* No tap highlight on iOS — we drive our own hover/active
     states on the inner bar element. */
  -webkit-tap-highlight-color: transparent;
  /* On touch devices, this also doubles as the swipe-start zone.
     touch-action: none lets us preventDefault on touchmove without
     fighting the browser's default scroll. */
  touch-action: none;
}
.wynn-portal__handle-bar {
  display: block;
  width: 44px;
  height: 4px;
  border-radius: 999px;
  background: rgba(120, 170, 255, 0.45);
  transition: background-color 220ms cubic-bezier(.22, .61, .36, 1),
              transform 220ms cubic-bezier(.22, .61, .36, 1);
  pointer-events: none;
}
.wynn-portal__handle:hover .wynn-portal__handle-bar,
.wynn-portal__handle:focus-visible .wynn-portal__handle-bar {
  background: rgba(160, 200, 255, 0.85);
  transform: scaleX(1.15);
}
.wynn-portal__handle:active .wynn-portal__handle-bar {
  background: rgba(180, 210, 255, 0.95);
}

/* HEADER — orb-mini + name + close.
   touch-action: none lets the swipe-down gesture preventDefault
   on touchmove so the modal tracks the finger cleanly without
   the browser's vertical scroll fighting it. */
.wynn-portal__header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 20px 14px;
  border-bottom: 1px solid rgba(58, 130, 255, 0.20);
  background: linear-gradient(180deg, rgba(58, 130, 255, 0.06) 0%, transparent 100%);
  touch-action: none;
  /* Pin the header at the top of the modal flex column. Never
     shrink — even when the modal compresses (mobile keyboard
     opens), the header stays at full size and the LOG absorbs
     the height loss. The orb, wordmark, voice toggle, and X
     button must always be visible to the user. */
  flex: 0 0 auto;
}
/* ===== WYNN ORB — header portrait =====
   Multi-layer construction for the "mythical Wynn" feel:
     - halo:    soft outer glow that breathes
     - ring 3:  outermost slow rotation, faint
     - ring 2:  mid ring counter-rotates
     - ring 1:  inner ring, brighter
     - core:    bright cyan/white glowing center
     - shimmer: top-left specular highlight, gives it depth
   When body[data-wynn-speaking="true"] (audio playing), the
   amplitude variable makes the orb pulse with the voice.
   ====================================== */
.wynn-portal__header-orb {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  position: relative;
  /* --wynn-amp is written by JS each frame from the speech audio's
     RMS amplitude (0..1). When voice is silent or off, it stays 0
     and the orb just runs its idle ambient animations. When voice
     is playing, --wynn-amp drives an extra layer of pulse on the
     halo and core scale, so the orb visibly "speaks" in sync.
     The CSS uses calc() against this variable to amplify a few
     visual properties. JS sets it via element.style.setProperty. */
  --wynn-amp: 0;
  /* Subtle scale follows amplitude — peak amp pushes the whole orb
     to ~1.10x. Transition is fast enough to feel reactive (90ms)
     but smooth enough not to jitter on every RAF tick. */
  transform: scale(calc(1 + var(--wynn-amp) * 0.10));
  transition: transform 0.09s ease-out;
  /* The visible orb itself sits inside this transparent box so we
     can layer rings + halo without breaking the flex parent. */
}
/* When voice is actively driving the orb (--wynn-amp > 0 implicitly),
   intensify halo brightness and core glow. We use calc() against the
   variable directly — no JS toggle class needed. Halo opacity climbs
   with amp, core box-shadow spreads with amp. */
.wynn-portal__header-orb .wynn-orb__halo {
  /* Layer the amp-driven brightness on top of the breathing animation
     by pushing opacity above 1 (clamped by browser at 1, but the
     halo already reads brighter because the radial-gradient inside
     gets amplified by filter: brightness which IS uncapped). */
  filter:
    blur(7px)
    brightness(calc(1 + var(--wynn-amp) * 0.6))
    saturate(calc(1 + var(--wynn-amp) * 0.4));
  transition: filter 0.09s ease-out;
}
.wynn-portal__header-orb .wynn-orb__core {
  /* Core glow expands on amplitude peaks. We disable the
     box-shadow keyframe pulse (wp-orb-core-pulse) on the header orb
     by overriding animation to ONLY run the inner-light drift —
     amplitude-driven glow takes its place. This is intentional:
     when Wynn is silent the orb runs its drift; when voice is
     playing, the box-shadow follows actual amplitude instead of a
     pre-baked pulse. Best of both reads. */
  animation: wp-orb-core-drift 11s ease-in-out infinite;
  box-shadow:
    0 0 calc(12px + var(--wynn-amp) * 14px) rgba(58, 130, 255, calc(0.85 + var(--wynn-amp) * 0.15)),
    0 0 calc(28px + var(--wynn-amp) * 24px) -2px rgba(58, 130, 255, calc(0.55 + var(--wynn-amp) * 0.30)),
    inset 0 0 calc(8px + var(--wynn-amp) * 6px) rgba(180, 220, 255, calc(0.45 + var(--wynn-amp) * 0.25));
  transition: box-shadow 0.09s ease-out;
}
/* Halo — aurora layer that breathes AND cycles hue subtly.
   Goes from cool blue → cyan → blue-violet over 9s. The hue shift
   is small enough to feel "alive" without screaming colored. */
.wynn-orb__halo {
  position: absolute;
  inset: -10px;
  border-radius: 50%;
  background:
    radial-gradient(circle at center,
      rgba(120, 170, 255, 0.50) 0%,
      rgba(80, 140, 230, 0.28) 30%,
      rgba(58, 130, 255, 0.10) 55%,
      rgba(58, 130, 255, 0.0) 75%);
  filter: blur(7px);
  animation:
    wp-orb-halo-breathe 3.2s ease-in-out infinite,
    wp-orb-halo-aurora 9s ease-in-out infinite;
  pointer-events: none;
}
@keyframes wp-orb-halo-breathe {
  0%, 100% { opacity: 0.70; transform: scale(0.92); }
  50%      { opacity: 1.00; transform: scale(1.10); }
}
@keyframes wp-orb-halo-aurora {
  0%, 100% { filter: blur(7px) hue-rotate(0deg); }
  33%      { filter: blur(7px) hue-rotate(15deg); }
  66%      { filter: blur(7px) hue-rotate(-12deg); }
}
.wynn-orb__ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid rgba(120, 170, 255, 0.45);
  pointer-events: none;
}
.wynn-orb__ring--1 {
  inset: 7px;
  border-color: rgba(180, 210, 255, 0.55);
  box-shadow: 0 0 8px rgba(120, 170, 255, 0.45) inset;
}
.wynn-orb__ring--2 {
  inset: 3px;
  border-color: rgba(140, 185, 255, 0.32);
  animation: wp-orb-ring-spin 14s linear infinite reverse;
}
.wynn-orb__ring--3 {
  inset: -1px;
  border-color: rgba(100, 160, 255, 0.18);
  animation: wp-orb-ring-spin 22s linear infinite;
}
@keyframes wp-orb-ring-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
/* Core — the bright cyan-white center. The radial gradient's
   "circle at" position drifts slowly via the wp-orb-core-drift
   animation, which makes the inner light look like it's
   moving inside the orb (mythical/alive, not static). Combined
   with the breathing pulse, it reads as "something living
   inside the sphere." */
.wynn-orb__core {
  position: absolute;
  inset: 10px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 38% 32%,
      rgba(255, 255, 255, 0.98) 0%,
      rgba(230, 240, 255, 0.92) 18%,
      rgba(140, 188, 255, 0.85) 45%,
      rgba(58, 130, 255, 0.75) 75%,
      rgba(20, 50, 110, 0.65) 100%);
  box-shadow:
    0 0 12px rgba(58, 130, 255, 0.85),
    0 0 28px -2px rgba(58, 130, 255, 0.55),
    inset 0 0 8px rgba(180, 220, 255, 0.45);
  animation:
    wp-orb-core-pulse 2.6s ease-in-out infinite,
    wp-orb-core-drift 11s ease-in-out infinite;
}
@keyframes wp-orb-core-pulse {
  /* 2026-05-09 — added scale(0.94 ↔ 1.06) so the orb itself visibly
     expands and contracts with the breath, not just the glow. */
  0%, 100% { transform: scale(0.94); box-shadow: 0 0 12px rgba(58, 130, 255, 0.85), 0 0 28px -2px rgba(58, 130, 255, 0.55), inset 0 0 8px rgba(180, 220, 255, 0.45); }
  50%      { transform: scale(1.06); box-shadow: 0 0 22px rgba(58, 130, 255, 1.00), 0 0 42px -2px rgba(58, 130, 255, 0.78), inset 0 0 10px rgba(200, 230, 255, 0.68); }
}
/* Inner-light drift — the radial gradient's bright spot moves
   slowly across an arc inside the orb. 11s cycle is long enough
   to feel ambient, not animated. */
@keyframes wp-orb-core-drift {
  0%   { background: radial-gradient(circle at 38% 32%, rgba(255,255,255,0.98) 0%, rgba(230,240,255,0.92) 18%, rgba(140,188,255,0.85) 45%, rgba(58,130,255,0.75) 75%, rgba(20,50,110,0.65) 100%); }
  33%  { background: radial-gradient(circle at 50% 28%, rgba(255,255,255,0.98) 0%, rgba(232,244,255,0.92) 18%, rgba(150,195,255,0.85) 45%, rgba(58,130,255,0.75) 75%, rgba(22,52,112,0.65) 100%); }
  66%  { background: radial-gradient(circle at 62% 36%, rgba(255,255,255,0.98) 0%, rgba(228,238,255,0.92) 18%, rgba(135,180,255,0.85) 45%, rgba(58,130,255,0.75) 75%, rgba(20,48,108,0.65) 100%); }
  100% { background: radial-gradient(circle at 38% 32%, rgba(255,255,255,0.98) 0%, rgba(230,240,255,0.92) 18%, rgba(140,188,255,0.85) 45%, rgba(58,130,255,0.75) 75%, rgba(20,50,110,0.65) 100%); }
}

/* Refraction edge — faint chromatic-aberration ring on the inner
   ring suggesting crystal/glass refraction. Makes the orb feel
   like a physical object made of something otherworldly, not a
   flat gradient. */
.wynn-orb__ring--1::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 1px solid transparent;
  background:
    linear-gradient(135deg,
      rgba(255, 80, 200, 0.18) 0%,
      transparent 30%,
      transparent 70%,
      rgba(80, 220, 255, 0.18) 100%) border-box;
  -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  animation: wp-orb-refract 7s ease-in-out infinite;
}
@keyframes wp-orb-refract {
  0%, 100% { opacity: 0.55; transform: rotate(0deg); }
  50%      { opacity: 0.85; transform: rotate(180deg); }
}
.wynn-orb__shimmer {
  position: absolute;
  top: 9px;
  left: 11px;
  width: 12px;
  height: 8px;
  border-radius: 50%;
  background:
    radial-gradient(ellipse at center,
      rgba(255, 255, 255, 0.85) 0%,
      rgba(255, 255, 255, 0.0) 70%);
  filter: blur(1px);
  pointer-events: none;
  animation: wp-orb-shimmer-flicker 4s ease-in-out infinite;
}
@keyframes wp-orb-shimmer-flicker {
  0%, 100% { opacity: 0.85; }
  40%      { opacity: 0.55; }
  60%      { opacity: 0.95; }
}

/* ===== ORB MORPH — flash on the source orb when chat opens =====
   When the user taps an orb on QP, the orb itself gets a brief
   flash + scale-up to read as "this orb is becoming the conversation".
   On close, a softer pulse signals "the chat is collapsing back into
   the orb." Coupled with the modal scaling out from the orb's exact
   coordinates, the effect feels like the orb opened up.
   ============================================================== */
.orb-stage[data-wynn-morphing="true"] {
  animation: wp-source-orb-flash 0.5s cubic-bezier(.18,.7,.28,1.0);
}
.orb-stage[data-wynn-morphing="returning"] {
  animation: wp-source-orb-return 0.6s cubic-bezier(.22,.61,.36,1);
}
@keyframes wp-source-orb-flash {
  0%   { transform: scale(1);    filter: brightness(1) blur(0); }
  35%  { transform: scale(1.18); filter: brightness(1.6) blur(0.5px); }
  100% { transform: scale(1);    filter: brightness(1) blur(0); }
}
@keyframes wp-source-orb-return {
  0%   { transform: scale(0.92); filter: brightness(0.85) blur(1px); }
  60%  { transform: scale(1.08); filter: brightness(1.3) blur(0); }
  100% { transform: scale(1);    filter: brightness(1) blur(0); }
}

/* Wordmark image — premium brand mark for the header */
.wynn-portal__wordmark {
  display: block;
  height: 22px;
  width: auto;
  max-width: 120px;
  object-fit: contain;
  /* Subtle drop-shadow to lift it off the header bg. */
  filter: drop-shadow(0 0 6px rgba(58, 130, 255, 0.25));
}
.wynn-portal__header-text {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.wynn-portal__header-name {
  font-size: calc(15px * var(--qp-text-scale));
  font-weight: 600;
  color: rgba(245, 248, 255, 0.98);
  letter-spacing: 0.01em;
}
.wynn-portal__header-role {
  font-size: calc(11.5px * var(--qp-text-scale));
  color: rgba(180, 200, 240, 0.62);
  letter-spacing: 0.02em;
}
.wynn-portal__close,
.wynn-portal__voice {
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(58, 130, 255, 0.06);
  color: rgba(180, 200, 240, 0.85);
  border: 1px solid rgba(58, 130, 255, 0.30);
  border-radius: 8px;
  cursor: pointer;
  transition: color 0.14s, background-color 0.14s, border-color 0.14s, transform 0.14s;
  padding: 0;
  font-family: inherit;
}
.wynn-portal__close:hover,
.wynn-portal__voice:hover {
  color: rgba(255, 255, 255, 1);
  background: rgba(58, 130, 255, 0.18);
  border-color: rgba(90, 147, 255, 0.65);
  transform: scale(1.06);
}
/* Voice toggle — show one icon at a time based on aria-pressed.
   pressed=true  → speaker-on icon visible (voice is ON)
   pressed=false → speaker-off icon visible (voice is muted)
   When muted, the button gets a subtler treatment so it reads
   "this is currently off" without a hard red strikeout. */
.wynn-portal__voice .wynn-portal__voice-on,
.wynn-portal__voice .wynn-portal__voice-off { display: none; }
.wynn-portal__voice[aria-pressed="true"]  .wynn-portal__voice-on  { display: block; color: var(--twg-blue-hi, #5a93ff); }
.wynn-portal__voice[aria-pressed="false"] .wynn-portal__voice-off { display: block; }
.wynn-portal__voice[aria-pressed="false"] {
  background: transparent;
  border-color: rgba(120, 130, 150, 0.30);
  color: rgba(150, 160, 180, 0.65);
}

/* LOG — scrollable message area */
.wynn-portal__log {
  flex: 1 1 auto;
  /* min-height: 0 is REQUIRED for a flex child to shrink below
     its content size. Without it, a log full of messages would
     refuse to compress when the keyboard opens, and the input
     row would be pushed off the bottom of the modal. With it,
     the log absorbs all the height loss and just becomes more
     scrollable — exactly what we want. */
  min-height: 0;
  overflow-y: auto;
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
}

/* Message bubbles — user gets the BLUE bubble, assistant gets a
   neutral dark bubble with subtle blue rim. Inverted from before. */
.wynn-msg {
  display: flex;
  width: 100%;
}
.wynn-msg.user { justify-content: flex-end; }
.wynn-msg.assistant { justify-content: flex-start; }
.wynn-msg .bubble {
  max-width: 84%;
  padding: 10px 14px;
  border-radius: 14px;
  font-size: calc(14px * var(--qp-text-scale));
  line-height: 1.5;
  letter-spacing: 0.005em;
  white-space: pre-wrap;
  word-wrap: break-word;
}
.wynn-msg.user .bubble {
  background: linear-gradient(180deg, rgba(58, 130, 255, 0.85) 0%, rgba(58, 130, 255, 0.65) 100%);
  border: 1px solid rgba(120, 170, 255, 0.55);
  color: rgba(255, 255, 255, 0.98);
  border-bottom-right-radius: 4px;
  box-shadow: 0 4px 14px -4px rgba(58, 130, 255, 0.55);
}
.wynn-msg.assistant .bubble {
  background: rgba(20, 28, 50, 0.62);
  border: 1px solid rgba(58, 130, 255, 0.20);
  color: rgba(245, 248, 255, 0.95);
  border-bottom-left-radius: 4px;
}
.wynn-msg .bubble[data-typing="true"]::after {
  content: '';
  display: inline-block;
  width: 6px;
  height: 12px;
  background: var(--twg-blue-hi, #5a93ff);
  margin-left: 2px;
  vertical-align: middle;
  opacity: 0.85;
  animation: wp-caret-blink 0.8s steps(2) infinite;
}
@keyframes wp-caret-blink {
  0%, 50% { opacity: 0.85; }
  51%, 100% { opacity: 0; }
}

/* THINKING pill — shown while Sonnet processes. Italic phrase
   ("I'm taking it all in…" / "Making sure I have this correct…")
   followed by three pulsing dots. Reads as a real consultant
   pausing to process, not a system loading-state. */
.wynn-msg--thinking {
  justify-content: center;
  padding: 4px 0;
  animation: wp-thinking-fade-in 0.32s cubic-bezier(.22,.61,.36,1);
}
.wynn-msg--thinking.wynn-msg--leaving {
  animation: wp-thinking-fade-out 0.22s cubic-bezier(.22,.61,.36,1) forwards;
}
@keyframes wp-thinking-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes wp-thinking-fade-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-4px); }
}
.wynn-thinking {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  background: rgba(58, 130, 255, 0.06);
  border: 1px solid rgba(58, 130, 255, 0.18);
  border-radius: 999px;
  font-size: 13px;
  font-style: italic;
  color: rgba(180, 200, 240, 0.78);
  letter-spacing: 0.01em;
}
.wynn-thinking__dots {
  display: inline-flex;
  gap: 3px;
  align-items: center;
}
.wynn-thinking__dots span {
  display: inline-block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(120, 170, 255, 0.85);
  animation: wp-thinking-dot 1.4s ease-in-out infinite;
}
.wynn-thinking__dots span:nth-child(2) { animation-delay: 0.2s; }
.wynn-thinking__dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes wp-thinking-dot {
  0%, 60%, 100% { opacity: 0.30; transform: scale(0.85); }
  30%          { opacity: 1.00; transform: scale(1.10); }
}

/* CHIPS — quick-reply suggestions, all blue */
.wynn-portal__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 20px 4px;
  min-height: 0;
  /* Pin so it doesn't compress when the keyboard opens. */
  flex: 0 0 auto;
}
.wynn-portal__chips:empty { padding: 0; }
.wynn-chip {
  padding: 6px 12px;
  background: rgba(58, 130, 255, 0.10);
  border: 1px solid rgba(58, 130, 255, 0.40);
  border-radius: 999px;
  color: rgba(180, 210, 255, 0.92);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.14s, border-color 0.14s, color 0.14s, transform 0.14s;
  font-family: inherit;
}
.wynn-chip:hover {
  background: rgba(58, 130, 255, 0.22);
  border-color: rgba(120, 170, 255, 0.75);
  color: rgba(255, 255, 255, 1);
  transform: translateY(-1px);
}

/* INPUT ROW — pinned at the bottom of the modal flex column.
   Never shrinks when the keyboard opens. The textarea + send
   button stay visible right above the on-screen keyboard. */
.wynn-portal__input-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  padding: 14px 18px 16px;
  border-top: 1px solid rgba(58, 130, 255, 0.20);
  background: rgba(2, 6, 14, 0.40);
  flex: 0 0 auto;
}
.wynn-portal__input {
  flex: 1 1 auto;
  resize: none;
  padding: 11px 14px;
  background: rgba(8, 14, 28, 0.72);
  border: 1px solid rgba(58, 130, 255, 0.25);
  border-radius: 10px;
  color: rgba(245, 248, 255, 0.98);
  font-size: 14px;
  font-family: inherit;
  line-height: 1.4;
  min-height: 42px;
  max-height: 120px;
  outline: none;
  transition: border-color 0.14s, background-color 0.14s, box-shadow 0.14s;
}
.wynn-portal__input:focus {
  border-color: rgba(120, 170, 255, 0.75);
  background: rgba(8, 14, 28, 0.92);
  box-shadow: 0 0 0 3px rgba(58, 130, 255, 0.15);
}
.wynn-portal__input::placeholder {
  color: rgba(180, 200, 240, 0.40);
}
.wynn-portal__send {
  flex: 0 0 42px;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg, #5a93ff 0%, #3478ff 100%);
  color: #ffffff;
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  transition: transform 0.14s ease, box-shadow 0.14s ease, filter 0.14s;
  box-shadow:
    0 -1px 0 rgba(180, 210, 255, 0.55) inset,
    0 4px 14px -4px rgba(58, 130, 255, 0.65);
}
.wynn-portal__send:hover {
  transform: translateY(-1px);
  filter: brightness(1.08);
  box-shadow:
    0 -1px 0 rgba(180, 210, 255, 0.75) inset,
    0 8px 20px -4px rgba(58, 130, 255, 0.85);
}

/* ===== ORB REMINDER — caption that overlaps the lower third
   of the orb itself, like a thought bubble emanating from
   Wynn. Per Rick 2026-05-08: "show up OVERLAPPING (centered
   between middle and lower edge) the lower third of the orb."
   Anchored to .orb-stage via position:absolute. The vertical
   position uses top: 66% so it sits between the middle and
   the lower edge of the orb (lower third), centered. */
.wynn-portal-hint {
  position: absolute;
  top: 66%;
  left: 50%;
  transform: translate(-50%, 4px);
  white-space: nowrap;
  padding: 7px 14px;
  background: rgba(8, 14, 28, 0.92);
  border: 1px solid rgba(58, 130, 255, 0.55);
  border-radius: 999px;
  color: rgba(220, 235, 255, 0.98);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  pointer-events: none;
  opacity: 0;
  transition:
    opacity 0.7s cubic-bezier(.22, .61, .36, 1),
    transform 0.7s cubic-bezier(.22, .61, .36, 1);
  z-index: 10;
  /* Soft glow + a subtle backdrop blur so the pill reads
     cleanly even when overlapping the bright orb core. */
  -webkit-backdrop-filter: blur(8px) saturate(1.1);
  backdrop-filter: blur(8px) saturate(1.1);
  box-shadow:
    0 0 22px -4px rgba(58, 130, 255, 0.55),
    0 4px 14px -6px rgba(0, 0, 0, 0.7);
}
.wynn-portal-hint[data-visible="true"] {
  opacity: 1;
  transform: translate(-50%, 0);
}
/* Prominent first-of-session hint — slightly larger, brighter
   border, but still sits over the orb's lower third for
   continuity with the recurring hints. */
.wynn-portal-hint[data-prominent="true"] {
  font-size: 13px;
  padding: 8px 16px;
  background: rgba(12, 22, 42, 0.94);
  border-color: rgba(120, 170, 255, 0.70);
  box-shadow:
    0 0 28px -4px rgba(58, 130, 255, 0.70),
    0 4px 18px -6px rgba(0, 0, 0, 0.7);
}

/* ===== WELCOME CINEMATIC — first-ever-visit intro =====
   Same shape as CB's WynnWelcome but in QP-blue. Fires ONCE per
   browser the very first time the homeowner sees the orb. Three
   numbered points cascade in. "Let's go." CTA dismisses. Skip
   link top-right. localStorage-gated. After this fires once, the
   orb-hint takes over for ongoing reminders. */
.wynn-welcome-qp {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0;
  transition: opacity 600ms cubic-bezier(.22, .61, .36, 1);
}
.wynn-welcome-qp[data-visible="true"] {
  pointer-events: auto;
  opacity: 1;
}
.wynn-welcome-qp__backdrop {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 60% 45% at 50% 38%,
      rgba(58, 130, 255, 0.10) 0%,
      rgba(0, 0, 0, 0) 50%,
      transparent 70%),
    rgba(0, 0, 0, 0.84);
  -webkit-backdrop-filter: blur(12px) saturate(0.85);
  backdrop-filter: blur(12px) saturate(0.85);
}
.wynn-welcome-qp__skip {
  position: absolute; top: 22px; right: 22px;
  /* z-index above stage (which is also absolute inset:0 and would
     otherwise capture hover/clicks meant for the skip button). */
  z-index: 2;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  font-family: inherit;
  font-size: 13px; font-weight: 400;
  color: rgba(180, 200, 240, 0.65);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(58, 130, 255, 0.25);
  border-radius: 100px;
  cursor: pointer;
  transition: color 220ms ease, background 220ms ease, border-color 220ms ease;
}
.wynn-welcome-qp__skip:hover {
  color: rgba(220, 230, 250, 0.95);
  background: rgba(58, 130, 255, 0.10);
  border-color: rgba(120, 170, 255, 0.55);
}
.wynn-welcome-qp__stage {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 80px 28px 60px;
  text-align: center;
  gap: 28px;
}
@media (max-width: 540px) {
  .wynn-welcome-qp__stage { padding: 60px 20px 40px; gap: 22px; }
}
/* Mini hero orb — multi-ring construction in QP-blue. Larger
   than the chat header orb because this is the moment when Wynn
   is introducing himself for the first time. */
.wynn-welcome-qp__orb {
  width: clamp(180px, 30vmin, 260px);
  height: clamp(180px, 30vmin, 260px);
  position: relative;
  display: flex; align-items: center; justify-content: center;
  isolation: isolate;
  opacity: 0; transform: scale(0.92);
  animation: wynn-welcome-qp-orb-arrive 900ms 200ms cubic-bezier(.34, 1.45, .64, 1) forwards;
}
@keyframes wynn-welcome-qp-orb-arrive {
  to { opacity: 1; transform: scale(1); }
}
/* Parallax inner — receives the cursor-tracking translate3d
   transform from JS so it doesn't fight the outer orb's
   arrival scale animation. Same trick the page orb uses
   (.orb-parallax inside .orb-stage). */
.wynn-welcome-qp__orb-parallax {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  transform: translate3d(0, 0, 0);
  transition: transform 220ms cubic-bezier(.22, .61, .36, 1);
  will-change: transform;
}
.wynn-welcome-qp__orb-ring {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}
/* Ring breathing — each ring scales and dims slightly out of sync
   so the orb feels alive even when voice is off. Same pattern as
   the page orb (ring-breathe keyframe in styles.html), tuned per
   layer with different periods so the breaths overlap organically.
   Per Rick 2026-05-08: "Wynn is alive, always". */
.wynn-welcome-qp__orb-ring--6 { width: 100%; height: 100%; background: radial-gradient(circle, rgba(58, 130, 255, 0.10) 0%, rgba(58, 130, 255, 0.02) 55%, transparent 70%); animation: wynn-welcome-qp-ring-breathe 5.5s ease-in-out -2s infinite; }
.wynn-welcome-qp__orb-ring--5 { width: 80%; height: 80%; background: radial-gradient(circle, rgba(58, 130, 255, 0.14) 0%, rgba(58, 130, 255, 0.04) 60%, transparent 75%); border: 1px solid rgba(58, 130, 255, 0.10); animation: wynn-welcome-qp-ring-breathe 4.6s ease-in-out -1s infinite; }
.wynn-welcome-qp__orb-ring--4 { width: 62%; height: 62%; background: radial-gradient(circle, rgba(58, 130, 255, 0.20) 0%, rgba(58, 130, 255, 0.06) 65%, transparent 80%); border: 1px solid rgba(58, 130, 255, 0.16); animation: wynn-welcome-qp-ring-breathe 4.0s ease-in-out -0.4s infinite; }
.wynn-welcome-qp__orb-ring--3 { width: 47%; height: 47%; background: radial-gradient(circle, rgba(58, 130, 255, 0.32) 0%, rgba(58, 130, 255, 0.10) 65%, transparent 85%); border: 1px solid rgba(58, 130, 255, 0.22); animation: wynn-welcome-qp-ring-breathe 3.6s ease-in-out -1.5s infinite; }
.wynn-welcome-qp__orb-ring--2 { width: 35%; height: 35%; background: radial-gradient(circle, rgba(58, 130, 255, 0.42) 0%, rgba(58, 130, 255, 0.18) 70%, transparent 95%); border: 1px solid rgba(58, 130, 255, 0.32); animation: wynn-welcome-qp-ring-breathe 3.2s ease-in-out -0.8s infinite; }
.wynn-welcome-qp__orb-ring--1 {
  width: 26%; height: 26%;
  background: radial-gradient(circle at 38% 32%, rgba(255, 255, 255, 0.95) 0%, rgba(165, 195, 255, 0.55) 25%, rgba(58, 130, 255, 0.45) 50%, rgba(31, 85, 201, 0.25) 80%, transparent 100%);
  box-shadow: 0 0 60px 8px rgba(58, 130, 255, 0.55), 0 0 120px 28px rgba(58, 130, 255, 0.32);
  animation: wynn-welcome-qp-ring-breathe 2.8s ease-in-out 0s infinite;
}
/* Bumped 2026-05-09 — more visible breathing. Was scale(1.05)/0.88,
   now scale(1.10)/0.74 so the orb noticeably expands and contracts. */
@keyframes wynn-welcome-qp-ring-breathe {
  0%, 100% { transform: scale(1.0); opacity: 1.0; }
  50%      { transform: scale(1.10); opacity: 0.74; }
}
.wynn-welcome-qp__orb-core {
  position: absolute;
  width: 12%; height: 12%;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 28%, #ffffff 0%, #e6efff 35%, #6fa0ff 70%, #2a6df4 100%);
  box-shadow: 0 0 24px 4px rgba(255, 255, 255, 0.45), 0 0 48px 12px rgba(58, 130, 255, 0.55);
  z-index: 2;
  animation: wynn-welcome-qp-core-pulse 4s ease-in-out infinite;
}
@keyframes wynn-welcome-qp-core-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.08); }
}

/* CINEMATIC ORB — audio-reactive (rev 2026-05-10).
   When body[data-wynn-speaking="true"] is set (host page speakWynn
   sets it during ElevenLabs playback) AND --wynn-amp is being driven
   from the audio analyser, the cinematic orb reacts in sync with the
   words — same pattern as the page orb. Used by the "Alright! Let's
   get to it!" demo line so the orb visibly pulses with Wynn's voice
   the moment voice mode is toggled on. The amp variable is set on
   document.body, inheritable here. */
body[data-wynn-speaking="true"] .wynn-welcome-qp__orb-core {
  transform: scale(calc(1.05 + var(--wynn-amp, 0) * 0.18));
  filter: brightness(calc(1.20 + var(--wynn-amp, 0) * 0.40));
  transition: transform 30ms linear, filter 30ms linear;
}
body[data-wynn-speaking="true"] .wynn-welcome-qp__orb-ring--2,
body[data-wynn-speaking="true"] .wynn-welcome-qp__orb-ring--3 {
  transform: scale(calc(1.04 + var(--wynn-amp, 0) * 0.20));
  filter: brightness(calc(1.25 + var(--wynn-amp, 0) * 0.45));
  transition: transform 40ms linear, filter 40ms linear;
}
body[data-wynn-speaking="true"] .wynn-welcome-qp__orb-ring--4,
body[data-wynn-speaking="true"] .wynn-welcome-qp__orb-ring--5,
body[data-wynn-speaking="true"] .wynn-welcome-qp__orb-ring--6 {
  filter: brightness(calc(1.18 + var(--wynn-amp, 0) * 0.32)) saturate(calc(1.10 + var(--wynn-amp, 0) * 0.45));
  transition: filter 60ms linear;
}

/* Reduced-motion users — kill the breathing animations so we
   don't fight their accessibility preference. The orb stays
   still but visible; voice + parallax (if pointer is fine)
   still convey presence. */
@media (prefers-reduced-motion: reduce) {
  .wynn-welcome-qp__orb-ring--1,
  .wynn-welcome-qp__orb-ring--2,
  .wynn-welcome-qp__orb-ring--3,
  .wynn-welcome-qp__orb-ring--4,
  .wynn-welcome-qp__orb-ring--5,
  .wynn-welcome-qp__orb-ring--6,
  .wynn-welcome-qp__orb-core {
    animation: none;
  }
}
/* Hero block — hello line + sub line stacked. The sub carries
   the promise: "real range, about 2 minutes." Front and center
   so users see the value prop before the cascading points. */
.wynn-welcome-qp__hero {
  display: flex; flex-direction: column;
  align-items: center; gap: 10px;
  max-width: 720px;
}
/* Hero — typewriter animation matches every other QP hero
   (typePrompt rules: char-by-char, 28ms per char, 180ms on
   punctuation, voice 50ms behind first char, line turns blue
   while audio plays via wynn-line--clickable[data-playing]).
   The h2 itself is opacity:1 from the start — the typed text
   span fills in character-by-character, the caret blinks
   beside it, and clicking the line replays the audio. */
/* Hero base — common rules. The two scale variants
   (--identity = large, --promise = smaller) override font-size +
   max-width. Same h2 element gets reused across both lines via JS
   class swap, so transitions between them are instant on the
   element level (only opacity animates between phases via the
   --leaving modifier). */
.wynn-welcome-qp__hello {
  font-family: 'Instrument Serif', Georgia, 'Times New Roman', serif;
  line-height: 1.12;
  letter-spacing: -0.012em;
  color: rgba(245, 248, 255, 0.98);
  margin: 0;
  text-wrap: balance;
  /* The hero is wynn-line--clickable, so the global QP rule
     gives it cursor:pointer + transition on color. It also gets
     data-playing="true" while audio plays, turning the whole
     line blue. Both rules already exist in styles.html. */
  /* Smooth opacity transition between sequence phases — the
     --leaving modifier sets opacity 0, otherwise it's 1. */
  opacity: 1;
  transition: opacity 380ms cubic-bezier(.22, .61, .36, 1),
              font-size 320ms cubic-bezier(.22, .61, .36, 1),
              max-width 320ms cubic-bezier(.22, .61, .36, 1);
}
/* Hero scales (rev 2026-05-10):
   - identity + promise share the SAME size and style. They are the
     two main beats of the cinematic and any size shift between them
     was making the orb visibly jump up/down to recenter. Per Rick:
     "I don't like the second line smaller... the orb gets pushed
     around." Locked at the larger scale so the cinematic feels
     deliberate and steady.
   - handoff is allowed to step down — by the time it shows, the
     three info-tile rows are about to appear below it, so a smaller
     line reads as a quieter handoff cue. Per Rick: "I don't mind if
     '3 quick things before we begin' is smaller."
   - min-height is locked to the LARGER scale's height so the slot
     never resizes between identity and promise. The orb stays put. */
.wynn-welcome-qp__hello--identity,
.wynn-welcome-qp__hello--promise {
  font-size: calc(clamp(30px, 4.2vw, 44px) * var(--qp-text-scale));
  max-width: 760px;
  line-height: 1.18;
  min-height: calc(1.18em * 2); /* room for up to 2 wrapped lines so
                                    promise (longer) doesnt push orb */
  color: rgba(245, 248, 255, 0.98);
  font-style: normal;
}
/* Handoff scale — line 3 only. Smaller, italic, quieter — pairs
   with the info-tile rows that follow. */
.wynn-welcome-qp__hello--handoff {
  font-size: calc(clamp(20px, 2.4vw, 28px) * var(--qp-text-scale));
  font-style: italic;
  max-width: 640px;
  line-height: 1.32;
  min-height: calc(1.18em * 2); /* MATCH identity/promise so the orb
                                    doesnt move when handoff lands */
  color: rgba(220, 235, 255, 0.92);
}
/* Leaving state — set during the fade-out beat between lines 1
   and 2. Opacity drops to 0 over 380ms (matches HERO_LINE_FADE_OUT_MS
   in JS); during this window the typed text stays in place but
   invisible so the layout doesn't jump as the slot empties. */
.wynn-welcome-qp__hello--leaving {
  opacity: 0;
}
/* Caret — same shape and blink as QP's .prompt__caret, scaled
   to match the cinematic hero font size. Sits right after the
   typed text. Blinks while typing, fades out with data-finished. */
.wynn-welcome-qp__caret {
  display: inline-block;
  width: 3px;
  height: 0.92em;
  background: var(--twg-blue-hi, #5a93ff);
  vertical-align: -0.10em;
  margin-left: 6px;
  border-radius: 1px;
  animation: wynn-welcome-qp-caret-blink 1s steps(1, end) infinite;
}
.wynn-welcome-qp__caret[data-finished="true"] {
  animation: wynn-welcome-qp-caret-fade 600ms 300ms forwards;
}
@keyframes wynn-welcome-qp-caret-blink { 50% { opacity: 0; } }
@keyframes wynn-welcome-qp-caret-fade { to { opacity: 0; transform: scaleY(0); } }
/* Hero gets the same blue-while-reading rule as QP's heroes.
   The wynn-line--clickable class on the h2 already gives it
   cursor:pointer and the data-playing="true" → blue rule is
   defined in styles.html (lines 192-195). The selector below
   is a defensive fallback in case wynn-portal.css loads in a
   page where styles.html's rules aren't present. */
.wynn-welcome-qp__hello.wynn-line--clickable[data-playing="true"] {
  color: var(--twg-blue-hi, #5a93ff);
}
/* The static .wynn-welcome-qp__sub element used to live here. As
   of v7 the cinematic chains three typewriter beats (identity →
   promise → handoff) all in the same hero h2 slot, so the static
   sub paragraph is no longer needed. The handoff line ("3 quick
   things before we begin.") is now the third typewriter beat
   driven by _runHeroSequence. Keeping this comment as a marker
   in case anyone goes looking for the old sub styling. */
.wynn-welcome-qp__sub em {
  font-family: 'Instrument Serif', Georgia, serif;
  font-style: italic;
  color: rgba(160, 200, 255, 0.95);
  font-weight: 400;
  font-size: calc(1.08em * var(--qp-text-scale));
  letter-spacing: 0;
}
@keyframes wynn-welcome-qp-fade-up {
  to { opacity: 1; transform: translateY(0); }
}
.wynn-welcome-qp__points {
  display: flex; flex-direction: column;
  gap: 16px;
  width: 100%;
  max-width: 620px;
  margin: 0 auto;
}
@media (max-width: 540px) {
  .wynn-welcome-qp__points { gap: 12px; max-width: 480px; }
}
/* Numbered points — held invisible until the JS sequencer flips
   the parent's data-revealed="true" AFTER the sub-line lands.
   The cascade runs as three staggered fade-ups, but only when
   the parent flag is set — no more racing the typewriter. */
.wynn-welcome-qp__point {
  display: flex; align-items: flex-start; gap: 16px;
  text-align: left;
  padding: 14px 18px;
  border-radius: 14px;
  background: rgba(58, 130, 255, 0.04);
  border: 1px solid rgba(58, 130, 255, 0.20);
  -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
  opacity: 0;
  transform: translateY(10px);
  /* No animation by default — parent flag triggers it. */
}
.wynn-welcome-qp__points[data-revealed="true"] .wynn-welcome-qp__point {
  animation: wynn-welcome-qp-fade-up 600ms cubic-bezier(.22, .61, .36, 1) forwards;
}
/* Per-point cascade delays. Was 800ms / 1600ms — too fast, the
   user couldn't finish reading point 1 before point 2 arrived.
   Bumped to 1800ms / 3600ms so each point gets ~1.8s of solo
   screen time. Sequencer's CTA-after-cascade timer is bumped
   to match below so the CTA still lands AFTER point 3. */
.wynn-welcome-qp__points[data-revealed="true"] .wynn-welcome-qp__point:nth-child(1) { animation-delay: 0ms; }
.wynn-welcome-qp__points[data-revealed="true"] .wynn-welcome-qp__point:nth-child(2) { animation-delay: 1800ms; }
.wynn-welcome-qp__points[data-revealed="true"] .wynn-welcome-qp__point:nth-child(3) { animation-delay: 3600ms; }
.wynn-welcome-qp__point-num {
  width: 28px; height: 28px;
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: rgba(58, 130, 255, 0.16);
  border: 1px solid rgba(120, 170, 255, 0.45);
  font-family: 'Instrument Serif', Georgia, serif;
  font-size: calc(14px * var(--qp-text-scale)); font-weight: 400;
  color: rgba(180, 210, 255, 0.95);
}
.wynn-welcome-qp__point-text {
  flex: 1;
  font-family: inherit;
  font-size: calc(16px * var(--qp-text-scale));
  line-height: 1.55;
  color: rgba(220, 230, 250, 0.92);
  margin: 0;
}
.wynn-welcome-qp__point-text em {
  font-family: 'Instrument Serif', Georgia, serif;
  font-style: italic;
  color: rgba(160, 200, 255, 0.95);
  font-size: calc(17px * var(--qp-text-scale));
  font-weight: 400;
}
@media (max-width: 540px) {
  .wynn-welcome-qp__point-text { font-size: calc(15px * var(--qp-text-scale)); }
  .wynn-welcome-qp__point-text em { font-size: calc(16px * var(--qp-text-scale)); }
}

/* Point #3 with inline voice toggle — text on left, toggle pill
   on right, all inside the same numbered card. Same shape as
   CB's WynnWelcome point #3. */
.wynn-welcome-qp__point--has-toggle {
  align-items: center;
  gap: 14px;
}
.wynn-welcome-qp__point--has-toggle .wynn-welcome-qp__point-text {
  flex: 1;
}
.wynn-welcome-qp__voice-inline {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 11px;
  flex-shrink: 0;
  font-family: inherit;
  font-size: 12px; font-weight: 500;
  letter-spacing: 0.04em;
  color: rgba(180, 200, 240, 0.78);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(58, 130, 255, 0.25);
  border-radius: 100px;
  cursor: pointer;
  transition:
    color 220ms cubic-bezier(.22,.61,.36,1),
    background 220ms cubic-bezier(.22,.61,.36,1),
    border-color 220ms cubic-bezier(.22,.61,.36,1),
    transform 220ms cubic-bezier(.22,.61,.36,1),
    box-shadow 220ms cubic-bezier(.22,.61,.36,1);
}
.wynn-welcome-qp__voice-inline:hover {
  color: rgba(245, 248, 255, 1);
  background: rgba(58, 130, 255, 0.10);
  border-color: rgba(120, 170, 255, 0.55);
  transform: translateY(-1px);
}
.wynn-welcome-qp__voice-inline[aria-pressed="true"] {
  color: rgba(180, 210, 255, 1);
  background: rgba(58, 130, 255, 0.14);
  border-color: rgba(90, 147, 255, 0.65);
  box-shadow: 0 0 0 0 rgba(58, 130, 255, 0);
}
.wynn-welcome-qp__voice-inline[aria-pressed="true"]:hover {
  background: rgba(58, 130, 255, 0.22);
  box-shadow: 0 0 16px -2px rgba(58, 130, 255, 0.55);
}
.wynn-welcome-qp__voice-inline-icon { display: inline-flex; align-items: center; }
.wynn-welcome-qp__voice-inline-label { font-weight: 500; }

/* Attention pulse — fires when point #3 has finished its
   cascade-in. Bright blue breaths with a ring-pulse outward
   so the toggle reads as alive and interactive. Five iterations
   so even users skimming will catch one. Stops the moment the
   toggle is pressed (aria-pressed flips, the ":not(...)"
   selector below excludes the pressed state). */
.wynn-welcome-qp__voice-inline:not([aria-pressed="true"])[data-pulse="true"] {
  animation: wynn-welcome-qp-voice-attention 1.6s ease-in-out 5;
}
@keyframes wynn-welcome-qp-voice-attention {
  0% {
    box-shadow:
      0 0 0 0 rgba(58, 130, 255, 0.55),
      0 0 0 0 rgba(58, 130, 255, 0.0);
    border-color: rgba(58, 130, 255, 0.25);
    color: rgba(180, 200, 240, 0.78);
    transform: scale(1);
  }
  35% {
    box-shadow:
      0 0 0 8px rgba(58, 130, 255, 0.0),
      0 0 28px 4px rgba(58, 130, 255, 0.85);
    border-color: rgba(140, 185, 255, 0.95);
    color: rgba(235, 245, 255, 1);
    transform: scale(1.05);
  }
  70% {
    box-shadow:
      0 0 0 14px rgba(58, 130, 255, 0.0),
      0 0 18px 2px rgba(58, 130, 255, 0.45);
    border-color: rgba(120, 170, 255, 0.65);
    color: rgba(220, 235, 255, 0.95);
    transform: scale(1.02);
  }
  100% {
    box-shadow:
      0 0 0 18px rgba(58, 130, 255, 0.0),
      0 0 0 0 rgba(58, 130, 255, 0.0);
    border-color: rgba(58, 130, 255, 0.25);
    color: rgba(180, 200, 240, 0.78);
    transform: scale(1);
  }
}
@media (max-width: 540px) {
  .wynn-welcome-qp__voice-inline { padding: 5px 10px; font-size: 11px; gap: 6px; }
  .wynn-welcome-qp__skip { padding: 6px 11px; font-size: 12px; }
}
.wynn-welcome-qp__cta {
  display: inline-flex; align-items: center; gap: 12px;
  height: 54px;
  padding: 0 28px;
  border-radius: 100px;
  background: linear-gradient(180deg, #5a93ff 0%, #3478ff 100%);
  color: #ffffff;
  border: 1px solid rgba(120, 170, 255, 0.65);
  font-family: inherit;
  font-size: calc(16px * var(--qp-text-scale)); font-weight: 500;
  cursor: pointer;
  /* Held invisible until JS sets data-revealed="true" after the
     points cascade lands. Auto-fire animation-delay removed —
     the sequencer paces this beat now. */
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  box-shadow:
    0 -1px 0 rgba(180, 210, 255, 0.55) inset,
    0 16px 40px -18px rgba(58, 130, 255, 0.65);
  transition: transform 240ms ease, box-shadow 240ms ease, filter 240ms ease,
              opacity 600ms cubic-bezier(.22, .61, .36, 1);
}
.wynn-welcome-qp__cta[data-revealed="true"] {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.wynn-welcome-qp__cta:hover {
  filter: brightness(1.08);
  transform: translateY(-1px);
  box-shadow:
    0 -1px 0 rgba(180, 210, 255, 0.75) inset,
    0 22px 50px -18px rgba(58, 130, 255, 0.85);
}

/* Reduced motion — kill animations */
@media (prefers-reduced-motion: reduce) {
  .wynn-portal-overlay,
  .wynn-portal-overlay__modal,
  .wynn-portal__header-orb {
    transition: none !important;
    animation: none !important;
  }
}
