:root {
  --night: #12142b;
  --night-deep: #0c0e20;
  --hearth: #1a1d3d;
  --card: #1e2145;
  --magenta: #d1487a;
  --magenta-dark: #a83261;
  --plum: #7c4a9e;
  --mustard: #e0a940;
  --ember: #e0703c;
  --gold: #e8c468;
  --parchment: #f6ecd9;
  --parchment-dim: #c9c2de;
  --border: #33366a;
  --deep-purple: #3a2657;
  --deep-purple-light: #4d3370;
}

* { box-sizing: border-box; }

/* The header-compact fix on .site-header alone wasn't enough — scroll
   anchoring doesn't just watch the resizing element, it can pick ANY
   element as the anchor (often the first one below the fold) and
   compensate scrollY when THAT element's position shifts, which still
   happens whenever the header above it resizes. Only disabling it
   page-wide actually stops the browser from fighting the header's own
   scroll listener. */
html {
  overflow-anchor: none;
}

body {
  margin: 0;
  position: relative;
  background:
    /* small scattered stars, mixed colors */
    radial-gradient(1.4px 1.4px at 10% 15%, rgba(255,255,255,0.55), transparent),
    radial-gradient(1.4px 1.4px at 80% 8%, rgba(232,196,104,0.6), transparent),
    radial-gradient(1px 1px at 55% 25%, rgba(255,255,255,0.4), transparent),
    radial-gradient(1.4px 1.4px at 30% 60%, rgba(209,72,122,0.5), transparent),
    radial-gradient(1px 1px at 90% 45%, rgba(255,255,255,0.45), transparent),
    radial-gradient(1.4px 1.4px at 65% 80%, rgba(124,74,158,0.5), transparent),
    radial-gradient(1px 1px at 15% 90%, rgba(255,255,255,0.4), transparent),
    radial-gradient(1.4px 1.4px at 40% 40%, rgba(232,196,104,0.4), transparent),
    radial-gradient(1px 1px at 72% 65%, rgba(255,255,255,0.4), transparent),
    radial-gradient(1.2px 1.2px at 5% 45%, rgba(209,72,122,0.4), transparent),
    radial-gradient(1.2px 1.2px at 95% 75%, rgba(124,74,158,0.4), transparent),
    radial-gradient(1px 1px at 25% 8%, rgba(255,255,255,0.4), transparent),
    /* soft painterly aurora blobs, like watercolor sky washes */
    radial-gradient(60% 45% at 12% 8%, rgba(46, 107, 110, 0.35), transparent 70%),
    radial-gradient(55% 40% at 90% 12%, rgba(124, 74, 158, 0.28), transparent 70%),
    radial-gradient(50% 45% at 78% 70%, rgba(168, 50, 97, 0.22), transparent 70%),
    radial-gradient(60% 50% at 15% 85%, rgba(46, 90, 110, 0.3), transparent 70%),
    linear-gradient(180deg, var(--night-deep), var(--night) 35%, var(--night) 100%);
  background-attachment: fixed;
  color: var(--parchment);
  font-family: "EB Garamond", "Iowan Old Style", serif;
  line-height: 1.6;
}

/* Fixed full-page layer for gently twinkling stars + drifting gold dust,
   generated by app.js. Sits above the body background but below the
   actual page content (main/footer get their own stacking context below). */
.magic-sky {
  position: fixed;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

/* Very faint ember-colored glow that fades in while the fireplace is lit
   (body.is-hearth-lit, toggled alongside the existing .is-playing state
   in initSoundToggle) — "the room getting slightly warmer" from the
   ChatGPT review. Kept intentionally as subtle as the idle hint sparks for
   now, matching Eloise's call — turn up the opacity/size below if it needs
   to read more strongly later. Sits low in the stacking order, same as
   .magic-sky, so it reads as ambient light rather than a UI element. */
.hearth-warmth {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.4s ease;
  background: radial-gradient(ellipse 80% 60% at 50% 100%, rgba(224, 112, 60, 0.09), transparent 65%);
}

body.is-hearth-lit .hearth-warmth {
  opacity: 1;
}

.magic-sky .sky-star {
  position: absolute;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 6px 1px currentColor;
  animation: twinkle 4s ease-in-out infinite;
}

.magic-sky .dust-mote {
  position: absolute;
  bottom: -10px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 8px 2px rgba(232, 196, 104, 0.6);
  opacity: 0;
  animation: dustDrift linear infinite;
}

@keyframes dustDrift {
  0% { transform: translate(0, 0) scale(0.7); opacity: 0; }
  12% { opacity: 0.85; }
  55% { opacity: 0.5; }
  100% { transform: translate(var(--drift-x, 30px), -105vh) scale(1); opacity: 0; }
}

/* Sky bats — Halloween-only (Aug 1–Nov 1 2026, see SEASON_WINDOWS in
   app.js), sitewide ambient decor generated into the same #magicSky layer
   as the twinkling stars/dust above so it drifts behind every page's
   content, not just one. Same rising-drift idiom as .dust-mote (start
   below the viewport, rise up and fade in/out along the way, loop), just
   bat-shaped and shrunk down to sit quietly in the background rather than
   read as a focal decor piece — per Eloise, "shrink it down to fit the
   style of the background, but enough so you can still get the Halloween
   effect." No click, purely atmospheric. The wing-flap wobble lives on
   the inner <img> so it composes independently of the outer span's drift
   (same layering trick as every other multi-animation piece on the site).
   Auto-generated fresh by initSkyBats() in app.js on every page load —
   nothing to swap in the HTML come Nov 1, it just stops generating them
   once currentSeasonName() moves past Halloween. */
.magic-sky .sky-bat {
  position: absolute;
  bottom: -60px;
  pointer-events: none;
  opacity: 0;
  animation-name: batDrift;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  filter: drop-shadow(0 2px 5px rgba(6, 4, 20, 0.35));
}

.magic-sky .sky-bat img {
  display: block;
  width: 100%;
  height: auto;
  animation-name: batFlap;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes batDrift {
  0% { transform: translate(0, 0) rotate(-3deg); opacity: 0; }
  10% { opacity: 0.55; }
  50% { transform: translate(var(--drift-x, 40px), -55vh) rotate(4deg); }
  90% { opacity: 0.55; }
  100% { transform: translate(calc(var(--drift-x, 40px) * 1.6), -115vh) rotate(-3deg); opacity: 0; }
}

@keyframes batFlap {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(0.82); }
}

/* Sky ghosts — same Halloween-only sitewide ambient treatment as the bats
   above (generated into #magicSky by initSkyGhosts() in app.js, same
   rising-drift idiom, same auto-stop once Halloween's window closes), just
   a second creature in the mix so the background doesn't read as "one bat
   repeated" — per Eloise, "I believe in repetition here and there, nothing
   boring." No wing flap (ghosts don't have wings) — a gentle side-to-side
   sway on the inner <img> instead, same independent-layering trick as the
   bat's flap (drift on the outer span, sway on the inner image, so the two
   compose without fighting each other). This same ghost art is also going
   to be used a second way elsewhere on the site (Eloise's plan, not yet
   built) — this block is only the ambient-background use. */
.magic-sky .sky-ghost {
  position: absolute;
  bottom: -60px;
  pointer-events: none;
  opacity: 0;
  animation-name: ghostDrift;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  filter: drop-shadow(0 2px 6px rgba(6, 4, 20, 0.3));
}

.magic-sky .sky-ghost img {
  display: block;
  width: 100%;
  height: auto;
  animation-name: ghostSway;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes ghostDrift {
  0% { transform: translate(0, 0); opacity: 0; }
  10% { opacity: 0.5; }
  50% { transform: translate(var(--drift-x, 40px), -55vh); }
  90% { opacity: 0.5; }
  100% { transform: translate(calc(var(--drift-x, 40px) * 1.6), -115vh); opacity: 0; }
}

@keyframes ghostSway {
  0%, 100% { transform: rotate(-4deg); }
  50% { transform: rotate(4deg); }
}

/* Sky spiders — third Halloween-only sitewide ambient creature, same
   rising-drift generation as the bats/ghosts above (generated into
   #magicSky by initSkySpiders() in app.js, same auto-stop once
   Halloween's window closes). Sized smaller than the bats per Eloise
   ("a little smaller than the bat"). No wings, no billowing fabric —
   a quick leg-jitter scuttle on the inner <img> instead of a flap/sway,
   so all three creatures read as distinct rather than one shape
   reused three ways. Slightly offset slot phase (0.75 slots) from both
   the bats and ghosts so all three don't start on top of each other. */
.magic-sky .sky-spider {
  position: absolute;
  bottom: -60px;
  pointer-events: none;
  opacity: 0;
  animation-name: spiderDrift;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  filter: drop-shadow(0 2px 5px rgba(6, 4, 20, 0.35));
}

.magic-sky .sky-spider img {
  display: block;
  width: 100%;
  height: auto;
  animation-name: spiderScuttle;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes spiderDrift {
  0% { transform: translate(0, 0) rotate(-2deg); opacity: 0; }
  10% { opacity: 0.55; }
  50% { transform: translate(var(--drift-x, 40px), -55vh) rotate(2deg); }
  90% { opacity: 0.55; }
  100% { transform: translate(calc(var(--drift-x, 40px) * 1.6), -115vh) rotate(-2deg); opacity: 0; }
}

@keyframes spiderScuttle {
  0%, 100% { transform: translate(0, 0) scale(1); }
  20% { transform: translate(-1px, 1px) scale(1.03, 0.97); }
  40% { transform: translate(1px, -1px) scale(0.98, 1.02); }
  60% { transform: translate(-1px, 0) scale(1.02, 0.98); }
  80% { transform: translate(1px, 1px) scale(0.99, 1.01); }
}

main, footer { position: relative; z-index: 2; }

h1, h2, h3, h4, .logo, .footer-logo {
  font-family: "Cormorant Garamond", serif;
  font-weight: 600;
  letter-spacing: 0.01em;
  margin: 0;
}

a { color: inherit; }

.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: 0.75rem;
  color: var(--gold);
  margin: 0 0 10px;
}

.divider-flourish {
  display: block;
  height: 22px;
  width: auto;
  max-width: 220px;
  margin: 18px auto;
  opacity: 0.85;
}

/* ---------- Header ----------
   Storybook title-margin rewrite (2026-07-29). No boxes, no photo — the
   background is the same gradient+glow recipe as the body, so the header
   reads as "the night sky continues upward" instead of a separate banner.
   Three-part layout: witch-figure bookend (left) / title+nav (center) /
   fireplace bookend (right), matching sizes so the weight balances. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  /* The header's own height changes a lot between its normal and
     .header-compact states (padding 26px -> 8px, figure images collapse
     to 0). Since it's sticky (still part of normal flow), that resize
     shifts the content below it while scrolled — which some browsers'
     scroll-anchoring then "corrects" by nudging scrollY, which re-crosses
     the threshold and toggles the class again. That feedback loop is the
     stutter/jitter reported when scrolling past the compact threshold.
     Disabling anchoring on the header removes the browser's part in that
     loop; the JS-side hysteresis below (initHeaderScroll) removes the
     rest. */
  overflow-anchor: none;
  background:
    radial-gradient(55% 140% at 12% 0%, rgba(124, 74, 158, 0.35), transparent 70%),
    radial-gradient(50% 150% at 92% 10%, rgba(224, 169, 64, 0.22), transparent 70%),
    radial-gradient(60% 160% at 55% 120%, rgba(168, 50, 97, 0.18), transparent 70%),
    linear-gradient(180deg, #120c22, #23172e 100%);
  border-top: 1px solid rgba(232, 196, 104, 0.35);
  border-bottom: 1px solid rgba(232, 196, 104, 0.35);
  overflow: hidden;
  transition: padding 0.35s ease;
}

/* The overflow:hidden above keeps the fairy-dust swirl (see .fairy-dust
   below — its orbit radius is bigger than the header's own padding, so
   without clipping it would bleed past the header edges) contained to the
   header band. But that same clipping was also cutting off the nav-group
   dropdown menus and the mobile nav panel, both of which render as
   position:absolute panels that extend below the header's box — only a
   thin sliver of each was visible, the rest got clipped away. Rather than
   removing the clip entirely (and losing the fairy-dust containment),
   JS toggles this class on for exactly as long as a dropdown/mobile panel
   is open (initNavGroups() / initMobileNav() in app.js), so the header
   only turns clipping off for the brief moment something needs to escape
   it. */
.site-header.menu-open {
  overflow: visible;
}

.header-inner {
  position: relative;
  max-width: 1080px;
  margin: 0 auto;
  padding: 26px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  transition: padding 0.35s ease;
}

/* Bookend illustrations — witch figure (left) / fireplace (right), sized
   to match so neither side outweighs the other. */
.header-figure {
  flex: 0 0 auto;
  display: block;
  text-decoration: none;
  transition: opacity 0.3s ease, margin 0.3s ease;
}

.header-figure-left {
  position: relative;
}

.header-figure-left picture,
.header-figure-left img {
  display: block;
  height: 118px;
  width: auto;
  filter: drop-shadow(0 4px 12px rgba(6, 4, 20, 0.6));
  transition: height 0.35s ease;
}

/* ---------- Fairy dust — tiny gold specks orbiting the witch figure,
   generated by initFairyDust() in app.js (same particle-generation
   pattern as the page-wide .magic-sky dust). Ambient/always-on rather
   than toggle-triggered, since it doesn't depend on the chime sound
   Eloise hasn't generated yet — those can link up later. ---------- */
.fairy-dust {
  position: absolute;
  inset: -18% -35%;
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  transition: opacity 0.4s ease;
}

/* Hidden until she's clicked — .is-swirling is toggled on the link itself
   (.header-figure-left) by initFairyDust() in app.js. */
.header-figure-left.is-swirling .fairy-dust {
  opacity: 1;
}

/* Each speck lives inside its own pre-rotated wrapper — that static
   rotation (set inline per-instance in initFairyDust) is what spaces the
   specks evenly around the full circle from frame one. The speck itself
   still runs its own fairySwirl animation, which composes on top of the
   wrapper's fixed angle. */
.dust-orbit {
  position: absolute;
  inset: 0;
}

.dust-speck {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 3px;
  height: 3px;
  margin: -1.5px 0 0 -1.5px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 6px 1px rgba(232, 196, 104, 0.75);
  opacity: 0;
  animation-name: fairySwirl;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes fairySwirl {
  0% { transform: rotate(0deg) translateX(var(--orbit-radius, 44px)) rotate(0deg) scale(0.6); opacity: 0; }
  15% { opacity: 1; }
  50% { transform: rotate(180deg) translateX(var(--orbit-radius, 44px)) rotate(-180deg) scale(1); }
  85% { opacity: 1; }
  100% { transform: rotate(360deg) translateX(var(--orbit-radius, 44px)) rotate(-360deg) scale(0.6); opacity: 0; }
}

/* ---------- Idle "try me" cues — a faint, always-on hint that the witch
   and the fireplace are interactive, for first-time visitors who'd
   otherwise have no reason to click either one. Deliberately much smaller
   and quieter than the click-triggered fairy-dust ring or the lit-fire
   glow, which stay the "reward" for actually clicking. ---------- */
.witch-hint-spark {
  position: absolute;
  top: 6%;
  right: 16%;
  width: 11px;
  height: 11px;
  pointer-events: none;
  z-index: 1;
  color: var(--gold);
  opacity: 0;
  animation: hintTwinkle 3.2s ease-in-out infinite;
}
.witch-hint-spark svg { width: 100%; height: 100%; fill: currentColor; }

/* Hidden once she's actually clicked — the real fairy-dust ring is doing
   the work by then, no need to compete with it. */
.header-figure-left.is-swirling .witch-hint-spark {
  opacity: 0 !important;
  animation: none;
}

@keyframes hintTwinkle {
  0%, 100% { opacity: 0; transform: scale(0.7); }
  50% { opacity: 0.85; transform: scale(1.05); }
}

/* Faint ember pulse behind the header fireplace icon while it's unlit —
   same idea as the witch's hint spark. Stops once lit; .sound-toggle
   .is-playing's own drop-shadow glow (below) takes over from there. */
.sound-toggle:not(.is-playing) .fireplace-art-header::after {
  content: "";
  position: absolute;
  inset: 10% 20%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(232, 196, 104, 0.35), transparent 70%);
  animation: emberPulse 3.6s ease-in-out infinite;
  pointer-events: none;
}

@keyframes emberPulse {
  0%, 100% { opacity: 0.15; transform: scale(0.9); }
  50% { opacity: 0.55; transform: scale(1.08); }
}

.header-center {
  /* Needed so .nav-toggle's position:absolute (top/right) resolves against
     THIS box, not the next positioned ancestor up (.header-inner). Without
     it, the hamburger's "right: 0" was measured from .header-inner's full
     padding box — which on mobile (once .header-figure is hidden and this
     becomes the only flex child) stretches edge-to-edge with the viewport.
     That pinned the tap target flush against the physical screen edge,
     right where iOS's edge-swipe-back gesture and safe-area insets tend to
     eat touches — the most likely reason taps on it weren't registering. */
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.header-title {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-weight: 600;
  font-size: 2.15rem;
  color: var(--parchment);
  text-decoration: none;
  text-shadow: 0 2px 10px rgba(6, 4, 20, 0.6);
  transition: font-size 0.35s ease;
}

.header-flourish {
  display: block;
  height: 16px;
  width: auto;
  margin: 8px 0 14px;
  opacity: 0.85;
  transition: opacity 0.3s ease, max-height 0.3s ease, margin 0.3s ease;
  overflow: hidden;
}

.nav-links {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  row-gap: 8px;
  column-gap: 10px;
  font-size: 0.88rem;
}

.nav-sep {
  color: var(--gold);
  font-size: 0.6rem;
  opacity: 0.75;
}

.nav-links a {
  position: relative;
  text-decoration: none;
  color: var(--parchment);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 500;
  padding: 2px 1px 6px;
  transition: color 0.15s ease;
}

.nav-links a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.2s ease;
}

.nav-links a:hover { color: var(--gold); }
.nav-links a:hover::after { transform: scaleX(1); }
.nav-links a.active-tab { color: var(--gold); }
.nav-links a.active-tab::after { transform: scaleX(1); }

/* Hamburger toggle — hidden on desktop, shown below the mobile breakpoint */
.nav-toggle {
  display: none;
  position: absolute;
  top: -4px;
  right: 0;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 34px;
  height: 34px;
  border: none;
  background: none;
  cursor: pointer;
  padding: 0;
}
.nav-toggle span {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--gold);
  margin: 0 auto;
}

/* ---------- Grouped nav dropdowns (Read / Play / Shop) ---------- */
/* Desktop: click-to-open dropdown, same look/feel as a plain nav link
   until opened. Mobile override further down always shows the group
   contents inline instead — see the comment on the nav markup in
   index.html for why. */

.nav-group {
  position: relative;
  display: inline-flex;
}

.nav-group-toggle {
  font-family: inherit;
  background: none;
  border: none;
  cursor: pointer;
  position: relative;
  text-decoration: none;
  color: var(--parchment);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 500;
  font-size: 0.88rem;
  padding: 2px 1px 6px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  transition: color 0.15s ease;
}

.nav-group-toggle::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.2s ease;
}

.nav-group-toggle:hover,
.nav-group.is-open .nav-group-toggle,
.nav-group.nav-group-active .nav-group-toggle {
  color: var(--gold);
}
.nav-group.is-open .nav-group-toggle::after,
.nav-group.nav-group-active .nav-group-toggle::after {
  transform: scaleX(1);
}

.nav-caret {
  font-size: 0.65em;
  transition: transform 0.2s ease;
}
.nav-group.is-open .nav-caret { transform: rotate(180deg); }

.nav-group-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-top: 10px;
  flex-direction: column;
  min-width: 180px;
  background: var(--night-deep);
  border: 1px solid rgba(232, 196, 104, 0.3);
  border-radius: 12px;
  padding: 8px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);
  z-index: 20;
}

.nav-group.is-open .nav-group-menu { display: flex; }

.nav-group-menu a {
  text-decoration: none;
  color: var(--parchment);
  text-transform: none;
  letter-spacing: 0.02em;
  font-weight: 500;
  padding: 9px 12px;
  border-radius: 8px;
  transition: background 0.15s ease, color 0.15s ease;
}

.nav-group-menu a:hover,
.nav-group-menu a.active-tab {
  color: var(--gold);
  background: rgba(232, 196, 104, 0.1);
}

/* ---------- Compact header on scroll ----------
   Once app.js adds .header-compact (past a scroll threshold), the header
   shrinks down toward the old slim-bar proportions instead of permanently
   occupying a big chunk of the viewport while sticky. */
.site-header.header-compact .header-inner {
  padding: 8px 24px;
}

.site-header.header-compact .header-figure-left picture,
.site-header.header-compact .header-figure-left img {
  height: 0;
  opacity: 0;
}

.site-header.header-compact .fairy-dust,
.site-header.header-compact .witch-hint-spark {
  opacity: 0;
}

.site-header.header-compact .fireplace-art-header,
.site-header.header-compact .fireplace-art-header .fireplace-pic-base img {
  height: 0;
  opacity: 0;
}

.site-header.header-compact .header-title {
  font-size: 1.3rem;
}

.site-header.header-compact .header-flourish {
  opacity: 0;
  max-height: 0;
  margin: 0;
}

/* ---------- Ambient sound toggle (header) ---------- */
.sound-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  transition: filter 0.3s ease;
}

.sound-toggle.is-playing {
  filter: drop-shadow(0 0 10px rgba(232, 196, 104, 0.55));
}

/* ---------- Illustrated fireplace (Eloise's own art) — shared by the
   header toggle and the hero's "Light the hearth" button. Two images on
   identical canvases stacked in one spot: a static unlit base (logs, no
   flame) and a flame-only layer that fades in and gently flickers once
   [data-sound-toggle] gets .is-playing. ---------- */
.fireplace-art {
  position: relative;
  display: block;
  line-height: 0;
}

.fireplace-art-sm { width: 56px; }
.fireplace-art-lg { width: 150px; }

/* Header bookend — sized by height (not width) to match the witch
   figure's .header-figure-left, which is a tall portrait crop. */
.fireplace-art-header { height: 118px; width: auto; transition: height 0.35s ease; }
.fireplace-art-header .fireplace-pic-base img { height: 118px; width: auto; }
.fireplace-art-header .fireplace-pic-flame { width: 100%; height: 100%; }
.fireplace-art-header .fireplace-pic-flame img { width: 100%; height: 100%; }

.fireplace-pic { display: block; }
.fireplace-pic img { display: block; width: 100%; height: auto; }

.fireplace-pic-flame {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform-origin: 50% 68%;
  transition: opacity 0.5s ease;
}

.is-playing .fireplace-pic-flame {
  opacity: 1;
  animation: fireFlicker 1.8s ease-in-out infinite;
}

@keyframes fireFlicker {
  0%, 100% { transform: scale(1) rotate(0deg); opacity: 1; }
  25% { transform: scale(1.04, 0.97) rotate(-1deg); opacity: 0.93; }
  50% { transform: scale(0.97, 1.03) rotate(0.8deg); opacity: 1; }
  75% { transform: scale(1.02, 0.98) rotate(-0.6deg); opacity: 0.95; }
}

/* Soft warm glow behind the large hero fireplace — the "atmosphere" —
   sits behind the art (z-index -1) and only appears once lit. */
.fireplace-glow {
  position: absolute;
  inset: -30%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 60%, rgba(224, 169, 64, 0), transparent 70%);
  filter: blur(22px);
  opacity: 0;
  transition: opacity 0.7s ease;
  pointer-events: none;
}

.is-playing .fireplace-glow {
  opacity: 1;
  background: radial-gradient(circle at 50% 60%, rgba(224, 169, 64, 0.55) 0%, rgba(209, 72, 122, 0.22) 45%, transparent 72%);
  animation: glowPulse 2.6s ease-in-out infinite;
}

@keyframes glowPulse {
  0%, 100% { opacity: 0.8; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.07); }
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  max-width: 1080px;
  margin: 0 auto;
  padding: 90px 24px 70px;
  text-align: center;
  /* Was overflow: hidden — everything that bleeds inside here (hero-stars,
     hero-art-glow) stays within its own safe bounds regardless, and the
     hallway frame in the margin needs to be allowed to render outside the
     hero's own box, wider than the generic 760px sections. */
  overflow: visible;
}

.hero-stars { position: absolute; inset: 0; pointer-events: none; }
.hero-stars .star {
  position: absolute;
  width: 18px;
  height: 18px;
  animation: twinkle 3.5s ease-in-out infinite;
}
.hero-stars .star path { fill: currentColor; }

.star-1 { top: 10%; left: 6%; width: 22px; height: 22px; color: var(--mustard); animation-delay: 0s; }
.star-2 { top: 20%; right: 12%; width: 14px; height: 14px; color: var(--magenta); animation-delay: 0.6s; }
.star-3 { top: 58%; left: 3%; width: 12px; height: 12px; color: var(--plum); animation-delay: 1.2s; }
.star-4 { bottom: 14%; right: 8%; width: 18px; height: 18px; color: var(--gold); animation-delay: 1.8s; }
.star-5 { top: 42%; right: 26%; width: 10px; height: 10px; color: var(--mustard); animation-delay: 2.4s; }
/* star-6/7/8: scattered lower-right, where the shorter hero-art column
   leaves the most leftover vertical space beside the taller text/card
   column — keeps that area feeling like part of the scene rather than a
   dead gap. */
.star-6 { bottom: 10%; right: 20%; width: 14px; height: 14px; color: var(--plum); animation-delay: 0.9s; }
.star-7 { bottom: 4%; right: 34%; width: 10px; height: 10px; color: var(--gold); animation-delay: 1.6s; }
.star-8 { bottom: 20%; right: 6%; width: 12px; height: 12px; color: var(--magenta); animation-delay: 2.1s; }

@keyframes twinkle {
  0%, 100% { opacity: 0.35; transform: scale(0.85); }
  50% { opacity: 1; transform: scale(1.1); }
}

