/* SKY SUNDER layered dynamic-town test.
 * Enabled by default. Use ?dynamicTownState=0|1|3|5 to inspect
 * milestones, or ?dynamicTownTest=0 for the legacy-background fallback.
 * milestone scenes without changing a save. Omit dynamicTownState for live data.
 */

body.ss-dynamic-town-test:has(#prep:not(.hidden)) #prep {
  background: #090812 !important;
  background-image: none !important;
  background-position: 0 0 !important;
  background-size: auto !important;
  /* The v18 PNGs contain the approved color, edge, foundation, and shadow
   * treatment. Keep runtime presentation neutral to avoid double grading. */
  --dt-building-integration: none;
}

body.ss-dynamic-town-test.pw-edit #prep #pwPaint {
  display: none !important;
}
/* Town stage is parented under #pwWorld (camera host). Keep it behind walkers. */
body.ss-dynamic-town-test.pw-edit #dynamicTownRuntime {
  z-index: 0;
}
body.ss-dynamic-town-test.pw-edit #dynamicTownFrame {
  display: none;
}

body.ss-dynamic-town-test #dynamicTownRuntime {
  position: absolute;
  left: var(--dt-left, 0px);
  top: var(--dt-top, 0px);
  width: var(--dt-width, 100%);
  height: var(--dt-height, 100%);
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  border-radius: 8px;
  background-image:
    linear-gradient(180deg, rgba(35, 16, 64, .08), rgba(9, 8, 18, .16)),
    radial-gradient(ellipse at 50% 115%, rgba(49, 42, 88, .32), transparent 68%),
    linear-gradient(180deg, #111936 0%, #141b38 46%, #0d1631 100%);
  background-position: center;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  background-color: #0d1631;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .44);
}

/* Same gold rounded chrome as .prepPanel (YOUR TOWN / SPELLS). Drawn above
   walkers so the border stays crisp on the open-town rectangle. */
body.ss-dynamic-town-test #dynamicTownFrame {
  position: absolute;
  left: var(--dt-left, 0px);
  top: var(--dt-top, 0px);
  width: var(--dt-width, 100%);
  height: var(--dt-height, 100%);
  z-index: 5;
  pointer-events: none;
  box-sizing: border-box;
  border: 1px solid rgba(200, 163, 90, .64);
  border-radius: 8px;
  background: transparent;
  box-shadow:
    inset 0 0 0 1px rgba(241, 230, 200, .035),
    inset 0 1px 0 rgba(255, 255, 255, .045),
    0 12px 30px rgba(0, 0, 0, .44);
}

body.ss-dynamic-town-test #dynamicTownScene {
  position: absolute;
  left: 50%;
  top: 0;
  width: 110%;
  aspect-ratio: 2048 / 1080;
  transform: translateX(-50%);
  transform-origin: 50% 0;
}

body.ss-dynamic-town-test #dynamicTownBase,
body.ss-dynamic-town-test #dynamicTownScene .dynamicTownBuilding {
  position: absolute;
  display: block;
  margin: 0;
  max-width: none;
  pointer-events: none;
  user-select: none;
}

body.ss-dynamic-town-test #dynamicTownBase {
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
}

body.ss-dynamic-town-test #dynamicTownScene .dynamicTownBuilding {
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  filter: var(--dt-building-integration);
  image-rendering: auto;
  transform:
    translate(var(--dt-building-dx, 0%), var(--dt-building-dy, 0%))
    rotate(var(--dt-building-rotate, 0deg));
  transform-origin: var(--dt-building-origin-x, 50%) var(--dt-building-origin-y, 50%);
  transition: opacity 240ms ease, filter 240ms ease, transform 240ms ease;
}

body.ss-dynamic-town-test #dynamicTownScene .dynamicTownBuilding.is-visible {
  opacity: 1;
}

/* Buildings are moved into #dynamicTownWalkerForeground (walker-z slot). No
 * duplicate scene copy, so nothing to hide here. */

/* A soft, foundation-only shadow connects each active transparent building to
 * its platform without recreating the hard silhouette bands rejected in v07. */
