/* Doris Scenes — shared scene styling.
   Purple and plum are homage and accent. Gold is the structure.
   Nothing here fakes photorealistic content; the plate carries the world. */

:root {
  --gold-1: #f6e3a8;
  --gold-2: #d9ae52;
  --gold-3: #9a7526;
  --cream:  #f6efdf;
  --plum:   #3a1436;
  --plum-2: #1d0a1c;
  --ink:    #0b0709;
  --green-d:#0d1a12;

  --serif: "Cormorant Garamond", "Hoefler Text", Georgia, "Times New Roman", serif;
  --script: "Pinyon Script", "Snell Roundhand", "Apple Chancery", cursive;
  --sans: "Optima", "Gill Sans", "Segoe UI", system-ui, sans-serif;

  --ease: cubic-bezier(.22,.61,.36,1);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--ink); color: var(--cream); }
body { font-family: var(--sans); -webkit-font-smoothing: antialiased; overflow-x: hidden; }
a { color: inherit; text-decoration: none; }

/* ---------- the stage ---------- */
.stage {
  position: relative;
  width: 100%;
  max-width: 1760px;
  margin: 0 auto;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: #05040a;
}
/* FEI-DORIS-CELEBRATION-REBALANCE 2026-09-09: real, previously-undiscovered
   bug found while verifying the owner's "no scroll to reach Enter" rule --
   at the common 1920x1080 desktop breakpoint the 3:2-aspect stage (capped
   at 1760px wide) rendered 1173px tall, taller than the 1080px viewport;
   at 1440x900 it rendered 960px tall against a 900px viewport. Either way
   the footer line -- and at some window heights the action row itself --
   fell below the fold, silently requiring a scroll the design never
   intended. Rather than touch the 3:2 aspect ratio (that would crop the
   approved plate images differently than they were rendered/approved),
   the stage's WIDTH is capped so its derived height never exceeds a safe
   viewport-height budget; it simply letterboxes (extra dark margin, not a
   crop) on short/wide windows instead of overflowing. Flow mode (mobile/
   tablet) is untouched -- .flow .stage below already sets its own
   aspect-ratio/max-height and is more specific, so this rule has no effect
   there regardless. */
body:not(.flow) .stage {
  width: min(1760px, 100vw, 147vh);
}
.stage__plate {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* At rest the plate is exactly the approved framing — no zoom. The small
     scale only comes in while a pointer is moving, so parallax never has an
     edge to expose, and a screenshot can be compared to the render 1:1. */
  transform: none;
  transition: transform 1.2s var(--ease), opacity .8s ease;
  will-change: transform;
}
.stage__plate.is-loading { opacity: 0; }
.stage__plate.is-parallax { transform: scale(1.035); }
.stage__vignette {
  position: absolute; inset: 0; pointer-events: none;
  /* Kept deliberately faint. The approved render is the acceptance target,
     so the vignette may seat the objects, never restate the grade.
     FEI-DORIS-CELEBRATION-REBALANCE 2026-09-09: eased further per the
     owner's "brighten substantially, reduce the heavy black/dark-green
     center" directive -- same shape, lower opacity. */
  background:
    radial-gradient(125% 95% at 30% 35%, transparent 66%, rgba(4,2,6,.10) 100%),
    linear-gradient(to bottom, transparent 84%, rgba(4,2,6,.16) 100%);
}
/* FEI-DORIS-CELEBRATION-REBALANCE 2026-09-09: a soft, purely atmospheric
   warm wash -- golden-hour light quality, not a drawn/procedural scene
   element (no invented leaves/flowers/birds here; the owner has twice
   rejected CSS standing in for real art). Scoped to .scene-celebration
   only (see scene-engine.js render()). mix-blend-mode:soft-light means it
   can only ever warm/lighten what is already in the approved plate --  it
   has no pixels of its own to misread as fake foliage. */
.scene-celebration .stage__glow {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(120% 85% at 50% 12%, rgba(255,205,130,.16), transparent 58%),
    linear-gradient(180deg, rgba(255,190,110,.09), transparent 32%);
  mix-blend-mode: soft-light;
  opacity: .85;
  animation: celebrationGlowDrift 26s ease-in-out infinite;
}
@keyframes celebrationGlowDrift {
  0%, 100% { opacity: .7; transform: translateY(0); }
  50%      { opacity: 1;  transform: translateY(-1.5%); }
}

/* an object is placed by the engine; the CSS only styles it */
.obj { position: absolute; }
.obj--hidden { display: none; }

/* ---------- title ---------- */
.title { text-align: center; display: flex; flex-direction: column; justify-content: flex-start; }
/* FEI-DORIS-CELEBRATION-REBALANCE 2026-09-09 (owner directive): this scrim,
   not the vignette above, was the real source of the "heavy black/
   dark-green center" complaint -- its negative inset bled left far enough
   to sit directly on top of the hero photograph's right edge, which is
   also what created the "hard vertical collision" the directive calls out
   (a dark rectangle overlapping her photo instead of a soft blend). Fixed
   two ways: the left bleed is pulled back so the scrim no longer reaches
   onto the hero photo at all (the .heroplane mask below now owns that
   transition), and the color/opacity is warmed and lightened to match
   "golden hour," not funeral black. */
.title::before {
  content: ""; position: absolute; inset: -6% 8% -10% 2%; z-index: -1;
  background: radial-gradient(60% 62% at 54% 45%, rgba(30,18,10,.5), rgba(30,18,10,0) 72%);
}
.title__eyebrow {
  font-size: clamp(8px, .95vw, 15px); letter-spacing: .34em; color: var(--cream);
  line-height: 1.9; opacity: .62;
}
.title__rule { height: 1px; margin: .5em auto; width: 78%;
  background: linear-gradient(to right, transparent, var(--gold-2), transparent); }
/* FEI-DORIS-CELEBRATION-REBALANCE 2026-09-09 (owner directive: "Primary:
   Doris's Birthday Celebration of Life. Secondary: The Seaton Street
   Celebration. Her name remains elegant and prominent.") Her name was
   previously the single largest text on the page (82px cap), outsizing the
   actual Celebration headline (64px cap) -- inverted from the requested
   hierarchy. Her name stays first, in gold script, unmistakably hers, but
   now reads as an elegant identification rather than the dominant
   headline; .title__main below is enlarged to take that role instead. */