.hero-inner {
  position: relative;
  display: flex;
  /* Single centered column: intro copy, then the hero portrait, then the
     three tiles — all sharing one max-width so the page reads as one
     centered composition instead of a two-column split. */
  flex-direction: column;
  align-items: center;
  gap: 36px;
  max-width: 620px;
  margin: 0 auto;
  text-align: center;
}

.hero-text { width: 100%; max-width: 560px; }

.hero-art {
  position: relative;
  width: 100%;
  max-width: 320px;
}

.hero-art-glow {
  position: absolute;
  inset: -14%;
  background: radial-gradient(circle at 50% 45%, rgba(224, 169, 64, 0.4), rgba(209, 72, 122, 0.22) 45%, transparent 72%);
  filter: blur(26px);
  z-index: 0;
}

.hero-art-frame {
  position: relative;
  z-index: 1;
  border-radius: 20px;
  overflow: hidden;
  border: 1px solid rgba(232, 196, 104, 0.45);
  box-shadow:
    0 24px 48px rgba(6, 4, 20, 0.55),
    0 0 0 6px rgba(18, 20, 43, 0.6),
    0 0 40px rgba(224, 169, 64, 0.2);
}

.hero-art picture,
.hero-art img {
  display: block;
  width: 100%;
  height: auto;
}

.hero h1 {
  font-size: 3.2rem;
  font-style: italic;
}

.hero-copy {
  font-size: 1.15rem;
  color: var(--parchment-dim);
  margin: 0 0 32px;
}

.hero-ctas {
  display: flex;
  flex-direction: column;
  width: 100%;
  gap: 16px;
}

/* Follow Along links — same flex-column stacking as .hero-ctas above,
   just centered (align-items) instead of full-width, since these buttons
   sit inside the plain centered .social section rather than a hero card
   stack. */
.social-links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-top: 8px;
}

.social-link-icon {
  display: inline-block;
  width: 18px;
  height: 18px;
  object-fit: contain;
  vertical-align: -3px;
  margin-right: 4px;
}

/* ---------- Sticker pack (Follow Along) ---------- */
.sticker-pack {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--hearth), var(--card));
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 30px;
  margin: 40px auto 0;
  max-width: 520px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 18px;
}

.sticker-pack-preview {
  display: flex;
  gap: 10px;
  justify-content: center;
}

.sticker-pack-preview img {
  width: 64px;
  height: 64px;
  object-fit: contain;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.35));
}

.sticker-pack-text h3 {
  font-size: 1.3rem;
  font-style: italic;
  margin: 0 0 8px;
}

.sticker-pack-text p {
  color: var(--parchment-dim);
  margin: 0 0 16px;
}

.sticker-pack-note {
  font-size: 0.82rem;
  margin: 14px 0 0 !important;
  opacity: 0.8;
}

/* ---------- Illustrated hero tiles (Hear a tale / Discover the books) —
   same size and card styling as the fireplace toggle below, so all three
   read as one family. Plain links, no toggle state — clicking one just
   takes you to that tab. ---------- */
.hero-tile {
  display: flex;
  align-items: center;
  text-align: left;
  gap: 20px;
  padding: 14px 26px 14px 14px;
  border-radius: 22px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: var(--parchment-dim);
  text-decoration: none;
  transition: all 0.2s ease;
}

.hero-tile:hover {
  border-color: var(--gold);
  background: rgba(232, 196, 104, 0.06);
  transform: translateY(-1px);
}

.hero-tile-icon {
  flex: 0 0 auto;
  width: 150px;
}

.hero-tile-icon img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(6, 4, 20, 0.5));
}

/* Tiles waiting on real icon art (Quiet Hollow, Follow Along) — an empty
   .hero-tile-icon gets a same-proportioned dashed placeholder box instead
   of just collapsing to nothing, so the whole tile stack still lines up
   evenly while the layout is being checked. Swap in a real <picture>/<img>
   inside the span once art exists and this rule stops applying on its own
   (:empty no longer matches). */
.hero-tile-icon:empty {
  aspect-ratio: 700 / 900;
  border: 1px dashed var(--border);
  border-radius: 12px;
}

.hero-tile-text { display: flex; flex-direction: column; gap: 3px; }

.hero-tile-label {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--parchment);
}

.hero-tile-hint {
  font-size: 0.85rem;
  color: var(--parchment-dim);
}

/* ---------- Hearth-light button (hero sound invitation) — the fireplace
   is the visual centerpiece here, sized large with a warm glow so it
   reads as an atmospheric moment rather than a small settings toggle. ---------- */
.hearth-light-btn {
  display: flex;
  align-items: center;
  width: 100%;
  box-sizing: border-box;
  gap: 20px;
  padding: 14px 26px 14px 14px;
  border-radius: 22px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: var(--parchment-dim);
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  transition: all 0.2s ease;
}

.hearth-light-btn:hover {
  border-color: var(--gold);
  background: rgba(232, 196, 104, 0.06);
  transform: translateY(-1px);
}

.hearth-light-btn.is-playing {
  border-color: var(--gold);
  background: rgba(232, 196, 104, 0.1);
  box-shadow: 0 0 26px rgba(232, 196, 104, 0.2);
}

.hearth-light-icon {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hearth-light-text { display: flex; flex-direction: column; gap: 3px; }

.hearth-light-label {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--parchment);
}

.hearth-light-hint {
  font-size: 0.85rem;
  color: var(--parchment-dim);
}

/* ---------- Radio tile (hero sound invitation, second one) — same card
   family as the hearth-light button above, own icon size to match the
   other tiles' 150px column. The flicker on click reuses the fireplace's
   exact fireFlicker keyframe (defined earlier in this file), not a copy —
   same recipe, same reasoning as the gramophone and cottage plant. ---------- */
.radio-tile-btn {
  display: flex;
  align-items: center;
  width: 100%;
  box-sizing: border-box;
  gap: 20px;
  padding: 14px 26px 14px 14px;
  border-radius: 22px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: var(--parchment-dim);
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  transition: all 0.2s ease;
}

.radio-tile-btn:hover {
  border-color: var(--gold);
  background: rgba(232, 196, 104, 0.06);
  transform: translateY(-1px);
}

.radio-tile-btn.is-playing {
  border-color: var(--gold);
  background: rgba(232, 196, 104, 0.1);
  box-shadow: 0 0 26px rgba(232, 196, 104, 0.2);
}

.radio-tile-icon {
  flex: 0 0 auto;
  width: 150px;
  line-height: 0;
}

.radio-tile-icon-img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(6, 4, 20, 0.5));
  transform-origin: 50% 68%;
}

.radio-tile-btn.is-playing .radio-tile-icon-img {
  animation: fireFlicker 1.8s ease-in-out infinite;
}

.radio-tile-text { display: flex; flex-direction: column; gap: 3px; }

.radio-tile-label {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--parchment);
}

.radio-tile-hint {
  font-size: 0.85rem;
  color: var(--parchment-dim);
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-block;
  text-decoration: none;
  padding: 13px 26px;
  border-radius: 999px;
  font-size: 0.98rem;
  letter-spacing: 0.02em;
  transition: all 0.15s ease;
  border: 1px solid transparent;
  cursor: pointer;
  font-family: inherit;
}

.btn-primary {
  background: linear-gradient(135deg, var(--magenta) 0%, var(--magenta-dark) 100%);
  color: var(--parchment);
  font-weight: 600;
  box-shadow: 0 8px 20px rgba(168, 50, 97, 0.35);
}
.btn-primary:hover { box-shadow: 0 10px 24px rgba(168, 50, 97, 0.5); transform: translateY(-1px); }

.btn-ghost {
  border-color: var(--border);
  color: var(--parchment);
  background: rgba(255, 255, 255, 0.02);
}
.btn-ghost:hover { border-color: var(--gold); color: var(--gold); }

.btn-gold {
  background: linear-gradient(135deg, var(--gold) 0%, var(--mustard) 100%);
  color: #2a1830;
  font-weight: 600;
  font-style: italic;
  letter-spacing: 0.03em;
  box-shadow: 0 8px 22px rgba(224, 169, 64, 0.35), 0 0 0 1px rgba(246, 236, 217, 0.15) inset;
}
.btn-gold:hover {
  box-shadow: 0 10px 26px rgba(224, 169, 64, 0.5), 0 0 0 1px rgba(246, 236, 217, 0.2) inset;
  transform: translateY(-1px);
}

.btn-small { padding: 9px 20px; font-size: 0.88rem; }
.btn:disabled { opacity: 0.6; cursor: default; }

/* ---------- Share row (public/share-cards.js) — one shared component used
   by the tale generator, the herbal bath generator, and the self-love tip
   tiles. Pinterest/X/Facebook/WhatsApp buttons plus a "save image" button,
   with a small status line for the brief moment the card image is being
   drawn/uploaded before the first share opens. ---------- */
.share-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 18px;
}

.share-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 16px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: var(--parchment);
  font-family: inherit;
  font-size: 0.88rem;
  cursor: pointer;
  transition: all 0.15s ease;
}

.share-btn-icon {
  display: block;
  width: 18px;
  height: 18px;
  object-fit: contain;
}

.share-btn:hover {
  border-color: var(--gold);
  color: var(--gold);
  transform: translateY(-1px);
}

.share-status {
  font-size: 0.8rem;
  color: var(--parchment-dim);
  font-style: italic;
}

/* ---------- Sections (generic) ---------- */
section {
  position: relative; /* containing block for hallway pieces in the margin */
  max-width: 760px;
  margin: 0 auto;
  padding: 90px 24px;
  text-align: center;
}

/* Mobile showcase pieces — every margin piece on the site (hallway
   cluster, cushion cat, gramophone, treasure chest, scatter pumpkins...)
   is built the same way: a position:absolute box that sits in the empty
   gutter beside the ~760-1080px content column. Below ~1300px that gutter
   space runs out, so up to now everything tagged .mobile-showcase used to
   just vanish (display:none) at that breakpoint along with everything
   else. This class instead pulls a curated handful of them (the ones
   Eloise picked as the most charming/important — see the .halloween-decor-
   style comments on each one in the HTML) back into the normal document
   flow: position:static so they sit right where they already are in the
   markup (which, for most of these, is already right next to the content
   they're thematically paired with), full click/animation behaviour
   unchanged since it's the same element with the same classes and the
   same JS listeners, just repositioned. Sized down a bit from their
   desktop width so they read as a nice divider rather than dominating a
   phone screen. Every OTHER margin piece not tagged with this class keeps
   its old display:none behaviour below 1300px, same as before — this is
   a deliberately small "greatest hits" selection, not everything. */
@media (max-width: 1300px) {
  /* !important on these four: every piece tagged .mobile-showcase (hallway
     cluster, ritual-pumpkin-cat, almanac-bush, about-cat, books-chest,
     the four pumpkin-jacks...) has its OWN unconditional
     position/transform rule declared further down this file, with the same
     specificity as this class selector. Since a later same-specificity rule
     wins regardless of whether it's the "mobile" one, every single one of
     those was silently overriding this block and staying absolutely
     positioned (and pushed far off-screen by its desktop transform) on
     phones — meaning the whole mobile "greatest hits" feature never
     actually showed up on a real phone, despite looking right in the code.
     !important is the reliable fix without having to move or rewrite every
     individual piece's rule. */
  .mobile-showcase {
    position: static !important;
    display: block !important;
    transform: none !important;
    width: 150px;
    margin: 32px auto;
  }

  /* Treasure chest keeps the claim link inside it, so it gets a little
     more breathing room than the purely-decorative pieces. */
  .books-chest.mobile-showcase {
    width: 190px;
  }

  /* Pumpkins read as too small/fiddly at the generic 150px on a phone —
     bumped up a bit so they're easier to see and tap. */
  .ritual-pumpkin-cat.mobile-showcase,
  .home-pumpkin-jack.mobile-showcase,
  .almanac-pumpkin-jack.mobile-showcase,
  .about-pumpkin-jack.mobile-showcase,
  .books-pumpkin-jack.mobile-showcase {
    width: 210px;
  }
}

section h2 {
  font-size: 2.3rem;
  font-style: italic;
  margin-bottom: 18px;
}

.section-intro {
  color: var(--parchment-dim);
  max-width: 520px;
  margin: 0 auto 48px;
  font-size: 1.05rem;
}

/* ---------- The Hallway ---------- */
/* Eloise's "walk past a hallway of things" idea: small painted pieces that
   live in the empty margin beside the ~760px content column, anchored near
   the top of whichever section they belong to (roughly level with the
   witch-logo/header, per her steer), and scroll away normally with the
   page rather than following the viewport. Each section that hosts one
   needs `position: relative` so the absolute offset is scoped to that
   section instead of the whole page.

   Two kinds of piece so far:
   - The kitten (Books page) — ambient only, no click interaction: yarn
     rocks continuously, one eye blinks (this pose only paints one).
   - The framed gallery wall (.hallway-cluster) — originally 2 sprite
     sheets, later split into 4 (Eloise sent a fresh set of 4, each
     isolating a smaller group of frames — raven+fireplace, book alone,
     witch alone, both cats — so more of the wall can move on its own
     curve instead of averaging out across 3 frames per layer). All 4 are
     cropped to the SAME bounding box so they're pixel-aligned, exactly
     the fireplace base+flame technique. Composited together the 6 frames
     still tile into one seamless wall with zero overlap. One click
     target for the whole wall; all 4 layers wobble at once, each with
     its own rotation curve and pivot point, so it doesn't read as one
     rigid photo. */
.hallway-cluster {
  position: absolute;
  top: 108px;
  left: 50%;
  transform: translateX(-628px);
  z-index: 2;
  width: 282px;
  cursor: pointer;
}

.hallway-cluster:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.hallway-layer {
  display: block;
  width: 100%;
  height: auto;
}

/* Different pivot points, not just different degrees — rotating layers
   from the same center made different rotation amounts still read as
   "the same wobble," since the sweep looked identical either way.
   Anchoring each layer near where its own content actually sits (raven+
   fireplace upper-left, the small book frame lower-left, the witch frame
   mid-left, both cats upper-right) means the 4 swings arc from different
   points entirely, which is what makes them look like separate motions
   rather than one photo moving as a block. */
.hallway-layer-a {
  transform-origin: 22% 15%;
}

.hallway-layer-b {
  position: absolute;
  inset: 0;
  z-index: 1;
  transform-origin: 12% 90%;
}

.hallway-layer-c {
  position: absolute;
  inset: 0;
  z-index: 2;
  transform-origin: 22% 60%;
}

.hallway-layer-d {
  position: absolute;
  inset: 0;
  z-index: 3;
  transform-origin: 80% 10%;
}

/* Runs for the full length of the knock sound (20.832s) instead of a
   quick few-second swing — the wobble now keeps gently re-nudging in
   sync with roughly where the louder knock clusters land in the audio
   (checked via ffmpeg silencedetect against the uncropped file), rather
   than one push that settles long before the sound finishes. Each
   keyframe here is a single pulse; letting the browser interpolate the
   curve between alternating +/- values does the "swing through center"
   motion for free, no extra return-to-zero keyframes needed. Amplitude
   trends down overall but bumps back up around 55-60%, matching the
   busiest run of knocks in the middle of the file. */
.hallway-cluster.is-wobbling .hallway-layer-a {
  animation: hallwayWobbleA 20.832s cubic-bezier(0.37, 0.15, 0.24, 0.94);
}

.hallway-cluster.is-wobbling .hallway-layer-b {
  animation: hallwayWobbleB 20.832s cubic-bezier(0.37, 0.15, 0.24, 0.94);
}

.hallway-cluster.is-wobbling .hallway-layer-c {
  animation: hallwayWobbleC 20.832s cubic-bezier(0.37, 0.15, 0.24, 0.94);
}

.hallway-cluster.is-wobbling .hallway-layer-d {
  animation: hallwayWobbleD 20.832s cubic-bezier(0.37, 0.15, 0.24, 0.94);
}

@keyframes hallwayWobbleA {
  0% { transform: rotate(0deg); }
  2.9% { transform: rotate(-14deg); }
  17.8% { transform: rotate(10deg); }
  32.2% { transform: rotate(-8deg); }
  44.6% { transform: rotate(6deg); }
  58.6% { transform: rotate(-7deg); }
  73.4% { transform: rotate(5deg); }
  84% { transform: rotate(-3deg); }
  93.6% { transform: rotate(1.5deg); }
  100% { transform: rotate(0deg); }
}

/* Mirrored signs and offset pulse timing across all 4 keyframe sets so no
   two layers are ever swinging the same direction at the same moment. */
@keyframes hallwayWobbleB {
  0% { transform: rotate(0deg); }
  4% { transform: rotate(11deg); }
  20% { transform: rotate(-9deg); }
  34% { transform: rotate(7deg); }
  47% { transform: rotate(-5deg); }
  60% { transform: rotate(6deg); }
  75% { transform: rotate(-4deg); }
  86% { transform: rotate(2.5deg); }
  95% { transform: rotate(-1.2deg); }
  100% { transform: rotate(0deg); }
}

@keyframes hallwayWobbleC {
  0% { transform: rotate(0deg); }
  6% { transform: rotate(9deg); }
  22% { transform: rotate(-7deg); }
  36% { transform: rotate(8deg); }
  50% { transform: rotate(-6deg); }
  64% { transform: rotate(5deg); }
  78% { transform: rotate(-4deg); }
  88% { transform: rotate(2deg); }
  96% { transform: rotate(-1deg); }
  100% { transform: rotate(0deg); }
}

@keyframes hallwayWobbleD {
  0% { transform: rotate(0deg); }
  3.5% { transform: rotate(-12deg); }
  15% { transform: rotate(9deg); }
  29% { transform: rotate(-7deg); }
  41% { transform: rotate(6deg); }
  55% { transform: rotate(-5deg); }
  69% { transform: rotate(4deg); }
  81% { transform: rotate(-2.5deg); }
  92% { transform: rotate(1deg); }
  100% { transform: rotate(0deg); }
}

/* Cushion cat — home hero, right margin, mirrors .hallway-cluster on the
   left (same translateX(420px) right-margin offset already used by
   .books-chest/.hallway-nook elsewhere on the site). Three images cropped
   to the same box (base cat+cushion, plant, petal scatter) — base layer
   sizes the container via normal flow (position:relative, width:100%,
   height:auto), the other two stack on top via inset:0, same convention
   as the treasure chest and hallway pieces, chosen specifically to avoid
   the earlier .books-chest bug where making the *only* sizing layer
   absolute collapsed the container. Top offset is an estimate from the
   hero's normal-flow stack (text block + art frame), not a live render. */
.hero-cushion-cat {
  position: absolute;
  top: 820px;
  left: 50%;
  transform: translateX(339px);
  z-index: 2;
  width: 254px;
  line-height: 0;
  cursor: pointer;
}

.hero-cushion-cat:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

/* display:block is required here, not optional — this is a <span>
   (inline by default), and width:100% does nothing on an inline element.
   Worse, an inline box containing block-level children (the img below,
   plus the absolutely-positioned plant/confetti layers) is invalid CSS
   that browsers "fix" by auto-wrapping in anonymous block boxes, which is
   exactly the inconsistent sizing that made the 3 layers stack out of
   register. Same root cause .books-chest-art already has a comment about
   — this piece was just missing the fix. */
.hero-cushion-cat-scene {
  display: block;
  position: relative;
  width: 100%;
}

.hero-cushion-cat-img {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
}