body.ss-dynamic-town-test #dynamicTownContactLayer {
  position: absolute;
  inset: 0;
  z-index: 10;
  overflow: visible;
  pointer-events: none;
  display: none;
}

body.ss-dynamic-town-test #dynamicTownContactLayer .dynamicTownContact {
  position: absolute;
  left: var(--dt-contact-x);
  top: var(--dt-contact-y);
  width: var(--dt-contact-w);
  height: var(--dt-contact-h);
  border-radius: 50%;
  opacity: 0;
  transform:
    translate(-50%, -50%)
    rotate(var(--dt-contact-rotate, 0deg))
    scale(var(--dt-contact-state-scale, .8));
  background: radial-gradient(
    ellipse at center,
    rgba(2, 4, 12, .54) 0%,
    rgba(4, 6, 16, .34) 44%,
    rgba(7, 8, 18, .10) 68%,
    transparent 82%
  );
  filter: blur(5px);
  mix-blend-mode: multiply;
  transition: opacity 280ms ease, transform 280ms ease;
}

body.ss-dynamic-town-test #dynamicTownContactLayer .dynamicTownContact.is-visible {
  opacity: var(--dt-contact-opacity);
}

/* Compact near-shadows reinforce the first quarter of each approved baked
 * cast shadow without changing any PNG asset. They are enabled by default;
 * use ?townNearShadows=0 for an instant baseline comparison. The older
 * townHallNearShadow query remains an alias for previously shared links. */
body.ss-dynamic-town-test.ss-town-near-shadow-test #dynamicTownContactLayer {
  display: block;
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact {
  --dt-contact-opacity: .45 !important;
  --dt-contact-rotate: -8deg !important;
  border-radius: 42% 74% 68% 46%;
  background:
    radial-gradient(
      ellipse at 18% 52%,
      rgba(1, 2, 9, .72) 0%,
      rgba(2, 3, 11, .58) 34%,
      rgba(3, 4, 13, .30) 62%,
      rgba(5, 6, 16, .10) 78%,
      transparent 90%
    );
  filter: blur(3.5px);
  transform:
    translate(-35%, -50%)
    rotate(var(--dt-contact-rotate))
    skewX(-16deg)
    scale(var(--dt-contact-state-scale, .8));
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact[data-family="hall"] {
  --dt-contact-x: 54% !important;
  --dt-contact-y: 43% !important;
  --dt-contact-w: 15.5% !important;
  --dt-contact-h: 7.2% !important;
  --dt-contact-opacity: .45 !important;
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact[data-family="treasury"] {
  --dt-contact-x: 26.4% !important;
  --dt-contact-y: 31.7% !important;
  --dt-contact-w: 8.8% !important;
  --dt-contact-h: 4.45% !important;
  --dt-contact-opacity: .43 !important;
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact[data-family="armory"] {
  --dt-contact-x: 31.2% !important;
  --dt-contact-y: 72.6% !important;
  --dt-contact-w: 24.55% !important;
  --dt-contact-h: 6.67% !important;
  --dt-contact-opacity: .43 !important;
  --dt-contact-rotate: -4deg !important;
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact[data-family="prison"] {
  --dt-contact-x: 78.8% !important;
  --dt-contact-y: 66.6% !important;
  --dt-contact-w: 11.55% !important;
  --dt-contact-h: 5.37% !important;
  --dt-contact-opacity: .44 !important;
  --dt-contact-rotate: -5deg !important;
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact[data-family="summoning"] {
  --dt-contact-x: 74.45% !important;
  --dt-contact-y: 26.25% !important;
  --dt-contact-w: 8.6% !important;
  --dt-contact-h: 4.26% !important;
  --dt-contact-opacity: .42 !important;
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact[data-family="arcanum"] {
  --dt-contact-x: 86.55% !important;
  --dt-contact-y: 29.25% !important;
  --dt-contact-w: 9.25% !important;
  --dt-contact-h: 5% !important;
  --dt-contact-opacity: .44 !important;
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact[data-family="troopchan"] {
  --dt-contact-x: 85.5% !important;
  --dt-contact-y: 44.35% !important;
  --dt-contact-w: 8.2% !important;
  --dt-contact-h: 4.45% !important;
  --dt-contact-opacity: .42 !important;
}

body.ss-dynamic-town-test.ss-town-near-shadow-test
  #dynamicTownContactLayer .dynamicTownContact[data-family="channeler"] {
  --dt-contact-x: 73.95% !important;
  --dt-contact-y: 40.95% !important;
  --dt-contact-w: 8% !important;
  --dt-contact-h: 4.26% !important;
  --dt-contact-opacity: .42 !important;
}

/* Walkers sit above the town painting. FRONT clips (scenery) sit above walkers.
 * Purchased buildings are the top art layer — never under FRONT, never under troops. */
body.ss-dynamic-town-test #prep #pwWorld {
  z-index: 1;
  /* Canvases cover all of #prep; without this, south-gate entrants (and tall
     sprites) paint into the Bazaar / side panels outside the gold town frame.
     Stage already clips the PNG layers — match that window here. */
  clip-path: inset(
    var(--dt-top, 0px)
    calc(100% - var(--dt-left, 0px) - var(--dt-width, 100%))
    calc(100% - var(--dt-top, 0px) - var(--dt-height, 100%))
    var(--dt-left, 0px)
    round 8px
  );
}

body.ss-dynamic-town-test #prep #pwRearCanvas {
  z-index: 1;
}
html:not(.ss-electron) body.ss-dynamic-town-test #prep #pwRearCanvas {
  filter: drop-shadow(0 1px 0 rgba(5, 5, 12, .42));
}

body.ss-dynamic-town-test #prep #dynamicTownWalkerForeground {
  position: absolute;
  z-index: 4;
  overflow: visible;
  pointer-events: none;
}

body.ss-dynamic-town-test #prep #dynamicTownWalkerForeground .dynamicTownForegroundBuilding {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  max-width: none;
  opacity: 0;
  filter: var(--dt-building-integration);
  image-rendering: auto;
  transform:
    translate(var(--dt-building-dx, 0%), var(--dt-building-dy, 0%))
    rotate(var(--dt-building-rotate, 0deg));
  transform-origin: var(--dt-building-origin-x, 50%) var(--dt-building-origin-y, 50%);
  pointer-events: none;
  user-select: none;
}

body.ss-dynamic-town-test #prep #dynamicTownWalkerForeground .dynamicTownForegroundBuilding.is-visible {
  opacity: 1;
}

