/* G24 Halloween decorations — the sheet for static/js/halloween.js.
 *
 * Loaded from `{% block head %}` in both game templates, so it lands AFTER
 * styles.css and wins ties, and only inside the seasonal window.
 *
 * Everything is scoped under `.m67-hw` and every keyframe is prefixed `hw-`,
 * so this file can be deleted in November without grepping the other 7,600
 * lines. Two rules it must keep obeying:
 *
 *  - **Nothing here sets `display` on a piece.** Existence is decided in JS
 *    with `el.hidden` (breakpoint set, then the no-overlap veto), and the
 *    site-wide `[hidden] { display: none !important }` rule is what carries it
 *    out. A `display` here would either fight that rule or make `hidden=false`
 *    a lie. There are NO `[hidden]` rules in this file.
 *  - **Geometry lives here, at three breakpoints; JS only sets what it has to
 *    measure** — the bat roosts, the lantern's clear-of-the-board nudge and
 *    the canvas size.
 *  - **The phone cut is `max-width: 768px`, matching the GAME's own layout
 *    query in styles.css (~2595), NOT 767.** At exactly 768 the sidebar is
 *    already gone and the board is full width; a one-pixel disagreement put
 *    every desktop piece on top of the board. Change both or neither, and
 *    keep `isPhoneLayout()` in halloween.js equal to this.
 */

/* ── The layer ─────────────────────────────────────────────────────────── */
.m67-hw {
  position: fixed;
  inset: 0;
  pointer-events: none;      /* the whole point; interactive pieces opt back in */
  z-index: 850;              /* under the Pixi solve FX (900) and every modal */
  overflow: hidden;
  --hw-glow: 1;              /* JS republishes this once a second from --emp */
  --hw-orange: #ff7a1a;
  --hw-ember: #ffc43c;
  --hw-purple: #8b5cf6;
  --hw-bone: #e8e4d9;
}