.hero-cushion-cat-plant {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Same fireFlicker-family rustle used on the Books cottage plant, reused
   directly (not redefined) and timed to the same 2.976s rustle clip. */
.hero-cushion-cat-plant.is-rustling {
  animation: booksCottagePlantFlicker 2.976s ease-in-out;
}

/* Ambient, continuous blink — no click, no JS, exact same technique and
   keyframe as .hearth-raven-eyelid ("like the raven" per Eloise), just
   repositioned to this cat's two pupils and recolored to its own fur/
   highlight tone instead of the raven's dark background. */
.hero-cushion-cat-eyelid {
  position: absolute;
  width: 2.3%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #fff7ee 55%, #ffe9d6 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}

/* Re-measured directly off the pupils by locating the darkest pixel
   cluster in each eye (my first hand-eyeballed percentages, especially
   the right eye, sat visibly off-center once zoomed in — this is what
   read as "messed up alignment"; the 3 source layers themselves were
   never touched and are still pixel-exact to how Eloise sent them). */
.hero-cushion-cat-eyelid-l { left: 43.03%; top: 43.53%; }
.hero-cushion-cat-eyelid-r { left: 55.27%; top: 41.88%; }

@media (max-width: 1300px) {
  .hero-cushion-cat {
    display: none;
  }
}

/* Reading nook (rocking chair + painting) — right margin, anchored to the
   ritual zone specifically rather than the top of the page. Click (or
   Enter/Space) triggers the motion plus a 5s rocking-chair recording,
   both sized to that same 5.064s duration — same "movement lasts as long
   as the sound" idea as the gallery wall, just a shorter clip this time
   instead of the full 20.8s knock file. */
.hearth-zone-ritual-wrap {
  position: relative;
  /* .hearth-altar is a flex column with align-items:center (not stretch),
     so a flex child with no explicit width shrinks to fit its content —
     which made this wrapper (and the ritual zone at width:100% inside it)
     narrower than the intro/quote zones above it, which are direct flex
     children and don't have this problem. Matching their padding wasn't
     enough; the tile itself was a different width. */
  width: 100%;
}

.hallway-nook {
  position: absolute;
  top: -90px;
  left: 50%;
  transform: translateX(417px);
  z-index: 2;
  width: 228px;
  line-height: 0;
  cursor: pointer;
}

.hallway-nook:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.hallway-nook img {
  display: block;
  width: 100%;
  height: auto;
}

.hallway-nook-chair {
  position: relative;
  z-index: 1;
  transform-origin: 67.5% 99.7%;
}

.hallway-nook-painting {
  position: absolute;
  inset: 0;
  z-index: 2;
  transform-origin: 27.4% 1%;
}

.hallway-nook.is-rocking .hallway-nook-chair {
  animation: nookChairRock 5.064s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}

.hallway-nook.is-rocking .hallway-nook-painting {
  animation: nookPaintingSway 5.064s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}

/* One firm push, then decaying rocks settling back to still — same
   pendulum logic as the gallery wall wobble, just tuned gentler since a
   chair rocks in a straight line rather than swinging on a nail. */
@keyframes nookChairRock {
  0% { transform: rotate(0deg); }
  10% { transform: rotate(-8deg); }
  25% { transform: rotate(6deg); }
  40% { transform: rotate(-4deg); }
  55% { transform: rotate(2.5deg); }
  70% { transform: rotate(-1.5deg); }
  85% { transform: rotate(0.7deg); }
  100% { transform: rotate(0deg); }
}

/* Subtler than the chair — "wobble a little" rather than a full swing. */
@keyframes nookPaintingSway {
  0% { transform: rotate(0deg); }
  15% { transform: rotate(-2deg); }
  40% { transform: rotate(1.5deg); }
  65% { transform: rotate(-0.8deg); }
  85% { transform: rotate(0.3deg); }
  100% { transform: rotate(0deg); }
}

@media (max-width: 1300px) {
  .hallway-nook,
  .ritual-pumpkin-scatter {
    display: none;
  }
}

/* Cat-on-pumpkin — ritual zone, left margin, mirrors .hallway-nook's
   right-margin placement (same translateX(-X - 100%) mirroring trick
   already used by .hearth-gramophone). Two full illustrations: normal
   (friendly jack-o'-lantern face) sizes the box, evil (mean carved face,
   baked directly into that artwork) crossfades on top on click. A small
   eyelid overlay (same ravenBlink technique as everywhere else on the
   site) gives the CAT an ambient blink at rest — separate from the
   crossfade, which is the pumpkin's own expression changing, not the
   cat's eyes. Click sequence: crossfade to evil (slow, ~1s, so it reads
   as one deliberate transformation rather than a snap), then once evil
   is fully in, the whole piece grows for the rest of the laugh sfx,
   settling back to normal (and back to size) when the sound ends. See
   initRitualPumpkinCat() in app.js. Halloween-only piece — tagged
   data-season="halloween" so it auto-hides once that window ends (see
   updateSeasonalDecor() in app.js) along with the rest of the site's
   seasonal art (see Content-Schedule.md; no Christmas/New Year
   equivalent exists yet). */
.ritual-pumpkin-cat {
  position: absolute;
  top: 245px;
  left: 50%;
  transform: translateX(-641px);
  z-index: 2;
  width: 190px;
  line-height: 0;
  cursor: pointer;
}

.ritual-pumpkin-cat:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.ritual-pumpkin-cat-scene {
  display: block;
  position: relative;
  width: 100%;
  /* Grows on click, once the evil crossfade has finished — lives on this
     inner wrapper rather than .ritual-pumpkin-cat itself, since that outer
     element is what ?edit=1 drags/resizes via inline style.transform; a
     second transform source on the same element would fight the tool the
     same way the floating ghosts' animation once did. transform-origin
     50%/100% so it swells upward from where it's sitting rather than
     drifting off its footprint. Base transition (0.4s) is the quick
     settle-back once .is-growing is removed; the slower 1.98s override
     below only applies while actually growing. */
  transform: scale(1);
  transform-origin: 50% 100%;
  transition: transform 0.4s ease-out;
}

/* 1.98s = the 2.88s evil-laugh sfx minus the 0.9s crossfade below, so the
   grow finishes right as the sound (and the scare) ends. */
.ritual-pumpkin-cat.is-growing .ritual-pumpkin-cat-scene {
  transform: scale(1.4);
  transition: transform 1.98s ease-in;
}

.ritual-pumpkin-cat-normal {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
}

/* Ambient, continuous blink at rest — same ravenBlink technique as every
   other blinking piece on the site (raven, almanac cat, shop bunny,
   hero cushion cat): a small dot colored to match the fur immediately
   around each pupil, scaled from 0 (invisible, eye open) to 1 (covers the
   pupil, eye "closed") on a loop. Center points and fill color were
   measured directly off pumpkin-cat-normal.webp (darkest-pixel cluster
   per eye, then sampled the fur tone touching each one), same method as
   the other pieces' eyelid comments describe.
   Sits between the normal and evil images in the DOM so it's drawn on top
   of the resting face but underneath the evil overlay — no extra z-index
   needed, the evil image just naturally covers it once .is-evil fades it
   in. */
.ritual-pumpkin-cat-eyelid {
  position: absolute;
  width: 4.2%;
  height: 2.7%;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #fda85d 55%, #f2934a 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}

.ritual-pumpkin-cat-eyelid-l { left: 40.18%; top: 28.56%; }
.ritual-pumpkin-cat-eyelid-r { left: 56.21%; top: 27.26%; }

/* Ambient blink pauses the instant the scare kicks in — the evil image
   covers this area anyway, but hiding it too means there's no chance of a
   stray blink dot peeking out mid-crossfade. */
.ritual-pumpkin-cat.is-evil .ritual-pumpkin-cat-eyelid {
  animation-play-state: paused;
  opacity: 0;
}

.ritual-pumpkin-cat-evil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  /* Slowed from 0.22s — a snap crossfade read as a glitch rather than a
     deliberate transformation. 0.9s lets it settle in properly before the
     grow phase (below) takes over. */
  transition: opacity 0.9s ease-in;
}

/* Reuses the same brightness/drop-shadow pulse as every other jack-o-
   lantern's candle glow (.pumpkin-glow / pumpkinCandleGlow below) rather
   than inventing a second glow recipe — just on a quicker cycle, since
   this is a brief, energetic scare moment rather than ambient background
   flicker. Makes the pumpkin's already-lit carved face read as noticeably
   "more illuminated" during the laugh, on top of the mean/glowing art
   itself. */
.ritual-pumpkin-cat.is-evil .ritual-pumpkin-cat-evil {
  opacity: 1;
  animation: pumpkinCandleGlow 1.2s ease-in-out infinite;
}

/* Second scatter pumpkin (carved face this time), stacked below the cat
   in the same left margin — the "couple of pumpkins... here and there"
   Eloise asked for, without crowding either piece. Also Halloween-only. */
.ritual-pumpkin-scatter {
  position: absolute;
  top: 437px;
  left: 50%;
  transform: translateX(-470px);
  z-index: 1;
  width: 85px;
  line-height: 0;
  pointer-events: none;
}

.ritual-pumpkin-scatter img {
  display: block;
  width: 100%;
  height: auto;
}

/* Cat behind the plant (Almanac page) — same "layer them directly" build
   as the gallery wall and reading nook: the plant sits on top of the cat,
   pre-aligned by Eloise so the two crop to the same canvas. Peek-a-boo
   click behaviour instead of ambient/rock: the cat-wrap starts translated
   down so the pot and leaves hide it entirely, the plant gives a quick
   ruffle, then the cat rises into its aligned position, blinks twice, and
   sinks back out of sight — one self-contained animation, not a toggle. */
.almanac-bush {
  position: absolute;
  /* Sized/positioned so the plant's own vertical midpoint (it sits lower
     in the frame than the composite's center, since the cat's hat fills
     the top ~26%) lines up with the middle of the "She keeps one eye..."
     intro line below the heading — estimated from the section's padding
     and type stack, not a live render, so may want a small nudge. */
  top: 98px;
  left: 50%;
  transform: translateX(-612px);
  z-index: 2;
  width: 170px;
  overflow: hidden;
  line-height: 0;
  cursor: pointer;
}

.almanac-bush:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.almanac-bush-plant {
  position: relative;
  z-index: 2;
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 97%;
}

.almanac-bush-cat-wrap {
  position: absolute;
  inset: 0;
  z-index: 1;
  transform: translateY(35%);
}

.almanac-bush-cat {
  display: block;
  width: 100%;
  height: auto;
}

/* Eye centres measured directly off the source art (percent of the
   cat+plant canvas), same approach as the kitten's blinking eyelid. */
/* Re-measured 2026-08-19 as part of a site-wide blink-coverage audit —
   the old 1.4% dot (sized for a much smaller pupil) sat too small and
   too high, landing on the hat brim instead of the eyes. New size/
   position measured directly off almanac-bush-cat.webp. */
.almanac-bush-eyelid {
  position: absolute;
  z-index: 2;
  width: 6.5%;
  height: 3.5%;
  transform: translate(-50%, -50%) scaleY(0);
  border-radius: 50%;
}

.almanac-bush-eyelid-l {
  left: 42.2%;
  top: 24.5%;
  background: #86603a;
}

.almanac-bush-eyelid-r {
  left: 55.0%;
  top: 26.5%;
  background: #693f24;
}

/* Stretched from 3.2s to 4.6s (+1s) so the bush gets a full second of
   rustling to itself — roughly matching the rustle clip's own length —
   before the cat starts rising, instead of the two motions starting
   almost together. */
.almanac-bush.is-peeking .almanac-bush-plant {
  animation: almanacBushRuffle 4.6s ease-out;
}

.almanac-bush.is-peeking .almanac-bush-cat-wrap {
  animation: almanacBushPop 4.6s cubic-bezier(0.34, 1.4, 0.4, 1);
}

.almanac-bush.is-peeking .almanac-bush-eyelid {
  animation: almanacBushBlink 4.6s ease-in-out;
}

/* Same flicker recipe as .fireplace-pic-flame's fireFlicker keyframes —
   non-uniform scale(x, y) distortion paired with a slight rotate, rather
   than a plain rotate-only wobble — just decaying to rest by 43.5% instead
   of looping forever like the flame does. Origin stays at the pot's base
   so it reads as swaying from where it's rooted. */
@keyframes almanacBushRuffle {
  0% { transform: scale(1, 1) rotate(0deg); }
  7% { transform: scale(1.04, 0.97) rotate(-2deg); }
  14% { transform: scale(0.97, 1.03) rotate(1.6deg); }
  21% { transform: scale(1.02, 0.98) rotate(-1.2deg); }
  28% { transform: scale(0.99, 1.01) rotate(0.8deg); }
  35% { transform: scale(1.01, 1) rotate(-0.4deg); }
  43.5%, 100% { transform: scale(1, 1) rotate(0deg); }
}

@keyframes almanacBushPop {
  0%, 43.5% { transform: translateY(35%); }
  50% { transform: translateY(-3%); }
  54.3% { transform: translateY(2%); }
  58.7%, 87% { transform: translateY(0%); }
  95% { transform: translateY(14%); }
  100% { transform: translateY(35%); }
}

@keyframes almanacBushBlink {
  0%, 65% { transform: translate(-50%, -50%) scaleY(0); }
  67% { transform: translate(-50%, -50%) scaleY(1); }
  69%, 78% { transform: translate(-50%, -50%) scaleY(0); }
  80% { transform: translate(-50%, -50%) scaleY(1); }
  82%, 100% { transform: translate(-50%, -50%) scaleY(0); }
}

/* Blinking cat (Almanac page) — bottom right, sitting right at the
   boundary with the newsletter/signup block that follows. Anchored with
   `bottom` since the section's height is fixed, so anchoring to the
   bottom is exact instead of an estimated `top` offset (the easel and
   bird pieces in the newsletter block itself use vertical centering
   instead, since they sit alongside the signup form rather than at a
   section boundary). Purely decorative ambient blink, same technique and
   even the same 5.5s timing as
   .hearth-raven-eyelid — no click, no JS, no sound, just an infinite
   loop. Eye centers found by scanning the source art for its saturated
   yellow iris color (same approach used for the raven's eye) rather than
   eyeballing coordinates. */
.almanac-cat {
  position: absolute;
  top: 938px;
  left: 50%;
  transform: translateX(467px);
  z-index: 2;
  width: 172px;
  line-height: 0;
  cursor: pointer;
}
.almanac-cat:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.almanac-cat-img {
  display: block;
  width: 100%;
  height: auto;
}

.almanac-cat-eyelid {
  position: absolute;
  z-index: 2;
  width: 10.5%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: #fe7090;
  border-radius: 50%;
  animation: almanacCatBlink 5.5s ease-in-out infinite;
}

.almanac-cat-eyelid-l {
  left: 38.16%;
  top: 37.24%;
}

.almanac-cat-eyelid-r {
  left: 62.37%;
  top: 35.38%;
}

@keyframes almanacCatBlink {
  0%, 92% { transform: translate(-50%, -50%) scaleY(0); }
  94% { transform: translate(-50%, -50%) scaleY(1); }
  96%, 100% { transform: translate(-50%, -50%) scaleY(0); }
}

@media (max-width: 1300px) {
  .almanac-cat {
    display: none;
  }
}

/* Wall lantern (Storyteller/Wise Woman page) — top right corner, mirrors
   the almanac-bush's "outside the page" placement but on the opposite
   side and anchored near the very top instead of lined up with body
   copy. Single flat image, no layered poses and no click toggle — just
   an always-on ambient glow using the exact same pumpkinCandleGlow
   recipe every jack-o-lantern uses (see .pumpkin-glow), applied
   directly here. Replaced the old wall lantern that used to live here
   (real on/off toggle, two crossfaded poses) — Eloise liked that piece
   but wanted it out of this scene specifically. Permanent, no
   data-season. Rough placeholder position, drag into place with
   ?edit=1. */
.hearth-lava-lamp {
  position: absolute;
  top: 65px;
  left: 50%;
  transform: translateX(440px);
  z-index: 2;
  width: 135px;
  line-height: 0;
}

.hearth-lava-lamp-img {
  display: block;
  width: 100%;
  height: auto;
  animation: pumpkinCandleGlow 3.6s ease-in-out infinite;
}

@media (max-width: 1300px) {
  .hearth-lava-lamp {
    display: none;
  }
}

/* Bunny (Storyteller/Wise Woman page) — single flat portrait, plus one
   tiny independent part: the nose. The base image has its own painted
   nose patched over with a sampled fur color (done at crop time, not in
   CSS) so only the separate nose sprite below is ever visible, sitting
   exactly on top of that patch — same technique as every blinking eyelid
   on the site, just for a wiggle instead of a blink. Percentages measured
   directly off the source crop, not eyeballed. Initial placement is a
   rough guess — drag into place with ?edit=1. */
.hearth-bunny {
  position: absolute;
  top: 659px;
  left: 50%;
  transform: translateX(306px);
  z-index: 2;
  width: 152px;
  line-height: 0;
  cursor: pointer;
}

.hearth-bunny:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.hearth-bunny-base-img {
  display: block;
  width: 100%;
  height: auto;
}

.hearth-bunny-nose-img {
  position: absolute;
  left: 44.29%;
  top: 33.63%;
  width: 8.1%;
  height: auto;
  transform-origin: 50% 50%;
}

.hearth-bunny.is-wiggling .hearth-bunny-nose-img {
  animation: bunnyNoseWiggle 0.9s ease-in-out;
}

/* Original pass (scale/rotate only, centered on the nose's own tiny
   8.1%-width box) read as barely-there — rotating/scaling something only
   ~15px on screen by a few percent just isn't visible at that size. Real
   displacement (translate) reads far more clearly than scale/rotate at
   this scale, and three quick twitches instead of one soft pulse actually
   looks like a bunny sniffing rather than a faint shimmer. */
@keyframes bunnyNoseWiggle {
  0%, 100% { transform: translate(0, 0) scale(1) rotate(0deg); }
  8% { transform: translate(-2px, 0.6px) scale(1.3, 0.75) rotate(-16deg); }
  18% { transform: translate(2px, -0.6px) scale(0.78, 1.28) rotate(15deg); }
  28% { transform: translate(-1.8px, 0.5px) scale(1.26, 0.78) rotate(-14deg); }
  38% { transform: translate(1.8px, -0.5px) scale(0.8, 1.24) rotate(12deg); }
  50% { transform: translate(-1.5px, 0.4px) scale(1.2, 0.82) rotate(-11deg); }
  60% { transform: translate(1.5px, -0.4px) scale(0.84, 1.18) rotate(9deg); }
  72% { transform: translate(-1px, 0.3px) scale(1.12, 0.88) rotate(-6deg); }
  84% { transform: translate(0.6px, -0.2px) scale(0.94, 1.06) rotate(3deg); }
}

@media (max-width: 1300px) {
  .hearth-bunny {
    display: none;
  }
}

/* Fairy (Wise Woman page) — permanent decor, no data-season. She's turned
   to the side in the source art, so only one eye is actually drawn —
   one eyelid overlay, same ravenBlink technique/timing as every other
   blinking piece on the site, center point measured directly off
   hearth-fairy.webp, fill color sampled from her own skin tone right next
   to the eye so the "closed eye" blends in. Click also bursts a ring of
   sparkles + magic chime (see .hearth-fairy-sparkles below and
   initHearthFairy() in app.js). Rough placeholder position, drag into
   place with ?edit=1. */
.hearth-fairy {
  position: absolute;
  top: 71px;
  left: 50%;
  transform: translateX(-629px);
  z-index: 2;
  width: 228px;
  line-height: 0;
  cursor: pointer;
}
.hearth-fairy:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.hearth-fairy img {
  display: block;
  width: 100%;
  height: auto;
  position: relative;
  z-index: 1;
}
/* Click-triggered float — same idea as the Keepsakes balloon/Quiet
   Hollow butterflies, except she needs to keep floating for as long as
   the chime plays (15.41s) rather than one fixed-length bob. The
   keyframes loop (infinite) instead of running once; initHearthFairy()
   in app.js removes .is-floating when the chime's "ended" event fires,
   so the two stay in sync regardless of the audio file's exact length. */
.hearth-fairy.is-floating img {
  animation: hearthFairyFloat 3.8s ease-in-out infinite;
}
@keyframes hearthFairyFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  22% { transform: translateY(-18px) rotate(-2deg); }
  50% { transform: translateY(-30px) rotate(2deg); }
  78% { transform: translateY(-14px) rotate(-1deg); }
}
.hearth-fairy-eyelid {
  position: absolute;
  left: 38.6%;
  top: 24.8%;
  width: 3%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #ffbb9d 55%, #f5a884 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
  z-index: 2;
}
.hearth-fairy-sparkles {
  position: absolute;
  inset: -40px;
  pointer-events: none;
  z-index: 3;
}
.hearth-sparkle {
  position: absolute;
  top: 40%;
  left: 50%;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: radial-gradient(circle, #fffef2 0%, #fff8e6 30%, var(--gold) 65%, transparent 100%);
  box-shadow: 0 0 8px 2px rgba(232, 196, 104, 0.65);
  opacity: 0;
  animation-name: hearthSparkleBurst;
  animation-timing-function: ease-out;
  animation-fill-mode: forwards;
}
@keyframes hearthSparkleBurst {
  0% { transform: translate(-50%, -50%) translate(0, 0) scale(0.5); opacity: 0; }
  15% { opacity: 1; }
  60% { opacity: 1; }
  100% {
    transform: translate(-50%, -50%) translate(var(--spark-x, 40px), var(--spark-y, -40px)) scale(0.25);
    opacity: 0;
  }
}

@media (max-width: 1300px) {
  .hearth-fairy {
    display: none;
  }
}

/* Fox with books (Wise Woman page) — permanent decor, no data-season.
   Ambient blinking eyes, same ravenBlink technique used site-wide, center
   points measured off hearth-fox.webp, fill color sampled from the cream
   fur patch right around each eye. No click. Rough placeholder position,
   drag into place with ?edit=1. */
.hearth-fox {
  position: absolute;
  top: 1137px;
  left: 50%;
  transform: translateX(-470px);
  z-index: 2;
  width: 170px;
  line-height: 0;
}
.hearth-fox img {
  display: block;
  width: 100%;
  height: auto;
}
.hearth-fox-eyelid {
  position: absolute;
  top: 27%;
  width: 4.2%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #ffce92 55%, #f5b96f 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}
/* Both eyes blink together — same 5.5s ravenBlink timing, no offset
   between them, so it reads as one blink instead of two eyes blinking
   independently. */
.hearth-fox-eyelid-a { left: 45.6%; top: 26.9%; }
.hearth-fox-eyelid-b { left: 64.9%; top: 29.8%; }

@media (max-width: 1300px) {
  .hearth-fox {
    display: none;
  }
}

/* Gramophone (Storyteller/Wise Woman page) — left margin, lined up with
   the divider between the book tile and the raven/quote tile. Single
   image (not a pre-aligned pair like the other pieces), so the "movement"
   is the same fireFlicker recipe used on the fireplace flame and the
   almanac bush — non-uniform scale(x, y) plus a slight rotate — just
   looping for as long as the click sequence is playing instead of
   decaying to rest or running once. app.js drives the loop entirely off
   real audio `ended` events (start sound, then song), so the animation
   naturally tracks however long the song actually runs rather than a
   hardcoded duration that could drift out of sync. */
.hearth-gramophone {
  position: absolute;
  top: 656px;
  left: 50%;
  /* scaleX(-1) mirrors it horizontally, same as .hearth-lantern. Kept here
     even though the edit-mode panel's copied value only reports the plain
     translateX — that tool doesn't know about the extra scaleX(-1), so it
     has to be re-added by hand each time a position update comes from it. */
  transform: translateX(-653px) scaleX(-1);
  z-index: 2;
  width: 190px;
  line-height: 0;
  cursor: pointer;
}

.hearth-gramophone:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.hearth-gramophone-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 70%;
}

.hearth-gramophone.is-spinning .hearth-gramophone-img {
  animation: gramophoneFlicker 1.8s ease-in-out infinite;
}

@keyframes gramophoneFlicker {
  0%, 100% { transform: scale(1) rotate(0deg); }
  25% { transform: scale(1.04, 0.97) rotate(-1deg); }
  50% { transform: scale(0.97, 1.03) rotate(0.8deg); }
  75% { transform: scale(1.02, 0.98) rotate(-0.6deg); }
}

@media (max-width: 1300px) {
  .hearth-gramophone {
    display: none;
  }
}

/* Candle-flicker glow — for standalone scatter pumpkins, so their carved
   faces read as lit from within (soft dim/brighten) rather than a hard
   blink. Each instance below gets its own animation-delay so a page with
   more than one pumpkin doesn't glow in lockstep. */
@keyframes pumpkinCandleGlow {
  0%, 100% { filter: brightness(0.92) drop-shadow(0 0 5px rgba(255, 170, 60, 0.35)); }
  50% { filter: brightness(1.1) drop-shadow(0 0 13px rgba(255, 170, 60, 0.65)); }
}

.pumpkin-glow {
  animation: pumpkinCandleGlow 3.6s ease-in-out infinite;
}

/* Shared base for the "2 more scatter pumpkins and 2 more jack-o-
   lanterns per page" batch — every page that already had one pumpkin
   pair gets 2 extra plain gourds and 2 extra jack-o-lanterns on top of
   what was there before, reusing the exact same assets/glow rather than
   new art. Rather than writing position/z-index/line-height/width out 20
   times, this shared class carries everything EXCEPT top/transform,
   which each individual selector below sets on its own so the drag/
   resize tool (?edit=1) can still address each one independently. All
   are ambient/no-click (pointer-events:none) and data-season="halloween"
   like their originals, and start at rough placeholder positions —
   they're meant to be dragged into their real spots. */
.pumpkin-scatter-extra {
  position: absolute;
  z-index: 2;
  line-height: 0;
  width: 80px;
  pointer-events: none;
}

/* Missing on the first pass — without this, the <img> has no width/height
   of its own and renders at its full native pixel size regardless of the
   wrapper's width, which is why these came in "giant" and the resize
   tool only looked like it was shrinking an invisible box, not the
   pumpkin itself. Every other decor piece on the site has this same
   img-fills-wrapper rule; this one just got missed. */
.pumpkin-scatter-extra img {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 1300px) {
  .pumpkin-scatter-extra {
    display: none;
  }
}

.home-pumpkin-scatter-2 { top: 109px; transform: translateX(177px); width: 80px; }
.home-pumpkin-scatter-3 { top: 2908px; transform: translateX(-364px); width: 92px; }
.home-pumpkin-jack-2 { top: 113px; transform: translateX(-264px); width: 80px; }
.home-pumpkin-jack-3 { top: 3289px; transform: translateX(-488px); width: 80px; }

.hearth-pumpkin-scatter-2 { top: 86px; transform: translateX(213px); width: 98px; }
.hearth-pumpkin-scatter-3 { top: 287px; transform: translateX(573px); width: 110px; }
.hearth-pumpkin-jack-2 { top: 85px; transform: translateX(-205px); width: 92px; }
.hearth-pumpkin-jack-3 { top: 812px; transform: translateX(-461px); width: 122px; }

.almanac-pumpkin-scatter-2 { top: 100px; transform: translateX(215px); width: 80px; }
.almanac-pumpkin-scatter-3 { top: 1081px; transform: translateX(566px); width: 116px; }
.almanac-pumpkin-jack-2 { top: 92px; transform: translateX(-291px); width: 86px; }
.almanac-pumpkin-jack-3 { top: 1644px; transform: translateX(-201px); width: 122px; }

.books-pumpkin-scatter-2 { top: 289px; transform: translateX(-453px); width: 92px; }
.books-pumpkin-scatter-3 { top: 96px; transform: translateX(110px); width: 80px; }
.books-pumpkin-jack-2 { top: 1034px; transform: translateX(-659px); width: 86px; }
.books-pumpkin-jack-3 { top: 1551px; transform: translateX(-486px); width: 68px; }

.about-pumpkin-scatter-2 { top: 728px; transform: translateX(530px); width: 92px; }
.about-pumpkin-scatter-3 { top: 1252px; transform: translateX(-503px); width: 98px; }
.about-pumpkin-jack-2 { top: 1276px; transform: translateX(411px); width: 80px; }
.about-pumpkin-jack-3 { top: 414px; transform: translateX(-561px); width: 74px; }

.market-pumpkin-scatter-2 { top: 287px; transform: translateX(-407px); width: 108px; }
.market-pumpkin-scatter-3 { top: 87px; transform: translateX(97px); width: 100px; }
.market-pumpkin-jack-2 { top: 91px; transform: translateX(-195px); width: 102px; }
.market-pumpkin-jack-3 { top: 1154px; transform: translateX(419px); width: 108px; }

/* Bat + spider's web — Eloise's two newest Halloween pieces, added to
   EVERY page (Home, Wise Woman, Almanac, Self Love, Bookstore, Little
   Faye) so the site's Halloween pass feels consistent everywhere, not
   just the pages that already had pumpkins. Same shared-base-class
   pattern as .pumpkin-scatter-extra above: one class carries every rule
   except top/transform/width so ?edit=1 can address each instance on its
   own. Purely ambient, pointer-events:none, tagged data-season="halloween"
   in the HTML so they hide with the rest of the seasonal decor after the
   window ends. Positions below are rough placeholder guesses — meant to
   be dragged into their real spots with the edit tool, same as the extra
   scatter pumpkins were. */
.halloween-bat,
.halloween-web {
  position: absolute;
  z-index: 2;
  line-height: 0;
  width: 90px;
  pointer-events: none;
}

.halloween-bat img,
.halloween-web img {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 1300px) {
  .halloween-bat,
  .halloween-web {
    display: none;
  }
}

.home-bat { top: 1137px; transform: translateX(-650px); width: 90px; }
.home-web { top: 1389px; transform: translateX(-614px); width: 112px; }

.hearth-bat { top: 1962px; transform: translateX(428px); width: 78px; }
.hearth-web { top: 215px; transform: translateX(512px); width: 142px; }

.almanac-bat { top: 50px; transform: translateX(590px); width: 90px; }
.almanac-web { top: 2065px; transform: translateX(-365px); width: 70px; }

.selflove-bat { top: 56px; transform: translateX(-176px); width: 78px; }
.selflove-web { top: 85px; transform: translateX(-12px); width: 130px; }

.books-bat { top: 247px; transform: translateX(564px); width: 78px; }
.books-web { top: 92px; transform: translateX(-206px); width: 118px; }

.about-bat { top: 145px; transform: translateX(-425px); width: 60px; }
.about-web { top: 76px; transform: translateX(-84px); width: 148px; }

/* Scatter pumpkin (no face) — The Wise Woman page, intro zone right margin, mirrors
   .hearth-gramophone's left-margin placement and top offset (same
   section-relative coordinate frame, just the opposite side). Purely
   ambient, no click — pointer-events:none so it never steals a click
   meant for something behind it. Top offset is an estimate, not a live
   render — may want a nudge once Eloise sees it. Tagged
   data-season="halloween" like the carved pumpkins — plain gourd or not,
   it's still Halloween-only decor, so it hides once that window ends too
   (see updateSeasonalDecor() in app.js). */
.hearth-pumpkin-scatter {
  position: absolute;
  top: 2060px;
  left: 50%;
  transform: translateX(-514px);
  z-index: 1;
  width: 112px;
  line-height: 0;
  pointer-events: none;
}

.hearth-pumpkin-scatter img {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 1300px) {
  .hearth-pumpkin-scatter {
    display: none;
  }
}

/* Scatter pumpkins on the other pages (Home, Almanac, About, Gift Shop) —
   same idea as the Wise Woman page's pair: one plain gourd and one carved
   jack-o-lantern, BOTH tagged data-season="halloween" in the HTML (plain
   gourd or not, they're still Halloween-only decor) so both auto-hide
   together once that window ends via the same updateSeasonalDecor() sweep
   in app.js — no new JS needed. All purely ambient, pointer-events:none
   so they never steal a click. Top offsets are estimates picked to clear
   the page's existing decorative pieces with generous room, not a live
   render — may want nudges once Eloise sees them. */
.home-pumpkin-scatter,
.home-pumpkin-jack,
.almanac-pumpkin-scatter,
.almanac-pumpkin-jack,
.about-pumpkin-scatter,
.about-pumpkin-jack,
.books-pumpkin-scatter,
.books-pumpkin-jack,
.selflove-pumpkin-scatter,
.selflove-pumpkin-jack,
.market-pumpkin-scatter,
.social-pumpkin-scatter,
.social-pumpkin-jack,
.naming-pumpkin-scatter,
.naming-pumpkin-jack {
  position: absolute;
  left: 50%;
  z-index: 1;
  line-height: 0;
  pointer-events: none;
}

.home-pumpkin-scatter img,
.home-pumpkin-jack img,
.almanac-pumpkin-scatter img,
.almanac-pumpkin-jack img,
.about-pumpkin-scatter img,
.about-pumpkin-jack img,
.books-pumpkin-scatter img,
.books-pumpkin-jack img,
.selflove-pumpkin-scatter img,
.selflove-pumpkin-jack img,
.market-pumpkin-scatter img,
.social-pumpkin-scatter img,
.social-pumpkin-jack img,
.naming-pumpkin-scatter img,
.naming-pumpkin-jack img {
  display: block;
  width: 100%;
  height: auto;
}

/* Home hero — plain pumpkin, sized way up to match .hero-art's own
   max-width (320px) and centered vertically alongside that hero portrait
   instead of its original small size mirroring .hero-cushion-cat. hero-art
   runs roughly top:450-820px in this coordinate frame (between the
   hallway cluster above and the cushion-cat/pumpkin-jack row below).
   Nudged down and right (toward center) from the first pass per Eloise's
   feedback, to sit more centered within the margin's open space rather
   than hugging the hallway cluster's own inner boundary. */
.home-pumpkin-scatter {
  top: 1385px;
  transform: translateX(-458px);
  width: 98px;
}

.home-pumpkin-jack {
  top: 1834px;
  transform: translateX(-348px);
  width: 85px;
}

/* Almanac — both sit below .almanac-bush/.almanac-cat's own territory
   (bush is top-anchored near the page heading, cat is bottom-anchored
   near the newsletter handoff) so they land in the open middle ground
   between the two. */
.almanac-pumpkin-scatter {
  top: 898px;
  transform: translateX(-508px);
  width: 108px;
}

.almanac-pumpkin-jack {
  top: 299px;
  transform: translateX(-670px);
  width: 85px;
}

/* ---- Witch flying past a harvest moon (Almanac page) — PERMANENT
   decor, year-round, like the crow-moon piece on Home. Deliberately NOT
   tagged data-season anywhere — see "This should be a permanent
   fixture." Two images pre-aligned on one shared canvas (same union-
   bbox-crop convention as the hallway cluster and skeleton radio scene):
   moon is the base layer in normal flow (sets the wrapper's own size),
   witch stacks on top via inset:0. Initial placement below is a rough
   guess — drag it into its real spot with ?edit=1. ----- */
.almanac-witch-moon {
  position: absolute;
  top: 105px;
  left: 50%;
  transform: translateX(418px);
  z-index: 2;
  width: 230px;
  line-height: 0;
  cursor: pointer;
}

.almanac-witch-moon:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.almanac-witch-moon-layer {
  display: block;
  width: 100%;
  height: auto;
}

/* Moon just illuminates — same warm dim/brighten pulse as every scatter
   pumpkin's candle glow (pumpkinCandleGlow, reused via .pumpkin-glow
   directly rather than a new keyframe), not the white/silver moonGlow
   built for the crow. Always on, no click needed. */
.almanac-witch-moon-moon {
  position: relative;
  z-index: 1;
}

/* Witch layer stacks on top of the moon at the identical crop, then only
   gets its "flight" animation once the piece is clicked (see
   initAlmanacWitchMoon in app.js). Pivot point sits near her hand where
   she grips the broom handle, so rotating this layer visibly dips the
   broom's front tip down and brings it back up — a continuous "scrapbook
   art" bounce rather than a one-shot swing. */
.almanac-witch-moon-witch {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  transform-origin: 68% 47%;
}

.almanac-witch-moon.is-flying .almanac-witch-moon-witch {
  animation: witchBroomPivot 2.4s ease-in-out infinite;
}

@keyframes witchBroomPivot {
  0%, 100% { transform: rotate(0deg); }
  35% { transform: rotate(8deg); }
  65% { transform: rotate(-3deg); }
}

@media (max-width: 1300px) {
  .almanac-witch-moon {
    display: none;
  }
}

/* About — page is short and only has 2 existing pieces (cat top-right,
   plants lower-left), so these tuck into the two open corners: above the
   plants on the left, below the cat on the right. */
.about-pumpkin-scatter {
  top: 98px;
  transform: translateX(138px);
  width: 85px;
}

.about-pumpkin-jack {
  top: 101px;
  transform: translateX(-215px);
  width: 85px;
}

/* Naming Well — the one page that never got a scatter-pumpkin pair when
   the rest of the site did. Rough placeholder spots, same as every other
   page's pair started out; drag into place with ?edit=1. */
.naming-pumpkin-scatter {
  top: 1275px;
  transform: translateX(-489px);
  width: 85px;
}

.naming-pumpkin-jack {
  top: 994px;
  transform: translateX(-449px);
  width: 85px;
}

/* Gift Shop — left margin is already crowded (cottage, lamp, radio), so
   both new pieces go on the right margin instead, in the large open gap
   between .books-chest (ends around top 540px) and .shop-bunny (starts
   at top 1700px). */
.books-pumpkin-scatter {
  top: 914px;
  transform: translateX(342px);
  width: 138px;
}

.books-pumpkin-jack {
  top: 410px;
  transform: translateX(369px);
  width: 85px;
}

/* The Quiet Hollow — new page, no other decor to work around yet besides
   the bat/web pair up top, so these go in the open lower half. Rough
   placeholder positions, drag into place with ?edit=1. */
.selflove-pumpkin-scatter {
  top: 548px;
  transform: translateX(258px);
  width: 106px;
}

.selflove-pumpkin-jack {
  top: 1396px;
  transform: translateX(-427px);
  width: 79px;
}

/* The Market — new page, rough placeholder positions, drag into place
   with ?edit=1. Kept close to the top of the page on purpose — this page
   is sparse (intro line + filter row + however many cards load), so
   anything placed too far down risks sitting in empty space below the
   actual rendered content. */
.market-pumpkin-scatter {
  top: 1743px;
  transform: translateX(-428px);
  width: 110px;
}

/* Follow Along — new page, rough placeholder positions, drag into place
   with ?edit=1. */
.social-pumpkin-scatter {
  top: 370px;
  transform: translateX(237px);
  width: 84px;
}

.social-pumpkin-jack {
  top: 513px;
  transform: translateX(-475px);
  width: 100px;
}

.social-pumpkin-scatter-2 { top: 1321px; transform: translateX(207px); width: 96px; }
.social-pumpkin-scatter-3 { top: 2446px; transform: translateX(-487px); width: 90px; }
.social-pumpkin-jack-2 { top: 1654px; transform: translateX(-322px); width: 100px; }
.social-pumpkin-jack-3 { top: 2447px; transform: translateX(404px); width: 96px; }

/* Permanent pastel bat (Quiet Hollow page) — plain still piece, no click,
   no animation, and deliberately NOT tagged data-season anywhere — unlike
   the seasonal Halloween bat/web pair up top, this one stays year-round.
   Rough placeholder position, drag into place with ?edit=1. */
/* Flower pot — potted flowering plant, permanent (no data-season). Two
   pre-aligned layers on one shared canvas (flowers behind, pot in front,
   per Eloise), same stacking convention as .hero-cushion-cat-scene. Sits
   in the spot the old permanent bat used to occupy (removed 2026-08-13).
   Rough placeholder position carried over from that piece — nudge with
   ?edit=1 if it needs adjusting now that the art is different. */
.selflove-flower-plant {
  position: absolute;
  top: 2034px;
  left: 50%;
  transform: translateX(406px);
  z-index: 2;
  width: 220px;
  line-height: 0;
  cursor: pointer;
}

.selflove-flower-plant:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

/* display:block wrapper is required, not optional — same reasoning as the
   .hero-cushion-cat-scene comment (this is a <span>, inline by default,
   and can't validly contain the absolutely-positioned overlay layer
   otherwise). Idle sway lives here (always on) so both stacked layers
   move together as one rigid piece rather than drifting apart; the click
   rustle below swaps this same animation out temporarily. */
.selflove-flower-plant-scene {
  display: block;
  position: relative;
  width: 100%;
  transform-origin: 50% 96%;
  animation: selfloveFlowerIdleSway 6.4s ease-in-out infinite;
}

.selflove-flower-plant-flowers-img {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
}

.selflove-flower-plant-pot-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

@keyframes selfloveFlowerIdleSway {
  0%, 100% { transform: rotate(0deg); }
  50% { transform: rotate(1.1deg); }
}

/* Click — same fireFlicker-family shimmer as the Books cottage plant and
   Home hero cushion cat, reused directly (not redefined) and timed to the
   same 2.976s rustle clip, per Eloise's request to reuse the existing
   bush-rustle sound + "flame movement" for this piece too. Overrides the
   idle sway above for its duration; app.js removes this class on
   animationend, so the sway picks back up on its own afterward, and that
   same animationend is what triggers the petal-fall burst (see
   .selflove-petal below). */
.selflove-flower-plant.is-rustling .selflove-flower-plant-scene {
  animation: booksCottagePlantFlicker 2.976s ease-in-out;
}

/* Petal-fall burst — one-shot, silent (per Eloise: "no sound needed for
   that"), fired from app.js right as the rustle animation ends. Generated
   fresh each click (see initSelfloveFlowerPlant), same
   generate-then-let-CSS-animate approach as the header's fairy dust, just
   falling instead of orbiting. */
.selflove-petal-burst {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}

.selflove-petal {
  position: absolute;
  top: 14%;
  width: 13px;
  height: 13px;
  background: #ea9dbd;
  box-shadow: 0 0 4px 1px rgba(234, 157, 189, 0.45);
  border-radius: 60% 40% 55% 45%;
  opacity: 0;
  animation-name: selflovePetalFall;
  animation-timing-function: ease-in;
  animation-fill-mode: forwards;
}

@keyframes selflovePetalFall {
  0% { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  12% { opacity: 1; }
  100% { transform: translate(var(--petal-drift, 20px), 150px) rotate(var(--petal-spin, 220deg)); opacity: 0; }
}

@media (max-width: 1300px) {
  .selflove-flower-plant {
    display: none;
  }
}

/* Butterfly (Quiet Hollow page) — permanent decor, no data-season. Small
   ambient light blocks scattered in a loose ring around it, each one
   flickering in place (opacity + scale, no drift) rather than swirling —
   per Eloise, an "old school defragmented light effects" look, like tiny
   blocks of light flickering on and off rather than floating dust. Reuses
   the site's existing twinkle keyframe (see .hero-stars), just recolored
   to square blocks instead of star SVGs and given its own stagger.
   Rough placeholder position, drag into place with ?edit=1. */
.selflove-butterfly {
  position: absolute;
  left: 50%;
  z-index: 2;
  line-height: 0;
  cursor: pointer;
}
/* Four butterflies, same page, same art/behavior (shared class above
   carries the animation/glimmer rules) — these modifier classes each hold
   just one instance's own position/size. Rough placeholder positions,
   drag each into place with ?edit=1. */
.selflove-butterfly-1 {
  top: 1070px;
  transform: translateX(390px);
  width: 56px;
}
.selflove-butterfly:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.selflove-butterfly img {
  display: block;
  width: 100%;
  height: auto;
  position: relative;
  z-index: 1;
}
/* Click-triggered float — same drifting up/down idea as the Keepsakes
   balloon (.market-balloon), but one-shot instead of a continuous ambient
   loop: plays through once on click, then settles back down. See
   initSelfloveButterfly() in app.js. */
.selflove-butterfly.is-floating img {
  animation: selfloveButterflyFloat 4s ease-in-out;
}
@keyframes selfloveButterflyFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  22% { transform: translateY(-16px) rotate(-3deg); }
  50% { transform: translateY(-26px) rotate(2deg); }
  78% { transform: translateY(-12px) rotate(-2deg); }
}
.selflove-butterfly-glimmer {
  position: absolute;
  inset: -30px;
  pointer-events: none;
  z-index: 2;
}
.selflove-glimmer-block {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 1px;
  opacity: 0;
}
/* Only animates while the click-triggered float is running (see
   .selflove-butterfly.is-floating above) — no click yet, no lights. Runs
   for as long as is-floating is on the element, same ~3.2s window as the
   flutter itself, then both stop together. */
.selflove-butterfly.is-floating .selflove-glimmer-block {
  animation: twinkle 2.6s ease-in-out infinite;
}
.gb-1 { top: 4%;  left: 8%;  width: 5px; height: 5px; background: var(--gold); animation-delay: 0s; }
.gb-2 { top: 14%; left: 82%; width: 7px; height: 7px; background: var(--magenta); animation-delay: 0.4s; }
.gb-3 { top: 40%; left: 94%; width: 5px; height: 5px; background: var(--mustard); animation-delay: 0.8s; }
.gb-4 { top: 68%; left: 88%; width: 6px; height: 6px; background: var(--plum); animation-delay: 1.2s; }
.gb-5 { top: 86%; left: 60%; width: 5px; height: 5px; background: var(--gold); animation-delay: 1.6s; }
.gb-6 { top: 90%; left: 22%; width: 7px; height: 7px; background: var(--magenta); animation-delay: 0.2s; }
.gb-7 { top: 62%; left: 2%;  width: 5px; height: 5px; background: var(--mustard); animation-delay: 1.4s; }
.gb-8 { top: 24%; left: -2%; width: 6px; height: 6px; background: var(--plum); animation-delay: 1.9s; }

.selflove-butterfly-2 {
  top: 1020px;
  transform: translateX(557px);
  width: 76px;
}
.selflove-butterfly-3 {
  top: 3798px;
  transform: translateX(-457px);
  width: 128px;
}
.selflove-butterfly-4 {
  top: 1169px;
  transform: translateX(606px);
  width: 30px;
}

@media (max-width: 1300px) {
  .selflove-butterfly {
    display: none;
  }
}

/* Lavender bush (Quiet Hollow page) — permanent decor, no data-season.
   Click plays the exact same bush-rustle sound + fire-flicker shimmer as
   every other bush/plant on the site (Almanac bush, Books cottage plant,
   this page's own flower pot) — see initSelfloveLavender() in app.js.
   Rough placeholder position, drag into place with ?edit=1. */
.selflove-lavender {
  position: absolute;
  top: 1056px;
  left: 50%;
  transform: translateX(418px);
  z-index: 2;
  width: 200px;
  line-height: 0;
  cursor: pointer;
}
.selflove-lavender:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.selflove-lavender-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 96%;
}
.selflove-lavender.is-rustling .selflove-lavender-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out;
}