/* Slow silhouette settle + obscuring dust/mist (menus stay immediate). */
body.ss-dynamic-town-test #dynamicTownScene .dynamicTownBuilding.is-visible.is-upgrading,
body.ss-dynamic-town-test #prep #dynamicTownWalkerForeground .dynamicTownForegroundBuilding.is-visible.is-upgrading {
  will-change: opacity;
  animation: dt-building-upgrade-reveal 1500ms cubic-bezier(.33, 0, .3, 1) both;
}

/* OPACITY ONLY — deliberately. These sprites are full-scene 2048×1080 PNGs, and the old
   reveal animated blur() + brightness() on two of them at once (incoming and ghost) while
   ~27 blurred dust nodes spawned; re-filtering that every frame on the main thread is what
   made the morph stutter. A pure opacity animation composites. */
@keyframes dt-building-upgrade-reveal {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Outgoing state, cloned under the incoming art so the swap dissolves instead of blinking.
   Holds full opacity while the new building is still faint, then thins out behind it. */
body.ss-dynamic-town-test #dynamicTownScene .dynamicTownBuilding.is-morph-out,
body.ss-dynamic-town-test #prep #dynamicTownWalkerForeground .dynamicTownForegroundBuilding.is-morph-out {
  opacity: 1;
  pointer-events: none;
  transition: none;
  will-change: opacity;
  animation: dt-building-morph-out 1500ms cubic-bezier(.33, 0, .3, 1) both;
}

/* Holds solid while the incoming is still faint, then clears from behind it. */
@keyframes dt-building-morph-out {
  0%   { opacity: 1; }
  58%  { opacity: 1; }
  100% { opacity: 0; }
}

body.ss-dynamic-town-test #prep #dynamicTownFxLayer {
  position: absolute;
  inset: 0;
  z-index: 40;
  overflow: visible;
  pointer-events: none;
}

