/* Stage art and ambient life (owner: scene; CONTRACTS.md §5): stage ghost page rules, FX, fog, art variant
   fallbacks, hotspots, Peek. Decorative @keyframes only under no-preference; a light's strength is color-mix() or
   the layer's opacity (gap S5). */

/* ---------- Stage ghosts (F22): .bf-ghost.bf-ghost--ambient, moved by transform from js/scene.js ---------- */
/* #ambient is pointer-events:none (app.css) and .bf-ghost does not set it back (gap S9). */
#ambient .bf-ghost--ambient{pointer-events:auto}
html[data-peek] .bf-ghost--ambient{pointer-events:none}

/* ---------- FX nodes (F15): pooled in #fx (.bf-deco), moved by the rAF loop or element.animate() ---------- */
#fx{overflow:hidden}
.fx{position:absolute;left:0;top:0;opacity:0;will-change:transform,opacity;pointer-events:none}
/* 52px: one cell of the Batface flap sprite (8 frames, facing right); the bat fills about 30px of it, as wide as the old
   glyph, and scene.js centers on the cell. .is-left flips it for a right-to-left flight. */
.fx-bat{width:52px;height:52px}
.fx-bat::before{content:"";position:absolute;inset:0;border-radius:50%;background:radial-gradient(closest-side,color-mix(in srgb,var(--moon) 35%,transparent),transparent)}
.fx-bat i{position:relative;display:block;width:100%;height:100%;background:url("../assets/batface-fly-v1-160.webp?v=20261016g") 0 0/800% 100% no-repeat}
.fx-bat.is-left i{transform:scaleX(-1)}
/* Ember and poof sizes are particle geometry; the negative margin centers the ember on its point. */
.fx-ember{width:6px;height:6px;margin:-3px 0 0 -3px;border-radius:50%;background:radial-gradient(circle,var(--bone-50) 0,var(--candle-300) 35%,color-mix(in srgb,var(--pumpkin-600) 60%,transparent) 60%,transparent 72%)}
.fx-ember.is-slime{width:9px;height:9px;background:radial-gradient(circle,var(--bone-50) 0,var(--slime-400) 40%,color-mix(in srgb,var(--slime-700) 50%,transparent) 62%,transparent 74%)}
.fx-wisp{width:220px;height:56px;border-radius:50%;background:radial-gradient(closest-side,color-mix(in srgb,var(--mist-300) 20%,transparent),color-mix(in srgb,var(--slime-400) 6%,transparent) 60%,transparent)}
.fx-poof{width:14px;height:14px;border-radius:50%;background:radial-gradient(circle,color-mix(in srgb,var(--ecto-200) 95%,transparent) 0,color-mix(in srgb,var(--mist-300) 55%,transparent) 55%,transparent 72%)}
.fx-flare{width:240px;height:240px;border-radius:50%;background:radial-gradient(closest-side,color-mix(in srgb,var(--slime-400) 60%,transparent),color-mix(in srgb,var(--slime-400) 18%,transparent) 45%,transparent)}
html[data-lights="low"] .fx-wisp{display:none}
@media (prefers-reduced-motion:no-preference){
  @keyframes fx-flap{to{background-position-x:calc(800% / 7)}}
  /* .48s: one full wing beat across the 8 frames (no token for it, owner gap S5). */
  .fx-bat.is-fly i{animation:fx-flap .48s steps(8,end) infinite}
}