@media (max-width: 1300px) {
  .selflove-lavender {
    display: none;
  }
}

@media (max-width: 1300px) {
  .selflove-bat-permanent {
    display: none;
  }
}

@media (max-width: 1300px) {
  .home-pumpkin-scatter,
  .almanac-pumpkin-scatter,
  .about-pumpkin-scatter,
  .books-pumpkin-scatter,
  .selflove-pumpkin-scatter,
  .social-pumpkin-scatter,
  .social-pumpkin-jack,
  .naming-pumpkin-scatter {
    display: none;
  }
}

/* ---- Skeleton radio scene (Home page, right margin) — new Halloween-only
   piece: a boombox with a skeleton reaching through it, plus two companion
   jack-o-lanterns and scattered candy, sitting in the open gap between the
   hallway cluster (top:90px) and the cushion cat/pumpkin scatter
   (top:820px) — same coordinate frame as every other margin piece above
   (absolute, left:50%, translateX from center).
   Three IMG layers (radio/pumpkins/candy) were all cropped from Eloise's
   original art to the exact same bounding box, so stacking them with
   inset:0 keeps them in perfect registration — identical technique to
   .hallway-layer-a/b/c/d above. Do NOT re-crop or reposition any one
   layer independently; if the art ever needs to change, re-export all
   three from the same shared crop box or the alignment breaks again.
   Click targets are separate invisible .home-halloween-hit elements
   layered on top, positioned as percentages of this same shared frame
   (see comment above each one) — this is what lets the radio and the two
   pumpkins be independently clickable without ever cutting the artwork
   itself apart.
   Desktop-only for now (no .mobile-showcase); can revisit for mobile
   later. This exact radio art/audio chain is meant to be reused on
   another page too, hence initSkeletonRadio() in app.js taking a
   selector instead of being hardcoded to one element. ----- */
.home-halloween-radio-scene {
  position: absolute;
  top: 377px;
  left: 50%;
  /* Clears hero-inner's own content edge (max-width:620px, so its right
     edge sits at center+310px) with a real buffer — this is the mistake
     last time: individual pieces drifted back inside that edge and
     overlapped the hero text. */
  transform: translateX(342px);
  z-index: 2;
  width: 286px;
  line-height: 0;
}

.home-halloween-scene-layer {
  display: block;
  width: 100%;
  height: auto;
}

.home-halloween-scene-pumpkins,
.home-halloween-scene-candy {
  position: absolute;
  inset: 0;
}

.home-halloween-scene-pumpkins { z-index: 1; }
.home-halloween-scene-candy { z-index: 2; }

.home-halloween-scene-radio {
  filter: drop-shadow(0 4px 10px rgba(6, 4, 20, 0.5));
}

/* Ambient candle glow on the pumpkin layer, same recipe as every other
   scatter pumpkin (pumpkinCandleGlow / .pumpkin-glow above) — always on,
   independent of the click-triggered float below. */
.home-halloween-scene-pumpkins {
  animation: pumpkinCandleGlow 3.6s ease-in-out infinite;
}

/* Slow, gentle breathing scale on the candy layer — just enough life per
   Eloise's "a bit of life not a lot", not bouncy. */
.home-halloween-scene-candy {
  animation: candyBreathe 4.4s ease-in-out infinite;
}

@keyframes candyBreathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.04); }
}

/* Invisible click targets, positioned as percentages of the shared
   620×459 frame all three layers above share — measured directly from
   where the radio/pumpkins actually sit in that frame, not eyeballed. */
.home-halloween-hit {
  position: absolute;
  z-index: 3;
  cursor: pointer;
}

.home-halloween-hit:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Radio body — roughly the middle 70% of the frame's width, top 75% of
   its height. */
.home-halloween-radio-hit {
  left: 17%;
  top: 1%;
  width: 71%;
  height: 75%;
}

/* Left jack-o-lantern. */
.home-halloween-pumpkin-hit-a {
  left: 1%;
  top: 36%;
  width: 34%;
  height: 47%;
}

/* Right jack-o-lantern. */
.home-halloween-pumpkin-hit-b {
  left: 64%;
  top: 55%;
  width: 35%;
  height: 36%;
}

/* Same "settle in and play" flicker as every other radio's is-playing
   state (.books-radio.is-playing / .radio-tile-btn.is-playing above) —
   reusing fireFlicker rather than inventing a second recipe, per Eloise's
   ask that this radio "do the same animation as the normal radio". The
   class lands on the scene wrapper (from JS) so it can drive the radio
   IMG layer specifically. */
.home-halloween-radio-scene.is-playing .home-halloween-scene-radio {
  animation: fireFlicker 1.8s ease-in-out infinite;
}

/* Click-triggered light float on a pumpkin — layers the float on top of
   the ambient glow (both list on the shared pumpkins IMG) rather than
   overwriting it. is-floating-a/b land on the scene wrapper (from JS,
   since the pumpkins are one shared image layer, not two separate
   elements) and drive the SAME image with a translateY nudge; the two
   classes use slightly different keyframes so clicking one doesn't look
   identical to clicking the other. */
.home-halloween-radio-scene.is-floating-a .home-halloween-scene-pumpkins {
  animation: pumpkinCandleGlow 3.6s ease-in-out infinite, pumpkinFloatA 1.6s ease-in-out;
}

.home-halloween-radio-scene.is-floating-b .home-halloween-scene-pumpkins {
  animation: pumpkinCandleGlow 3.6s ease-in-out infinite, pumpkinFloatB 1.6s ease-in-out;
}

@keyframes pumpkinFloatA {
  0% { transform: translate(0, 0); }
  35% { transform: translate(-3px, -10px); }
  65% { transform: translate(-1px, -3px); }
  100% { transform: translate(0, 0); }
}

@keyframes pumpkinFloatB {
  0% { transform: translate(0, 0); }
  35% { transform: translate(3px, -10px); }
  65% { transform: translate(1px, -3px); }
  100% { transform: translate(0, 0); }
}

@media (max-width: 1300px) {
  .home-halloween-radio-scene {
    display: none;
  }
}

/* ---- Crow + moon (Home page, left margin) — PERMANENT decor, year-round.
   Deliberately NOT tagged data-season anywhere (in the HTML or here) —
   this one stays up regardless of what updateSeasonalDecor() in app.js is
   doing to everything else in this margin. Single flat image (moon + crow
   already composited together), mirrors home-pumpkin-jack's top:1200px
   estimate on the opposite margin since that's roughly where the
   Bookstore/Almanac tile seam in .hero-ctas falls. ----- */
.hero-crow-moon {
  position: absolute;
  top: 2431px;
  left: 50%;
  transform: translateX(-590px);
  z-index: 2;
  width: 224px;
  line-height: 0;
}

.hero-crow-moon-img {
  display: block;
  width: 100%;
  height: auto;
  /* Same dim/bright pulse as the scatter pumpkins' candle glow
     (pumpkinCandleGlow above), just recolored to white/silver light for
     the moon instead of orange — applying it to the whole flat image
     (moon + crow together) reads as "the moon glowing" in practice, since
     the already-bright moon shows the brightness swing far more than the
     near-black crow silhouette does. */
  animation: moonGlow 3.6s ease-in-out infinite;
}

@keyframes moonGlow {
  0%, 100% { filter: brightness(0.92) drop-shadow(0 0 5px rgba(255, 255, 255, 0.4)); }
  50% { filter: brightness(1.1) drop-shadow(0 0 14px rgba(255, 255, 255, 0.7)); }
}

/* Ambient, continuous blink — same ravenBlink technique as every other
   blinking piece on the site. Center point and fill color measured
   directly off the art: located the white eye-crescent's own bounding box
   (not just eyeballed), then sampled the dark navy fur immediately
   surrounding it for the "closed eye" color. */
.hero-crow-moon-eyelid {
  position: absolute;
  left: 27.81%;
  top: 41.58%;
  width: 2.6%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #14224f 55%, #04092f 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}

@media (max-width: 1300px) {
  .hero-crow-moon {
    display: none;
  }
}

/* Jack-o-lantern perched at the crow-moon piece's bottom-right corner,
   roughly where the tail feathers sweep down — SEASONAL (data-season in
   the HTML), unlike the crow itself. Reuses the exact same asset/glow as
   every other scatter jack-o-lantern (.pumpkin-glow, pumpkinCandleGlow)
   rather than new art. z-index above the crow-moon piece so it visibly
   sits in front, like it's been set down next to the tail. This one
   STAYS here by the crow — the much bigger plain pumpkin next to the
   hero portrait is .home-pumpkin-scatter (further down in this file),
   the pumpkin already sitting in this same margin resized/repositioned,
   not a new element. */
.hero-crow-moon-pumpkin {
  position: absolute;
  top: 2931px;
  left: 50%;
  transform: translateX(336px);
  z-index: 3;
  width: 100px;
  line-height: 0;
  pointer-events: none;
}

.hero-crow-moon-pumpkin img {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 1300px) {
  .hero-crow-moon-pumpkin {
    display: none;
  }
}

/* Framed rabbit-in-a-hat portrait (Home page) — plain still art, no
   click, no animation, no data-season. Initial placement is a rough
   guess — drag into place with ?edit=1. */
.hero-rabbit-portrait {
  position: absolute;
  top: 2707px;
  left: 50%;
  transform: translateX(373px);
  z-index: 2;
  width: 220px;
  line-height: 0;
}

.hero-rabbit-portrait-img {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 1300px) {
  .hero-rabbit-portrait {
    display: none;
  }
}

/* Bunny jumping on a mushroom (Home page) — two pre-aligned layers,
   mushroom stays put as the base image, only the bunny layer gets the
   jump. Initial placement is a rough guess — drag into place with
   ?edit=1. */
.hero-bunny-mushroom {
  position: absolute;
  top: 1193px;
  left: 50%;
  transform: translateX(-622px);
  z-index: 2;
  width: 252px;
  line-height: 0;
  cursor: pointer;
}

.hero-bunny-mushroom:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.hero-bunny-mushroom-layer {
  display: block;
  width: 100%;
  height: auto;
}

.hero-bunny-mushroom-bunny {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  transform-origin: 50% 100%;
}

/* Two bounces across the jump sound's real 1.776s length — up, down,
   up, down, with a little squash on each landing/takeoff rather than a
   plain linear hop. One-shot, not a loop (see initHeroBunnyMushroom in
   app.js — same remove/reflow/re-add retrigger pattern as the hallway
   wobble). */
.hero-bunny-mushroom.is-jumping .hero-bunny-mushroom-bunny {
  animation: bunnyJump 1.776s ease-in-out;
}

@keyframes bunnyJump {
  0% { transform: translateY(0) scale(1, 1); }
  10% { transform: translateY(0) scale(1.08, 0.9); }
  25% { transform: translateY(-45px) scale(0.95, 1.05); }
  40% { transform: translateY(0) scale(1.08, 0.9); }
  50% { transform: translateY(0) scale(1, 1); }
  60% { transform: translateY(0) scale(1.08, 0.9); }
  75% { transform: translateY(-45px) scale(0.95, 1.05); }
  90% { transform: translateY(0) scale(1.08, 0.9); }
  100% { transform: translateY(0) scale(1, 1); }
}

/* Mushroom reacts to the bunny's weight rather than sitting dead still
   underneath it — same "bounce" idea as the radio scene's jack-o-lantern
   float on click, just applied here as a squash/release paired with the
   bunny's own jump instead of a separate float. Pivot at the base (stem)
   so it reads as compressing into the ground, not swelling from the
   middle. Timed identically to bunnyJump (same 1.776s), but inverted:
   compresses right as the bunny pushes off/lands (weight on the cap),
   springs back while the bunny's airborne (weight released). */
.hero-bunny-mushroom-mushroom {
  transform-origin: 50% 100%;
}

.hero-bunny-mushroom.is-jumping .hero-bunny-mushroom-mushroom {
  animation: mushroomBounce 1.776s ease-in-out;
}

@keyframes mushroomBounce {
  0% { transform: scale(1, 1); }
  10% { transform: scale(1.03, 0.94); }
  25% { transform: scale(0.97, 1.05); }
  40% { transform: scale(1.04, 0.93); }
  50% { transform: scale(1, 1); }
  60% { transform: scale(1.03, 0.94); }
  75% { transform: scale(0.97, 1.05); }
  90% { transform: scale(1.04, 0.93); }
  100% { transform: scale(1, 1); }
}

@media (max-width: 1300px) {
  .hero-bunny-mushroom {
    display: none;
  }
}

/* Two potted plants (About page) — single flat image this time, not a
   pre-aligned layer pair, so there's no separate "leaves" element to
   animate independently. Click sways the whole piece as one unit, same
   scale+rotate flicker recipe as the fireplace flame and the almanac
   bush's ruffle, just stretched across the full rustle clip (4.206s)
   instead of settling early, since there's no second "pop" phase after
   it here — the sway IS the whole animation. */
.about-plants {
  position: absolute;
  /* Dropped down to sit level with the blockquote/signoff/Instagram-button
     area near the bottom of the section, closer to where the page hands
     off to the newsletter signup strip right below it — estimated from
     the section's padding and type stack, not a live render, so may want
     a nudge. */
  top: 309px;
  left: 50%;
  transform: translateX(-521px);
  z-index: 2;
  width: 140px;
  line-height: 0;
  cursor: pointer;
}

.about-plants:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.about-plants-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 96%;
}