body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust {
  position: absolute;
  left: var(--dt-dust-x);
  top: var(--dt-dust-y);
  width: var(--dt-dust-w);
  height: var(--dt-dust-h);
  /* Sit on the foundation; plume reads upward from a dense foot. */
  transform: translate(-50%, -88%);
  opacity: 0;
  pointer-events: none;
  /* Opaque through the plume body, tapering only at the very top. */
  -webkit-mask-image: linear-gradient(
    to top,
    #000 0%,
    #000 62%,
    rgba(0, 0, 0, .7) 82%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to top,
    #000 0%,
    #000 62%,
    rgba(0, 0, 0, .7) 82%,
    transparent 100%
  );
}

/* Authored BASE polyline: host is the full scene; puffs sit on the line in scene %. */
body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-authored {
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  transform: none;
  -webkit-mask-image: none;
  mask-image: none;
}

body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-authored .dtDustSheet,
body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-authored .dtDustSheet--lift,
body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-authored .dtDustSheet--veil {
  right: auto;
  bottom: auto;
  border-radius: 45%;
  transform: none;
}

body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-on {
  animation: dt-dust-host 2050ms ease-out both;
}

/* Hold the host solid; individual puffs do their own fade-out. */
@keyframes dt-dust-host {
  0%   { opacity: 0; }
  5%   { opacity: 1; }
  92%  { opacity: 1; }
  100% { opacity: 0; }
}

/* Foot haze on the foundation. */
body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dtDustSheet {
  position: absolute;
  left: 8%;
  right: 8%;
  bottom: -6%;
  height: 32%;
  border-radius: 50%;
  background:
    radial-gradient(ellipse at 50% 90%, rgba(220, 204, 172, .62) 0%, rgba(160, 140, 110, .3) 44%, transparent 74%);
  filter: blur(5px);
  opacity: .85;
  transform: scaleX(1) scaleY(.75);
  animation: dt-dust-sheet 1800ms ease-out both;
}

body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-arcane .dtDustSheet {
  background:
    radial-gradient(ellipse at 50% 90%, rgba(190, 174, 250, .55) 0%, rgba(110, 92, 190, .26) 44%, transparent 74%);
}

body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dtDustSheet--lift,
body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dtDustSheet--veil {
  display: none;
}

@keyframes dt-dust-sheet {
  0%   { opacity: 0; transform: scaleX(.75) scaleY(.5); }
  10%  { opacity: .9; transform: scaleX(1.05) scaleY(.85); }
  62%  { opacity: .7; transform: scaleX(1.1) scaleY(.95); }
  100% { opacity: 0; transform: scaleX(1.14) scaleY(1); }
}

body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-authored .dtDustSheet {
  animation-name: dt-dust-sheet-authored;
  transform-origin: 50% 90%;
  filter: blur(4.5px);
}

@keyframes dt-dust-sheet-authored {
  0%   { opacity: 0; transform: scale(.8); }
  10%  { opacity: .85; transform: scale(1.05); }
  62%  { opacity: .65; transform: scale(1.1); }
  100% { opacity: 0; transform: scale(1.14); }
}

/* Round, dusty billows. `--rise` is the px of upward travel, set per row from the
   building's measured art height so the plume tops out just above the roof. */
body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dtDustPuff {
  position: absolute;
  left: var(--px);
  top: var(--py);
  width: calc(18% * var(--scale));
  min-width: 22px;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(.45);
  transform-origin: 50% 50%;
  opacity: 0;
  background:
    radial-gradient(circle at 44% 42%,
      rgba(240, 230, 206, .95) 0%,
      rgba(214, 198, 166, .8) 34%,
      rgba(168, 150, 120, .42) 58%,
      rgba(150, 132, 104, .12) 78%,
      transparent 88%);
  filter: blur(3px);
  animation: dt-dust-puff 1650ms linear both;
  animation-delay: var(--delay);
}

/* Authored: scene-% billows rising off the BASE line. */
body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-authored .dtDustPuff {
  width: calc(3.6% * var(--scale));
  min-width: 26px;
  max-width: 120px;
  filter: blur(3.5px);
}