.title__name {
  font-family: var(--script);
  font-size: clamp(24px, 3.6vw, 54px); line-height: .98; margin: .1em 0 .08em;
  background: linear-gradient(160deg, var(--gold-1) 8%, #fff6d8 34%, var(--gold-2) 62%, var(--gold-3) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 3px 10px rgba(0,0,0,.65));
}
.title__dates {
  font-family: var(--serif); letter-spacing: .16em;
  font-size: clamp(9px, 1.1vw, 18px); color: var(--cream);
}
.title__kicker { font-family: var(--serif); font-size: clamp(15px, 2.1vw, 32px); margin-top: .4em; opacity: .95; }

/* FEI-DORIS-HAPPYBDAY-OVERLAY 2026-09-10 (owner-approved reference, Section A:
   "Keep/add the owner-approved Happy Birthday Doris animation as a real
   overlay/component, not baked text"). A real gold-script reveal + a brief
   sparkle flourish on first entry, then a slow ambient glow -- restrained,
   not a funeral-program animation. prefers-reduced-motion (this file's
   existing global rule) already turns off every animation here, leaving the
   line simply visible and still. */
.title__happybday {
  position: relative; margin-top: .5em;
  font-family: var(--script); font-size: clamp(20px, 3.4vw, 46px); line-height: 1.1;
  color: var(--gold-1); text-shadow: 0 0 18px rgba(224,180,99,.5), 0 2px 10px rgba(0,0,0,.6);
  opacity: 0; transform: translateY(6px);
  animation: hbReveal .9s ease .15s forwards, hbGlow 3.6s ease-in-out 1.2s infinite;
}
.title__hb-name { color: var(--gold-3); }
.title__hb-heart { display: inline-block; animation: hbPulse 2.4s ease-in-out 1.4s infinite; }
.title__hb-sparkle {
  display: inline-block; font-size: .55em; color: var(--gold-2); opacity: 0;
  animation: hbSparkle 1.8s ease-out .7s 1;
}
.title__hb-sparkle--b { animation-delay: .95s; }
@keyframes hbReveal {
  0% { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: translateY(0); }
}
@keyframes hbGlow {
  0%, 100% { text-shadow: 0 0 18px rgba(224,180,99,.5), 0 2px 10px rgba(0,0,0,.6); }
  50% { text-shadow: 0 0 28px rgba(224,180,99,.85), 0 2px 10px rgba(0,0,0,.6); }
}
@keyframes hbPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.15); }
}
@keyframes hbSparkle {
  0% { opacity: 0; transform: scale(.4) rotate(0deg); }
  40% { opacity: 1; transform: scale(1.1) rotate(90deg); }
  100% { opacity: 0; transform: scale(.6) rotate(180deg); }
}
.title__main {
  font-family: var(--script); font-size: clamp(30px, 5.6vw, 88px); line-height: 1.0;
  background: linear-gradient(160deg, var(--gold-1), #fff3d0 40%, var(--gold-2) 75%, var(--gold-3));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 4px 12px rgba(0,0,0,.6));
}
.title__theme {
  font-family: var(--serif); letter-spacing: .17em; color: var(--cream);
  font-size: clamp(9px, 1.25vw, 20px); margin-top: .25em;
}
.values {
  text-align: center; color: var(--cream); letter-spacing: .12em;
  font-size: clamp(7px, .82vw, 13px); font-weight: 600;
  text-shadow: 0 2px 8px rgba(0,0,0,.8);
}

/* ---------- side notes ---------- */
.sidenote {
  font-family: var(--serif); font-style: italic; color: var(--cream);
  font-size: clamp(10px, 1.15vw, 19px); line-height: 1.35;
  text-shadow: 0 2px 10px rgba(0,0,0,.75);
}
.sidenote--right { text-align: right; }
.sidenote__heart { color: var(--gold-2); font-style: normal; display: block; margin-top: .3em; }

/* ---------- photo frames ---------- */
.frame {
  display: block; padding: 0; border: 0; background: none;
  cursor: pointer; text-align: center;
}
/* FEI-DORIS-FRAME-SOFTEN 2026-09-10 (owner: "make those four frames at the
   bottom look better or remove the frames like you did up top"). Keeps
   the frame concept (these are real interactive buttons, not decoration)
   but softens how each one sits on the street plate -- a soft warm
   blurred glow behind the molding, same spirit as the hero haze, so each
   frame reads as gently lit rather than a hard rectangle dropped on the
   scene. */
.frame::before {
  content: "";
  position: absolute; inset: -14px;
  background: radial-gradient(ellipse at center, rgba(246, 227, 168, .38), transparent 72%);
  filter: blur(11px);
  z-index: -1;
}
.frame__molding {
  position: absolute; inset: 0; border-radius: 6px;
  background: linear-gradient(145deg, var(--gold-1), var(--gold-2) 38%, var(--gold-3) 70%, var(--gold-1));
  box-shadow: 0 10px 26px rgba(0,0,0,.4), inset 0 0 0 1px rgba(255,255,255,.25);
}
.frame__mat {
  position: absolute; inset: 4px; background: var(--cream);
  box-shadow: inset 0 0 22px rgba(120,90,40,.28);
  display: flex; flex-direction: column;
}
.frame__window {
  position: relative; flex: 1 1 auto; margin: 3px; overflow: hidden;
  /* a contained photograph sits on mat board, never on a black void */
  background: linear-gradient(160deg, #efe6cd, #ded0a9);
}
.frame__photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; object-position: center;
}
.frame__photo--cover { object-fit: cover; }
.frame__empty {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: linear-gradient(160deg, #efe4c9, #d8c79c);
  font-family: var(--script); font-size: 2.2em; color: var(--gold-3);
}
.frame__glass {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(118deg, rgba(255,255,255,.30) 0%, rgba(255,255,255,.05) 26%,
              rgba(255,255,255,0) 46%, rgba(255,255,255,.10) 82%, rgba(255,255,255,0) 100%);
  background-size: 260% 100%; background-position: 0% 0%;
  transition: background-position 1.6s var(--ease);
}
.frame:hover .frame__glass, .frame:focus-visible .frame__glass { background-position: 100% 0%; }
.frame__caption {
  flex: 0 0 auto; padding: .35em 0 .45em;
  background: linear-gradient(to bottom, #f3e7c8, #e2d0a4);
  color: #2a1c07; font-family: var(--serif);
}
.frame__caption b { display: block; font-size: clamp(9px, 1.05vw, 17px); letter-spacing: .06em; font-weight: 600; }
.frame__caption span { display: block; font-size: clamp(7px, .72vw, 12px); letter-spacing: .09em; opacity: .8; }
.frame:focus-visible .frame__molding { outline: 3px solid #fff3c9; outline-offset: 3px; }

/* ---------- event bar ---------- */
.eventbar {
  display: flex; align-items: center; justify-content: center; gap: clamp(10px, 2.6vw, 46px);
  color: var(--cream);
}
.eventbar__cell { display: flex; align-items: center; gap: .6em; font-family: var(--serif); }
.eventbar__cell + .eventbar__cell { border-left: 1px solid rgba(214,174,82,.45); padding-left: clamp(10px, 2.4vw, 40px); }
.eventbar__glyph { color: var(--gold-2); font-size: clamp(14px, 1.9vw, 30px); line-height: 1; }
.eventbar__lines { line-height: 1.25; }
.eventbar__lines b { display: block; letter-spacing: .12em; font-size: clamp(10px, 1.15vw, 19px); font-weight: 600; }
.eventbar__lines span { display: block; font-size: clamp(9px, 1.0vw, 16px); opacity: .92; }
/* FEI-DORIS-CELEBRATION-REBALANCE 2026-09-09 (owner directive: "do not let
   the script text overwhelm the actual logistics"). "All Are Welcome" is
   kept, but sized down relative to the real day/date/time/venue facts
   beside it (bumped up a touch above) instead of dwarfing them. */
.eventbar__welcome { font-family: var(--script); color: var(--gold-1); font-size: clamp(13px, 1.9vw, 30px); }

/* ---------- actions ---------- */
.actions { display: flex; gap: clamp(6px, 1.2vw, 20px); align-items: stretch; }
.action {
  flex: 1 1 0; display: flex; align-items: center; gap: .7em;
  padding: 0 clamp(8px, 1.1vw, 18px);
  border-radius: 999px; border: 1px solid rgba(214,174,82,.85);
  /* FEI-DORIS-GLASSMORPH-FIX 2026-09-10 (owner: "six glass morph panels...
     the panels are green" -- these had a green-tinted gradient but were
     92-96% opaque with NO backdrop-filter at all, so they read as solid
     dark pills, not glass. Real translucent green glass now: lower alpha
     so the golden-hour scene behind genuinely shows/blurs through, plus a
     visible blur + a soft outer gold glow matching the approved reference
     spec ("translucent deep-green glass; light blur/refraction; thin
     warm-gold luminous border; subtle inner highlight"). */
  background: linear-gradient(180deg, rgba(24,58,34,.42), rgba(8,20,12,.55));
  backdrop-filter: blur(14px) saturate(1.25);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
  box-shadow: 0 10px 26px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.14),
              0 0 22px rgba(217,174,82,.14);
  color: var(--cream); cursor: pointer; font: inherit;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease), border-color .25s;
}
.action:hover, .action:focus-visible {
  transform: translateY(-2px); border-color: var(--gold-1);
  background: linear-gradient(180deg, rgba(30,70,42,.52), rgba(10,26,15,.62));
  box-shadow: 0 14px 32px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.22),
              0 0 30px rgba(217,174,82,.24);
}
.action:active { transform: translateY(0); }
.action__glyph { color: var(--gold-1); font-size: clamp(12px, 1.5vw, 24px); }
.action__text { flex: 1 1 auto; text-align: left; line-height: 1.2; }
.action__text b { display: block; letter-spacing: .1em; font-size: clamp(8px, .95vw, 15px); font-weight: 600; }
.action__text span { display: block; font-size: clamp(7px, .78vw, 12px); opacity: .85; }
.action__chev { color: var(--gold-1); font-size: clamp(11px, 1.3vw, 20px); }

.footer-line {
  text-align: center; color: rgba(246,239,223,.85);
  letter-spacing: .22em; font-size: clamp(7px, .78vw, 12px);
}

/* ---------- flow mode (narrow screens) ---------- */
.flow .stage { aspect-ratio: 4 / 5; max-height: 58vh; }
.flow .stage__plate { object-position: 50% 18%; }
.flow .obj { position: relative; left: auto; top: auto; width: auto; height: auto; }
.flow .obj--overlayonly { display: none; }
.flow__body {
  padding: 18px 16px 40px;
  background: linear-gradient(to bottom, var(--plum-2), var(--ink) 60%);
  display: flex; flex-direction: column; gap: 20px;
}
.flow .title { padding-top: 4px; }
.flow .title::before { display: none; }
.flow .frames-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.flow .frame { position: relative; aspect-ratio: 3 / 4.2; width: 100%; }
.flow .actions { flex-direction: column; }
.flow .action { padding: 12px 16px; }
.flow .eventbar { flex-direction: column; gap: 14px; text-align: center; }
.flow .eventbar__cell { flex-direction: column; }
.flow .eventbar__cell + .eventbar__cell { border-left: 0; padding-left: 0; }

/* ---------- panel ---------- */
.panel {
  position: fixed; inset: auto 0 0 0; z-index: 40;
  transform: translateY(100%); visibility: hidden;
  transition: transform .45s var(--ease), visibility 0s linear .45s;
  background: linear-gradient(180deg, rgba(29,10,28,.98), rgba(11,7,9,.99));
  border-top: 1px solid rgba(214,174,82,.6);
  padding: 22px 20px 30px; max-height: 78vh; overflow: auto;
}
.panel.is-open { transform: translateY(0); visibility: visible; transition-delay: 0s; }
.panel h2 { font-family: var(--script); color: var(--gold-1); font-size: 30px; margin: 0 0 10px; }
.panel p { margin: .35em 0; line-height: 1.5; }
.panel__close {
  position: absolute; top: 12px; right: 14px; background: none; border: 0;
  color: var(--gold-1); font-size: 24px; cursor: pointer;
}

/* FEI-DORIS-ORB-CLICK-TO-MEMORY 2026-09-09: styling for a single real
   memory rendered into the shared .panel (see scene-engine.js's
   openMemoryPanel()). Kept in the same gold/plum language as the rest of
   this sheet -- .card, .scenenav__link -- rather than a new visual system. */
.panel__media { margin: 4px 0 12px; border-radius: 8px; overflow: hidden; background: #0a0710; }
.panel__media img, .panel__media video {
  width: 100%; max-height: 46vh; object-fit: contain; display: block; background: #0a0710;
}
.panel__memlabel { font-family: var(--serif); font-weight: 600; color: var(--cream); }
.panel__memfacts { font-size: .82em; opacity: .8; }
.panel__memfacts span:not(:last-child)::after { content: " \2022 "; opacity: .6; }
.panel__memaudio { width: 100%; margin: .4em 0; }
.panel__memby { opacity: .85; font-size: .92em; }
.panel__memsong { color: var(--gold-1); font-family: var(--serif); }
.panel__memnav {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 16px; padding-top: 12px; border-top: 1px solid rgba(214,174,82,.3);
}
.panel__navbtn {
  font: inherit; font-size: .82em; letter-spacing: .06em; color: var(--cream);
  background: none; border: 1px solid rgba(214,174,82,.6); border-radius: 999px;
  padding: .4em 1em; cursor: pointer; transition: border-color .2s;
}
.panel__navbtn:hover:not([disabled]), .panel__navbtn:focus-visible:not([disabled]) { border-color: var(--gold-1); }
.panel__navbtn[disabled] { opacity: .35; cursor: not-allowed; }
.panel__navcount { font-size: .78em; letter-spacing: .1em; opacity: .75; }
.panel__seeall {
  display: block; width: 100%; margin-top: 10px; font: inherit; font-size: .82em;
  letter-spacing: .08em; text-align: center; color: var(--gold-1); background: none;
  border: 1px dashed rgba(214,174,82,.55); border-radius: 999px; padding: .55em 1em; cursor: pointer;
}
.panel__seeall:hover, .panel__seeall:focus-visible { border-style: solid; border-color: var(--gold-1); }

/* FEI-DORIS-MUSIC-PANEL-PORT 2026-09-09: the real Spotify player inside the
   MUSIC panel -- same visual language/class-naming convention as the
   original .scene-music widget it was ported from (memories.html). */
.scene-music { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid rgba(214,174,82,.28); max-width: 30rem; }
.scene-music__btn {
  display: inline-flex; align-items: center; gap: .5em;
  font-family: var(--sans); font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink); background: linear-gradient(135deg, var(--gold-1), var(--gold-3));
  border: none; border-radius: 999px; padding: .65em 1.2em; cursor: pointer;
}
.scene-music__btn svg { width: 1.05em; height: 1.05em; }
.scene-music__btn:hover { filter: brightness(1.06); }
.scene-music__player {
  margin-top: .7rem; display: none; flex-direction: column; gap: .5rem;
  max-height: 320px; overflow-y: auto; padding-right: 2px;
}
.scene-music__player.open { display: flex; }
.scene-music__player iframe { border-radius: 10px; display: block; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition-duration: .01ms !important; }
  .stage__plate { transform: none; }
}