.about-plants.is-swaying .about-plants-img {
  animation: aboutPlantsSway 4.206s ease-out;
}

@keyframes aboutPlantsSway {
  0% { transform: scale(1, 1) rotate(0deg); }
  6% { transform: scale(1.03, 0.97) rotate(-3.5deg); }
  14% { transform: scale(0.97, 1.03) rotate(2.8deg); }
  23% { transform: scale(1.02, 0.98) rotate(-2.2deg); }
  33% { transform: scale(0.99, 1.01) rotate(1.7deg); }
  44% { transform: scale(1.01, 0.99) rotate(-1.2deg); }
  56% { transform: scale(1, 1) rotate(0.8deg); }
  69% { transform: scale(1, 1) rotate(-0.5deg); }
  83% { transform: scale(1, 1) rotate(0.25deg); }
  100% { transform: scale(1, 1) rotate(0deg); }
}

@media (max-width: 1300px) {
  .about-plants {
    display: none;
  }
}

/* Jumping cat (About page, right margin) — base pose + a tongue-out
   overlay, pre-aligned same as always. Click toggles the tongue on/off
   several times ("blowing a raspberry") across the full length of the
   sound (2.952s) rather than a single swap, then rests back on the closed
   mouth. Paired close-together percentage stops (e.g. 0%, 9%) hold each
   state solid with only a hair of linear interpolation between them, so
   it snaps rather than crossfades. */
.about-cat {
  position: absolute;
  top: 209px;
  left: 50%;
  transform: translateX(358px);
  z-index: 2;
  width: 188px;
  line-height: 0;
  cursor: pointer;
}

.about-cat:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.about-cat-base {
  display: block;
  width: 100%;
  height: auto;
}

.about-cat-tongue {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
}

.about-cat.is-blowing .about-cat-tongue {
  animation: aboutCatRaspberry 2.952s linear;
}

/* Framed cat-with-yarn illustration (About page) — plain still art, no
   click, no animation, no data-season. Initial placement is a rough
   guess (right margin, below .about-cat) — drag into place with
   ?edit=1. */
.about-cat-frame {
  position: absolute;
  top: 167px;
  left: 50%;
  transform: translateX(-629px);
  z-index: 2;
  width: 127px;
  line-height: 0;
}

.about-cat-frame-img {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 1300px) {
  .about-cat-frame {
    display: none;
  }
}

/* Ornate hanging lantern (About page) — same off/on crossfade-toggle
   recipe as .hearth-lantern/.books-lamp: two full poses on one shared
   canvas, opacity-crossfaded rather than stacked, real on/off toggle
   (not a one-shot animation), same glow-behind-the-art treatment once
   lit. Initial placement is a rough guess — drag into place with
   ?edit=1. */
.about-lantern {
  position: absolute;
  top: 557px;
  left: 50%;
  transform: translateX(408px);
  z-index: 2;
  width: 174px;
  line-height: 0;
  cursor: pointer;
}

/* Market's copy of the same lantern — placeholder position only, drag
   into place with ?edit=1. Everything else (crossfade, glow, is-lit
   state) is shared with the About version via the paired "about-lantern"
   and "market-lantern" prefixed selectors below. Unlike About's instance
   this one IS tagged data-season="halloween" in the HTML, per Eloise's
   ask that this piece disappear after Halloween on the Market page. */
.market-lantern {
  position: absolute;
  top: 923px;
  left: 50%;
  transform: translateX(424px);
  z-index: 2;
  width: 216px;
  line-height: 0;
  cursor: pointer;
}

/* Follow Along's own lamp — same crossfade/glow/is-lit recipe as the
   lanterns above, just its own off/on art (a proper table lamp rather
   than a hanging lantern). Permanent, no data-season, same as the piper
   scene it sits alongside. Rough placeholder position, drag into place
   with ?edit=1. */
.social-lamp {
  position: absolute;
  top: 278px;
  left: 50%;
  transform: translateX(-618px);
  z-index: 2;
  width: 276px;
  line-height: 0;
  cursor: pointer;
}

.about-lantern:focus-visible,
.market-lantern:focus-visible,
.social-lamp:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.about-lantern-off-img,
.market-lantern-off-img,
.social-lamp-off-img {
  display: block;
  width: 100%;
  height: auto;
}

.about-lantern-on-img,
.market-lantern-on-img,
.social-lamp-on-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.18s linear;
}

.about-lantern.is-lit .about-lantern-on-img,
.market-lantern.is-lit .market-lantern-on-img,
.social-lamp.is-lit .social-lamp-on-img {
  opacity: 1;
}

.about-lantern-glow,
.market-lantern-glow,
.social-lamp-glow {
  position: absolute;
  inset: -30%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(224, 169, 64, 0), transparent 70%);
  filter: blur(18px);
  opacity: 0;
  transition: opacity 0.7s ease;
  pointer-events: none;
}

.about-lantern.is-lit .about-lantern-glow,
.market-lantern.is-lit .market-lantern-glow,
.social-lamp.is-lit .social-lamp-glow {
  opacity: 1;
  background: radial-gradient(circle at 50% 50%, rgba(224, 169, 64, 0.55) 0%, rgba(209, 72, 122, 0.22) 45%, transparent 72%);
  animation: glowPulse 2.6s ease-in-out infinite;
}

@media (max-width: 1300px) {
  .about-lantern,
  .market-lantern,
  .social-lamp {
    display: none;
  }
}

/* Hot air balloon (Keepsakes page) — permanent wall decor, no data-season.
   Gentle continuous float, same "always-on ambient movement, no click"
   convention as the flower plant's idle sway. Rough placeholder position,
   drag into place with ?edit=1. */
.market-balloon {
  position: absolute;
  top: 107px;
  left: 50%;
  transform: translateX(-557px);
  z-index: 2;
  width: 224px;
  line-height: 0;
}
.market-balloon img {
  display: block;
  width: 100%;
  height: auto;
  animation: marketBalloonFloat 5.2s ease-in-out infinite;
}
@keyframes marketBalloonFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-16px) rotate(1deg); }
}

/* Bookworm on a mushroom (Keepsakes page) — permanent wall decor, no
   data-season. Ambient blinking eyes, same ravenBlink technique and
   overlay-eyelid approach used site-wide (raven, almanac cat, shop
   bunny...) — two small dot overlays, colored to match the whites of the
   eyes, scale down to nothing then back to simulate a blink. Center
   points measured directly off market-worm.webp. Rough placeholder
   position, drag into place with ?edit=1. */
.market-worm {
  position: absolute;
  top: 1620px;
  left: 50%;
  transform: translateX(406px);
  z-index: 2;
  width: 202px;
  line-height: 0;
}
.market-worm img {
  display: block;
  width: 100%;
  height: auto;
}
/* Re-sized 2026-08-19 (5% -> 8%) — a site-wide blink-coverage audit
   found the old dot only covered the pupil and left a ring of white
   visible around it every blink. */
.market-worm-eyelid {
  position: absolute;
  top: 23.2%;
  width: 8%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: #7cb342;
  border-radius: 50%;
  animation: ravenBlink 6s ease-in-out infinite;
}
.market-worm-eyelid-a { left: 63%; }
.market-worm-eyelid-b { left: 80.6%; animation-delay: 0.08s; }

/* Lemon tree (Keepsakes page) — permanent wall decor, no data-season,
   plain still piece (no click, no animation), same as the About page's
   cat-with-yarn frame. Rough placeholder position, drag into place with
   ?edit=1. */
.market-lemontree {
  position: absolute;
  top: 875px;
  left: 50%;
  transform: translateX(-638px);
  z-index: 2;
  width: 252px;
  line-height: 0;
}
.market-lemontree img {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 1300px) {
  .market-balloon,
  .market-worm,
  .market-lemontree {
    display: none;
  }
}

/* Two candles (Quiet Hollow page) — pre-aligned pair on one shared canvas
   (same union-bbox-crop convention as every other layered piece — never
   crop/reposition the two independently). Base layer (candles, unlit) is
   always visible; the flame layer sits on top via inset:0 and starts at
   opacity:0. Unlike the crossfade lanterns/lamps elsewhere, lighting these
   reuses the exact fireFlicker + glowPulse recipe from the fireplace
   itself (per Eloise: "burn like the fireplace does and illuminate the
   environment") rather than a second full "on" pose — there isn't one,
   just the one small flame layer that needs to flicker and glow. Real
   on/off toggle (not one-shot), same click-sound convention as every
   other toggle-lit piece on the site. Initial placement is a rough
   guess — drag into place with ?edit=1. */
.selflove-candles {
  position: absolute;
  top: 399px;
  left: 50%;
  transform: translateX(-621px);
  z-index: 2;
  width: 218px;
  line-height: 0;
  cursor: pointer;
}

.selflove-candles:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.selflove-candles-base-img {
  display: block;
  width: 100%;
  height: auto;
}

.selflove-candles-flame-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transform-origin: 50% 30%;
  transition: opacity 0.5s ease;
}

.selflove-candles.is-lit .selflove-candles-flame-img {
  opacity: 1;
  animation: fireFlicker 1.8s ease-in-out infinite;
}

.selflove-candles-glow {
  position: absolute;
  inset: -30%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 40%, rgba(224, 169, 64, 0), transparent 70%);
  filter: blur(20px);
  opacity: 0;
  transition: opacity 0.7s ease;
  pointer-events: none;
}

.selflove-candles.is-lit .selflove-candles-glow {
  opacity: 1;
  background: radial-gradient(circle at 50% 40%, rgba(224, 169, 64, 0.55) 0%, rgba(209, 72, 122, 0.22) 45%, transparent 72%);
  animation: glowPulse 2.6s ease-in-out infinite;
}

@media (max-width: 1300px) {
  .selflove-candles {
    display: none;
  }
}

/* ---- Quiet Hollow scene (rocks, mushrooms, log) — one pre-aligned
   three-layer scene sharing a single position/width, same pattern as
   .hero-bunny-mushroom and .market-lantern: ONE outer wrapper carries the
   real page placement, and every layer inside is inset:0 so it lines up
   exactly with the others rather than sitting off on its own. Permanent,
   no data-season. Rough placeholder position — drag the whole scene into
   place with ?edit=1 (only the outer .quiethollow-scene is listed in
   EDITABLE_DECOR; the three layers move together as one unit).

   Log and mushrooms are both interactive but sit in the exact same
   inset:0 box as each other (that's the whole point of stacking them),
   so plain CSS hit-testing can't tell which one you meant to click —
   whichever has the higher z-index would silently swallow every click
   meant for the other, same bug rocks had before pointer-events:none was
   added to it. All three art layers get pointer-events:none here; the
   wrapper itself owns the one real click listener and figures out which
   layer you actually clicked by sampling each source image's alpha
   channel at that exact point (see initQuietHollowScene in app.js).
   Keyboard access (Tab + Enter/Space) still targets the log and
   mushrooms directly, since pointer-events:none only blocks mouse
   hit-testing, not focus. ---- */
.quiethollow-scene {
  position: absolute;
  top: 1208px;
  left: 50%;
  transform: translateX(-667px);
  z-index: 2;
  width: 284px;
  line-height: 0;
  cursor: pointer;
}

.quiethollow-scene-layer {
  display: block;
  width: 100%;
  height: auto;
}

/* Mushrooms at the back, log in the middle, rocks up front — per
   Eloise's ask. The log stays the position:relative element (it's the
   one that establishes the wrapper's height); z-index alone controls the
   stacking order, independent of which layer is in normal flow. */
.quiethollow-mushrooms {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.quiethollow-log {
  position: relative;
  z-index: 2;
  pointer-events: none;
}

.quiethollow-rocks {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.quiethollow-log:focus-visible,
.quiethollow-mushrooms:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

/* Rocks — no click, just a slow ambient "breathing" scale. The animation
   lives on the img itself, not the positioned layer div, so it never
   fights the inset:0 placement that keeps it aligned with the other two
   layers. */
.quiethollow-rocks .quiethollow-scene-layer {
  transform-origin: 50% 60%;
  animation: rocksBreathe 6s ease-in-out infinite;
}

@keyframes rocksBreathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.035); }
}

/* Mushrooms — click plays the exact same sound + squash-bounce as the
   mushroom the bunny lands on (Home page): same hero-bunny-jump.mp3,
   same mushroomBounce keyframes/timing (see @keyframes mushroomBounce
   above, shared by both pieces), just triggered directly instead of
   paired with a separate bunny layer. See initQuietHollowScene in
   app.js — same remove/reflow/re-add retrigger pattern as that piece. */
.quiethollow-mushrooms .quiethollow-scene-layer {
  transform-origin: 50% 100%;
}

.quiethollow-mushrooms.is-bouncing .quiethollow-scene-layer {
  animation: mushroomBounce 1.776s ease-in-out;
}

/* Log — click toggles the exact same fireFlicker + glowPulse recipe the
   fireplace/candles/lantern use, plus the same chime as the header logo
   lady (#witchChime / witch-chime.mp3, see initFairyDust in app.js) and a
   gold glitter burst (see spawnGlitterBurst in app.js — same speck-
   generation technique as the ember burst below, just gold and splashing
   outward in every direction instead of only rising). There's no separate
   flame-only image here (unlike the fireplace/candles), so fireFlicker
   runs on the whole log image itself — it already has a glowing ember
   crack baked into the art, so the flicker just brings that to life. */
.quiethollow-log.is-lit .quiethollow-scene-layer {
  animation: fireFlicker 1.8s ease-in-out infinite;
}

.quiethollow-log-glow {
  position: absolute;
  inset: -30%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 55%, rgba(224, 169, 64, 0), transparent 70%);
  filter: blur(22px);
  opacity: 0;
  transition: opacity 0.7s ease;
  pointer-events: none;
}

.quiethollow-log.is-lit .quiethollow-log-glow {
  opacity: 1;
  background: radial-gradient(circle at 50% 55%, rgba(224, 169, 64, 0.55) 0%, rgba(209, 72, 122, 0.22) 45%, transparent 72%);
  animation: glowPulse 2.6s ease-in-out infinite;
}

@media (max-width: 1300px) {
  .quiethollow-scene {
    display: none;
  }
}

/* Harp — same on/off toggle + fireFlicker "on" animation as the radios
   elsewhere (see .books-radio.is-playing above), just a two-stage tuning
   → song chain instead of switch → tuning → song, since there's no
   separate switch-click sound for this one (see initQuietHollowHarp in
   app.js). Permanent, no data-season. Rough placeholder position — drag
   into place with ?edit=1. */
.quiethollow-harp {
  position: absolute;
  top: 96px;
  left: 50%;
  transform: translateX(423px);
  z-index: 2;
  width: 236px;
  line-height: 0;
  cursor: pointer;
}

.quiethollow-harp:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.quiethollow-harp-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 68%;
}

.quiethollow-harp.is-playing .quiethollow-harp-img {
  animation: fireFlicker 1.8s ease-in-out infinite;
}

@media (max-width: 1300px) {
  .quiethollow-harp {
    display: none;
  }
}

/* ---- Follow Along piper scene — three pre-aligned layers on one shared
   canvas, same pattern as .quiethollow-scene: the piper (with its own
   walking bunny baked into that art) is the non-interactive back layer,
   and the two small bunnies sit on top, each independently clickable.
   Since all three layers share the exact same inset:0 box, plain CSS
   click listeners can't tell the bunnies apart from each other or from
   the piper — same box-vs-pixel problem the Quiet Hollow scene had. All
   three art layers get pointer-events:none here; the wrapper owns the
   one real click listener and samples each bunny's source image alpha
   channel to figure out which one you clicked (see initSocialPiperScene
   in app.js). Keyboard access (Tab + Enter/Space) still targets each
   bunny directly. Permanent, no data-season. Rough placeholder position —
   drag into place with ?edit=1. ---- */
.social-piper-scene {
  position: absolute;
  top: 113px;
  left: 50%;
  transform: translateX(318px);
  z-index: 2;
  width: 290px;
  line-height: 0;
  cursor: pointer;
}

.social-scene-layer {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 100%;
}

.social-piper {
  position: relative;
  z-index: 1;
  pointer-events: none;
}

.social-bunny-a,
.social-bunny-b {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.social-bunny-a { z-index: 2; }
.social-bunny-b { z-index: 3; }

.social-bunny-a:focus-visible,
.social-bunny-b:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

/* Same bunnyJump keyframes as the Home hero's bunny-on-a-mushroom piece
   above (two hops timed to hero-bunny-jump.mp3's real 1.776s length) —
   reused as-is rather than redefined. */
.social-bunny-a.is-jumping .social-scene-layer,
.social-bunny-b.is-jumping .social-scene-layer {
  animation: bunnyJump 1.776s ease-in-out;
}

@media (max-width: 1300px) {
  .social-piper-scene {
    display: none;
  }
}

@keyframes aboutCatRaspberry {
  0%, 9% { opacity: 0; }
  10%, 19% { opacity: 1; }
  20%, 29% { opacity: 0; }
  30%, 39% { opacity: 1; }
  40%, 49% { opacity: 0; }
  50%, 59% { opacity: 1; }
  60%, 69% { opacity: 0; }
  70%, 79% { opacity: 1; }
  80%, 89% { opacity: 0; }
  90%, 99% { opacity: 1; }
  100% { opacity: 0; }
}


/* Cottage couch corner — replaces the kitten+yarn in the hallway margin.
   3 source images share one canvas (Eloise's usual layering convention):
   the couch+4-paintings scene is used at full size as the base, while the
   cottage painting and the plant are each cropped tight to their own alpha
   bbox and positioned by percentage (computed from that shared canvas) —
   that tight crop is what gives each one a click target scoped to just
   itself, rather than one hit zone covering the whole piece. */
.books-cottage {
  position: absolute;
  top: 100px;
  left: 50%;
  transform: translateX(calc(-420px - 100%));
  z-index: 2;
  width: 260px;
  line-height: 0;
}

.books-cottage-scene-img {
  position: relative;
  z-index: 2;
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 92%;
  /* Was the same idle-sway recipe as the newsletter easel, but Eloise found
     it read as bouncing rather than breathing — its own slower, rotation-
     free keyframe now (see booksCottageBreathe below), just a gentle scale
     pulse, not shared with the easel elsewhere. */
  animation: booksCottageBreathe 9s ease-in-out infinite;
  /* This image's own box spans the WHOLE card (it's the full-size base
     layer), not just the sliver where it visually overlaps the plant —
     without this, sitting above the painting/plant in z-index (needed so
     the couch's edge occludes that overlap sliver) meant it was silently
     eating every click across the entire piece, painting and plant
     included, even though most of its pixels are transparent there.
     pointer-events: none lets clicks fall through to whatever's actually
     underneath at that point. */
  pointer-events: none;
}

.books-cottage-painting {
  position: absolute;
  left: 1.222%;
  top: 3.042%;
  width: 49.111%;
  z-index: 1;
  line-height: 0;
  cursor: pointer;
}

.books-cottage-painting:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 4px;
  border-radius: 6px;
}

.books-cottage-painting-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 6%;
}

/* Same wobble family as the hallway gallery wall, timed to the same
   20.832s knock recording since it reuses that exact sound file. */
.books-cottage-painting.is-dangling .books-cottage-painting-img {
  animation: booksCottagePaintingDangle 20.832s cubic-bezier(0.37, 0.15, 0.24, 0.94);
}

@keyframes booksCottagePaintingDangle {
  0% { transform: rotate(0deg); }
  2.9% { transform: rotate(-13deg); }
  17.8% { transform: rotate(9deg); }
  32.2% { transform: rotate(-7deg); }
  44.6% { transform: rotate(5.5deg); }
  58.6% { transform: rotate(-6deg); }
  73.4% { transform: rotate(4deg); }
  84% { transform: rotate(-2.5deg); }
  93.6% { transform: rotate(1.2deg); }
  100% { transform: rotate(0deg); }
}

.books-cottage-plant {
  position: absolute;
  left: 1%;
  top: 59.078%;
  width: 28%;
  z-index: 1;
  line-height: 0;
  cursor: pointer;
}

.books-cottage-plant:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 4px;
  border-radius: 6px;
}

.books-cottage-plant-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 96%;
}

/* Same fireFlicker recipe as the fireplace/gramophone, but a single pass
   timed to the rustle clip (2.976s) rather than infinite while lit. */
.books-cottage-plant.is-rustling .books-cottage-plant-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out;
}

@keyframes booksCottagePlantFlicker {
  0%, 100% { transform: scale(1) rotate(0deg); }
  25% { transform: scale(1.04, 0.97) rotate(-1.5deg); }
  50% { transform: scale(0.97, 1.03) rotate(1.2deg); }
  75% { transform: scale(1.02, 0.98) rotate(-0.8deg); }
}

/* Treasure chest — right margin, lower than the kitten so the two don't
   crowd each other, roughly level with the book grid. Closed/open are two
   full poses of the same chest (crossfade via opacity, not a layer stack)
   plus a key pre-aligned by Eloise to float at the lock, same canvas
   convention as everything else. Unlike the other hallway pieces this one
   is a one-time, permanent unlock rather than a replayable peek — once
   opened it stays open and reveals the claim link, so the class names
   below track state (is-unlocking → is-open → is-claimable) instead of
   just replaying an animation. */
.books-chest {
  position: absolute;
  top: 300px;
  left: 50%;
  transform: translateX(420px);
  z-index: 2;
  width: 240px;
  text-align: center;
  cursor: pointer;
}

.books-chest:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.books-chest.is-open {
  cursor: default;
}

/* All three images were cropped to the exact same box and resized
   identically, so they only stay pixel-aligned if all three fill the
   container the same way. An explicit aspect-ratio on the wrapper plus
   inset:0 + width/height:100% on every layer does that cleanly — the
   earlier version had .books-chest-closed sized by normal-flow
   (width:100%;height:auto) while .books-chest-open/-key were absolutely
   positioned with inset:0 AND height:auto, which is an over-constrained
   box that some browsers resolve by stretching the image off its own
   aspect ratio instead of the container's. That's almost certainly what
   was reading as "off." */
.books-chest-art {
  /* Must be block — this is a <span>, and once its 3 children below all
     become position:absolute (removed from flow) an inline box has no
     content left to size itself by, so aspect-ratio has nothing to apply
     to and the whole thing collapses to zero size. That's why the art was
     invisible but the (block-level, because position:absolute forces that)
     .books-chest wrapper around it still worked as a click target. */
  display: block;
  position: relative;
  aspect-ratio: 320 / 294;
  line-height: 0;
}

.books-chest-closed,
.books-chest-open,
.books-chest-key {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* The clip-path wipe fixed the timing theory but looked bad — a hard edge
   scanning down through a full illustrated scene reads as a glitch, not a
   lid opening. The smooth crossfade after that never fully landed either
   ("looked finished before it's done", no matter the easing). Replaced
   with the same stop-motion snap the newsletter bird already uses —
   app.js toggles .is-flicker-closed on/off every 180ms for the full 5.6s,
   then settles on open. No transition here at all; JS drives every beat,
   same reasoning as the bird's comment above it. */
.books-chest-closed,
.books-chest-open {
  transition: none;
}

.books-chest-closed {
  z-index: 0;
}

.books-chest-open {
  z-index: 1;
  opacity: 0;
}

.books-chest.is-open .books-chest-closed {
  opacity: 0;
}

.books-chest.is-open .books-chest-open {
  opacity: 1;
}

/* Mid-flicker frame — briefly swaps back to closed during the snap
   sequence. Only ever toggled on top of .is-open, so it always ends up
   back on the open pose once app.js clears the interval. */
.books-chest.is-open.is-flicker-closed .books-chest-closed {
  opacity: 1;
}

.books-chest.is-open.is-flicker-closed .books-chest-open {
  opacity: 0;
}

.books-chest-key {
  z-index: 2;
  opacity: 0;
  transform: scale(0.7) rotate(-25deg);
  transform-origin: 77.4% 63.2%;
}

/* Slowed a little past the lock-and-key sound's own length (0.936s) on
   purpose — the key now finishes its turn a beat after the click sound
   stops, rather than snapping to rest right on top of it. */
.books-chest.is-unlocking .books-chest-key {
  animation: chestKeyUnlock 1.6s ease-out forwards;
}

@keyframes chestKeyUnlock {
  0% { opacity: 0; transform: scale(0.7) rotate(-25deg); }
  35% { opacity: 1; transform: scale(1.05) rotate(8deg); }
  55% { transform: scale(1) rotate(-16deg); }
  75% { transform: scale(1) rotate(5deg); }
  90% { opacity: 1; transform: scale(1) rotate(0deg); }
  100% { opacity: 0; transform: scale(1) rotate(0deg); }
}

.books-chest-claim {
  display: block;
  margin-top: 14px;
  font-style: italic;
  font-size: 0.95rem;
  color: var(--gold);
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.books-chest.is-claimable .books-chest-claim {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* Floor lamp — left margin, immediately left of the radio. Same off/on
   crossfade recipe as .hearth-lantern (two full poses, same canvas,
   opacity transition, real persistent toggle not a one-shot animation).
   Top/right-edge offset below are computed from the radio's own box
   (130px wide, ~132.9px tall at that width, top:980px) so the two bases
   line up exactly: lamp bottom = radio bottom (980 + 132.9 ≈ 1112.9px),
   lamp top = that minus the lamp's own height at 100px wide (≈357px) ≈
   756px. Horizontally, translateX(-564px - 100%) puts the lamp's right
   edge ~14px left of the radio's left edge (radio's left edge sits at
   center-550px, translateX(-420px-100%) + its own 130px width). Still an
   estimate built from CSS math rather than a live render, so may want a
   nudge once seen alongside the actual rendered radio. */
.books-lamp {
  position: absolute;
  top: 753px;
  left: 50%;
  transform: translateX(-610px);
  z-index: 2;
  width: 100px;
  line-height: 0;
  cursor: pointer;
}

.books-lamp:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.books-lamp-off-img {
  display: block;
  width: 100%;
  height: auto;
}

.books-lamp-on-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.18s linear;
}

.books-lamp.is-lit .books-lamp-on-img {
  opacity: 1;
}

/* Same fireplace-glow recipe, but this piece is a tall thin floor lamp
   rather than a compact icon, so inset:-30% on the whole box would center
   the glow on the pole instead of the shade. Located the shade's actual
   glowing pixels the same way the cushion cat's pupils were found (bright/
   warm pixel cluster in the "on" image) — center sits near the top of the
   box, not the middle, so this uses a positioned circle instead of inset. */
.books-lamp-glow {
  position: absolute;
  left: 48.5%;
  top: 19.9%;
  width: 220%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(224, 169, 64, 0), transparent 70%);
  filter: blur(18px);
  opacity: 0;
  transition: opacity 0.7s ease;
  pointer-events: none;
}