/* ---------- Fog (two pre-blurred gradient layers; drift is a slow transform) ---------- */
#fog{overflow:hidden;opacity:0;transition:opacity var(--dur-reveal) var(--ease-out)}
html.art-ready #fog{opacity:1}
#fog::before,#fog::after{content:"";position:absolute;left:-25%;right:-25%;pointer-events:none}
#fog::before{bottom:-4%;height:34%;background:radial-gradient(ellipse 40% 60% at 30% 70%,color-mix(in srgb,var(--mist-300) 16%,transparent),transparent 70%),radial-gradient(ellipse 35% 55% at 76% 80%,color-mix(in srgb,var(--slime-400) 11%,transparent),transparent 70%)}
#fog::after{bottom:6%;height:24%;background:radial-gradient(ellipse 30% 50% at 60% 60%,color-mix(in srgb,var(--mist-300) 10%,transparent),transparent 70%),radial-gradient(ellipse 25% 45% at 15% 70%,color-mix(in srgb,var(--slime-400) 6%,transparent),transparent 70%)}
html[data-lights="low"] #fog{display:none}
@media (prefers-reduced-motion:no-preference){
  @keyframes fog-drift{to{transform:translateX(12%)}}
  #fog::before{animation:fog-drift var(--dur-fog) var(--ease-linear) infinite alternate}
  #fog::after{animation:fog-drift calc(var(--dur-fog) * .75) var(--ease-linear) infinite alternate-reverse}
}

/* ---------- Art variants (F18 party, F20 dawn): crossfade over the base art ---------- */
#artVariant{transition:opacity var(--dur-gate) var(--ease-out)}
#artVariant.is-on{opacity:1}
/* Dawn fallback (until I7): the base art itself, through a static filter; only opacity animates */
#artVariant[data-fallback="dawn"]{filter:saturate(.75) sepia(.2) brightness(1.1)}
/* Party fallback (until I4): the act's wash spilling from the gate (act-wash follows data-theme). */
#hotspots::before{content:"";position:absolute;inset:0;pointer-events:none;background:radial-gradient(circle at 50% 62%,var(--act-wash),transparent 45%);mix-blend-mode:screen;opacity:0;transition:opacity var(--dur-gate) var(--ease-out)}
html[data-art="party"]:not(.art-variant-real) #hotspots::before{opacity:1}
/* Dawn sky wash (until I7) */
#sceneArt::after{content:"";position:absolute;inset:0;pointer-events:none;background:linear-gradient(var(--act-wash),transparent 40%);opacity:0;transition:opacity var(--dur-gate) var(--ease-out)}
html[data-art="dawn"]:not(.art-variant-real) #sceneArt::after{opacity:1}
/* First paint in a later phase: no fade */
html.art-instant #artVariant,html.art-instant #hotspots::before,html.art-instant #sceneArt::after{transition:none!important}
/* Calm keeps these fades, at dur-ui (D8). */
html[data-calm] #artVariant,html[data-calm] #hotspots::before,html[data-calm] #sceneArt::after{transition-duration:var(--dur-ui)!important}