/* =========================================================
   Scene 2 and 3 objects — garden and backyard
   ========================================================= */

.scenenav { display: flex; gap: 10px; align-items: center; }
.scenenav--column { flex-direction: column; align-items: stretch; gap: 6px; }
.scenenav--column .scenenav__link { text-align: left; padding: .42em .9em; }
.scenenav__link {
  font: inherit; letter-spacing: .12em; font-size: clamp(8px, .82vw, 13px);
  padding: .5em 1.1em; border-radius: 999px; cursor: pointer; color: var(--cream);
  border: 1px solid rgba(214,174,82,.7);
  /* FEI-DORIS-GLASSMORPH-FIX 2026-09-10: matched to .action's real glass
     treatment (green tint, not purple) so both button rows read as one
     consistent set of glass panels, per the approved reference. */
  background: linear-gradient(180deg, rgba(24,58,34,.4), rgba(8,20,12,.52));
  backdrop-filter: blur(14px) saturate(1.25);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 0 18px rgba(217,174,82,.12);
  transition: border-color .25s, transform .25s var(--ease), box-shadow .25s;
}
.scenenav__link:hover, .scenenav__link:focus-visible {
  border-color: var(--gold-1); transform: translateY(-1px);
  background: linear-gradient(180deg, rgba(30,70,42,.5), rgba(10,26,15,.6));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 0 26px rgba(217,174,82,.22);
}
/* FEI-DORIS-CELEBRATION-REBALANCE 2026-09-09: the one quiet "Join
   Virtually" link in the Celebration scene's secondary nav row gets a
   small live-pulse dot (reusing .pdock__pulse's own keyframes, defined
   further down this file) -- everything else about it is an ordinary
   .scenenav__link. */