.books-lamp.is-lit .books-lamp-glow {
  opacity: 1;
  background: radial-gradient(circle, rgba(224, 169, 64, 0.55) 0%, rgba(209, 72, 122, 0.22) 45%, transparent 72%);
  animation: glowPulse 2.6s ease-in-out infinite;
}

/* Radio — left margin, dropped further down (Eloise: "next to the bottom
   of the tales of the valley tile") from its first estimate of 760px.
   Still an estimate, not a live render — the grid's actual height depends
   on rendered card content, so this may need another nudge. Same
   fireFlicker recipe as the fireplace/gramophone/hero radio tile, just
   standing alone as a wall piece rather than living inside a tile card. */
.books-radio {
  position: absolute;
  top: 980px;
  left: 50%;
  transform: translateX(-532px);
  z-index: 2;
  width: 130px;
  line-height: 0;
  cursor: pointer;
}

.books-radio:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.books-radio-img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(6, 4, 20, 0.5));
  transform-origin: 50% 68%;
}

.books-radio.is-playing .books-radio-img {
  animation: fireFlicker 1.8s ease-in-out infinite;
}

/* Bunny in a top hat — right margin, positioned next to the bottom of the
   "Good Old Times" card. The grid is 2 columns (repeat(2, 1fr)) and that
   card is the 4th published item, so it's row 2 / right column — worked
   out roughly where row 2 ends using the same card-height math (cover
   aspect-ratio 0.7 inside a ~366px column, plus tag/title/blurb/button)
   that .books-radio's own live-nudged 980px (row 1's bottom) was checked
   against. Still an estimate, not a live render, so likely needs a nudge. */
.shop-bunny {
  position: absolute;
  top: 812px;
  left: 50%;
  transform: translateX(434px);
  z-index: 2;
  width: 194px;
  line-height: 0;
}

/* display:block is required — see the .hero-cushion-cat-scene comment
   above for why an inline <span> wrapping block-level image layers is a
   real bug, not a style nicety. */
.shop-bunny-scene {
  display: block;
  position: relative;
  width: 100%;
}

.shop-bunny-img {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
}

/* Ambient, continuous blink — same ravenBlink technique as every other
   blinking piece, repositioned to this bunny's two pupils and recolored
   to the warm tan fur right around its eyes (sampled from the art). This
   is the only interaction left on this piece — the nose-wiggle overlay
   was removed, it left a visible soft shadow around the nose on this
   lighter-furred bunny. */
.shop-bunny-eyelid {
  position: absolute;
  width: 2.6%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #ffbb80 55%, #ff9a56 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}

.shop-bunny-eyelid-l { left: 41.25%; top: 38.12%; }
.shop-bunny-eyelid-r { left: 64.17%; top: 35.87%; }

/* Below this width, half the viewport minus the content column no longer
   leaves enough room in the margin for a 190px piece plus a comfortable
   gap — same reasoning as the raven/kitten hide-on-mobile before this,
   just a wider threshold since these live further out in the gutter. */
@media (max-width: 1300px) {
  .books-cottage,
  .books-radio,
  .books-lamp,
  .shop-bunny {
    display: none;
  }
}

/* ---------- Storyteller / Hearth ---------- */
/* Redesigned (2026-07-31) from one flat card into 3 distinct zones — intro,
   quote, ritual — separated by the site's existing divider-flourish image
   rather than plain rules, so it reads as a sequence of small moments
   instead of one form. See isole.md for the full reasoning. */

.hearth-altar {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.hearth-zone {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--hearth), var(--card));
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 36px;
  text-align: left;
  width: 100%;
}

.divider-flourish-sm {
  height: 16px;
  max-width: 140px;
  margin: 28px auto;
  opacity: 0.6;
}

/* Subtle paper-grain texture, applied to any card via the .grain class — a
   tiled noise photo blended over the card so it feels like textured paper
   rather than a flat digital gradient. (Previous version used a live SVG
   feTurbulence filter, but at a visible opacity it was too fine-grained to
   actually read as texture — replaced with a pre-rendered noise tile, which
   is both more visible and lighter to render.) */
.grain::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  border-radius: inherit;
  opacity: 0.28;
  mix-blend-mode: overlay;
  background-image: url("data:image/png;base64,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");
  background-repeat: repeat;
  background-size: 150px 150px;
}

.hearth-zone > * { position: relative; z-index: 1; }

/* Zone 1 — who she is */
.hearth-zone-intro {
  text-align: center;
  padding: 32px 36px 28px;
}

/* Book + quill avatar (Eloise's own art) — same two-layer technique as
   .fireplace-art: a static base image (the open book) and an animated
   layer on an identical canvas (the quill) stacked directly on top, so
   they land pre-aligned exactly as painted rather than needing manual
   positioning. Sized up from the original 150px (matching .fireplace-art-lg)
   to 230px so it fills more of the intro card. */
.hearth-book-art {
  position: relative;
  display: block;
  line-height: 0;
  width: 230px;
  margin: 0 auto 20px;
  cursor: pointer;
}

.hearth-book-art:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 4px;
  border-radius: 8px;
}

.hearth-book-pic,
.hearth-quill-pic {
  display: block;
  width: 100%;
  height: auto;
}

.hearth-quill-pic {
  position: absolute;
  inset: 0;
  transform-origin: 32% 78%; /* roughly where the nib meets the page */
  animation: quillWrite 3.4s ease-in-out infinite;
}

@keyframes quillWrite {
  0%, 100% { transform: rotate(0deg) translateX(0); }
  25% { transform: rotate(-4.5deg) translateX(-3px); }
  50% { transform: rotate(3.5deg) translateX(3px); }
  75% { transform: rotate(-2.5deg) translateX(-2px); }
}

.hearth-zone-intro h3 {
  font-size: 1.5rem;
  margin: 0;
}

.hearth-role {
  color: var(--gold);
  font-size: 0.9rem;
  letter-spacing: 0.04em;
  margin: 6px 0 0;
}

/* Zone 2 — the quote, given real room instead of being buried mid-card */
.hearth-zone-quote {
  text-align: center;
  padding: 40px 44px;
}

.hearth-zone-quote blockquote {
  font-style: italic;
  color: var(--parchment);
  font-size: 1.15rem;
  line-height: 1.65;
  margin: 0 auto;
  max-width: 520px;
}

.hearth-zone-quote blockquote::before {
  content: "✦";
  display: block;
  color: var(--gold);
  font-size: 0.85rem;
  opacity: 0.7;
  margin: 0 0 16px;
}

/* Raven flanking the quote — an animated ornament sized proportionate to
   the text rather than competing with it. (A cauldron-over-fire piece was
   tried on the other side too but pulled — Eloise felt it read as chaotic
   and wants to try different firepit art before it comes back. See
   isole.md.) */
.hearth-quote-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 26px;
}

.hearth-quote-row blockquote {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 400px;
}

.hearth-quote-raven {
  position: relative;
  flex: 0 0 auto;
  width: 190px;
  line-height: 0;
}

.hearth-quote-raven img {
  display: block;
  width: 100%;
  height: auto;
}

.hearth-raven-eyelid {
  position: absolute;
  left: 67.48%;
  top: 28.41%;
  width: 4.6%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #16255a 55%, #0d1b3e 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}

@keyframes ravenBlink {
  0%, 92% { transform: translate(-50%, -50%) scaleY(0); }
  94% { transform: translate(-50%, -50%) scaleY(1); }
  96% { transform: translate(-50%, -50%) scaleY(0); }
  97.5% { transform: translate(-50%, -50%) scaleY(1); }
  99%, 100% { transform: translate(-50%, -50%) scaleY(0); }
}

@media (max-width: 760px) {
  .hearth-quote-raven {
    display: none;
  }

  .hearth-quote-row blockquote {
    max-width: 520px;
  }
}

/* Zone 3 — the actual ritual: whisper a theme, or let the fire decide.
   Matches the quote tile's padding above it (40px 44px) so the two cards
   read as the same size rather than the ritual card looking slightly
   tighter/smaller with the base .hearth-zone padding. */
.hearth-zone-ritual {
  padding: 40px 44px;
}

.theme-label {
  display: block;
  font-size: 0.95rem;
  font-style: italic;
  color: var(--parchment-dim);
  text-align: center;
  margin: 0 0 16px;
}

.theme-input-wrap {
  position: relative;
  margin-bottom: 16px;
}

/* Theme suggestion chips — a mix of evergreen book-themed prompts and one
   seasonal chip whose label/theme swaps on the same dates as the site's
   themed graphics (see initThemeChips() in app.js). Clicking a chip just
   fills the theme input; it doesn't submit the form. */
.theme-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: -6px 0 16px;
}

.theme-chip {
  background: rgba(232, 196, 104, 0.05);
  border: 1px solid rgba(232, 196, 104, 0.28);
  border-radius: 999px;
  padding: 6px 14px;
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--parchment-dim);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.theme-chip:hover,
.theme-chip:focus-visible {
  background: rgba(232, 196, 104, 0.12);
  border-color: var(--gold);
  color: var(--gold);
}

.theme-chip-seasonal {
  border-style: dashed;
}

.theme-chip.is-selected {
  background: rgba(232, 196, 104, 0.22);
  border-color: var(--gold);
  color: var(--gold);
}

.theme-chip-hint {
  margin: -10px 0 14px;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--parchment-dim);
  opacity: 0.85;
}

#taleForm input[type="text"] {
  position: relative;
  z-index: 1;
  width: 100%;
  background: var(--night);
  border: 1px solid rgba(232, 196, 104, 0.4);
  color: var(--parchment);
  padding: 15px 42px 15px 18px;
  border-radius: 12px;
  font-family: inherit;
  font-size: 1.02rem;
  box-shadow: inset 0 0 0 1px rgba(232, 196, 104, 0.06);
  transition: box-shadow 0.25s ease, border-color 0.25s ease;
}

#taleForm input[type="text"]:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: inset 0 0 0 1px rgba(232, 196, 104, 0.14), 0 0 18px rgba(232, 196, 104, 0.22);
}

.theme-input-sparkle {
  position: absolute;
  top: 50%;
  right: 16px;
  z-index: 1;
  transform: translateY(-50%);
  color: var(--gold);
  font-size: 0.9rem;
  opacity: 0.5;
  pointer-events: none;
  animation: sparkleTwinkle 3.6s ease-in-out infinite;
}

@keyframes sparkleTwinkle {
  0%, 100% { opacity: 0.22; transform: translateY(-50%) scale(0.85); }
  50% { opacity: 0.7; transform: translateY(-50%) scale(1.05); }
}

/* Smoke wisps rising from the input, behind it — the "whisper into the
   smoke" line made literal. Deliberately faint and slow, matching the same
   restraint as the idle hint sparks / hearth warmth elsewhere on the site
   — a detail you'd only notice if you paused to look, not a flourish. */
.theme-smoke {
  position: absolute;
  left: 20px;
  bottom: 100%;
  width: 60px;
  height: 42px;
  pointer-events: none;
  z-index: 0;
}

.smoke-wisp {
  position: absolute;
  bottom: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(232, 214, 196, 0.18), transparent 70%);
  filter: blur(3px);
  opacity: 0;
  animation: smokeRise 7s ease-in infinite;
}
.smoke-wisp-1 { left: 4px; animation-delay: 0s; }
.smoke-wisp-2 { left: 24px; animation-delay: 2.3s; }
.smoke-wisp-3 { left: 42px; animation-delay: 4.6s; }

@keyframes smokeRise {
  0% { opacity: 0; transform: translateY(0) scale(0.6); }
  18% { opacity: 0.45; }
  80% { opacity: 0; }
  100% { opacity: 0; transform: translateY(-40px) scale(1.3); }
}

.quick-draw-hint {
  margin: 0 0 8px;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--parchment-dim);
  opacity: 0.85;
}

.quick-draw-row {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
}

.quick-draw-btn {
  flex: 1 1 0;
  background: rgba(232, 196, 104, 0.06);
  border: 1px solid rgba(232, 196, 104, 0.25);
  border-radius: 999px;
  padding: 9px 14px;
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--parchment-dim);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.quick-draw-btn:hover {
  background: rgba(232, 196, 104, 0.12);
  color: var(--gold);
  border-color: rgba(232, 196, 104, 0.4);
}

.fire-decide-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  background: rgba(232, 196, 104, 0.06);
  border: 1px solid rgba(232, 196, 104, 0.35);
  border-radius: 999px;
  padding: 12px 20px;
  font-family: inherit;
  font-style: italic;
  font-size: 0.95rem;
  color: var(--gold);
  cursor: pointer;
  margin-bottom: 14px;
  transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

.fire-decide-btn:hover {
  background: rgba(232, 196, 104, 0.12);
  box-shadow: 0 0 20px rgba(232, 196, 104, 0.2);
  transform: translateY(-1px);
}

.fire-decide-icon {
  display: inline-block;
  transition: transform 0.2s ease;
}
.fire-decide-btn:hover .fire-decide-icon { transform: rotate(15deg) scale(1.15); }

/* Ember burst on click — spawned by initFireDecide() in app.js, same
   speck-generation technique as the fairy dust / idle hint sparks. */
.ember-speck {
  position: fixed;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--ember);
  box-shadow: 0 0 6px 2px rgba(224, 112, 60, 0.6);
  pointer-events: none;
  z-index: 50;
  animation: emberRise 900ms ease-out forwards;
}

@keyframes emberRise {
  0% { opacity: 1; transform: translate(0, 0) scale(1); }
  100% { opacity: 0; transform: translate(var(--ember-x, 0px), -60px) scale(0.4); }
}

/* Gold glitter burst on click — spawned by spawnGlitterBurst() in app.js
   (Quiet Hollow log). Same speck-generation technique as the ember burst
   above, but specks splash outward in every direction with a spin rather
   than just drifting up, since this one is meant to read as a scatter/
   splash of glitter rather than embers rising off a fire. */
.glitter-speck {
  position: fixed;
  width: 5px;
  height: 5px;
  border-radius: 2px;
  background: var(--gold);
  box-shadow: 0 0 6px 2px rgba(232, 196, 104, 0.7);
  pointer-events: none;
  z-index: 50;
  animation: glitterSplash 850ms ease-out forwards;
}

@keyframes glitterSplash {
  0% { opacity: 1; transform: translate(0, 0) rotate(0deg) scale(1); }
  100% {
    opacity: 0;
    transform: translate(var(--glitter-x, 0px), var(--glitter-y, -40px)) rotate(var(--glitter-r, 180deg)) scale(0.4);
  }
}

#taleForm button[type="submit"] { width: 100%; }

.loading {
  text-align: center;
  padding: 32px 0 0;
  color: var(--parchment-dim);
  font-style: italic;
}

.spinner {
  width: 32px;
  height: 32px;
  margin: 0 auto 14px;
  border: 3px solid var(--border);
  border-top-color: var(--magenta);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

.hidden { display: none !important; }

.tale-result {
  margin-top: 32px;
  padding-top: 28px;
  border-top: 1px solid var(--border);
}

.tale-result h4 {
  font-size: 1.4rem;
  font-style: italic;
  margin-bottom: 14px;
}

.tale-result p {
  white-space: pre-wrap;
  color: var(--parchment-dim);
  font-size: 1.05rem;
}

.match-callout {
  margin-top: 28px;
  padding: 24px;
  background: rgba(209, 72, 122, 0.1);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.match-label {
  text-transform: uppercase;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--gold);
  font-weight: 600;
  margin: 0 0 12px;
}

.match-details h4 { font-size: 1.2rem; margin-bottom: 8px; }
.match-details p { margin: 0 0 8px; color: var(--parchment-dim); font-size: 0.96rem; }
.match-reason { font-style: italic; }

.error {
  margin-top: 24px;
  background: rgba(180, 60, 40, 0.12);
  border: 1px solid #a35a34;
  color: #e2b09a;
  padding: 14px 18px;
  border-radius: 8px;
  font-size: 0.92rem;
}

/* ---------- Almanac (moon phase + celestial events) ---------- */
.almanac {
  max-width: 900px;
}

.almanac-grid {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 28px;
  text-align: left;
  align-items: start;
}

.moon-card,
.events-card {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--hearth), var(--card));
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 32px 26px;
}

.moon-card > *,
.events-card > * { position: relative; z-index: 1; }

.moon-card {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.moon-cycle-portrait {
  width: 100%;
  max-width: 210px;
  border-radius: 14px;
  margin-bottom: 18px;
  border: 1px solid rgba(232, 196, 104, 0.35);
  box-shadow: 0 12px 28px rgba(6, 4, 20, 0.45), 0 0 24px rgba(224, 169, 64, 0.18);
}

.moon-phase-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-bottom: 10px;
}

.moon-icon {
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  filter: drop-shadow(0 0 12px rgba(232, 196, 104, 0.35));
}

.moon-card h3 {
  font-size: 1.2rem;
  font-style: italic;
  margin: 0;
}

.moon-blurb {
  color: var(--parchment-dim);
  font-size: 0.95rem;
  margin: 0;
}

.events-card h3 {
  font-size: 1.15rem;
  font-style: italic;
  margin: 0 0 20px;
  text-align: center;
}

.events-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.event-item {
  display: flex;
  gap: 14px;
  padding-left: 14px;
  border-left: 2px solid var(--event-color, var(--gold));
}

.event-date {
  flex: 0 0 auto;
  min-width: 64px;
  color: var(--event-color, var(--gold));
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding-top: 2px;
}

.event-title {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.08rem;
  margin: 0 0 4px;
}

.event-blurb {
  color: var(--parchment-dim);
  font-size: 0.92rem;
  margin: 0;
}

.events-empty {
  color: var(--parchment-dim);
  font-style: italic;
  text-align: center;
}

.almanac-subscribe {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  text-align: center;
}

.almanac-subscribe-copy {
  color: var(--parchment-dim);
  font-size: 0.86rem;
  margin: 0 0 14px;
}

.almanac-subscribe-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}

.almanac-copy-message {
  margin: 10px 0 0;
  font-size: 0.82rem;
  color: var(--gold);
  word-break: break-all;
}

@media (max-width: 700px) {
  .almanac-grid { grid-template-columns: 1fr; }
}

/* ---------- Self Love ---------- */

.bath-generator {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--hearth), var(--card));
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 36px 30px;
  margin: 0 0 56px;
}

.bath-generator > * { position: relative; z-index: 1; }

.bath-generator h3 {
  font-size: 1.5rem;
  font-style: italic;
  margin: 0 0 8px;
}

.bath-generator-intro {
  color: var(--parchment-dim);
  margin: 0 0 22px;
}

.bath-result {
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
  text-align: left;
}

.bath-result-eyebrow {
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 8px;
}

.bath-intention {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.35rem;
  color: var(--parchment);
  margin: 0 0 24px;
}

.bath-ingredients {
  list-style: none;
  margin: 0 0 8px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.bath-ingredients li {
  display: grid;
  grid-template-columns: 90px 140px 1fr;
  gap: 12px;
  align-items: baseline;
}

.bath-ingredient-label {
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold);
}

.bath-ingredient-name {
  font-weight: 600;
  color: var(--parchment);
}

.bath-ingredient-note {
  color: var(--parchment-dim);
  font-size: 0.92rem;
}

/* ---------- The Naming Well ---------- */
/* Same visual recipe as .bath-generator above (gradient card, gold
   eyebrow, italic serif result) — kept as its own set of classes rather
   than reusing .bath-* directly, since sharing class names across two
   unrelated features just to save a few lines of CSS makes both harder
   to change independently later. */

.naming-generator {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--hearth), var(--card));
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 36px 30px;
  margin: 0 0 56px;
}

/* Same trick as .site-header.menu-open — the card clips overflow so its
   grain texture respects the rounded corners, but the themed-select
   dropdown below extends past the card's own box when open. Toggled on/
   off by enhanceThemedSelect() in naming.js. */
.naming-generator.menu-open {
  overflow: visible;
}

.naming-generator > * { position: relative; z-index: 1; }

.naming-generator h3 {
  font-size: 1.5rem;
  font-style: italic;
  margin: 0 0 8px;
}

.naming-generator-intro {
  color: var(--parchment-dim);
  margin: 0 0 22px;
}

.naming-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 22px;
}

.naming-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--parchment-dim);
  flex: 1 1 200px;
}

.naming-field select {
  background: var(--night);
  border: 1px solid rgba(232, 196, 104, 0.4);
  color: var(--parchment);
  padding: 12px 14px;
  border-radius: 12px;
  font-family: inherit;
  font-size: 0.98rem;
  box-shadow: inset 0 0 0 1px rgba(232, 196, 104, 0.06);
  transition: box-shadow 0.25s ease, border-color 0.25s ease;
  cursor: pointer;
}

.naming-field select:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: inset 0 0 0 1px rgba(232, 196, 104, 0.14), 0 0 18px rgba(232, 196, 104, 0.22);
}

/* ---------- Themed select ----------
   Browsers won't let CSS restyle a native <select>'s OPEN option list —
   only the closed box above. This layers a themed button + panel on top
   (same dark/gold look as the .nav-group-menu dropdowns) while the real
   <select> stays in the DOM, visually hidden, as the actual value store.
   Built by enhanceThemedSelect() in naming.js. */
.themed-select {
  position: relative;
}

.themed-select select {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}

.themed-select-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  background: var(--night);
  border: 1px solid rgba(232, 196, 104, 0.4);
  color: var(--parchment);
  padding: 12px 14px;
  border-radius: 12px;
  font-family: inherit;
  font-size: 0.98rem;
  box-shadow: inset 0 0 0 1px rgba(232, 196, 104, 0.06);
  transition: box-shadow 0.25s ease, border-color 0.25s ease;
  cursor: pointer;
  text-align: left;
}

.themed-select-toggle:hover,
.themed-select.is-open .themed-select-toggle {
  border-color: var(--gold);
}

.themed-select-toggle:focus-visible {
  outline: none;
  border-color: var(--gold);
  box-shadow: inset 0 0 0 1px rgba(232, 196, 104, 0.14), 0 0 18px rgba(232, 196, 104, 0.22);
}

.themed-select-caret {
  font-size: 0.7em;
  color: var(--gold);
  transition: transform 0.2s ease;
  flex: 0 0 auto;
}

.themed-select.is-open .themed-select-caret {
  transform: rotate(180deg);
}

.themed-select-menu {
  display: none;
  flex-direction: column;
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  max-height: 320px;
  overflow-y: auto;
  background: var(--night-deep);
  border: 1px solid rgba(232, 196, 104, 0.3);
  border-radius: 12px;
  padding: 8px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);
  z-index: 30;
}

.themed-select.is-open .themed-select-menu {
  display: flex;
}

.themed-select-group-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--parchment-dim);
  opacity: 0.6;
  padding: 8px 10px 4px;
}

.themed-select-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  font-family: inherit;
  color: var(--parchment);
  font-size: 0.92rem;
  padding: 9px 10px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.themed-select-option:hover,
.themed-select-option.is-active {
  color: var(--gold);
  background: rgba(232, 196, 104, 0.1);
}

.themed-select-option-check {
  color: var(--gold);
  opacity: 0;
  font-size: 0.85em;
}

.themed-select-option.is-selected .themed-select-option-check {
  opacity: 1;
}

.naming-result {
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
  text-align: left;
}

.naming-result-eyebrow {
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 8px;
}

.naming-name {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.75rem;
  color: var(--parchment);
  margin: 0 0 24px;
}

.naming-result-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.scene-elements {
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.scene-elements li {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: 12px;
  align-items: baseline;
}

.scene-element-label {
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold);
}

.scene-element-name {
  font-weight: 600;
  color: var(--parchment);
}

.selflove-tips h3 {
  font-size: 1.6rem;
  font-style: italic;
  margin-bottom: 6px;
}

.tips-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 22px;
  text-align: left;
}

.tip-tile {
  position: relative;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 26px 24px;
}

.tip-tile > * { position: relative; z-index: 1; }

/* Optional per-tip image — bleeds to the tile's own edges (negative
   margin matching .tip-tile's padding) rather than sitting inset, so it
   reads as a cover band rather than a framed thumbnail. Not every tip has
   one yet (see self-love.js tileHtml() — only renders this block if
   tip.image is set), so the headline/body still work fine as plain text
   cards until each tip gets its own art. */