/* ---------- Hotspots: percentages of #artFrame, per art variant (§2.3); measured with ?hs=1 ---------- */
/* ?hs=1 outlines every hotspot for measuring: css/hs.css, which only that view loads (boot script). */
/* z-index 1 (2: compact boxes over the gate box): local stacking inside #artFrame. */
.hs{position:absolute;left:var(--l);top:var(--t);width:var(--w);height:var(--h);margin:0;padding:0;border:0;border-radius:var(--r-sheet);background:transparent;z-index:1}
/* A 45px hit floor (plan §5.7), grown about the box's centre; the percentages resolve against #artFrame. */
.hs:not([data-secret="batface"]){left:calc(var(--l) + (var(--w) - max(var(--w),45px)) / 2);top:calc(var(--t) + (var(--h) - max(var(--h),45px)) / 2);width:max(var(--w),45px);height:max(var(--h),45px)}
.hs:active{scale:none} /* hotspots react instead of pressing (the bundle's press is :where(), so this wins) */
/* Landscape: bg-1672-v1 (the Gathering, and every act until its variant art has loaded). */
.hs[data-secret="gate"]{--l:37%;--t:54%;--w:26%;--h:30%}
.hs[data-secret="pool"]{--l:1%;--t:73%;--w:20%;--h:18%}
.hs[data-secret="patchL"]{--l:16%;--t:73%;--w:22%;--h:23%}
.hs[data-secret="patchR"]{--l:63%;--t:72%;--w:27%;--h:25%}
/* Wall lanterns: the left wall's outer lamp and the right wall's inner one (clear of the plate and the rail). */
.hs[data-secret="wallL"]{--l:5.5%;--t:54.5%;--w:7.5%;--h:13%}
.hs[data-secret="wallR"]{--l:74.75%;--t:56.5%;--w:7.5%;--h:13%}
.hs[data-secret="moon"]{--l:47%;--t:36%;--w:7%;--h:12%}
.hs[data-secret="batface"]{--l:48%;--t:2%;--w:5%;--h:8%}
@media (orientation:landscape){
  /* Party and dawn landscapes (once loaded, .art-variant-real), measured on their 1672 files; the dawn "moon" is
     the sun, and the gate box starts under it. */
  html.art-variant-real[data-art="party"] .hs[data-secret="gate"]{--l:34%;--t:38%;--w:32%;--h:36%}
  html.art-variant-real[data-art="party"] .hs[data-secret="pool"]{--l:2%;--t:76%;--w:28%;--h:15%}
  html.art-variant-real[data-art="party"] .hs[data-secret="patchL"]{--l:13%;--t:54%;--w:12%;--h:14%}
  html.art-variant-real[data-art="party"] .hs[data-secret="patchR"]{--l:79%;--t:62%;--w:18%;--h:19%}
  html.art-variant-real[data-art="party"] .hs[data-secret="wallL"]{--l:21.5%;--t:42%;--w:6%;--h:11%}
  html.art-variant-real[data-art="party"] .hs[data-secret="wallR"]{--l:73.5%;--t:42%;--w:6%;--h:11%}
  html.art-variant-real[data-art="party"] .hs[data-secret="moon"]{--l:50%;--t:0%;--w:12.5%;--h:14%}
  html.art-variant-real[data-art="dawn"] .hs[data-secret="gate"]{--l:35%;--t:54%;--w:30%;--h:22%}
  html.art-variant-real[data-art="dawn"] .hs[data-secret="pool"]{--l:3%;--t:77%;--w:28%;--h:15%}
  html.art-variant-real[data-art="dawn"] .hs[data-secret="patchL"]{--l:12%;--t:54%;--w:13%;--h:13%}
  html.art-variant-real[data-art="dawn"] .hs[data-secret="patchR"]{--l:80%;--t:62%;--w:18%;--h:20%}
  html.art-variant-real[data-art="dawn"] .hs[data-secret="wallL"]{--l:22%;--t:42%;--w:6%;--h:11%}
  html.art-variant-real[data-art="dawn"] .hs[data-secret="wallR"]{--l:73.5%;--t:42%;--w:6%;--h:11%}
  html.art-variant-real[data-art="dawn"] .hs[data-secret="moon"]{--l:50%;--t:43%;--w:9%;--h:10%}
}
@media (orientation:portrait){
  /* Percentages of the portrait painting (D15), measured on hero-portrait-v1; every box ends at or above 66%. */
  html.art-portrait .hs[data-secret="gate"]{--l:26%;--t:42%;--w:48%;--h:19%}
  html.art-portrait .hs[data-secret="pool"]{--l:0%;--t:59%;--w:28%;--h:7%}
  html.art-portrait .hs[data-secret="patchL"]{--l:14%;--t:58%;--w:28%;--h:8%}
  html.art-portrait .hs[data-secret="patchR"]{--l:64%;--t:57%;--w:36%;--h:9%}
  html.art-portrait .hs[data-secret="wallL"]{--l:4%;--t:34%;--w:22%;--h:9%}
  html.art-portrait .hs[data-secret="wallR"]{--l:74%;--t:34%;--w:22%;--h:9%}
  html.art-portrait .hs[data-secret="moon"]{--l:39%;--t:30%;--w:22%;--h:10%}
  /* Party art (once loaded): walls and moon start at 27%, under the title band (gap S10). */
  html.art-portrait.art-variant-real[data-art="party"] .hs[data-secret="gate"]{--l:26%;--t:41%;--w:48%;--h:18%}
  html.art-portrait.art-variant-real[data-art="party"] .hs[data-secret="pool"]{--l:0%;--t:55%;--w:27%;--h:11%}
  html.art-portrait.art-variant-real[data-art="party"] .hs[data-secret="patchL"]{--l:24%;--t:59%;--w:18%;--h:7%}
  html.art-portrait.art-variant-real[data-art="party"] .hs[data-secret="patchR"]{--l:64%;--t:57%;--w:36%;--h:9%}
  html.art-portrait.art-variant-real[data-art="party"] .hs[data-secret="wallL"]{--l:1%;--t:27%;--w:17%;--h:14%}
  html.art-portrait.art-variant-real[data-art="party"] .hs[data-secret="wallR"]{--l:82%;--t:27%;--w:17%;--h:14%}
  html.art-portrait.art-variant-real[data-art="party"] .hs[data-secret="moon"]{--l:39%;--t:27%;--w:22%;--h:8%}
  /* Dawn art (hero-after-v1-portrait, after): the "moon" box moves to the rising sun, clear of the
     sleeping Batface under the arch. */
  html.art-portrait.art-variant-real[data-art="dawn"] .hs[data-secret="gate"]{--l:26%;--t:45%;--w:48%;--h:16%}
  html.art-portrait.art-variant-real[data-art="dawn"] .hs[data-secret="pool"]{--l:0%;--t:60%;--w:29%;--h:6%}
  html.art-portrait.art-variant-real[data-art="dawn"] .hs[data-secret="patchL"]{--l:12%;--t:59%;--w:30%;--h:7%}
  html.art-portrait.art-variant-real[data-art="dawn"] .hs[data-secret="patchR"]{--l:66%;--t:58%;--w:34%;--h:8%}
  html.art-portrait.art-variant-real[data-art="dawn"] .hs[data-secret="wallL"]{--l:2%;--t:33%;--w:22%;--h:9%}
  html.art-portrait.art-variant-real[data-art="dawn"] .hs[data-secret="wallR"]{--l:76%;--t:33%;--w:22%;--h:9%}
  html.art-portrait.art-variant-real[data-art="dawn"] .hs[data-secret="moon"]{--l:42%;--t:38.5%;--w:18%;--h:6.5%}
  /* Compact: the chips cover the pumpkins, so the patches and the pool rise into one 48px row sp-2 above them
     (--hs-floor); the gate ends where the row starts (45px at least), under the small boxes (z 2). */
  html.art-portrait.compact{--hs-floor:calc(var(--vh100) - var(--stack-net) - var(--hit) - var(--sp-2));--hs-row:48px}
  html.art-portrait.compact .hs:is([data-secret="patchL"],[data-secret="patchR"],[data-secret="pool"]){top:min(var(--t),calc(var(--hs-floor) - var(--hs-row)));height:var(--hs-row);z-index:2}
  html.art-portrait.compact .hs[data-secret="pool"]{left:0;width:26%}
  html.art-portrait.compact .hs[data-secret="patchL"]{left:26%;width:24%}
  html.art-portrait.compact .hs[data-secret="patchR"]{left:64%;width:36%}
  html.art-portrait.compact .hs[data-secret="moon"]{z-index:2}
  html.art-portrait.compact .hs[data-secret="gate"]{--gt:min(var(--t),calc(var(--hs-floor) - var(--hs-row) - 45px));top:var(--gt);height:min(var(--h),calc(var(--hs-floor) - var(--hs-row) - var(--gt)))}
}