.scenenav__link--live { display: inline-flex; align-items: center; gap: .55em; }
.scenenav__dot {
  width: clamp(6px, .5vw, 8px); height: clamp(6px, .5vw, 8px); border-radius: 50%; flex: 0 0 auto;
  background: radial-gradient(circle, #fff6d4, var(--gold-2));
  animation: pdockPulse 1.8s ease-in-out infinite;
}

.scenetitle { text-align: center; }
.scenetitle--left { text-align: left; }
.scenetitle__eyebrow { letter-spacing: .3em; font-size: clamp(7px, .78vw, 12px); color: var(--cream); opacity: .9; }
.scenetitle__main {
  font-family: var(--script); line-height: 1.0;
  font-size: clamp(26px, 4.4vw, 66px);
}
.scenetitle--sm .scenetitle__main {
  font-size: clamp(20px, 2.9vw, 44px);
  background: linear-gradient(160deg, var(--gold-1), #fff3d0 42%, var(--gold-2) 76%, var(--gold-3));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 4px 12px rgba(0,0,0,.7));
}
.scenetitle__sub { font-family: var(--serif); letter-spacing: .16em; font-size: clamp(8px, .95vw, 15px); color: var(--cream); }
.scenetitle__script { font-family: var(--script); color: var(--gold-1); font-size: clamp(13px, 1.7vw, 26px); }

/* hotspots mark what is already in the scene */
.hotspot {
  border: 0; background: none; padding: 0; cursor: pointer;
  display: flex; align-items: center; gap: .5em;
}
.hotspot__pulse {
  flex: 0 0 auto; width: clamp(10px, 1vw, 16px); height: clamp(10px, 1vw, 16px);
  border-radius: 50%; background: radial-gradient(circle at 35% 35%, #fff6d4, var(--gold-2) 60%, rgba(214,174,82,.15));
  box-shadow: 0 0 0 0 rgba(246,227,168,.5);
  animation: hotspotPulse 3.6s var(--ease) infinite;
}
@keyframes hotspotPulse {
  0%   { box-shadow: 0 0 0 0 rgba(246,227,168,.45); }
  70%  { box-shadow: 0 0 0 14px rgba(246,227,168,0); }
  100% { box-shadow: 0 0 0 0 rgba(246,227,168,0); }
}
.hotspot__label {
  text-align: left; color: var(--cream); line-height: 1.15;
  padding: .38em .8em; border-radius: 999px;
  background: linear-gradient(180deg, rgba(16,8,18,.66), rgba(6,4,8,.78));
  border: 1px solid rgba(214,174,82,.5);
  opacity: 0; transform: translateX(-6px);
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.hotspot:hover .hotspot__label, .hotspot:focus-visible .hotspot__label { opacity: 1; transform: none; }
.hotspot__label b { display: block; letter-spacing: .1em; font-size: clamp(8px, .82vw, 13px); font-weight: 600; }
.hotspot__label span { display: block; font-size: clamp(7px, .72vw, 11px); opacity: .82; }
.hotspot--seat .hotspot__label { opacity: 1; transform: none; border-color: var(--gold-1); }

/* memory orbs — unlit until an approved memory lights them */
.orb { border: 0; background: none; padding: 0; cursor: pointer; display: grid; place-items: center; }
.orb__glow {
  position: absolute; inset: 0; border-radius: 50%;
  background: radial-gradient(circle at 40% 38%, rgba(255,246,214,.55), rgba(214,174,82,.22) 55%, rgba(214,174,82,0) 72%);
  opacity: .45; transition: opacity .8s ease;
  animation: orbBreathe 7s ease-in-out infinite;
  animation-delay: var(--orb-phase, 0s);
}
.orb.is-lit .orb__glow { opacity: 1; }
@keyframes orbBreathe { 0%,100% { transform: scale(1); } 50% { transform: scale(1.06); } }
.orb__leaf {
  position: absolute; left: 6%; bottom: 4%; width: 26%; height: 26%;
  background: radial-gradient(ellipse at 20% 80%, #7fbf6a, #2f6b34 70%, transparent 72%);
  transform: scale(0) rotate(-12deg); transform-origin: 10% 90%;
  transition: transform 1.1s var(--ease);
}
.orb.is-lit .orb__leaf { transform: scale(1) rotate(0); }
.orb__label {
  font-family: var(--serif); font-style: italic; color: var(--cream);
  font-size: clamp(8px, .85vw, 14px); text-shadow: 0 2px 8px rgba(0,0,0,.8);
}
.orb__count {
  position: absolute; right: 2%; top: 2%; background: var(--gold-2); color: #2a1c07;
  border-radius: 999px; padding: 0 .45em; font-size: clamp(7px, .7vw, 11px); font-weight: 700;
}

/* =========================================================
   Ambient Memory Glow -- FEI-DORIS-AMBIENT-MEMORY-GLOW 2026-09-09
   A passive companion to the memory orbs above (which stay the deliberate,
   click-through way to browse memories -- this never duplicates that job).
   Sits at the candle already lit on Doris's own table (see garden.json),
   inside her established sightline toward the open "Sit With Doris" chair,
   so real approved memories glow and fade there while she is, visually,
   already looking that way. Purely decorative: no pointer events, aria-
   hidden, and z-index never raised above a real hotspot/orb/button, so it
   can never steal a click or compete with them. Overlay-only (see
   obj--overlayonly + scene-engine.js's hydrate()); resting halo stays lit
   at low intensity even between memories so the candle never simply goes
   dark. Global @media (prefers-reduced-motion) below already turns off
   every animation/transition sitewide -- that alone gives a reduced-motion
   visitor the single still memory startAmbientGlow() shows them, held with
   no rotation. */
.memglow { pointer-events: none; display: grid; place-items: center; z-index: 1; }
.memglow__halo {
  position: absolute; inset: -35%; border-radius: 50%;
  background: radial-gradient(circle, rgba(255,214,140,.55), rgba(255,180,90,.16) 55%, rgba(255,180,90,0) 74%);
  opacity: .48; transition: opacity 2.4s ease;
  animation: memglowBreathe 6.4s ease-in-out infinite;
}
.memglow.is-on .memglow__halo { opacity: .95; }
@keyframes memglowBreathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } }
.memglow__frame {
  position: relative; width: 74%; height: 74%; border-radius: 10px; overflow: hidden;
  opacity: 0; transform: scale(.92);
  transition: opacity 2.6s ease, transform 2.6s ease;
  border: 1px solid rgba(255,214,140,.75);
  box-shadow: 0 0 14px 3px rgba(255,190,110,.35);
  background: rgba(20,10,8,.55);
}
.memglow.is-on .memglow__frame { opacity: 1; transform: scale(1); }
.memglow__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.memglow__mark {
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  color: var(--gold-1); font-size: clamp(13px, 1.4vw, 21px);
}
/* per-kind warmth, echoing memories.html's own memory-orb--photo/video/
   audio/text convention -- kept subtle, never a second competing animation. */
.memglow--video .memglow__halo {
  background: radial-gradient(circle, rgba(255,225,170,.6), rgba(224,180,99,.18) 55%, rgba(224,180,99,0) 74%);
}
.memglow--audio .memglow__halo { animation-duration: 3.4s; }
.memglow--text .memglow__halo { animation: none; opacity: .38; }

/* FEI-DORIS-MEDIAWALL-ANGLE 2026-09-21 (owner: "you can see where it is
   supposed to be where that pink flower it goes back at a slight angle
   and i can see where it is suppose to be"). The approved plate
   (assets/scenes/backyard-2048.webp) shows this screen mounted flush
   against the house's side fence, which recedes away from camera toward
   the right -- the live render was a flat axis-aligned plane ("never part
   of the plate," see the comment this replaces), which reads as floating
   rather than mounted. A real, subtle 3D tilt (not a full homography warp)
   recedes the right edge to match the art's own perspective; left edge
   (anchored nearest the house) stays put via transform-origin. */
.mediawall { display: flex; flex-direction: column; perspective: 1400px; }
.mediawall__screen { position: relative; flex: 1 1 auto; overflow: hidden; background: #0a0710;
  box-shadow: 0 0 40px rgba(0,0,0,.5), inset 0 0 0 1px rgba(214,174,82,.35);
  transform: rotateY(-7deg); transform-origin: left center; }
/* FEI-DORIS-MEDIAWALL-ROTATION 2026-09-10: two stacked layers, each holding
   either an <img> or <video>, crossfaded via opacity as the playlist
   advances (js/scene-engine.js). Replaces the old fixed poster+video pair;
   .mediawall__media fills whichever layer is currently populated the same
   way the old .mediawall__poster/.mediawall__video always filled the
   screen. */
.mediawall__layer {
  position: absolute; inset: 0; opacity: 0; transition: opacity 1s ease;
}
.mediawall__layer.is-on { opacity: 1; }
.mediawall__media {
  display: block; width: 100%; height: 100%;
  /* FEI-DORIS-MEDIAWALL-SAFE-FIT 2026-09-10 (owner: "some heads are getting
     cut off"). Was a hard object-fit:cover with no face-safety logic --
     reusing the same proven SAFE_COVER method the photo frames already use
     (js/photo-frames.js applyFit(), .frame__photo--cover below), which
     defaults to contain and only switches to a cover crop biased toward
     where faces usually sit, or falls back to contain entirely when the
     photo's aspect ratio is too different from the frame's to safely crop. */
  object-fit: contain; object-position: center;
}
/* Higher specificity (two classes) than the bare .frame__photo--cover rule
   above, so this reliably wins the cascade regardless of source order --
   the bare-class version alone lost silently to .mediawall__media's later
   position in this file on first attempt (confirmed: every image stayed on
   "contain" even when applyFit() correctly added the cover class). */
.mediawall__media.frame__photo--cover { object-fit: cover; }
.mediawall__strip {
  flex: 0 0 auto; text-align: center; letter-spacing: .18em;
  font-size: clamp(6px, .62vw, 10px); color: var(--cream);
  background: linear-gradient(180deg, rgba(58,20,54,.85), rgba(20,8,20,.9));
  padding: .35em 0;
}

/* FEI-DORIS-MEDIAWALL-POPULATE 2026-09-10: honest "not live yet" slide for
   the venue-camera rotation slot (js/scene-engine.js media_wall "placeholder"
   type) -- text on the wall's own screen background, not a fabricated video. */
.mediawall__placeholder {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: .4em; padding: 1em;
  background: radial-gradient(120% 100% at 50% 30%, rgba(90,30,90,.55), rgba(10,4,12,.92));
}
.mediawall__placeholder-headline {
  font-family: var(--serif); letter-spacing: .08em;
  font-size: clamp(9px, 1.1vw, 17px); color: var(--gold-1);
}
.mediawall__placeholder-sub {
  font-family: var(--serif); font-size: clamp(7px, .78vw, 12px); color: var(--cream); opacity: .85;
}

.card {
  padding: .5em .7em; border-radius: 6px; color: var(--cream);
  background: linear-gradient(180deg, rgba(16,8,18,.72), rgba(6,4,8,.82));
  border: 1px solid rgba(214,174,82,.45); backdrop-filter: blur(3px);
}
.card--right { text-align: right; }
.card__title { font-family: var(--script); color: var(--gold-1); font-size: clamp(12px, 1.3vw, 21px); line-height: 1.05; }
.card__lines { font-family: var(--serif); font-size: clamp(7px, .72vw, 12px); line-height: 1.4; letter-spacing: .04em; }
.card__cta {
  margin-top: .5em; font: inherit; font-size: clamp(7px, .75vw, 12px); letter-spacing: .1em;
  color: var(--cream); background: none; border: 1px solid rgba(214,174,82,.7);
  border-radius: 999px; padding: .35em .9em; cursor: pointer;
}
.card__cta:hover { border-color: var(--gold-1); }

.wordlist {
  font-family: var(--serif); text-align: right; color: var(--cream);
  font-size: clamp(9px, 1vw, 16px); line-height: 1.9; opacity: .92;
  text-shadow: 0 2px 8px rgba(0,0,0,.75);
}

.player { display: flex; align-items: center; gap: .8em; color: var(--cream); }
.player__play {
  flex: 0 0 auto; width: clamp(24px, 2.4vw, 40px); height: clamp(24px, 2.4vw, 40px);
  border-radius: 50%; border: 1px solid rgba(214,174,82,.8); cursor: pointer;
  background: linear-gradient(180deg, rgba(24,12,26,.9), rgba(8,5,10,.95)); color: var(--gold-1);
}
.player__now { font-family: var(--serif); font-size: clamp(8px, .85vw, 14px); }
.player__quote { font-family: var(--script); color: var(--gold-1); font-size: clamp(12px, 1.5vw, 24px); margin-left: auto; }

.counter { text-align: center; color: var(--cream); font-family: var(--serif); }
.counter b { display: block; font-size: clamp(16px, 2vw, 32px); color: var(--gold-1); }
.counter span { font-size: clamp(7px, .75vw, 12px); letter-spacing: .14em; }

/* flow mode for these objects */
.flow .hotspot { justify-content: flex-start; }
.flow .hotspot__label { opacity: 1; transform: none; }
/* The memory orbs stay on a phone. They become a row of lit tokens under the
   garden rather than points in space, but the interaction survives. */
.flow .orb {
  display: flex; align-items: center; gap: .6em; width: 100%;
  padding: .55em .8em; border-radius: 999px;
  border: 1px solid rgba(214,174,82,.5);
  background: linear-gradient(180deg, rgba(20,10,22,.72), rgba(8,5,10,.82));
}
.flow .orb__glow { position: relative; inset: auto; width: 22px; height: 22px; flex: 0 0 auto; }
.flow .orb__leaf { display: none; }
.flow .orb__label { text-align: left; }
.flow .orb__count { position: static; margin-left: auto; }
.flow .mediawall { aspect-ratio: 16 / 10; }
.flow .scenenav { flex-wrap: wrap; justify-content: center; }
.flow .player { flex-direction: column; gap: .5em; text-align: center; }
.flow .player__quote { margin-left: 0; }


/* the fifth photo position -- a real rotating photo slot */
/* FEI-DORIS-HERO-HAZE-ROTATE 2026-09-10 (owner directive, verbatim: "is
   there a way to like fade that baked in part by putting some type of
   haze over it but then the rotating pictures are over it"). Supersedes
   the 09-10 ECHO-FIX's `display:none`. That fix correctly stopped a
   second, misaligned copy of the same photo from rendering on top of the
   plate's own baked-in figure -- but the right long-term fix for a slot
   that's meant to rotate through real photos of her (per the owner) isn't
   "hide the overlay and leave the single baked plate photo showing
   forever," it's "haze the baked plate figure so it's no longer a
   competing photo, then let a real, rotating set of her curated solo
   photos live cleanly on top of that haze." Does not touch the approved
   plate file itself -- the haze is a view-only blur/wash on top of it,
   same "cropping/blending is a view, never a rewrite" principle already
   used elsewhere on this page. */
.heroplane {
  overflow: hidden;
  position: relative;
}
.heroplane__haze {
  position: absolute; inset: 0;
  /* FEI-DORIS-HERO-HAZE-SOFTEN 2026-09-10 (owner: "get rid of the square
     box edge and lighten up the color of the blur, that brown"). Two
     fixes: (1) the haze itself now carries the same kind of all-edge
     radial mask as the photo layer, so its blur/tint fades out softly
     instead of presenting its own hard rectangle once the photo on top
     of it has faded away -- that was the real source of the remaining
     "square box edge," not the photo. (2) the tint's dark stop was a
     near-black brown; replaced with a soft warm gold/cream wash, no
     stop darker than a muted mid-gold, so the haze reads as soft light
     rather than a muddy brown smear. Blurs + warms whatever the plate
     shows behind this box; does not touch the approved plate file. */
  /* FEI-DORIS-HERO-HAZE-SOFTEN-2 2026-09-10: the second stop (214,178,120)
     was still a tan/brown, even at low opacity -- replaced with the same
     light cream/gold family as --cream/--gold-1 elsewhere on this page,
     so there's no brown component left in the wash at all. */
  background: linear-gradient(165deg, rgba(255, 246, 232, .30), rgba(246, 227, 168, .18));
  backdrop-filter: blur(26px) saturate(.92);
  -webkit-backdrop-filter: blur(26px) saturate(.92);
  -webkit-mask-image: radial-gradient(ellipse 74% 80% at 50% 44%, #000 50%, transparent 98%);
          mask-image: radial-gradient(ellipse 74% 80% at 50% 44%, #000 50%, transparent 98%);
}
.heroplane__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 1.4s ease;
  /* Soft feather on ALL four sides (the old mask only faded the right
     edge, which is what let a hard-edged duplicate read as an "echo
     face"/"hard square edges" in the first place) -- a rotating photo now
     dissolves into the haze on every side instead of presenting a
     rectangle. Radius is deliberately small (percentages here are % of
     the box's own width/height, not a fraction-of-radius trick) so the
     fade actually completes before the box edge instead of falling
     entirely off-screen. */
  -webkit-mask-image: radial-gradient(ellipse 56% 62% at 50% 44%, #000 40%, transparent 90%);
          mask-image: radial-gradient(ellipse 56% 62% at 50% 44%, #000 40%, transparent 90%);
}
.heroplane__img.is-on { opacity: 1; }

/* FEI-DORIS-CROWD-PHOTO 2026-09-10 (owner request, "not these animations" --
   real extracted family members standing in the crowd). Sized to real
   background-crowd scale, not hero scale -- the elephant-ear mistake earlier
   today was exactly an oversized photo reading as pasted-on; kept this
   deliberately modest (height, not width, drives scale so aspect ratio holds
   regardless of the source photo's crop). A warm brightness/contrast grade
   plus a soft ground shadow are the cheap, reversible CSS equivalent of the
   offline color-grade step that made the reviewed candidate composite read
   as photoreal rather than pasted-in -- adjust here, not by regenerating the
   source cutout, while she's reviewing placement. */
/* FEI-DORIS-SCENE-GROUNDING 2026-09-22: the four --gr-* custom properties
   are set per-object by scene-engine.js's applyGroundingEffects() from each
   object's own box.y (depth in this fixed-camera scene) -- see that
   function's own comment for the full reasoning. Every value here has a
   fallback matching this rule's own pre-existing 2026-09-10 defaults, so an
   object the JS never touches (or a page loaded with JS partially failed)
   renders exactly as before this change -- nothing here is a hard
   dependency on the new JS running. */
.crowdphoto img,
.crowdphoto video {
  height: 100%; width: auto; max-width: none;
  filter: brightness(var(--gr-brightness, .88)) saturate(var(--gr-saturate, 1.04)) contrast(1.05)
          blur(var(--gr-blur, 0px))
          drop-shadow(var(--gr-shadow, 0 10px 14px rgba(0,0,0,.45)));
  object-fit: contain; object-position: bottom;
  transform: scale(var(--gr-scale, 1));
  transform-origin: 50% 100%;
}

/* FEI-DORIS-CROWD-SWAY 2026-09-10 (owner request: the crowd should look like
   they're moving/present, not a frozen photo collage). Honest scope, per the
   same day's own crowd-behavior feasibility research
   (docs-internal/crowd_behavior_ai_rnd_20260910/FEASIBILITY.md): there is no
   live venue audio/video feed to react to yet, and per-limb/facial animation
   is mechanically blocked on these small background cutouts (see that
   file's face-resolution finding) -- this does NOT attempt either. It is a
   plain, deterministic ambient sway/weight-shift (translate+rotate), the
   same family of technique already proven on the singer figure
   (docs-internal/band_avatar_rnd_20260910/mesh_warp_sway_prototype.py),
   applied here as a much cheaper CSS transform since these are small
   background figures, not a hero portrait. Three phase/duration variants
   via :nth-of-type so the crowd doesn't move in visible lockstep. Bottom-
   anchored transform-origin so feet stay planted. The file's existing global
   prefers-reduced-motion rule (above) already disables this. */
.crowdphoto { transform-origin: 50% 100%; animation: crowdSway 5.4s ease-in-out infinite; }
.crowdphoto:nth-of-type(3n+1) { animation-duration: 4.6s; animation-delay: -1.1s; }
.crowdphoto:nth-of-type(3n+2) { animation-duration: 6.1s; animation-delay: -3.4s; }
.crowdphoto:nth-of-type(3n)   { animation-duration: 5.4s; animation-delay: -2.2s; }
@keyframes crowdSway {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-2px) rotate(.5deg); }
}

/* FEI-DORIS-FURNITURE-ENGINE v1 2026-09-10: furniture placed as independent
   scene objects on the new empty "1750 Backyard BBQ" base (see scene-engine.js
   "furniture" case). Grounded with a soft shadow like crowdphoto, but no
   brightness/saturation push -- that grading was tuned for skin-tone photo
   cutouts, not decor. transform-origin bottom center so rotation/scale
   (both optional, off by default) pivot from where the object sits on the
   ground, not its image center. */
.furnitureprop {
  transform-origin: 50% 100%;
}
/* FEI-DORIS-SCENE-GROUNDING 2026-09-22: same shared depth custom properties
   as .crowdphoto above (see that rule's comment + applyGroundingEffects() in
   scene-engine.js). Fallbacks intentionally match furniture's own pre-
   existing "no brightness/saturation push" default (1 / no-op), since that
   grading was originally tuned for skin-tone photo cutouts, not decor --
   the JS still applies a much gentler depth grade to furniture than to
   crowd figures for the same reason, distinct ranges, one shared mechanism. */
.furnitureprop img {
  height: 100%; width: auto; max-width: none;
  filter: brightness(var(--gr-brightness, 1)) saturate(var(--gr-saturate, 1))
          blur(var(--gr-blur, 0px))
          drop-shadow(var(--gr-shadow, 0 8px 10px rgba(0,0,0,.4)));
  object-fit: contain; object-position: bottom;
  transform: scale(var(--gr-scale, 1));
  transform-origin: 50% 100%;
}

/* FEI-DORIS-BAND-SWAY 2026-09-10 (owner: "we will need to animate this so
   the band plays the songs on the screen"). Honest scope, same limits as
   the crowd's own sway (see FEI-DORIS-CROWD-SWAY above and
   docs-internal/band_avatar_rnd_20260910/LIPSYNC_FINDINGS.md): there is no
   way to fabricate real instrument-playing hand motion or lip-sync from
   this cutout today (that R&D found it mechanically blocked -- source
   resolution + no real audio-sync path) -- this is deliberately NOT that.
   It's the same proven ambient sway technique, just a touch faster/livelier
   than the crowd's cadence to read as "performing" rather than "standing
   around," scoped only to the band via its object id so it doesn't apply
   to any other furniture piece. */
.furnitureprop[data-object-id="furniture_band"] {
  animation: bandSway 2.6s ease-in-out infinite;
}
@keyframes bandSway {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-3px) rotate(-.8deg); }
}
/* FEI-DORIS-BAND-FOLLOWS-SCREEN 2026-09-10: a livelier variant, engaged only
   while body.band-performing is set (js/scene-engine.js toggles this ~320ms
   after the media wall shows its song_video item, so the stage band visibly
   "picks up its cue" from the screen rather than moving in lockstep with
   it) -- still the same honest ambient-sway family, just faster/bigger
   amplitude to read as actively performing rather than idling. */
body.band-performing .furnitureprop[data-object-id="furniture_band"] {
  animation: bandSwayActive 1.1s ease-in-out infinite;
}
@keyframes bandSwayActive {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-5px) rotate(-1.6deg); }
}