/* First pass used a fixed 160px-tall landscape band with object-fit:cover
   — fine for a wide crop, but Eloise's tip art is full-body portrait work
   (700x900, same ratio as the homepage hero tiles), so that band would've
   cropped out most of each piece. Matching the box's own aspect-ratio to
   the art's real 700/900 ratio instead means object-fit:cover has nothing
   to crop — the whole image shows, just scaled to fit the tile's width. */
.tip-tile-image {
  margin: -26px -24px 18px;
  overflow: hidden;
  aspect-ratio: 700 / 900;
}

.tip-tile-image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tip-tile-headline {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--gold);
  margin: 0 0 10px;
}

.tip-tile-body {
  color: var(--parchment-dim);
  font-size: 0.96rem;
  margin: 0;
}

@media (max-width: 620px) {
  .bath-ingredients li {
    grid-template-columns: 1fr;
    gap: 2px;
  }
}

/* ---------- Books / Gift Shop ---------- */

.shop-filter {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 0 0 36px;
}

.shop-filter-btn {
  padding: 9px 18px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: var(--parchment-dim);
  font-family: inherit;
  font-size: 0.9rem;
  cursor: pointer;
  transition: all 0.15s ease;
}

.shop-filter-btn:hover {
  border-color: var(--gold);
  color: var(--gold);
}

.shop-filter-btn.is-active {
  border-color: var(--gold);
  background: rgba(232, 196, 104, 0.12);
  color: var(--gold);
}

.book-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 28px;
  text-align: left;
}


.book-card {
  position: relative;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 30px 26px;
  display: flex;
  flex-direction: column;
}

.book-card > * { position: relative; z-index: 1; }

/* aspect-ratio (not a fixed height) because every cover image we actually
   have is a portrait-ish book-cover shape (~0.7 wide:tall) — matching the
   box to that means object-fit: cover barely has to crop anything, so the
   whole design stays visible instead of getting cut off top and bottom
   the way a flatter, more landscape box was doing. */
.book-cover {
  aspect-ratio: 0.7;
  border-radius: 10px;
  margin-bottom: 20px;
  overflow: hidden;
  border: 1px solid var(--border);
}

.book-cover-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Only used if an item has no dedicated cover art yet — keeps the card
   from looking broken rather than being the default look. */
.book-cover-fallback {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px;
  text-align: center;
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.1rem;
  background: linear-gradient(135deg, var(--plum), var(--magenta-dark));
  color: var(--gold);
}

.shop-more-note {
  text-align: center;
  color: var(--parchment-dim);
  font-size: 0.95rem;
  margin-top: 32px;
}

.shop-more-note a {
  color: var(--gold);
}

.book-tag {
  color: var(--gold);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  margin: 0 0 6px;
}

.book-card h3 { font-size: 1.35rem; margin-bottom: 12px; }
.book-card p.blurb { color: var(--parchment-dim); font-size: 0.98rem; flex-grow: 1; margin-bottom: 20px; }

.badge-coming-soon {
  display: inline-block;
  align-self: flex-start;
  color: var(--parchment-dim);
  border: 1px solid var(--border);
  padding: 8px 18px;
  border-radius: 999px;
  font-size: 0.9rem;
}

/* ---------- About ---------- */
.about blockquote {
  font-style: italic;
  color: var(--parchment-dim);
  font-size: 1.1rem;
  margin: 0 auto 24px;
  max-width: 560px;
}

.signoff {
  color: var(--gold);
  font-style: italic;
  margin-bottom: 28px;
}

/* ---------- Footer moon-phase strip ---------- */
/* A thin band of the moon-phase glitter motif, tiled edge-to-edge, sitting
   right above the footer on every page. PNG background-image with a WebP
   override for browsers that support image-set() (nearly all current ones);
   falls back gracefully to PNG otherwise. */
.moon-strip {
  position: relative;
  z-index: 2;
  height: 15px;
  background-image: url("assets/moon-strip.png");
  background-image: image-set(url("assets/moon-strip.webp") type("image/webp"), url("assets/moon-strip.png") type("image/png"));
  background-repeat: repeat-x;
  background-position: center;
  background-size: auto 15px;
  opacity: 0.55;
}

/* ---------- Newsletter signup ---------- */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.newsletter {
  max-width: 640px;
  margin: 0 auto;
  padding: 50px 24px;
  text-align: center;
}

/* Equal breathing room on both sides of the divider, not just a pull-up.
   My first attempt only adjusted margin-top and left the default 18px
   margin-bottom in place, which — combined with how margins actually
   stack in normal flow, not the "total gap" math I'd assumed — left
   next to nothing between the divider and the signup heading. Solved
   properly this time: previous section's 90px bottom padding + this
   section's 50px top padding = 140px before any margin is added, so
   margin-top lands the divider's top edge some distance below the tab
   content above, and margin-bottom matches that same distance below the
   divider before the signup heading starts. */
.newsletter-divider {
  margin-top: -55px;
  margin-bottom: 85px;
}

.newsletter-inner h2 {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 2rem;
  margin: 4px 0 12px;
}

.newsletter-copy {
  color: var(--parchment-dim);
  margin: 0 0 24px;
}

.newsletter-form {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
}

.newsletter-form input[type="email"] {
  flex: 1 1 260px;
  max-width: 340px;
  padding: 13px 18px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: var(--parchment);
  font-family: inherit;
  font-size: 0.95rem;
}
.newsletter-form input[type="email"]:focus {
  outline: none;
  border-color: var(--gold);
}
.newsletter-form input[type="email"]::placeholder { color: var(--parchment-dim); opacity: 0.7; }

/* Deliberately quiet — this is a nod to people who've already signed up,
   not a second call-to-action. It should never compete with "Join the
   circle" above it, which is why it's small, low-contrast, and un-bolded
   until hovered. */
.newsletter-returning {
  margin: 14px 0 0;
  font-size: 0.8rem;
  color: var(--parchment-dim);
  opacity: 0.7;
}
.newsletter-returning a {
  color: var(--parchment-dim);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.newsletter-returning a:hover {
  color: var(--gold);
}

.newsletter-spam-note {
  margin: 8px 0 0;
  font-size: 0.75rem;
  color: var(--parchment-dim);
  opacity: 0.6;
  font-style: italic;
}

.newsletter-message {
  min-height: 1.4em;
  margin: 16px 0 0;
  font-size: 0.9rem;
  color: var(--parchment-dim);
}
.newsletter-message.is-success { color: var(--gold); }
.newsletter-message.is-error { color: var(--magenta); }

/* Fades out once someone's joined — no longer needed, and leaving it up
   alongside the wallpaper reveal was what made the whole block feel like
   five headings stacked on top of each other. */
.newsletter-pitch {
  transition: opacity 0.4s ease;
}
.newsletter-pitch.is-fading {
  opacity: 0;
}

/* Painting on an easel — bottom left of the newsletter block, which is
   the one piece of margin decor that isn't scoped to a single tab (this
   section sits outside the tab system entirely, so it shows on every
   page). Anchored with `bottom` instead of `top` since the section's own
   height is known and fixed, unlike the tab pages where a piece further
   down the content has to be estimated from the type stack. */
.newsletter-easel {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translateY(-50%) translateX(calc(-320px - 100%));
  z-index: 2;
  width: 115px;
  line-height: 0;
  cursor: pointer;
}

.newsletter-easel:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.newsletter-easel-canvas {
  display: block;
  width: 100%;
  height: auto;
}

/* Persistent toggle (like .hearth-lantern), not a one-shot animation —
   click paints the cat in and it stays; click again fades it back out.
   The 1.2s fade roughly matches the scribble sound's own length (see
   initNewsletterEasel in app.js) so the "painting in" feel lines up with
   the sound instead of finishing well before or after it. */
.newsletter-easel-cat {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 1.2s ease-out;
}

.newsletter-easel.is-painted .newsletter-easel-cat {
  opacity: 1;
}

/* Ambient sway — always running regardless of paint state, so the piece
   doesn't just sit dead until clicked. Same non-uniform scale(x, y) +
   slight rotate recipe as the fireplace flame and almanac bush, just
   calmer (smaller amplitude, slower, no decay) since an easel gently
   settling reads differently than a flame or a startled cat. Both layers
   share the exact same animation so they move as one rigid unit and stay
   aligned; rooted near the easel's feet (transform-origin) so it reads
   as swaying from where it stands rather than pivoting from its center. */
.newsletter-easel-canvas,
.newsletter-easel-cat {
  transform-origin: 50% 92%;
  animation: easelIdle 5s ease-in-out infinite;
}

@keyframes easelIdle {
  0%, 100% { transform: scale(1, 1) rotate(0deg); }
  25% { transform: scale(1.015, 0.985) rotate(-0.6deg); }
  50% { transform: scale(0.985, 1.015) rotate(0.5deg); }
  75% { transform: scale(1.01, 0.99) rotate(-0.35deg); }
}

/* Cottage couch scene's own idle motion — no rotation at all (that's what
   read as "bouncing"), just a slow single in-out scale pulse, one full
   cycle every 9s instead of easelIdle's busier 4-beat 5s cycle. */
@keyframes booksCottageBreathe {
  0%, 100% { transform: scale(1, 1); }
  50% { transform: scale(1.012, 0.988); }
}

@media (max-width: 1300px) {
  .newsletter-easel {
    display: none;
  }
}

/* Bird — right side of the newsletter block, vertically centered to sit
   level with the signup form (mirrors the easel on the opposite side).
   Closed/open beak crossfade (opacity) rather than stack, same idea as
   the lantern's off/on layers. Toggling loops for as long as the click
   sequence is playing — driven off real `ended` events in app.js (chirp
   clip, then her song) rather than a hardcoded duration, same reasoning
   as the gramophone.
   Desktop-only on purpose — not part of .mobile-showcase, since the
   fast beak crossfade read as glitchy on phones. It simply doesn't
   render on mobile (pushed off in the gutter by its own transform, same
   as every other margin piece not opted into .mobile-showcase). */
.newsletter-bird {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translateY(-50%) translateX(320px);
  z-index: 2;
  width: 135px;
  line-height: 0;
  cursor: pointer;
}

.newsletter-bird:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}

.newsletter-bird-closed {
  display: block;
  width: 100%;
  height: auto;
}

.newsletter-bird-open {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
}

/* Plain class-driven opacity swap, no keyframe animation — same simple
   mechanism as .hearth-lantern's is-lit toggle. JS toggles this class on
   /off every 260ms (slowed from an original 180ms, which read as
   pulsing/growing rather than a blink) for as long as the chirp/song
   audio plays. Closed layer is explicitly hidden on is-open so it's a
   real swap rather than a stack (the beak doesn't line up pixel-for-
   pixel between poses, so stacking let the closed art show through at
   the edges). */
.newsletter-bird.is-open .newsletter-bird-open {
  opacity: 1;
}

.newsletter-bird.is-open .newsletter-bird-closed {
  opacity: 0;
}


/* ---------- Wallpaper claim (newsletter signup incentive) ---------- */
/* Eases in a beat after the pitch fades (see the setTimeout in
   initNewsletter) rather than snapping into place, so the gift feels like
   it's being revealed rather than just appearing. */
.wallpaper-claim {
  margin-top: 28px;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.wallpaper-claim.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.wallpaper-claim-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.72rem;
  color: var(--gold);
  margin: 0 0 8px;
}

.wallpaper-claim-heading {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.3rem;
  color: var(--parchment);
  margin: 0 0 20px;
}

.wallpaper-group-label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--parchment-dim);
  margin: 20px 0 10px;
}

.wallpaper-group-label:first-of-type { margin-top: 0; }

/* A screen-width guess at which size someone probably wants — a visual
   nudge only, never a gate, since the guess can be wrong. */
.wallpaper-group-label.is-recommended {
  color: var(--gold);
}
.wallpaper-group-label.is-recommended::after {
  content: " — recommended for this device";
  font-style: italic;
  text-transform: none;
  letter-spacing: normal;
  opacity: 0.8;
}

.wallpaper-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}

.wallpaper-card {
  position: relative;
  display: block;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  transition: all 0.2s ease;
}

.wallpaper-card:hover {
  border-color: var(--gold);
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(6, 4, 20, 0.4);
}

.wallpaper-card img {
  display: block;
  width: 100%;
  height: 140px;
  object-fit: cover;
}

.wallpaper-grid-phone .wallpaper-card img { height: 180px; object-fit: cover; object-position: top; }

.wallpaper-download-icon {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: rgba(8, 6, 20, 0.65);
  color: var(--gold);
  font-size: 0.85rem;
  backdrop-filter: blur(2px);
}

@media (max-width: 640px) {
  .wallpaper-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
  .wallpaper-card img { height: 90px; }
  .wallpaper-grid-phone .wallpaper-card img { height: 120px; }
}

/* Lets a visitor pick which specific wallpaper in an edition gets drawn
   into the share card, instead of always sharing the first image. Sits
   directly above the share-row buttons it feeds. */
.wallpaper-share-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 12px;
}

.wallpaper-share-swatch {
  position: relative;
  display: block;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 9px;
  overflow: hidden;
  border: 2px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  cursor: pointer;
  transition: all 0.2s ease;
}

.wallpaper-share-swatch img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.wallpaper-share-swatch:hover {
  border-color: var(--gold);
}

.wallpaper-share-swatch.is-selected {
  border-color: var(--gold);
  box-shadow: 0 0 0 2px rgba(232, 196, 104, 0.35);
}

/* ---------- Footer ---------- */
footer {
  border-top: 1px solid var(--border);
  padding: 48px 24px;
  text-align: center;
  color: var(--parchment-dim);
  font-size: 0.9rem;
}

.footer-logo { font-size: 1.15rem; margin-bottom: 16px; }

.footer-links {
  display: flex;
  gap: 24px;
  justify-content: center;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.footer-links a { text-decoration: none; color: var(--parchment-dim); }
.footer-links a:hover { color: var(--gold); }

.copyright { opacity: 0.7; font-size: 0.82rem; }

/* ---------- Printable tale / "Save as PDF" ---------- */
.print-byline,
.print-date { display: none; }

.tale-actions { margin-top: 24px; }

/* Generic "share this" block — the tale result, the bath ritual card, and
   each self-love tip tile all use this same label+row pairing. */
.share-block {
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}

.share-block-label {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 1.05rem;
  color: var(--gold);
  margin: 0 0 4px;
}

/* Lighter variant for share-blocks nested inside a compact list item (each
   celestial event in .events-list) rather than a standalone result card —
   same label+row pairing, just less vertical weight so 5 of them in a row
   doesn't feel like 5 separate cards. */
.event-share-block {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

/* Print-only decoration piece — lives inside #printArea in the markup so
   it's swept up by the print isolation rules below, but must stay hidden
   during ordinary on-screen browsing (it has no other on-screen styling,
   so without this it would render as a plain oversized inline image on
   the Storyteller page). Shown again, sized and positioned, inside
   @media print further down. */
.print-lineart-fireplace {
  display: none;
}

/* On screen, only .no-print elements are visible normally. When printing, we
   isolate #printArea so a visitor's "Save as PDF" only captures the tale
   itself (title, story, matched book) rather than the whole page. */
@media print {
  body * { visibility: hidden; }
  #printArea, #printArea * { visibility: visible; }
  #printArea {
    /* Was position:absolute, anchored "top:0" against its nearest
       positioned ancestor (.hearth-zone-ritual) rather than the physical
       page. Everything above that ancestor (header, hero, nav) is only
       visibility:hidden, not display:none, so it still occupies its full
       layout height — pushing .hearth-zone-ritual, and #printArea inside
       it, well down the page. The result: a blank first printed page,
       with the actual tale starting on page 2. position:fixed escapes
       that ancestor chain entirely and anchors to the page itself
       (nothing here has a transform, so there's no fixed-position
       containing block in the way), which is the actual fix — found
       2026-08-13 after Eloise reported the blank first page. */
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    padding: 40px;
    background: #fff;
  }
  .no-print { display: none !important; }

  /* #printArea no longer sits inside .hearth-zone-ritual's box (it's
     fixed to the page now), but this stays — .hearth-zone-ritual is still
     part of the (invisible) document flow above it and its own
     overflow:hidden served no purpose in print anyway. */
  .hearth-zone { overflow: visible; }

  /* ---------- Fairytale line-art border for printed/saved tales ----------
     A little illustrated frame so every tale someone prints or saves as a
     PDF looks like a page from a storybook, not a plain browser printout.
     Fixed-position + print-only, so it repeats on every physical page
     without needing to be placed per-tale. Only the fireplace piece exists
     so far (2026-08-13) — a matching cottage-roofline piece for the top of
     the page is coming; add it the same way once it's ready. */
  .print-lineart-fireplace {
    display: block;
    position: fixed;
    bottom: 0;
    right: 0;
    width: 140px;
    height: auto;
    opacity: 0.35;
    pointer-events: none;
  }

  body { background: #fff; }
  .tale-result, .match-callout { background: #fff; border: none; }
  #taleTitle, #matchTitle { color: #2a1830; }
  #taleBody, #matchBlurb, #matchReason { color: #2f2a3d; }

  .print-byline {
    display: block;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: 0.75rem;
    color: #8a5a3a;
    margin-bottom: 8px;
  }
  .print-date {
    display: block;
    font-style: italic;
    color: #8a8194;
    margin-top: 20px;
    font-size: 0.85rem;
  }
}

/* ---------- Responsive ---------- */
@media (max-width: 860px) {
  /* The hero is already a single centered column at every width now — this
     just tightens it up for narrower screens. */
  .hero-inner { max-width: 460px; gap: 28px; }
  .hero-art { max-width: 240px; }
}

/* Header collapses to a hamburger menu below this width — there just
   isn't room for two bookend illustrations plus a full nav row once the
   viewport gets phone-narrow. */
@media (max-width: 760px) {
  .header-figure { display: none; }

  .header-inner { padding: 16px 20px; }
  .site-header.header-compact .header-inner { padding: 8px 20px; }

  .header-title { font-size: 1.6rem; }
  .site-header.header-compact .header-title { font-size: 1.15rem; }

  /* Slightly bigger tap target + a little breathing room from the edge
     (on top of the .header-center containing-block fix above) — 34px flush
     against the corner is below Apple's 44px minimum recommended tap size
     and sits right in the zone iOS reserves for its edge-swipe gesture. */
  .nav-toggle {
    display: flex;
    top: -9px;
    right: 4px;
    width: 44px;
    height: 44px;
  }

  .nav-links {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--night-deep);
    border-top: 1px solid rgba(232, 196, 104, 0.3);
    border-bottom: 1px solid rgba(232, 196, 104, 0.3);
    padding: 6px 0;
  }

  .nav-links.nav-open { display: flex; }

  .nav-links a {
    padding: 12px 20px;
    text-align: center;
  }
  .nav-links a::after { display: none; }
  .nav-links a.active-tab { background: rgba(232, 196, 104, 0.08); }

  .nav-sep { display: none; }

  /* Mobile: no dropdown interaction — the group "button" becomes a plain
     inert section label, and its menu is always visible inline, so the
     whole panel is still just one flat scrollable list (same shape as
     before groups existed, just with two extra label rows). Deliberately
     not adding a second layer of taps here — see the comment on the nav
     markup in index.html. */
  .nav-group {
    display: flex;
    flex-direction: column;
    width: 100%;
  }
  .nav-group-toggle {
    pointer-events: none;
    justify-content: center;
    padding: 12px 20px 4px;
    color: var(--gold);
    opacity: 0.75;
    font-size: 0.78rem;
  }
  .nav-group-toggle::after { display: none; }
  .nav-caret { display: none; }
  .nav-group-menu {
    display: flex;
    position: static;
    transform: none;
    margin: 0;
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    width: 100%;
  }
  .nav-group-menu a {
    text-align: center;
    border-radius: 0;
    padding: 10px 20px;
  }
}

@media (max-width: 640px) {
  .hero h1 { font-size: 2.5rem; }
  .hero { padding: 70px 20px 60px; }
  section { padding: 60px 20px; }
  .book-grid { grid-template-columns: 1fr; }
  .hearth-zone { padding: 28px 22px; }
  .hearth-zone-quote,
  .hearth-zone-ritual { padding: 32px 24px; }
}

/* Moon/star scatter — one painted moon-phase or star piece per page
   (eclipse, full red moon, nebula orb, crescent, star burst), permanent
   decor, no data-season. Per Eloise: "scatter one of each to every page."
   Five art pieces cycle across the site's nine pages so every design gets
   used somewhere, rather than stacking all five onto every page.
   Movement is deliberately NOT a smooth float — per her request, a "stop
   motion" look: steps(1, end) removes the in-between interpolation frames
   CSS would normally generate, so the piece jumps between a handful of
   small offsets instead of gliding, reading like a hand-animated paper
   cutout nudged frame by frame. Shared class below carries the art sizing
   + animation; each page gets its own one-line position-only modifier
   class (same "-2/-3" convention as the scatter pumpkins), added to
   EDITABLE_DECOR in app.js. Rough placeholder positions, drag into place
   with ?edit=1. */
.decor-moonscatter {
  position: absolute;
  z-index: 1;
  line-height: 0;
  pointer-events: none;
}
.decor-moonscatter img {
  display: block;
  width: 100%;
  height: auto;
  animation: decorMoonDrift 7.4s steps(1, end) infinite;
}
@keyframes decorMoonDrift {
  0% { transform: translate(0, 0); }
  20% { transform: translate(4px, -3px); }
  40% { transform: translate(-3px, 3px); }
  60% { transform: translate(3px, 4px); }
  80% { transform: translate(-4px, -2px); }
  100% { transform: translate(0, 0); }
}

.home-moonscatter { top: 2027px; left: 50%; transform: translateX(418px); width: 156px; }
.hearth-moonscatter { top: 483px; left: 50%; transform: translateX(476px); width: 142px; }
.naming-moonscatter { top: 95px; left: 50%; transform: translateX(-619px); width: 120px; }
.almanac-moonscatter { top: 1166px; left: 50%; transform: translateX(-441px); width: 150px; }
.selflove-moonscatter { top: 2973px; left: 50%; transform: translateX(-538px); width: 126px; }
.books-moonscatter { top: 733px; left: 50%; transform: translateX(568px); width: 90px; }
.market-moonscatter { top: 1523px; left: 50%; transform: translateX(546px); width: 112px; }
.about-moonscatter { top: 782px; left: 50%; transform: translateX(-573px); width: 156px; }
.social-moonscatter { top: 922px; left: 50%; transform: translateX(524px); width: 114px; }

@media (max-width: 1300px) {
  .decor-moonscatter {
    display: none;
  }
}

/* Potted plant (Naming Well page) — permanent decor, no data-season.
   Click plays the exact same bush-rustle sound + fire-flicker shimmer as
   every other bush/plant on the site (Almanac bush, Books cottage plant,
   Quiet Hollow flower pot/lavender). See initNamingPlant() in app.js.
   Rough placeholder position, drag into place with ?edit=1. */
.naming-plant {
  position: absolute;
  top: 444px;
  left: 50%;
  transform: translateX(467px);
  z-index: 2;
  width: 184px;
  line-height: 0;
  cursor: pointer;
}
.naming-plant:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.naming-plant-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 96%;
}
.naming-plant.is-rustling .naming-plant-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out;
}

@media (max-width: 1300px) {
  .naming-plant {
    display: none;
  }
}

/* Spider (Naming Well page) — Halloween decor. Click plays a short
   wiggle sound (naming-spider-wiggle.mp3, 1.392s, measured via ffprobe)
   and loops the exact same pot-plant flicker shimmer as the plants/
   bushes across the site (booksCottagePlantFlicker), stopped via the
   sound's own "ended" event so the wiggle motion tracks the sound's
   real length instead of a fixed guessed duration — same recipe as
   .selflove-ghost. See initNamingSpider() in app.js. Rough placeholder
   position, drag into place with ?edit=1. */
.naming-spider {
  position: absolute;
  top: 623px;
  left: 50%;
  transform: translateX(461px);
  z-index: 2;
  width: 118px;
  line-height: 0;
  cursor: pointer;
}
.naming-spider:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.naming-spider-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 50%;
}
.naming-spider.is-active .naming-spider-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out infinite;
}

@media (max-width: 1300px) {
  .naming-spider {
    display: none;
  }
}

/* Spider (Home page) — Halloween decor. Same wiggle-sound + pot-plant
   flicker-loop recipe as .naming-spider. See initHeroSpider() in app.js.
   Rough placeholder position, drag into place with ?edit=1. */
.hero-spider {
  position: absolute;
  top: 1413px;
  left: 50%;
  transform: translateX(433px);
  z-index: 2;
  width: 180px;
  line-height: 0;
  cursor: pointer;
}
.hero-spider:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.hero-spider-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 50%;
}
.hero-spider.is-active .hero-spider-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out infinite;
}
@media (max-width: 1300px) {
  .hero-spider {
    display: none;
  }
}

/* Spider (Wise Woman page) — Halloween decor. Same wiggle-sound +
   pot-plant flicker-loop recipe as .naming-spider. See
   initHearthSpider() in app.js. Rough placeholder position, drag into
   place with ?edit=1. */
.hearth-spider {
  position: absolute;
  top: 1681px;
  left: 50%;
  transform: translateX(274px);
  z-index: 2;
  width: 168px;
  line-height: 0;
  cursor: pointer;
}
.hearth-spider:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.hearth-spider-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 50%;
}
.hearth-spider.is-active .hearth-spider-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out infinite;
}
@media (max-width: 1300px) {
  .hearth-spider {
    display: none;
  }
}

/* Spider (Follow Along page) — Halloween decor. Same wiggle-sound +
   pot-plant flicker-loop recipe as .naming-spider. See
   initSocialSpider() in app.js. Rough placeholder position, drag into
   place with ?edit=1. */
.social-spider {
  position: absolute;
  top: 1774px;
  left: 50%;
  transform: translateX(365px);
  z-index: 2;
  width: 126px;
  line-height: 0;
  cursor: pointer;
}
.social-spider:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.social-spider-img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 50%;
}
.social-spider.is-active .social-spider-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out infinite;
}
@media (max-width: 1300px) {
  .social-spider {
    display: none;
  }
}