/* ---------- Hotspot lights (F09/F30): ::before = static tints, ::after = tap glow / hint glint ---------- */
.hs::before,.hs::after{content:"";position:absolute;pointer-events:none;border-radius:50%;opacity:0}
.hs::before{inset:-15%;transition:opacity var(--dur-gate) var(--ease-out)}
.hs::after{inset:-10%;background:radial-gradient(closest-side,var(--hs-glow),transparent);mix-blend-mode:screen}
.hs{--hs-glow:var(--candle-300)}
.hs[data-glow="slime"]{--hs-glow:var(--slime-700)}
.hs[data-glow="pumpkin"]{--hs-glow:var(--pumpkin-600)}
.hs[data-glow="lilac"]{--hs-glow:var(--moon)}
.hs[data-glow="amber"]{--hs-glow:var(--amber-400)}
.hs.is-react::after{opacity:.55} /* Calm: a steady glow while the reaction lasts (≤ 1 per 650 ms) */
/* Calm keeps the tints' and the reaction's fades, at dur-ui (D8): no 0 → glow step. */
html[data-calm] .hs::before{transition-duration:var(--dur-ui)!important}
html[data-calm] .hs::after{transition:opacity var(--dur-ui) var(--ease-out)!important}
@media (prefers-reduced-motion:no-preference){
  @keyframes hs-pop{0%{opacity:0;transform:scale(.7)}35%{opacity:.6}100%{opacity:0;transform:scale(1.15)}}
  @keyframes hs-glint{50%{opacity:.4}}
  @keyframes hs-heart{0%,100%{opacity:.5}50%{opacity:1}}
  html:not([data-calm]) .hs.is-react::after{animation:hs-pop var(--dur-reveal) var(--ease-out) forwards}
  /* secrets.js toggles .is-hint on one unfound secret after 8 s idle (never under Calm) */
  html:not([data-calm]) .hs.is-hint::after{animation:hs-glint var(--dur-pulse) var(--ease-loop) 2}
}
/* The gate warms up for the final hour, beats in the final minute, and glows for good at 6,666 */
.hs[data-secret="gate"]::before{background:radial-gradient(closest-side,color-mix(in srgb,var(--amber-400) 50%,transparent),color-mix(in srgb,var(--pumpkin-600) 18%,transparent) 60%,transparent);mix-blend-mode:screen}
html:is([data-phase="final-hour"],[data-phase="final-minute"]) .hs[data-secret="gate"]::before,
html[data-milestones~="gate"] .hs[data-secret="gate"]::before{opacity:.75}
@media (prefers-reduced-motion:no-preference){
  /* The gate's heartbeat loops on dur-pulse like every loop (owner confirmation S10; it was 1 s). */
  html[data-phase="final-minute"]:not([data-calm]) .hs[data-secret="gate"]::before{animation:hs-heart var(--dur-pulse) var(--ease-loop) infinite}
}
/* The moon: crimson at 5,000 (F37), deeper during a Blood Moon, eerie lilac in the Witching Hour */
.hs[data-secret="moon"]::before{inset:-22%;background:radial-gradient(closest-side,var(--bloodmoon),var(--bloodmoon-wash) 55%,transparent);mix-blend-mode:multiply}
html[data-milestones~="bloodmoon"] .hs[data-secret="moon"]::before{opacity:.6}
html.blood-moon .hs[data-secret="moon"]::before{opacity:1}
html[data-phase="witching"]:not(.blood-moon) .hs[data-secret="moon"]::before{opacity:.8;background:radial-gradient(closest-side,color-mix(in srgb,var(--slime-400) 45%,transparent),color-mix(in srgb,var(--lilac-300) 30%,transparent) 55%,transparent);mix-blend-mode:screen}
/* Batface perch (F30; secrets.js un-hides it and picks data-perch). The portrait perches live in extras.css. */
.hs[data-secret="batface"]{min-width:var(--hit);min-height:var(--hit);background:url("../favicon.svg?v=20261016g") center/contain no-repeat}
.hs[data-secret="batface"][data-perch="left"]{--l:27%;--t:28%}
.hs[data-secret="batface"][data-perch="right"]{--l:70%;--t:28%}

/* ---------- Peek (F30): pan the portrait cover plate; the text steps aside for 6 s ---------- */
@media (orientation:portrait){
  html.art-cover #sceneArt{transition:transform var(--dur-peek) var(--ease-out)}
  html.art-cover[data-peek="left"] #sceneArt{transform:translateX(calc(64.38 * var(--u)))}
  html.art-cover[data-peek="right"] #sceneArt{transform:translateX(calc(-64.66 * var(--u)))}
}
/* The title card and plate fade in the bundle; the live banner and the ghosts here. */
#liveBanner,#ambient{transition:opacity var(--dur-ui) var(--ease-out)}
html[data-peek] :is(#liveBanner,#ambient){opacity:var(--opacity-peek)}