/* FEI-DORIS-ELEPHANT-EAR-DECO 2026-09-10 REMOVED same day -- owner: "remove
   those elephant ears that is horrible." The two flanking plant photos read
   as oversized/out of place against the golden-hour scene. Reverted. */
/* FEI-DORIS-CELEBRATION-REBALANCE 2026-09-09: "this should not be a static
   poster... restore depth and movement... tasteful frame-photo movement."
   A slow, continuous, very subtle Ken Burns drift on the real curated
   photographs already in the four frames -- no new art, no rotation
   through a photo bucket (that would need real backend/catalog work, named
   as a follow-up rather than faked). Scoped to the Celebration entrance
   only; Garden/Backyard frames are unaffected. The global
   prefers-reduced-motion rule near the top of this file already zeroes all
   animation durations, so this automatically holds still for anyone who
   has that preference set -- no separate override needed here. */
.scene-celebration .frame__photo {
  animation: celebrationFrameDrift 46s ease-in-out infinite alternate;
  transform-origin: center;
}
@keyframes celebrationFrameDrift {
  0%   { transform: scale(1); }
  100% { transform: scale(1.055); }
}

.player__play[disabled] { opacity: .45; cursor: not-allowed; }

/* =========================================================
   Presence dock -- FEI-DORIS-PRESENCE-CONTINUITY 2026-09-09
   The embedded, in-page Green Room. Fixed/persistent so it survives
   scene-engine.js's in-place scene transitions (it lives outside #scene in
   the page markup). Two states: full (a real, usable video-call-sized
   panel) and minimized (a small pulsing pill so a guest can keep browsing
   scenes while still on the call, tap to bring the call back full-size).
   z-index sits above scene content but below the slide-up .panel (40) so a
   panel opened while the dock is up is never trapped behind it.
   ========================================================= */