/* Girl reading (Naming Well page) — permanent decor, no data-season.
   Ambient blinking eyes, same ravenBlink technique used site-wide, center
   points measured off naming-girl.webp, fill color sampled from her own
   skin tone next to each eye. No click. Rough placeholder position, drag
   into place with ?edit=1. */
.naming-girl {
  position: absolute;
  top: 861px;
  left: 50%;
  transform: translateX(-594px);
  z-index: 2;
  width: 172px;
  line-height: 0;
}
.naming-girl img {
  display: block;
  width: 100%;
  height: auto;
}
/* Re-measured 2026-08-19 (size 3.6% -> 6%, position nudged) as part of
   a site-wide blink-coverage audit — the old dot undershot her actual
   pupils and left a sliver of iris showing on the left eye. */
.naming-girl-eyelid {
  position: absolute;
  width: 6%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #ffcab6 55%, #f5b199 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}
.naming-girl-eyelid-a { left: 42.0%; top: 29.6%; }
.naming-girl-eyelid-b { left: 55.1%; top: 31.5%; }

@media (max-width: 1300px) {
  .naming-girl {
    display: none;
  }
}

/* Worm on an apple (Naming Well page) — permanent decor, no data-season.
   Ambient blinking eyes, same ravenBlink technique used site-wide, center
   points measured off naming-worm-apple.webp, fill color sampled from the
   worm's own yellow face right next to each eye. No click. Rough
   placeholder position, drag into place with ?edit=1. */
.naming-worm-apple {
  position: absolute;
  top: 797px;
  left: 50%;
  transform: translateX(294px);
  z-index: 2;
  width: 110px;
  line-height: 0;
}
.naming-worm-apple img {
  display: block;
  width: 100%;
  height: auto;
}
.naming-worm-apple-eyelid {
  position: absolute;
  width: 3.6%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #fce44b 55%, #ecd23a 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}
.naming-worm-apple-eyelid-a { left: 51.8%; top: 10.7%; }
.naming-worm-apple-eyelid-b { left: 60.3%; top: 11.3%; }

@media (max-width: 1300px) {
  .naming-worm-apple {
    display: none;
  }
}

/* Fountain (Naming Well page) — permanent decor, no data-season. Off/on
   are two full poses on one pre-aligned canvas (still water vs. spouting
   water), same crossfade-toggle convention as the lanterns/lamps
   (.about-lantern etc.) — off image is the base, on image sits on top via
   inset:0 and fades in/out. Starts off. Click also starts the fountain
   sound looping until clicked again — see initNamingFountain() in
   app.js. Rough placeholder position, drag into place with ?edit=1. */
.naming-fountain {
  position: absolute;
  top: 45px;
  left: 50%;
  transform: translateX(383px);
  z-index: 2;
  width: 270px;
  line-height: 0;
  cursor: pointer;
}
.naming-fountain:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.naming-fountain-off-img {
  display: block;
  width: 100%;
  height: auto;
}
.naming-fountain-on-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.18s linear;
}
.naming-fountain.is-on .naming-fountain-on-img {
  opacity: 1;
}

@media (max-width: 1300px) {
  .naming-fountain {
    display: none;
  }
}

/* Zombies — Halloween decor (data-season="halloween" in the HTML, same
   auto swap-out on Nov 1 as every other seasonal piece, see SEASON_WINDOWS
   in app.js — nothing to manually remove when the season ends). Three
   separate characters across three pages: blue zombie (Naming Well),
   green-haired zombie (Almanac), pink zombie (Quiet Hollow). Ambient
   blinking eyes run independently via the usual ravenBlink technique,
   click or not, center points measured off each character's own art,
   fill colors sampled from each one's own skin tone next to the eyes.
   Click plays a short "zombie utterance" grunt and grows the whole
   character slightly for the ~1.4s the sound actually plays (matches
   zombie-uttering.mp3's real length), same remove/reflow/add restart
   trick as every other click animation on the site. See initZombies() in
   app.js. Rough placeholder positions, drag each into place with
   ?edit=1. */
.zombie-piece {
  position: absolute;
  z-index: 2;
  line-height: 0;
  cursor: pointer;
}
.zombie-piece:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.zombie-piece img {
  display: block;
  width: 100%;
  height: auto;
}
.zombie-piece.is-uttering img {
  animation: zombieUtter 1.4s ease-in-out;
}
@keyframes zombieUtter {
  0%, 100% { transform: scale(1); }
  35% { transform: scale(1.08); }
  65% { transform: scale(1.06); }
}
.zombie-eyelid {
  position: absolute;
  width: 3.6%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}

/* Sticker-pack reveal hint — a small download button that fades in
   under a piece once it's been clicked, and stays visible from then on
   (class is added, never removed). Shared by the Quiet Hollow zombie
   (Halloween pack) and the Almanac cat (Lunar pack). Positioned
   absolute against the piece's own box, so the parent just needs
   position != static, which .zombie-piece and .almanac-cat already
   have. */
.sticker-hint {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translate(-50%, 2px);
  margin-top: 8px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease, transform 0.6s ease;
  white-space: nowrap;
  z-index: 6;
}
.sticker-hint a {
  white-space: nowrap;
}
.zombie-piece.is-revealed .sticker-hint,
.almanac-cat.is-revealed .sticker-hint {
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, 10px);
}

.naming-zombie-blue {
  top: 134px;
  left: 50%;
  transform: translateX(-498px);
  width: 136px;
}
/* Zombie eyelid sizes/positions re-measured 2026-08-19 as part of a
   site-wide blink-coverage audit — the shared 3.6% circular dot (sized
   for smaller simple-dot eyes elsewhere on the site) was much too small
   for these big cartoon eyes, leaving a wide ring of white sclera
   visible on every blink. Each piece now gets its own width/height
   overriding .zombie-eyelid's default, measured directly off each
   character's actual eyes. */
.naming-zombie-blue-eyelid-a { left: 28.57%; top: 24.4%; width: 15%; height: 7%; background: radial-gradient(circle, #a0e4f0 55%, #78bfca 100%); }
.naming-zombie-blue-eyelid-b { left: 58.1%; top: 23.2%; width: 15%; height: 8%; background: radial-gradient(circle, #a0e4f0 55%, #78bfca 100%); }

.almanac-zombie-green {
  top: 1328px;
  left: 50%;
  transform: translateX(-604px);
  width: 132px;
}
.almanac-zombie-green-eyelid-a { left: 31.9%; top: 19.2%; width: 13%; height: 5.5%; background: radial-gradient(circle, #dcdce4 55%, #b0b0ba 100%); }
.almanac-zombie-green-eyelid-b { left: 69.6%; top: 19.2%; width: 13%; height: 5.5%; background: radial-gradient(circle, #dcdce4 55%, #b0b0ba 100%); }

.selflove-zombie-red {
  top: 3140px;
  left: 50%;
  transform: translateX(-673px);
  width: 170px;
}
.selflove-zombie-red-eyelid-a { left: 53.8%; top: 17.9%; width: 14.3%; height: 5.76%; background: radial-gradient(circle, #e0b3cb 55%, #b3879f 100%); }
.selflove-zombie-red-eyelid-b { left: 78.57%; top: 17.80%; width: 16.7%; height: 5.76%; background: radial-gradient(circle, #e0b3cb 55%, #b3879f 100%); }

@media (max-width: 1300px) {
  .naming-zombie-blue,
  .almanac-zombie-green,
  .selflove-zombie-red {
    display: none;
  }
}

/* Dragonfly scene (Quiet Hollow page) — three pre-aligned layers stacked
   on one shared canvas (same pattern as .quiethollow-scene): foliage at
   the back, cosmos flower in the middle, dragonfly up front. Permanent,
   no data-season. One click zone for the whole scene (not per-layer, per
   Eloise) triggers all three at once: the flower and foliage each play
   their OWN one-shot float — different duration/shape so they drift
   noticeably out of sync with each other rather than moving as one unit —
   while the dragonfly gets the same click-triggered twinkle/glimmer light
   blocks as the butterfly (reuses .selflove-glimmer-block/.gb-1..8/twinkle
   as-is) plus a bigger, faster "zoom around" dart loop of its own. See
   initSelfloveDragonflyScene() in app.js. Rough placeholder position, drag
   the whole scene into place with ?edit=1 (only the outer
   .selflove-dragonflyscene is listed in EDITABLE_DECOR; the three layers
   move together as one unit). */
.selflove-dragonflyscene {
  position: absolute;
  top: 4517px;
  left: 50%;
  transform: translateX(353px);
  z-index: 2;
  width: 264px;
  line-height: 0;
  cursor: pointer;
}
.selflove-dragonflyscene:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.selflove-scene-layer {
  display: block;
  width: 100%;
  height: auto;
}
.selflove-scene-foliage-layer {
  position: relative;
  z-index: 1;
  pointer-events: none;
}
.selflove-scene-flower-layer {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}
.selflove-scene-dragonfly-layer {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

/* Flower and foliage — deliberately different durations/amplitudes/easing
   so the two never read as moving in sync, even though both are triggered
   by the same click. */
.selflove-dragonflyscene.is-active .selflove-scene-flower-layer .selflove-scene-layer {
  animation: sceneFlowerFloat 3.2s ease-in-out;
}
@keyframes sceneFlowerFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  30% { transform: translateY(-10px) rotate(2deg); }
  60% { transform: translateY(-4px) rotate(-1.5deg); }
  85% { transform: translateY(-8px) rotate(1deg); }
}
.selflove-dragonflyscene.is-active .selflove-scene-foliage-layer .selflove-scene-layer {
  animation: sceneFoliageFloat 4.6s ease-in-out;
}
@keyframes sceneFoliageFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  18% { transform: translateY(-3px) rotate(-1deg); }
  45% { transform: translateY(-11px) rotate(1.5deg); }
  70% { transform: translateY(-6px) rotate(-2deg); }
  90% { transform: translateY(-2px) rotate(0.5deg); }
}

/* Dragonfly — same click-triggered glimmer as the butterfly (shared
   classes, just a new container so it doesn't collide with the butterfly's
   own .selflove-butterfly-glimmer instance), plus its own bigger, faster
   dart/loop flight ("zoom around a bit" per Eloise) layered on top —
   steps(1, end) instead of ease-in-out so it snaps between positions
   stop-motion style (same technique as .decor-moonscatter's jitter)
   rather than gliding, since the smooth version read as an awkward
   wobble rather than a darting zoom. */
.selflove-dragonfly-glimmer {
  position: absolute;
  inset: -30px;
  pointer-events: none;
  z-index: 4;
}
.selflove-dragonflyscene.is-active .selflove-dragonfly-glimmer .selflove-glimmer-block {
  animation: twinkle 2.6s ease-in-out infinite;
}
.selflove-dragonflyscene.is-active .selflove-scene-dragonfly-layer .selflove-scene-layer {
  animation: dragonflyZoom 3.8s steps(1, end);
}
@keyframes dragonflyZoom {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  9% { transform: translate(30px, -22px) rotate(10deg); }
  18% { transform: translate(-8px, -42px) rotate(-4deg); }
  27% { transform: translate(-34px, -14px) rotate(-9deg); }
  36% { transform: translate(-16px, 10px) rotate(-2deg); }
  45% { transform: translate(22px, 6px) rotate(7deg); }
  55% { transform: translate(38px, -20px) rotate(11deg); }
  64% { transform: translate(6px, -38px) rotate(2deg); }
  73% { transform: translate(-28px, -30px) rotate(-8deg); }
  82% { transform: translate(-20px, 2px) rotate(-5deg); }
  91% { transform: translate(14px, -8px) rotate(4deg); }
}

@media (max-width: 1300px) {
  .selflove-dragonflyscene {
    display: none;
  }
}

/* Ghost (Quiet Hollow page) — Halloween decor, same art as the sitewide
   .sky-ghost background but a standalone foreground piece here, much
   bigger and actually interactive. Click plays a spooky "wooo" sound
   (selflove-ghost-wooo.mp3, 6.624s) and, for as long as it plays, the
   ghost gets BOTH the potted-plant shake (booksCottagePlantFlicker, same
   keyframe reused as-is, just looped instead of one-shot since it's much
   shorter than the sound) AND a fireplace-style glow pulse behind it
   (glowPulse, same recipe as .quiethollow-log-glow) — per Eloise, "the pot
   plant and fire for fireplace animation." Both are toggled by is-active
   and stopped by the audio's own "ended" event in initSelfloveGhost()
   (app.js) rather than animationend, since infinite animations never fire
   that event. Rough placeholder position, drag into place with ?edit=1. */
.selflove-ghost {
  position: absolute;
  z-index: 2;
  top: 3715px;
  left: 50%;
  transform: translateX(468px);
  width: 180px;
  line-height: 0;
  cursor: pointer;
}
.selflove-ghost:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.selflove-ghost-img {
  display: block;
  width: 100%;
  height: auto;
  position: relative;
  z-index: 1;
}
.selflove-ghost.is-active .selflove-ghost-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out infinite;
}
.selflove-ghost-glow {
  position: absolute;
  inset: -30%;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 55%, rgba(224, 169, 64, 0), transparent 70%);
  filter: blur(22px);
  opacity: 0;
  transition: opacity 0.7s ease;
  pointer-events: none;
}
.selflove-ghost.is-active .selflove-ghost-glow {
  opacity: 1;
  background: radial-gradient(circle at 50% 55%, rgba(224, 169, 64, 0.5) 0%, rgba(124, 74, 158, 0.28) 45%, transparent 72%);
  animation: glowPulse 2.6s ease-in-out infinite;
}

@media (max-width: 1300px) {
  .selflove-ghost {
    display: none;
  }
}

/* Ghost trio (Market, Almanac/Valley, About pages) — Halloween decor,
   same "wooo" sound + pot-plant-shake + fireplace-glow click combo as
   .selflove-ghost above, but these three float continuously (ambient,
   always-on — per Eloise, "they should float around"), not just on
   click. Float lives on the outer .ghost-piece wrapper (translateY, same
   marketBalloonFloat keyframe reused as-is) while the click flicker
   lives on the inner <img> (scale/rotate) — two different elements so
   the two transforms don't fight each other, same layering trick used
   everywhere else on the site multi-animation pieces need it. Ambient
   blinking eyes are independent of both. See initGhostPieces() in app.js
   (a separate, generalized handler from .selflove-ghost's own
   initSelfloveGhost — same click/sound/cleanup logic, just looped over
   all three instances via querySelectorAll instead of one). Rough
   placeholder positions, drag each into place with ?edit=1. */
.ghost-piece {
  position: absolute;
  z-index: 2;
  line-height: 0;
  cursor: pointer;
}
.ghost-piece:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
/* The ambient float lives on this inner wrapper, not on .ghost-piece
   itself — .ghost-piece is the element ?edit=1 drags/resizes via its own
   inline "top"/"transform: translateX(...)", and a CSS animation running
   on that same element's transform would silently win over the tool's
   inline value every frame, which is exactly why horizontal dragging
   stopped working (only the animation's own vertical bob showed through).
   Keeping position: relative here so the eyelids/glow below (position:
   absolute) resolve their percentages against THIS box and therefore
   move as one unit with the float, instead of staying pinned to the
   outer wrapper while the art bobs underneath them. */
.ghost-float {
  position: relative;
  display: block;
  animation: marketBalloonFloat 5.2s ease-in-out infinite;
}
.ghost-piece-img {
  display: block;
  width: 100%;
  height: auto;
  position: relative;
  z-index: 1;
}
.ghost-piece.is-active .ghost-piece-img {
  animation: booksCottagePlantFlicker 2.976s ease-in-out infinite;
}
.ghost-glow {
  position: absolute;
  inset: -30%;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 55%, rgba(224, 169, 64, 0), transparent 70%);
  filter: blur(22px);
  opacity: 0;
  transition: opacity 0.7s ease;
  pointer-events: none;
}
.ghost-piece.is-active .ghost-glow {
  opacity: 1;
  background: radial-gradient(circle at 50% 55%, rgba(224, 169, 64, 0.5) 0%, rgba(124, 74, 158, 0.28) 45%, transparent 72%);
  animation: glowPulse 2.6s ease-in-out infinite;
}
.ghost-eyelid {
  position: absolute;
  width: 3.6%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  border-radius: 50%;
  z-index: 2;
  animation: ravenBlink 5.5s ease-in-out infinite;
}

.books-ghost-rainbow {
  top: 171px;
  left: 50%;
  transform: translateX(376px);
  width: 190px;
}
.books-ghost-rainbow-eyelid-a { left: 62.7%; top: 16.0%; background: radial-gradient(circle, #fffff6 55%, #e6e6dd 100%); }
.books-ghost-rainbow-eyelid-b { left: 76.1%; top: 16.1%; background: radial-gradient(circle, #fffff6 55%, #e6e6dd 100%); }

.home-ghost-mushroom {
  top: 656px;
  left: 50%;
  transform: translateX(-553px);
  width: 160px;
}
.home-ghost-mushroom-eyelid-a { left: 34.2%; top: 38.4%; background: radial-gradient(circle, #fff9ff 55%, #e6e0e6 100%); }
.home-ghost-mushroom-eyelid-b { left: 71.3%; top: 36.4%; background: radial-gradient(circle, #fff9ff 55%, #e6e0e6 100%); }

.about-ghost-pink {
  top: 81px;
  left: 50%;
  transform: translateX(529px);
  width: 106px;
}
.about-ghost-pink-eyelid-a { left: 40.7%; top: 27.5%; background: radial-gradient(circle, #fff8ed 55%, #e6dfd5 100%); }
.about-ghost-pink-eyelid-b { left: 59.1%; top: 28.1%; background: radial-gradient(circle, #fff8ed 55%, #e6dfd5 100%); }

@media (max-width: 1300px) {
  .books-ghost-rainbow,
  .home-ghost-mushroom,
  .about-ghost-pink {
    display: none;
  }
}

/* Witch reading (Follow Along) — permanent, no data-season. Replaced
   2026-08-19 with cleaner art where her eyes are properly open, so she
   now gets the usual ambient blink (same ravenBlink keyframe/timing as
   every other blinking piece — raven, naming-girl, worm-apple). Her eyes
   are drawn bigger/glossier than those pieces (visible white sclera +
   thick lash line), so the eyelid dot is sized larger (5.2% vs the usual
   3.6%) to fully cover the eye when closed — a smaller dot left a ring of
   white/eyeliner showing and read as a stuck-open eye rather than a
   blink. Center points measured off the new social-witch-reading.webp,
   fill color sampled from her own skin tone next to each eye. No click.
   Rough placeholder position, drag into place with ?edit=1. */
.social-witch {
  position: absolute;
  z-index: 2;
  top: 1053px;
  left: 50%;
  transform: translateX(389px);
  width: 218px;
  line-height: 0;
}
.social-witch-img {
  display: block;
  width: 100%;
  height: auto;
}
.social-witch-eyelid {
  position: absolute;
  width: 5.2%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(circle, #fce0c8 55%, #f0c19f 100%);
  border-radius: 50%;
  animation: ravenBlink 5.5s ease-in-out infinite;
}
.social-witch-eyelid-a { left: 51.52%; top: 34.27%; }
.social-witch-eyelid-b { left: 58.73%; top: 34.23%; }

/* Moon (Follow Along) — click triggers a white/purple illumination glow
   (glowPulse recipe, same as every other glow on the site, recolored)
   plus a handful of misty wisps drifting past it (smokeRise recipe,
   same as .theme-smoke, recolored + repositioned around the moon
   instead of rising from a corner), then settles back on its own. See
   initSocialMoon() in app.js. Rough placeholder position, drag into
   place with ?edit=1. */
.social-moon-scene {
  position: absolute;
  z-index: 2;
  top: 1811px;
  left: 50%;
  transform: translateX(-620px);
  width: 256px;
  line-height: 0;
  cursor: pointer;
}
.social-moon-scene:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.social-moon-img {
  display: block;
  width: 100%;
  height: auto;
  position: relative;
  z-index: 1;
}
.social-moon-glow {
  position: absolute;
  inset: -25%;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0), transparent 70%);
  filter: blur(20px);
  opacity: 0;
  transition: opacity 0.8s ease;
  pointer-events: none;
}
.social-moon-scene.is-active .social-moon-glow {
  opacity: 1;
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.6) 0%, rgba(170, 120, 230, 0.4) 45%, transparent 72%);
  animation: glowPulse 2.6s ease-in-out infinite;
}
.social-moon-mist {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}
.moon-mist-wisp {
  position: absolute;
  bottom: 20%;
  width: 15%;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(225, 215, 250, 0.42), transparent 70%);
  filter: blur(4px);
  opacity: 0;
}
.social-moon-scene.is-active .moon-mist-wisp {
  animation: moonMistRise 3.6s ease-in infinite;
}
.moon-mist-wisp-1 { left: 12%; animation-delay: 0s; }
.moon-mist-wisp-2 { left: 38%; animation-delay: 0.9s; }
.moon-mist-wisp-3 { left: 58%; animation-delay: 1.8s; }
.moon-mist-wisp-4 { left: 80%; animation-delay: 2.7s; }

@keyframes moonMistRise {
  0% { opacity: 0; transform: translateY(0) scale(0.6); }
  20% { opacity: 0.5; }
  80% { opacity: 0; }
  100% { opacity: 0; transform: translateY(-65%) scale(1.4); }
}

@media (max-width: 1300px) {
  .social-witch,
  .social-moon-scene {
    display: none;
  }
}

/* Skeletons — Halloween decor, one on Quiet Hollow, one on Follow Along,
   one on Naming Well. Ambient blink (same ravenBlink dot technique as
   every other blinking piece, colored to match each skeleton's own bone
   tone). Click plays a bone-jingle sound and rattles the whole figure for
   its real length (1.1755s, measured via ffprobe) — the shake animation
   lives on an inner .skeleton-shake wrapper, never on .skeleton-piece
   itself, since that's the element ?edit=1 drags via inline
   style.transform (see the ghost-piece fix earlier in this file for why
   that separation matters). See initSkeletons() in app.js. */
.skeleton-piece {
  position: absolute;
  z-index: 2;
  line-height: 0;
  cursor: pointer;
}
.skeleton-piece:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 6px;
  border-radius: 6px;
}
.skeleton-shake {
  position: relative;
  display: block;
}
.skeleton-piece.is-rattling .skeleton-shake {
  animation: skeletonRattle 1.1755s ease-in-out;
}
.skeleton-piece-img {
  display: block;
  width: 100%;
  height: auto;
  position: relative;
  z-index: 1;
}
.skeleton-eyelid {
  position: absolute;
  transform: translate(-50%, -50%) scaleY(0);
  transform-origin: 50% 50%;
  border-radius: 50%;
  z-index: 2;
  animation: ravenBlink 5.5s ease-in-out infinite;
}

@keyframes skeletonRattle {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  10% { transform: translate(-3px, 1px) rotate(-3deg); }
  20% { transform: translate(3px, -1px) rotate(3deg); }
  30% { transform: translate(-3px, 2px) rotate(-2deg); }
  40% { transform: translate(3px, -2px) rotate(2deg); }
  50% { transform: translate(-2px, 1px) rotate(-3deg); }
  60% { transform: translate(2px, -1px) rotate(3deg); }
  70% { transform: translate(-2px, 2px) rotate(-2deg); }
  80% { transform: translate(2px, -2px) rotate(2deg); }
  90% { transform: translate(-1px, 1px) rotate(-1deg); }
}

/* Girly skeleton (Quiet Hollow) — orange dress + bow-topped hat. */
.quiethollow-skeleton-girly {
  top: 2054px;
  left: 50%;
  transform: translateX(-605px);
  width: 168px;
}
.quiethollow-skeleton-girly-eyelid-a {
  left: 40.63%;
  top: 27.84%;
  width: 17%;
  height: 8%;
  background: radial-gradient(circle, #fff4f0 55%, #f8d9d6 100%);
}
.quiethollow-skeleton-girly-eyelid-b {
  left: 60.42%;
  top: 25.80%;
  width: 17%;
  height: 8%;
  background: radial-gradient(circle, #fff4f0 55%, #f8d9d6 100%);
}

/* Waving skeleton (Follow Along) — floral witch hat, plain bones. */
.social-skeleton-waving {
  top: 1183px;
  left: 50%;
  transform: translateX(-559px);
  width: 165px;
}
.social-skeleton-waving-eyelid-a {
  left: 47.71%;
  top: 24.97%;
  width: 13%;
  height: 6.5%;
  background: radial-gradient(circle, #fdf1ea 55%, #f0c7b5 100%);
}
.social-skeleton-waving-eyelid-b {
  left: 67.08%;
  top: 23.80%;
  width: 13%;
  height: 6.5%;
  background: radial-gradient(circle, #fdf1ea 55%, #f0c7b5 100%);
}

/* Trick-or-treat skeleton (Naming Well) — carrying two candy-filled
   jack-o-lantern buckets. Head is tilted/turned in the source art, so the
   two eye sockets are genuinely different sizes — eyelid dots sized to
   match each one individually rather than sharing one size. Re-measured
   2026-08-19 as part of a site-wide blink-coverage audit — the original
   placeholder position/size for eyelid-a was off the socket entirely
   (landing in empty space beside the head) and eyelid-b was oversized
   enough to spill onto the mouth; both re-measured directly off
   naming-skeleton-candy.webp. */
.naming-skeleton-candy {
  top: 465px;
  left: 50%;
  transform: translateX(-473px);
  width: 168px;
}
.naming-skeleton-candy-eyelid-a {
  left: 45.4%;
  top: 25.85%;
  width: 11.5%;
  height: 8.2%;
  background: radial-gradient(circle, #fff0f5 55%, #ffb8cc 100%);
}
.naming-skeleton-candy-eyelid-b {
  left: 34.7%;
  top: 31.55%;
  width: 7.3%;
  height: 7.1%;
  background: radial-gradient(circle, #fff0f5 55%, #ffb8cc 100%);
}

@media (max-width: 1300px) {
  .quiethollow-skeleton-girly,
  .social-skeleton-waving,
  .naming-skeleton-candy {
    display: none;
  }
}