.m67-hw__embers {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* ── Fog: two slow drifts in opposite directions ───────────────────────── */
.m67-hw__fog {
  position: absolute;
  left: -30%;
  width: 160%;
  bottom: 0;
  height: 22vh;
  opacity: .18;
  will-change: transform;
}
.m67-hw__fog--a {
  background: radial-gradient(ellipse 60% 120% at 25% 115%, rgba(190, 175, 220, .85) 0%, rgba(140, 120, 180, .25) 45%, transparent 72%);
  animation: hw-fog-a 38s linear infinite;
}
.m67-hw__fog--b {
  background: radial-gradient(ellipse 55% 120% at 70% 118%, rgba(160, 200, 210, .7) 0%, rgba(110, 140, 170, .2) 45%, transparent 70%);
  animation: hw-fog-b 51s linear infinite;
  opacity: .14;
}
@keyframes hw-fog-a { from { transform: translateX(-8%); } to { transform: translateX(8%); } }
@keyframes hw-fog-b { from { transform: translateX(7%); } to { transform: translateX(-7%); } }

/* ── Cobwebs: static, faint, corner-anchored ───────────────────────────── */
.m67-hw__web { position: absolute; opacity: .12; }
.m67-hw__web--b { transform: scaleX(-1); }
.m67-hw__web-svg { width: 100%; height: 100%; display: block; }
.m67-hw__web-svg path {
  fill: none;
  stroke: #dfe6f2;
  stroke-width: 1.4;
  stroke-linecap: round;
}

/* ── Moon ──────────────────────────────────────────────────────────────── */
.m67-hw__moon { position: absolute; }
/* overflow:visible or the 30px halo drop-shadow is clipped to the 56px svg box
   and the moon renders inside a pale SQUARE. Only a screenshot catches it. */
.m67-hw__moon-svg { width: 100%; height: 100%; display: block; overflow: visible; }
.m67-hw__moon-disc {
  fill: #f6efd6;
  filter: drop-shadow(0 0 14px rgba(255, 240, 190, .55)) drop-shadow(0 0 30px rgba(255, 210, 120, .30));
}
.m67-hw__craters { fill: rgba(190, 178, 146, .55); }

/* ── Jack-o'-lantern ───────────────────────────────────────────────────── */
.m67-hw__lantern {
  position: absolute;
  pointer-events: auto;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.m67-hw__lantern:focus-visible { outline: 2px solid var(--hw-ember); outline-offset: 4px; border-radius: 12px; }
.m67-hw__lantern-svg { width: 100%; height: 100%; display: block; overflow: visible; }
.m67-hw__gourd { fill: #e8661a; }
.m67-hw__stem { fill: #4f7a2a; }
.m67-hw__rib { fill: none; stroke: rgba(120, 45, 5, .35); stroke-width: 2.5; }
.m67-hw__face,
.m67-hw__grin {
  fill: #ffdf7a;
  filter: drop-shadow(0 0 calc(7px * var(--hw-glow)) rgba(255, 176, 40, .95))
          drop-shadow(0 0 calc(18px * var(--hw-glow)) rgba(255, 120, 20, .55));
  animation: hw-flicker-a 3.1s ease-in-out infinite;
}
.m67-hw__grin { animation: hw-flicker-b 4.7s ease-in-out infinite; }
.m67-hw__grin--wide { opacity: 0; }
.m67-hw__lantern--wide .m67-hw__grin { opacity: 0; }
.m67-hw__lantern--wide .m67-hw__grin--wide { opacity: 1; }
.m67-hw__lantern--flash .m67-hw__face,
.m67-hw__lantern--flash .m67-hw__grin { fill: #fff6c9; }
.m67-hw__lantern--flash .m67-hw__gourd { fill: #ff8a2c; }
/* Two keyframes at 3.1s and 4.7s so the flicker never visibly loops. */
@keyframes hw-flicker-a {
  0%, 100% { opacity: 1; }
  38%      { opacity: .82; }
  61%      { opacity: .95; }
}
@keyframes hw-flicker-b {
  0%, 100% { opacity: .96; }
  27%      { opacity: .78; }
  74%      { opacity: 1; }
}

/* ── Candles ───────────────────────────────────────────────────────────── */
/* The base size is a FLOOR, not the design: an absolutely-positioned box with
   no width around an `width:100%` <svg> resolves circularly and shrink-to-fits
   to the SVG's 300x150 default, so every candle must have a size before its
   group's placer runs. The strip overrides it at >=1100, the mantel and the
   phone band write their own inline (heights vary per candle there). */
.m67-hw__candle {
  position: absolute;
  width: 24px;
  height: 58px;
  pointer-events: auto;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.m67-hw__candle:focus-visible { outline: 2px solid var(--hw-ember); outline-offset: 4px; border-radius: 8px; }
.m67-hw__candle-svg { width: 100%; height: 100%; display: block; overflow: visible; }
.m67-hw__wax { fill: #efe3cd; }
.m67-hw__wick { fill: none; stroke: #3c3228; stroke-width: 2; }
.m67-hw__flame {
  fill: #ffd15c;
  transform-origin: 50% 90%;
  filter: drop-shadow(0 0 6px rgba(255, 180, 50, .95));
  animation: hw-flame 1.1s ease-in-out infinite;
}
/* A solid low-alpha ellipse, not a gradient: SVG `fill` takes no gradient
   function, and a <radialGradient> def would be two more nodes per candle. */
.m67-hw__halo {
  fill: rgba(255, 190, 70, .16);
  filter: blur(4px);
  animation: hw-halo 2.3s ease-in-out infinite;
}
.m67-hw__candle[data-lit="0"] .m67-hw__flame,
.m67-hw__candle[data-lit="0"] .m67-hw__halo { opacity: 0; }
.m67-hw__candle[data-lit="0"] .m67-hw__wick { stroke: #1d1a16; }
.m67-hw__pop .m67-hw__flame { animation: hw-pop .2s ease-out; }
@keyframes hw-flame {
  0%, 100% { transform: scaleY(1) skewX(0deg); }
  30%      { transform: scaleY(1.12) skewX(-5deg); }
  62%      { transform: scaleY(.93) skewX(4deg); }
}
@keyframes hw-halo { 0%, 100% { opacity: 1; } 45% { opacity: .62; } }
@keyframes hw-pop { from { transform: scale(.2); } to { transform: scale(1); } }

/* ── The witch ─────────────────────────────────────────────────────────── */
/* Transient: she carries NO `data-hw-piece`, so she never goes through the
   breakpoint pass, the overlap veto or the hit-test audit — she is hidden
   between flights and `pointer-events:none` during one, and her whole path is
   over the header band. The layer's `overflow:hidden` is what clips her while
   she is still off the edge, which is why she is `absolute` in here rather
   than `fixed`.
   The bob lives on an INNER element on purpose: the crossing is a WAAPI
   translateX on the outer node, and a single element cannot carry both without
   one animation stamping on the other's `transform`. */
.m67-hw__witch {
  position: absolute;
  left: 0;
  pointer-events: none;
  will-change: transform;
}
.m67-hw__witch-bob {
  width: 100%;
  height: 100%;
  animation: hw-witch-bob 1.2s ease-in-out infinite;
}
.m67-hw__witch-svg { width: 100%; height: 100%; display: block; overflow: visible; }
/* A silhouette needs a rim or it is a hole in the page — the midnight palette
   is nearly the same value as the fill. The rim is a drop-shadow on the GROUP,
   not a stroke on each path: strokes outline every subpath, so the arm over
   the cape and the handle behind the body all got gold seams and the whole
   thing read as a wireframe tangle rather than one figure (screenshotted at
   5x before it was believed). A filter follows the union's alpha, so only the
   true outline glows. */
.m67-hw__witch-body {
  fill: #0e0718;
  filter: drop-shadow(0 0 2.5px rgba(255, 196, 60, .95))
          drop-shadow(0 0 7px rgba(255, 140, 30, .55));
}
.m67-hw__witch-bristles {
  fill: none;
  stroke: #0e0718;
  stroke-width: 3;
  stroke-linecap: round;
}
@keyframes hw-witch-bob {
  0%, 100% { transform: translateY(-12px); }
  50%      { transform: translateY(12px); }
}

/* ── Bats ──────────────────────────────────────────────────────────────── */
/* JS sets left/top to the roost. The half-width offset is a MARGIN, never a
   translate: the flight animation owns `transform` outright, and a centring
   translate there would be wiped the moment a bat is startled. */
.m67-hw__bat {
  position: absolute;
  width: 40px;
  height: 30px;
  margin-left: -20px;
  transform-origin: 50% 0;        /* hanging by the feet */
  pointer-events: auto;
  cursor: pointer;
  animation: hw-sway 3s ease-in-out infinite alternate;
  -webkit-tap-highlight-color: transparent;
}
.m67-hw__bat:focus-visible { outline: 2px solid var(--hw-purple); outline-offset: 3px; border-radius: 6px; }
.m67-hw__bat-svg { width: 100%; height: 100%; display: block; overflow: visible; }
.m67-hw__bat-body { fill: #1d1526; stroke: rgba(139, 92, 246, .55); stroke-width: 1; }
.m67-hw__bat-eyes { fill: var(--hw-ember); }
/* Upside down at the roost; the right way up the moment it lets go. */
.m67-hw__bat .m67-hw__bat-svg { transform: rotate(180deg); }
.m67-hw__bat--flying { animation: hw-flap .16s linear infinite; }
.m67-hw__bat--flying .m67-hw__bat-svg { transform: rotate(0deg); }
.m67-hw__bat--eek { animation: hw-eek .18s ease-in-out 5; }
@keyframes hw-sway { from { transform: rotate(-7deg); } to { transform: rotate(7deg); } }
@keyframes hw-flap {
  0%, 100% { scale: 1 1; }
  50%      { scale: .74 1.1; }
}
@keyframes hw-eek {
  0%, 100% { rotate: 0deg; }
  25%      { rotate: -14deg; }
  75%      { rotate: 14deg; }
}

/* ── Skeleton ──────────────────────────────────────────────────────────── */
.m67-hw__skel {
  position: absolute;
  pointer-events: auto;
  cursor: pointer;
  transform-origin: top center;
  animation: hw-swing 5s ease-in-out infinite alternate;
  -webkit-tap-highlight-color: transparent;
}
.m67-hw__skel:focus-visible { outline: 2px solid var(--hw-bone); outline-offset: 4px; border-radius: 8px; }
.m67-hw__skel-svg { width: 100%; height: 100%; display: block; overflow: visible; }
.m67-hw__rope { fill: none; stroke: #8a7350; stroke-width: 2.5; }
.m67-hw__bones { fill: none; stroke: var(--hw-bone); stroke-width: 4; stroke-linecap: round; stroke-linejoin: round; }
.m67-hw__skull { fill: var(--hw-bone); }
.m67-hw__jaw { fill: var(--hw-bone); transform-origin: 50% 82px; transition: transform .18s ease; }
.m67-hw__sockets { fill: #15121b; }
.m67-hw__jaw-open .m67-hw__jaw { transform: translateY(5px) rotate(6deg); }
.m67-hw__rattle { animation: hw-rattle .5s ease-in-out; }
@keyframes hw-swing { from { transform: rotate(-5deg); } to { transform: rotate(5deg); } }
@keyframes hw-rattle {
  0%, 100% { transform: translateX(0) rotate(0deg); }
  15%      { transform: translateX(-4px) rotate(-4deg); }
  35%      { transform: translateX(4px) rotate(4deg); }
  55%      { transform: translateX(-3px) rotate(-3deg); }
  78%      { transform: translateX(3px) rotate(2deg); }
}

/* ── Placement: three breakpoints ──────────────────────────────────────── */

/* ≥1100px — the full set. Sizes and the y's live here; the x's of the candles
   and the skeleton come from the live rects in placeDesktopPieces(), because
   the strip between the sidebar and the board (427-567 at 1280x800) narrows
   with the viewport and a fixed `left:438px` sat inside the sidebar at 1100.
   The right strip (1139-1280 at 1280) holds the lantern and a web.
   Round 2: NO candle group gets a `left`/`top`/`right` here any more — there
   are four groups now and every one of them is positioned from a live rect
   (the strip from the sidebar/board gap, the mantel from the header seam and
   the moon, the shelf from the lantern's own rect, the band from the phone
   band). Only the two groups whose SIZE is a constant declare one. */
@media (min-width: 1100px) {
  /* Webs and skeleton: `top` is the header seam, set by placeDesktopPieces(). */
  .m67-hw__moon   { left: 300px; top: 20px; width: 56px; height: 56px; }
  .m67-hw__web--a { left: 0; width: 120px; height: 120px; }
  .m67-hw__web--b { right: 0; width: 120px; height: 120px; }
  /* The viewport's bottom-right CORNER, 30% bigger than round 1, lifted by
     exactly the rain pill's published height — the same variable the
     tournament chip reads (`rain_hud.js` publishes `height + 10px` when the
     pill is up and `0px` when it is not). No JS, no veto, no race: when it
     rains the pumpkin rises, when it stops it settles back.
     `placeLantern()` overrides the WIDTH when the right strip is too narrow to
     hold 125px clear of the board, and falls through to its other perches when
     even 83 will not fit. */
  .m67-hw__lantern {
    right: 8px;
    bottom: calc(12px + var(--m67-rain-hud-h, 0px));
    width: 125px;
    height: 125px;
  }
  .m67-hw__skel   { width: 72px; height: 152px; }
  .m67-hw__candle[data-hw-group="strip"] { width: 28px; height: 68px; }
  .m67-hw__candle[data-hw-group="shelf"] { width: 24px; height: 58px; }
}

/* 769–1099px — no skeleton, no moon (JS hides them), and NO fixed x's: the
   strips beside the board are too narrow and the viewport's bottom-left is the
   sidebar (the first build pinned the candles there, onto leaderboard rows).
   placeDesktopPieces() puts the candles in the board wrapper's bottom-left
   corner and placeLantern() puts the lantern in its bottom-right, both at
   phone size. Only sizes live here. */
@media (min-width: 769px) and (max-width: 1099px) {
  .m67-hw__web--a { left: 0; width: 96px; height: 96px; }
  .m67-hw__web--b { right: 0; width: 96px; height: 96px; }
  .m67-hw__lantern {
    right: 12px;
    bottom: calc(96px + var(--m67-rain-hud-h, 0px));
    width: 83px;
    height: 83px;
  }
  .m67-hw__candle[data-hw-group="strip"] { width: 24px; height: 58px; }
  .m67-hw__lantern--band { width: 83px; height: 83px; }
}

/* <=768px — the phone set, and it ALL lives in the band between the header
   seam (y≈94) and the board (y≈201). The bottom of a phone is HUD land: the
   rain pill and, below 700px, a full-width tournament chip stacked above it.
   Nothing of ours goes down there. */
@media (max-width: 768px) {
  .m67-hw__web--a { left: 0; top: 94px; width: 48px; height: 48px; }
  .m67-hw__web--b { right: 0; top: 94px; width: 48px; height: 48px; }
  /* Sizes only. `placeBandPieces()` owns every x and y down here, because the
     band's HEIGHT is 49-107px across real phones and both the lantern and the
     candles are scaled to fit it. The inset from the right edge (88 and 120,
     set in JS) is deliberate: the roosts are spread across the WHOLE viewport,
     so the corner is a bat's, and a candle stacked under a bat reads as one
     smudged object at this size. */
  .m67-hw__lantern { width: 83px; height: 83px; }
  .m67-hw__fog { height: 18vh; }
}

/* ── Reduced motion ────────────────────────────────────────────────────── */
/* Two copies on purpose: the class is what JS sets (and what the module's own
   static mode means), the media query catches the case where the preference
   flips after load and before JS notices. */
.m67-hw--static *,
.m67-hw--static *::before,
.m67-hw--static *::after {
  animation: none !important;
  transition: none !important;
}
@media (prefers-reduced-motion: reduce) {
  .m67-hw *,
  .m67-hw *::before,
  .m67-hw *::after {
    animation: none !important;
    transition: none !important;
  }
}