body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust.is-arcane .dtDustPuff {
  background:
    radial-gradient(circle at 44% 42%,
      rgba(232, 224, 255, .92) 0%,
      rgba(190, 174, 250, .74) 34%,
      rgba(124, 108, 205, .4) 58%,
      rgba(96, 82, 170, .12) 78%,
      transparent 88%);
}

/* Bloom fast on the foot, then hang and drift up slowly before dissipating.
   The long opaque plateau is what actually veils the building reveal. */
@keyframes dt-dust-puff {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.5);
    animation-timing-function: cubic-bezier(.2, .85, .35, 1);
  }
  9% {
    opacity: var(--op, .9);
    transform: translate(-50%, calc(-50% - var(--rise, 60px) * .09)) scale(1.02);
    animation-timing-function: linear;
  }
  48%  { opacity: var(--op, .9); transform: translate(-50%, calc(-50% - var(--rise, 60px) * .44)) scale(1.2); }
  76%  { opacity: calc(var(--op, .9) * .6); transform: translate(-50%, calc(-50% - var(--rise, 60px) * .75)) scale(1.38); }
  100% { opacity: 0; transform: translate(-50%, calc(-50% - var(--rise, 60px))) scale(1.52); }
}

@media (prefers-reduced-motion: reduce) {
  body.ss-dynamic-town-test #dynamicTownScene .dynamicTownBuilding.is-upgrading,
  body.ss-dynamic-town-test #prep #dynamicTownWalkerForeground .dynamicTownForegroundBuilding.is-upgrading,
  body.ss-dynamic-town-test #dynamicTownScene .dynamicTownBuilding.is-morph-out,
  body.ss-dynamic-town-test #prep #dynamicTownWalkerForeground .dynamicTownForegroundBuilding.is-morph-out {
    animation-duration: 1ms !important;
  }
  body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dynamicTownBuildDust,
  body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dtDustPuff,
  body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dtDustSheet,
  body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dtDustSheet--lift,
  body.ss-dynamic-town-test #prep #dynamicTownFxLayer .dtDustSheet--veil {
    animation-duration: 1ms !important;
    opacity: 0 !important;
  }
}

body.ss-dynamic-town-test #prep #pwCanvas,
body.ss-dynamic-town-test #prep #pwFront {
  z-index: 3;
}

/* Live plants sit with the town painting, behind troops. FRONT annotations
   are the only way scenery (torches, bushes, walls) paints over walkers. */
body.ss-dynamic-town-test #prep #pwPlants {
  z-index: 0;
}

html:not(.ss-electron) body.ss-dynamic-town-test #prep #pwCanvas,
html:not(.ss-electron) body.ss-dynamic-town-test #prep #pwRearCanvas {
  filter: drop-shadow(0 1px 0 rgba(5, 5, 12, .42));
}
html.ss-electron body.ss-dynamic-town-test #prep #pwCanvas,
html.ss-electron body.ss-dynamic-town-test #prep #pwRearCanvas {
  filter: none;
}

body.ss-dynamic-town-test #prep #pwLights {
  display: block;
  /* Above buildings (z 4) and FRONT (z 3) so torch glow still sits on the sprites.
     Editor SVG is also z 5 and mounts later, so FRONT vertices stay clickable. */
  z-index: 5;
}

body.ss-dynamic-town-test #prep #pwLights .pwGlow-torch {
  width: calc(4.6% * (0.55 + 0.45 * var(--pw-light-intensity)));
  aspect-ratio: 1.35 / 1;
}

body.ss-dynamic-town-test #prep #pwLights .pwGlow-crystal,
body.ss-dynamic-town-test #prep #pwLights .pwGlow-violet {
  width: calc(6.8% * (0.55 + 0.45 * var(--pw-light-intensity)));
}

body.ss-dynamic-town-test #prep #pwLights .pwGlow-crystal .pwFibril,
body.ss-dynamic-town-test #prep #pwLights .pwGlow-violet .pwFibril {
  opacity: .7;
}

body.ss-dynamic-town-test #prep > #prepHead,
body.ss-dynamic-town-test #prep > #prepBody,
body.ss-dynamic-town-test #prep > .ss-prep-brand-mark {
  z-index: 5;
}