.pdock {
  position: fixed; z-index: 35;
  right: clamp(10px, 2vw, 26px); bottom: clamp(10px, 2vw, 26px);
  /* FEI-DORIS-PDOCK-JOINFORM-FIX 2026-09-10 (owner: "sit with doris form is
     not usable" / "You can't type in the field to enter your name"). Real
     cause: the real join form inside the presence.think4ei.com iframe
     (name field + 3 consent checkboxes + join button) is taller than this
     box's old 300px cap, so the bottom of the form -- including the name
     field -- rendered clipped outside the visible dock (confirmed directly:
     the name input's own bounding box landed partly below the dock's
     bottom edge). Sized up so the whole join step fits; still capped well
     under full-screen so it stays a compact dock once a call is live. */
  width: min(420px, calc(100vw - 20px)); height: min(520px, 78vh);
  border-radius: 14px; overflow: hidden;
  border: 1px solid rgba(214,174,82,.7);
  background: linear-gradient(180deg, rgba(29,10,28,.98), rgba(11,7,9,.99));
  box-shadow: 0 12px 40px rgba(0,0,0,.55);
  display: flex; flex-direction: column;
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.pdock[hidden] { display: none; }
.pdock__pill[hidden] { display: none; }
.pdock__bar {
  flex: 0 0 auto; display: flex; align-items: center; gap: .6em;
  padding: .55em .4em .55em .9em;
  border-bottom: 1px solid rgba(214,174,82,.35);
  background: rgba(0,0,0,.25);
}
.pdock__title {
  font-family: var(--serif); font-size: clamp(11px, 1.1vw, 14px);
  color: var(--gold-1); flex: 1 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pdock__btn {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid rgba(214,174,82,.6); background: rgba(0,0,0,.3);
  color: var(--gold-1); font-size: 16px; line-height: 1; cursor: pointer;
}
.pdock__btn:hover, .pdock__btn:focus-visible { border-color: var(--gold-1); }
.pdock__frame { flex: 1 1 auto; position: relative; background: #05040a; }
.pdock__iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }

/* Minimized: the call keeps running (the iframe is still mounted and
   rendering, just shrunk to a strip) rather than removed or display:none'd,
   which is what would actually risk pausing video/audio in some browsers. */
.pdock.is-min { width: auto; height: auto; background: none; border: 0; box-shadow: none; }
.pdock.is-min .pdock__bar, .pdock.is-min .pdock__frame { display: none; }
.pdock__pill {
  display: flex; align-items: center; gap: .55em;
  padding: .6em 1.1em; border-radius: 999px; cursor: pointer;
  font-family: var(--sans); font-size: clamp(10px, 1vw, 13px); color: var(--cream);
  border: 1px solid rgba(214,174,82,.8);
  background: linear-gradient(180deg, rgba(24,12,26,.94), rgba(8,5,10,.97));
  box-shadow: 0 8px 26px rgba(0,0,0,.5);
}
.pdock__pulse {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  background: radial-gradient(circle, #fff6d4, var(--gold-2));
  animation: pdockPulse 1.8s ease-in-out infinite;
}
@keyframes pdockPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(217,174,82,.55); }
  50% { box-shadow: 0 0 0 6px rgba(217,174,82,0); }
}

@media (max-width: 640px) {
  .pdock { right: 8px; bottom: 8px; left: 8px; width: auto; height: min(46vh, 340px); }
}
@media (prefers-reduced-motion: reduce) {
  .pdock__pulse { animation: none; }
}
.player__pending { opacity: .6; font-style: italic; }

/* =========================================================
   Share panel -- FEI-DORIS-SHARE-PANEL 2026-09-09 (owner directive,
   verbatim spec: a clear "leave a memory" call to action; clicking it
   slides a panel out from the RIGHT (not a page navigation, not the old
   scroll-over-background garden); only the form inside scrolls, Doris and
   the scene behind stay completely static; submit reuses the real
   moderation queue unchanged; approval lights the real orb unchanged).

   The panel hosts an <iframe> onto share-a-memory.html?embed=1 (see
   scene-engine.js openSharePanel()) rather than a re-implemented form --
   that real, working, heavily-tested submit/upload/dictate/record logic
   is reused verbatim, not duplicated. This also gives the "never scrolls"
   requirement for free and for real: the iframe's document has its OWN
   independent scroll context, completely walled off from this page's
   (this page has no scroll at all to begin with -- #scene is a static,
   full-viewport stage) -- so there is no code path by which scrolling the
   form could ever move anything behind the panel, not even accidentally.

   z-index sits above BOTH the slide-up .panel (40) and the Presence dock
   (35): opening "Share a Memory" while either is up is a real, deliberate
   action that should not be trapped behind them. */
.share-panel-scrim {
  position: fixed; inset: 0; z-index: 49;
  background: rgba(6,3,9,.55);
  opacity: 0; visibility: hidden;
  transition: opacity .35s var(--ease), visibility 0s linear .35s;
}
.share-panel-scrim.is-open { opacity: 1; visibility: visible; transition-delay: 0s; }
.share-panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 50;
  width: min(30rem, 94vw); max-width: 100%;
  background: linear-gradient(180deg, rgba(29,10,28,.99), rgba(11,7,9,1));
  border-left: 1px solid rgba(214,174,82,.5);
  box-shadow: -26px 0 70px rgba(0,0,0,.55);
  transform: translateX(100%);
  transition: transform .42s var(--ease);
  display: flex; flex-direction: column;
}
.share-panel[hidden] { display: none; }
.share-panel.is-open { transform: translateX(0); }
.share-panel__bar {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
  padding: .6em .6em .6em 1.1em;
  border-bottom: 1px solid rgba(214,174,82,.3);
  background: rgba(0,0,0,.22);
}
.share-panel__invite {
  font-family: var(--serif, 'Cormorant Garamond', serif);
  font-style: italic; font-weight: 500;
  font-size: clamp(13px, 2.6vw, 15px);
  color: var(--gold-1, #E0B463);
  letter-spacing: .01em;
  padding-right: .75em;
}
.share-panel__close {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid rgba(214,174,82,.6); background: rgba(0,0,0,.3);
  color: var(--gold-1); font-size: 18px; line-height: 1; cursor: pointer;
}
.share-panel__close:hover, .share-panel__close:focus-visible { border-color: var(--gold-1); }
.share-panel__frame-host { flex: 1 1 auto; position: relative; background: #100616; }
.share-panel__iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
@media (max-width: 640px) {
  .share-panel { width: 100vw; }
}
