/* ============================================================
   THEME: night in Istanbul + Iznik tiles
   Cobalt, turquoise, coral and emerald over enameled white,
   with gold on the frames and mosaic lanterns.
   ============================================================ */

:root {
  --night: #0B1434;
  --night-2: #16275E;
  --night-deep: #080F2A;
  --cobalt: #1B3F8B;
  --turquoise: #2A9DA8;
  --turquoise-light: #8FDCDF;
  --coral: #C8382E;
  --emerald: #2F7D5B;
  --enamel: #F4F6F2;
  --gold: #D9A441;
  --ink: #14224D;
  --ink-soft: #4A5580;
  --moonlight: #C9D3F0;

  --font-title: "Marcellus", "Times New Roman", serif;
  --font-text: "Alegreya Sans", system-ui, sans-serif;

  /* Iznik tile (the same one drawn in ornaments.js) */
  --tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Crect width='40' height='40' fill='%23F4F6F2'/%3E%3Cg fill='%231B3F8B'%3E%3Cpath d='M20 5C25 11 25 15 20 20C15 15 15 11 20 5Z'/%3E%3Cpath d='M20 35C25 29 25 25 20 20C15 25 15 29 20 35Z'/%3E%3Cpath d='M5 20C11 15 15 15 20 20C15 25 11 25 5 20Z'/%3E%3Cpath d='M35 20C29 15 25 15 20 20C25 25 29 25 35 20Z'/%3E%3C/g%3E%3Cg fill='%232A9DA8'%3E%3Ccircle r='7'/%3E%3Ccircle cx='40' r='7'/%3E%3Ccircle cy='40' r='7'/%3E%3Ccircle cx='40' cy='40' r='7'/%3E%3C/g%3E%3Cg fill='%232F7D5B'%3E%3Ccircle cx='20' r='2.2'/%3E%3Ccircle cx='20' cy='40' r='2.2'/%3E%3Ccircle cy='20' r='2.2'/%3E%3Ccircle cx='40' cy='20' r='2.2'/%3E%3C/g%3E%3Ccircle cx='20' cy='20' r='3.2' fill='%23C8382E'/%3E%3C/svg%3E");
  --tile-coral: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Crect width='40' height='40' fill='%23F4F6F2'/%3E%3Cg fill='%23C8382E'%3E%3Cpath d='M20 5C25 11 25 15 20 20C15 15 15 11 20 5Z'/%3E%3Cpath d='M20 35C25 29 25 25 20 20C15 25 15 29 20 35Z'/%3E%3Cpath d='M5 20C11 15 15 15 20 20C15 25 11 25 5 20Z'/%3E%3Cpath d='M35 20C29 15 25 15 20 20C25 25 29 25 35 20Z'/%3E%3C/g%3E%3Cg fill='%231B3F8B'%3E%3Ccircle r='7'/%3E%3Ccircle cx='40' r='7'/%3E%3Ccircle cy='40' r='7'/%3E%3Ccircle cx='40' cy='40' r='7'/%3E%3C/g%3E%3Cg fill='%232F7D5B'%3E%3Ccircle cx='20' r='2.2'/%3E%3Ccircle cx='20' cy='40' r='2.2'/%3E%3Ccircle cy='20' r='2.2'/%3E%3Ccircle cx='40' cy='20' r='2.2'/%3E%3C/g%3E%3Ccircle cx='20' cy='20' r='3.2' fill='%23D9A441'/%3E%3C/svg%3E");
  /* Very subtle golden lattice for the night background */
  --lattice: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64' viewBox='0 0 64 64'%3E%3Cg fill='none' stroke='%23D9A441' stroke-opacity='.08' stroke-width='1.2'%3E%3Crect x='20' y='20' width='24' height='24'/%3E%3Crect x='20' y='20' width='24' height='24' transform='rotate(45 32 32)'/%3E%3Ccircle cx='32' cy='32' r='4'/%3E%3Ccircle cx='0' cy='0' r='9'/%3E%3Ccircle cx='64' cy='0' r='9'/%3E%3Ccircle cx='0' cy='64' r='9'/%3E%3Ccircle cx='64' cy='64' r='9'/%3E%3C/g%3E%3C/svg%3E");
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

/* The site is meant to be played with like a toy, not like a page:
   no selecting text, blue flash on tap, long-press menu or dragging images.
   (the right-click menu and dragging in Firefox are blocked in js/common.js) */
html {
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
img, a, svg { -webkit-user-drag: none; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-text);
  font-size: 1.125rem;
  line-height: 1.55;
  color: var(--enamel);
  background: var(--lattice), var(--night-deep);
  /* Safety net on phones: nothing pushes the page sideways.
     "clip" (and not "hidden") so it doesn't break the position: sticky of the TV guide and the shelf. */
  overflow-x: clip;
}

/* Single-screen pages (entrance and hall, challenge floors, balcony: body.single-screen).
   Everything is drawn to fit the screen exactly, so the page never scrolls or "bounces":
   - the height is the visible screen's (svh; the phone's 100vh is taller than it and allowed scrolling);
   - overscroll-behavior: none removes pull-to-refresh and the edge bounce.
   What scrolls on purpose inside (the DM chat, the challenge window) keeps scrolling. */
html:has(> body.single-screen) {
  overflow: hidden;
  overscroll-behavior: none;
}
body.single-screen {
  height: 100vh;
  height: 100svh;
  min-height: 0;
  overflow: hidden;
  overscroll-behavior: none;
}

:focus-visible { outline: 3px solid var(--gold); outline-offset: 4px; }

/* ---------- Common pieces ---------- */

/* Band of tiles between the sky and the hall, like on the walls of Topkapı */
.frieze {
  height: 36px;
  background: var(--tile) 0 0 / 36px 36px repeat-x;
  border-block: 3px solid var(--gold);
}

/* Tile frame with an enameled white panel inside */
.frame {
  padding: 14px;
  background: var(--tile) 0 0 / 32px 32px;
  border: 3px solid var(--gold);
  border-radius: 14px;
  box-shadow: 0 30px 70px rgb(0 0 0 / 0.55);
}
.enamel {
  color: var(--ink);
  background: var(--enamel);
  border: 2px solid var(--gold);
  border-radius: 8px;
}

.divider { display: block; width: 160px; margin: 14px auto; }
.divider svg { display: block; width: 100%; }

.hint { margin: 0; text-align: center; font-style: italic; color: var(--moonlight); }

/* ---------- Mosaic lanterns ---------- */

.lanterns {
  display: flex;
  justify-content: space-around;
  align-items: flex-start;
  max-width: 1100px;
  margin: 0 auto;
  pointer-events: none;
}

/* Each lantern hangs lower or higher (--drop, times --drop-scale: a page can pull them all up) but never floats: a
   cord runs from it all the way up to the top of its row (the top of the screen, a ceiling, or into the balcony's
   eave), and it sways from that point */
.lantern {
  position: relative;
  width: calc(clamp(42px, 6vw, 70px) * var(--scale, 1));
  margin-top: calc(var(--drop, 0px) * var(--drop-scale, 1));
  transform-origin: 50% calc(-1 * var(--drop, 0px) * var(--drop-scale, 1));
  animation: sway 5s ease-in-out var(--delay, 0s) infinite alternate;
}
/* The cord, in one piece: from the top of the row down to the lantern's golden cap (y=38 of its 142-tall drawing,
   26.8% of its height), behind the lantern */
.lantern::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: calc(-1 * var(--drop, 0px) * var(--drop-scale, 1) - 2px);
  bottom: 73.2%;
  left: 50%;
  width: max(2px, 4%);
  translate: -50% 0;
  background: #d9a441; /* gold, like the hall's lantern cords */
}
.lantern svg {
  display: block;
  width: 100%;
  filter: drop-shadow(0 0 12px rgb(255 190 100 / 0.6)) drop-shadow(0 0 34px rgb(255 150 60 / 0.3));
}
.lanterns-small .lantern { width: calc(clamp(30px, 4.5vw, 50px) * var(--scale, 1)); }

@keyframes sway {
  from { transform: rotate(-3deg); }
  to { transform: rotate(3deg); }
}

/* ---------- Entrance: Guangdong night sky ---------- */

.sky {
  /* The moon's place and size, shared by .moon, .moon-halo and the entrance's title (which always starts below it) */
  --moon-top: clamp(24px, 6vh, 56px);
  --moon-size: clamp(64px, 9vw, 110px);
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: clamp(560px, 88vh, 780px);
  overflow: hidden;
  background:
    radial-gradient(1.5px 1.5px at 12% 22%, #fff9 50%, transparent 51%),
    radial-gradient(1px 1px at 28% 12%, #fff8 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 44% 30%, #fff7 50%, transparent 51%),
    radial-gradient(1px 1px at 58% 8%, #fff9 50%, transparent 51%),
    radial-gradient(1px 1px at 66% 36%, #fff6 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 90% 44%, #fff8 50%, transparent 51%),
    radial-gradient(1px 1px at 6% 48%, #fff7 50%, transparent 51%),
    radial-gradient(1px 1px at 36% 52%, #fff5 50%, transparent 51%),
    radial-gradient(ellipse at 78% 12%, #2a4a9a 0, transparent 50%),
    linear-gradient(var(--night) 0%, var(--night-2) 65%, #22397c 100%);
}

.moon {
  position: absolute;
  top: var(--moon-top);
  right: 9%;
  width: var(--moon-size);
  filter: drop-shadow(0 0 24px rgb(246 231 190 / 0.45));
}

.sky-text {
  position: relative;
  z-index: 2;
  padding: 240px 16px 0;
  text-align: center;
}
.sky-text .ornament { display: block; width: 180px; margin: 0 auto 8px; }

.party-title {
  max-width: 13ch;
  margin: 0 auto;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: clamp(2.6rem, 8vw, 5.2rem);
  line-height: 1.04;
  letter-spacing: 0.01em;
  color: var(--enamel);
  /* Moonlit gold letters: the gradient shows through the text's shape (background-clip: text).
     The glow is a drop-shadow, because a text-shadow would show through the transparent letters */
  background: linear-gradient(180deg, #fffaf0 0%, #f9e6b8 50%, var(--gold) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 0 18px rgb(255 255 255 / 0.45));
}

.subtitle {
  max-width: 32em;
  margin: 18px auto 0;
  color: var(--moonlight);
  text-align: center;
}

.city-near {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: clamp(110px, 18vw, 210px);
  margin-top: 20px;
}

/* The Pearl River with golden reflections of the lanterns */
.river {
  height: 52px;
  background:
    repeating-linear-gradient(to bottom, transparent 0 8px, rgb(217 164 65 / 0.13) 8px 9px),
    linear-gradient(#0a1233, var(--night-deep));
}

/* ---------- Hall: arched doors ---------- */

/* ---------- A palace floor seen from inside (one screen, no scrolling) ---------- */

/* After the entrance, the sky leaves the scene: she's already inside the palace */
body:not(.before-party) .sky,
body:not(.before-party) .frieze { display: none; }

.tower {
  --floor-line: 78%; /* where the wall meets the floor */
  position: relative;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  background: #000;
}

.floor-scene {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 50% 30%, rgb(255 190 110 / 0.13), transparent 60%),
    var(--lattice),
    linear-gradient(#1a2860, #101942 var(--floor-line));
}

/* Ceiling of wooden beams with a coral tile frieze */
.ceiling {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 9%;
  background: repeating-linear-gradient(90deg, #2b170f 0 22px, #3d2417 22px 110px);
  border-bottom: 3px solid var(--gold);
  box-shadow: 0 12px 30px rgb(0 0 0 / 0.6);
}
.ceiling::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 16px;
  margin-top: 3px;
  background: var(--tile-coral) 0 0 / 16px 16px repeat-x;
  border-bottom: 3px solid var(--gold);
}

/* Lanterns hanging from the ceiling, only on the sides so they don't cover the title */
.floor-scene .lanterns {
  position: absolute;
  top: calc(9% + 22px);
  left: 0;
  right: 0;
  max-width: none;
  padding: 0 4%;
  justify-content: space-between;
}
.floor-scene .lantern:nth-child(3),
.floor-scene .lantern:nth-child(4) { visibility: hidden; }

/* Tile wainscot on the lower part of the wall, in half-shadow */
.wainscot {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% - var(--floor-line));
  height: 15%;
  background: linear-gradient(rgb(8 15 42 / 0.35), rgb(8 15 42 / 0.6)), var(--tile) 0 0 / 34px 34px;
  border-block: 3px solid var(--gold);
}

/* Floor tiles in perspective, with the red carpet leading to the doors */
.flooring {
  position: absolute;
  top: var(--floor-line);
  left: -60%;
  right: -60%;
  height: 70%;
  transform-origin: 50% 0;
  transform: perspective(520px) rotateX(58deg);
  background:
    linear-gradient(rgb(0 0 0 / 0.6), transparent 55%),
    linear-gradient(90deg, transparent calc(50% - 150px), var(--gold) 0 calc(50% - 142px), #8e2a22 0 calc(50% + 142px), var(--gold) 0 calc(50% + 150px), transparent 0),
    repeating-conic-gradient(from 45deg, #22306a 0 25%, #18234f 0 50%) 50% 0 / 90px 90px;
}

/* Welcome sign hanging on the wall */
/* The floor's title: sits right above the content (the rooms or the challenge), not stuck to the ceiling */
.floor-column,
.challenge-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.floor-sign {
  max-width: min(620px, 64vw);
  margin-bottom: clamp(14px, 3vh, 32px);
  text-align: center;
}
.floor-title {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: clamp(1.6rem, min(4.2vw, 6vh), 3.2rem);
  line-height: 1.08;
  text-shadow: 0 0 30px rgb(217 164 65 / 0.4);
}
/* The hall's title ("Hoş geldin, Tyler!"): golden letters with a shine passing slowly,
   dark relief underneath, a golden halo and a thin golden line underneath.
   (With the text transparent, the relief and halo come from the filter: a text-shadow would show through the letters) */
.hall .floor-title {
  font-size: clamp(2rem, min(5.4vw, 7.5vh), 4rem);
  color: transparent;
  background: linear-gradient(100deg, #b98a2e 0%, #f7e3a8 20%, #D9A441 38%, #fff6d8 50%, #D9A441 62%, #f7e3a8 80%, #b98a2e 100%);
  background-size: 250% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  text-shadow: none;
  filter: drop-shadow(0 2px 0 #6e4a12) drop-shadow(0 0 18px rgb(217 164 65 / 0.55));
  animation: title-shine 6s linear infinite;
}
@keyframes title-shine {
  from { background-position: 100% 0; }
  to { background-position: -150% 0; }
}
/* the gold line under the title, fading out at both ends, with a gold diamond inlaid with red in its middle (the
   layers, from the top one: the diamond, the line) */
.hall .floor-title::after {
  content: "";
  display: block;
  width: min(60%, 320px);
  height: 16px;
  margin: 0.3em auto 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M6 0L12 6L6 12L0 6Z' fill='%23f3d58a'/%3E%3Cpath d='M6 3.2L8.8 6L6 8.8L3.2 6Z' fill='%238e1a14'/%3E%3C/svg%3E") 50% 50% / 16px 16px no-repeat,
    linear-gradient(90deg, transparent, var(--gold) 30%, #fff1c2 50%, var(--gold) 70%, transparent) 50% 50% / 100% 2px no-repeat;
}
@media (prefers-reduced-motion: reduce) {
  .hall .floor-title { animation: none; background-position: 50% 0; }
}
/* A soft shadow strip behind the title's words (not its gold line below them): a dark rectangle, its edges blurred,
   and all four sides melting away (a fade over the outer 30% of its length and 22% of its height, the two crossed,
   on top of the blur), centered on the words. The wrapper takes the title's size, so its "em" is the title's: the
   strip ends within the gap above the line (the line's 16px, with its ornament, and its 0.3em gap are at the bottom).
   The challenge floors' titles have it too (.tower: their size and line are set with the rest of the tower) */
:is(.hall, .tower) .title-strip { position: relative; isolation: isolate; }
.hall .title-strip { font-size: clamp(1.6rem, min(5.4vw, 6svh), 3.4rem); }
:is(.hall, .tower) .title-strip .floor-title { font-size: 1em; }
:is(.hall, .tower) .title-strip::before {
  content: "";
  position: absolute;
  inset: -0.22em -16% calc(0.08em + 16px); /* as far above the words as below them (0.22em into the line's 0.3em gap) */
  z-index: -1;
  background: rgb(0 0 0 / 0.5);
  filter: blur(6px);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent),
    linear-gradient(transparent, #000 22%, #000 78%, transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent),
    linear-gradient(transparent, #000 22%, #000 78%, transparent);
  mask-composite: intersect;
  pointer-events: none;
}
/* After the final letter (body.after-party, from the script at the top of index.html): a coral fabric
   banner with a gold border and swallowtail ends, a little crooked, hanging above the title */
.party-banner { display: none; }
body.after-party .party-banner {
  display: block;
  width: fit-content;
  margin: 0 auto clamp(10px, 2vh, 20px);
  padding: 0.35em 2.2em 0.4em;
  font-family: var(--font-title);
  font-size: clamp(1rem, min(2.6vw, 3.6vh), 1.6rem);
  color: #fff6d8;
  text-shadow: 0 1px 0 #6e1d17;
  background: linear-gradient(#e0574a, var(--coral) 55%, #9e2a22);
  border-block: 2px solid var(--gold);
  clip-path: polygon(0 0, 100% 0, calc(100% - 0.9em) 50%, 100% 100%, 0 100%, 0.9em 50%);
  rotate: -2deg;
}
/* In the hall it hangs where the title was, out of its flow: the title keeps the same height as before the party,
   so the hall is laid out the same (js/hall.js sizes everything around the title) and nothing changes size */
body.after-party .hall .party-banner {
  position: absolute;
  top: 0;
  left: 50%;
  translate: -50% 0;
  margin: 0;
  white-space: nowrap;
}
/* ...and takes the welcome title's place: the title is only hidden, still holding its room, so the hall stays laid
   out the same */
body.after-party .hall .title-strip { visibility: hidden; }

/* Bunting: right below the hall's ceiling, across the whole screen (drawn in js/hall.js, which also
   sets --ceiling, the ceiling's height) */
.bunting,
.balloons { display: none; }
body.after-party .bunting {
  position: absolute;
  top: var(--ceiling, 9%);
  left: 0;
  z-index: 3;
  display: block;
  width: 100%;
  height: clamp(60px, 14svh, 130px);
  pointer-events: none;
}

/* Balloons: a bunch on each side of the Challenges door, tied to a weight on the floor (js/hall.js places them)
   and swaying slowly (only the transform animates, so the browser doesn't redraw anything) */
body.after-party .balloons {
  position: absolute;
  display: block;
  pointer-events: none;
  transform-origin: 50% 100%;
  animation: balloons-sway 4s ease-in-out infinite alternate;
}
.balloons svg { display: block; width: 100%; }
.balloons-right { animation-delay: -2s; }
@keyframes balloons-sway {
  from { rotate: -3deg; }
  to { rotate: 3deg; }
}
@media (prefers-reduced-motion: reduce) {
  body.after-party .balloons { animation: none; }
}

.floor-hint {
  margin: 8px 0 0;
  font-size: clamp(0.95rem, 2.2vh, 1.1rem);
  color: var(--moonlight);
}

/* The doors and the stairs stand on the floor line */
.floor-row {
  position: absolute;
  left: clamp(52px, 5vw, 80px); /* room for the floor rail, the same on both sides */
  right: clamp(52px, 5vw, 80px);
  bottom: calc(100% - var(--floor-line));
  display: flex;
  align-items: flex-end;
  justify-content: space-evenly;
  gap: 2vw;
}

.doors {
  display: grid;
  grid-template-columns: repeat(3, min(14vw, 27svh, 200px));
  gap: clamp(12px, 2.4vw, 40px);
}

/* ---------- The hall (layout: docs/mockups/hall-layouts.html) ---------- */

/* The scene fills the screen: js/hall.js draws it at the screen's size and places the doors and the title */
.hall { background: #1e0f0c; }
.hall-stage { position: absolute; inset: 0; }
.hall-scene { position: absolute; inset: 0; width: 100%; height: 100%; }
/* the layer in front of the doors (the mezzanines' railings, the columns and their banners, the gallery stairs): it lets the clicks through to the doors */
.hall-front { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

/* The Art Gallery's door, over the mosaic window (js/hall.js places it): only after the final letter, when the window
   glows open */
.gallery-door { display: none; position: absolute; z-index: 1; border-radius: 50% 50% 0 0 / 31% 31% 0 0; }
body.after-party .gallery-door { display: block; }
.gallery-door:focus-visible { outline: 3px solid var(--gold); outline-offset: 4px; }
/* Going through it (js/hall.js): the gold motes of its steps (.gallery-mote) rising from below the screen, one after
   another, each bigger than the last, until the biggest cover the whole screen, in the time the door holds the page
   change. All of them drawn in this one canvas over the whole screen, never catching a click */
.gallery-enter-bubbles {
  position: fixed;
  inset: 0;
  z-index: 60;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* 凯文 on the rug (js/hall.js places and sizes him): a click makes him meow and wiggle, like in the courtyard */
.hall-cat {
  all: unset;
  position: absolute;
  z-index: 1;
  /* No size until js/hall.js places him (inline width and height): otherwise his drawing, already in the HTML,
     would fill the whole screen for the moment before the hall's first draw */
  width: 0;
  height: 0;
  cursor: pointer;
}
.hall-cat:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; border-radius: 6px; }
.hall-cat-body { display: block; width: 100%; height: 100%; transform-origin: 50% 100%; }
.hall-cat-body svg { display: block; width: 100%; height: 100%; overflow: visible; }
/* the same few quick bobs as the courtyard's (cat-wiggle), in his own size */
.hall-cat-body.meowing { animation: hall-cat-wiggle 0.5s ease-in-out; }
@keyframes hall-cat-wiggle {
  20% { transform: translateY(-5%); }
  40% { transform: translateY(0); }
  60% { transform: translateY(-3.5%); }
  80% { transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) { .hall-cat-body.meowing { animation: none; } }
/* the "Meow!" word floating up from his head (the courtyard's cat-meow-float), its size set by js/hall.js */
.hall-meow {
  position: absolute;
  bottom: 100%;
  left: 50%;
  translate: calc(-50% + var(--shift, 0%)) 0;
  font-family: var(--font-title);
  white-space: nowrap;
  color: #fff6d8;
  pointer-events: none;
  transform-origin: 50% 100%;
  animation: cat-meow-float 1.2s ease-out forwards;
}

/* Lanterns hanging from the ceiling, swaying from the top of their cord; their glow on the wall breathes */
.hall-lantern {
  transform-box: fill-box;
  transform-origin: 50% 0;
  animation: sway 5s ease-in-out var(--delay, 0s) infinite alternate;
}
.lantern-glow { animation: flicker 2.6s ease-in-out infinite alternate; }

/* The title: js/hall.js sets where it sits (on the middle wall, or across the top on a phone) */
.hall .floor-sign {
  position: absolute;
  left: 50%;
  z-index: 4;
  width: max-content;
  margin: 0;
  translate: -50% 0;
}
.hall .floor-title { font-size: clamp(1.6rem, min(5.4vw, 6svh), 3.4rem); touch-action: manipulation; }
/* The title's cheat word ("Welcome", js/hall.js): inline, not inline-block like the floors' ones, so the title's gold
   (its background, painted through the letters) covers it too; the title takes the quick-taps-don't-zoom instead */
.hall .floor-title .cheat-word { display: inline; }
.hall .floor-hint { text-shadow: 0 1px 6px #000; }

/* ---------- The doors: links laid over the scene (js/hall.js sets their place and size) ---------- */

.hall .room,
.hall-challenges {
  position: absolute;
  color: var(--ink);
  text-decoration: none;
}
/* the door's shadow and, on hover, its warm glow: on its drawing only, not its name sign (which has its own shadow) */
:is(.hall .room, .hall-challenges) .room-drawing {
  filter: drop-shadow(0 10px 22px rgb(0 0 0 / 0.45));
  transition: filter 0.3s ease;
}
:is(.hall .room, .hall-challenges):hover .room-drawing { filter: drop-shadow(0 10px 22px rgb(0 0 0 / 0.45)) drop-shadow(0 0 26px rgb(255 190 110 / 0.55)); }
/* The gate stands at the back of its niche: only a small shadow, or it darkens the niche's walls around it */
.hall .hall-challenges .room-drawing { filter: drop-shadow(0 3px 6px rgb(0 0 0 / 0.3)); }
.hall .hall-challenges:hover .room-drawing { filter: drop-shadow(0 3px 6px rgb(0 0 0 / 0.3)) drop-shadow(0 0 26px rgb(255 190 110 / 0.55)); }
:is(.hall .room, .hall-challenges):focus-visible { outline: none; }
:is(.hall .room, .hall-challenges):focus-visible .room-sign { outline: 3px solid var(--enamel); outline-offset: 3px; }
.room-drawing { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }

/* Silk curtains: closed until the room is visited; a little peek on hover; drawn open once visited.
   They open from the outer edges of the doorway (their transform-origin is set in js/hall.js) */
.curtain { transform-box: view-box; transition: transform 1s cubic-bezier(0.3, 0.7, 0.2, 1); }
.room:not(.visited):hover .curtain { transform: scaleX(0.84); }
.room.visited .curtain { transform: scaleX(0.3); }

/* The room's light: dark until visited, then warm and flickering a little */
.room-light { opacity: 0; transition: opacity 0.8s ease 0.3s; }
.room.visited .room-light { opacity: 1; animation: flicker 2.6s ease-in-out 1.1s infinite alternate; }
@media (prefers-reduced-motion: reduce) {
  .room.visited .room-light, .lantern-glow { animation: none; }
}

/* The couplet banners on the handrail sway gently from their nail (the top middle of the drawing: js/hall.js
   staggers the two so they don't move together) */
.banner-sway {
  transform-box: fill-box;
  transform-origin: 50% 0;
  animation: banner-sway 4s ease-in-out infinite alternate;
}
@keyframes banner-sway {
  from { transform: rotate(-3deg); }
  to { transform: rotate(3deg); }
}
@media (prefers-reduced-motion: reduce) {
  .banner-sway { animation: none; }
}

/* The name sign above each door: a gold board with dark letters hanging on two little chains, like the stairs'
   signs (.stairs-label) on the challenge floors, with the door's icon after the name (js/hall.js adds the icon and
   sizes the text to the door). Its bottom edge sits at "top" (it's moved up its own height) */
.room-sign {
  position: absolute;
  top: 2%;
  left: 50%;
  display: flex;
  align-items: center;
  justify-content: center; /* (the room signs are all made as wide as the widest: js/hall.js) */
  gap: 0.35em;
  width: max-content;
  padding: 0.3em 0.6em 0.35em;
  translate: -50% -100%;
  font-family: var(--font-text);
  font-weight: 700;
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  color: #3d2708;
  /* letters engraved in the metal: a light edge under them */
  text-shadow: 0 1px 0 rgb(255 244 210 / 0.55);
  /* a faint brushed-metal grain over the gold */
  background:
    repeating-linear-gradient(rgb(255 255 255 / 0.07) 0 1px, transparent 1px 3px),
    linear-gradient(#f1d596, var(--gold) 60%, #a57d2e);
  border: 1px solid #8a6420;
  border-radius: 4px;
  /* a bright top edge and a darker bottom one (a bevel), a light inner edge with a fine engraved line inside it,
     and its shadow on the wall */
  box-shadow:
    inset 0 1px 0 rgb(255 248 220 / 0.7),
    inset 0 -1px 0 rgb(110 74 18 / 0.5),
    inset 0 0 0 2px rgb(255 240 200 / 0.35),
    inset 0 0 0 3px rgb(138 100 32 / 0.45),
    0 3px 8px rgb(0 0 0 / 0.5);
  pointer-events: none;
}
/* each chain hangs from a gold nail in the wall, the same as the couplet banners' (js/hall.js): a plain gold ball
   shaded like the hall's gold (#hall-gold); the chain starts behind its middle */
.room-sign::before,
.room-sign::after {
  content: "";
  position: absolute;
  bottom: 100%;
  width: 8px;
  height: 17px;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3ClinearGradient id='g' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='%23a57d2e'/%3E%3Cstop offset='.5' stop-color='%23f7e3a8'/%3E%3Cstop offset='1' stop-color='%23b98a2e'/%3E%3C/linearGradient%3E%3Ccircle cx='4' cy='4' r='4' fill='url(%23g)'/%3E%3C/svg%3E") 50% 0 / 8px 8px no-repeat,
    repeating-linear-gradient(to bottom, var(--gold) 0 3px, #8a6420 3px 4px) 50% 100% / 2px calc(100% - 4px) no-repeat;
}
.room-sign::before { left: calc(16% - 3px); }
.room-sign::after { right: calc(16% - 3px); }
.sign-icon { flex: none; width: 1.15em; height: 1.15em; filter: drop-shadow(0 1px 0 rgb(255 244 210 / 0.55)); }/* the name, centered on the board: trimmed to its letters (from the capitals' top to the baseline), as the font keeps
   more room above them than below; the board then gets the same padding above and below */
@supports (text-box: trim-both cap alphabetic) {
  .sign-name { text-box: trim-both cap alphabetic; }
  .room-sign { padding-block: 0.3em; }
}
/* the gate's sign hangs where its plaque used to be, over the top of its surround */
.hall-challenges .room-sign { top: 20.9%; }

/* The Challenges gate: shut while locked, with light leaking through the seam; the padlock hangs on the
   knockers. Unlocking: the padlock falls, then the two leaves swing in on their hinges */
.hall-challenges .padlock { top: 71%; width: clamp(30px, 22%, 80px); }
.gate-leaf { transform-box: view-box; transition: transform 1.6s cubic-bezier(0.4, 0.1, 0.3, 1); }
.hall-challenges:not(.locked) .gate-leaf,
.hall-challenges.unlocking .gate-leaf { transform: scaleX(0.14); }
.hall-challenges.unlocking .gate-leaf { transition-delay: 1.1s; }
.gate-seam { animation: flicker 2.2s ease-in-out infinite alternate; }
/* The seam's two halves ride on the leaves' inner edges: a leaf shrinking to 0.14 of its 50 width from its hinge
   brings its inner edge from 100 to 57 (left) or 143 (right), so each half slides 43 with the same timing and easing
   as the leaves, and stays there once the gate is open */
.seam-half { transition: transform 1.6s cubic-bezier(0.4, 0.1, 0.3, 1); }
.hall-challenges:not(.locked) .seam-left,
.hall-challenges.unlocking .seam-left { transform: translateX(-43px); }
.hall-challenges:not(.locked) .seam-right,
.hall-challenges.unlocking .seam-right { transform: translateX(43px); }
.hall-challenges.unlocking .seam-half { transition-delay: 1.1s; }
/* The light under the shut leaves fades as they part (an animation, not a transition, so it's sure to play out) and
   stays gone once open */
.hall-challenges:not(.locked):not(.unlocking) .seam-floor { opacity: 0; }
.hall-challenges.unlocking .seam-floor { animation: seam-fade 0.5s ease-in 1.1s both; }
@keyframes seam-fade {
  from { opacity: 0.6; }
  to { opacity: 0; }
}
.hall-challenges .door-glow { transition: opacity 0.8s ease; }
.hall-challenges:not(.locked) .door-glow { opacity: 0.3; } /* (dim: the stairwell stays dark) */
.hall-challenges .arrows-up { opacity: 0; transition: opacity 0.6s ease; }
.hall-challenges:not(.locked) .arrows-up { opacity: 1; animation: arrows-bob 1.6s ease-in-out infinite; }
.hall-challenges.just-opened { animation: beckon 1.3s ease-in-out 3; }
@keyframes arrows-bob {
  50% { transform: translateY(-5px); }
}
@media (prefers-reduced-motion: reduce) {
  .hall-challenges .arrows-up { animation: none; }
}
/* The gate's 4 wall torches (js/hall.js), one per room key: out until that room is visited, then the flame
   dances. The last key's torch flares up while the gate unlocks */
.gate-torch.lit .stairs-flame { opacity: 1; animation: gate-flame-flicker 1.2s linear calc(-1 * var(--flame-delay, 0s)) infinite; }
/* Fire flickers rather than breathes: small, quick, uneven jumps, the base planted in the cup and mostly the tip
   wavering (the lean, from the bottom). One way round and looped, at an even speed */
@keyframes gate-flame-flicker {
  0% { transform: scale(1, 1) skewX(0deg); }
  13% { transform: scale(0.98, 1.04) skewX(1.5deg); }
  27% { transform: scale(1.01, 0.97) skewX(-1deg); }
  41% { transform: scale(0.99, 1.05) skewX(-2deg); }
  58% { transform: scale(1.02, 0.98) skewX(0.5deg); }
  72% { transform: scale(0.98, 1.03) skewX(2deg); }
  86% { transform: scale(1, 0.98) skewX(-1deg); }
  100% { transform: scale(1, 1) skewX(0deg); }
}
/* a bright halo hugging the flame itself, so it reads even small on a phone (in the torch's own units: it scales with it) */
.gate-torch .stairs-flame { filter: drop-shadow(0 0 4px rgb(255 210 130 / 0.7)); }
/* "screen": the flame adds its light to the wall behind it, like real fire, instead of painting over it */
.gate-torch .stairs-flame { mix-blend-mode: screen; }
.gate-torch.lit .stairs-glow { opacity: 1; animation: flicker 1.8s ease-in-out infinite alternate; }
.gate-torch.igniting .stairs-flame { animation: flame-ignite 1.1s ease-out both, gate-flame-flicker 1.2s linear 1.1s infinite; }
.gate-torch.igniting .stairs-glow { animation: glow-ignite 1.4s ease-out both, flicker 1.8s ease-in-out 1.4s infinite alternate; }
@media (prefers-reduced-motion: reduce) {
  .gate-torch :is(.stairs-flame, .stairs-glow) { animation: none; }
}

/* ---------- The Art Gallery: hidden in the mosaic above the gate ---------- */
/* During the game: just a mosaic window with an oval top. After the final letter: the window glowing open as a door,
   with granite steps up to it from the walkway (like the gate's) */
.gallery-revealed { display: none; }
body.after-party .gallery-revealed { display: inline; }
/* the gallery's steps open its door too (js/hall.js) */
.gallery-steps { cursor: pointer; }
body.after-party .gallery-hidden { display: none; }
.gallery-glow { animation: gallery-pulse 3s ease-in-out infinite alternate; }
@keyframes gallery-pulse {
  from { opacity: 0.55; }
  to { opacity: 1; }
}
/* the colored wash over the gallery's steps, slowly breathing: each hue shows only where it's brighter than the stone
   ("lighten" blending), so the colors stay pure and saturated (not pastel) and the stone's lit edges and glow, where
   brighter, stay on top */
.gallery-tint {
  mix-blend-mode: lighten;
  opacity: 0.5;
  animation: gallery-tint-breathe 7s ease-in-out infinite alternate;
}
@keyframes gallery-tint-breathe {
  from { opacity: 0.4; }
  to { opacity: 0.6; }
}
/* the same wash inside the gallery's glowing doorway (js/hall.js, middleSVG): stronger, as the door's light is much
   brighter than the steps' stone (with "lighten", a hue only shows where it's brighter than what's under it) */
.gallery-portal-tint { animation-name: gallery-portal-tint-breathe; }
@keyframes gallery-portal-tint-breathe {
  from { opacity: 0.7; }
  to { opacity: 0.95; }
}
/* its blobs of color, each drifting back and forth its own way and pace (js/hall.js sets the vars) */
.gallery-blob { animation: gallery-blob-drift var(--dur, 8s) ease-in-out var(--delay, 0s) infinite alternate; }
@keyframes gallery-blob-drift {
  from { transform: translate(calc(-1 * var(--dx, 0px)), calc(-1 * var(--dy, 0px))); }
  to { transform: translate(var(--dx, 0px), var(--dy, 0px)); }
}
/* the white light from beneath each of the gallery's steps, gently pulsing */
.gallery-underlight { opacity: 0.85; animation: gallery-underlight-pulse 3.5s ease-in-out infinite alternate; }
@keyframes gallery-underlight-pulse {
  from { opacity: 0.65; }
  to { opacity: 1; }
}
/* gold motes drifting up the gallery's steps toward the door, each at its own pace (js/hall.js sets the vars) */
.gallery-mote {
  opacity: 0;
  animation: mote-rise var(--dur, 4s) linear var(--delay, 0s) infinite; /* (rising at a steady pace) */
}
@keyframes mote-rise {
  0% { opacity: 0; transform: none; }
  20% { opacity: 1; }
  70% { opacity: 0.9; }
  100% { opacity: 0; transform: translateY(var(--rise, -30px)); }
}

/* The reveal, only the first time after the letter (js/hall.js): the mosaic's tiles dissolve one by one,
   the glowing doorway blooms behind them, and the steps up to it settle into place one by one, from the bottom up.
   Every delay counts back --reveal-shift, the time already gone when the hall was last redrawn (the screen's size
   changed), so the redrawn reveal carries on where it was instead of starting over */
body.after-party .hall.revealing .gallery-hidden { display: inline; }
.hall.revealing .tess { animation: fade-away 0.88s ease-in calc(0.8s + var(--d) - var(--reveal-shift, 0s)) both; }
.hall.revealing .gallery-medallion {
  transform-box: fill-box;
  transform-origin: center;
  animation: medallion-bloom 1.6s ease-out calc(1.6s - var(--reveal-shift, 0s)) both;
}
.hall.revealing .gallery-steps g[style]:not(.gallery-tint) {
  animation: step-appear 0.4s ease-out calc(3.5s + var(--i) * 0.4s - var(--reveal-shift, 0s)) both;
}
/* The steps' colored wash fades in with the top step too, but on its own and through a filter, not its opacity: it keeps
   breathing (its opacity, gallery-tint-breathe) and keeps blending with the stone under it the whole time, so it looks
   the same during the reveal and after (see js/hall.js) */
.hall.revealing .gallery-steps .gallery-tint {
  animation: gallery-tint-breathe 7s ease-in-out infinite alternate,
    gallery-tint-reveal 0.4s ease-out calc(3.5s + var(--i) * 0.4s - var(--reveal-shift, 0s)) both;
}
/* the doorway's wash the same way, on the door's timing (medallion-bloom), but without growing with it */
.hall.revealing .gallery-portal-tint {
  animation: gallery-portal-tint-breathe 7s ease-in-out infinite alternate,
    gallery-tint-reveal 1s ease-out calc(1.6s - var(--reveal-shift, 0s)) both;
}
@keyframes gallery-tint-reveal {
  from { filter: opacity(0); }
  to { filter: opacity(1); }
}
/* (the groups with a blur in them, the light under each step, the door's light and the colored wash, the frame's
   glowing line and the motes, only fade in, without rising: a blur moving shows its box's edge as a line) */
.hall.revealing .gallery-steps g.gallery-fade { animation-name: gallery-fade-in; }
@keyframes gallery-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
/* Until the browser allows sound (js/hall.js), the reveal waits paused at its start: the mosaic whole, the door and
   its steps not there yet */
.hall.reveal-waiting :is(.tess, .gallery-medallion, .gallery-portal-tint, .gallery-steps g[style]) {
  animation-play-state: paused;
}
@keyframes fade-away { to { opacity: 0; } }
@keyframes medallion-bloom {
  from { opacity: 0; transform: scale(0.8); }
  60% { opacity: 1; transform: scale(1.05); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes step-appear {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .hall-lantern, .gallery-glow, .gate-seam,
  .hall.revealing :is(.tess, .gallery-medallion, .gallery-steps g[style]) { animation: none; }
  .hall.revealing :is(.gallery-steps .gallery-tint, .gallery-portal-tint) { animation: none; }
  body.after-party .hall.revealing .gallery-hidden { display: none; }
  .gallery-mote { display: none; }
  .gallery-tint, .gallery-blob, .gallery-underlight { animation: none; }
}

/* Torches on the tower's stairs (js/floors.js) */
@keyframes flame-dance {
  0% { transform: scale(1, 1) skewX(0deg); }
  50% { transform: scale(0.92, 1.08) skewX(3deg); }
  100% { transform: scale(1.05, 0.94) skewX(-2deg); }
}
@keyframes flame-ignite {
  0% { transform: scale(0.1); opacity: 0; }
  35% { transform: scale(1.5, 1.8); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes glow-ignite {
  0% { opacity: 0; }
  35% { opacity: 1; transform: scale(1.3); }
  100% { opacity: 1; transform: scale(1); }
}

/* Life in the scenes: tea steam, the TV screen flickering, notes floating */
.steam { animation: steam-rise 2.4s ease-in-out infinite; }
@keyframes steam-rise {
  0% { transform: translateY(6px); opacity: 0; }
  40% { opacity: 1; }
  100% { transform: translateY(-10px); opacity: 0; }
}
.room-tv-screen, .room-tv-glow { animation: flicker 1.6s steps(3) infinite alternate; }
.room-notes { animation: notes-float 2.8s ease-in-out infinite; }
@keyframes notes-float {
  50% { transform: translateY(-8px); opacity: 0.6; }
}

/* Staircase: 61% of the drawing is wall (the arch) and 39% are the steps on the floor */
.stairs {
  --height: min(50svh, 40vw);
  position: relative;
  display: block;
  height: var(--height);
  aspect-ratio: 240 / 360;
  margin-bottom: calc(var(--height) * -0.39);
  color: var(--enamel);
  text-decoration: none;
  filter: drop-shadow(0 18px 24px rgb(0 0 0 / 0.5));
  transition: filter 0.3s ease;
}
.stairs:hover { filter: drop-shadow(0 18px 24px rgb(0 0 0 / 0.5)) drop-shadow(0 0 22px rgb(217 164 65 / 0.45)); }
.stairs-drawing { display: block; width: 100%; height: 100%; }
/* Brass sign hanging by two little chains above the arch, like the rooms' signs */
.stairs-label {
  position: absolute;
  bottom: 100%;
  left: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: max-content;
  max-width: 160%;
  margin-bottom: 10px; /* hanging on the wall a little above the arch's keystone, not sitting on it */
  padding: 5px 14px 6px;
  translate: -50% 0;
  text-align: center;
  line-height: 1.2;
  white-space: nowrap; /* "Go up" / "Go down" always on a single line */
  color: #3d2708;
  /* the same finish as the hall's door signs (.room-sign): letters engraved in the metal, a faint brushed grain, a
     bevel and a fine engraved line inside the edge */
  text-shadow: 0 1px 0 rgb(255 244 210 / 0.55);
  background:
    repeating-linear-gradient(rgb(255 255 255 / 0.07) 0 1px, transparent 1px 3px),
    linear-gradient(#f1d596, var(--gold) 60%, #a57d2e);
  border: 1px solid #8a6420;
  border-radius: 4px;
  box-shadow:
    inset 0 1px 0 rgb(255 248 220 / 0.7),
    inset 0 -1px 0 rgb(110 74 18 / 0.5),
    inset 0 0 0 2px rgb(255 240 200 / 0.35),
    inset 0 0 0 3px rgb(138 100 32 / 0.45),
    0 3px 8px rgb(0 0 0 / 0.5);
}
/* The little chains, each hanging from a gold nail in the wall, as on the hall's door signs */
.stairs-label::before,
.stairs-label::after {
  content: "";
  position: absolute;
  bottom: 100%;
  width: 8px;
  height: 17px;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3ClinearGradient id='g' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='%23a57d2e'/%3E%3Cstop offset='.5' stop-color='%23f7e3a8'/%3E%3Cstop offset='1' stop-color='%23b98a2e'/%3E%3C/linearGradient%3E%3Ccircle cx='4' cy='4' r='4' fill='url(%23g)'/%3E%3C/svg%3E") 50% 0 / 8px 8px no-repeat,
    repeating-linear-gradient(to bottom, var(--gold) 0 3px, #8a6420 3px 4px) 50% 100% / 2px calc(100% - 4px) no-repeat;
}
.stairs-label::before { left: calc(16% - 3px); }
.stairs-label::after { right: calc(16% - 3px); }
.stairs-label strong { font-family: var(--font-title); font-weight: 400; font-size: clamp(1.05rem, 2.2vh, 1.3rem); }.stairs .padlock { top: 40%; width: 36%; }

/* The lattice screen (.portcullis, js/floors.js): lowered when locked, rises out of the arch when unlocking */
.stairs:not(.locked) .portcullis,
.stairs.unlocking .portcullis { transform: translateY(-200px); }
.stairs.unlocking .portcullis { transition: transform 1.6s ease-in-out 0.9s; }
.stairs.just-opened { animation: beckon 1.3s ease-in-out 3; }

/* The hall's stairs (the 3-key one): warm light pulsing behind the portcullis while it's locked */
.portcullis-light { opacity: 0.35; }
.stairs.locked .portcullis-light { animation: portcullis-light-pulse 2.8s ease-in-out infinite alternate; }
@keyframes portcullis-light-pulse { from { opacity: 0.25; } to { opacity: 0.9; } }

/* The stairs' 3 torches, one per key, like the ones on the room doorways: cold and out while
   the key is missing, the flame dancing when it's found (and all lit with the stairs open).
   The glow goes past the edge of the drawing, so the SVG doesn't clip the overflow. */
.stairs-keys .stairs-drawing { overflow: visible; }
/* The sign goes up a little so it doesn't touch the flame of the torch at the top of the arch */
.stairs-keys .stairs-label { margin-bottom: max(8px, calc(var(--height) * 0.04)); }
.stairs-flame, .stairs-glow { opacity: 0; transition: opacity 0.6s ease; }
.stairs-flame { transform-box: fill-box; transform-origin: 50% 100%; }
.stairs-glow { transform-box: fill-box; transform-origin: center; }
:is(.key-torch.lit, .stairs-keys:not(.locked) .key-torch) .stairs-flame {
  opacity: 1;
  animation: flame-dance 0.9s ease-in-out var(--flame-delay, 0s) infinite alternate;
}
:is(.key-torch.lit, .stairs-keys:not(.locked) .key-torch) .stairs-glow {
  opacity: 1;
  animation: flicker 1.8s ease-in-out infinite alternate;
}
/* The last key, while unlocking: the torch lights up with a flare */
.key-torch.igniting .stairs-flame { animation: flame-ignite 1.1s ease-out both, flame-dance 0.9s ease-in-out 1.1s infinite alternate; }
.key-torch.igniting .stairs-glow { animation: glow-ignite 1.4s ease-out both, flicker 1.8s ease-in-out 1.4s infinite alternate; }
@media (prefers-reduced-motion: reduce) {
  .key-torch .stairs-flame, .key-torch .stairs-glow { animation: none !important; }
}

/* Floor rail: a thin column of little dots on the left edge.
   Each floor's name shows on a label on mouse hover. */
.floor-map {
  position: absolute;
  top: 50%;
  left: 12px;
  z-index: 5;
  padding: 10px 7px;
  translate: 0 -50%;
  background: rgb(8 15 42 / 0.6);
  border: 1px solid rgb(217 164 65 / 0.4);
  border-radius: 999px;
  backdrop-filter: blur(4px);
}
.map-title {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}
.floor-map ol { position: relative; margin: 0; padding: 0; list-style: none; }
/* the stairwell connecting the floors */
.floor-map ol::before {
  content: "";
  position: absolute;
  top: 14px;
  bottom: 14px;
  left: 50%;
  translate: -1px 0;
  border-left: 2px dotted rgb(217 164 65 / 0.5);
}
.map-floor > * {
  position: relative;
  display: flex;
  align-items: center;
  padding: 5px 0;
  font-size: 0.9rem;
  color: var(--moonlight);
  text-decoration: none;
}
/* Label with the name, to the right of the dot */
.floor-name {
  position: absolute;
  left: calc(100% + 14px);
  top: 50%;
  padding: 3px 10px;
  translate: -4px -50%;
  white-space: nowrap;
  color: var(--enamel);
  background: rgb(8 15 42 / 0.92);
  border: 1px solid rgb(217 164 65 / 0.5);
  border-radius: 6px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s, translate 0.2s;
}
.map-floor > *:hover .floor-name,
.map-floor > *:focus-visible .floor-name { opacity: 1; translate: 0 -50%; }
.map-floor a:hover .marker { box-shadow: 0 0 10px var(--gold); }
.marker {
  flex: none;
  display: grid;
  place-items: center;
  width: 14px;
  height: 14px;
  font-size: 9px;
  line-height: 1;
  color: var(--night);
  background: var(--night);
  border: 2px solid var(--gold);
  border-radius: 50%;
}
.map-floor.done .marker { background: var(--gold); }
.map-floor.done .marker::after { content: "✓"; font-weight: 700; }
.map-floor.current .floor-name { font-weight: 700; }
.map-floor.current .marker { box-shadow: 0 0 0 3px rgb(217 164 65 / 0.35), 0 0 12px var(--gold); }
.map-floor.locked-floor .marker { border-style: dashed; opacity: 0.45; }
.map-floor.locked-floor .floor-name::after { content: " 🔒"; font-size: 0.75em; }

/* The 3 keys stacked under the rail */
.keys {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid rgb(217 164 65 / 0.35);
}
.key { width: 18px; color: rgb(201 211 240 / 0.3); }
.key.earned { color: var(--gold); filter: drop-shadow(0 0 5px rgb(217 164 65 / 0.7)); }
.key svg { display: block; width: 100%; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }

.door {
  position: relative;
  display: block;
  aspect-ratio: 300 / 420;
  color: var(--enamel);
  text-decoration: none;
  filter: drop-shadow(0 20px 28px rgb(0 0 0 / 0.55));
  transition: transform 0.3s ease, filter 0.3s ease;
}
.door:hover {
  transform: translateY(-8px);
  filter: drop-shadow(0 20px 28px rgb(0 0 0 / 0.55)) drop-shadow(0 0 26px rgb(217 164 65 / 0.4));
}
.door:focus-visible { outline: none; }
.door:focus-visible .door-name { text-decoration: underline; text-decoration-color: var(--gold); }

.arch { position: absolute; inset: 0; width: 100%; height: 100%; }

.door-content {
  position: absolute;
  top: 32%;
  bottom: 7%;
  left: 16%;
  right: 16%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
}

.door-icon { display: block; width: clamp(48px, 6vw, 66px); color: var(--gold); }
.door-icon svg {
  display: block;
  width: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.door-name { font-family: var(--font-title); font-size: clamp(1.15rem, 2.2vw, 1.6rem); line-height: 1.1; }
.door-status { min-height: 1.4em; font-size: 1rem; color: var(--turquoise-light); }
.door.visited .door-status { color: var(--gold); }

/* Wax seal on the arch's keystone: room completed */
.door.visited::after {
  content: "✓";
  position: absolute;
  top: 2.5%;
  left: 50%;
  display: grid;
  place-items: center;
  width: 17%;
  aspect-ratio: 1;
  translate: -50% 0;
  font-weight: 700;
  font-size: clamp(0.9rem, 1.6vw, 1.2rem);
  color: var(--gold);
  background: radial-gradient(circle at 35% 30%, #e56a5c, var(--coral) 55%, #8b2219);
  border: 2px solid var(--gold);
  border-radius: 50%;
  box-shadow: 0 3px 8px rgb(0 0 0 / 0.5);
}

@keyframes beckon {
  50% { filter: drop-shadow(0 20px 28px rgb(0 0 0 / 0.55)) drop-shadow(0 0 40px rgb(217 164 65 / 0.7)); }
}

/* ---------- Locked things (stairs, doors): padlock ---------- */

.locked { cursor: not-allowed; }

.padlock {
  position: absolute;
  top: 60%;
  left: 50%;
  width: 34%;
  /* visible: the shackle, lifted as it opens, rises past the top of the drawing (viewBox), and would be cut off */
  overflow: visible;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 6px 10px rgb(0 0 0 / 0.6));
}
.padlock-shackle { transition: transform 0.3s ease-out; }
.keyhole { fill: #3b2a0e; transition: fill 0.4s ease; }
.keyhole.lit { fill: #fff1c2; filter: drop-shadow(0 0 3px #ffcf7a); }

/* Click on something locked: the padlock shakes */
.denied .padlock { animation: padlock-deny 0.45s ease; }
@keyframes padlock-deny {
  20% { transform: translate(-50%, -50%) rotate(-9deg); }
  45% { transform: translate(-50%, -50%) rotate(8deg); }
  70% { transform: translate(-50%, -50%) rotate(-4deg); }
}

/* A found key going into it (js/hall.js): the padlock is pushed in a little, then swings on its shackle */
.padlock.wiggle { transform-origin: 50% 20%; animation: padlock-wiggle 0.7s ease-out; }
@keyframes padlock-wiggle {
  15% { transform: translate(-50%, -46%) rotate(0); }
  35% { transform: translate(-50%, -50%) rotate(-8deg); }
  55% { transform: translate(-50%, -50%) rotate(6deg); }
  75% { transform: translate(-50%, -50%) rotate(-3deg); }
}

/* The message on the locked Challenges gate (js/hall.js): a red silk card with a gold edge over the leaves, above
   the padlock, for a few seconds after a tap */
.gate-hint {
  position: absolute;
  top: 42%;
  left: 50%;
  z-index: 3;
  width: max-content;
  max-width: min(240px, 80vw);
  translate: -50% -50%;
  padding: 0.5em 0.9em;
  border: 2px solid var(--gold);
  border-radius: 10px;
  background: #7a1c14;
  color: #fff3d0;
  font-size: 0.9rem;
  line-height: 1.35;
  text-align: center;
  box-shadow: 0 8px 20px rgb(0 0 0 / 0.5);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.gate-hint.shown { opacity: 1; }

/* Unlocking: the padlock shakes, the shackle opens and it falls */
.unlocking .padlock { animation: padlock-open 2.1s ease-in forwards; }
.unlocking .padlock-shackle { transform: translateY(-13px); transition-delay: 0.9s; }

@keyframes padlock-open {
  0%, 35% { transform: translate(-50%, -50%) rotate(0); }
  10% { transform: translate(-50%, -50%) rotate(-10deg); }
  20% { transform: translate(-50%, -50%) rotate(10deg); }
  28% { transform: translate(-50%, -50%) rotate(-5deg); }
  55% { transform: translate(-50%, -58%) rotate(-4deg); opacity: 1; }
  100% { transform: translate(-50%, 170%) rotate(32deg); opacity: 0; }
}
/* Discreet button in the corner of the ground floor */
.reset {
  position: absolute;
  right: 12px;
  bottom: 10px;
  z-index: 5;
  padding: 4px 12px;
  font: inherit;
  font-size: 0.8rem;
  color: var(--moonlight);
  background: transparent;
  border: 1px solid rgb(217 164 65 / 0.5);
  border-radius: 999px;
  cursor: pointer;
}
.reset:hover { color: var(--enamel); border-color: var(--gold); background: rgb(27 63 139 / 0.4); }

/* Mute button (js/soundtrack.js): top-right corner, above the surprise entrance
   but below the loading screen */
.mute-button {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 95;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  color: var(--gold);
  background: rgb(8 15 42 / 0.6);
  border: 1px solid rgb(217 164 65 / 0.5);
  border-radius: 50%;
  backdrop-filter: blur(4px);
  cursor: pointer;
  transition: opacity 0.8s ease, color 0.2s, border-color 0.2s, background 0.2s;
}
.mute-button svg { width: 22px; height: 22px; }
.mute-button:hover { color: var(--enamel); border-color: var(--gold); background: rgb(27 63 139 / 0.5); }
.mute-button.muted { color: var(--moonlight); opacity: 0.75; }
/* In the dark of the surprise it almost disappears, so it doesn't spoil the moment */
body.entrance-active .mute-button { opacity: 0.25; }

/* Phone held upright: compact map at the top left, narrower doors and stairs */
@media (max-width: 700px) {
  .tower { --floor-line: 80%; }
  /* On phones it doesn't fit between the stairs: the title goes back to the top of the screen */
  .floor-sign {
    position: fixed;
    top: calc(9svh + 64px);
    left: 50%;
    width: 92vw;
    max-width: none;
    margin: 0;
    translate: -50% 0;
  }
  /* the map becomes a row of dots, from the ground floor (left) to the balcony (right) */
  .floor-map { top: calc(9% + 26px); left: 50%; padding: 6px 14px; translate: -50% 0; }
  .map-title, .floor-name, .keys { display: none; }
  .floor-map ol { display: flex; flex-direction: row-reverse; gap: 16px; }
  .floor-map ol::before { top: 50%; bottom: auto; left: 8px; right: 8px; border-left: 0; border-top: 2px dotted rgb(217 164 65 / 0.5); }
  .map-floor > * { padding: 0; }
  .floor-row { left: 8px; right: 8px; gap: 8px; }
  .stairs { --height: min(40svh, 40vw); }
  .stairs-label { padding: 3px 8px 4px; }
  .stairs-label strong { font-size: 0.9rem; }
}
@media (max-width: 460px) {
  .sky-text { padding-top: 185px; }
}

/* ---------- Inner pages ---------- */

.page-header {
  position: relative;
  padding: 0 16px 40px;
  text-align: center;
  background:
    radial-gradient(ellipse at 50% 0%, #2a4a9a 0, transparent 60%),
    linear-gradient(var(--night), var(--night-2));
  overflow: hidden;
}
.page-header .lanterns { min-height: 130px; }

/* Back button: a little arched door in the corner (fixed, always at hand even while scrolling),
   dark inside with light coming from below and the golden arrow; the text shows on mouse hover */
.back {
  position: fixed;
  top: 14px;
  left: 14px;
  z-index: 35;
  display: grid;
  place-items: center;
  width: 52px;
  height: 64px;
  padding-top: 6px;
  font-family: var(--font-title);
  color: var(--enamel);
  text-decoration: none;
  background:
    radial-gradient(ellipse 70% 45% at 50% 100%, rgb(255 207 122 / 0.45), transparent),
    linear-gradient(#2c4a9a, #101a44);
  border: 2px solid var(--gold);
  border-radius: 50% 50% 8px 8px / 40% 40% 8px 8px;
  box-shadow: 0 6px 18px rgb(0 0 0 / 0.45), inset 0 0 0 3px rgb(16 26 68), inset 0 0 0 4px rgb(217 164 65 / 0.35);
  transition: box-shadow 0.25s, background 0.25s;
}
.back:hover,
.back:focus-visible {
  outline: none;
  box-shadow: 0 6px 18px rgb(0 0 0 / 0.45), 0 0 22px rgb(217 164 65 / 0.55), inset 0 0 0 3px rgb(16 26 68), inset 0 0 0 4px rgb(217 164 65 / 0.6);
}

.back-icon { display: block; width: 24px; color: var(--gold); transition: transform 0.25s; }
.back-icon svg {
  display: block;
  width: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.back:hover .back-icon { transform: translateX(-3px); }

/* "Back to the hall": label next to the little door, only with the mouse or the keyboard */
.back-label {
  position: absolute;
  top: 50%;
  left: calc(100% + 10px);
  padding: 5px 12px;
  translate: -6px -50%;
  font-size: 0.95rem;
  white-space: nowrap;
  background: rgb(8 15 42 / 0.92);
  border: 1px solid rgb(217 164 65 / 0.5);
  border-radius: 6px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s, translate 0.2s;
}
.back:hover .back-label,
.back:focus-visible .back-label { opacity: 1; translate: 0 -50%; }

/* On phones: the little door a bit smaller and no label (there's no "mouse hover") */
@media (max-width: 520px) {
  .back { width: 44px; height: 54px; }
  .back-label {
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
  }
}

/* In the rooms (TV, Music, Food, Reading, the Art Gallery) and on the balcony, the back button becomes a brass sign hanging from the ceiling
   by two little chains, at the left end of the row of lanterns, swaying along with them */
:is(.page-header, .balcony, .cabin, .gallery) .back {
  position: absolute;
  top: 34px; /* the length of the chains */
  left: clamp(12px, 3vw, 40px);
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: auto;
  height: auto;
  padding: 10px 20px 11px 14px;
  color: var(--ink);
  background: linear-gradient(#f1d596, var(--gold) 60%, #a57d2e);
  border: 1px solid #8a6420;
  border-radius: 6px;
  box-shadow: 0 6px 14px rgb(0 0 0 / 0.45), inset 0 0 0 2px rgb(255 240 200 / 0.35);
  transform-origin: 50% -34px; /* sways from the point where the chains attach to the ceiling */
  animation: sway 5s ease-in-out -1.3s infinite alternate;
}
/* (in the Art Gallery, hanging a little higher: 10% of its height) */
.gallery .back { translate: 0 -10%; }
:is(.page-header, .balcony, .cabin, .gallery) .back:hover,
:is(.page-header, .balcony, .cabin, .gallery) .back:focus-visible {
  outline: none;
  box-shadow: 0 6px 14px rgb(0 0 0 / 0.45), 0 0 20px rgb(255 207 122 / 0.7), inset 0 0 0 2px rgb(255 240 200 / 0.5);
}
:is(.page-header, .balcony, .cabin, .gallery) .back:focus-visible { outline: 3px solid var(--enamel); outline-offset: 3px; }
/* The two little chains (links in golden dots) */
:is(.page-header, .balcony, .cabin, .gallery) .back::before,
:is(.page-header, .balcony, .cabin, .gallery) .back::after {
  content: "";
  position: absolute;
  bottom: 100%;
  width: 3px;
  height: 36px;
  background: repeating-linear-gradient(to bottom, #d9a441 0 5px, transparent 5px 7px);
}
:is(.page-header, .balcony, .cabin, .gallery) .back::before { left: 18%; }
:is(.page-header, .balcony, .cabin, .gallery) .back::after { right: 18%; }
:is(.page-header, .balcony, .cabin, .gallery) .back-icon { width: 24px; color: var(--ink); }
/* The palace silhouette on the sign: "back to the hall" (still on hover; it's the sign that glows) */
:is(.page-header, .balcony, .cabin, .gallery) .back-door { width: 28px; margin-top: -3px; }
:is(.page-header, .balcony, .cabin, .gallery) .back-door .icon-filled { fill: currentColor; stroke: none; }
:is(.page-header, .balcony, .cabin, .gallery) .back:hover .back-door { transform: none; }
/* The text shows written on the sign (it's no longer the label that only appeared with the mouse) */
:is(.page-header, .balcony, .cabin, .gallery) .back .back-label {
  position: static;
  width: auto;
  height: auto;
  padding: 0;
  overflow: visible;
  clip-path: none;
  translate: none;
  font-size: 1.2rem;
  line-height: 1.1;
  background: none;
  border: 0;
  opacity: 1;
}
/* The lanterns start after the sign, so they don't sit on top of it */
.page-header:has(.back) .lanterns { padding-left: clamp(226px, 24vw, 286px); }
/* Phone: the sign a little smaller, to leave room for the lanterns */
@media (max-width: 520px) {
  :is(.page-header, .balcony, .cabin, .gallery) .back { padding: 8px 16px 9px 12px; }
  :is(.page-header, .balcony, .cabin, .gallery) .back-icon { width: 22px; }
  :is(.page-header, .balcony, .cabin, .gallery) .back .back-label { font-size: 1.05rem; }
  .page-header:has(.back) .lanterns { padding-left: 200px; }
  /* With the bigger sign, only 3 lanterns fit next to it */
  .page-header:has(.back) .lantern:nth-child(n + 4) { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  :is(.page-header, .balcony, .cabin, .gallery) .back { animation: none; }
}
/* On Food, room at the end of the page so the fixed tray doesn't cover the last dish */
body[data-area="food"] .page-content { padding-bottom: 150px; }

.page-title {
  margin: 8px 0 0;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: clamp(2.2rem, 6.5vw, 3.8rem);
  line-height: 1.08;
  color: var(--enamel);
  text-shadow: 0 0 36px rgb(217 164 65 / 0.3);
}

.page-content { padding: 56px 16px 96px; }

/* ---------- Food: arched menu ---------- */

.menu {
  max-width: 620px;
  margin: 0 auto;
  border-radius: 50% 50% 14px 14px / 170px 170px 14px 14px;
}
.menu .enamel {
  padding: 96px clamp(22px, 6vw, 60px) 44px;
  border-radius: 50% 50% 8px 8px / 156px 156px 8px 8px;
}
.menu h2 {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: clamp(1.9rem, 6vw, 2.7rem);
  line-height: 1.1;
  color: var(--cobalt);
  text-align: center;
}
.menu-subtitle { margin: 6px 0 0; text-align: center; font-style: italic; color: var(--ink-soft); }

/* Menu: a golden line under each dish, fading softly at the ends */
.dishes { list-style: none; margin: 18px 0 0; padding: 0; }
/* Photo on the left; name and description together in the right column;
   underneath, the price tag (which is the button) at full width */
.dish {
  display: grid;
  grid-template-columns: clamp(64px, 16vw, 88px) minmax(0, 1fr); /* (the photo's size, also in .dish-photo) */
  grid-template-areas:
    "photo   name"
    "photo   description"
    "actions actions";
  /* every dish has the same height: the photo always takes the room of 2 lines of name and 3 of description (its
     margin, below), the text starts at the top and the leftover room goes under the description (longer texts are
     cut with "…") */
  --text-height: calc(2 * 1.2 * 1.1rem + 4px + 3 * 1.4 * 0.95rem);
  grid-template-rows: auto 1fr auto;
  column-gap: 14px;
  row-gap: 4px;
  align-items: center;
  /* a little room on the sides (taken back by the margin, so the text stays in place) for the ordered dish's card */
  position: relative;
  margin: 0 -12px;
  padding: 18px 12px;
  border-radius: 10px;
  /* (an ordered dish lights up at once, without a fade: fading its shadow would redraw the card on every frame of
     the photo's flight) */
  background: linear-gradient(90deg, transparent, rgb(217 164 65 / 0.7) 15%, rgb(217 164 65 / 0.7) 85%, transparent) bottom / 100% 1px no-repeat;
  /* each card lays out and draws on its own: ordering one dish only redraws that card, not the whole menu (nothing
     in a card draws outside it, the seal included) */
  contain: layout paint;
}
/* (the room on the right is the seal's, kept even before ordering, so the name never re-wraps when it's stamped;
   in rem, so it doesn't shrink with a long name. Its min-height keeps a one-line name as tall as the seal, so the
   description always starts below the seal instead of under it) */
.dish-name { grid-area: name; align-self: start; padding-right: 2.4rem; min-height: 2.2rem; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; font-family: var(--font-title); font-weight: 700; font-size: 1.1rem; line-height: 1.2; color: var(--cobalt); }
/* Round photo of the dish, with a golden frame, at the top of its dish, next to the name. Its bottom margin fills
   the rest of the text's room, so every dish is the same height */
.dish-photo {
  --photo-size: clamp(64px, 16vw, 88px);
  grid-area: photo;
  align-self: start;
  margin-bottom: calc(var(--text-height) - var(--photo-size));
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border: 3px solid var(--gold);
  border-radius: 50%;
  box-shadow: 0 4px 12px rgb(20 34 77 / 0.25);
}
.dish-description { grid-area: description; align-self: start; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; margin: 0; font-size: 0.95rem; line-height: 1.4; text-align: justify; hyphens: auto; color: var(--ink-soft); }
.dish:not(:has(.dish-photo)) :is(.dish-name, .dish-description) { grid-column: 1 / -1; }
.dish-actions { margin-top: 8px; }

/* The price tag is the order button itself, to the right of the dish, on a single line:
   "PRICE price ┆ + Order" — the label shows that it's the price, and the perforation separates the order.
   The price never wraps. After ordering it turns green with "✓ Ordered". */
.dish-actions {
  grid-area: actions;
  display: flex;
  justify-content: flex-end;
  /* shadow outside the tag's cutout: crisp, like the tag's paper edge (a blurred one, on all 21 tags, was too slow
     to draw while scrolling) */
  filter: drop-shadow(0 2px 0 rgb(20 34 77 / 0.2));
}
/* The focus outline goes on the box around it (the tag's cutout would clip the outline) */
.dish-actions:has(.dish-price:focus-visible) { outline: 3px solid var(--gold); outline-offset: 3px; border-radius: 6px; }

/* Tag: cut point on the left and a little hole */
.dish-price {
  all: unset;
  box-sizing: border-box;
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 14px 5px 22px;
  color: #fff;
  background: var(--coral);
  border-radius: 0 6px 6px 0;
  clip-path: polygon(13px 0, 100% 0, 100% 100%, 13px 100%, 0 50%);
  cursor: pointer;
  transition: background 0.2s, transform 0.15s;
}
.dish-price::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 8px;
  width: 5px;
  height: 5px;
  background: var(--enamel);
  border-radius: 50%;
  transform: translateY(-50%);
}
.dish-price:hover { background: #d9463b; }
.dish-price:active { transform: scale(0.96); }
.dish-price:disabled { background: var(--emerald); cursor: default; transform: none; }

/* The "✕" of an ordered dish: one more piece at the end of its green tag, after another perforation
   ("PRICE price ┆ ✓ Ordered ┆ ✕"); the tag gives it its rounded end */
.dish-remove {
  all: unset;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  padding: 0 11px 0 9px;
  font-size: 0.8rem;
  font-weight: 700;
  color: #fff;
  background: var(--coral);
  border-left: 1px dashed rgb(255 255 255 / 0.6);
  border-radius: 0 6px 6px 0;
  cursor: pointer;
  transition: background 0.2s;
}
.dish-remove:hover { background: #d9463b; }
.dish-remove:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }
.dish.ordered .dish-price { border-radius: 0; }

.price-value { font-size: 1.02rem; font-style: italic; line-height: 1.25; white-space: nowrap; }
/* "PRICE" in golden small caps in front of the value: makes it clear that it's the price */
.price-label {
  margin-right: -4px; /* closer to the value than to the perforation */
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #ffd98a;
}
.order-label {
  align-self: stretch;
  display: flex;
  align-items: center;
  padding-left: 10px;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  white-space: nowrap;
  border-left: 1px dashed rgb(255 255 255 / 0.6);
}

/* Phone: everything on one line even with "an afternoon of chatting" */
@media (max-width: 420px) {
  .dish-price { gap: 8px; padding: 5px 10px 5px 20px; }
  .price-label { font-size: 0.6rem; letter-spacing: 0.05em; }
  .price-value { font-size: 0.85rem; }
  .order-label { padding-left: 8px; font-size: 0.74rem; }
}
@media (max-width: 360px) {
  .price-value { font-size: 0.78rem; }
}

/* An ordered dish: the whole card lights up (a jade wash and edge, the photo ringed, the name in jade) and a red seal
   is stamped in its corner, the teahouse's seal from the bill */
.dish.ordered {
  background-color: rgb(47 125 91 / 0.12);
  box-shadow: inset 4px 0 0 var(--emerald), inset 0 0 0 1px rgb(47 125 91 / 0.3);
}
.dish.ordered .dish-photo { border-color: var(--emerald); box-shadow: 0 0 0 3px rgb(47 125 91 / 0.3), 0 4px 12px rgb(20 34 77 / 0.25); }
.dish.ordered .dish-name { color: var(--emerald); }
/* (the seal is 2.2em, 10px from the corner, drawn in the middle of a 3.2em box that stays inside the card: the stamp
   grows the drawing inside that box, not the box itself. Scaling the box would put it on its own layer of unknown
   size, and the browser would then redraw every card below it while the dish's photo flies) */
.dish.ordered::after {
  content: "";
  position: absolute;
  top: calc(10px - 0.5em);
  right: calc(10px - 0.5em);
  width: 3.2em;
  height: 3.2em;
  opacity: 0.85;
  rotate: -8deg;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Crect x='2' y='2' width='36' height='36' rx='4' fill='%23c8382e'/%3E%3Crect x='6' y='6' width='28' height='28' rx='2' fill='none' stroke='%23fbf4e4' stroke-width='1.6'/%3E%3Cpath d='M12 13H28M20 13V28M13 20H27M12 28H28M15 17L13 24M25 17L27 24' stroke='%23fbf4e4' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat center / 69%;
  animation: seal-stamp 0.35s cubic-bezier(0.3, 1.4, 0.5, 1);
  pointer-events: none;
}
/* the seal comes down onto the paper: big and faint, then pressed in place */
@keyframes seal-stamp {
  from { opacity: 0; background-size: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .dish.ordered::after { animation: none; }
}

/* A section's title in the menu ("Dim Sum 点心", ...), between golden lines */
.menu-section {
  margin: 26px 0 0;
  padding-bottom: 6px;
  font-family: var(--font-title);
  font-size: 1.15rem;
  letter-spacing: 0.04em;
  text-align: center;
  color: var(--coral);
  border-bottom: 2px solid rgb(217 164 65 / 0.8);
}
.menu-section:first-child { margin-top: 8px; }

/* The table is full: the dishes she didn't pick fade out, their tag turns gray ("Table full") */
.dish.unavailable { opacity: 0.45; }
.dish.unavailable .dish-price { background: #8a8478; }

/* ---------- Food as a Cantonese teahouse ----------
   Only on this page, the site's colors become the teahouse's: dark teak and red lacquer instead of the night sky,
   rice paper instead of enamel, a gold "回" key pattern instead of the Iznik tiles (the frieze and the menu's
   frame use it), cinnabar red, jade green and gold */
body[data-area="food"] {
  --night: #1e0e0a;
  --night-2: #3a1a12;
  --night-deep: #1a0b08;
  --cobalt: #8a2418;
  --coral: #b8322a;
  --emerald: #2f7a5c;
  --enamel: #f6ecd6;
  --ink: #3a2414;
  --ink-soft: #6e5038;
  --moonlight: #e8d4a8;
  /* the background: little dishes scattered at random, drawn in faint gold (a teacup, a dim sum steamer, a rice bowl
     with chopsticks, a bubble tea, a dumpling, a teapot, a bao bun and another teacup), each at its own angle; the
     200px block repeats, but it's big and uneven enough to look scattered */
  --lattice: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'%3E%3Cdefs%3E%3Cg id='cup'%3E%3Cpath d='M-11 -6H11Q10 10 0 10Q-10 10 -11 -6Z'/%3E%3Cpath d='M-10 -1H10'/%3E%3C/g%3E%3Cg id='steamer'%3E%3Cellipse cy='-6' rx='13' ry='4'/%3E%3Cpath d='M-13 -6V6Q0 11 13 6V-6M-13 0Q0 5 13 0'/%3E%3C/g%3E%3Cg id='bowl'%3E%3Cpath d='M-13 -2H13Q12 11 0 11Q-12 11 -13 -2Z'/%3E%3Cpath d='M-9 -2Q-4 -8 0 -4Q4 -9 9 -2M-6 -14L10 -4M-2 -15L12 -6'/%3E%3C/g%3E%3Cg id='boba'%3E%3Cpath d='M-8 -10H8L6 13H-6Z'/%3E%3Cpath d='M-9 -10H9M2 -10L5 -18'/%3E%3Ccircle cx='-3' cy='9' r='1.5'/%3E%3Ccircle cx='2' cy='10' r='1.5'/%3E%3Ccircle cx='0' cy='6' r='1.5'/%3E%3C/g%3E%3Cg id='dumpling'%3E%3Cpath d='M-12 4Q-12 -8 0 -8Q12 -8 12 4Z'/%3E%3Cpath d='M-6 -6Q-4 -2 -6 1M0 -8V-2M6 -6Q4 -2 6 1'/%3E%3C/g%3E%3Cg id='teapot'%3E%3Cpath d='M-9 -4Q-11 10 0 10Q11 10 9 -4Z'/%3E%3Cpath d='M9 0Q16 -2 15 -8M-9 -1Q-15 0 -14 6M-5 -4Q0 -9 5 -4'/%3E%3Ccircle cy='-8' r='1.5'/%3E%3C/g%3E%3Cg id='bao'%3E%3Cpath d='M-11 5Q-11 -7 0 -7Q11 -7 11 5Z'/%3E%3Cpath d='M-3 -6Q0 -2 3 -6'/%3E%3C/g%3E%3C/defs%3E%3Cg fill='none' stroke='%23D9A441' stroke-opacity='.13' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cuse href='%23cup' transform='translate(30 34) rotate(-12)'/%3E%3Cuse href='%23steamer' transform='translate(112 24) rotate(8)'/%3E%3Cuse href='%23bowl' transform='translate(170 82) rotate(-6)'/%3E%3Cuse href='%23boba' transform='translate(62 108) rotate(14)'/%3E%3Cuse href='%23dumpling' transform='translate(138 148) rotate(-10)'/%3E%3Cuse href='%23teapot' transform='translate(26 168) rotate(6)'/%3E%3Cuse href='%23bao' transform='translate(96 184) rotate(-4)'/%3E%3Cuse href='%23cup' transform='translate(182 188) rotate(20)'/%3E%3C/g%3E%3C/svg%3E");
  --tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Crect width='40' height='40' fill='%237a1f16'/%3E%3Cpath d='M5 35V5H35V31H11V11H29V25H17V17H23' fill='none' stroke='%23d9a441' stroke-width='2.2'/%3E%3Ccircle cx='0' cy='0' r='2' fill='%233f9a74'/%3E%3Ccircle cx='40' cy='0' r='2' fill='%233f9a74'/%3E%3Ccircle cx='0' cy='40' r='2' fill='%233f9a74'/%3E%3Ccircle cx='40' cy='40' r='2' fill='%233f9a74'/%3E%3C/svg%3E");
}
/* the header: the dim sum nook behind the Food door in the hall (js/hall.js) — its warm amber light glowing in the
   middle, the faint stripes on the wall (as in the music and TV rooms), and along the bottom its wooden rail over
   cream tiles (the room's things and curtains: .room-side and .room-curtain, with Music's) */
body[data-area="food"] .page-header {
  isolation: isolate;
  padding-bottom: 46px;
  background:
    linear-gradient(to top, transparent 0 34px, #b4875c 34px 35px, #7a5236 35px 38px, transparent 38px),
    repeating-linear-gradient(0deg, rgb(0 0 0 / 0.16) 0 1px, transparent 1px 9px) left bottom / 100% 34px no-repeat,
    repeating-linear-gradient(90deg, rgb(0 0 0 / 0.16) 0 1px, transparent 1px 9px) left bottom / 100% 34px no-repeat,
    linear-gradient(rgb(244 230 192 / 0.14), rgb(244 230 192 / 0.1)) left bottom / 100% 34px no-repeat,
    repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px),
    radial-gradient(ellipse 65% 110% at 50% 72%, #b0703e 0%, #7a3a1e 40%, #3e1a0e 75%, #1e0d07 100%);
}
/* (the nook's wall continues below the header: see the page's content below; the body is just its darkest color) */
body[data-area="food"] { background: #1e0d07; }

/* the room below the beam: the same dim sum nook as the header, its wall carrying on down, with depth. From the
   bottom up (in the page's bottom padding, under the tray): the floorboards in two rows, the contact shadow where they
   meet the wall, a baseboard with a lit top edge, the door's cream tiles, the shadow the rail casts on them, the rail
   (a rounded molding catching the light), a soft shadow on the wall just above it. Then the wall: its faint stripes
   (as in the music and TV rooms), lit from the top and darker lower down and toward the corners, with the beam's
   light from above and the amber light pooled behind the menu */
body[data-area="food"] .page-content {
  position: relative;
  background:
    linear-gradient(rgb(0 0 0 / 0.5), transparent) left bottom 18px / 100% 8px no-repeat,
    repeating-linear-gradient(90deg, transparent 0 45px, rgb(0 0 0 / 0.35) 45px 46px, rgb(255 220 170 / 0.06) 46px 47px) left bottom / 100% 26px no-repeat,
    linear-gradient(#8a5832, #6a3c1e 48%, rgb(0 0 0 / 0.35) 48% 52%, #6a3c1e 52%, #4a2614) left bottom / 100% 26px no-repeat,
    linear-gradient(#c08a5c 0 1px, #5a3018 1px, #2a140c) left bottom 26px / 100% 7px no-repeat,
    linear-gradient(rgb(0 0 0 / 0.4), transparent) left bottom 70px / 100% 10px no-repeat,
    repeating-linear-gradient(0deg, rgb(0 0 0 / 0.2) 0 1px, transparent 1px 12px) left bottom 33px / 100% 47px no-repeat,
    repeating-linear-gradient(90deg, rgb(0 0 0 / 0.2) 0 1px, transparent 1px 12px) left bottom 33px / 100% 47px no-repeat,
    linear-gradient(rgb(244 230 192 / 0.2), rgb(244 230 192 / 0.13)) left bottom 33px / 100% 47px no-repeat,
    linear-gradient(#e0b07a, #a8703e 35%, #6a3e24 75%, #3b1d12) left bottom 80px / 100% 7px no-repeat,
    linear-gradient(transparent, rgb(0 0 0 / 0.25)) left bottom 87px / 100% 30px no-repeat,
    radial-gradient(ellipse 45% 520px at 50% 420px, rgb(255 207 122 / 0.14), transparent),
    radial-gradient(ellipse 70% 320px at 50% 0, rgb(255 190 120 / 0.12), transparent),
    radial-gradient(ellipse 130% 100% at 50% 40%, transparent 55%, rgb(12 4 2 / 0.5)),
    linear-gradient(rgb(255 255 255 / 0.03), rgb(0 0 0 / 0.18)),
    repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px),
    linear-gradient(#3e1a0e, #1e0d07);
}

/* the menu: rice paper, with the sections' titles in jade */
body[data-area="food"] .menu-section { color: var(--emerald); }
body[data-area="food"] .dish-photo { box-shadow: 0 4px 12px rgb(60 30 10 / 0.3); }

/* the tray: red lacquer with a gold rim, and each spot a little bamboo steamer seen from above */
body[data-area="food"] .order-tray {
  background:
    radial-gradient(circle at 30% 30%, rgb(255 255 255 / 0.16), transparent 45%),
    radial-gradient(ellipse at 50% 40%, #b8322a, #8e2018 65%, #5a120c);
  box-shadow:
    inset 0 0 0 5px rgb(90 18 12 / 0.55),
    inset 0 0 0 7px rgb(217 164 65 / 0.6),
    0 0 30px rgb(217 164 65 / 0.45),
    0 14px 34px rgb(0 0 0 / 0.6);
}
body[data-area="food"] .tray-slot {
  background: repeating-radial-gradient(circle, #d9b36a 0 3px, #c49a52 3px 5px);
  border: 3px solid #a07a3a;
  box-shadow: inset 0 0 0 2px rgb(255 240 200 / 0.35);
}
body[data-area="food"] .tray-slot.filled {
  display: grid;
  place-items: center;
  font-family: var(--font-title);
  font-size: 1.2rem;
  color: var(--ink);
  border: 4px solid #c9a258;
  box-shadow: 0 0 0 1px #8a6430, 0 3px 8px rgb(0 0 0 / 0.45);
}
body[data-area="food"] .flying-photo img { border-color: #c9a258; }

/* the bill: a teahouse receipt, with a red band on top and the teahouse's name in red */
body[data-area="food"] .bill { border-top: 8px solid var(--coral); }
body[data-area="food"] .bill-restaurant { color: var(--cobalt); }

/* ----- the teahouse's details ----- */

/* the page's title on the door's chalkboard: chalk letters on a dark green board with a few smudges of chalk dust,
   in a wooden frame (lit along the top, in shade along the bottom; the board set back in it), hanging from a string
   on a brass nail (::before), a chalk squiggle under the word (::after), and the amber light warm on the wall around */
body[data-area="food"] .page-title {
  display: inline-block;
  margin-top: 30px; /* room for the string */
  padding: 0.16em 0.8em 0.36em;
  font-family: "Fredericka the Great", var(--font-title);
  font-size: clamp(2.1rem, 6vw, 3.5rem);
  line-height: 1.1;
  color: #f4f6ee;
  text-shadow: 0 0 1px rgb(255 255 255 / 0.6), 0 0 8px rgb(255 255 255 / 0.15);
  background:
    radial-gradient(ellipse 35% 45% at 28% 72%, rgb(255 255 255 / 0.07), transparent),
    radial-gradient(ellipse 25% 35% at 78% 28%, rgb(255 255 255 / 0.05), transparent),
    linear-gradient(160deg, #24453a, #1a3329);
  border: 9px solid;
  border-color: #b07644 #7a4428 #5a3018 #8a5432;
  border-radius: 4px;
  box-shadow:
    inset 0 0 0 1px #2a140c,
    inset 0 4px 10px rgb(0 0 0 / 0.45),
    0 0 0 1px #2a140c,
    0 0 44px 6px rgb(255 190 110 / 0.16),
    0 12px 24px rgb(0 0 0 / 0.55);
}
/* the string, from the nail down to the frame's top, and the nail */
body[data-area="food"] .page-title::before {
  content: "";
  position: absolute;
  left: 15%;
  right: 15%;
  bottom: calc(100% + 9px);
  height: 32px;
  background:
    radial-gradient(circle at 50% 3px, #f1d596 0 1.2px, #d9a441 1.6px 3px, transparent 3.5px),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpath d='M50 3L0 100M50 3L100 100' fill='none' stroke='%232a1408' stroke-opacity='.75' stroke-width='1.3' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
}
/* the chalk squiggle under the word, drawn in once when the loading screen has gone */
body[data-area="food"] .page-title::after {
  content: "";
  position: absolute;
  left: 20%;
  right: 20%;
  bottom: 0.14em;
  height: 0.2em;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 12' preserveAspectRatio='none'%3E%3Cpath d='M2 7Q10 2 18 7T34 7T50 7T66 7T82 7T98 6' fill='none' stroke='%23f4f6ee' stroke-opacity='.75' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
}
body[data-area="food"]:not(:has(.loading)) .page-title::after { animation: chalk-draw 0.9s ease-out 0.4s both; }
@keyframes chalk-draw {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 0 0 0); }
}
@media (prefers-reduced-motion: reduce) {
  body[data-area="food"] .page-title::after { animation: none !important; }
}

/* the subtitle in italics, between two little gold curls of steam */
body[data-area="food"] .page-header .subtitle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-size: 1.2rem;
  font-style: italic;
  color: #fff3e2;
  text-shadow: 0 1px 8px rgb(30 12 4 / 0.9); /* readable over the amber light */
}
body[data-area="food"] .page-header .subtitle::before,
body[data-area="food"] .page-header .subtitle::after {
  content: "";
  flex: none;
  width: 14px;
  height: 20px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 20'%3E%3Cpath d='M5 19C2 15 8 12 5 8C3 5 6 2 8 1M10 19C8 16 12 14 10 11' fill='none' stroke='%23d9a441' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
@media (max-width: 440px) {
  body[data-area="food"] .page-header .subtitle::before,
  body[data-area="food"] .page-header .subtitle::after { display: none; }
}

/* Chinese knots hanging from both sides of the menu's arch, swaying a little */
body[data-area="food"] .menu { position: relative; }
.menu-knot {
  position: absolute;
  top: 150px;
  z-index: 1;
  width: 30px;
  transform-origin: 50% 0;
  animation: sway 4.6s ease-in-out infinite alternate;
  filter: drop-shadow(0 4px 6px rgb(0 0 0 / 0.4));
}
.menu-knot svg { display: block; width: 100%; }
.knot-left { left: -8px; }
.knot-right { right: -8px; animation-delay: -2.3s; }
@media (prefers-reduced-motion: reduce) {
  .menu-knot { animation: none; }
}

/* the rice paper: gold cloud scrolls in its two bottom corners, and a faint warm shade along its edges (gradients,
   not a blurred inner shadow: on a sheet this tall with an arched top, the blur was too slow to draw while scrolling) */
body[data-area="food"] .menu .enamel {
  background:
    linear-gradient(90deg, rgb(160 120 60 / 0.14), transparent 36px, transparent calc(100% - 36px), rgb(160 120 60 / 0.14)),
    linear-gradient(0deg, rgb(160 120 60 / 0.14), transparent 36px),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44'%3E%3Cg fill='none' stroke='%23c9a258' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 4V32Q4 40 12 40H40'/%3E%3Cpath d='M10 4V28Q10 34 16 34H40'/%3E%3Cpath d='M18 27a5 5 0 1 1 6 5'/%3E%3C/g%3E%3C/svg%3E") left 10px bottom 10px / 44px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44'%3E%3Cg fill='none' stroke='%23c9a258' stroke-width='2' stroke-linecap='round' transform='matrix(-1 0 0 1 44 0)'%3E%3Cpath d='M4 4V32Q4 40 12 40H40'/%3E%3Cpath d='M10 4V28Q10 34 16 34H40'/%3E%3Cpath d='M18 27a5 5 0 1 1 6 5'/%3E%3C/g%3E%3C/svg%3E") right 10px bottom 10px / 44px no-repeat,
    var(--enamel);
}

/* the sections' titles between two little gold diamonds */
body[data-area="food"] .menu-section::before,
body[data-area="food"] .menu-section::after {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin: 0 12px;
  vertical-align: 0.2em;
  background: var(--gold);
  rotate: 45deg;
}

/* on the tray: a pair of chopsticks resting on the rim (left) and a little teacup (right) */
body[data-area="food"] .order-tray::before,
body[data-area="food"] .order-tray::after {
  content: "";
  position: absolute;
  pointer-events: none;
  background: no-repeat center / contain;
  filter: drop-shadow(0 3px 3px rgb(0 0 0 / 0.45));
}
body[data-area="food"] .order-tray::before {
  top: -7px;
  left: 18px;
  width: 78px;
  height: 14px;
  rotate: -7deg;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='16' viewBox='0 0 90 16'%3E%3Cpath d='M2 5L88 2M2 11L88 12' stroke='%23e8d2a8' stroke-width='3.2' stroke-linecap='round'/%3E%3Cpath d='M68 2.7L88 2M68 11.8L88 12' stroke='%23b8322a' stroke-width='3.4' stroke-linecap='round'/%3E%3C/svg%3E");
}
body[data-area="food"] .order-tray::after {
  top: -16px;
  right: 22px;
  width: 32px;
  height: 24px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='26' viewBox='0 0 36 26'%3E%3Cpath d='M3 6H33Q32 24 18 24Q4 24 3 6Z' fill='%23fbf4e4' stroke='%23c9b896'/%3E%3Cpath d='M4 11.5H32' stroke='%232f5a9a' stroke-width='2.4'/%3E%3Cellipse cx='18' cy='6' rx='15' ry='3' fill='%23b07a3a' stroke='%23c9b896'/%3E%3C/svg%3E");
}

/* on the bill: a red seal stamped in the top right corner, a little crooked */
body[data-area="food"] .bill::after {
  content: "";
  position: absolute;
  top: 1em;
  right: 1em;
  width: 2.6em;
  height: 2.6em;
  opacity: 0.85;
  rotate: -8deg;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Crect x='2' y='2' width='36' height='36' rx='4' fill='%23c8382e'/%3E%3Crect x='6' y='6' width='28' height='28' rx='2' fill='none' stroke='%23fbf4e4' stroke-width='1.6'/%3E%3Cpath d='M12 13H28M20 13V28M13 20H27M12 28H28M15 17L13 24M25 17L27 24' stroke='%23fbf4e4' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat center / contain;
}


/* Order tray: a round lacquered tray with a golden rim, like a teahouse's serving tray (red lacquer in Food's theme).
   (its own name so it doesn't get mixed up with the golden plate under the cake) */
.order-tray {
  position: fixed;
  left: 50%;
  bottom: 20px;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 18px;
  width: max-content;
  max-width: calc(100vw - 24px);
  min-height: 84px;
  padding: 12px 18px 12px 28px;
  color: #fff6e6;
  background:
    radial-gradient(circle at 30% 30%, rgb(255 255 255 / 0.2), transparent 45%),
    radial-gradient(ellipse at 50% 40%, #c7874a, #9a5a2b 65%, #6e3b1a);
  border: 4px solid var(--gold);
  border-radius: 999px;
  box-shadow:
    inset 0 0 0 5px rgb(110 59 26 / 0.55),
    inset 0 0 0 7px rgb(217 164 65 / 0.6),
    0 0 30px rgb(217 164 65 / 0.45),
    0 14px 34px rgb(0 0 0 / 0.6);
  transform: translateX(-50%);
  /* its own layer for good: its big soft shadows are drawn once, and the photos flying over it and its shake
     only move layers around */
  will-change: transform;
}
/* Hidden below the screen until loading finishes; then it rises with a bounce */
.order-tray:not(.arrived) { transform: translate(-50%, 160%); }
.order-tray.arrived { animation: tray-rise 0.8s cubic-bezier(0.3, 1.5, 0.5, 1) 0.2s both; }
@keyframes tray-rise {
  from { transform: translate(-50%, 160%); }
  to { transform: translate(-50%, 0); }
}
/* A shake when a dish arrives */
.order-tray.received { animation: tray-shake 0.45s ease; }
@keyframes tray-shake {
  0%, 100% { transform: translate(-50%, 0); }
  30% { transform: translate(-50%, 6px) rotate(-1.5deg); }
  60% { transform: translate(-50%, -3px) rotate(1deg); }
}

.tray-title {
  font-family: var(--font-title);
  font-size: 1.35rem;
  white-space: nowrap;
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.55);
}

/* The tray's spots: a dashed circle per dish, filled when the dish arrives */
.tray-dishes { display: flex; align-items: center; gap: 6px; }
.tray-slot {
  display: block;
  width: 52px;
  height: 52px;
  overflow: hidden;
  background: rgb(0 0 0 / 0.18);
  border: 2px dashed rgb(255 246 230 / 0.6);
  border-radius: 50%;
}
.tray-slot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.tray-slot.filled {
  border: 3px solid var(--gold);
  box-shadow: 0 3px 8px rgb(0 0 0 / 0.45);
  animation: mini-arrive 0.35s cubic-bezier(0.3, 1.6, 0.5, 1);
}
/* the photo lands at the spot's full size: the spot gives a little bounce (starting small would make it jump) */
@keyframes mini-arrive { 40% { transform: scale(1.12); } }

/* (digits all the same width, so "1 of 6" → "2 of 6" never resizes the tray, which would redraw it) */
.tray-counter {
  padding: 3px 12px;
  font-size: 1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  background: rgb(0 0 0 / 0.28);
  border-radius: 999px;
}

/* Little bubble on top of the tray, until the first order */
.tray-hint {
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  padding: 6px 16px;
  font-weight: 700;
  white-space: nowrap;
  color: var(--ink);
  background: var(--enamel);
  border: 2px solid var(--gold);
  border-radius: 999px;
  box-shadow: 0 6px 14px rgb(0 0 0 / 0.4);
  transform: translateX(-50%);
  animation: hint-float 1.4s ease-in-out infinite alternate;
  transition: opacity 0.3s;
}
.tray-hint::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  border: 8px solid transparent;
  border-top-color: var(--gold);
  transform: translateX(-50%);
}
@keyframes hint-float { to { transform: translate(-50%, -6px); } }
.order-tray.has-order .tray-hint { opacity: 0; pointer-events: none; animation-play-state: paused; }

/* "Hesap, lütfen! (The bill, please)" shows below the dishes and hides the title and the counter
   (with the 5 dishes on the tray, the "5 of 5" is already visible) */
.ask-bill {
  all: unset;
  max-width: 10em;
  padding: 8px 18px;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  color: var(--ink);
  background: var(--gold);
  border-radius: 999px;
  cursor: pointer;
  animation: bill-beckon 1.4s ease-in-out infinite;
}
.ask-bill:focus-visible { outline: 3px solid var(--enamel); outline-offset: 2px; }
@keyframes bill-beckon { 50% { box-shadow: 0 0 22px var(--gold); } }
.order-tray:has(.ask-bill:not([hidden])) :is(.tray-title, .tray-counter) { display: none; }
/* With the bill button, the tray becomes two rows: the dishes on top and the button below */
.order-tray:has(.ask-bill:not([hidden])) {
  flex-direction: column;
  gap: 10px;
  padding: 14px 22px;
  border-radius: 36px;
}
.order-tray .ask-bill { max-width: none; text-wrap: balance; }

/* Compact tray up to 640px (it used to be 560px: between 561 and 640 the bill button spilled out of the tray) */
@media (max-width: 640px) {
  .order-tray { gap: 8px; min-height: 64px; padding: 8px 10px 8px 14px; }
  .tray-title { display: none; }
  .tray-dishes { gap: 3px; }
  .tray-slot { width: clamp(26px, 8vw, 38px); height: clamp(26px, 8vw, 38px); }
  .ask-bill { max-width: 9em; padding: 6px 12px; font-size: 0.9rem; }
}

/* The photo flying to the tray (js/food.js): the box travels, the div inside hops, the photo shrinks. Each one is
   kept on its own layer for the whole flight, so the browser only moves them and never redraws them */
.flying-photo {
  position: fixed;
  z-index: 30;
  pointer-events: none;
}
.flying-photo div { height: 100%; }
.flying-photo, .flying-photo * { will-change: transform; }
.flying-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 3px solid var(--gold);
  border-radius: 50%;
}

/* A dish's whole photo: the round preview opens it uncropped, with just the name under it (js/food.js) */
.dish-photo[role="button"] { cursor: zoom-in; }
body.viewer-open { overflow: hidden; }
.dish-viewer {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgb(5 7 15 / 0.85);
  cursor: zoom-out;
  animation: dish-viewer-in 0.2s ease-out;
}
.dish-viewer:focus { outline: none; }
.dish-viewer-frame {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin: 0;
  max-width: 100%;
}
.dish-viewer-photo {
  display: block;
  max-width: min(92vw, 720px);
  max-height: 78vh;
  object-fit: contain;
  border: 3px solid var(--gold);
  border-radius: 8px;
  box-shadow: 0 10px 40px rgb(0 0 0 / 0.6);
}
/* Still loading (.is-loading, js/food.js): the photo hidden in a square of room, and a gold ring spinning in its
   middle, like the gallery's (.gallery-spinner). The ring fades in after a moment, so a quick photo never shows it */
.dish-viewer-photo-box { position: relative; }
.dish-viewer-photo { transition: opacity 0.3s ease-out; }
.dish-viewer-photo-box.is-loading .dish-viewer-photo {
  min-width: min(60vw, 240px);
  min-height: min(60vw, 240px);
  opacity: 0;
}
.dish-viewer-spinner {
  position: absolute;
  inset: 0;
  width: 48px;
  height: 48px;
  margin: auto;
  border-radius: 50%;
  border: 4px solid rgb(255 255 255 / 0.2);
  border-top-color: var(--gold);
  pointer-events: none;
  display: none;
}
.dish-viewer-photo-box.is-loading .dish-viewer-spinner {
  display: block;
  animation: spin 0.9s linear infinite, dish-viewer-in 0.3s ease-out 0.3s backwards;
}
.dish-viewer-name {
  font-family: "Marcellus", serif;
  font-size: 1.3rem;
  text-align: center;
  color: var(--gold);
}
@keyframes dish-viewer-in {
  from { opacity: 0; }
}

/* The bill: receipt paper, coming out of the printer */
body.bill-open { overflow: hidden; }
.bill-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgb(5 7 15 / 0.75);
}
.bill {
  --serration: 12px;
  position: relative;
  width: min(360px, 100%);
  /* Never scrolls: if it doesn't fit, js/food.js shrinks the font (everything in here is in "em") */
  max-height: 92vh;
  max-height: 92svh;
  overflow: hidden;
  padding: 1.7em 1.5em calc(1.4em + var(--serration));
  font-family: "Courier Prime", "Courier New", monospace;
  font-size: 0.95rem;
  line-height: 1.5;
  color: #222;
  background: #fbfaf5;
  box-shadow: 0 20px 50px rgb(0 0 0 / 0.6);
  /* Serrated bottom edge, like paper torn from the printer */
  -webkit-mask:
    linear-gradient(#000 0 0) top / 100% calc(100% - var(--serration)) no-repeat,
    conic-gradient(from -45deg at bottom, #0000, #000 1deg 89deg, #0000 90deg) bottom / calc(var(--serration) * 2) var(--serration) repeat-x;
  mask:
    linear-gradient(#000 0 0) top / 100% calc(100% - var(--serration)) no-repeat,
    conic-gradient(from -45deg at bottom, #0000, #000 1deg 89deg, #0000 90deg) bottom / calc(var(--serration) * 2) var(--serration) repeat-x;
  animation: print 1.3s steps(14) both;
}
@keyframes print {
  from { clip-path: inset(0 0 100% 0); }
  to { clip-path: inset(0 0 0 0); }
}

.bill p { margin: 0; }
.bill-restaurant { font-size: 1.25em; font-weight: 700; text-align: center; }
/* the house motto under the name, like a slogan printed on a receipt */
.bill-motto { margin-top: 0.2em !important; text-align: center; letter-spacing: 0.15em; color: #666; }
.bill-table { margin-bottom: 0.9em !important; text-align: center; color: #666; }
.bill-items {
  margin: 0 0 0.8em;
  padding: 0.8em 0;
  list-style: none;
  border-block: 2px dashed #bbb;
}
.bill-line { display: flex; justify-content: space-between; gap: 1em; }
.bill-line span:last-child { text-align: right; }
/* Each dish on a single line: "name ........ price". A name too long is cut with "…"; the price is never cut */
.bill-items .bill-line { align-items: baseline; gap: 0.4em; }
.bill-dish { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bill-dots { flex: 1 0 1em; border-bottom: 2px dotted #999; }
.bill-items .bill-line span:last-child { flex: none; white-space: nowrap; }.bill-total { margin: 0.25em 0 0.5em !important; font-size: 1.15em; font-weight: 700; }
.bill-message {
  margin: 1em 0 0.8em !important;
  padding-top: 0.8em;
  text-align: center;
  font-style: italic;
  border-top: 2px dashed #bbb;
}
.bill-footer { text-align: center; font-weight: 700; }
.close-bill {
  all: unset;
  display: block;
  margin: 1.1em auto 0;
  padding: 0.4em 1em;
  font-family: var(--font-text);
  font-weight: 700;
  color: var(--enamel);
  background: var(--cobalt);
  border-radius: 999px;
  cursor: pointer;
}
.close-bill:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }

/* ---------- Reading: the cabin library (js/reading.js draws it at the screen's size) ---------- */

.cabin {
  position: relative;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  background: #1a0f08;
}
.cabin-scene { position: absolute; inset: 0; width: 100%; height: 100%; }
.cabin-books { position: absolute; inset: 0; pointer-events: none; }
/* The fireflies over the bank outside the window (js/reading.js), each blinking slowly at its own pace */
.cabin-scene .firefly { animation: firefly-blink 3s ease-in-out infinite alternate; }
@keyframes firefly-blink {
  from { opacity: 0.1; }
  to { opacity: 1; }
}
/* The ripples on the lake outside the window (js/reading.js): each drifts its --drift to one side and back,
   fading in as it moves and out at each end, so the water seems to move */
.cabin-scene .water-ripple {
  opacity: 0.12;
  animation: water-drift 6s ease-in-out infinite alternate;
}
@keyframes water-drift {
  0% { transform: translateX(calc(var(--drift) * -1)); opacity: 0.03; }
  50% { opacity: 0.22; }
  100% { transform: translateX(var(--drift)); opacity: 0.03; }
}
/* The smoke curling up from the incense on the desk and the steam off the tea (js/reading.js): each wisp rises its
   --rise from its source, drifting --sway to one side, widening and fading as it goes, the wisps staggered so the
   column never breaks. The incense's ember glows on and off */
.cabin-scene .wisp {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: wisp-rise var(--rise-time, 4.8s) ease-out infinite;
}
@keyframes wisp-rise {
  0% { transform: translate(0, 0) scale(0.8, 0.7); opacity: 0; }
  20% { opacity: 1; }
  100% { transform: translate(var(--sway), var(--rise)) scale(1.5, 1.3); opacity: 0; }
}
/* the incense's wisps: their wave flows up them at the pace they rise (js/reading.js), so they rise at a steady
   speed and only widen (stretching them taller would outrun the wave) */
.cabin-scene .wisp.flowing { animation-name: wisp-flow; animation-timing-function: linear; }
@keyframes wisp-flow {
  0% { transform: translate(0, 0) scale(0.8, 1); opacity: 0; }
  20% { opacity: 1; }
  100% { transform: translate(var(--sway), var(--rise)) scale(1.5, 1); opacity: 0; }
}
.cabin-scene .ember {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: ember-glow 1.6s ease-in-out infinite alternate;
}
@keyframes ember-glow {
  from { opacity: 0.45; transform: scale(0.8); }
  to { opacity: 1; transform: scale(1.15); }
}
/* The bulbs hanging from the ceiling (js/reading.js): each sways gently on its cord, pivoting where the cord comes out
   of the ceiling (js/reading.js sets that point: the drawing's own coordinates) */
.cabin-scene .bulb-sway {
  transform-box: view-box;
  animation: bulb-sway 3.4s ease-in-out infinite alternate;
}
@keyframes bulb-sway {
  from { rotate: -1.5deg; }
  to { rotate: 1.5deg; }
}
/* The tree outside the window (js/reading.js): its crown sways from the top of the trunk (js/reading.js sets that
   point), its lit leaves fluttering --flutter to and fro on their own, a little quicker */
.cabin-scene .crown-sway {
  transform-box: view-box;
  animation: crown-sway 3.2s ease-in-out infinite alternate;
}
@keyframes crown-sway {
  from { rotate: -2.5deg; }
  to { rotate: 2.5deg; }
}
.cabin-scene .leaves-flutter { animation: leaves-flutter 1.3s ease-in-out -0.4s infinite alternate; }
@keyframes leaves-flutter {
  from { translate: calc(var(--flutter) * -1) 0; }
  to { translate: var(--flutter) calc(var(--flutter) * -0.5); }
}
/* The grass on the bank outside the window (js/reading.js): each tuft sways its --sway either way from its foot, the
   tufts staggered so the breeze rolls across */
.cabin-scene .grass-sway {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: grass-sway 2.6s ease-in-out infinite alternate;
}
@keyframes grass-sway {
  from { transform: skewX(calc(var(--sway) * -1)); }
  to { transform: skewX(var(--sway)); }
}
@media (prefers-reduced-motion: reduce) {
  .cabin-scene .firefly, .cabin-scene .water-ripple, .cabin-scene .wisp, .cabin-scene .ember, .cabin-scene .grass-sway,
  .cabin-scene .bulb-sway, .cabin-scene .crown-sway, .cabin-scene .leaves-flutter { animation: none; }
  .cabin-scene .wisp { opacity: 0.6; }
}

/* The room's name on an improvised sign, above the bookshelf (js/reading.js sets where): an offcut of rough pine,
   nailed up level, its letters burned in. The board itself is the ::before (so its rough outline can be cut out
   without cutting off the shadow, which follows that outline), the nails the ::after */
.cabin-sign {
  position: absolute;
  z-index: 2;
  width: max-content;
  padding: 11px 38px 13px;
  translate: -50% 0;
  text-align: center;
  color: #2a1608;
  filter: drop-shadow(0 6px 7px rgb(0 0 0 / 0.55));
  pointer-events: none;
}
/* the board: weathered pine, its grain streaked across it (noise stretched sideways, like the walls'), darker toward
   its edges (dirt and old scorch marks); roughly cut all around, in a few long
   uneven lines: its ends broken off jagged, its top and bottom edges dipping a little here and there */
.cabin-sign::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='70'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.012 0.4' numOctaves='3' seed='5' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.24 0 0 0 0 0.13 0 0 0 0 0.05 1.1 0 0 0 -0.4'/%3E%3C/filter%3E%3Crect width='260' height='70' filter='url(%23g)'/%3E%3C/svg%3E") 0 0 / 260px 70px,
    linear-gradient(90deg, rgb(40 20 8 / 0.4), transparent 7%, transparent 93%, rgb(40 20 8 / 0.4)),
    linear-gradient(rgb(40 20 8 / 0.2), transparent 18%, transparent 80%, rgb(40 20 8 / 0.3)),
    linear-gradient(#dcb47e, #c49a64);
  clip-path: polygon(2.6% 0%, 23% 5%, 48% 1%, 72% 6%, 97.4% 0%, 100% 24%, 97.8% 51%, 99.8% 76%, 97.2% 100%, 77% 95%,
    53% 99%, 28% 94%, 2% 100%, 0% 70%, 2.6% 43%, 0.3% 18%);
}
/* two iron nails driven in near its ends */
.cabin-sign::after {
  --nail: radial-gradient(circle at 40% 35%, #c8c0b0 0 0.8px, #6a6460 1.3px 2.6px, #2a2622 3px 3.4px, transparent 3.9px);
  content: "";
  position: absolute;
  inset: 0;
  background: var(--nail) left 13px center / 9px 9px no-repeat, var(--nail) right 13px center / 9px 9px no-repeat;
}
.cabin-sign > * { position: relative; }
/* the letters burned into the wood: dark, a little scorched around, the wood catching the light along their lower
   edge */
.cabin-sign h1 {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 700; /* Marcellus has no bold of its own: the browser thickens it */
  font-size: clamp(1.3rem, 3.4vh, 2rem);
  line-height: 1.1;
  text-shadow: 0 0 1.5px rgb(60 30 10 / 0.6), 0 1px 0 rgb(255 225 175 / 0.3);
}
.cabin-sign p {
  margin: 2px 0 0;
  font-size: clamp(0.8rem, 1.8vh, 0.95rem);
  font-style: italic;
  color: #3e2412;
  text-shadow: 0 1px 0 rgb(255 225 175 / 0.25);
}

/* A polaroid taped crooked over the window's corner: a square photo on a cream card, the bottom border thicker
   (js/reading.js sets where and how big: --pic-w is its width, since % padding would follow the cabin's width) */
.cabin-pic {
  position: absolute;
  box-sizing: border-box;
  width: var(--pic-w);
  padding: calc(var(--pic-w) * 0.06) calc(var(--pic-w) * 0.06) calc(var(--pic-w) * 0.24);
  background: linear-gradient(160deg, #fbf7ec, #ece4d2);
  border-radius: 2px;
  box-shadow: 0 6px 14px rgb(0 0 0 / 0.55);
  rotate: -10deg;
}
/* The two photos together, one button the size of the first one (the second peeks out of it): pointed at, both
   lifted a little off the wall together, to say they open */
.cabin-pics {
  all: unset;
  position: absolute;
  z-index: 1;
  width: var(--pic-w);
  height: calc(var(--pic-w) * 1.18);
  cursor: zoom-in;
  transition: scale 0.2s ease;
  /* a tight white glow breathing around both photos, so they aren't missed */
  filter: drop-shadow(0 0 2px rgb(255 255 255 / 0.6)) drop-shadow(0 0 4px rgb(255 255 255 / 0.4));
  animation: pics-glow 2.4s ease-in-out infinite alternate;
}
@keyframes pics-glow {
  to { filter: drop-shadow(0 0 3px rgb(255 255 255 / 1)) drop-shadow(0 0 7px rgb(255 255 255 / 0.85)) brightness(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  .cabin-pics { animation: none; }
}
/* Once touched, they've been found: the glow goes away */
.cabin-pics.found { animation: none; filter: none; }
.cabin-pics:hover,
.cabin-pics:focus-visible { scale: 1.08; }
.cabin-pics:focus-visible { outline: 3px solid #f3d58a; outline-offset: 4px; }
.cabin-pic img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; filter: sepia(0.15) saturate(0.9); }
/* A strip of tape holding it to the wall */
.cabin-pic::before {
  content: "";
  position: absolute;
  top: calc(var(--pic-w) * -0.06);
  left: 50%;
  width: 40%;
  height: calc(var(--pic-w) * 0.14);
  translate: -50% 0;
  rotate: 4deg;
  background: rgb(240 228 196 / 0.7);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
}
/* A caption handwritten in ink on the thick bottom border: one line of soft, even waves (too small to read anyway, so
   it's just the flow of cursive) */
.cabin-pic::after {
  content: "";
  position: absolute;
  left: 14%;
  right: 14%;
  bottom: calc(var(--pic-w) * 0.06);
  height: calc(var(--pic-w) * 0.12);
  opacity: 0.6;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 30' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%232b3a6b' stroke-width='1.2' stroke-linecap='round' vector-effect='non-scaling-stroke'%3E%3Cpath vector-effect='non-scaling-stroke' d='M2 17q4-4 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0'/%3E%3C/g%3E%3C/svg%3E") no-repeat center / 100% 100%;
}
/* The second photo, fanned out the other way behind the first, its tape tilted the other way too (and its caption
   mirrored, so the two don't read as the same scribble) */
.cabin-pic-second { rotate: 8deg; }
.cabin-pic-second::before { rotate: -5deg; }
.cabin-pic-second::after { scale: -1 1; }

/* A photo from the window shown bigger: the cabin dims behind it (like behind an open book), the same polaroid as at
   the window grown big (a square photo, the same borders), a little crooked, the book's close button on its corner */
body.photo-open { overflow: hidden; }
.photo-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 20px;
  padding: 16px;
  background: rgb(10 6 3 / 0.78);
}
/* (the miniature's proportions, .cabin-pic: borders of 6% of the width, the bottom one 24%, so the card is 1.18 times
   as tall as wide. --view-w, its width: as big as fits the screen with the arrows under it) */
.photo-view {
  --view-w: min(calc(100vw - 32px), calc((100svh - 130px) / 1.18), 520px);
  position: relative;
  box-sizing: border-box;
  width: var(--view-w);
  margin: 0;
  padding: calc(var(--view-w) * 0.06) calc(var(--view-w) * 0.06) calc(var(--view-w) * 0.24);
  background: linear-gradient(160deg, #fbf7ec, #ece4d2);
  border-radius: 3px;
  box-shadow: 0 24px 40px rgb(0 0 0 / 0.6);
  rotate: -2deg;
  touch-action: pan-y; /* sideways swipes are ours: they turn to the other photos */
  user-select: none;
}
/* in the frame's wide bottom margin: what's written on it by hand, in ink (centered in it, where the miniature has its
   scribble) */
.photo-caption {
  position: absolute;
  left: 6%;
  right: 6%;
  bottom: 0;
  height: calc(var(--view-w) * 0.24);
  display: grid;
  place-items: center;
  margin: 0;
  font-family: "Caveat", cursive;
  font-weight: 500;
  font-size: clamp(1.3rem, 3.2vh, 1.7rem);
  line-height: 1.1;
  text-align: center;
  color: #3a2a1c;
}
/* the arrows side by side under the photo, for going to the other photos without swiping: round wooden knobs with a
   brass rim, like the cabin's (lit from the top left, a highlight along their top, shaded inside along their bottom,
   their shadow under them), a cream arrow on each. Pointed at, they lift a little and glow; pressed, they sink in */
.photo-controls { display: flex; gap: 28px; }
/* its buttons (the close one and the arrows) wait for the photo to land, then fade in; hidden meanwhile, so they
   can't be tapped yet either */
.photo-view .book-close,
.photo-controls { transition: opacity 0.2s ease, visibility 0.2s; }
.photo-backdrop.popping .book-close,
.photo-backdrop.popping .photo-controls { visibility: hidden; opacity: 0; }
.photo-arrow {
  all: unset;
  box-sizing: border-box;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  color: #f3e2b8;
  background: radial-gradient(circle at 35% 30%, #a06a40, #6a4024 60%, #4a2c16);
  border: 2px solid #d9a441;
  border-radius: 50%;
  box-shadow:
    inset 0 2px 0 rgb(255 225 170 / 0.3),
    inset 0 -3px 5px rgb(0 0 0 / 0.4),
    0 6px 14px rgb(0 0 0 / 0.5);
  cursor: pointer;
  transition: translate 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.photo-arrow svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 1px 0 rgb(0 0 0 / 0.5));
}
.photo-arrow[hidden] { display: none; }
.photo-arrow:hover {
  translate: 0 -2px;
  filter: brightness(1.12);
  box-shadow:
    inset 0 2px 0 rgb(255 225 170 / 0.3),
    inset 0 -3px 5px rgb(0 0 0 / 0.4),
    0 8px 16px rgb(0 0 0 / 0.5),
    0 0 16px rgb(255 207 122 / 0.45);
}
.photo-arrow:active {
  translate: 0 1px;
  box-shadow: inset 0 3px 6px rgb(0 0 0 / 0.5), 0 3px 8px rgb(0 0 0 / 0.5);
}
.photo-arrow:focus-visible { outline: 3px solid #f3d58a; outline-offset: 3px; }
.photo-view img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover; /* (cropped square, like the miniature's) */
  filter: sepia(0.15) saturate(0.9);
}

/* Her books on the shelf: a cloth spine with gold bands, the title along it, glowing softly to say "touch me" */
.spine {
  all: unset;
  box-sizing: border-box;
  position: absolute;
  display: grid;
  place-items: center;
  color: #f3d58a;
  background:
    linear-gradient(90deg, rgb(0 0 0 / 0.35), transparent 30%, rgb(255 255 255 / 0.18) 50%, transparent 70%, rgb(0 0 0 / 0.35)),
    var(--book);
  border-radius: 1px; /* only just softened */
  box-shadow: inset 0 6px 0 -3px #d9a441, inset 0 -6px 0 -3px #d9a441, 0 0 10px rgb(255 207 122 / 0.35);
  cursor: pointer;
  pointer-events: auto;
  animation: spine-glow 2.8s ease-in-out infinite alternate;
  transition: translate 0.2s ease, box-shadow 0.2s ease;
}
.spine.light { color: #3a2414; }
.spine-title {
  max-height: 86%;
  overflow: hidden;
  font-family: var(--font-title);
  line-height: 1;
  white-space: nowrap;
  text-overflow: ellipsis;
  writing-mode: vertical-rl;
  rotate: 180deg;
}
/* A Chinese title (js/reading.js): upright characters reading from the top down, as on Chinese spines (turned like
   the English ones, they'd stand on their heads) */
.spine-title:lang(zh) { rotate: none; }
/* Pointed at (only where there's a real pointer: a phone keeps the last thing touched "hovered", which would leave a
   book lifted on the shelf after it's been read and closed) or reached with the keyboard: lifted a little, glowing */
.spine:focus-visible {
  outline: none;
  translate: 0 -6px;
  box-shadow: inset 0 6px 0 -3px #d9a441, inset 0 -6px 0 -3px #d9a441, 0 0 18px rgb(255 207 122 / 0.8);
}
@media (hover: hover) {
  .spine:hover {
    translate: 0 -6px;
    box-shadow: inset 0 6px 0 -3px #d9a441, inset 0 -6px 0 -3px #d9a441, 0 0 18px rgb(255 207 122 / 0.8);
  }
}
/* Lifted, it still covers the spot it rose from: an invisible strip under it, so the pointer near its bottom edge
   stays on it (instead of it dropping back under the pointer, lifting again, and so on) */
.spine::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 0;
}
@media (hover: hover) {
  .spine:hover::after { height: 8px; }
}
/* Lying down: the gold bands at its ends, the title read across */
.spine.lying {
  background:
    linear-gradient(rgb(0 0 0 / 0.35), transparent 30%, rgb(255 255 255 / 0.18) 50%, transparent 70%, rgb(0 0 0 / 0.35)),
    var(--book);
  box-shadow: inset 6px 0 0 -3px #d9a441, inset -6px 0 0 -3px #d9a441, 0 0 10px rgb(255 207 122 / 0.35);
}
.spine.lying .spine-title { max-height: none; max-width: 88%; writing-mode: horizontal-tb; rotate: none; }
.spine.lying:focus-visible { box-shadow: inset 6px 0 0 -3px #d9a441, inset -6px 0 0 -3px #d9a441, 0 0 18px rgb(255 207 122 / 0.8); }
@media (hover: hover) {
  .spine.lying:hover { box-shadow: inset 6px 0 0 -3px #d9a441, inset -6px 0 0 -3px #d9a441, 0 0 18px rgb(255 207 122 / 0.8); }
}
/* Leaning on one side, turned around its bottom corner (TILT in js/reading.js) */
.spine.tilt-left { rotate: -10deg; transform-origin: 0 100%; }
.spine.tilt-right { rotate: 10deg; transform-origin: 100% 100%; }
@keyframes spine-glow {
  from { filter: brightness(1); }
  to { filter: brightness(1.18); }
}
@media (prefers-reduced-motion: reduce) {
  .spine { animation: none; }
}

/* The book in the foreground: the cabin dims behind it */
body.book-open { overflow: hidden; }
.book-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: 16px;
  overflow: hidden; /* the opened cover may swing past the screen's left edge on phones */
  background: rgb(10 6 3 / 0.78);
}
.book-stage { position: relative; }
.book {
  position: relative;
  /* on phones, narrow enough (107vw tall = 71vw wide) that a fifth of the open book's left page shows beside it */
  height: min(78vh, 78svh, 107vw);
  aspect-ratio: 2 / 3;
  perspective: 2000px;
  filter: drop-shadow(0 24px 40px rgb(0 0 0 / 0.6));
}
/* The page under the cover: warm paper, a soft shadow along the binding, the passage in the middle */
.book-page {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding: 14% 12%;
  overflow-y: auto;
  color: #3a2414;
  background:
    linear-gradient(90deg, rgb(90 60 30 / 0.28), transparent 9%),
    radial-gradient(ellipse at 60% 40%, #f7ecd2, #eadbb6);
  border-radius: 3px 8px 8px 3px;
}
/* sideways swipes turn the cover and the pages (js/reading.js); dragging with a mouse doesn't select the text.
   touch-action on every part of the book: the page scrolls (overflow-y), and a phone only reads touch-action up to
   the nearest scrolling box, so a finger on the passage would otherwise let the browser take the swipe */
.book { user-select: none; -webkit-user-select: none; }
.book, .book * { touch-action: pan-y; }
.book-passage {
  margin: 0;
  font-size: clamp(1rem, 2.6vh, 1.25rem);
  line-height: 1.6;
}
.book-page-number {
  position: absolute;
  bottom: 5%;
  left: 0;
  right: 0;
  font-size: 0.85rem;
  font-style: italic;
  text-align: center;
  opacity: 0.6;
}
/* The page being turned: it rotates around the spine like the cover, its passage in front and blank paper on its
   back. --turn (0 flat, 1 standing up) shades it as it lifts (js/reading.js) */
.book-leaf {
  --turn: 0;
  position: absolute;
  inset: 0;
  transform-origin: left center;
  transform-style: preserve-3d;
}
.book-leaf > * { -webkit-backface-visibility: hidden; backface-visibility: hidden; }
.book-leaf-front::after,
.book-leaf-back::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: var(--turn);
}
.book-leaf-front::after { background: linear-gradient(90deg, rgb(60 35 15 / 0.05), rgb(60 35 15 / 0.4)); }
.book-leaf-back::after { background: linear-gradient(270deg, rgb(60 35 15 / 0.05), rgb(60 35 15 / 0.3)); }
/* The back of a page: blank paper with the binding's shadow on its right. The leaf's back while it turns, and the
   verso: the pages already turned, lying on the left (the same look, so the leaf lands on it seamlessly) */
.book-leaf-back,
.book-verso {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(270deg, rgb(90 60 30 / 0.28), transparent 9%),
    radial-gradient(ellipse at 40% 40%, #f7ecd2, #eadbb6);
  border-radius: 8px 3px 3px 8px;
}
.book-leaf-back { transform: rotateY(180deg); }
/* the text printed on the back of the pages, just pretending: faint lines inside the page's margins, a few of them
   stopping short like the end of a sentence or a paragraph (six lines, again and again down the page) */
.book-leaf-back::before,
.book-verso::before {
  content: "";
  position: absolute;
  inset: 14% 12% 18%;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 60' preserveAspectRatio='none'%3E%3Cg fill='%23503720' fill-opacity='0.26'%3E%3Crect y='4' width='100' height='2'/%3E%3Crect y='14' width='97' height='2'/%3E%3Crect y='24' width='100' height='2'/%3E%3Crect y='34' width='58' height='2'/%3E%3Crect y='44' width='100' height='2'/%3E%3Crect y='54' width='91' height='2'/%3E%3C/g%3E%3C/svg%3E") 0 0 / 100% clamp(84px, 13vh, 110px);
  pointer-events: none;
}
/* (not on the back cover) */
.book-leaf.back-cover .book-leaf-back::before { display: none; }
/* The last leaf's back is the back cover: the book's plain cloth, the spine's shading on its right (mirroring the
   front cover) */
.book-leaf.back-cover .book-leaf-back {
  background:
    linear-gradient(270deg, rgb(0 0 0 / 0.35), transparent 8%, rgb(255 255 255 / 0.1) 10%, transparent 14%),
    var(--book);
}
.book-verso { transform: translateX(-100%); }

/* The book coming off the shelf and going back (js/reading.js flyingBook): a little 3D book, its faces kept in 3D
   around its middle, built at the open book's size (and shrunk toward the shelf, so it stays sharp): a cover (the
   front one, a copy of the open book's; or the back one, the book's cloth with the binding's shading on its right and
   its outer corners rounded, like the back cover's) and its spine (the shelf's own, copied, scaled up) */
.book-flying {
  position: fixed;
  z-index: 41;
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
  pointer-events: none;
}
.book-flying-cover,
.book-flying-spine {
  position: absolute;
  top: 0;
  height: 100%;
  backface-visibility: hidden;
}
/* (the front cover's copy without the open book's shading for its turn: lying flat; both covers square along the
   binding, where they meet the spine) */
.book-flying-front { --turn: 0; }
.book-flying-front .book-cover { border-radius: 0 8px 8px 0; box-shadow: none; } /* (nor its gold frame) */
.book-flying-back {
  border-radius: 8px 0 0 8px;
  background:
    linear-gradient(270deg, rgb(0 0 0 / 0.35), transparent 8%, rgb(255 255 255 / 0.1) 10%, transparent 14%),
    var(--book);
}
.book-flying-spine { animation: none; transition: none; pointer-events: none; }

/* The swipe hint, just under the book: only while the cover is open */
.book-hint {
  position: absolute;
  top: calc(100% + 14px);
  left: 0;
  right: 0;
  margin: 0;
  font-size: 0.95rem;
  font-style: italic;
  text-align: center;
  color: #f3d58a;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.2s, visibility 0.2s;
}
/* (it appears once the cover has mostly swung open) */
.book.opened:not(.at-back) + .book-hint { visibility: visible; opacity: 0.85; transition: opacity 0.4s 0.5s, visibility 0.4s 0.5s; }
/* The cover and its endpaper turn together around the left edge, following the finger like the pages
   (js/reading.js; --turn shades them as they lift) */
.book-flap {
  --turn: 0;
  position: absolute;
  inset: 0;
  transform-origin: left center;
  transform-style: preserve-3d;
}
.book.can-open .book-flap { cursor: pointer; }
.book.opened .book-flap { transform: rotateY(-180deg); }
.book-flap > * { -webkit-backface-visibility: hidden; backface-visibility: hidden; }
.book-cover::after,
.book-endpaper::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: var(--turn);
}
.book-cover::after { background: linear-gradient(90deg, rgb(20 10 5 / 0.1), rgb(20 10 5 / 0.5)); }
.book-endpaper::after { background: linear-gradient(270deg, rgb(60 35 15 / 0.05), rgb(60 35 15 / 0.3)); }
/* The inside of the cover (seen once it has swung open): plain paper with the binding's shadow on its right */
.book-endpaper {
  position: absolute;
  inset: 0;
  transform: rotateY(180deg);
  background:
    linear-gradient(270deg, rgb(90 60 30 / 0.3), transparent 9%),
    #e6d5ae;
  border-radius: 8px 3px 3px 8px;
  box-shadow: inset 0 0 0 6px var(--book);
}
/* The cover: the book's cloth with the title and the author in gold (until the real cover arrives, which covers it) */
.book-cover {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 1em;
  height: 100%;
  padding: 12% 10%;
  overflow: hidden;
  text-align: center;
  color: #f3d58a;
  background:
    linear-gradient(90deg, rgb(0 0 0 / 0.35), transparent 8%, rgb(255 255 255 / 0.1) 10%, transparent 14%),
    var(--book);
  border-radius: 3px 8px 8px 3px;
  box-shadow: inset 0 0 0 6px rgb(217 164 65 / 0.5), inset 0 0 0 7px rgb(0 0 0 / 0.3);
}
.book.light .book-cover { color: #3a2414; }
.book-cover-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.book-cover-title { font-family: var(--font-title); font-size: clamp(1.4rem, 4.5vh, 2.4rem); line-height: 1.15; }
.book-cover-author { font-size: clamp(0.95rem, 2.4vh, 1.2rem); font-style: italic; }
.book-close {
  all: unset;
  position: absolute;
  top: -14px;
  right: -14px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  font-size: 1.6rem;
  line-height: 1;
  color: #3a2414;
  background: #f3e2b8;
  border: 2px solid #d9a441;
  border-radius: 50%;
  box-shadow: 0 4px 10px rgb(0 0 0 / 0.5);
  cursor: pointer;
}
.book-close:focus-visible { outline: 3px solid #f3d58a; outline-offset: 2px; }

/* ---------- TV: old wooden television ---------- */

.tv-room { display: flex; flex-direction: column; align-items: center; }
.tv-room .tracks-panel { width: 100%; }

/* TV guide on the left; the TV and the show's message on the right.
   On narrow screens, the guide comes on top and the TV below. */
.tv-stage {
  display: grid;
  grid-template-columns: minmax(240px, 300px) minmax(0, 640px);
  justify-content: center;
  align-items: start;
  gap: 28px;
  width: 100%;
}
.tv-column { display: flex; flex-direction: column; align-items: center; gap: 28px; min-width: 0; }
.tv-guide { position: sticky; top: 70px; }
.tv-guide .enamel { padding: 14px; }
.guide-title {
  margin: 2px 8px 6px;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: 1.35rem;
  color: var(--cobalt);
}
.tv-guide .guide { margin-top: 0; }

@media (max-width: 900px) {
  .tv-stage { grid-template-columns: minmax(0, 640px); }
  .tv-guide { position: static; }
  /* In one column, the TV and the show's description come first; the guide moves down below them */
  .tv-column { order: -1; }
}

.television {
  position: relative;
  display: flex;
  gap: 18px;
  width: min(640px, 100%);
  margin-top: 70px;
  padding: 22px;
  background: linear-gradient(160deg, #7a4428, #4a2616 60%, #3a1c10);
  border: 3px solid var(--gold);
  border-radius: 28px;
  box-shadow: inset 0 0 0 8px rgb(0 0 0 / 0.15), 0 30px 60px rgb(0 0 0 / 0.55);
}

/* "Rabbit ears" antenna */
.television::before,
.television::after {
  content: "";
  position: absolute;
  top: -84px;
  left: 50%;
  width: 4px;
  height: 84px;
  border-radius: 2px;
  background: linear-gradient(#f1d596, #a57d2e);
  transform-origin: 50% 100%;
}
.television::before { transform: translateX(-50%) rotate(-30deg); }
.television::after { transform: translateX(-50%) rotate(30deg); }
.antenna-base {
  position: absolute;
  top: -16px;
  left: 50%;
  width: 52px;
  height: 18px;
  border-radius: 26px 26px 0 0;
  background: radial-gradient(circle at 50% 100%, #f1d596, var(--gold) 50%, #8a6420);
  transform: translateX(-50%);
}

.screen {
  position: relative;
  flex: 1;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: #05070f;
  border: 6px solid #1a0e08;
  border-radius: 26px / 20px;
  box-shadow: inset 0 0 40px rgb(0 0 0 / 0.8);
}

/* The screen's glass: scan lines, dark edges and a reflection */
.screen::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background:
    linear-gradient(135deg, rgb(255 255 255 / 0.12), transparent 40%),
    repeating-linear-gradient(to bottom, transparent 0 2px, rgb(0 0 0 / 0.18) 2px 3px),
    radial-gradient(ellipse at center, transparent 55%, rgb(0 0 0 / 0.6));
}

/* Static between one channel and the next */
.screen::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E") 0 0 / 200px 200px, #777;
}
/* Light grain of an old TV, always moving while the TV is on */
/* mix-blend-mode: overlay makes the middle gray "invisible": only the light and dark specks
   show, so the picture keeps its color and contrast instead of looking washed out */
.screen:not(.off)::before {
  opacity: 0.9;
  mix-blend-mode: overlay;
  animation: static 0.2s steps(3) infinite;
}
/* Heavy static when changing channels and while the channel's song hasn't started yet (normal blending: it covers the picture) */
.screen.static::before,
.screen.no-signal::before { opacity: 1; mix-blend-mode: normal; animation: static 0.12s steps(3) infinite; }

@keyframes static {
  33% { background-position: -60px 40px; }
  66% { background-position: 50px -30px; }
}

.show {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 4px;
  padding: 8% 9%;
  color: var(--enamel);
  text-align: left;
  background: radial-gradient(ellipse at 30% 20%, #2a4a9a, var(--night) 70%);
}
.screen.off .show { visibility: hidden; }
.screen.turning-on .show { animation: tv-on 0.5s ease-out; }
/* Turning off: the picture flattens into a bright line and disappears into a dot */
.screen.turning-off .show { animation: tv-off 0.4s ease-in forwards; }
@keyframes tv-off {
  0% { transform: scale(1, 1); filter: brightness(1); }
  55% { transform: scale(1, 0.01); filter: brightness(3); }
  100% { transform: scale(0, 0.01); filter: brightness(5); }
}

@keyframes tv-on {
  from { transform: scaleY(0.01); filter: brightness(4); }
  60% { transform: scaleY(1); filter: brightness(2); }
}

.screen[data-category="series"] .show {
  background: radial-gradient(ellipse at 70% 20%, #d8483c, #6e1712 75%);
}

/* The show's image in the background, darkened at the bottom so the text shows */
.show-cover {
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
}
.show-cover::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgb(5 7 15 / 0.85), rgb(5 7 15 / 0.25) 60%, transparent);
}
.show-cover:not([style*="url"])::after { display: none; }
.show > :not(.show-cover, .channel-number) { position: relative; }

/* The music player: stays out of view (display: none could block the sound in some browsers) */
.radio {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}
.radio iframe { width: 200px; height: 120px; border: 0; }

.memory-song { margin: 8px 0 0; font-size: 0.95rem; font-style: italic; color: var(--turquoise-dark); }
.memory-song:empty { display: none; }
/* A track's own note under the record's memory (my review of each song on LOVED), set apart by a gold line */
.memory-note {
  margin: 12px 0 0;
  padding-left: 12px;
  font-style: italic;
  color: var(--ink-soft);
  border-left: 3px solid var(--gold);
}

/* The channel number in the corner, like on old TV screens */
.channel-number {
  position: absolute;
  z-index: 2;
  top: 7%;
  right: 8%;
  font-family: "Courier New", monospace;
  font-size: clamp(0.85rem, 2.6vw, 1.1rem);
  font-weight: 700;
  color: #8dffb0;
  text-shadow: 0 0 8px rgb(141 255 176 / 0.8);
}
.show-category {
  padding: 2px 12px;
  font-size: clamp(0.8rem, 2.4vw, 0.95rem);
  color: var(--ink);
  background: var(--gold);
  border-radius: 999px;
}
.show-title {
  margin: 6px 0 0;
  font-family: var(--font-title);
  font-size: clamp(1.4rem, 5.5vw, 2.6rem);
  line-height: 1.05;
  text-shadow: 0 2px 12px rgb(0 0 0 / 0.5);
}
.show-info { margin: 0; font-size: clamp(0.85rem, 2.6vw, 1rem); opacity: 0.85; }

/* The TV's panel: a darker wooden plate, built into the cabinet */
.tv-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 112px;
  padding: 12px 8px;
  background: linear-gradient(160deg, #3d1f12, #2a140a);
  border-radius: 14px;
  box-shadow: inset 0 2px 8px rgb(0 0 0 / 0.6), inset 0 0 0 1px rgb(217 164 65 / 0.25);
}

/* Power button with a little light */
.power { display: flex; align-items: center; gap: 10px; }
.led {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #5a1a14;
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.6);
  transition: background 0.3s, box-shadow 0.3s;
}
.led.on { background: #4dff88; box-shadow: 0 0 6px #4dff88, 0 0 14px rgb(77 255 136 / 0.6); }
.power-button {
  all: unset;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  color: #3a2508;
  background: radial-gradient(circle at 35% 30%, #fbe7b0, var(--gold) 55%, #7a5418);
  border-radius: 50%;
  box-shadow: 0 3px 0 #5c3d0e, 0 6px 10px rgb(0 0 0 / 0.5);
  cursor: pointer;
  transition: transform 0.1s, box-shadow 0.1s;
}
.power-button svg { width: 22px; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; }
.power-button:active,
.power-button[aria-pressed="true"] { transform: translateY(2px); box-shadow: 0 1px 0 #5c3d0e, 0 3px 6px rgb(0 0 0 / 0.5); }
.power-button:focus-visible { outline: 3px solid var(--enamel); outline-offset: 3px; }

/* Channel buttons: brass with arrows, and the green display between them */
.channel-control { gap: 6px; }
.channel-button {
  all: unset;
  display: grid;
  place-items: center;
  width: 56px;
  height: 34px;
  color: #3a2508;
  background: linear-gradient(#fbe7b0, var(--gold) 55%, #8a6420);
  border-radius: 10px;
  box-shadow: 0 3px 0 #5c3d0e, 0 5px 8px rgb(0 0 0 / 0.45);
  cursor: pointer;
  transition: transform 0.08s, box-shadow 0.08s, filter 0.2s;
}
.channel-button svg { width: 22px; fill: none; stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.channel-button:hover { filter: brightness(1.08); }
.channel-button:active { transform: translateY(3px); box-shadow: 0 0 0 #5c3d0e, 0 2px 4px rgb(0 0 0 / 0.45); }
.channel-button:focus-visible { outline: 3px solid var(--enamel); outline-offset: 3px; }
.channel-button:disabled { cursor: default; filter: none; transform: none; }

.channel-display {
  min-width: 56px;
  padding: 2px 0;
  font-family: "Courier New", monospace;
  font-size: 1.35rem;
  font-weight: 700;
  text-align: center;
  color: #8dffb0;
  text-shadow: 0 0 8px rgb(141 255 176 / 0.8);
  background: #06120b;
  border-radius: 6px;
  box-shadow: inset 0 2px 6px rgb(0 0 0 / 0.8);
}

/* Turning dial (volume) */
.control { display: flex; flex-direction: column; align-items: center; gap: 2px; width: 100%; }
.dial { width: 100%; cursor: grab; touch-action: none; border-radius: 50%; }
.dial.turning { cursor: grabbing; }
.dial:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }
.dial svg { display: block; width: 100%; overflow: visible; }
.dial-volume { width: 78%; }
.dial-knob { transition: transform 0.25s cubic-bezier(0.3, 1.4, 0.5, 1); }
.dial.turning .dial-knob { transition-duration: 0.12s; }
.volume-track { fill: none; stroke: rgb(0 0 0 / 0.45); stroke-width: 5; stroke-linecap: round; }
.volume-level {
  fill: none;
  stroke: var(--gold);
  stroke-width: 5;
  stroke-linecap: round;
  filter: drop-shadow(0 0 3px rgb(217 164 65 / 0.7));
}
.dial-label { font-size: 0.8rem; color: #e9c98f; letter-spacing: 0.04em; }

.speaker {
  flex: 1;
  width: 100%;
  min-height: 40px;
  border-radius: 8px;
  background: repeating-linear-gradient(to bottom, #1a0c05 0 3px, #4a2614 3px 6px);
}

@media (max-width: 520px) {
  .television { flex-direction: column; padding: 12px; }
  /* channel + volume + power need to fit in ~340px: narrower controls and smaller gaps */
  .tv-panel { flex-direction: row; width: 100%; padding: 10px; gap: 8px; }
  .control { width: 80px; }
  .speaker { min-width: 0; min-height: 0; height: 60px; }
}

.guide { margin-top: 10px; }

/* ---------- TV as a jade Lingnan parlor ----------
   Behind the hall's jade curtains: only on this page, the site's colors become the parlor's: jade walls instead of
   the night sky, rice paper instead of enamel, a gold "回" key pattern on dark jade instead of the Iznik tiles (the
   frieze and the panels' frames use it), and the old TV in a red-lacquered rosewood cabinet inlaid with gold */
body[data-area="tv"] {
  --night: #0b201c;
  --night-2: #12463e;
  --night-deep: #071512;
  --cobalt: #1d5c50;
  --coral: #b8322a;
  --turquoise: #2f8a76;
  --turquoise-dark: #1d6a5a;
  --enamel: #f6eedb;
  --ink: #2a1a10;
  --ink-soft: #5e4a36;
  --moonlight: #cfe8df;
  /* the background: little cinema things scattered at random, drawn in faint gold (a rabbit-ear TV, a film reel, a
     clapperboard, a popcorn bucket, a VHS tape, a ticket, a star and another TV), each at its own angle; the 200px
     block repeats, like the teahouse's dishes on Food */
  --lattice: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'%3E%3Cdefs%3E%3Cg id='tv'%3E%3Crect x='-11' y='-7' width='22' height='16' rx='3'/%3E%3Crect x='-8' y='-4' width='12' height='10' rx='1.5'/%3E%3Cpath d='M-3 -7L-8 -15M3 -7L8 -15'/%3E%3Ccircle cx='7' cy='-1' r='1'/%3E%3C/g%3E%3Cg id='reel'%3E%3Ccircle r='11'/%3E%3Ccircle r='1.8'/%3E%3Ccircle cy='-6' r='2.4'/%3E%3Ccircle cx='5.7' cy='-1.9' r='2.4'/%3E%3Ccircle cx='3.5' cy='4.9' r='2.4'/%3E%3Ccircle cx='-3.5' cy='4.9' r='2.4'/%3E%3Ccircle cx='-5.7' cy='-1.9' r='2.4'/%3E%3C/g%3E%3Cg id='clap'%3E%3Crect x='-11' y='-3' width='22' height='14' rx='1'/%3E%3Cpath d='M-11 -3L-10 -10L11 -7L11 -3M-5 -9L-6 -3M3 -8L2 -3'/%3E%3C/g%3E%3Cg id='pop'%3E%3Cpath d='M-7 -2H7L5 12H-5Z'/%3E%3Cpath d='M-2 -2L-1.5 12M2 -2L1.5 12'/%3E%3Cpath d='M-7 -2Q-9 -8 -4 -8Q-2 -13 2 -9Q7 -11 7 -2'/%3E%3C/g%3E%3Cg id='vhs'%3E%3Crect x='-12' y='-7' width='24' height='14' rx='1.5'/%3E%3Ccircle cx='-5' r='3'/%3E%3Ccircle cx='5' r='3'/%3E%3Cpath d='M-8 4.5H8'/%3E%3C/g%3E%3Cpath id='star' d='M0 -8L2.4 -2.5H8L3.6 1L5 7L0 3.5L-5 7L-3.6 1L-8 -2.5H-2.4Z'/%3E%3Cg id='ticket'%3E%3Cpath d='M-12 -6H12V-2A2 2 0 0 0 12 2V6H-12V2A2 2 0 0 0 -12 -2Z'/%3E%3Cpath d='M5 -4V4'/%3E%3C/g%3E%3C/defs%3E%3Cg fill='none' stroke='%23D9A441' stroke-opacity='.13' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cuse href='%23tv' transform='translate(30 34) rotate(-12)'/%3E%3Cuse href='%23reel' transform='translate(112 24) rotate(8)'/%3E%3Cuse href='%23clap' transform='translate(170 82) rotate(-6)'/%3E%3Cuse href='%23pop' transform='translate(62 108) rotate(14)'/%3E%3Cuse href='%23vhs' transform='translate(138 148) rotate(-10)'/%3E%3Cuse href='%23ticket' transform='translate(26 168) rotate(6)'/%3E%3Cuse href='%23star' transform='translate(96 184) rotate(-4)'/%3E%3Cuse href='%23tv' transform='translate(182 188) rotate(20)'/%3E%3C/g%3E%3C/svg%3E");
  --tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Crect width='40' height='40' fill='%230f3a33'/%3E%3Cpath d='M5 35V5H35V31H11V11H29V25H17V17H23' fill='none' stroke='%23d9a441' stroke-width='2.2'/%3E%3Ccircle cx='0' cy='0' r='2' fill='%23c8382e'/%3E%3Ccircle cx='40' cy='0' r='2' fill='%23c8382e'/%3E%3Ccircle cx='0' cy='40' r='2' fill='%23c8382e'/%3E%3Ccircle cx='40' cy='40' r='2' fill='%23c8382e'/%3E%3C/svg%3E");
}
/* the header: the TV room behind the TV door in the hall (js/hall.js) — the TV's mint glow lighting the jade wall
   in the middle, the wall's faint stripes (as in the music room), and a dark wooden dado with its rail along the bottom (the room's
   things and curtains: .room-side and .room-curtain, with Music's) */
body[data-area="tv"] .page-header {
  isolation: isolate;
  padding-bottom: 46px;
  background:
    linear-gradient(to top, rgb(52 24 16 / 0.8) 0 34px, #b4875c 34px 35px, #7a5236 35px 38px, transparent 38px),
    repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px),
    radial-gradient(ellipse 65% 110% at 50% 72%, #4f8f84 0%, #2a5e55 40%, #143a34 75%, #0b2420 100%);
}

/* the page's title on a cinema marquee: art-deco letters lit warm on a dark jade board, in a gold frame ringed with
   light bulbs, the bulbs' warm light spilling onto the wall. The frame is the h1's own background, the bulbs ::before
   (over the whole frame) and the board ::after (inside the ring of bulbs, covering the rest); the h1 is a stacking
   context (position + z-index), so both sit behind its letters */
body[data-area="tv"] .page-title {
  --ring: 14px; /* the frame's band, where the bulbs are */
  display: inline-block;
  margin-top: 4px;
  padding: calc(var(--ring) + 0.12em) calc(var(--ring) + 0.6em) calc(var(--ring) + 0.18em);
  font-family: "Limelight", var(--font-title);
  font-size: clamp(2.2rem, 6.5vw, 3.8rem);
  line-height: 1.1;
  letter-spacing: 0.02em;
  color: #fff4d6;
  text-shadow: 0 0 6px rgb(255 214 130 / 0.8), 0 0 18px rgb(255 190 90 / 0.45);
  background: linear-gradient(#f1d596, var(--gold) 45%, #a57d2e);
  border-radius: 10px;
  box-shadow:
    inset 0 0 0 1px rgb(255 240 200 / 0.5),
    0 0 44px 6px rgb(255 200 110 / 0.25),
    0 12px 26px rgb(0 0 0 / 0.55);
}
body[data-area="tv"] .page-title::before,
body[data-area="tv"] .page-title::after {
  content: "";
  position: absolute;
  z-index: -1;
}
/* the bulbs: a warm white center, an amber glass, and a soft halo, every 16px all around the band */
body[data-area="tv"] .page-title::before {
  inset: 0;
  border-radius: 10px;
  background: radial-gradient(circle, #fffbe8 0 1.6px, #ffd77a 2.6px, #e8a030 3.6px, rgb(255 190 80 / 0.35) 4.6px, transparent 6.5px) 0 0 / 16px 16px round;
}
/* the board inside the ring of bulbs: dark jade, a fine gold line around it, lit a little from the bulbs at its edges */
body[data-area="tv"] .page-title::after {
  inset: var(--ring);
  border-radius: 4px;
  background: radial-gradient(ellipse at 50% 40%, #1c5246, #0c2a24 75%);
  box-shadow: 0 0 0 1px #6a4a16, inset 0 0 0 2px rgb(217 164 65 / 0.55), inset 0 0 16px rgb(255 200 110 / 0.18);
}
/* the bulbs (and the letters) switch on once, flickering, when the loading screen has gone */
body[data-area="tv"]:not(:has(.loading)) .page-title::before { animation: bulbs-on 1.1s linear 0.3s both; }
body[data-area="tv"]:not(:has(.loading)) .page-title { animation: marquee-on 1.1s linear 0.3s both; }
@keyframes bulbs-on {
  0%, 12%, 20%, 30% { opacity: 0.15; }
  13%, 19%, 31%, 100% { opacity: 1; }
}
@keyframes marquee-on {
  0%, 30% { color: #8a7a5a; text-shadow: none; }
  31%, 100% { color: #fff4d6; text-shadow: 0 0 6px rgb(255 214 130 / 0.8), 0 0 18px rgb(255 190 90 / 0.45); }
}
@media (prefers-reduced-motion: reduce) {
  body[data-area="tv"] .page-title,
  body[data-area="tv"] .page-title::before { animation: none !important; }
}
@media (max-width: 600px) {
  body[data-area="tv"] .page-title { --ring: 12px; }
}

/* the subtitle in italics, between two little gold sparkles */
body[data-area="tv"] .page-header .subtitle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-size: 1.2rem;
  font-style: italic;
  color: #effaf5;
  text-shadow: 0 1px 8px rgb(4 20 16 / 0.9); /* readable over the TV's glow */
}
body[data-area="tv"] .page-header .subtitle::before,
body[data-area="tv"] .page-header .subtitle::after {
  content: "";
  flex: none;
  width: 16px;
  height: 16px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 0L9.6 6.4L16 8L9.6 9.6L8 16L6.4 9.6L0 8L6.4 6.4Z' fill='%23d9a441'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* the cabinet: red lacquered rosewood with a gold inlaid line, and a gold cloud scroll carved in each corner
   (the speaker keeps its original dark slats: it has to read as a speaker)
   (the scrolls on Food's rice paper, turned to face each corner) */
body[data-area="tv"] .television {
  border-radius: 22px;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44'%3E%3Cg fill='none' stroke='%23d9a441' stroke-width='2.4' stroke-linecap='round' transform='matrix(1 0 0 -1 0 44)'%3E%3Cpath d='M4 4V32Q4 40 12 40H40'/%3E%3Cpath d='M10 4V28Q10 34 16 34H40'/%3E%3Cpath d='M18 27a5 5 0 1 1 6 5'/%3E%3C/g%3E%3C/svg%3E") left 7px top 7px / 15px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44'%3E%3Cg fill='none' stroke='%23d9a441' stroke-width='2.4' stroke-linecap='round' transform='matrix(-1 0 0 -1 44 44)'%3E%3Cpath d='M4 4V32Q4 40 12 40H40'/%3E%3Cpath d='M10 4V28Q10 34 16 34H40'/%3E%3Cpath d='M18 27a5 5 0 1 1 6 5'/%3E%3C/g%3E%3C/svg%3E") right 7px top 7px / 15px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44'%3E%3Cg fill='none' stroke='%23d9a441' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M4 4V32Q4 40 12 40H40'/%3E%3Cpath d='M10 4V28Q10 34 16 34H40'/%3E%3Cpath d='M18 27a5 5 0 1 1 6 5'/%3E%3C/g%3E%3C/svg%3E") left 7px bottom 7px / 15px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44'%3E%3Cg fill='none' stroke='%23d9a441' stroke-width='2.4' stroke-linecap='round' transform='matrix(-1 0 0 1 44 0)'%3E%3Cpath d='M4 4V32Q4 40 12 40H40'/%3E%3Cpath d='M10 4V28Q10 34 16 34H40'/%3E%3Cpath d='M18 27a5 5 0 1 1 6 5'/%3E%3C/g%3E%3C/svg%3E") right 7px bottom 7px / 15px no-repeat,
    radial-gradient(ellipse at 30% 0%, rgb(255 200 160 / 0.14), transparent 60%),
    linear-gradient(160deg, #7a2a1a, #561a10 55%, #330d07);
  box-shadow:
    inset 0 0 0 4px rgb(0 0 0 / 0.22),
    inset 0 0 0 5px rgb(217 164 65 / 0.55),
    0 0 60px rgb(54 160 140 / 0.18),
    0 30px 60px rgb(0 0 0 / 0.55);
}
/* the control panel: a darker lacquer plate set into the cabinet */
body[data-area="tv"] .tv-panel {
  background: linear-gradient(160deg, #3e0f08, #240704);
  box-shadow: inset 0 2px 8px rgb(0 0 0 / 0.6), inset 0 0 0 1px rgb(217 164 65 / 0.35);
}
/* the screen's own glow, before a picture comes in: jade for movies (series keep their red) */
body[data-area="tv"] .show { background: radial-gradient(ellipse at 30% 20%, #2f8a76, var(--night) 70%); }

/* A Chinese knot hanging from the cabinet's top right corner (the same knot as on Food's menu) */
.tv-knot {
  position: absolute;
  top: 26px;
  right: -20px;
  z-index: 1;
  width: 26px;
  transform-origin: 50% 0;
  animation: sway 4.6s ease-in-out -1.1s infinite alternate;
  filter: drop-shadow(0 4px 6px rgb(0 0 0 / 0.45));
  pointer-events: none;
}
.tv-knot svg { display: block; width: 100%; }
@media (prefers-reduced-motion: reduce) {
  .tv-knot { animation: none; }
}

/* (the room's wall continues below the header: see .tv-room below; the body is just its darkest color) */
body[data-area="tv"] { background: #0b2420; }

/* the room below the beam: the same TV room as the header, its wall carrying on down, with depth. From the bottom up
   (in the page's bottom padding): the floorboards in two rows, the contact shadow where they meet the wall, a
   baseboard with a lit top edge, the wooden dado in raised panels (beveled seams), the shadow its rail casts on it,
   the rail (a rounded molding catching the light), a soft shadow on the wall just above it. Then the wall: its
   faint stripes (as in the music room), lit from the top and darker lower down and
   toward the corners, with the beam's light from above and the TV's mint glow pooled where the television stands */
body[data-area="tv"] .tv-room {
  --glow: ellipse 38% 420px at 60% 330px; /* (on the right on wide screens) */
  position: relative;
  background:
    linear-gradient(rgb(0 0 0 / 0.5), transparent) left bottom 18px / 100% 8px no-repeat,
    repeating-linear-gradient(90deg, transparent 0 45px, rgb(0 0 0 / 0.35) 45px 46px, rgb(255 220 170 / 0.06) 46px 47px) left bottom / 100% 26px no-repeat,
    linear-gradient(#8a5832, #6a3c1e 48%, rgb(0 0 0 / 0.35) 48% 52%, #6a3c1e 52%, #4a2614) left bottom / 100% 26px no-repeat,
    linear-gradient(#c08a5c 0 1px, #5a3018 1px, #2a140c) left bottom 26px / 100% 7px no-repeat,
    repeating-linear-gradient(90deg, rgb(0 0 0 / 0.4) 0 2px, rgb(255 220 170 / 0.1) 2px 3px, transparent 3px 140px) left bottom 33px / 100% 47px no-repeat,
    linear-gradient(rgb(0 0 0 / 0.45), transparent) left bottom 70px / 100% 10px no-repeat,
    linear-gradient(#4a2414, #3b1d12 60%, #2e160c) left bottom 33px / 100% 47px no-repeat,
    linear-gradient(#e0b07a, #a8703e 35%, #6a3e24 75%, #3b1d12) left bottom 80px / 100% 7px no-repeat,
    linear-gradient(transparent, rgb(0 0 0 / 0.25)) left bottom 87px / 100% 30px no-repeat,
    radial-gradient(var(--glow), rgb(200 244 234 / 0.18), transparent),
    radial-gradient(ellipse 70% 320px at 50% 0, rgb(170 230 210 / 0.12), transparent),
    radial-gradient(ellipse 130% 100% at 50% 40%, transparent 55%, rgb(2 12 10 / 0.5)),
    linear-gradient(rgb(255 255 255 / 0.03), rgb(0 0 0 / 0.18)),
    repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px),
    linear-gradient(#143a34, #0b2420);
}
@media (max-width: 900px) {
  body[data-area="tv"] .tv-room { --glow: ellipse 70% 360px at 50% 260px; } /* (at the top on narrow ones) */
}

/* the guide and the show's message: framed on the wall like Music's back cover (the frame: see the music rules),
   the guide's title between two gold diamonds, the chosen channel marked in cinnabar on a jade wash */
body[data-area="tv"] .guide-title { text-align: center; }
body[data-area="tv"] .guide-title::before,
body[data-area="tv"] .guide-title::after {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin: 0 12px;
  vertical-align: 0.2em;
  background: var(--gold);
  rotate: 45deg;
}
body[data-area="tv"] .track:hover { background: rgb(29 92 80 / 0.08); }
body[data-area="tv"] .track:disabled { cursor: default; background: none; }
body[data-area="tv"] .track.active { background: rgb(47 138 118 / 0.16); box-shadow: inset 4px 0 0 var(--coral); }
body[data-area="tv"] .memory { background: rgb(47 138 118 / 0.1); }

/* Phone: the cabinet's padding is too thin for the corner scrolls; the knot hangs a little closer */
@media (max-width: 520px) {
  body[data-area="tv"] .television {
    background:
      radial-gradient(ellipse at 30% 0%, rgb(255 200 160 / 0.14), transparent 60%),
      linear-gradient(160deg, #7a2a1a, #561a10 55%, #330d07);
  }
  .tv-knot { right: -12px; width: 22px; }
}

/* ---------- Music: turntable + record shelf ---------- */

.music-room { display: flex; flex-direction: column; align-items: center; gap: 32px; }

/* Three columns: record shelf → turntable → back cover with the tracks.
   On medium screens the back cover moves down below the turntable;
   on phones it's all in one column, with the shelf as horizontal rows. */
.music-stage {
  display: grid;
  grid-template-columns: minmax(220px, 250px) minmax(0, 600px) minmax(260px, 320px);
  justify-content: center;
  align-items: start;
  gap: 24px;
  width: 100%;
  max-width: 1280px;
}
@media (max-width: 1220px) {
  .music-stage { grid-template-columns: minmax(210px, 240px) minmax(0, 600px); }
  .music-stage .shelf { grid-row: span 2; }
  .music-stage .back-cover { grid-column: 2; }
}
@media (max-width: 820px) {
  .music-stage { grid-template-columns: minmax(0, 620px); }
  .music-stage .shelf { grid-row: auto; }
  .music-stage .back-cover { grid-column: auto; }
}

/* Shelf as a column (wide screens): a wooden panel with the list of records */
@media (min-width: 821px) {
  .music-stage .shelf {
    position: sticky;
    top: 70px;
    max-height: calc(100vh - 90px);
    overflow-y: auto;
    padding: 14px 12px;
    background: linear-gradient(160deg, #5a2e18, #3d1f10);
    border: 3px solid var(--gold);
    border-radius: 16px;
    box-shadow: 0 20px 40px rgb(0 0 0 / 0.45);
    scrollbar-color: var(--gold) transparent;
  }
  .music-stage .shelf-row-title { margin: 14px 6px 6px; font-size: 1rem; }
  .music-stage .shelf-row {
    flex-direction: column;
    gap: 4px;
    padding: 0;
    overflow: visible;
    background: none;
  }
  .music-stage .cover {
    display: grid;
    grid-template-columns: 52px 1fr;
    column-gap: 10px;
    align-items: center;
    width: 100%;
    box-sizing: border-box;
    padding: 6px;
    border-radius: 10px;
    transition: background 0.2s;
  }
  .music-stage .cover:hover { background: rgb(255 255 255 / 0.07); }
  .music-stage .cover-art { grid-row: span 2; width: 52px; }
  .music-stage .cover-art::before { display: none; }
  .music-stage .cover-art img { box-shadow: 0 3px 8px rgb(0 0 0 / 0.5); }
  .music-stage .cover-title { margin: 0; font-size: 0.92rem; -webkit-line-clamp: 1; }
  .music-stage .cover-artist { font-size: 0.8rem; }
  .music-stage .cover.active {
    margin: 0;
    transform: none;
    background: rgb(217 164 65 / 0.2);
    box-shadow: inset 3px 0 0 var(--gold);
  }
  .music-stage .cover.active .cover-art img { outline-width: 2px; }
}

.back-cover { width: 100%; }
.back-cover .enamel { padding: 18px; }
.back-cover-top { display: flex; align-items: center; gap: 14px; }
.back-cover-art {
  flex: none;
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 4px;
  box-shadow: 0 4px 10px rgb(20 34 77 / 0.35);
}
.back-cover-title { margin: 0; font-family: var(--font-title); font-size: 1.35rem; line-height: 1.15; color: var(--cobalt); }
.back-cover-sub { margin: 2px 0 0; font-size: 0.9rem; color: var(--ink-soft); }
.back-cover-sub:empty { display: none; }
.back-cover .memory { margin-top: 14px; }

/* Long LPs scroll inside the back cover, under the description, instead of stretching the page.
   The solid paper background lets Chrome scroll the list on the graphics card: with a see-through one, on
   regular (non-retina) screens it repaints the list and the back cover's blurred glow behind it every frame. */
.track-list {
  margin: 14px 0 0;
  padding: 10px 4px 0 0;
  max-height: 340px;
  overflow-y: auto;
  background: var(--enamel);
  scrollbar-width: thin;
  scrollbar-color: var(--gold) transparent;
  list-style: none;
  border-top: 2px dashed rgb(27 63 139 / 0.2);
}
.track-list:empty { display: none; }
.lp-track {
  all: unset;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px;
  border-radius: 8px;
  cursor: pointer;
}
.lp-track:hover { background: rgb(27 63 139 / 0.07); }
.lp-track:focus-visible { outline: 2px solid var(--gold); }
.lp-track.active { background: rgb(42 157 168 / 0.15); box-shadow: inset 4px 0 0 var(--coral); }
.lp-track-number { width: 1.8em; font-family: var(--font-title); font-size: 1.1rem; color: var(--coral); }
.lp-track-texts { flex: 1; min-width: 0; }
.lp-track-title,
.lp-track-artist { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.lp-track-title { font-weight: 700; color: var(--ink); }
.lp-track-artist { font-size: 0.85rem; color: var(--ink-soft); }

/* Little equalizer on the track that's playing */
.equalizer { display: none; align-items: flex-end; gap: 2px; height: 16px; }
.lp-track.active .equalizer { display: flex; }
.equalizer span { width: 3px; height: 35%; background: var(--coral); border-radius: 1px; }
.back-cover.playing .lp-track.active .equalizer span { animation: equalize 0.8s ease-in-out infinite alternate; }
.equalizer span:nth-child(2) { animation-delay: -0.3s !important; }
.equalizer span:nth-child(3) { animation-delay: -0.55s !important; }
@keyframes equalize {
  from { height: 25%; }
  to { height: 100%; }
}

.turntable { width: min(620px, 100%); scroll-margin-top: 70px; }

/* Walnut base seen from above, with a golden inlay line inside */
.turntable-base {
  position: relative;
  aspect-ratio: 1.32;
  background:
    repeating-linear-gradient(90deg, rgb(0 0 0 / 0) 0 18px, rgb(0 0 0 / 0.06) 18px 22px, rgb(255 255 255 / 0.03) 22px 28px),
    linear-gradient(150deg, #8a4d2c, #5a2e18 55%, #3d1f10);
  border: 3px solid var(--gold);
  border-bottom: none;
  border-radius: 22px 22px 0 0;
  box-shadow: 0 26px 50px rgb(0 0 0 / 0.55);
}
.turntable-base::before {
  content: "";
  position: absolute;
  inset: 10px;
  border: 1.5px solid rgb(217 164 65 / 0.4);
  border-radius: 14px;
  pointer-events: none;
}

/* Metal platter with the stroboscope marks on the edge
   (its own name so it doesn't get mixed up with Food's menu .dish) */
.turntable-platter {
  position: absolute;
  top: 7%;
  left: 5%;
  height: 86%;
  aspect-ratio: 1;
  border-radius: 50%;
  box-shadow: 0 10px 26px rgb(0 0 0 / 0.6), 0 0 0 3px #2a2a30;
}
.platter-spin {
  position: absolute;
  inset: 0;
  padding: 3.5%;
  border-radius: 50%;
  background: repeating-conic-gradient(#8e8e94 0 3deg, #dcdce0 3deg 7deg);
  animation: spin 1.8s linear infinite paused;
}
.turntable.playing .platter-spin { animation-play-state: running; }


/* The vinyl: grooves, little color bands and the label with the album cover */
.vinyl {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background:
    radial-gradient(circle, transparent 0 33%, rgb(255 255 255 / 0.05) 33.5%, transparent 35%, transparent 62%, rgb(255 255 255 / 0.05) 62.5%, transparent 64%),
    repeating-radial-gradient(circle, #0c0c10 0 1.2px, #1c1c24 1.2px 3px);
  box-shadow: inset 0 0 0 2px #000;
}
.vinyl.swapping { animation: record-drop 0.6s cubic-bezier(0.3, 1.3, 0.5, 1); }
@keyframes record-drop {
  from { opacity: 0; transform: scale(1.12); }
}

.vinyl-label {
  position: absolute;
  inset: 33%;
  display: grid;
  place-items: center;
  overflow: hidden;
  border: 3px solid #0b0b0f;
  border-radius: 50%;
  background: var(--coral);
}
.vinyl-cover { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* A still reflection of light on the record: it's what makes the spin "show" */
.vinyl-sheen {
  position: absolute;
  inset: 3.5%;
  border-radius: 50%;
  background: conic-gradient(from 30deg, transparent 0 7%, rgb(255 255 255 / 0.14) 11%, transparent 16% 57%, rgb(255 255 255 / 0.14) 61%, transparent 66%);
  pointer-events: none;
}
.spindle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 3%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #fff, #999 60%, #444);
  transform: translate(-50%, -50%);
}

/* Tonearm with needle: pivots on the brass axis (the top circle of the drawing) */
.tonearm {
  position: absolute;
  top: 5%;
  right: 3%;
  height: 82%;
  aspect-ratio: 120 / 300;
  transform-origin: 50% 13.33%;
  transition: transform 0.5s ease-out;
  filter: drop-shadow(3px 6px 4px rgb(0 0 0 / 0.5));
  pointer-events: none; /* (taps reach the hidden key under the needle, js/music.js) */
}

/* The hidden key riding the record (js/music.js lays it in the needle's groove, along it): it spins with the record
   until the needle hits it, like a real thing lying on it, so no glow or pulse here, only a thin dark edge against
   the vinyl (even all round: a shadow cast to one side would turn with the record), fading in as the record drops.
   Then the alert, over the song's name on the display */
.vinyl > .room-key {
  width: 34%;
  padding: 2%;
  filter: drop-shadow(0 0 1.5px rgb(0 0 0 / 0.9));
  animation: room-key-land 0.4s ease-out both;
}
@keyframes room-key-land { from { opacity: 0; } }
.key-alert {
  position: absolute;
  inset: -3px;
  z-index: 6;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 0.3em 0.8em;
  border: 2px solid var(--gold);
  border-radius: 10px;
  background: #7a1c14;
  color: #fff3d0;
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1.25;
  text-align: center;
  box-shadow: 0 8px 22px rgb(0 0 0 / 0.55);
  pointer-events: none;
  animation: key-alert-in 0.4s cubic-bezier(0.3, 1.4, 0.5, 1) both;
}
@keyframes key-alert-in { from { opacity: 0; transform: scale(0.8); } }

/* Front panel: a display with the current record and brass buttons */
.turntable-panel {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 18px;
  background: linear-gradient(#3d1f10, #2a140a);
  border: 3px solid var(--gold);
  border-top: 2px solid rgb(217 164 65 / 0.5);
  border-radius: 0 0 22px 22px;
  box-shadow: 0 20px 40px rgb(0 0 0 / 0.5);
}
.turntable-display {
  position: relative; /* (the hidden key's alert covers it, js/music.js) */
  flex: 1;
  min-width: 0;
  padding: 8px 14px;
  background: #0d0703;
  border-radius: 8px;
  box-shadow: inset 0 2px 8px rgb(0 0 0 / 0.8);
}
.display-title,
.display-artist { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.display-title { font-family: var(--font-title); font-size: 1.05rem; color: #ffcf7a; text-shadow: 0 0 8px rgb(255 190 90 / 0.6); }
.display-artist { font-size: 0.85rem; color: #c9a064; }
.display-track { display: block; font-size: 0.75rem; color: #9c7a48; }
.display-track:empty { display: none; }

/* Timeline: takes up the panel's full width, below the display and the buttons */
.turntable-panel { flex-wrap: wrap; }
.timeline { display: flex; align-items: center; gap: 10px; flex-basis: 100%; }
.time-readout {
  min-width: 2.8em;
  font-family: "Courier New", monospace;
  font-size: 0.85rem;
  color: #ffcf7a;
  text-align: center;
}
.time-bar {
  --progress: 0%;
  flex: 1;
  height: 8px;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  background: linear-gradient(to right, var(--coral) var(--progress), rgb(255 255 255 / 0.15) var(--progress));
  border-radius: 999px;
  box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.6);
  cursor: pointer;
}
.time-bar:disabled { cursor: default; opacity: 0.5; }
.time-bar:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }
.time-bar::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  background: radial-gradient(circle at 35% 30%, #fbe7b0, var(--gold) 55%, #7a5418);
  border-radius: 50%;
  box-shadow: 0 2px 5px rgb(0 0 0 / 0.6);
}
.time-bar::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: none;
  background: radial-gradient(circle at 35% 30%, #fbe7b0, var(--gold) 55%, #7a5418);
  border-radius: 50%;
  box-shadow: 0 2px 5px rgb(0 0 0 / 0.6);
}

/* Volume dial on the turntable's panel (the same as the TV's, in js/dial.js) */
.turntable-panel .volume-control { flex: none; width: 74px; }
.turntable-panel .dial-volume { width: 100%; }

.turntable-buttons { display: flex; align-items: center; gap: 10px; }
.turntable-button {
  all: unset;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  color: #3a2508;
  background: radial-gradient(circle at 35% 30%, #fbe7b0, var(--gold) 55%, #7a5418);
  border-radius: 50%;
  box-shadow: 0 3px 0 #5c3d0e, 0 6px 10px rgb(0 0 0 / 0.5);
  cursor: pointer;
  transition: transform 0.08s, box-shadow 0.08s, filter 0.2s;
}
.turntable-button svg { width: 20px; fill: currentColor; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.turntable-button:hover { filter: brightness(1.08); }
.turntable-button:active { transform: translateY(3px); box-shadow: 0 0 0 #5c3d0e, 0 2px 4px rgb(0 0 0 / 0.5); }
.turntable-button:focus-visible { outline: 3px solid var(--enamel); outline-offset: 3px; }
.play-button { width: 54px; height: 54px; }
.play-button svg { width: 24px; }
.play-button .icon-pause { display: none; }
.play-button.show-pause .icon-play { display: none; }
.play-button.show-pause .icon-pause { display: block; }

@media (max-width: 520px) {
  .turntable-panel { flex-wrap: wrap; justify-content: center; }
  .turntable-display { flex-basis: 100%; }
  .turntable-buttons { justify-content: center; }
}

/* Shelf: the records' covers on wooden shelves */
.shelf { width: 100%; min-width: 0; }
.shelf-row-title { margin: 18px 4px 0; font-family: var(--font-title); font-weight: 400; font-size: 1.2rem; color: var(--gold); }
/* the first row's title starts the shelf (its name is only for screen readers) */
.shelf .shelf-row-title:first-of-type { margin-top: 0; }

.shelf-row {
  display: flex;
  gap: 26px;
  padding: 18px 12px 30px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* The wooden plank the covers rest on */
  background: linear-gradient(to top, #3d1f10 0 10px, #7a4428 10px 22px, transparent 22px);
  border-radius: 0 0 8px 8px;
}

.cover {
  all: unset;
  position: relative;
  flex: none;
  /* each record as tall as its own name: stretched to the row's tallest, the button would center its content and a
     record with a one-line name would sit lower than its neighbors */
  align-self: flex-start;
  width: 124px;
  cursor: pointer;
  scroll-snap-align: start;
  transition: transform 0.3s ease, margin 0.3s ease;
}
.cover:focus-visible { outline: 3px solid var(--gold); outline-offset: 4px; border-radius: 6px; }
.cover-art { position: relative; display: block; width: 124px; aspect-ratio: 1; }
/* The record hidden inside the cover, which shows on mouse hover */
.cover-art::before {
  content: "";
  position: absolute;
  top: 5%;
  right: 0;
  width: 90%;
  aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(circle, var(--coral) 0 16%, #0b0b0f 16.5% 18%, transparent 18.5%),
    repeating-radial-gradient(circle, #0c0c10 0 1.2px, #1c1c24 1.2px 3px);
  transition: transform 0.35s ease;
}
.cover-art img {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 4px;
  box-shadow: 0 8px 16px rgb(0 0 0 / 0.55);
}
.cover:hover .cover-art::before { transform: translateX(26%); }
.cover.active { margin-right: 44px; transform: translateY(-6px); }
.cover.active .cover-art::before { transform: translateX(46%) rotate(40deg); }
.cover.active .cover-art img { outline: 3px solid var(--gold); outline-offset: 2px; }

.cover-title {
  display: -webkit-box;
  margin-top: 10px;
  overflow: hidden;
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.25;
  color: var(--enamel);
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.cover-artist { display: block; font-size: 0.85rem; color: var(--moonlight); }

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

/* ---------- Music as a plum listening pavilion ----------
   Behind the hall's plum curtains: only on this page, the site's colors become the pavilion's: plum walls under the
   curtains' warm peach light, rice paper instead of enamel, a gold "回" key pattern on dark plum instead of the Iznik
   tiles, the shelf in plum lacquer, and a gold plum blossom branch painted on the turntable's walnut */
body[data-area="music"] {
  --night: #33204a;
  --night-2: #5a2e7e;
  --night-deep: #2a1a3c;
  --cobalt: #5a2a7a;
  --coral: #b8322a;
  --turquoise: #8a5aa8;
  --turquoise-dark: #6a3a8a;
  --enamel: #f6eedb;
  --ink: #2a1a24;
  --ink-soft: #5e4a56;
  --moonlight: #e6d4ee;
  /* the background: little music things scattered at random, drawn in faint gold (a note, a record, a pipa, two
     beamed notes, a cassette, headphones, a plum blossom and another note), each at its own angle; the 200px block
     repeats, like the teahouse's dishes on Food and the cinema things on TV */
  --lattice: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'%3E%3Cdefs%3E%3Cg id='note'%3E%3Cellipse cx='-3' cy='6' rx='4' ry='3' transform='rotate(-20 -3 6)'/%3E%3Cpath d='M1 5V-10Q6 -8 7 -3'/%3E%3C/g%3E%3Cg id='beam'%3E%3Cellipse cx='-7' cy='7' rx='3.5' ry='2.6'/%3E%3Cellipse cx='7' cy='5' rx='3.5' ry='2.6'/%3E%3Cpath d='M-3.5 6.5V-8L10.5 -10V4.5M-3.5 -4L10.5 -6'/%3E%3C/g%3E%3Cg id='disc'%3E%3Ccircle r='11'/%3E%3Ccircle r='4'/%3E%3Ccircle r='0.8'/%3E%3Cpath d='M-7 -4A8 8 0 0 1 -2 -7.5'/%3E%3C/g%3E%3Cg id='pipa'%3E%3Cpath d='M0 -2C8 -2 10 8 6 12Q0 16 -6 12C-10 8 -8 -2 0 -2Z'/%3E%3Cpath d='M0 -2V-16M-2.5 -16H2.5M-3 -11H3'/%3E%3Cpath d='M-1 4V10M1 4V10'/%3E%3C/g%3E%3Cg id='phones'%3E%3Cpath d='M-9 4V0A9 9 0 0 1 9 0V4'/%3E%3Crect x='-11' y='2' width='5' height='8' rx='2'/%3E%3Crect x='6' y='2' width='5' height='8' rx='2'/%3E%3C/g%3E%3Cg id='tape'%3E%3Crect x='-12' y='-8' width='24' height='16' rx='1.5'/%3E%3Ccircle cx='-5' cy='-1' r='2.5'/%3E%3Ccircle cx='5' cy='-1' r='2.5'/%3E%3Cpath d='M-7 8L-5 4H5L7 8'/%3E%3C/g%3E%3Cg id='plum'%3E%3Ccircle cy='-4.5' r='3'/%3E%3Ccircle cx='4.3' cy='-1.4' r='3'/%3E%3Ccircle cx='2.6' cy='3.6' r='3'/%3E%3Ccircle cx='-2.6' cy='3.6' r='3'/%3E%3Ccircle cx='-4.3' cy='-1.4' r='3'/%3E%3C/g%3E%3C/defs%3E%3Cg fill='none' stroke='%23D9A441' stroke-opacity='.13' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cuse href='%23note' transform='translate(30 34) rotate(-12)'/%3E%3Cuse href='%23disc' transform='translate(112 24) rotate(8)'/%3E%3Cuse href='%23pipa' transform='translate(170 82) rotate(-6)'/%3E%3Cuse href='%23beam' transform='translate(62 108) rotate(14)'/%3E%3Cuse href='%23tape' transform='translate(138 148) rotate(-10)'/%3E%3Cuse href='%23phones' transform='translate(26 168) rotate(6)'/%3E%3Cuse href='%23plum' transform='translate(96 184) rotate(-4)'/%3E%3Cuse href='%23note' transform='translate(182 188) rotate(20)'/%3E%3C/g%3E%3C/svg%3E");
  --tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Crect width='40' height='40' fill='%234a2266'/%3E%3Cpath d='M5 35V5H35V31H11V11H29V25H17V17H23' fill='none' stroke='%23d9a441' stroke-width='2.2'/%3E%3Ccircle cx='0' cy='0' r='2' fill='%23c8382e'/%3E%3Ccircle cx='40' cy='0' r='2' fill='%23c8382e'/%3E%3Ccircle cx='0' cy='40' r='2' fill='%23c8382e'/%3E%3Ccircle cx='40' cy='40' r='2' fill='%23c8382e'/%3E%3C/svg%3E");
}
/* (the room's wall continues below the header: see .music-room below; the body is just its darkest color) */
body[data-area="music"] { background: #24102c; }

/* the header: the listening room behind the Music door in the hall (js/hall.js) — its warm lamp light glowing in
   the middle, faint stripes on the wall, and a dark wooden dado with its rail along the bottom */
body[data-area="music"] .page-header {
  isolation: isolate;
  padding-bottom: 46px;
  background:
    linear-gradient(to top, rgb(52 24 16 / 0.8) 0 34px, #b4875c 34px 35px, #7a5236 35px 38px, transparent 38px),
    repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px),
    radial-gradient(ellipse 65% 110% at 50% 72%, #c98a86 0%, #8a4a6e 40%, #45203f 75%, #2a1230 100%);
}
/* the room's things stand on both sides of the title, on the floor (the header's bottom edge); the curtains frame
   both edges, and everything else stands in front of them (the back sign has its own position and z-index) */
.room-side {
  position: absolute;
  bottom: 0;
  z-index: 0;
  width: clamp(150px, 26vw, 330px);
  height: auto;
  aspect-ratio: 4 / 3;
  pointer-events: none;
}
.room-side-left { left: clamp(28px, 5vw, 64px); }
.room-side-right { right: clamp(28px, 5vw, 64px); }
.room-curtain {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  width: clamp(40px, 6vw, 80px);
  height: 100%;
  filter: drop-shadow(4px 0 8px rgb(0 0 0 / 0.45));
  pointer-events: none;
}
.room-curtain-right { left: auto; right: 0; scale: -1 1; }
/* (Music's, TV's and Food's headers all show their room) */
body:is([data-area="music"], [data-area="tv"], [data-area="food"]) .page-header > :is(.lanterns, .page-title, .subtitle) { position: relative; z-index: 1; }

/* phones: the room's things smaller and below the subtitle (the header grows taller to make room for them) */
@media (max-width: 600px) {
  body:is([data-area="music"], [data-area="tv"], [data-area="food"]) .page-header { padding-bottom: 100px; }
  .room-side { width: 128px; }
  .room-side-left { left: 18px; }
  .room-side-right { right: 18px; }
  .room-curtain { width: 30px; }
}

/* the page's title as the room's neon sign: warm pink script tubes on a dark plum backing board with a thin metal
   edge and four screws, an amber sound-wave tube under the word, and the glow spilling onto the wall around it */
body[data-area="music"] .page-title {
  --neon-on: 0 0 2px #fff, 0 0 6px #ff7ab6, 0 0 14px #ff4fa0, 0 0 28px #ff4fa0, 0 0 48px rgb(255 79 160 / 0.6);
  display: inline-block;
  margin-top: 4px;
  padding: 0.04em 0.75em 0.42em;
  font-family: "Yellowtail", cursive;
  font-size: clamp(2.6rem, 7.5vw, 4.4rem);
  line-height: 1.15;
  color: #ffe6f0;
  text-shadow: var(--neon-on);
  background:
    radial-gradient(circle, #8a7a8e 0 1.6px, #3a2e40 2.2px 3px, transparent 3.5px) 8px 8px / 7px 7px no-repeat,
    radial-gradient(circle, #8a7a8e 0 1.6px, #3a2e40 2.2px 3px, transparent 3.5px) right 8px top 8px / 7px 7px no-repeat,
    radial-gradient(circle, #8a7a8e 0 1.6px, #3a2e40 2.2px 3px, transparent 3.5px) left 8px bottom 8px / 7px 7px no-repeat,
    radial-gradient(circle, #8a7a8e 0 1.6px, #3a2e40 2.2px 3px, transparent 3.5px) right 8px bottom 8px / 7px 7px no-repeat,
    linear-gradient(#26122e, #12081a);
  border: 2px solid #4a3a52;
  border-radius: 10px;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.08),
    0 0 46px 6px rgb(255 110 175 / 0.28),
    0 12px 26px rgb(0 0 0 / 0.55);
}
/* the sound-wave tube under the word, amber, always lit */
body[data-area="music"] .page-title::after {
  content: "";
  position: absolute;
  left: 22%;
  right: 22%;
  bottom: 0.14em;
  height: 0.24em;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 12' preserveAspectRatio='none'%3E%3Cpath d='M2 6H18Q21 1 24 6T30 6Q33 -2 36 6T42 6Q45 0 48 6T54 6Q57 -2 60 6T66 6Q69 1 72 6T78 6H98' fill='none' stroke='%23ffd9a0' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
  filter: drop-shadow(0 0 3px #ffb347) drop-shadow(0 0 8px #ff9a2a);
}
/* the neon flickers on once, when the loading screen has gone (until then it's simply lit, under the cover) */
body[data-area="music"]:not(:has(.loading)) .page-title { animation: neon-flicker 1.3s linear 0.3s both; }
@keyframes neon-flicker {
  0%, 9%, 13%, 21%, 25% { color: #6a3a55; text-shadow: none; }
  10%, 12%, 22%, 24%, 26%, 100% { color: #ffe6f0; text-shadow: var(--neon-on); }
}
@media (prefers-reduced-motion: reduce) {
  body[data-area="music"] .page-title { animation: none !important; }
}

/* the subtitle in italics, between two little gold sound waves that grow toward the words */
body[data-area="music"] .page-header .subtitle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-size: 1.2rem;
  font-style: italic;
  color: #fff1e4;
  text-shadow: 0 1px 8px rgb(30 8 24 / 0.9); /* readable over the lamp light */
}
body[data-area="music"] .page-header .subtitle::before,
body[data-area="music"] .page-header .subtitle::after {
  content: "";
  flex: none;
  width: 52px;
  height: 14px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='52' height='14' viewBox='0 0 52 14'%3E%3Cpath d='M0 7H10Q12 5 14 7T18 7Q21 3 24 7T30 7Q33.5 1 37 7T44 7' fill='none' stroke='%23d9a441' stroke-width='1.6' stroke-linecap='round'/%3E%3Ccircle cx='49' cy='7' r='2' fill='%23d9a441'/%3E%3C/svg%3E") center / contain no-repeat;
}
body[data-area="music"] .page-header .subtitle::after { scale: -1 1; }
@media (max-width: 440px) {
  body[data-area="music"] .page-header .subtitle::before,
  body[data-area="music"] .page-header .subtitle::after { display: none; }
}

/* where the wall meets the room: the room's "回" beam (the frieze, hidden on the other inner pages), casting a
   shadow (Music's plum one, TV's jade one, Food's red one) */
body:is([data-area="music"], [data-area="tv"], [data-area="food"]) .frieze {
  display: block;
  position: relative;
  z-index: 2;
  height: 22px;
  background-size: 22px 22px;
  border-block-width: 2px;
  box-shadow: 0 10px 22px rgb(10 4 18 / 0.55);
}

/* the room below the beam: the same listening room as the header, its wall carrying on down. From the bottom up:
   floorboards, a baseboard, the dark wooden dado with its rail (in the page's bottom padding, under the stage), then
   the wall's faint stripes, lit by the beam's light from above and by the two lamps' warm pools at the sides */
body[data-area="music"] .music-room {
  position: relative;
  background:
    /* floorboards: plank seams every 46px, a lighter top edge */
    repeating-linear-gradient(90deg, transparent 0 45px, rgb(0 0 0 / 0.35) 45px 46px) left bottom / 100% 26px no-repeat,
    linear-gradient(#7a4a2a, #5a3218) left bottom / 100% 26px no-repeat,
    /* baseboard, dado panel and its rail */
    linear-gradient(to top, transparent 0 26px, #2a140c 26px 32px, #3b1d12 32px 84px, #b4875c 84px 85px, #7a5236 85px 89px, transparent 89px),
    /* the lamps' light, the beam's light, darker corners */
    radial-gradient(ellipse 30% 420px at 8% 140px, rgb(255 200 150 / 0.12), transparent),
    radial-gradient(ellipse 30% 420px at 92% 140px, rgb(255 200 150 / 0.12), transparent),
    radial-gradient(ellipse 70% 320px at 50% 0, rgb(240 160 150 / 0.14), transparent),
    radial-gradient(ellipse 130% 100% at 50% 40%, transparent 55%, rgb(12 5 20 / 0.45)),
    /* the wall: faint stripes (as in the header) on deep plum-brown */
    repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px),
    linear-gradient(#3a1a3a, #2a1230);
}

/* the record shelf: on the room's wall (no cabinet). In the desktop column the records are simply listed on the wall;
   on narrow screens they stand on long walnut shelves on brass brackets (below) */
/* the rows' names, each followed by a thin gold line that fades out toward the right */
body[data-area="music"] .shelf-row-title {
  display: flex;
  align-items: center;
  gap: 12px;
}
body[data-area="music"] .shelf-row-title::after {
  content: "";
  flex: 1;
  height: 2px;
  border-radius: 1px;
  background: linear-gradient(90deg, rgb(217 164 65 / 0.75), transparent);
}
@media (min-width: 821px) {
  /* the column: no box, just the wall */
  body[data-area="music"] .music-stage .shelf { background: none; border: 0; box-shadow: none; }
}
@media (max-width: 820px) {
  /* one long shelf per row, drawn in the bottom padding of the row's wrapper (.shelf-plank), so it's under the
     scrolling row and its scrollbar. The wrapper reaches 12px into the page's side margins (the row's padding brings
     the records back in place), and the shelf stops 4px short of each side so both its ends show: on a phone, it
     ends about 8px from the screen's edges */
  body[data-area="music"] .shelf-row {
    padding-inline: 24px;
    scroll-padding-inline: 24px; /* the covers snap into place inside that padding, not against the row's edge */
    padding-bottom: 6px;
    background: none;
    border-radius: 0;
  }
  /* A gentle scrollbar, like a phone's: a thin gold rounded thumb on no track, with a gold arrow (a small solid triangle) at each end.
     Chrome, Edge and Safari draw it from these ::-webkit-scrollbar rules (the thumb is thin inside a 12px bar, a
     transparent border around it, so the arrows have room); each arrow is a single button, at its own end */
  body[data-area="music"] .shelf-row::-webkit-scrollbar { height: 12px; }
  body[data-area="music"] .shelf-row::-webkit-scrollbar-track { background: transparent; }
  body[data-area="music"] .shelf-row::-webkit-scrollbar-thumb {
    background: var(--gold);
    background-clip: padding-box;
    border: 3px solid transparent;
    border-radius: 6px;
  }
  body[data-area="music"] .shelf-row::-webkit-scrollbar-button { display: none; }
  body[data-area="music"] .shelf-row::-webkit-scrollbar-button:horizontal:start:decrement,
  body[data-area="music"] .shelf-row::-webkit-scrollbar-button:horizontal:end:increment {
    display: block;
    width: 14px;
    height: 12px;
    background: center / 12px 12px no-repeat;
  }
  body[data-area="music"] .shelf-row::-webkit-scrollbar-button:horizontal:start:decrement {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M7.6 3.6V8.4L4 6Z' fill='%23d9a441' stroke='%23d9a441' stroke-width='1' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
  body[data-area="music"] .shelf-row::-webkit-scrollbar-button:horizontal:end:increment {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M4.4 3.6V8.4L8 6Z' fill='%23d9a441' stroke='%23d9a441' stroke-width='1' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
  /* Firefox can't style the arrows: there, just the thin gold thumb (scrollbar-color would switch the rules above off
     in Chrome and Edge, so it's only for browsers without them) */
  @supports not selector(::-webkit-scrollbar) {
    body[data-area="music"] .shelf-row {
      scrollbar-width: thin;
      scrollbar-color: var(--gold) transparent;
    }
  }
  /* The shelf in 3D, seen from the front and a little from above, lit from the top left. From the bottom of the
     wrapper up: the two brackets (its background, so they tuck under the plank), the plank's front face (::after)
     and its top surface (::before), on which the records stand */
  body[data-area="music"] .shelf-plank {
    /* each bracket in brass, shaded like a round bar: its front, its inner side (the left one's in shade, the right
       one's lit), the plank's shadow over its top, two screws with a slot and a glint, and its soft shadow on the
       wall, down and to the right */
    --bracket-left: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='28' viewBox='0 0 20 28'%3E%3Cdefs%3E%3ClinearGradient id='f' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='%23f6e0a0'/%3E%3Cstop offset='.45' stop-color='%23d9a441'/%3E%3Cstop offset='1' stop-color='%239a7022'/%3E%3C/linearGradient%3E%3ClinearGradient id='t' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23000' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%23000' stop-opacity='0'/%3E%3C/linearGradient%3E%3Cfilter id='b' x='-50%25' y='-50%25' width='200%25' height='200%25'%3E%3CfeGaussianBlur stdDeviation='1.3'/%3E%3C/filter%3E%3C/defs%3E%3Cpath d='M5 1H15L14.5 22Q10.5 28 6 22Z' fill='%23000' opacity='.45' filter='url(%23b)'/%3E%3Cpath d='M9.5 0H12.5V20.5Q12 23.5 9.5 23.5Z' fill='%237a5418'/%3E%3Cpath d='M2 0H10V20Q6 25.5 2 20Z' fill='url(%23f)'/%3E%3Cpath d='M2.6 1V19.5' stroke='%23fff8e0' stroke-opacity='.6' stroke-width='.8'/%3E%3Cpath d='M2 0H12.5V5H2Z' fill='url(%23t)'/%3E%3Cg fill='%235a3a12'%3E%3Ccircle cx='6' cy='9' r='1.5'/%3E%3Ccircle cx='6' cy='16' r='1.5'/%3E%3C/g%3E%3Cpath d='M5 9.7L7 8.3M5 16.7L7 15.3' stroke='%232a1a08' stroke-width='.5'/%3E%3Cg fill='%23fff8e0' opacity='.7'%3E%3Ccircle cx='5.4' cy='8.4' r='.45'/%3E%3Ccircle cx='5.4' cy='15.4' r='.45'/%3E%3C/g%3E%3C/svg%3E");
    --bracket-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='28' viewBox='0 0 20 28'%3E%3Cdefs%3E%3ClinearGradient id='f' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='%23f6e0a0'/%3E%3Cstop offset='.45' stop-color='%23d9a441'/%3E%3Cstop offset='1' stop-color='%239a7022'/%3E%3C/linearGradient%3E%3ClinearGradient id='t' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23000' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%23000' stop-opacity='0'/%3E%3C/linearGradient%3E%3Cfilter id='b' x='-50%25' y='-50%25' width='200%25' height='200%25'%3E%3CfeGaussianBlur stdDeviation='1.3'/%3E%3C/filter%3E%3C/defs%3E%3Cpath d='M10 1H18L17.5 22Q13.5 28 10 22Z' fill='%23000' opacity='.45' filter='url(%23b)'/%3E%3Cpath d='M7.5 0H4.5V20.5Q5 23.5 7.5 23.5Z' fill='%23c08a34'/%3E%3Cpath d='M7 0H15V20Q11 25.5 7 20Z' fill='url(%23f)'/%3E%3Cpath d='M4.5 0H15V5H4.5Z' fill='url(%23t)'/%3E%3Cg fill='%235a3a12'%3E%3Ccircle cx='11' cy='9' r='1.5'/%3E%3Ccircle cx='11' cy='16' r='1.5'/%3E%3C/g%3E%3Cpath d='M10 9.7L12 8.3M10 16.7L12 15.3' stroke='%232a1a08' stroke-width='.5'/%3E%3Cg fill='%23fff8e0' opacity='.7'%3E%3Ccircle cx='10.4' cy='8.4' r='.45'/%3E%3Ccircle cx='10.4' cy='15.4' r='.45'/%3E%3C/g%3E%3C/svg%3E");
    position: relative;
    margin-inline: -12px;
    padding-bottom: 42px;
    background:
      var(--bracket-left) left 30px bottom 0 / 20px 28px no-repeat,
      var(--bracket-right) right 30px bottom 0 / 20px 28px no-repeat;
  }
  body[data-area="music"] .shelf-plank::before,
  body[data-area="music"] .shelf-plank::after {
    content: "";
    position: absolute;
    left: 4px;
    right: 4px;
    pointer-events: none;
  }
  /* the top surface: a trapezoid (its back edge, against the wall, a little shorter: the ends slant back), in shade
     at the back and lit toward the front */
  body[data-area="music"] .shelf-plank::before {
    bottom: 35px;
    height: 7px;
    clip-path: polygon(9px 0, calc(100% - 9px) 0, 100% 100%, 0 100%);
    background: linear-gradient(#4a2614, #8a5432 45%, #b8784a 85%, #d39a62);
  }
  /* the front face: a rounded lit edge at the top, darker toward the bottom, the ends a little rounded (lit on the
     left, in shade on the right); and under it a dark contact shadow and a softer, wider one on the wall */
  body[data-area="music"] .shelf-plank::after {
    bottom: 26px;
    height: 9px;
    border-radius: 0 0 2px 2px;
    background: linear-gradient(#e0aa70 0, #a8693c 2px, #7a4428 55%, #4a2614);
    box-shadow:
      inset 3px 0 0 rgb(255 255 255 / 0.08),
      inset -3px 0 0 rgb(0 0 0 / 0.25),
      inset 0 -2px 0 rgb(0 0 0 / 0.3),
      0 2px 2px rgb(0 0 0 / 0.5),
      0 12px 14px -6px rgb(0 0 0 / 0.6),
      0 24px 28px -12px rgb(0 0 0 / 0.4);
  }
  /* room between a shelf (its brackets) and the next row's name */
  body[data-area="music"] .shelf-plank + .shelf-row-title { margin-top: 34px; }
  /* every name takes two lines' room, so all the records stand on the shelf at the same height */
  body[data-area="music"] .shelf-row .cover-title { min-height: 2.5em; }
}

/* the back cover: rice paper, the playing track marked in cinnabar on a plum wash */
/* framed on the wall like the framed record in the Music door's room: a dark walnut frame (its bevel lit at the top
   left, in shade at the bottom right), a fine gold fillet around the paper, the paper set back in the frame (the
   frame's shadow falls on it), and the whole frame's shadow on the wall
   (TV's guide and show message hang in the same frames) */
body[data-area="music"] .back-cover,
body[data-area="tv"] :is(.tv-guide, .tv-synopsis) {
  padding: 12px;
  background: linear-gradient(145deg, #7a4a2c, #5a3820 50%, #3d2414);
  border: 1px solid #2a140c;
  border-radius: 4px;
  box-shadow:
    inset 2px 2px 0 rgb(255 230 190 / 0.18),
    inset -2px -2px 0 rgb(0 0 0 / 0.35),
    0 3px 6px rgb(0 0 0 / 0.4),
    0 22px 40px rgb(0 0 0 / 0.5);
}
body[data-area="music"] .back-cover .enamel,
body[data-area="tv"] :is(.tv-guide, .tv-synopsis) .enamel {
  border: 0;
  border-radius: 1px;
  box-shadow:
    0 0 0 2px #d9a441,
    0 0 0 3px #3d2414,
    inset 2px 3px 6px rgb(0 0 0 / 0.28),
    inset 0 0 40px rgb(160 120 60 / 0.18);
}
body[data-area="music"] .back-cover-art { box-shadow: 0 4px 10px rgb(42 26 36 / 0.35); }
body[data-area="music"] .track-list { border-top-color: rgb(90 42 122 / 0.25); }
body[data-area="music"] .lp-track:hover { background: rgb(90 42 122 / 0.08); }
body[data-area="music"] .lp-track.active { background: rgb(138 90 168 / 0.16); box-shadow: inset 4px 0 0 var(--coral); }
body[data-area="music"] .memory { background: rgb(138 90 168 / 0.1); }

/* the shelf's records each sit in a white paper sleeve: the cover art inset in a white border (padding in %, so it
   keeps its proportion on the big covers and on the column's small ones), the paper lit a little from the top left */
body[data-area="music"] .cover-art img {
  box-sizing: border-box;
  padding: 7%;
  background: linear-gradient(150deg, #ffffff, #f1ece2);
  border-radius: 3px;
}

.tracks-panel { max-width: 580px; margin: 0 auto; }
.tracks-panel .enamel { padding: 14px; }

.tracks { list-style: none; margin: 0; padding: 0; }

.track {
  all: unset;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 12px 16px;
  border-radius: 8px;
  cursor: pointer;
}
.track:hover { background: rgb(27 63 139 / 0.07); }
.track:focus-visible { outline: 2px solid var(--gold); outline-offset: 0; }
.track.active { background: rgb(42 157 168 / 0.15); box-shadow: inset 4px 0 0 var(--coral); }
.track-number { width: 1.8em; font-family: var(--font-title); font-size: 1.2rem; color: var(--coral); }
.track-title { display: block; font-weight: 700; color: var(--ink); }
.track-artist { display: block; font-size: 0.95rem; color: var(--ink-soft); }

.memory {
  min-height: 3.5em;
  margin-top: 10px;
  padding: 16px 20px;
  background: rgb(42 157 168 / 0.1);
  border-left: 4px solid var(--turquoise);
  border-radius: 8px;
}
.memory .hint { color: var(--ink-soft); }
.memory-title { margin: 0; font-family: var(--font-title); font-size: 1.2rem; color: var(--cobalt); }
.memory-text { margin: 4px 0 0; color: var(--ink); white-space: pre-line; }

.player:not(:empty) { margin-top: 12px; }
.player iframe { display: block; border: 0; border-radius: 12px; }

.list-title {
  margin: 22px 16px 6px;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: 1.4rem;
  color: var(--cobalt);
}

/* ---------- Challenge floors ---------- */

/* The challenge's frame stands in front of the wall, stepping a little onto the floor */
.challenge-panel { margin-bottom: -10svh; }
.mission .enamel { padding: 14px 18px; text-align: center; }

/* The challenge's highlight: golden rays spinning slowly and a glow that breathes, behind the frame.
   Both are pseudo-elements of the mission with z-index -1: the panel creates its own stacking
   (z-index: 0), so they stay behind the frame but above the wall. The stairs sit
   one level up, so the rays never pass over them. */
.challenge-panel { position: relative; z-index: 0; }
body[data-floor] .stairs,
body[data-floor] .stairs-down { z-index: 1; }
.mission { position: relative; }
/* The Tea House's section has no frame: it's rounded like the WeChat phone, so the victory halo follows the edge */
.mission-teahouse { border-radius: 26px; }
.mission::before,
.mission::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: -1;
  translate: -50% -50%;
  pointer-events: none;
}
/* Rays: slices of light around the center, fading toward the ends */
.mission::before {
  /* Half the screen's width, but never less than 1.6× the frame: the rays fade out gradually
     from the middle to the tip, so they need to extend well past the frame to show around it
     (in the phone's thumbnail the frame already takes up almost half the screen). */
  --rays-size: 50vw;
  --rays-color: rgb(248 226 165 / 0.5);
  width: max(var(--rays-size), 160%);
  aspect-ratio: 1;
  border-radius: 50%;
  /* 12 wide rays (one every 30°): each has a full core and edges dissolving gradually.
     The blur is drawn into the gradient itself (no filter: blur), so it spins lightly, without stuttering. */
  background: repeating-conic-gradient(
    transparent 0deg,
    var(--rays-color) 6deg 11deg,
    transparent 17deg 30deg
  );
  -webkit-mask-image: radial-gradient(closest-side, #000 45%, transparent);
  mask-image: radial-gradient(closest-side, #000 45%, transparent);
  animation: challenge-rays 60s linear infinite;
  will-change: transform, opacity;
}
/* Warm glow right behind the frame, growing and shrinking */
.mission::after {
  width: calc(100% + 100px);
  height: calc(100% + 100px);
  border-radius: 50px;
  /* The radial gradient is already soft: no filter: blur, which would be heavy on every frame of the animation */
  background: radial-gradient(closest-side, rgb(255 232 170 / 0.85), rgb(217 164 65 / 0.4) 60%, transparent);
  animation: challenge-glow 3.2s ease-in-out infinite alternate;
  will-change: transform, opacity;
}
/* Restart button (js/challenges.js): a round golden icon sitting on the frame's top-right corner */
.restart-challenge {
  position: absolute;
  top: -14px;
  right: -14px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  color: var(--gold);
  background: var(--night);
  border: 2px solid var(--gold);
  border-radius: 50%;
  box-shadow: 0 3px 8px rgb(0 0 0 / 0.5);
  cursor: pointer;
  transition: color 0.2s, background 0.2s;
}
.restart-challenge svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: rotate 0.3s ease;
}
.restart-challenge:hover { color: var(--enamel); background: var(--cobalt); }
/* the little arrow does a half-turn backward on mouse hover */
.restart-challenge:hover svg { rotate: -40deg; }
/* Challenge won (js/challenges.js adds the "complete" class):
   - the rays light up, grow and do a quick spin, then go back to the slow spin;
   - the glow behind bursts and then goes back to breathing;
   - a band of light sweeps across the frame and confetti flies out of its middle.
   (The frame itself isn't animated with scale: that would make it a layer of its own and the rays,
   which sit behind it, could disappear during the animation.) */
/* After the celebration, the won challenge keeps celebrating: lighter and faster rays,
   a stronger breathing glow, a golden halo pulsing around the frame and little sparkles (js/challenges.js) */
/* Everything here animates only rotation, size and transparency (which the graphics card handles on its own, without redrawing):
   animated filters and shadows made the celebration stutter, especially on phones. */
.mission.complete::before {
  --rays-color: rgb(255 238 190 / 0.8); /* lighter rays while the challenge stays won */
  animation:
    victory-rays 1.8s cubic-bezier(0.2, 0.7, 0.3, 1),
    challenge-rays 20s linear 1.8s infinite;
}
.mission.complete::after {
  animation:
    victory-glow 1.6s ease-out,
    challenge-glow 1.6s ease-in-out 1.6s infinite alternate;
}
@keyframes victory-rays {
  0% { rotate: 0deg; scale: 0.6; opacity: 0.4; }
  30% { scale: 1.35; opacity: 1; }
  100% { rotate: 540deg; scale: 1; opacity: 1; }
}
/* Golden halo around the won frame (a span from js/challenges.js): the shadow stays still
   and only the transparency pulses */
.victory-halo {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit; /* follows the challenge's corner (frame or the DM's phone) */
  box-shadow: 0 0 0 3px rgb(255 236 180 / 0.95), 0 0 40px 12px rgb(241 213 150 / 0.7);
  pointer-events: none;
  animation: halo-pulse 1s ease-in-out infinite alternate;
  will-change: opacity;
}
@keyframes halo-pulse {
  from { opacity: 0.35; }
  to { opacity: 1; }
}

/* Little sparkles: golden stars that twinkle around the won frame */
.victory-sparkle {
  position: absolute;
  z-index: 3;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  background: #fff3c4;
  clip-path: polygon(50% 0, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0 50%, 39% 39%);
  filter: drop-shadow(0 0 4px rgb(255 220 130 / 0.9));
  pointer-events: none;
  animation: sparkle-blink 1.2s ease-in-out both;
}
@keyframes sparkle-blink {
  0%, 100% { scale: 0; rotate: 0deg; opacity: 0; }
  50% { scale: 1; rotate: 90deg; opacity: 1; }
}

/* Band of light sweeping across the frame (goes away on its own at the end) */
.victory-sweep {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  border-radius: inherit; /* follows the challenge's corner (frame or the DM's phone) */
  pointer-events: none;
  animation: sweep-end 1.1s linear forwards;
}
.victory-sweep::before {
  content: "";
  position: absolute;
  top: -20%;
  bottom: -20%;
  left: 0;
  width: 45%;
  background: linear-gradient(100deg, transparent, rgb(255 250 225 / 0.85) 50%, transparent);
  translate: -120% 0;
  rotate: 12deg;
  animation: sweep-pass 0.9s ease-in-out 0.15s both;
}
@keyframes sweep-pass { to { translate: 300% 0; } }
@keyframes sweep-end { to { opacity: 1; } }

/* Confetti: comes out of the middle of the frame and then falls, spinning and fading */
.victory-confetti {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 3;
  width: 9px;
  height: 14px;
  margin: -7px 0 0 -4px;
  border-radius: 2px;
  pointer-events: none;
  animation: confetti-fly 1.6s cubic-bezier(0.15, 0.7, 0.4, 1) both;
}
.victory-confetti.round { width: 10px; height: 10px; border-radius: 50%; }
@keyframes confetti-fly {
  0% { translate: 0 0; rotate: 0deg; scale: 0.4; opacity: 1; }
  55% { translate: var(--dx) var(--dy); scale: 1; opacity: 1; }
  100% { translate: calc(var(--dx) * 1.15) calc(var(--dy) + 120px); rotate: var(--spin); scale: 0.8; opacity: 0; }
}
@keyframes victory-glow {
  0% { opacity: 0.6; scale: 1; }
  25% { opacity: 1; scale: 1.5; }
  100% { opacity: 0.55; scale: 0.96; }
}
@keyframes challenge-rays { to { rotate: 360deg; } }
@keyframes challenge-glow {
  from { opacity: 0.55; scale: 0.96; }
  to { opacity: 1; scale: 1.04; }
}
@media (prefers-reduced-motion: reduce) {
  .mission::before,
  .mission::after,
  .mission.complete::before,
  .mission.complete::after,
  .victory-halo { animation: none; }
}

/* Sizes designed to fit the screen without scrolling.
   On medium screens the challenge also can't go past the space left between the stairs, otherwise
   it pushes the stairs against the edges: the screen minus the row's margins, the two stairs
   (2/3 of their height), the gaps between them and the challenge (8vw) and the challenge's frame (82px: its 18px
   fret band and 3px rim, and the panel's 18px padding and 2px rim, on both sides). */
body[data-floor] {
  --challenge-width-desktop: calc(100vw - 2 * var(--row-inset) - 2 * min(28svh, 16vw) - 8vw - 82px);
}
body[data-floor] .tile-canvas { width: min(calc(34svh * var(--ratio, 1)), 40vw, var(--challenge-width-desktop)); }
/* (on a wide screen the painting shares the width with the paint box beside it: see "The colors of my life" below;
   with the paint box out of its column it can be taller) */
@media (min-width: 1101px) {
  body[data-floor] .tile-canvas { width: min(calc(46svh * var(--ratio, 1)), 40vw, calc(var(--challenge-width-desktop) - 132px - 24px)); }
}
/* Sized by width only (from the photo's --ratio, set by js/challenges.js): the height follows from aspect-ratio,
   so a width limit can never stretch or squash the photo */
body[data-floor] .puzzle {
  width: min(calc(min(46svh, 60vw, var(--challenge-width-desktop)) * var(--ratio, 1)), 48vw, var(--challenge-width-desktop));
  height: auto;
  max-width: none;
}
body[data-floor] .pairs-grid { width: min(40svh, 48vw, var(--challenge-width-desktop)); }
/* The WeChat phone: 66% of the screen's height, without going past the width left between the stairs
   (the width comes from the 9:16 ratio, so it never stretches or squashes) */
body[data-floor] .wechat { height: min(66svh, calc(min(48vw, var(--challenge-width-desktop) + 74px) * 16 / 9)); }

/* Letter with the message of the challenge won */
.challenge-letter {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 6;
  width: min(460px, 90vw);
  translate: -50% -50%;
  animation: letter-arrive 0.6s cubic-bezier(0.2, 0.8, 0.3, 1.2) both;
}
.challenge-letter .enamel { padding: 26px 28px 22px; text-align: center; }
/* Always one line: shrinks with the screen on phones instead of wrapping */
.letter-title { display: block; font-family: var(--font-title); font-weight: 400; font-size: min(1.6rem, 5vw); white-space: nowrap; color: var(--coral); }
.letter-text { margin: 8px 0 18px; color: var(--ink); }
.letter-close {
  padding: 8px 22px;
  font: inherit;
  font-weight: 700;
  color: var(--enamel);
  background: var(--cobalt);
  border: 2px solid var(--gold);
  border-radius: 999px;
  cursor: pointer;
}
.letter-close:hover { background: var(--turquoise); }
/* "Update your painting?": the letter with two answers side by side (wrapping on a narrow phone); the second one
   quieter, just its gold rim */
.update-painting-buttons,
.confirm-buttons { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }

/* "Start over?" (js/hall.js): the challenges' letter card, over the dimmed hall, in a red lacquer frame */
.reset-confirm {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgb(10 5 4 / 0.6);
}
.reset-confirm-card {
  width: min(420px, 100%);
  background: #7a1c14;
  animation: letter-arrive 0.6s cubic-bezier(0.2, 0.8, 0.3, 1.2) both;
}
.reset-confirm .enamel { padding: 26px 28px 22px; text-align: center; }
.letter-close-quiet { color: var(--cobalt); background: transparent; }
.letter-close-quiet:hover { color: var(--enamel); }
@keyframes letter-arrive {
  from { opacity: 0; transform: translateY(24px) scale(0.92); }
}

/* Down stairs: the same frame as the up stairs (arch in the wall + an open gap in the floor) */
.stairs-down {
  --height: min(50svh, 40vw);
  position: relative;
  display: block;
  height: var(--height);
  aspect-ratio: 240 / 360;
  margin-bottom: calc(var(--height) * -0.39);
  color: var(--enamel);
  text-decoration: none;
  filter: drop-shadow(0 18px 24px rgb(0 0 0 / 0.5));
  transition: filter 0.3s ease;
}
.stairs-down:hover { filter: drop-shadow(0 18px 24px rgb(0 0 0 / 0.5)) drop-shadow(0 0 22px rgb(217 164 65 / 0.45)); }
/* On the floors three things fit in the row (down, challenge, up): the stairs a little smaller */
body[data-floor] .stairs,
body[data-floor] .stairs-down { --height: min(42svh, 24vw); }

/* The arrows move down (or up) slightly, calling to the other floor */
.arrows-down { animation: arrows-descend 1.8s ease-in-out infinite; }
@keyframes arrows-descend {
  50% { transform: translateY(8px); opacity: 0.55; }
}
.arrows-up { animation: arrows-ascend 1.8s ease-in-out infinite; transition: opacity 0.6s ease; }
@keyframes arrows-ascend {
  50% { transform: translateY(-8px); opacity: 0.55; }
}
/* With the stairs locked the arrows disappear (the padlock takes their place); they come back when the portcullis rises */
.stairs.locked .arrows-up { opacity: 0; animation: none; }

/* The balcony's stairs (the last ones): the corridor from its loading screen (js/climb.js), with the door
   lit way up high. Rays spinning, aura pulsing, torches flickering, dust rising, golden glow */
.stairs-balcony:not(.locked) { filter: drop-shadow(0 18px 24px rgb(0 0 0 / 0.5)) drop-shadow(0 0 16px rgb(217 164 65 / 0.35)); }
/* The balcony's sign: lighter brass, with a glow around it */
.stairs-balcony .stairs-label {
  background: linear-gradient(#fbe9b8, #eac06a 60%, #b98a2e);
  box-shadow: inset 0 0 0 2px rgb(255 245 215 / 0.5), 0 3px 8px rgb(0 0 0 / 0.5), 0 0 16px rgb(217 164 65 / 0.55);
}
.stairs-balcony .balcony-rays { transform-origin: 120px 80px; animation: rays-spin 90s linear infinite; }
.stairs-balcony .balcony-aura { transform-box: fill-box; transform-origin: center; animation: aura-pulse 2.9s ease-in-out infinite; }
.stairs-balcony .balcony-flame { transform-box: fill-box; transform-origin: 50% 100%; animation: flame-quiver 0.45s ease-in-out infinite alternate; }
.stairs-balcony .dust { animation: dust-rise 4.2s linear infinite; opacity: 0; }
.stairs-balcony .balcony-sparkle { animation: sparkle-twinkle 3.2s ease-in-out infinite; }
.stairs-balcony .balcony-sparkle:nth-child(even) { animation-delay: -1.6s; }
.stairs-balcony .bulb { animation: bulb-blink 1.6s ease-in-out infinite; }
@keyframes bulb-blink { 50% { opacity: 0.45; } }
@keyframes rays-spin { to { transform: rotate(360deg); } }
@keyframes aura-pulse { 50% { transform: scale(1.07); } }
@keyframes flame-quiver { to { transform: scale(0.94, 0.84); } }
@keyframes dust-rise {
  30%, 60% { opacity: 0.9; }
  to { transform: translateY(-26px); opacity: 0; }
}
@keyframes sparkle-twinkle { 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) {
  .stairs-balcony :is(.balcony-rays, .balcony-aura, .balcony-flame, .dust, .balcony-sparkle, .bulb) { animation: none; }
  .stairs-balcony .dust { opacity: 0.6; }
}

/* The buttons for opening and closing the floating challenge only exist on phones */
.open-challenge,
.close-challenge { display: none; }

/* Phone: down + challenge + up need to fit side by side, so the challenge becomes a thumbnail
   between the stairs and, with a tap, opens floating over the floor (js/challenges.js).
   It's always the same challenge (nothing is copied): it's built at the floating window's size
   and, on the wall, shrunk with "zoom" (which shrinks the real size, not just the drawing). */
@media (max-width: 700px) {
  body[data-floor] {
    /* the game's width in the floating window: the screen minus the window's margin (16px) and the frame (its 10px
       fret band, see the tower's .frame, + the panel's 8px), on both sides */
    --challenge-width: calc(100vw - 68px);
    --thumbnail-zoom: 0.5;
    /* Big stairs: each is 2/3 of its height wide, and the two share the screen leaving
       the edges (8px + 8px) and a gap in the middle of 30% of the screen (minus 10px):
       height = (100vw - 40px - (100vw - 32px) × 0.3) / 2 × 3/2. In height, never more than 26% of the screen,
       or on the wider ones (a big phone, a small tablet) they grow out of proportion with the room and crowd the
       challenge (on most phones the width keeps them smaller than that anyway). */
    --stairs-height: min(26svh, calc((100vw - 40px - (100vw - 32px) * 0.3) * 0.75));
  }
  /* Only the two stairs stay in the row (the thumbnail moves out to the middle of the screen): centered as a pair,
     30% of the screen apart, but never further than the row's room allows (the row ends short of the columns, see
     --row-inset; each stairs is 2/3 of --stairs-height wide), so they never reach behind the columns */
  body[data-floor] .floor-row {
    justify-content: center;
    gap: min((100vw - 32px) * 0.3, 100vw - 2 * var(--row-inset) - var(--stairs-height) * 4 / 3);
  }
  body[data-floor] .stairs,
  body[data-floor] .stairs-down { --height: var(--stairs-height); }
  body[data-floor] .mission.frame { padding: 6px; }
  body[data-floor] .mission .enamel { padding: 8px; }
  body[data-floor] .tile-canvas { width: min(calc(52svh * var(--ratio, 1)), var(--challenge-width)); }
  body[data-floor] .puzzle { width: min(calc(min(62svh, var(--challenge-width)) * var(--ratio, 1)), var(--challenge-width)); }
  /* As the thumbnail on the wall, the puzzle takes the painting's height limit (52svh, see .tile-canvas above), so the
     two floors' thumbnails come out the same height: in the window it's held back by the width instead, and its
     thumbnail would be smaller. Sized by width from the photo's --ratio (the height follows), so it keeps the photo's
     shape; a wide photo is capped so the shrunk thumbnail (--thumbnail-zoom) still fits across the screen */
  body[data-floor="puzzle"]:not(.challenge-open) .puzzle { width: min(calc(52svh * var(--ratio, 1)), calc(var(--challenge-width) / var(--thumbnail-zoom))); }
  body[data-floor] .pairs-grid { width: min(50svh, var(--challenge-width)); }
  /* In the open window, the WeChat phone takes up almost the full height (without going past the screen's width) */
  body[data-floor] .wechat { height: min(78svh, calc((100vw - 32px) * 16 / 9)); }
  /* Being tall, the WeChat thumbnail shrinks a little more than the others to fit between the title and the signs;
     on shorter screens it shrinks more still */
  body[data-floor="question_game"] { --thumbnail-zoom: 0.44; }
  @media (max-height: 720px) { body[data-floor="question_game"] { --thumbnail-zoom: 0.38; } }
  @media (max-height: 620px) { body[data-floor="question_game"] { --thumbnail-zoom: 0.32; } }

  /* The floor's title and the live thumbnail (shows the progress) together, centered in the free space
     between the floor map and the stairs' signs.
     The panel goes from the map down to the top of the signs (without receiving taps) and is a grid:
     - columns 1fr auto 1fr: the title takes up the top row end to end; the thumbnail and the invisible
       button sit stacked in the middle column. Since the title spans "fr" columns, it doesn't
       widen the middle one: it stays the thumbnail's size, and the stretched button covers exactly the thumbnail;
     - rows 1fr auto auto 1fr: space, title, thumbnail, space (the two equal spaces center the pair).
     At the bottom: the floor is 20% of the screen, the stairs' arch rises 61% of their height and the sign, about 56px
     above (its 10px gap over the arch, its height and its chains). */
  body[data-floor] .challenge-panel {
    position: fixed;
    inset: 0 0 calc(20svh + var(--stairs-height) * 0.61 + 56px);
    z-index: 0; /* below the stairs (z-index 1): the thumbnail's rays pass behind them */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    grid-template-rows: 1fr auto auto 1fr;
    margin: 0;
    padding-top: calc(9svh + 64px); /* right below the floor map */
    pointer-events: none;
  }
  body[data-floor] .floor-sign {
    grid-area: 2 / 1 / 3 / -1;
    position: static;
    width: auto;
    margin: 0 4vw 14px;
    translate: none;
  }
  body[data-floor] .mission,
  body[data-floor] .open-challenge { grid-area: 3 / 2; }
  body[data-floor] .mission { zoom: var(--thumbnail-zoom); pointer-events: none; }
  /* In the thumbnail the restart button disappears; it comes back in the open window */
  body[data-floor] .restart-challenge { display: none; }
  body.challenge-open .restart-challenge { display: grid; }
  /* The paint box too: the thumbnail shows only the painting */
  body[data-floor] .palette { display: none; }
  body.challenge-open .palette { display: grid; }
  body[data-floor] .tile-zoom { display: none; }
  body.challenge-open .tile-zoom { display: block; }
  /* and the whole painting, even if she left it zoomed in */
  body[data-floor]:not(.challenge-open) .tile-view .tile-canvas { transform: none; }
  body[data-floor] .open-challenge {
    display: block;
    place-self: stretch;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    pointer-events: auto;
  }

  /* Open: the same frame floats over the floor, at its real size, on a dark background */
  body.challenge-open .challenge-panel {
    position: fixed;
    inset: 0;
    z-index: 40;
    display: flex;
    justify-content: center;
    padding: 16px;
    /* up and down only: with overflow-y set, overflow-x would turn "auto" too, and anything poking out past the
       screen's sides (the restart button on the frame's corner, the rays) would make it scroll sideways */
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain; /* scrolling the window to the end doesn't pass the scroll on to the page */
    pointer-events: auto;
    background: rgb(5 8 20 / 0.85);
    animation: challenge-open-fade 0.25s ease-out;
  }
  body.challenge-open .mission,
  body.challenge-open .floor-sign { grid-area: auto; }
  body.challenge-open .mission { zoom: 1; pointer-events: auto; }
  /* In the open window the rays are pinned to the screen (fixed), centered behind the challenge:
     that way they don't count toward the window's scrolling and don't create scrollbars.
     The glow is left out (pinned to the screen it would cover the whole screen). */
  body.challenge-open .mission::before {
    position: fixed;
    --rays-size: 100vw;
  }
  body.challenge-open .mission::after { display: none; }
  /* The confetti also comes out of the middle of the screen, for the same reason */
  body.challenge-open .victory-confetti { position: fixed; }
  body.challenge-open .open-challenge { display: none; }
  /* The floor's title and hint go along, right above the challenge */
  body.challenge-open .floor-sign { position: static; width: auto; margin-bottom: 12px; translate: none; }
  body.challenge-open .close-challenge {
    position: fixed;
    top: 12px;
    left: 12px; /* the mute button is on the right */
    z-index: 41;
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    padding: 0;
    color: var(--gold);
    background: rgb(8 15 42 / 0.6);
    border: 1px solid rgb(217 164 65 / 0.5);
    border-radius: 50%;
    cursor: pointer;
  }
  .close-challenge svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; }
}
@keyframes challenge-open-fade {
  from { opacity: 0; }
}

/* "The colors of my life": the painting (a canvas she taps to pour the color), and the chosen color's name under it */
/* --ratio (width / height) comes from js/challenges.js once the drawing loads: the canvas takes the drawing's shape,
   and its height stays what a square's would be */
.tile-canvas {
  display: block;
  width: min(calc(320px * var(--ratio, 1)), 100%);
  height: auto;
  margin: 0 auto;
  cursor: pointer;
  touch-action: manipulation;
  border-radius: 6px;
  box-shadow: 0 4px 14px rgb(0 0 0 / 0.25);
}
/* The zoom slider stands up on the painting's left, as tall as it, zoomed in at the top. The painting gives up the
   slider's width when there isn't room for both (min-width: 0 lets it shrink, keeping its shape) */
.tile-stage { display: flex; justify-content: center; }
/* The painting zooms inside a frame that keeps its size and hides what spills past it (the frame wears the canvas's
   rounded corners and shadow, which the clipping would cut off). --zoom, --pan-x and --pan-y come from js/challenges.js */
.tile-view {
  min-width: 0;
  overflow: hidden;
  border-radius: 6px;
  box-shadow: 0 4px 14px rgb(0 0 0 / 0.25);
}
.tile-view .tile-canvas {
  max-width: 100%;
  margin: 0;
  border-radius: 0;
  box-shadow: none;
  transform-origin: 0 0;
  transform: translate(var(--pan-x, 0px), var(--pan-y, 0px)) scale(var(--zoom, 1));
  touch-action: pan-y; /* at 1×, a finger still scrolls the window up and down */
}
/* Zoomed in, a finger or the mouse drags the painting around (js/challenges.js) instead of scrolling the window */
.tile-view.zoomed .tile-canvas { touch-action: none; }
/* Drawn by hand (appearance: none): a blue bar with rounded ends (a dot at each end of a straight bar), and the
   knob a gold disc with a blue magnifying glass. The blue is written out: on the challenge floors --cobalt is red */
.tile-zoom {
  flex: none;
  width: 26px;
  margin: 0 6px 0 0;
  padding: 0;
  writing-mode: vertical-lr;
  direction: rtl; /* the small end at the bottom */
  appearance: none;
  -webkit-appearance: none;
  background:
    radial-gradient(circle, #1B3F8B 3.5px, transparent 4px) center top / 8px 8px no-repeat,
    radial-gradient(circle, #1B3F8B 3.5px, transparent 4px) center bottom / 8px 8px no-repeat,
    linear-gradient(#1B3F8B, #1B3F8B) center / 8px calc(100% - 8px) no-repeat;
  cursor: pointer;
}
.tile-zoom::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--gold) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231B3F8B' stroke-width='2.6' stroke-linecap='round'%3E%3Ccircle cx='10' cy='10' r='5.5'/%3E%3Cpath d='M14.2 14.2 19 19'/%3E%3C/svg%3E") center / 18px no-repeat;
  box-shadow: 0 2px 5px rgb(0 0 0 / 0.3);
}
.tile-zoom::-moz-range-thumb {
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 50%;
  background: var(--gold) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231B3F8B' stroke-width='2.6' stroke-linecap='round'%3E%3Ccircle cx='10' cy='10' r='5.5'/%3E%3Cpath d='M14.2 14.2 19 19'/%3E%3C/svg%3E") center / 18px no-repeat;
  box-shadow: 0 2px 5px rgb(0 0 0 / 0.3);
}
/* The hint's secret word (cheat code, js/challenges.js): looks like the rest of the hint; quick taps on it don't zoom
   the page on a phone (touch-action needs a box, hence inline-block) */
.cheat-word { display: inline-block; touch-action: manipulation; }
/* The palette as a paint box: a gold-rimmed tray with 8 square pans per row (they shrink on narrow screens) */
.palette {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 6px;
  box-sizing: border-box;
  width: min(100%, 290px);
  margin: 10px auto 0;
  padding: 8px;
  background: rgb(20 34 77 / 0.08);
  border: 1px solid var(--gold);
  border-radius: 10px;
}
.paint {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  padding: 0;
  border: 2px solid rgb(20 34 77 / 0.35);
  border-radius: 6px;
  cursor: pointer;
  transition: transform 0.15s, box-shadow 0.15s;
}
/* The picked pan lifts a little, with a gold ring (no scaling: the pans sit too close for that) */
.paint.active { transform: translateY(-2px); box-shadow: 0 0 0 2px var(--gold), 0 3px 6px rgb(0 0 0 / 0.3); }
/* The eraser: a rubber eraser drawn on a white swatch */
.paint.eraser { display: grid; place-items: center; background: #fff; }
.paint.eraser svg { width: 75%; height: 75%; }
/* Undo: a full row under the pans (so the rows of colors stay full in both layouts), bright gold with a navy arrow */
.undo-paint {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px;
  font: inherit;
  color: var(--night);
  background: var(--gold);
  border: 2px solid var(--gold);
  border-radius: 6px;
  box-shadow: 0 2px 5px rgb(0 0 0 / 0.25);
  cursor: pointer;
  transition: filter 0.2s, opacity 0.2s;
}
.undo-paint svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.undo-paint:hover { filter: brightness(1.12); }
.undo-paint:disabled { opacity: 0.4; cursor: default; filter: none; }

/* On a wide computer screen, side by side: the painting on the left, and on the right the paint box standing up as a
   tall strip, 3 pans across and 8 down, centered beside it (narrow, so the painting keeps most of the little width
   there is between the stairs; about as tall as the painting). Narrower and on a phone, it stays one column */
@media (min-width: 1101px) {
  .mission[data-mission="tile"] .enamel {
    display: grid;
    grid-template-columns: auto 132px;
    grid-template-areas: "canvas palette";
    column-gap: 24px;
    align-items: center;
  }
  .mission[data-mission="tile"] .tile-stage { grid-area: canvas; }
  .mission[data-mission="tile"] .palette { grid-area: palette; grid-template-columns: repeat(3, 1fr); width: 100%; margin: 0; }
}

/* Puzzle: pieces separated by golden threads */
.puzzle {
  display: grid;
  /* --size (pieces per side) is set by js/challenges.js */
  grid-template-columns: repeat(var(--size, 3), 1fr);
  grid-template-rows: repeat(var(--size, 3), 1fr);
  gap: 3px;
  width: min(520px, 100%);
  aspect-ratio: 1;
  margin: 0 auto;
  background: var(--gold);
  border: 3px solid var(--gold);
  border-radius: 6px;
  overflow: hidden;
  transition: gap 0.5s;
}
.puzzle.solved { gap: 0; }

.piece {
  all: unset;
  position: relative;
  display: grid;
  place-items: center;
  background-image: var(--photo);
  background-size: calc(var(--size, 3) * 100%) calc(var(--size, 3) * 100%);
  cursor: pointer;
}
.piece.selected { outline: 4px solid var(--coral); outline-offset: -4px; z-index: 1; }
.piece:focus-visible { outline: 3px solid var(--gold); outline-offset: -3px; }
.piece-number { display: none; }
.no-photo .piece { background: var(--tile) 0 0 / 30px 30px; }
.no-photo .piece-number {
  display: grid;
  place-items: center;
  width: 2em;
  height: 2em;
  font-family: var(--font-title);
  font-size: 1.4rem;
  color: var(--ink);
  background: var(--enamel);
  border: 2px solid var(--gold);
  border-radius: 50%;
}

/* "The Oracle's Door": twelve cards face down in a grid (4 × 3), to be turned two at a time */
.pairs-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; width: min(420px, 100%); margin: 0 auto; }
/* A card is a button with two sides stacked on each other: the back (starry blue) and the face (sunflower yellow),
   which starts turned away. .turned flips the card over in 3D; .matched makes a found pair glow */
.tarot-card {
  all: unset;
  position: relative;
  aspect-ratio: 2 / 3;
  border-radius: 8px;
  cursor: pointer;
  transform-style: preserve-3d;
  transition: transform 0.45s cubic-bezier(0.3, 0.7, 0.2, 1), box-shadow 0.3s;
}
.tarot-card.turned { transform: rotateY(180deg); cursor: default; }
.tarot-card.matched { box-shadow: 0 0 0 3px #fff1c2, 0 0 22px #ffcf7a; }
.tarot-card:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }
:is(.tarot-back, .tarot-face) {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  border-radius: 8px;
  box-shadow: 0 4px 10px rgb(0 0 0 / 0.2);
  backface-visibility: hidden;
}
.tarot-back {
  display: block;
  background: radial-gradient(circle at 50% 50%, #3d5fa8, #1f3366 75%);
  border: 3px solid #f2b84a;
}
.tarot-back svg { display: block; width: 100%; height: 100%; }
.tarot-face {
  display: block;
  padding: 5px;
  background: linear-gradient(160deg, #fbe39a, #f2b84a);
  border: 3px solid #2e4a8a;
  transform: rotateY(180deg);
  /* the face measures itself: its texts are sized by the card (cqw), not the screen, so they always fit inside it */
  container-type: size;
}
.tarot-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  height: 100%;
  padding: 6% 4%;
  text-align: center;
  color: #2e4a8a;
  background: radial-gradient(circle at 50% 40%, #fff8e0, #f6e2b0 70%);
  border: 1px solid rgb(46 74 138 / 0.4);
  border-radius: 5px;
}
.tarot-numeral { font-family: var(--font-title); font-size: 26cqw; }
.tarot-symbol { font-size: 70cqw; line-height: 1; color: #d0473a; }
.tarot-name { font-family: var(--font-title); font-size: 13cqw; line-height: 1.1; }

/* The Tea House: a chat in the style of WeChat (light mode, as she sees it).
   Here the look imitates the app, hence its fonts: Apple's own on iPhone/Mac or wherever SF is installed (SF UI Text, Display from 20px up, +
   PingFang SC for Chinese), and elsewhere
   Android's Roboto + Noto Sans SC, loaded from Google Fonts so Windows doesn't fall back to Segoe UI. */
/* The phone has the shape of a phone screen (9:16): the height comes from outside (body[data-floor] .wechat)
   and the width comes from the ratio */
.wechat {
  position: relative;
  display: flex;
  flex-direction: column;
  aspect-ratio: 9 / 16;
  width: auto;
  max-width: 100%;
  margin: 0 auto;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, "SF UI Text", "SF Pro Text", "PingFang SC", Roboto, "Noto Sans SC", sans-serif;
  text-align: left;
  color: #111;
  background: #ededed;
  border: 3px solid var(--gold);
  border-radius: 26px;
  box-shadow: 0 16px 34px rgb(0 0 0 / 0.3);
}

/* Top bar: the name centered between the back arrow and, on the right, the questions counter and the "⋯" */
.wechat-header {
  display: grid;
  grid-template-columns: 76px 1fr 76px;
  align-items: center;
  padding: 12px 12px 10px;
  text-align: center;
  background: #ededed;
  border-bottom: 1px solid #d9d9d9;
}
.wechat-header strong { font-size: 1rem; font-weight: 500; }
.wechat-back { font-family: -apple-system, BlinkMacSystemFont, "SF UI Display", "SF Pro Display", "PingFang SC", Roboto, "Noto Sans SC", sans-serif; font-size: 1.7rem; line-height: 1; text-align: left; }
.wechat-more { font-size: 1.2rem; text-align: right; }
.wechat-header-right { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
/* The questions counter: a small pill with a chat bubble and "n/5"; it pops when it goes up and turns green when full */
.wechat-counter {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 7px;
  font-size: 0.72rem;
  font-weight: 700;
  color: #555;
  background: #dcdcdc;
  border-radius: 999px;
}
.wechat-counter svg { width: 11px; height: 11px; fill: currentColor; }
.wechat-counter.full { color: #fff; background: #07c160; }
.wechat-counter.bump { animation: counter-bump 0.45s cubic-bezier(0.3, 1.6, 0.5, 1); }
@keyframes counter-bump { 40% { transform: scale(1.3); } }
@media (prefers-reduced-motion: reduce) { .wechat-counter.bump { animation: none; } }

/* The messages: the header and the footer stay fixed and the chat takes up the rest */
.wechat-conversation {
  display: flex;
  flex-direction: column;
  gap: 14px;
  flex: 1 1 0;
  min-height: 0;
  padding: 12px 10px 18px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: #c7c7c7 transparent;
}
/* Dragged with the mouse to scroll (js/challenges.js): the closed hand while it moves */
.wechat-conversation.dragging { cursor: grabbing; }
.wechat-time { align-self: center; margin: 0; font-size: 0.75rem; color: #9b9b9b; }

/* A message: square avatar with rounded corners and the bubble with its little tail pointing at it.
   Yours (me) on the left in white, hers on the right in WeChat's green */
.wechat-msg { display: flex; align-items: flex-start; gap: 8px; max-width: 86%; }
.wechat-her { flex-direction: row-reverse; align-self: flex-end; }
.wechat-avatar {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  font-weight: 700;
  color: #fff;
  background: var(--cobalt);
  border-radius: 5px;
}
.wechat-her .wechat-avatar { background: #8a8a8a; }
/* With the profile photos loaded (js/challenges.js), they replace the letters */
.wechat.has-my-photo .wechat-me .wechat-avatar { font-size: 0; background: var(--my-photo) center / cover; }
.wechat.has-her-photo .wechat-her .wechat-avatar { font-size: 0; background: var(--her-photo) center / cover; }
.wechat-bubble {
  position: relative;
  margin: 0;
  padding: 8px 11px;
  font-size: 0.92rem;
  line-height: 1.4;
  background: #fff;
  border-radius: 5px;
  animation: emerge 0.3s ease both;
}
.wechat-bubble::before {
  content: "";
  position: absolute;
  top: 12px;
  left: -5px;
  border: 5px solid transparent;
  border-left: 0;
  border-right-color: #fff;
}
.wechat-her .wechat-bubble { background: #95ec69; }
.wechat-her .wechat-bubble::before { left: auto; right: -5px; border: 5px solid transparent; border-right: 0; border-left-color: #95ec69; }
/* WeChat's "sending" spinner: a small gray ring turning beside her bubble until it's sent (js/challenges.js) */
.wechat-sending {
  flex: none;
  align-self: center;
  width: 14px;
  height: 14px;
  border: 2px solid #cfcfcf;
  border-top-color: #8a8a8a;
  border-radius: 50%;
  animation: wechat-spin 0.7s linear infinite;
}
@keyframes wechat-spin { to { transform: rotate(360deg); } }
/* On her side the spinner floats just left of the bubble without taking up room in the row, so the bubble already
   has its final width (and line breaks) and doesn't widen when the spinner goes away */
.wechat-her { position: relative; }
.wechat-her .wechat-sending { position: absolute; top: 0; bottom: 0; right: calc(100% + 8px); margin: auto 0; }
/* Your sticker after each answer: big, without a bubble */
.wechat-sticker { position: relative; font-size: 3rem; line-height: 1; animation: emerge 0.3s ease both; }
/* An image sticker (img/stickers/): the size of the emoji ones, a bit bigger in the chat */
.wechat-sticker img { display: block; width: 2.71em; height: 2.71em; object-fit: contain; transition: opacity 0.2s; }
/* Still downloading: the image stays hidden and the "sending" ring (.wechat-sending) turns in its middle */
.wechat-sticker.is-loading img { opacity: 0; }
.wechat-sticker.is-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 20px;
  height: 20px;
  margin: auto;
  border: 2px solid #cfcfcf;
  border-top-color: #8a8a8a;
  border-radius: 50%;
  animation: wechat-spin 0.7s linear infinite;
}

/* Bottom: the input bar (her question types itself into it) and the panel with the questions, which the keyboard
   replaces while she "types" (.typing, js/challenges.js). Both share one height, so nothing ever jumps */
.wechat-footer { --panel-height: min(260px, 36svh); flex: none; background: #f7f7f7; border-top: 1px solid #d9d9d9; }
/* The input bar, drawn like WeChat's: line icons in circles (voice on the left, emoji and "+" on the right) around a
   white rounded field with its gray mic on the right */
.wechat-input { display: flex; align-items: center; gap: 7px; padding: 6px 8px; background: #f7f7f7; }
.wechat-icon {
  flex: none;
  width: 22px;
  height: 22px;
  fill: none;
  stroke: #181818;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.wechat-icon-fill { fill: #181818; stroke: none; }
.wechat-field-box {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  height: 30px;
  padding: 0 6px 0 8px;
  background: #fff;
  border-radius: 5px;
}
.wechat-field {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-size: 0.82rem;
  line-height: 30px;
  white-space: nowrap;
}
.wechat-mic {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: #6b6b6b;
  stroke-width: 1.6;
  stroke-linecap: round;
}

/* The panel: the theme tabs, then the questions. Always the same height, whatever the theme shows (it scrolls if
   they don't fit), so neither it nor the chat above ever jumps */
.wechat-questions {
  height: var(--panel-height);
  overflow-x: hidden; /* the questions sliding in stay inside */
  overflow-y: auto;
  overscroll-behavior: contain;
  background: #fff;
  border-top: 1px solid #e5e5e5;
  scrollbar-width: thin;
}
/* The themes as folder tabs on a gray strip: the others are gray text on the strip, the chosen one is a solid green
   tab standing on the strip's green bottom edge, over the list of its questions. A theme already asked from gets a green ✓.
   When the names don't fit, the strip scrolls sideways and fades at its edges */
.wechat-tabs-bar { position: sticky; top: 0; z-index: 1; }
.wechat-tabs {
  display: flex;
  gap: 2px;
  padding: 7px 10px 0;
  overflow-x: auto;
  scrollbar-width: none;
  background: #e6e6e6;
  border-bottom: 3px solid #07c160; /* joins the chosen tab (solid green) to its questions below */
  mask-image: linear-gradient(90deg, transparent, #000 10px, #000 calc(100% - 10px), transparent);
}
.wechat-tabs::-webkit-scrollbar { display: none; }
/* Dragged sideways with the mouse (js/challenges.js): the closed hand while it moves */
.wechat-tabs.dragging,
.wechat-tabs.dragging .wechat-tab { cursor: grabbing; }
/* The arrows on the strip's edges, only on a side that still hides tabs: a chevron over the strip's gray, fading
   into the tabs (above the green bottom edge, which stays whole) */
.wechat-tabs-arrow {
  position: absolute;
  top: 0;
  bottom: 3px;
  z-index: 1;
  display: flex;
  align-items: center;
  width: 22px;
  padding: 3px 0 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.wechat-tabs-arrow[hidden] { display: none; }
.wechat-tabs-arrow.prev { left: 0; justify-content: flex-start; padding-left: 3px; background: linear-gradient(90deg, #e6e6e6 55%, rgb(230 230 230 / 0)); }
.wechat-tabs-arrow.next { right: 0; justify-content: flex-end; padding-right: 3px; background: linear-gradient(270deg, #e6e6e6 55%, rgb(230 230 230 / 0)); }
.wechat-tabs-arrow svg {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: #555;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.wechat-tabs-arrow:hover svg { stroke: #07c160; }
.wechat-tabs-arrow:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
.wechat-tab {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  padding: 8px 12px 9px;
  font: inherit;
  font-size: 0.8rem;
  white-space: nowrap;
  color: #6f6f6f;
  background: none;
  border: 0;
  border-radius: 8px 8px 0 0;
  cursor: pointer;
  touch-action: manipulation;
}
.wechat-tab:hover:not([aria-selected="true"]) { color: #333; background: rgb(255 255 255 / 0.45); }
/* A thin vertical line between each theme and the next (not touching the green tab, on either side) */
.wechat-tab { position: relative; }
.wechat-tab + .wechat-tab::before {
  content: "";
  position: absolute;
  top: 28%;
  bottom: 28%;
  left: -2px;
  border-left: 2px solid #b8b8b8;
  border-radius: 1px;
}
.wechat-tab[aria-selected="true"]::before,
.wechat-tab[aria-selected="true"] + .wechat-tab::before { display: none; }
.wechat-tab[aria-selected="true"] { font-weight: 700; color: #fff; background: #07c160; }
.wechat-tab[aria-selected="true"] .wechat-tab-done { color: #fff; }
.wechat-tab-done { margin-left: -1px; font-weight: 700; color: #07c160; }
/* A theme already asked from: darker text and icon, so it reads as done at a glance */
.wechat-tab.used:not([aria-selected="true"]) { color: #444; }
/* The theme's icon, in the tab's color; white on the chosen (green) tab */
.wechat-tab-icon {
  flex: none;
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.wechat-tab[aria-selected="true"] .wechat-tab-icon { stroke: #fff; }
/* A theme already asked from shrinks to its icon and ✓, leaving room for the others; open again, it shows its name */
.wechat-tab.used:not([aria-selected="true"]) { padding-inline: 9px; }
.wechat-tab.used:not([aria-selected="true"]) .wechat-tab-name { display: none; }
.wechat-tab:focus-visible,
.wechat-question:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }

/* The questions: WeChat's settings-style list, full-width white rows split by hairlines (inset from the left, as in
   the app). An asked question gets a green ✓ on the right */
.wechat-list { display: flex; flex-direction: column; background: #fff; }
/* Switching themes: the new questions slide in from the side of the tab picked (js/challenges.js) */
.wechat-list.slide-next { animation: questions-from-right 0.3s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
.wechat-list.slide-prev { animation: questions-from-left 0.3s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
@keyframes questions-from-right { from { opacity: 0; transform: translateX(35%); } }
@keyframes questions-from-left { from { opacity: 0; transform: translateX(-35%); } }
@media (prefers-reduced-motion: reduce) {
  .wechat-list.slide-next,
  .wechat-list.slide-prev { animation: none; }
}
.wechat-question {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  font: inherit;
  font-size: 0.86rem;
  line-height: 1.35;
  text-align: left;
  color: #111;
  background: #fff;
  border: 0;
  cursor: pointer;
  touch-action: manipulation;
}
.wechat-question + .wechat-question::before {
  content: "";
  position: absolute;
  top: 0;
  left: 44px; /* starts where the text does, after the number badge */
  right: 0;
  border-top: 1px solid #ececec;
}
/* The question's number in its theme, plain and bold on the left (a fixed width, so the questions line up): green on
   the one asked */
.wechat-question-number { flex: none; width: 20px; font-size: 0.9rem; font-weight: 700; color: #9a9a9a; text-align: center; }
.wechat-question:disabled .wechat-question-number { color: #cdcdcd; }
.wechat-question.asked .wechat-question-number { color: #07c160; }
/* After the 5 themes: WeChat's sticker panel (js/challenges.js). The input bar's smiley turns into the keyboard icon,
   then the pack's name and the stickers in a grid of 3; a sticker darkens a little when pressed */
.wechat-icon-keyboard { display: none; }
.wechat-footer.stickers .wechat-icon-keyboard { display: block; }
.wechat-footer.stickers .wechat-icon-emoji { display: none; }
.wechat-sticker-panel { min-height: 100%; padding: 8px 10px 12px; background: #ededed; }
.wechat-sticker-title { margin: 0 2px 8px; font-size: 0.78rem; color: #7a7a7a; }
.wechat-sticker-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px 6px; }
.wechat-sticker-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 4px 0;
  font: inherit;
  background: none;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
  touch-action: manipulation;
}
.wechat-sticker-option .wechat-sticker { font-size: 2rem; animation: none; }
.wechat-sticker-option:hover:not(:disabled),
.wechat-sticker-option:active:not(:disabled) { background: rgb(0 0 0 / 0.06); }
.wechat-sticker-option:disabled { cursor: default; opacity: 0.5; }
.wechat-sticker-option:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
/* The slot on the right: empty on a question she can ask, the ✓ or the padlock otherwise (below) */
.wechat-question::after { content: ""; flex: none; margin-left: auto; font-size: 1.2rem; line-height: 1; color: #b2b2b2; }
.wechat-question:hover:not(:disabled) { background: #f2f2f2; }
.wechat-question:active:not(:disabled) { background: #e8e8e8; }
/* Can't be asked (theme used, or a reply still playing): grayed out. The one asked sits in a soft
   green box with its green ✓, so she sees which one she picked */
.wechat-question:disabled { cursor: default; color: #b2b2b2; }
.wechat-question:disabled::after { content: ""; }
.wechat-question.asked { margin: 4px 8px; padding-inline: 10px; color: #5f7a68; background: #cfe8d7; border-radius: 7px; }
/* the box needs no dividers touching it, above or below */
.wechat-question.asked::before,
.wechat-question.asked + .wechat-question::before { display: none; }
.wechat-question.asked::after { content: "✓"; font-size: 0.95rem; font-weight: 700; color: #07c160; }
/* A theme already asked from: the list looks locked, a grayer background and a small padlock on each question left */
.wechat-list.locked,
.wechat-list.locked .wechat-question:not(.asked) { background: #f6f6f6; }
.wechat-list.locked .wechat-question:not(.asked)::after {
  content: "";
  width: 13px;
  height: 13px;
  background: #c2c2c2;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 10V7a5 5 0 0 1 10 0v3h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2zm2 0h6V7a3 3 0 0 0-6 0z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 10V7a5 5 0 0 1 10 0v3h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2zm2 0h6V7a3 3 0 0 0-6 0z'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* The phone keyboard (shown only while she "types"): light gray, white keys with a soft bottom edge, the special keys
   darker, the send key in WeChat's green. A key lights up as its letter gets typed */
.wechat-keyboard {
  display: none;
  flex-direction: column;
  gap: 7px;
  height: var(--panel-height);
  padding: 8px 4px 10px;
  background: #d3d6dc;
  border-top: 1px solid #e5e5e5;
}
.wechat-footer.typing .wechat-questions { display: none; }
.wechat-footer.typing .wechat-keyboard { display: flex; }
/* After a message goes out, the keyboard (on its way out) or the panel slides down to nothing, until the input bar is
   tapped (.collapsed, js/challenges.js). Meanwhile the empty field shows a gray hint to tap it */
.wechat-questions,
.wechat-keyboard { transition: height 0.3s ease, padding 0.3s ease; }
.wechat-footer.collapsed .wechat-questions,
.wechat-footer.collapsed .wechat-keyboard {
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  overflow: hidden;
  border-top: 0;
}
.wechat-footer.collapsed .wechat-input { cursor: pointer; }
.wechat-footer.collapsed:not(.typing) .wechat-field:empty::before { content: attr(data-hint); color: #a0a0a0; }
/* The suggestion bar on top: three words split by thin lines, the middle one bold. A suggestion lights up when tapped */
.wechat-suggestions { display: flex; flex: none; height: 30px; margin: -4px 0 0; }
.wechat-suggestion {
  flex: 1;
  min-width: 0;
  padding: 0 4px;
  overflow: hidden;
  font-size: 0.88rem;
  line-height: 30px;
  text-align: center;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: #222;
  border-radius: 5px;
  transition: background 0.12s;
}
.wechat-suggestion + .wechat-suggestion { border-left: 1px solid #b4b8c0; border-radius: 0; }
.wechat-suggestion:nth-child(2) { font-weight: 600; }
.wechat-suggestion.pressed { background: #bfc3cb; }
.wechat-key-row { display: flex; flex: 1; gap: 4px; }
/* The middle row is shorter, so it sits indented by half a key, as on a phone */
.wechat-key-row:nth-child(2) { padding: 0 5%; }
.wechat-key {
  display: grid;
  place-items: center;
  flex: 1;
  min-width: 0;
  font-size: 0.95rem;
  color: #111;
  background: #fff;
  border-radius: 5px;
  box-shadow: 0 1px 0 #8e9096;
  transition: background 0.12s, transform 0.12s;
}
.wechat-key-shift,
.wechat-key-backspace,
.wechat-key-123 { flex: 1.4; font-size: 0.8rem; background: #abb0ba; }
.wechat-key-space { flex: 4.5; }
.wechat-key-send { flex: 1.9; font-size: 0.8rem; font-weight: 600; color: #fff; background: #07c160; }
.wechat-key.pressed { background: #c4c7ce; transform: scale(0.94); }
.wechat-key-send.pressed { background: #059a4c; }
/* While she types, a green caret blinks at the end of the text */
.wechat-footer.typing .wechat-field::after {
  content: "";
  display: inline-block;
  width: 1.5px;
  height: 1.3em;
  margin-left: 1px;
  vertical-align: middle;
  background: #07c160;
  animation: caret-blink 1s steps(1) infinite;
}
@keyframes caret-blink { 50% { opacity: 0; } }
@keyframes shake {
  20%, 60% { transform: translateX(-6px); }
  40%, 80% { transform: translateX(6px); }
}

/* ---------- The challenge floors as the palace tower's upper storeys ----------
   The same materials as the hall's (js/hall.js), so climbing the tower stays inside the same palace: a dark carved
   ceiling with two beams and a slate frieze of gold openwork, timber-paneled walls lit warm in the middle, red lacquer
   columns with gold capitals at both ends, a gray-blue brick dado under a green-glazed eave with a rosewood key-fret band
   at its foot, granite paving in perspective and the red rug edged in gold. The challenge's frame is that same
   rosewood key-fret band, and rice paper instead of enamel */
body[data-area="challenges"] {
  --cobalt: #8e1a14;
  --turquoise: #b02c20;
  --enamel: #f6eedb;
  --ink: #2a1a10;
  --ink-soft: #5e4a36;
  --ceiling-h: 9svh; /* the ceiling's height (the tower is 100svh tall); everything under it follows */
  --frieze-h: clamp(12px, 3svh, 22px); /* the slate frieze under the ceiling */
  --eave-h: clamp(6px, 1.4svh, 16px); /* the green-glazed eave over the brick */
  --fret-h: clamp(14px, 3.5svh, 34px); /* the rosewood key-fret band at the wall's foot */
  /* the columns: where the left one stands (the right one is its mirror) and how wide its shaft is, as in the hall
     (on a phone, sturdier for the narrow screen and a little off its edges) */
  --column-x: 52px;
  --column-w: clamp(16px, 3.2vw, 44px);
  /* the row of stairs and challenge never reaches behind the columns: it starts 8px past a column's capital (its widest
     part, from a quarter of the shaft before it to a quarter after), or where it always did if that's further in */
  --row-inset: max(clamp(52px, 5vw, 80px), calc(var(--column-x) + var(--column-w) * 1.25 + 8px));
}
/* a narrow computer screen: the columns at the very edges, 80% on the screen (the floor map's rail passes over the
   left one), and the stairs at the ends of their row, right beside them (only keeping clear of the rail), the
   challenge centered between them with all the room they leave */
@media (min-width: 701px) and (max-width: 1100px) {
  body[data-area="challenges"] { --column-x: calc(var(--column-w) * -0.2); }
  body[data-area="challenges"] .floor-row { justify-content: space-between; }
}
@media (max-width: 700px) {
  body[data-area="challenges"] {
    --column-w: clamp(18px, 5.5vw, 26px);
    --column-x: calc(var(--column-w) * -0.2); /* 80% of the shaft on the screen */
    --row-inset: max(8px, calc(var(--column-x) + var(--column-w) * 1.25 + 8px));
    /* the lanterns hang higher, all in a row right under the frieze (none of their usual drop), clear of the title */
    --drop-scale: 0;
  }
}
body[data-area="challenges"] .floor-row { left: var(--row-inset); right: var(--row-inset); }

/* the wall: timber panels (posts, and a rail across), lit warm in the middle where the challenge stands and darker
   toward the corners, the lanterns' light pooled above */
body[data-area="challenges"] .floor-scene {
  background:
    radial-gradient(ellipse at 50% 30%, rgb(255 190 110 / 0.13), transparent 60%),
    radial-gradient(circle 80vmax at 50% 45%, transparent 55%, rgb(0 0 0 / 0.55)),
    linear-gradient(transparent 38.5%, rgb(0 0 0 / 0.16) 38.5% calc(38.5% + 4px), transparent 0),
    repeating-linear-gradient(90deg, rgb(0 0 0 / 0.22) 0 5px, transparent 5px clamp(40px, 6.25vw, 90px)),
    radial-gradient(circle 80vmax at 50% 36%, #5a3424, #3c2218 45%, #1c0f0a);
}

/* the ceiling: dark carved wood with two beams and a gold edge (the layers, from the top one: the edge, the beams) */
body[data-area="challenges"] .ceiling {
  height: var(--ceiling-h);
  background:
    linear-gradient(90deg, #a57d2e, #f7e3a8, #b98a2e) 0 100% / 100% 3px no-repeat,
    linear-gradient(#4e2c1c, #2a160e) 0 26.2% / 100% 16% no-repeat,
    linear-gradient(#4e2c1c, #2a160e) 0 71.4% / 100% 16% no-repeat,
    linear-gradient(#1a0a07, #341a12);
  border-bottom: 0;
}
/* under it, the slate frieze carved with a gold openwork of squares and diamonds, a gold line at its foot */
body[data-area="challenges"] .ceiling::after {
  height: var(--frieze-h);
  margin-top: 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='6' height='6'%3E%3Cg fill='none' stroke='%23d9a441' stroke-opacity='.45'%3E%3Cpath d='M0 3H6M3 0V6' stroke-width='.6'/%3E%3Cpath d='M3 .9L5.1 3L3 5.1L.9 3Z' stroke-width='.5'/%3E%3C/g%3E%3C/svg%3E") 0 0 / 6px 6px,
    linear-gradient(#363d52, #22273a);
  border-bottom: 1.5px solid var(--gold);
  box-shadow: 0 10px 24px rgb(0 0 0 / 0.5);
}
/* the lanterns hang from under the frieze, clear of the columns */
body[data-area="challenges"] .floor-scene .lanterns {
  --hang: calc(var(--ceiling-h) + var(--frieze-h) + 2px); /* from the top of the page down to the row */
  top: var(--hang);
  padding: 0 calc(var(--column-x) + var(--column-w) + 2vw);
}
/* each cord runs on up through the frieze and the ceiling to the top of the page, and the lantern sways from there */
body[data-area="challenges"] .floor-scene .lantern {
  transform-origin: 50% calc(-1 * (var(--drop, 0px) * var(--drop-scale, 1) + var(--hang)));
}
body[data-area="challenges"] .floor-scene .lantern::before {
  top: calc(-1 * (var(--drop, 0px) * var(--drop-scale, 1) + var(--hang)));
}
/* the first lantern takes clicks (the row doesn't): its secret 6 clicks lock the floor again (js/challenges.js) */
body[data-area="challenges"] .floor-scene .lantern:first-child { pointer-events: auto; }

/* the dado, the hall's ground floor in small (from the top): the green-glazed eave with its rows of tiles, a gold
   line and the eave's shadow, the gray-blue brick, and the rosewood band carved with the gold key fret, on the floor */
body[data-area="challenges"] .wainscot {
  background:
    repeating-linear-gradient(90deg, transparent 0 2.7px, rgb(143 196 168 / 0.4) 2.7px 4.3px, transparent 4.3px 7px) 0 0 / 100% var(--eave-h) no-repeat,
    linear-gradient(#1e3e34, #3a7058) 0 0 / 100% var(--eave-h) no-repeat,
    linear-gradient(var(--gold), var(--gold)) 0 var(--eave-h) / 100% 1.5px no-repeat,
    linear-gradient(rgb(0 0 0 / 0.45), transparent) 0 calc(var(--eave-h) + 1.5px) / 100% calc(var(--eave-h) * 1.4) no-repeat,
    linear-gradient(90deg, #a57d2e, #f7e3a8, #b98a2e) 0 calc(100% - var(--fret-h)) / 100% 2px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M0 10h5v-5h5v10h5v-5h5' fill='none' stroke='%23f3c66b' stroke-opacity='.45' stroke-width='1.5'/%3E%3C/svg%3E") 0 100% / var(--fret-h) var(--fret-h) repeat-x,
    linear-gradient(#3a2416, #3a2416) 0 100% / 100% var(--fret-h) no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='8'%3E%3Cpath d='M0 0H16M0 4H16M0 0V4M8 4V8' stroke='%23000' stroke-opacity='.18' stroke-width='.8'/%3E%3C/svg%3E") 0 0 / 16px 8px,
    radial-gradient(circle 80vmax at 50% 0, #454c62, #2f3446 45%, #171a26);
  border-top: 0;
  border-bottom: 2px solid var(--gold);
}

/* The challenge's frame (and the letter's): the dado's rosewood key-fret band all around, one unbroken line: the fret
   running along the top and bottom bands and down the two sides, and in each corner a square of wood with a stepped
   key turn taking the line from one side round to the next (laid over the bands' ends). Every fret starts and ends
   on the band's middle, and "round" stretches them a hair so a whole number fits each side, so the line always meets
   the corners there. Each band is one fret tall: the frame's padding, wider than other frames' so the fret reads at
   its size (the challenges' widths count it: see --challenge-width-desktop and, on a phone, --challenge-width) */
body[data-area="challenges"] .frame {
  --fret: 18px;
  padding: var(--fret);
  --fret-along: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M0 10h5v-5h5v10h5v-5h5' fill='none' stroke='%23f3c66b' stroke-opacity='.45' stroke-width='1.5'/%3E%3C/svg%3E");
  --fret-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 0v5h-5v5h10v5h-5v5' fill='none' stroke='%23f3c66b' stroke-opacity='.45' stroke-width='1.5'/%3E%3C/svg%3E");
  /* the corners: top left (from the right side's middle round to the bottom's), top right, bottom left, bottom right */
  --fret-corner-tl: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect width='20' height='20' fill='%233a2416'/%3E%3Cpath d='M20 10H15V5H5V15H10V20' fill='none' stroke='%23f3c66b' stroke-opacity='.45' stroke-width='1.5'/%3E%3C/svg%3E");
  --fret-corner-tr: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect width='20' height='20' fill='%233a2416'/%3E%3Cpath d='M0 10H5V5H15V15H10V20' fill='none' stroke='%23f3c66b' stroke-opacity='.45' stroke-width='1.5'/%3E%3C/svg%3E");
  --fret-corner-bl: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect width='20' height='20' fill='%233a2416'/%3E%3Cpath d='M20 10H15V15H5V5H10V0' fill='none' stroke='%23f3c66b' stroke-opacity='.45' stroke-width='1.5'/%3E%3C/svg%3E");
  --fret-corner-br: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect width='20' height='20' fill='%233a2416'/%3E%3Cpath d='M0 10H5V15H15V5H10V0' fill='none' stroke='%23f3c66b' stroke-opacity='.45' stroke-width='1.5'/%3E%3C/svg%3E");
  background:
    var(--fret-corner-tl) 0 0 / var(--fret) var(--fret) no-repeat,
    var(--fret-corner-tr) 100% 0 / var(--fret) var(--fret) no-repeat,
    var(--fret-corner-bl) 0 100% / var(--fret) var(--fret) no-repeat,
    var(--fret-corner-br) 100% 100% / var(--fret) var(--fret) no-repeat,
    var(--fret-along) 0 0 / var(--fret) var(--fret) round no-repeat,
    var(--fret-along) 0 100% / var(--fret) var(--fret) round no-repeat,
    var(--fret-down) 0 0 / var(--fret) var(--fret) no-repeat round,
    var(--fret-down) 100% 0 / var(--fret) var(--fret) no-repeat round,
    #3a2416;
}
@media (max-width: 700px) {
  body[data-area="challenges"] .mission.frame { --fret: 10px; padding: var(--fret); }
}

/* the columns, from the ceiling down to the floor: a red lacquer shaft lit from the front so it looks round, carved
   with three grooves, a gold capital with a collar under it and a gold base (the layers, from the top one: capital,
   collar, base, grooves, shaft; the capital and base are half again as wide as the shaft) */
body[data-area="challenges"] .columns {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
body[data-area="challenges"] .columns::before,
body[data-area="challenges"] .columns::after {
  --cap: clamp(6px, calc(var(--column-w) * 0.5), 14px);
  content: "";
  position: absolute;
  top: var(--ceiling-h);
  bottom: calc(100% - var(--floor-line));
  left: calc(var(--column-x) - var(--column-w) * 0.25);
  width: calc(var(--column-w) * 1.5);
  background:
    linear-gradient(90deg, #a57d2e, #f7e3a8, #b98a2e) 0 0 / 100% var(--cap) no-repeat,
    linear-gradient(#b98a2e, #b98a2e) 50% var(--cap) / 82.7% calc(var(--cap) * 0.4) no-repeat,
    linear-gradient(90deg, #a57d2e, #f7e3a8, #b98a2e) 0 100% / 100% var(--cap) no-repeat,
    linear-gradient(90deg, transparent calc(25% - 1px), rgb(0 0 0 / 0.28) 0 calc(25% + 1px), transparent 0 calc(50% - 1px),
      rgb(0 0 0 / 0.28) 0 calc(50% + 1px), transparent 0 calc(75% - 1px), rgb(0 0 0 / 0.28) 0 calc(75% + 1px), transparent 0) 50% 0 / 66.67% 100% no-repeat,
    linear-gradient(90deg, #3e120c, #7a2a20 40%, #94382a 52%, #6e241a 72%, #34100a) 50% 0 / 66.67% 100% no-repeat;
  filter: drop-shadow(0 10px 16px rgb(0 0 0 / 0.45));
}
body[data-area="challenges"] .columns::after {
  left: auto;
  right: calc(var(--column-x) - var(--column-w) * 0.25);
}

/* the floor: granite slabs, and the hall's red rug running from the wall to the front (from the top layer: the shadow
   at the wall's foot, the key fret down the rug's two borders, the gold edges, the dark red borders and the navy line
   inside them, the rug's field, the slabs' joints, the granite). The plane is tilted by the base .flooring rule */
body[data-area="challenges"] .flooring {
  --fret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M3 2H17V17H7V8H12V12' fill='none' stroke='%23d9a441' stroke-width='1.6' stroke-linejoin='round'/%3E%3C/svg%3E");
  background:
    linear-gradient(rgb(0 0 0 / 0.55), transparent 55%),
    var(--fret) calc(50% - 157px) 0 / 18px 18px repeat-y,
    var(--fret) calc(50% + 157px) 0 / 18px 18px repeat-y,
    linear-gradient(90deg,
      transparent calc(50% - 170px), #d9a441 0 calc(50% - 168px), #5e120e 0 calc(50% - 146px),
      #d9a441 0 calc(50% - 144.5px), transparent 0 calc(50% - 141.5px), #1c3366 0 calc(50% - 140.3px),
      transparent 0 calc(50% + 140.3px), #1c3366 0 calc(50% + 141.5px), transparent 0 calc(50% + 144.5px),
      #d9a441 0 calc(50% + 146px), #5e120e 0 calc(50% + 168px), #d9a441 0 calc(50% + 170px), transparent 0),
    linear-gradient(#8e1a14, #ad2a1f) 50% 0 / 289px 100% no-repeat,
    repeating-linear-gradient(90deg, #4a4658 0 1.4px, transparent 1.4px 110px) 50% 0,
    repeating-linear-gradient(#4a4658 0 1.4px, transparent 1.4px 110px),
    linear-gradient(#2a2833, #3a3646);
}

/* the floor's title in the hall's gold: the shine passing slowly, the dark relief, the halo and the gold line with
   its diamond underneath (the same as .hall .floor-title) */
body[data-area="challenges"] .floor-title {
  color: transparent;
  background: linear-gradient(100deg, #b98a2e 0%, #f7e3a8 20%, #D9A441 38%, #fff6d8 50%, #D9A441 62%, #f7e3a8 80%, #b98a2e 100%);
  background-size: 250% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  text-shadow: none;
  filter: drop-shadow(0 2px 0 #6e4a12) drop-shadow(0 0 18px rgb(217 164 65 / 0.55));
  animation: title-shine 6s linear infinite;
}
body[data-area="challenges"] .floor-title::after {
  content: "";
  display: block;
  width: min(60%, 260px);
  height: 14px;
  margin: 0.25em auto 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M6 0L12 6L6 12L0 6Z' fill='%23f3d58a'/%3E%3Cpath d='M6 3.2L8.8 6L6 8.8L3.2 6Z' fill='%238e1a14'/%3E%3C/svg%3E") 50% 50% / 14px 14px no-repeat,
    linear-gradient(90deg, transparent, var(--gold) 30%, #fff1c2 50%, var(--gold) 70%, transparent) 50% 50% / 100% 2px no-repeat;
}
@media (prefers-reduced-motion: reduce) {
  body[data-area="challenges"] .floor-title { animation: none; background-position: 50% 0; }
}
/* the hall's shadow strip behind the title (see .title-strip): the wrapper takes the title's size, and the strip ends
   0.22em into the gap above this smaller line (14px, with its 0.25em gap) */
body[data-area="challenges"] .title-strip { font-size: clamp(1.6rem, min(4.2vw, 6vh), 3.2rem); }
body[data-area="challenges"] .title-strip::before { inset: -0.22em -16% calc(0.03em + 14px); }
body[data-area="challenges"] .floor-hint { text-shadow: 0 1px 6px #000; }

/* On a computer, the stairs keep their place, whatever the challenge's size: the panel between them is never wider
   than the room they leave (the row's width, the two stairs, 2/3 of their height each, and the row's two 2vw gaps),
   and shrinks into it, its title wrapping, so it can never push them out behind the columns */
@media (min-width: 701px) {
  body[data-area="challenges"] .challenge-panel {
    min-width: 0;
    max-width: calc(100vw - 2 * var(--row-inset) - 2 * min(28svh, 16vw) - 4vw);
  }
  body[data-area="challenges"] :is(.stairs, .stairs-down) { flex-shrink: 0; }
  body[data-area="challenges"] .floor-sign { max-width: min(620px, 64vw, 100%); }
  /* The title, the hint and the challenge (with the test button hanging under it) as one block, centered on the
     middle of the screen: the row runs from under the frieze down to as far from the screen's bottom (and not only
     up from the floor line), the panel in its middle; the stairs keep standing on the floor line (78% of the screen,
     22% above its bottom, so that much less the row's own margin there: their steps, 39% of the drawing, go on over
     the floor) */
  body[data-area="challenges"] .floor-row {
    --row-edge: calc(var(--ceiling-h) + var(--frieze-h) + 12px);
    top: var(--row-edge);
    bottom: var(--row-edge);
    align-items: center;
  }
  body[data-area="challenges"] :is(.stairs, .stairs-down) {
    align-self: flex-end;
    margin-bottom: calc(22svh - var(--row-edge) - var(--height) * 0.39);
  }
  /* (the bottom padding: the test button hangs half its height under the frame, about 12px) */
  body[data-area="challenges"] .challenge-panel { margin-bottom: 0; padding-bottom: 12px; }
}

/* the restart button in the room's woods, instead of the night's navy */
body[data-area="challenges"] .restart-challenge { background: #2a160e; }
/* on a phone, the open challenge floats over a warm dark veil, and its close button is dark wood */
@media (max-width: 700px) {
  /* the floor map's row of dots sits up in the ceiling, near its top (3.5% of the screen) */
  body[data-area="challenges"] .floor-map { top: 3.5svh; translate: -50% -50%; }
  /* the floor lower on a phone (at 84% of the screen and not 80%), bringing the stairs down with it */
  body[data-area="challenges"] .tower { --floor-line: 84%; }
  /* the title and the challenge, centered in the free space from under the frieze (the map is up in the ceiling) down
     to the top of the stairs' signs: the floor is now 16% of the screen, the stairs' arch rises 61% of their height
     and the signs, about 56px above (the same as the phone rules for body[data-floor], with the lower floor) */
  body[data-area="challenges"]:not(.challenge-open) .challenge-panel {
    bottom: calc(16svh + var(--stairs-height) * 0.61 + 56px);
    padding-top: calc(var(--ceiling-h) + var(--frieze-h) + 14px);
  }
  body[data-area="challenges"].challenge-open .challenge-panel { background: rgb(18 8 5 / 0.85); }
  body[data-area="challenges"].challenge-open .close-challenge { background: rgb(42 22 14 / 0.75); }
}

/* ---------- Balcony: the top floor (sky, Istanbul, Bosphorus, parapet and the cake) ---------- */

.balcony {
  --horizon: 66%;  /* where the sky meets the city */
  --parapet: 24%;  /* the parapet's height, from the floor */
  --eave: clamp(34px, 7vh, 64px);
  --candle-light: 1;
  position: relative;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  background: linear-gradient(#040923 0%, #0a1645 32%, #1b2966 50%, #3e3670 57%, #6b4672 var(--horizon), #0b1640 var(--horizon));
}

/* Sky */
.balcony-sky { position: absolute; top: 0; left: 0; right: 0; height: var(--horizon); }
.star {
  position: absolute;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 4px rgb(255 255 255 / 0.8);
  animation: twinkle 3.5s ease-in-out infinite alternate;
}
@keyframes twinkle {
  from { opacity: 0.2; }
  to { opacity: 1; }
}
.shooting-star {
  position: absolute;
  top: 10%;
  left: 18%;
  width: 150px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgb(255 255 255 / 0), #fff);
  opacity: 0;
  animation: shooting 11s linear 3s infinite;
}
@keyframes shooting {
  0%, 88% { opacity: 0; transform: rotate(16deg) translateX(0); }
  90% { opacity: 1; }
  97%, 100% { opacity: 0; transform: rotate(16deg) translateX(440px); }
}
.balcony-moon {
  position: absolute;
  top: 12%;
  right: 12%;
  width: clamp(70px, 12vh, 140px);
  filter: drop-shadow(0 0 30px rgb(246 231 190 / 0.55));
}

/* Fireworks: over the sky, in front of the city and the mountain, behind the balcony */
.fireworks { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

/* Guangzhou and the Pearl River */
.balcony-city {
  position: absolute;
  left: 0;
  bottom: calc(100% - var(--horizon));
  width: 100%;
  /* Tall enough that the buildings grow with the screen's width too (the drawing is 5× wider than tall) */
  height: clamp(150px, max(30vh, 25vw), 480px);
  /* On wide screens "slice" scales the drawing to the full width and its top would be cropped flat
     (mountain peaks, the Canton Tower's antenna): let it rise past the box into the sky instead */
  overflow: visible;
}
/* The city stays dark until the fireworks (.celebrating, js/cake.js); then each light comes on at its own --on delay.
   Blowing the candles out again ("Light the candles again") turns the city off again. */
.balcony-city .city-light { opacity: 0; transition: opacity 0.8s ease var(--on, 0s); }
.celebrating .balcony-city .city-light { opacity: 1; }
/* Before the fireworks the city is asleep, not dead: a few windows (js/cake.js) lit dimly, each switching
   on and off on its own rhythm, and the bridge's cables glowing a dim red and orange (its arch and deck stay dark) */
.balcony:not(.celebrating) .balcony-city .night-window {
  animation: night-window var(--blink) steps(1) var(--blink-delay) infinite;
}
/* on for most of each cycle, then off for a while */
@keyframes night-window {
  0%, 100% { opacity: 0.45; }
  60% { opacity: 0; }
  78% { opacity: 0.35; }
}
.balcony:not(.celebrating) .balcony-city .night-cable {
  opacity: 0.4;
  animation: night-cable 8s ease-in-out infinite alternate;
}
@keyframes night-cable {
  from { stroke: #d2402e; }
  to { stroke: #f08a3a; }
}
/* steps(3): the little windows change in small jumps, without redrawing the city (with its glow) every frame.
   Only once lit: the flicker animates opacity, which would otherwise show the windows while the city is dark */
.celebrating .window-flicker { animation: flicker 3s steps(3) var(--on, 0s) infinite alternate; }
/* Searchlights: each beam sweeps between its own two angles (--from, --to), pivoting on its lamp (cake.html) */
.searchlight { animation: beam-sweep 7s ease-in-out infinite alternate; }
@keyframes beam-sweep {
  from { transform: rotate(var(--from)); }
  to { transform: rotate(var(--to)); }
}
@media (prefers-reduced-motion: reduce) {
  .searchlight { animation-play-state: paused; }
}
.circle-lights { filter: drop-shadow(0 0 3px #ffcf7a); }
.shore-lights { stroke: #ffcf7a; filter: drop-shadow(0 0 2px #ffcf7a); }
.bridge-lights { stroke: #ffcf7a; animation: bridge-colors 12s linear infinite; }
.deck-lights { animation-delay: -3s; }
@keyframes bridge-colors {
  0%, 100% { stroke: #ffcf7a; }
  25% { stroke: #8fdcdf; }
  50% { stroke: #c18af0; }
  75% { stroke: #ff7a6b; }
}

.water {
  position: absolute;
  top: var(--horizon);
  left: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;
  background: linear-gradient(#0e1a4a, #060c2a 60%);
}
/* Still water: just a soft glow from the moon, no streaks or movement */
.moon-reflection {
  position: absolute;
  top: 0;
  right: calc(12% + clamp(35px, 6vh, 70px) - 70px);
  width: 140px;
  height: 80%;
  background: radial-gradient(ellipse 50% 100% at 50% 0, rgb(246 231 190 / 0.22), transparent 70%);
}
/* Boats: each with its own height on the water, size, speed and direction (set in the HTML) */
.boat {
  position: absolute;
  top: var(--top);
  left: 0;
  width: var(--width);
  filter: drop-shadow(0 0 6px rgb(255 207 122 / 0.45));
  animation: cross var(--duration) linear var(--delay) infinite;
}
.boat svg { display: block; width: 100%; }
.boat.leftward { animation-name: cross-left; }
.boat.leftward svg { transform: scaleX(-1); }
@keyframes cross {
  from { transform: translateX(-120%); }
  to { transform: translateX(110vw); }
}
@keyframes cross-left {
  from { transform: translateX(110vw); }
  to { transform: translateX(-120%); }
}

/* The balcony: eave, lanterns and marble columns framing the view */
.eave {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  width: 100%;
  height: var(--eave);
  filter: drop-shadow(0 8px 12px rgb(0 0 0 / 0.5));
}
.balcony .lanterns {
  position: absolute;
  top: calc(var(--eave) - 12px);
  left: 0;
  right: 0;
  z-index: 3;
  max-width: none;
  padding: 0 7%;
  justify-content: space-between;
}
.balcony .lantern:nth-child(2),
.balcony .lantern:nth-child(3),
.balcony .lantern:nth-child(4) { visibility: hidden; }
/* the two lanterns left on the right side, bigger */
.balcony .lantern:nth-child(n + 5) { width: calc(clamp(30px, 4.5vw, 50px) * 1.35); }
/* The eave's lower edge is scalloped, so its wood reaches different depths: the cords go well up into it
   (the eave is in front of the lanterns, so the extra length stays hidden behind the wood) */
.balcony .lantern::before { top: calc(-1 * var(--drop, 0px) - var(--eave) * 0.6); }

/* The rooms' brass sign (see .back up above), here hanging from the eave instead of the ceiling.
   It sits one layer below the eave (z-index 2 < 3), and the chains go up to the top of the screen:
   their tips disappear behind the wood, so they look attached to it whatever the eave's height */
.balcony .back {
  top: calc(var(--eave) + 30px);
  z-index: 2;
  transform-origin: 50% calc(-30px - var(--eave) / 2); /* sways from the wood */
}
.balcony .back::before,
.balcony .back::after { height: calc(var(--eave) + 30px); }
/* The sign takes the first lantern's place */
.balcony .lantern:first-child { visibility: hidden; }
/* The lanterns hang in front of the whole scene (columns, title, cake, jars: up to 5), below the letter and its
   buttons (7+). The eave goes one layer above them (7), so their cords still disappear into the wood.
   (The lanterns near the sign are hidden, so none covers it) */
.balcony .lanterns { z-index: 6; }
.balcony .eave { z-index: 7; }
/* On screens up to 1000px the sign already reaches the second lantern: it disappears too */
@media (max-width: 1000px) {
  .balcony .lantern:nth-child(2) { visibility: hidden; }
}

.balcony-column {
  position: absolute;
  top: 0;
  bottom: var(--parapet);
  z-index: 2;
  width: clamp(26px, 3.4vw, 56px);
  /* red lacquered columns, like the palace tower's, lit from the front */
  background: linear-gradient(90deg, #3e120c, #7a2a20 40%, #94382a 52%, #6e241a 72%, #34100a);
  box-shadow: 0 0 30px rgb(0 0 0 / 0.5);
}
.column-left { left: 0; }
.column-right { right: 0; }

/* Title: "Make a wish!" and then the happy birthday, shining in gold */
/* Title in the middle of the sky: centered between the eave (above) and the city (below).
   The middle is taken between the eave and half of the city's silhouette (the city goes up to the horizon and
   is clamp(120px, 22vh, 240px) tall): that way the text sits in the center of the open sky without
   touching the minarets. The -50% translate centers the title block on that point. */
.balcony-title {
  position: absolute;
  top: calc((var(--eave) + var(--horizon) - clamp(120px, 22vh, 240px) / 2) / 2);
  left: 50%;
  z-index: 4;
  width: min(90vw, 780px);
  translate: -50% -50%;
  text-align: center;
}
.cake-title {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: clamp(2rem, min(6vw, 8vh), 4.4rem);
  line-height: 1.05;
  color: #fff6e0;
  text-shadow: 0 0 26px rgb(217 164 65 / 0.55), 0 2px 0 rgb(0 0 0 / 0.35);
}
/* "İyi ki doğdun": golden letters like the hall's title (several tones, a shine passing),
   dark relief underneath, a golden halo and a gold ornament underneath (thin lines, a diamond and dots) */
.celebrating .cake-title {
  color: transparent;
  text-shadow: none;
  background: linear-gradient(100deg, #b98a2e 0%, #f7e3a8 20%, #D9A441 38%, #fff6d8 50%, #D9A441 62%, #f7e3a8 80%, #b98a2e 100%) 0 0 / 250% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  filter: drop-shadow(0 2px 0 #6e4a12) drop-shadow(0 0 18px rgb(217 164 65 / 0.6));
  animation: title-emerge 0.9s cubic-bezier(0.2, 0.8, 0.3, 1.2), title-shimmer 3.5s linear infinite;
}
.celebrating .cake-title::after {
  content: "";
  display: block;
  width: min(80%, 440px);
  height: clamp(12px, 1.8vh, 18px);
  margin: 0.25em auto 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 18'%3E%3Cdefs%3E%3ClinearGradient id='a' gradientUnits='userSpaceOnUse' x1='10' x2='176'%3E%3Cstop offset='0' stop-color='%23D9A441' stop-opacity='0'/%3E%3Cstop offset='1' stop-color='%23f7e3a8'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' gradientUnits='userSpaceOnUse' x1='224' x2='390'%3E%3Cstop offset='0' stop-color='%23f7e3a8'/%3E%3Cstop offset='1' stop-color='%23D9A441' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M10 9H176' stroke='url(%23a)' stroke-width='2'/%3E%3Cpath d='M224 9H390' stroke='url(%23b)' stroke-width='2'/%3E%3Ccircle cx='184' cy='9' r='2.5' fill='%23D9A441'/%3E%3Ccircle cx='216' cy='9' r='2.5' fill='%23D9A441'/%3E%3Cpath d='M200 1L208 9L200 17L192 9Z' fill='%23f7e3a8'/%3E%3Cpath d='M200 5L204 9L200 13L196 9Z' fill='%23b98a2e'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
}
@keyframes title-emerge {
  from { opacity: 0; transform: scale(0.8); }
}
@keyframes title-shimmer {
  to { background-position: -250% 0; }
}
.cake-hint {
  margin: 10px 0 0;
  font-size: clamp(1rem, 2.4vh, 1.2rem);
  font-style: italic;
  color: var(--moonlight);
}

/* Marble parapet with balusters (the water shows between them) */
.parapet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  height: var(--parapet);
}
.parapet-top {
  position: relative;
  flex: none;
  height: clamp(16px, 2.8vh, 26px);
  background: linear-gradient(#fbf6ec, #d8cfbd 60%, #a89e8a);
  border-top: 3px solid var(--gold);
  box-shadow: 0 8px 14px rgb(0 0 0 / 0.45);
}
/* the candlelight hitting the marble */
.parapet-top::after {
  content: "";
  position: absolute;
  inset: -3px 0 0;
  background: radial-gradient(ellipse 22% 260% at 50% 0, rgb(255 190 100 / 0.55), transparent 70%);
  opacity: var(--candle-light);
  transition: opacity 0.6s;
}
/* A Chinese marble balustrade: square posts, and between them panels with a rounded opening (the water shows through) */
.balusters {
  flex: 1;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 100' preserveAspectRatio='none'%3E%3Crect x='0' width='10' height='100' fill='%23d6cdbb' stroke='%238d8577'/%3E%3Crect x='3' width='4' height='100' fill='%23fff' opacity='.25'/%3E%3Cpath d='M10 18H60V92H10Z M18 30Q18 26 22 26H48Q52 26 52 30V78Q52 82 48 82H22Q18 82 18 78Z' fill='%23cfc6b4' fill-rule='evenodd' stroke='%238d8577'/%3E%3Cpath d='M10 18H60' stroke='%23a89e8a' stroke-width='3'/%3E%3C/svg%3E") repeat-x 0 0 / clamp(48px, 5.4vw, 72px) 100%;
}
.parapet-base {
  flex: none;
  height: 26%;
  background: linear-gradient(#b3a994, #6d6557);
  border-top: 2px solid #8d8577;
}

/* The jars stand on small marble posts, 2/3 as tall as the cake's pedestal, in front of the balustrade */
.balcony {
  /* One smooth size for every screen (no jump between phone and computer): about 72px on an upright phone
     (the width term, as big as fits next to the cake), about a tenth of the screen's height on a computer */
  --vase-width: clamp(56px, min(10vh, 19.5vw), 110px);
  --stand-height: calc(var(--pedestal-height) * 2 / 3);
  /* half the height of the post's top face: the same flatness as the cake's plate and pedestal
     (the top is 124% of the jar's width; its ellipse is ~6.8% as tall as wide) */
  --stand-r: calc(var(--vase-width) * 0.042);
}
/* The jar lives inside its post's box, centered on it: its foot rests on the middle of the post's
   elliptical top face */
.balcony-vase {
  position: absolute;
  left: 50%;
  bottom: calc(100% - var(--stand-r) * 1.1 - 4px);
  z-index: 3;
  width: var(--vase-width);
  translate: -50% 0;
}
/* The post: a smaller version of the cake's pedestal, made of the same pieces (.pedestal-top, .pedestal-shaft):
   a round slab seen slightly from above and a fluted shaft tucked under it, with the slab sticking out
   past the jar on both sides. In front of the cake (z-index 5), so the kapok branches never go behind it */
.vase-stand {
  position: absolute;
  bottom: 0;
  z-index: 5;
  pointer-events: none; /* in front of the cake, but taps go through to the candles */
  display: flex;
  flex-direction: column;
  align-items: center;
  width: var(--vase-width);
  height: var(--stand-height);
  filter: drop-shadow(0 8px 12px rgb(0 0 0 / 0.45));
  --r: var(--stand-r);
  --side: linear-gradient(90deg, #7d7566, #efe9dc 35%, #cfc6b4 62%, #6d6557);
}
.vase-stand .pedestal-top {
  width: 124%;
  height: calc(var(--r) * 2 + clamp(6px, 1.2vh, 11px));
}
/* the shaft stands straight on the floor: no curved bottom */
.vase-stand .pedestal-shaft {
  width: 86%;
  margin-bottom: 0;
  border-radius: 0;
}
.vase-left { left: 18%; }
.vase-right { right: 18%; }

/* The cake's width, which the pedestal under it also follows, and the pedestal's height:
   a little above the parapet (the cake goes up with it, without reaching the title's text) */
.balcony {
  --cake-width: min(42svh, 60vw, 440px);
  --pedestal-height: calc(var(--parapet) - 6px + clamp(20px, 4vh, 48px));
}

/* Marble pedestal: a wide top with a golden edge (the cake sits on it), a thin fluted shaft
   like the columns and a base over the parapet's lower band */
.cake-pedestal {
  position: absolute;
  bottom: 0;
  left: 50%;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* wider than the cake's golden plate (97% of the cake), so the plate rests inside the top's edge */
  width: calc(var(--cake-width) * 1.08);
  height: var(--pedestal-height);
  translate: -50% 0;
  filter: drop-shadow(0 10px 18px rgb(0 0 0 / 0.5));
  /* Seen from slightly above, like the cake's plate: every round face is an ellipse as flat as the plate's
     (height ≈ 7% of the width). --r = half the height of the top's ellipse; the base and shaft scale it to their width */
  --r: calc(var(--cake-width) * 0.037);
  --side: linear-gradient(90deg, #7d7566, #efe9dc 35%, #cfc6b4 62%, #6d6557); /* a cylinder's shading, as on the shaft */
}
/* The top: a round marble slab. The element is its side (a cylinder: half-ellipses at the top and bottom),
   ::before its top face with the golden rim, ::after the candlelight on that face */
.pedestal-top {
  position: relative;
  z-index: 2;
  flex: none;
  width: 100%;
  height: calc(var(--r) * 2 + clamp(10px, 2vh, 18px));
  background: var(--side);
  border-radius: 50% / var(--r);
  box-shadow: inset 0 -2px 0 #b98a2e, 0 6px 10px rgb(0 0 0 / 0.4);
}
.pedestal-top::before,
.pedestal-top::after {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: calc(var(--r) * 2);
  border-radius: 50%;
}
.pedestal-top::before {
  background: linear-gradient(#fdf9f0, #ddd4c1);
  border: 2px solid var(--gold);
}
/* the candlelight hitting the top, like on the parapet */
.pedestal-top::after {
  background: radial-gradient(ellipse 60% 120% at 50% 30%, rgb(255 190 100 / 0.55), transparent 70%);
  opacity: var(--candle-light);
  transition: opacity 0.6s;
}
/* The shaft: tucked under the top's curved underside, its own curved bottom resting on the base's top face */
.pedestal-shaft {
  position: relative;
  z-index: 1;
  flex: 1;
  width: 41.7%; /* same size as before the top got wider (45% of the cake) */
  margin: calc(var(--r) * -1) 0;
  border-radius: 0 0 50% 50% / 0 0 calc(var(--r) * 0.42) calc(var(--r) * 0.42);
  background:
    linear-gradient(transparent 18%, var(--gold) 18% calc(18% + 2px), transparent 0), /* golden line up high */
    repeating-linear-gradient(90deg, rgb(0 0 0 / 0.08) 0 2px, transparent 2px 12px),
    var(--side);
  box-shadow: inset 0 8px 10px -6px rgb(0 0 0 / 0.5);
}
/* The base: another round slab, its top face an ellipse with a golden rim; the bottom stands on the floor,
   at the screen's edge */
.pedestal-base {
  position: relative;
  flex: none;
  width: 75%; /* same size as before the top got wider (81% of the cake) */
  height: calc(var(--parapet) * 0.26);
  background: var(--side);
  border-radius: 50% 50% 0 0 / calc(var(--r) * 0.75) calc(var(--r) * 0.75) 0 0;
}
.pedestal-base::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: calc(var(--r) * 1.5);
  border-radius: 50%;
  background: linear-gradient(#efe8da, #c9bfab);
  border: 2px solid var(--gold);
}

/* The cake on top of the pedestal; the candles' size follows the cake's (cqw).
   The plate is an ellipse seen slightly from above, centered on the marble top's elliptical face:
   the plate's center is 3.3% of the cake's width above the drawing's bottom, the face's center
   3.7% below the pedestal's top (its --r) */
.balcony-cake {
  position: absolute;
  bottom: calc(var(--pedestal-height) - var(--cake-width) * 0.07);
  left: 50%;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: var(--cake-width);
  translate: -50% 0;
  container-type: inline-size;
}
.cake-drawing { position: relative; display: block; width: 100%; filter: drop-shadow(0 18px 20px rgb(0 0 0 / 0.5)); }
.candle-glow {
  position: absolute;
  top: 0;
  left: 50%;
  width: 150%;
  aspect-ratio: 1;
  translate: -50% -38%;
  background: radial-gradient(circle, rgb(255 190 100 / 0.4), transparent 58%);
  opacity: var(--candle-light);
  transition: opacity 0.6s;
  pointer-events: none;
}

.candles {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  gap: 2.2cqw;
  height: 22cqw;
  /* The drawing's top tier starts at y=40 out of 300 wide (≈13.3cqw below the top):
     the candles go down a little past that so they stick into the icing */
  margin-bottom: -14.6cqw;
}
.candle {
  all: unset;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  width: 5.4cqw;
  height: 22cqw;
  cursor: pointer;
}
.candle:focus-visible { outline: 2px dashed var(--gold); outline-offset: 2px; }
.wax {
  width: 3.2cqw;
  height: 12cqw;
  border-radius: 1cqw 1cqw 0.5cqw 0.5cqw;
  background: repeating-linear-gradient(-35deg, #F4F6F2 0 1.2cqw, #d0473a 1.2cqw 2.4cqw);
  box-shadow: inset -0.7cqw 0 0 rgb(0 0 0 / 0.15);
}
/* Candles in pairs (her 22): a wider gap after every second candle */
.candle:nth-child(2n):not(:last-child) { margin-right: 4cqw; }
.wick { width: 0.5cqw; height: 1.4cqw; background: #3a2a1a; }
.flame {
  width: 3.6cqw;
  height: 6.4cqw;
  margin-bottom: -0.3cqw;
  border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
  background: radial-gradient(ellipse at 50% 70%, #fff7b0 0 25%, #ffb52e 50%, #ff5a1f 80%, transparent 82%);
  filter: drop-shadow(0 0 1.6cqw #ffb52e);
  transform-origin: 50% 90%;
  animation: flutter 0.15s var(--delay, 0s) infinite alternate;
  transition: opacity 0.3s, scale 0.3s;
}
.candle:hover .flame { scale: 1.2; }
.candle.blown-out { cursor: default; }
.candle.blown-out .flame { opacity: 0; scale: 0.2; animation: none; }
.candle.blown-out::after {
  content: "";
  position: absolute;
  top: 4cqw;
  left: 50%;
  width: 3cqw;
  height: 3cqw;
  border-radius: 50%;
  background: rgb(216 216 216 / 0.7);
  animation: smoke 1.4s ease-out forwards;
}
@keyframes flutter {
  from { transform: scale(1) rotate(-3deg); }
  to { transform: scale(1.08, 0.94) rotate(3deg); }
}
@keyframes smoke {
  from { transform: translate(-50%, 10px) scale(0.5); opacity: 0.9; }
  to { transform: translate(-50%, -40px) scale(3); opacity: 0; }
}

/* Envelope that flies in over the river before the letter */
.balcony-envelope {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 8;
  width: min(440px, 86vw);
  padding: 0;
  font: inherit;
  color: var(--enamel);
  background: none;
  border: 0;
  cursor: pointer;
  translate: -50% -50%;
  animation: envelope-fly 2.2s cubic-bezier(0.3, 0.7, 0.4, 1) both;
}
.balcony-envelope svg {
  display: block;
  width: 100%;
  filter: drop-shadow(0 10px 18px rgb(0 0 0 / 0.5)) drop-shadow(0 0 22px rgb(217 164 65 / 0.35));
  animation: envelope-float 3s ease-in-out 2.2s infinite;
}
/* swoops in from the top left, fluttering like paper, and settles in the middle */
@keyframes envelope-fly {
  0% { opacity: 0; transform: translate(-70vw, -60vh) rotate(-35deg) scale(0.4); }
  10% { opacity: 1; }
  40% { transform: translate(-20vw, -12vh) rotate(18deg) scale(0.7); }
  65% { transform: translate(8vw, 4vh) rotate(-10deg) scale(0.9); }
  85% { transform: translate(-2vw, -1vh) rotate(4deg) scale(1.02); }
  100% { opacity: 1; transform: none; }
}
@keyframes envelope-float {
  50% { transform: translateY(-6px) rotate(-1.5deg); }
}
/* hangs below the envelope, outside the centering, so the envelope itself sits in the middle */
.envelope-hint {
  position: absolute;
  top: calc(100% + 16px);
  left: 50%;
  translate: -50% 0;
  white-space: nowrap;
  padding: 8px 18px;
  font-weight: 700;
  color: var(--ink);
  background: var(--enamel);
  border: 2px solid var(--gold);
  border-radius: 10px;
  box-shadow: 0 4px 14px rgb(0 0 0 / 0.5);
  opacity: 0;
  animation: envelope-hint 0.6s ease 2.2s forwards;
}
@keyframes envelope-hint {
  to { opacity: 1; }
}
/* Opening (after the tap), about 2 s, timed with js/cake.js:
   wiggle → the seal swells and pops → the flap folds up → hearts burst out → the paper slides up → the envelope fades */
.balcony-envelope svg { overflow: visible; }
.envelope-flap,
.envelope-flap-inside,
.envelope-seal,
.envelope-hearts path { transform-box: fill-box; }
.envelope-flap,
.envelope-flap-inside { transform-origin: top; }
.envelope-flap-inside { transform: scaleY(0); }
.envelope-seal,
.envelope-hearts path { transform-origin: center; }
.envelope-hearts path { opacity: 0; }
.balcony-envelope.opening { pointer-events: none; }
.balcony-envelope.opening svg { animation: envelope-wiggle 0.4s ease-in-out, envelope-away 0.45s ease-in 1.75s forwards; }
.balcony-envelope.opening .envelope-seal { animation: seal-pop 0.35s ease-in 0.25s forwards; }
.balcony-envelope.opening .envelope-flap { animation: flap-fold 0.22s ease-in 0.5s forwards; }
.balcony-envelope.opening .envelope-flap-inside { animation: flap-unfold 0.3s ease-out 0.72s forwards; }
.balcony-envelope.opening .envelope-paper { animation: paper-rise 0.6s cubic-bezier(0.3, 1.5, 0.5, 1) 1.02s forwards; }
.balcony-envelope.opening .envelope-hearts path { animation: heart-burst 1.1s ease-out calc(0.55s + var(--i) * 0.07s) forwards; }
.balcony-envelope.opening .envelope-hint { animation: envelope-hint-out 0.2s ease forwards; }
@keyframes envelope-wiggle {
  20% { transform: rotate(-5deg) scale(1.04); }
  45% { transform: rotate(4deg) scale(1.04); }
  70% { transform: rotate(-2deg); }
}
@keyframes seal-pop {
  40% { transform: scale(1.3); }
  100% { transform: scale(0); opacity: 0; }
}
@keyframes flap-fold {
  to { transform: scaleY(0); }
}
@keyframes flap-unfold {
  from { transform: scaleY(0); }
  to { transform: scaleY(-1); }
}
@keyframes paper-rise {
  to { transform: translateY(-58px); }
}
@keyframes heart-burst {
  0% { opacity: 0; transform: translate(0, 0) scale(0.3); }
  20% { opacity: 1; }
  70% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(1.2) rotate(15deg); }
}
@keyframes envelope-away {
  to { opacity: 0; transform: scale(1.08); }
}
@keyframes envelope-hint-out {
  from { opacity: 1; }
  to { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .balcony-envelope { animation: envelope-hint 0.6s ease both; }
  .balcony-envelope svg { animation: none; }
  .envelope-hint { animation-delay: 0.6s; }
}

/* Letter with the final message, opening over the view (the gallery's letter too, .gallery-letter-card) */
.balcony-letter,
.gallery-letter-card {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 8;
  width: min(560px, 90vw);
  max-height: 88svh;
  overflow: auto;
  translate: -50% -50%;
  animation: letter-open 0.9s cubic-bezier(0.2, 0.8, 0.3, 1.1) both;
}
/* Its scrollbar, like the music shelf's: a thin gold rounded thumb on no track, without arrows (Chrome, Edge and
   Safari from the ::-webkit-scrollbar rules; Firefox from scrollbar-color, which would switch those off elsewhere) */
:is(.balcony-letter, .gallery-letter-card)::-webkit-scrollbar { width: 12px; }
:is(.balcony-letter, .gallery-letter-card)::-webkit-scrollbar-track { background: transparent; }
:is(.balcony-letter, .gallery-letter-card)::-webkit-scrollbar-thumb {
  background: var(--gold);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 6px;
}
:is(.balcony-letter, .gallery-letter-card)::-webkit-scrollbar-button { display: none; }
@supports not selector(::-webkit-scrollbar) {
  .balcony-letter,
  .gallery-letter-card {
    scrollbar-width: thin;
    scrollbar-color: var(--gold) transparent;
  }
}
@keyframes letter-open {
  from { opacity: 0; transform: translateY(40px) scale(0.9); }
}
:is(.balcony-letter, .gallery-letter-card) .frame { margin-top: 24px; }
:is(.balcony-letter, .gallery-letter-card) .enamel { position: relative; padding: 34px clamp(20px, 5vw, 44px) 24px; text-align: center; }
:is(.balcony-letter, .gallery-letter-card) h2 {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  line-height: 1.1;
  color: var(--coral);
}
/* a Chinese knot hanging over the top of the letter */
.letter-knot {
  position: absolute;
  top: -72px;
  left: 50%;
  width: 28px;
  translate: -50% 0;
  filter: drop-shadow(0 4px 6px rgb(0 0 0 / 0.35));
}
.letter-knot svg { display: block; width: 100%; }
/* Long letter: aligned left like a real letter, paragraphs separated by \n\n */
.letter-message { font-size: 1.1rem; line-height: 1.6; text-align: left; white-space: pre-line; color: var(--ink); }
.signature { margin-bottom: 0; text-align: right; font-style: italic; color: var(--ink-soft); }
/* The P.S. under a letter's signature, a little smaller and softer than the letter (\n\n separates its paragraphs) */
.letter-postscript { margin: 18px 0 0; font-size: 0.95rem; line-height: 1.6; text-align: left; white-space: pre-line; color: var(--ink-soft); }
/* the balcony letter reads as justified paragraphs */
.balcony-letter .letter-message,
.balcony-letter .letter-postscript { text-align: justify; hyphens: auto; }
/* the P.S. pointing her back to the hall's last surprise: bold, in the palace's blue, so it isn't missed */
.letter-postscript.letter-surprise { font-size: 1.05rem; font-weight: 700; color: var(--cobalt); }
/* the gallery letter's P.S. all in bold, and her usernames at its end each in its own color (js/gallery.js) */
.gallery-letter-card .letter-postscript { font-weight: 700; }
/* the thin gold line between "Here it goes:" and the names, fading out at both ends */
.admired-divider {
  height: 1px;
  margin: 6px 0 8px;
  border: 0;
  background: linear-gradient(90deg, transparent, var(--gold) 15%, var(--gold) 85%, transparent);
}
.admired-name { font-size: 1.2rem; }
/* ten clearly different hues, warm and cool in turn so no two neighbors look alike */
.admired-name:nth-of-type(1) { color: #d62828; } /* red */
.admired-name:nth-of-type(2) { color: #0077b6; } /* blue */
.admired-name:nth-of-type(3) { color: #f07800; } /* orange */
.admired-name:nth-of-type(4) { color: #2a9d4b; } /* green */
.admired-name:nth-of-type(5) { color: #e0218a; } /* hot pink */
.admired-name:nth-of-type(6) { color: #009a9a; } /* cyan */
.admired-name:nth-of-type(7) { color: #b07d00; } /* mustard */
.admired-name:nth-of-type(8) { color: #7b2cbf; } /* purple */
.admired-name:nth-of-type(9) { color: #6b3e1f; } /* brown */
.admired-name:nth-of-type(10) { color: #4d5866; } /* slate */
/* the last name (Miso): filled with all the others' colors, flowing slowly across it */
.admired-name:last-of-type {
  background: linear-gradient(90deg, #d62828, #f07800, #b07d00, #2a9d4b, #009a9a, #0077b6, #7b2cbf, #e0218a, #d62828);
  background-size: 200% auto;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: admired-rainbow 4s linear infinite;
}
@keyframes admired-rainbow { to { background-position: 200% center; } }
/* and the heart after it, beating gently */
.admired-heart {
  display: inline-block;
  font-size: 1.2rem;
  font-style: normal;
  color: var(--coral);
  animation: admired-heartbeat 1.4s ease-in-out infinite;
}
@keyframes admired-heartbeat { 0%, 60%, 100% { scale: 1; } 15%, 40% { scale: 1.25; } 28% { scale: 1.05; } }
@media (prefers-reduced-motion: reduce) {
  .admired-name:last-of-type, .admired-heart { animation: none; }
}
.letter-buttons { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 20px; }
.letter-button {
  display: inline-block;
  padding: 9px 22px;
  font: inherit;
  font-weight: 700;
  color: var(--enamel);
  text-decoration: none;
  background: var(--cobalt);
  border: 2px solid var(--gold);
  border-radius: 999px;
  cursor: pointer;
}
.letter-button:hover { background: var(--turquoise); }

.reopen-letter {
  position: absolute;
  right: 16px;
  bottom: 16px;
  z-index: 7;
  padding: 8px 18px;
  font: inherit;
  font-weight: 700;
  color: var(--ink);
  background: linear-gradient(#f1d596, var(--gold) 60%, #a57d2e);
  border: 1px solid #8a6420;
  border-radius: 999px;
  box-shadow: 0 4px 14px rgb(0 0 0 / 0.5), 0 0 18px rgb(217 164 65 / 0.5);
  cursor: pointer;
  animation: letter-beckon 2.4s ease-in-out infinite;
}
@keyframes letter-beckon {
  50% { transform: translateY(-4px); }
}

.balcony-lock {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 8;
  width: min(420px, 88vw);
  translate: -50% -50%;
}
.balcony-lock .enamel { padding: 26px; text-align: center; }
.balcony-lock .door-icon { margin: 0 auto 8px; color: var(--gold); }

/* ---------- Balcony curtains: they open after the loading, revealing the cake ---------- */

.balcony-curtains {
  position: fixed;
  inset: 0;
  z-index: 60; /* on top of the whole balcony, below the loading screen */
  overflow: hidden;
}
.balcony-curtains.open { pointer-events: none; }

/* Each panel: red velvet with pleats (light and dark stripes), a shadow at the bottom and a golden fringe.
   Each one goes 9vw past the screen (and a bit past the middle), so no movement opens a gap at the edges.
   will-change lets the browser move the panel like a finished image, without redrawing every frame. */
.balcony-curtain {
  position: absolute;
  top: 0;
  bottom: 0;
  width: calc(50% + 9vw);
  background:
    linear-gradient(to top, rgb(0 0 0 / 0.4), transparent 35%),
    repeating-linear-gradient(90deg, #4f0c09 0, #8e2319 2.5%, #c8382e 4.5%, #8e2319 6.5%, #4f0c09 9%);
  will-change: transform;
}
.balcony-curtain::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 18px;
  background:
    repeating-linear-gradient(90deg, var(--gold) 0 3px, transparent 3px 6px) bottom / 100% 12px no-repeat,
    linear-gradient(#f1d596, var(--gold)) top / 100% 6px no-repeat;
}
/* Seam in the middle: an embroidered golden band + shadow, in the background itself (lighter than box-shadow to animate).
   The panels meet exactly at the center of the screen; each one's extra 9vw stays outside, at the edges. */
/* Origin at the bottom corner: when tilting (skew), the top leads and the bottom hem trails behind */
.balcony-curtain-left {
  left: -9vw;
  transform-origin: left bottom;
  background-image:
    linear-gradient(to left, #f1d596 0, var(--gold) 5px, #8a6420 9px, rgb(0 0 0 / 0.45) 9px, transparent 50px),
    linear-gradient(to top, rgb(0 0 0 / 0.4), transparent 35%),
    repeating-linear-gradient(90deg, #4f0c09 0, #8e2319 2.5%, #c8382e 4.5%, #8e2319 6.5%, #4f0c09 9%);
}
.balcony-curtain-right {
  right: -9vw;
  transform-origin: right bottom;
  background-image:
    linear-gradient(to right, #f1d596 0, var(--gold) 5px, #8a6420 9px, rgb(0 0 0 / 0.45) 9px, transparent 50px),
    linear-gradient(to top, rgb(0 0 0 / 0.4), transparent 35%),
    repeating-linear-gradient(90deg, #4f0c09 0, #8e2319 2.5%, #c8382e 4.5%, #8e2319 6.5%, #4f0c09 9%);
}

/* While waiting for the cut, the curtains breathe gently (like heavy fabric in a breeze) */
.balcony-curtains.ready:not(.cut) .balcony-curtain-left { animation: curtain-breathe-left 3.5s ease-in-out infinite; }
/* the right one starts 1.7s later, so the two don't breathe together. The delay is positive on purpose:
   a negative one would make it start mid-breath, jumping from straight to tilted in one frame */
.balcony-curtains.ready:not(.cut) .balcony-curtain-right { animation: curtain-breathe-right 3.5s ease-in-out 1.7s infinite; }
/* (they tilt inward, one over the other, so a gap never opens at the top in the middle) */
@keyframes curtain-breathe-left { 50% { transform: skewX(-0.6deg); } }
@keyframes curtain-breathe-right { 50% { transform: skewX(0.6deg); } }
/* After the cut, each panel stays still at the tilt it was in (js/cake.js saves it in --tilt) */
.balcony-curtains.cut .balcony-curtain { transform: skewX(var(--tilt, 0deg)); }

/* The opening, slow and in stages (4.5s):
   1. the two halves shudder, 2. they move gently away from the middle,
   3. they slide outward picking up speed and gathering, 4. they brake smoothly already off screen.
   The top opens faster than the hem, like a curtain pulled by the rail up top:
   the skewX with its origin at the bottom corner pushes the top outward, more and more tilted. */
.balcony-curtains.open .balcony-curtain-left { animation: open-curtain-left 4.5s linear forwards; }
.balcony-curtains.open .balcony-curtain-right { animation: open-curtain-right 4.5s linear forwards; }
@keyframes open-curtain-left {
  0% { transform: translateX(0) scaleX(1) skewX(var(--tilt, 0deg)); animation-timing-function: ease-in-out; }
  8% { transform: translateX(0.8%) scaleX(1) skewX(-0.5deg); animation-timing-function: ease-in-out; }
  16% { transform: translateX(-0.4%) scaleX(1) skewX(3deg); animation-timing-function: ease-in; }
  40% { transform: translateX(-12%) scaleX(0.95) skewX(10deg); animation-timing-function: linear; }
  75% { transform: translateX(-58%) scaleX(0.8) skewX(14deg); animation-timing-function: ease-out; }
  100% { transform: translateX(-100%) scaleX(0.65) skewX(12deg); }
}
@keyframes open-curtain-right {
  0% { transform: translateX(0) scaleX(1) skewX(var(--tilt, 0deg)); animation-timing-function: ease-in-out; }
  8% { transform: translateX(-0.8%) scaleX(1) skewX(0.5deg); animation-timing-function: ease-in-out; }
  16% { transform: translateX(0.4%) scaleX(1) skewX(-3deg); animation-timing-function: ease-in; }
  40% { transform: translateX(12%) scaleX(0.95) skewX(-10deg); animation-timing-function: linear; }
  75% { transform: translateX(58%) scaleX(0.8) skewX(-14deg); animation-timing-function: ease-out; }
  100% { transform: translateX(100%) scaleX(0.65) skewX(-12deg); }
}

/* ----- The golden rope that ties the curtains -----
   The rope, the knot and the tassels are drawn on a canvas by js/rope.js, with real rope physics.
   The button is just the invisible touch area on top: a band the width of the screen, from the height
   where the rope is tied (56%) down to below the tassels (the rope's sag drops ≈ 3% of the width). */
.rope-canvas { position: absolute; inset: 0; z-index: 3; width: 100%; height: 100%; pointer-events: none; }
.rope {
  all: unset;
  position: absolute;
  top: calc(56% - 30px);
  left: 0;
  right: 0;
  z-index: 3;
  height: calc(4vw + 110px);
  cursor: pointer;
}
.rope:focus-visible { outline: 3px dashed var(--enamel); outline-offset: -6px; }

/* Hint in a little enamel box with a golden border (like the site's others), right above the rope */
.rope-hint {
  position: absolute;
  bottom: calc(44% + 48px); /* the rope is centered at 56% from the top, 64px tall */
  left: 50%;
  z-index: 3;
  width: max-content;
  max-width: 86vw;
  margin: 0;
  padding: 10px 22px;
  translate: -50% 0;
  font-family: var(--font-title);
  font-size: clamp(1.05rem, 3.4vw, 1.4rem);
  text-align: center;
  color: var(--ink);
  background: var(--enamel);
  border: 3px solid var(--gold);
  border-radius: 12px;
  box-shadow: 0 10px 24px rgb(0 0 0 / 0.5), inset 0 0 0 2px rgb(217 164 65 / 0.3);
  transition: opacity 0.3s ease;
}
/* the rope and the hint are already there from the start, along with the curtains; the pulse starts when the loading leaves */
.ready .rope-hint { animation: hint-drift 2.4s ease-in-out 1s infinite; }
@keyframes hint-drift { 50% { translate: -50% -6px; } }

/* Spark at the cut point (js/rope.js says where: --cut-x / --cut-y, the knot's position) */
.balcony-curtains::after {
  content: "";
  position: absolute;
  z-index: 4;
  top: var(--cut-y, 60%);
  left: var(--cut-x, 50%);
  width: 70px;
  height: 70px;
  translate: -50% -50%;
  background:
    linear-gradient(transparent 46%, #fff 46% 54%, transparent 54%),
    linear-gradient(90deg, transparent 46%, #fff 46% 54%, transparent 54%),
    radial-gradient(circle, #fff 0 12%, rgb(255 225 150 / 0.8) 20%, transparent 55%);
  border-radius: 50%;
  opacity: 0;
  scale: 0.2;
  pointer-events: none;
}
.balcony-curtains.cut::after { animation: cut-spark 0.35s ease-out; }
@keyframes cut-spark {
  30% { opacity: 1; scale: 1.1; rotate: 20deg; }
  to { opacity: 0; scale: 1.5; rotate: 45deg; }
}

.cut .rope-hint { opacity: 0; animation: none; }
.cut .rope { cursor: default; }

/* Confetti from the cut: outside the curtains, right above them */
.curtain-confetti { position: fixed; inset: 0; z-index: 61; width: 100%; height: 100%; pointer-events: none; }

/* Up to 1100px the eave's 5th lantern falls right in front of the moon: it disappears, and the moon sits
   to the left of the last lantern (7% margin + the lantern's width + a little breathing room).
   The reflection on the water follows the middle of the moon. */
@media (max-width: 1100px) {
  .balcony .lantern:nth-child(5) { visibility: hidden; }
  .balcony-moon { right: calc(7% + clamp(30px, 4.5vw, 50px) + 14px); }
  .moon-reflection { right: calc(7% + clamp(30px, 4.5vw, 50px) + 14px + clamp(35px, 6vh, 70px) - 70px); }
}
@media (max-width: 700px) {
  .balcony { --horizon: 60%; --parapet: 20%; }
  /* Upright phone: any bigger and the Canton Tower and the Guangzhou Circle (x≈405–785 of 1200)
     would no longer fit between the columns */
  .balcony-city { height: 24vh; }
  /* On phones the moon is smaller and right below the eave, away from the title */
  .balcony-moon { top: calc(var(--eave) + 12px); width: clamp(56px, 9vh, 80px); }
  .moon-reflection { right: calc(7% + clamp(30px, 4.5vw, 50px) + 14px + clamp(28px, 4.5vh, 40px) - 70px); }
  /* (64vw: with the pedestal's top at 108% of the cake, it still leaves room for the jars at the edges) */
  .balcony { --cake-width: min(40svh, 64vw); }
  /* closer to the cake's pedestal: the posts' caps stay whole on screen, and the tips of the kapok branches
     pass just in front of the end of the pedestal's top */
  .vase-left { left: 4%; }
  .vase-right { right: 4%; }
}
@media (prefers-reduced-motion: reduce) {
  .shooting-star { animation: none; opacity: 0; }
  .boat { animation-play-state: paused; }
}

@keyframes emerge {
  from { opacity: 0; transform: translateY(24px) scale(0.96); }
}

#confetti {
  position: fixed;
  inset: 0;
  z-index: 10;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ---------- The hall's surprise entrance ---------- */

body.entrance-active { overflow: hidden; }

/* During the surprise, the captions over the hall (map, title, signs, stairs label, reset)
   stay hidden; when the guests leave, they fade in smoothly */
.floor-map,
.floor-sign,
.room-sign,
.stairs-label,
.reset { transition: opacity 0.8s ease, visibility 0.8s; }
body.entrance-active :is(.floor-map, .floor-sign, .room-sign, .stairs-label, .reset) {
  opacity: 0;
  visibility: hidden;
}

/* Before the surprise: the sky fills the whole screen and the house stands on the street, in front of the city */
body.before-party .sky-text .subtitle,
body.before-party .frieze,
body.before-party .hall { display: none; }
body:not(.before-party) .entrance-house,
body:not(.before-party) .entrance-invite,
body:not(.before-party) .entrance-chinese { display: none; }

/* Everything fits on one screen: the sky is exactly the screen's height and nothing scrolls */
body.before-party .sky {
  justify-content: flex-start;
  height: 100vh;
  height: 100svh;
  min-height: 0;
}
/* A flex column so the kapok ornament can move below the lines (order), only in the entrance: the hall keeps it on top.
   max(): the title never starts higher than the moon's bottom edge plus a gap, so the two never overlap */
body.before-party .sky-text {
  flex: none;
  display: flex;
  flex-direction: column;
  padding-top: max(clamp(64px, 11vh, 110px), calc(var(--moon-top) + var(--moon-size) + 16px));
}
body.before-party .sky-text .ornament { order: 1; width: clamp(90px, 14vh, 180px); margin: clamp(10px, 2vh, 20px) auto 0; }
body.before-party .party-title { font-size: clamp(2rem, min(8vw, 7vh), 5.2rem); }
body.before-party .entrance-invite { margin-top: clamp(6px, 1.5vh, 18px); }
/* The city and the Pearl River stay in the background, at the bottom of the screen; the house scene stays in the middle */
body.before-party .city-near { position: absolute; left: 0; bottom: 52px; margin: 0; }
body.before-party .river { position: absolute; left: 0; right: 0; bottom: 0; }

/* ---------- The entrance's living sky ----------
   Loose layers (position: absolute), so none of this moves the layout of the title or the palace.
   From back to front: Milky Way → stars → meteors → moon halo → paper lanterns and seagulls
   → moon → clouds → far shore → mist → city. */
.sky-backdrop { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }

/* The Milky Way band: "to bottom right" puts the middle of the gradient exactly on the diagonal
   from the bottom-left corner to the top-right (the JS stars follow the same line) */
.milky-way {
  position: absolute;
  inset: 0 0 25% 0;
  background:
    radial-gradient(ellipse 22% 14% at 30% 48%, rgb(190 160 255 / 0.12), transparent 70%),
    radial-gradient(ellipse 18% 10% at 62% 20%, rgb(120 210 225 / 0.08), transparent 70%),
    radial-gradient(ellipse 16% 12% at 12% 66%, rgb(220 130 200 / 0.07), transparent 70%),
    linear-gradient(to bottom right, transparent 38%, rgb(160 175 255 / 0.06) 45%, rgb(215 195 255 / 0.11) 50%, rgb(160 175 255 / 0.06) 55%, transparent 62%);
  /* (no filter: blur: the gradients already fade softly, and the blur was redone every frame of the animation) */
  animation: breathe 16s ease-in-out infinite alternate;
}
@keyframes breathe {
  from { opacity: 0.65; }
  to { opacity: 1; }
}

.entrance-stars { position: absolute; inset: 0 0 25% 0; }
/* The whole group twinkles (js/entrance.js), not each star */
.star-group { position: absolute; inset: 0; animation: twinkle 3.5s ease-in-out infinite alternate; }
.star-group .star { animation: none; }
/* Bigger stars with a cross-shaped glint */
.star.bright::before,
.star.bright::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 900%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.75), transparent);
  transform: translate(-50%, -50%);
}
.star.bright::after { transform: translate(-50%, -50%) rotate(90deg); }

/* Shooting stars, each with its own angle and rhythm */
.meteor {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 150px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgb(255 255 255 / 0), rgb(200 215 255 / 0.5) 60%, #fff);
  transform-origin: left center;
  opacity: 0;
  animation: meteor-streak var(--cycle) linear var(--delay) infinite;
}
.meteor::after {
  content: "";
  position: absolute;
  right: -2px;
  top: -2px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 8px 2px rgb(210 225 255 / 0.9);
}
@keyframes meteor-streak {
  0%, 90% { opacity: 0; transform: rotate(var(--angle)) translateX(0) scaleX(0.4); }
  91.5% { opacity: 1; }
  97%, 100% { opacity: 0; transform: rotate(var(--angle)) translateX(34vw) scaleX(1); }
}

/* Halo around the moon: the same place and size as the .moon, enlarged */
.moon-halo {
  position: absolute;
  top: var(--moon-top);
  right: 9%;
  width: var(--moon-size);
  aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(circle, transparent 52%, rgb(215 205 255 / 0.07) 57%, transparent 63%),
    radial-gradient(circle, rgb(246 231 190 / 0.2) 0 22%, rgb(160 180 255 / 0.08) 42%, transparent 70%);
  transform: scale(4.2);
  animation: breathe 7s ease-in-out infinite alternate;
}

/* Paper lanterns rising from behind the city: they're birthday wishes */
.flying-lanterns { position: absolute; inset: 0; }
.flying-lantern {
  position: absolute;
  left: var(--x);
  bottom: 16%;
  width: var(--size);
  aspect-ratio: 3 / 4;
  opacity: 0;
  animation: lantern-rise var(--duration) linear var(--delay) infinite;
}
/* The glow around it is a gradient behind the lantern, and not a filter: drop-shadow
   (with the flame flickering inside, the browser redid both blurs every frame, on all 14) */
.flying-lantern::after {
  content: "";
  position: absolute;
  inset: -110% -140%;
  z-index: -1;
  background: radial-gradient(closest-side, rgb(255 178 74 / 0.55), rgb(255 140 50 / 0.2) 55%, transparent);
}
.flying-lantern::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(#ffe9b0, #ffb24a 55%, #d9633f);
  clip-path: polygon(0 0, 100% 0, 82% 100%, 18% 100%);
  border-radius: 30% 30% 8% 8%;
  animation: flicker 1.8s ease-in-out infinite alternate;
}
@keyframes lantern-rise {
  0% { opacity: 0; transform: translate(0, 0) rotate(-3deg) scale(1); }
  8% { opacity: 1; }
  35% { transform: translate(var(--drift), -24vh) rotate(4deg) scale(0.85); }
  70% { opacity: 0.85; transform: translate(calc(var(--drift) * -0.4), -48vh) rotate(-3deg) scale(0.65); }
  100% { opacity: 0; transform: translate(var(--drift), -72vh) rotate(2deg) scale(0.45); }
}

/* A flock of seagulls crossing the sky now and then, lit by the moon */
.seagulls {
  position: absolute;
  top: 24%;
  left: 0;
  width: clamp(60px, 8vw, 110px);
  overflow: visible;
  fill: none;
  stroke: rgb(215 225 250 / 0.7);
  stroke-width: 1.8;
  stroke-linecap: round;
  transform: translateX(-30vw);
  animation: seagulls-fly 60s linear 5s infinite;
}
.seagull {
  transform-box: fill-box;
  transform-origin: center;
  animation: wing-flap 0.7s ease-in-out infinite alternate;
}
@keyframes seagulls-fly {
  0% { transform: translate(-30vw, 0); }
  25% { transform: translate(45vw, -6vh); }
  50%, 100% { transform: translate(125vw, 2vh); }
}
@keyframes wing-flap {
  to { transform: scaleY(-0.4); }
}

/* Thin, transparent clouds passing slowly (in front of the moon) */
.clouds { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.cloud {
  position: absolute;
  top: var(--y);
  left: 0;
  width: var(--width);
  aspect-ratio: 5 / 1;
  opacity: var(--opacity, 1);
  background:
    radial-gradient(ellipse 30% 45% at 25% 60%, rgb(150 165 225 / 0.2), transparent 70%),
    radial-gradient(ellipse 35% 55% at 50% 42%, rgb(175 185 235 / 0.22), transparent 70%),
    radial-gradient(ellipse 28% 40% at 76% 58%, rgb(140 155 220 / 0.18), transparent 70%),
    radial-gradient(ellipse 50% 28% at 50% 76%, rgb(120 140 210 / 0.16), transparent 70%);
  /* (no filter: blur, for the same reason as the Milky Way) */
  animation: cloud-drift var(--duration) linear var(--delay) infinite;
}
@keyframes cloud-drift {
  from { transform: translateX(-100%); }
  to { transform: translateX(100vw); }
}

/* The far shore of the Pearl River, taller and lighter than the city in front, and the mist between the two */
.city-far {
  position: absolute;
  left: 0;
  bottom: 52px;
  width: 100%;
  height: clamp(150px, 26vw, 300px);
}
/* The Canton Tower's lattice slowly cycling through its night colors */
.canton-lights {
  stroke: #ff5aa0;
  filter: drop-shadow(0 0 3px currentColor);
  animation: canton-colors 14s linear infinite;
}
@keyframes canton-colors {
  0%, 100% { stroke: #ff5aa0; color: #ff5aa0; }
  20% { stroke: #9a7bff; color: #9a7bff; }
  40% { stroke: #3cd2ff; color: #3cd2ff; }
  60% { stroke: #5cf0a0; color: #5cf0a0; }
  80% { stroke: #ffd25a; color: #ffd25a; }
}
.tower-light {
  filter: drop-shadow(0 0 4px #ff4d4d) drop-shadow(0 0 10px #ff4d4d);
  animation: tower-blink 2.4s steps(1) infinite;
}
@keyframes tower-blink {
  50% { opacity: 0.1; }
}
.horizon-mist {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 52px;
  height: clamp(90px, 16vw, 190px);
  background: linear-gradient(to top, rgb(45 65 150 / 0.5), rgb(45 65 150 / 0.18) 55%, transparent);
  pointer-events: none;
  animation: breathe 11s ease-in-out infinite alternate;
}

/* The city's little windows: some flicker, others go dark and light up again */
#shore-windows { filter: drop-shadow(0 0 2px #ffb24a); }
.window-dim { animation: window-off var(--duration, 20s) steps(1) var(--delay, 0s) infinite; }
@keyframes window-off {
  45% { opacity: 0.04; }
  75% { opacity: 0.9; }
}

@media (prefers-reduced-motion: reduce) {
  .meteor { animation: none; opacity: 0; }
  .milky-way, .moon-halo, .horizon-mist, .cloud, .seagulls, .seagull, .canton-lights,
  .flying-lantern, .flying-lantern::before, .window-dim, .star-group { animation-play-state: paused; }
  .seagulls { display: none; }
}

.entrance-invite {
  margin: 18px 0 0;
  font-family: var(--font-title);
  font-size: clamp(1.2rem, 3.6vw, 1.6rem);
  letter-spacing: 0.04em;
  text-align: center;
  color: var(--moonlight);
  text-shadow: 0 0 12px rgb(201 211 240 / 0.35);
}
/* The Chinese line in brush calligraphy, between two thin gold threads */
.entrance-chinese {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  margin: clamp(4px, 1.2vh, 14px) 0 0;
  font-family: "Ma Shan Zheng", "STKaiti", "KaiTi", serif;
  font-size: clamp(1.3rem, min(4.4vw, 4vh), 1.9rem);
  line-height: 1.3;
  letter-spacing: 0.12em;
  color: var(--gold);
  text-shadow: 0 0 14px rgb(217 164 65 / 0.45);
}
.entrance-chinese::before,
.entrance-chinese::after {
  content: "";
  width: clamp(24px, 6vw, 48px);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold));
}
.entrance-chinese::after { background: linear-gradient(90deg, var(--gold), transparent); }

/* On arrival, the three lines rise in one after another, once the loading screen has left */
body.before-party:not(.loading-active) .party-title,
body.before-party:not(.loading-active) .entrance-invite,
body.before-party:not(.loading-active) .entrance-chinese {
  animation: sky-line-rise 1.2s ease-out both;
}
body.before-party:not(.loading-active) .entrance-invite { animation-delay: 0.6s; }
body.before-party:not(.loading-active) .entrance-chinese { animation-delay: 1.2s; }
body.before-party.loading-active .party-title,
body.before-party.loading-active .entrance-invite,
body.before-party.loading-active .entrance-chinese { opacity: 0; }
@keyframes sky-line-rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  body.before-party:not(.loading-active) .party-title,
  body.before-party:not(.loading-active) .entrance-invite,
  body.before-party:not(.loading-active) .entrance-chinese { animation: none; }
}

/* The street scene: takes up all the space left below the title.
   container-type turns the space into a measurable "container" (cqw/cqh),
   and the scene picks the biggest size that fits in it without distorting. */
.entrance-house {
  position: relative;
  z-index: 3;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: grid;
  place-items: end center;
  padding: 8px 0 52px; /* the courtyard rests on the river's edge (the same height as .river) */
  container-type: size;
}
.house-scene {
  position: relative;
  width: min(96cqw, 100cqh * 600 / 460);
  aspect-ratio: 600 / 460;
}
/* overflow visible: the street drawn past the viewBox shows up to the screen's edges (the .sky clips the excess) */
.palace-facade { display: block; width: 100%; height: 100%; overflow: visible; filter: drop-shadow(0 -6px 30px rgb(0 0 0 / 0.5)); }

/* Wide screen: the title on the left and the palace on the right, side by side */
@media (min-width: 820px) {
  body.before-party .sky {
    display: grid;
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    align-items: center;
  }
  body.before-party .sky-text {
    padding: 0 0 26vh clamp(32px, 7vw, 120px);
    text-align: left;
  }
  body.before-party .sky-text .ornament { margin: clamp(10px, 2vh, 20px) 0 0; }
  body.before-party .party-title { margin: 0; }
  body.before-party .entrance-invite { text-align: left; }
  /* Left-aligned like the lines above it: only the thread after the text */
  body.before-party .entrance-chinese { justify-content: flex-start; }
  body.before-party .entrance-chinese::before { display: none; }
  body.before-party .entrance-house {
    align-self: stretch;
    place-items: end center;
    padding: clamp(80px, 15vh, 150px) clamp(16px, 4vw, 64px) 52px 0;
  }
}

/* The house's living lights: lamp, the door's crack and windows flickering.
   steps(3): the light changes in little jumps (about 4 times per second) instead of 60, because each change
   inside the SVG drawing forces the browser to redraw the facade around it */
.lamp-glow { animation: flicker 2.6s steps(3) infinite alternate; }
.lamp { filter: drop-shadow(0 0 3px rgb(255 207 122 / 0.5)); }
.window-light { animation: flicker 3.4s steps(3) infinite alternate; }
.door-gap-light {
  filter: drop-shadow(0 0 3px #ffcf7a) drop-shadow(0 0 8px #ffb24a);
  transition: opacity 0.4s;
}
@keyframes flicker {
  30% { opacity: 0.85; }
  55% { opacity: 1; }
  80% { opacity: 0.9; }
}

/* The door opening inward: each leaf swings on its hinge */
.door-leaf { transform-box: fill-box; transition: transform 1s cubic-bezier(0.4, 0.1, 0.3, 1); }
.leaf-left { transform-origin: left center; }
.leaf-right { transform-origin: right center; }
.entrance-house.opening .door-leaf { transform: scaleX(0.08); }
/* On mouse hover, the door opens just a tiny bit: you can peek at the dark inside */
.entrance-house:not(.opening):has(.enter-button:hover) .door-leaf { transform: scaleX(0.9); transition-duration: 0.45s; }
.entrance-house.opening .door-gap-light { opacity: 0; }
/* Button: covers the whole door, with the golden sign hanging above it */
.enter-button {
  all: unset;
  position: absolute;
  /* The door's position inside the scene (600x460 frame): drawn at x 253–347, y 277–410, then the palace is
     scaled 1.3× around (300, 410) in index.html → x 239–361, y 237–410 */
  left: 39.82%;
  top: 51.54%;
  width: 20.37%;
  height: 37.59%;
  cursor: pointer;
}
.enter-button:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; border-radius: 6px; }
.entrance-house.opening .enter-button { pointer-events: none; }

/* 凯文, the courtyard cat: a click makes him meow and wiggle, a few small quick bobs up and down (js/entrance.js) */
.entrance-cat { cursor: pointer; transform-box: fill-box; transform-origin: 50% 100%; }
.entrance-cat.meowing { animation: cat-wiggle 0.5s ease-in-out; }
@keyframes cat-wiggle {
  20% { transform: translateY(-2px); }
  40% { transform: translateY(0); }
  60% { transform: translateY(-1.5px); }
  80% { transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) { .entrance-cat.meowing { animation: none; } }
/* The "Meow!" word: SVG text in the facade's units, floating up from the cat's head and fading */
.cat-meow {
  font-family: var(--font-title);
  font-size: 11px;
  fill: #fff6d8;
  text-anchor: middle;
  paint-order: stroke;
  stroke: #0d0b12;
  stroke-width: 2.5px;
  pointer-events: none;
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: cat-meow-float 1.2s ease-out forwards;
}
@keyframes cat-meow-float {
  0% { opacity: 0; transform: translateY(4px) scale(0.6); }
  15% { opacity: 1; transform: translateY(0) scale(1.1); }
  25% { transform: translateY(-2px) scale(1); }
  100% { opacity: 0; transform: translateY(-18px); }
}

/* The door button's text: invisible, only for screen readers */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* With the keyboard, the door shows it's clickable: the leaves glow on focus (not on mouse hover) */
.door-leaves { transition: filter 0.3s; }
.entrance-house:has(.enter-button:focus-visible) .door-leaves { filter: brightness(1.25) drop-shadow(0 0 6px #ffcf7a); }
/* The middle crack only shows on keyboard focus, and disappears with the others when the door opens */
/* (a band with a gradient + blur: the light spreads out instead of becoming a streak) */
.door-gap-light-middle { opacity: 0; filter: blur(7px); transition: opacity 0.3s; }
.entrance-house:has(.enter-button:focus-visible) .door-gap-light-middle { opacity: 1; }
.entrance-house.opening .door-gap-light-middle { opacity: 0; }

/* The surprise layer: invisible until the click, then it darkens gradually */
.entrance {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: none;
  overflow: hidden;
  background: rgb(0 0 0 / 0);
  transition: background 1.2s ease;
}
.entrance.active { display: block; }

/* 1. Lights off: all black, only the whispers */
.entrance.dark { background: rgb(0 0 0 / 1); }

.whisper {
  position: absolute;
  font-style: italic;
  color: rgb(255 255 255 / 0.55);
  pointer-events: none;
  animation: whisper-fade 1.7s ease forwards;
}
@keyframes whisper-fade {
  from { opacity: 0; transform: translateY(6px); }
  25%, 60% { opacity: 1; }
  to { opacity: 0; transform: translateY(-6px); }
}

/* 2. Light! The background disappears and reveals the hall, with a flash */
/* (it keeps catching clicks until it's removed, so nothing behind it can be clicked during the surprise) */
.entrance.lit { background: rgb(0 0 0 / 0); transition: background 0.15s ease; }
.entrance.lit::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #fff;
  pointer-events: none;
  animation: flash-fade 0.9s ease-out forwards;
}
@keyframes flash-fade {
  from { opacity: 0.9; }
  to { opacity: 0; }
}

.surprise {
  position: absolute;
  top: 20%;
  left: 0;
  right: 0;
  z-index: 2;
  margin: 0;
  font-family: var(--font-title);
  font-size: clamp(3.5rem, 15vw, 8.5rem);
  line-height: 1;
  text-align: center;
  color: var(--enamel);
  text-shadow: 0 0 20px var(--gold), 0 0 60px var(--coral), 0 6px 0 var(--coral);
  opacity: 0;
}
.entrance.lit .surprise { animation: burst 0.7s cubic-bezier(0.3, 1.6, 0.5, 1) 0.1s forwards; }
@keyframes burst {
  from { opacity: 0; transform: scale(0.2) rotate(-12deg); }
  to { opacity: 1; transform: scale(1) rotate(-4deg); }
}

/* Guests: they rise from the bottom of the screen and keep jumping */
.guests { position: absolute; left: 0; right: 0; bottom: 0; height: 45%; z-index: 1; }
.guest {
  position: absolute;
  bottom: 0;
  width: calc(clamp(90px, 16vw, 170px) * var(--scale, 1));
  transform: translateY(110%);
}
.guest svg { display: block; width: 100%; }
.entrance.lit .guest {
  animation: guest-appear 0.6s cubic-bezier(0.3, 1.5, 0.5, 1) var(--delay) forwards;
}
.entrance.lit .guest-body {
  animation: party-jump 0.42s ease-in-out calc(var(--delay) + 0.6s) infinite alternate;
}
@keyframes guest-appear { to { transform: translateY(8%); } }
@keyframes party-jump { to { transform: translateY(-10%); } }

/* The guests' speech bubbles (their own name: .shout is the goal text on the TV) */
.guest-shout {
  position: absolute;
  top: -18%;
  left: 50%;
  padding: 4px 12px;
  font-family: var(--font-title);
  font-size: clamp(0.8rem, 2.6vw, 1.15rem);
  white-space: nowrap;
  color: var(--ink);
  background: var(--enamel);
  border: 2px solid var(--gold);
  border-radius: 999px;
  transform: translateX(-50%) scale(0);
}
.entrance.lit .guest-shout { animation: shout-bubble 0.4s cubic-bezier(0.3, 1.6, 0.5, 1) calc(var(--delay) + 0.5s) forwards; }
@keyframes shout-bubble { to { transform: translateX(-50%) scale(1); } }

/* 3. Everyone leaves and the hall remains */
.entrance.leaving .surprise { animation: surprise-fade 1s ease forwards; }
.entrance.leaving .guest { animation: guest-descend 1s ease-in forwards; }
.entrance.leaving .guest-shout { transform: translateX(-50%) scale(1); animation: none; }
@keyframes surprise-fade {
  from { opacity: 1; transform: scale(1) rotate(-4deg); }
  to { opacity: 0; transform: translateY(-30px) rotate(-4deg); }
}
@keyframes guest-descend {
  from { transform: translateY(8%); }
  to { transform: translateY(120%); }
}

.entrance-confetti { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 3; pointer-events: none; }

/* ---------- Loading screen ---------- */

body.loading-active { overflow: hidden; }

.loading {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 24px;
  background: var(--lattice), radial-gradient(ellipse at top, var(--night-2), var(--night-deep) 70%);
  transition: opacity 0.5s ease;
}
.loading.leaving { opacity: 0; pointer-events: none; }
/* Music, TV and Food: their room's wall (as in their header and below it), darker toward the corners, with the room's
   light pooled behind the loading scene. (body[data-area], so the cover from the very first moment has it too)
   Music: faint stripes on plum, the lamp's warm light */
body[data-area="music"] .loading {
  --glow: rgb(255 200 160 / 0.3);
  --wall: #8a4a6e, #45203f 60%, #2a1230;
  --pattern: repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px);
}
/* TV: the same faint stripes on jade, the TV's mint glow */
body[data-area="tv"] .loading {
  --glow: rgb(200 244 234 / 0.22);
  --wall: #2f6a60, #143a34 60%, #0b2420;
  --pattern: repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px);
}
/* Food: the same faint stripes on the nook's dark wall, its warm amber light */
body[data-area="food"] .loading {
  --glow: rgb(255 207 122 / 0.25);
  --wall: #8a4a26, #3e1a0e 60%, #1e0d07;
  --pattern: repeating-linear-gradient(90deg, transparent 0 22px, rgb(0 0 0 / 0.08) 22px 34px);
}
/* Challenges: the tower's wall, with its posts, in deep night blue to go with the porcelain yin-yang, and a cool
   blue light behind it */
body[data-area="challenges"] .loading {
  --glow: rgb(150 190 240 / 0.2);
  --wall: #24406e, #132546 60%, #0a1428;
  --pattern: repeating-linear-gradient(90deg, rgb(0 0 0 / 0.22) 0 5px, transparent 5px clamp(40px, 6.25vw, 90px));
}
/* Reading: the cabin's dark plank wall, and a warm reading light */
body[data-area="reading"] .loading {
  --glow: rgb(255 190 110 / 0.24);
  --wall: #5e3a22, #2e1a0e 60%, #160c06;
  --pattern: repeating-linear-gradient(0deg, rgb(0 0 0 / 0.25) 0 3px, transparent 3px 40px);
}
body:is([data-area="music"], [data-area="tv"], [data-area="food"], [data-area="challenges"], [data-area="reading"]) .loading {
  background:
    radial-gradient(ellipse 45% 55% at 50% 45%, var(--glow), transparent 70%),
    radial-gradient(ellipse 120% 100% at 50% 45%, transparent 50%, rgb(4 4 10 / 0.6)),
    var(--pattern),
    radial-gradient(ellipse 70% 90% at 50% 45%, var(--wall));
}

.loading-text {
  min-height: 1.3em;
  margin: 0;
  font-family: var(--font-title);
  font-size: 1.4rem;
  text-align: center;
  color: var(--enamel);
}
.loading-text.swapping { animation: phrase-enter 0.4s ease; }
@keyframes phrase-enter { from { opacity: 0; transform: translateY(6px); } }
.ready .loading-text { color: var(--gold); }

.loading-percent {
  margin: -6px 0 0;
  font-family: "Courier New", monospace;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--gold);
  text-shadow: 0 0 10px rgb(217 164 65 / 0.5);
}

/* Each scene reads --progress (0 to 1), updated by js/loading.js with each file downloaded;
   .ready comes in when everything has finished, for the "grand finale" before the screen goes away */
.loading-scene {
  display: block;
  width: min(220px, 60vw);
  overflow: visible;
  filter: drop-shadow(0 0 22px rgb(217 164 65 / 0.25));
}

/* Food: a dumpling in its bamboo steamer. It puffs up from its bottom (hidden in the steamer) with the progress,
   and its steam thickens. At the end it hops with a squash and stretch, its dot eyes become happy arcs, and the
   steam bursts up and away.
   (fixed speeds: the progress changes every frame, and changing an animation's duration every frame makes it stutter) */
.scene-dumpling-steamer { width: min(200px, 56vw); }
.scene-dumpling,
.scene-dumpling-puff { transform-box: fill-box; transform-origin: 50% 100%; }
.scene-dumpling-puff { transform: scale(calc(0.78 + var(--progress, 0) * 0.22)); }
.ready .scene-dumpling { animation: dumpling-hop 0.7s cubic-bezier(0.3, 0.9, 0.5, 1); }
@keyframes dumpling-hop {
  30% { transform: translateY(-16px) scale(0.95, 1.06); }
  60% { transform: translateY(0) scale(1.1, 0.9); }
  80% { transform: scale(0.97, 1.03); }
}
.scene-dumpling-happy { opacity: 0; }
.ready .scene-dumpling-happy { opacity: 1; }
.ready .scene-dumpling-eyes { opacity: 0; }

.scene-dumpling-steam path {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  opacity: calc(0.15 + var(--progress, 0) * 0.75);
  animation: dumpling-steam 1.6s ease-in-out infinite alternate;
}
.scene-dumpling-steam path:nth-child(2) { animation-delay: -0.5s; }
.scene-dumpling-steam path:nth-child(3) { animation-delay: -1s; }
@keyframes dumpling-steam {
  from { transform: translateY(6px); }
  to { transform: translateY(-6px); }
}
.ready .scene-dumpling-steam path { animation: dumpling-steam-burst 0.7s ease-out forwards; }
@keyframes dumpling-steam-burst { to { opacity: 0; transform: translateY(-24px) scale(1.4); } }
@media (prefers-reduced-motion: reduce) {
  .scene-dumpling-steam path, .ready .scene-dumpling, .ready .scene-dumpling-steam path { animation: none; }
}


/* TV: static on the screen, signal bars lighting up, and at the end the TV turns on to "CANLI" */
.scene-tv { position: relative; width: min(200px, 56vw); padding-top: 34px; filter: none; }
.scene-tv::before,
.scene-tv::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 3px;
  height: 40px;
  border-radius: 2px;
  background: linear-gradient(#f1d596, #a57d2e);
  transform-origin: 50% 100%;
}
.scene-tv::before { transform: translateX(-50%) rotate(-28deg); }
.scene-tv::after { transform: translateX(-50%) rotate(28deg); }

.scene-tv-box {
  position: relative;
  display: flex;
  gap: 10px;
  padding: 12px;
  background: linear-gradient(160deg, #7a4428, #4a2616 60%, #3a1c10);
  border: 3px solid var(--gold);
  border-radius: 18px;
  box-shadow: 0 16px 34px rgb(0 0 0 / 0.5), 0 0 26px rgb(217 164 65 / 0.2);
}
.scene-tv-screen {
  position: relative;
  flex: 1;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E") 0 0 / 120px 120px, #777;
  border: 4px solid #1a0e08;
  border-radius: 16px / 12px;
  animation: static 0.15s steps(3) infinite;
}
.scene-tv-screen::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(135deg, rgb(255 255 255 / 0.14), transparent 40%),
    repeating-linear-gradient(to bottom, transparent 0 2px, rgb(0 0 0 / 0.2) 2px 3px);
}
.ready .scene-tv-screen {
  background: radial-gradient(ellipse at 30% 20%, #2a4a9a, var(--night) 70%);
  animation: tv-on 0.5s ease-out;
}

.scene-tv-panel { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 22%; padding-top: 4px; }
.scene-tv-knob {
  width: 70%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fbe7b0, var(--gold) 55%, #7a5418);
  box-shadow: 0 2px 0 #5c3d0e;
}
.scene-tv-grille {
  flex: 1;
  width: 80%;
  border-radius: 4px;
  background: repeating-linear-gradient(to bottom, #1a0c05 0 3px, #4a2614 3px 6px);
}

.scene-signal { display: flex; align-items: flex-end; justify-content: center; gap: 4px; height: 22px; margin-top: 12px; }
.scene-bar {
  width: 7px;
  background: rgb(255 255 255 / 0.15);
  border-radius: 2px;
  transition: background 0.3s, box-shadow 0.3s;
}
.scene-bar.lit { background: #8dffb0; box-shadow: 0 0 6px #8dffb0; }

/* Music: the record spins, the tonearm moves onto the record as it loads, and at the end the notes fly */
/* The tonearm stays resting off the record for almost the whole loading and only moves onto it
   in the last 15% (from 85% to 100%), right before the finale */
.scene-record { transform-box: fill-box; transform-origin: center; animation: spin 1.2s linear infinite; }
.scene-tonearm {
  --entry: clamp(0, (var(--progress, 0) - 0.85) / 0.15, 1); /* 0 up to 85%, rises to 1 at 100% */
  transform-box: view-box;
  transform-origin: 184px 34px; /* the tonearm's brass pivot */
  transform: rotate(calc(8deg + var(--entry) * 20deg));
  transition: transform 0.25s ease-out;
}
.scene-notes text { opacity: 0; transform-box: fill-box; transform-origin: center; }
.ready .scene-notes text { animation: note-fly 0.5s cubic-bezier(0.3, 1.5, 0.5, 1) both; }
.ready .scene-notes text:nth-child(2) { animation-delay: 0.08s; }
.ready .scene-notes text:nth-child(3) { animation-delay: 0.16s; }
@keyframes note-fly {
  from { opacity: 0; transform: translateY(24px) scale(0.5); }
  40% { opacity: 1; }
  to { opacity: 1; transform: translateY(-12px) scale(1.15) rotate(-10deg); }
}

/* Reading: an open book. Its pages keep turning (drawn in js/loading.js: turningPages), and the light from its pages
   grows with the progress. At the end a heart rises out of it (like the music's notes) */
.scene-reading { width: min(210px, 58vw); }
.scene-reading-glow { opacity: calc(0.25 + var(--progress, 0) * 0.75); }
.scene-reading-sparks > * { opacity: 0; transform-box: fill-box; transform-origin: center; }
.ready .scene-reading-sparks > * { animation: note-fly 0.5s cubic-bezier(0.3, 1.5, 0.5, 1) both; }

/* Balcony: only the climb, on the whole screen (the corridor, the door and the light are drawn frame by
   frame in js/climb.js). No percentage or phrase on screen: the climb already shows the progress */
.loading[data-scene="balcony"] { background: #05040b; }
.loading[data-scene="balcony"] > .loading-stage { position: absolute; inset: 0; }
.loading[data-scene="balcony"] .loading-percent { display: none; }
/* the phrase leaves the screen, but is still read by screen readers (the screen is role="status") */
.loading[data-scene="balcony"] .loading-text {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.scene-balcony { position: relative; width: 100%; height: 100%; overflow: hidden; filter: none; }
/* Its layers (js/climb.js): the corridor and the front are two SVGs on top of each other */
.climb-layer { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }
/* The door's aura and rays: each on its own layer, which the graphics card moves, scales, pulses and turns without
   repainting anything. Sized in the drawing's units (aura 100×128, rays 480×480); js/climb.js (place) centers and
   scales them over the door */
.climb-glow { position: absolute; top: 0; left: 0; transform-origin: 0 0; will-change: transform, opacity; pointer-events: none; }
.climb-aura { width: 100px; height: 128px; }
.climb-aura > span {
  display: block;
  width: 100%;
  height: 100%;
  background: radial-gradient(closest-side, rgb(255 207 122 / 0.75), rgb(255 207 122 / 0.28) 45%, rgb(255 207 122 / 0));
  animation: climb-aura-pulse 1.43s ease-in-out infinite alternate;
}
.climb-rays { width: 480px; height: 480px; }
.climb-rays > svg { display: block; width: 100%; height: 100%; overflow: visible; animation: climb-rays-turn 90s linear infinite; }
/* The golden dust: a point on the door (no size of its own), each mote placed around it at 4 px per unit of the
   drawing (DUST_PX in js/climb.js). The outer span sways side to side, the inner one rises 60 units over its life
   while fading in and out */
.climb-dust { width: 0; height: 0; }
.climb-mote {
  position: absolute;
  animation: climb-mote-sway var(--sway) ease-in-out var(--sway-delay) infinite alternate;
}
.climb-mote > span {
  display: block;
  width: var(--size);
  height: var(--size);
  margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2);
  border-radius: 50%;
  background: #ffe9b0;
  opacity: 0;
  animation:
    climb-mote-rise var(--life) linear var(--delay) infinite,
    climb-mote-fade var(--life) ease-in-out var(--delay) infinite;
}
@keyframes climb-mote-sway { from { translate: -20px 0; } to { translate: 20px 0; } }
@keyframes climb-mote-rise { to { translate: 0 -240px; } }
@keyframes climb-mote-fade { 50% { opacity: var(--op); } }
@keyframes climb-aura-pulse { from { transform: scale(0.94); } to { transform: scale(1.06); } }
@keyframes climb-rays-turn { to { transform: rotate(360deg); } }
/* Over everything: the light filling the screen through the door (opacity from js/climb.js) and the vignette */
.climb-glare, .climb-vignette { position: absolute; inset: 0; pointer-events: none; }
.climb-glare { background: #fff4d6; opacity: 0; will-change: opacity; }
.climb-vignette { background: radial-gradient(ellipse 75% 75% at 50% 45%, rgb(2 3 10 / 0) 50%, rgb(2 3 10 / 0.85)); }
@media (prefers-reduced-motion: reduce) {
  .climb-aura > span, .climb-rays > svg, .climb-mote { animation: none; }
  /* the dust stays still, softly lit */
  .climb-mote > span { animation: none; opacity: calc(var(--op) * 0.6); }
}
/* She's the one who does the climb (js/climb.js): each tap on the screen is a step */
.loading[data-scene="balcony"] { cursor: pointer; touch-action: manipulation; user-select: none; }
.climb-hint {
  position: absolute;
  bottom: max(28px, 8vh);
  left: 50%;
  margin: 0;
  translate: -50% 0;
  font-family: var(--font-title);
  font-size: clamp(1.1rem, 2.6vh, 1.5rem);
  letter-spacing: 0.04em;
  white-space: nowrap;
  color: #ffe7b0;
  text-shadow: 0 0 14px rgb(255 190 100 / 0.8), 0 2px 4px rgb(0 0 0 / 0.8);
  pointer-events: none;
  transition: opacity 0.5s ease;
  animation: hint-breathe 2.2s ease-in-out infinite alternate;
}
.climb-hint.faded { opacity: 0; animation: none; }
@keyframes hint-breathe { from { opacity: 0.55; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .climb-hint { animation: none; }
}
/* The climb is its own page (climb.html): through the door, its screen goes away onto the door's light (the climb's
   glare), held until the balcony (cake.html) takes over, starting from that same light and fading it away
   (ARRIVAL_MS in js/cake.js: delay + duration) */
body.climb-page { background: #fff4d6; }
.balcony-arrival {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #fff4d6;
  pointer-events: none;
  animation: arrival-fade 0.9s ease-in 0.2s both;
}
@keyframes arrival-fade { to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .balcony-arrival { display: none; }
}

/* Hall and Art Gallery: only a plain golden ring spinning. The percentage is hidden and the phrase leaves the screen
   (still read by screen readers, the screen is role="status"). Its background is the room's wall: the hall's wood
   (#hall-wall in js/hall.js), the gallery's enchanted violet stone (.gallery) */
.loading[data-scene="hall"] { background: radial-gradient(circle, #5a3424, #3c2218 45%, #1c0f0a); }
.loading[data-scene="gallery"] { background: radial-gradient(circle, #b3a8e3, #8f82c4 45%, #5a4e8c); }
/* (before the surprise, index.html opens on the entrance, not the hall: the cover is the entrance's night sky, so it
   fades straight into the .sky behind it) */
body.before-party .loading[data-scene="hall"] { background: linear-gradient(var(--night) 0%, var(--night-2) 65%, #22397c 100%); }
:is(.loading[data-scene="hall"], .loading[data-scene="gallery"]) .loading-percent { display: none; }
:is(.loading[data-scene="hall"], .loading[data-scene="gallery"]) .loading-text {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.scene-spinner {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 4px solid rgb(217 164 65 / 0.2);
  border-top-color: var(--gold);
  filter: none;
  animation: spin 0.9s linear infinite;
}
/* (the gallery's in white, like the lines of light in its stone) */
.loading[data-scene="gallery"] .scene-spinner { border-color: rgb(255 255 255 / 0.25); border-top-color: #fff; }

/* Yin-yang (challenge floors): spins inside the eight trigrams, which turn slowly the other way and light up one by
   one (.lit, from js/loading.js), inside the ring that keeps closing; at the end it pops */
.scene-yin-yang { width: min(180px, 50vw); filter: drop-shadow(0 0 22px rgb(120 170 235 / 0.3)); }
.scene-yin-yang-spin { transform-box: fill-box; transform-origin: center; animation: spin 2.4s linear infinite; }
.scene-trigrams { transform-box: fill-box; transform-origin: center; animation: spin 24s linear infinite reverse; }
.scene-trigram { fill: #9cc0ec; opacity: 0.25; transition: opacity 0.4s ease, fill 0.4s ease, filter 0.4s ease; }
.scene-trigram.lit { fill: #eef4fc; opacity: 1; filter: drop-shadow(0 0 3px #9cc0ec); }
.scene-ring {
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - var(--progress, 0) * 100);
  transition: stroke-dashoffset 0.4s ease;
}
.ready .scene-yin-yang { animation: yin-yang-ready 0.6s cubic-bezier(0.3, 1.6, 0.5, 1); }
@keyframes yin-yang-ready { 50% { transform: scale(1.15); } }
@media (prefers-reduced-motion: reduce) {
  .scene-yin-yang-spin, .scene-trigrams, .ready .scene-yin-yang { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .lantern, .cake-door.unlocked { animation: none; }
}

/* ---------- Waiting screen (waiting.html): the black cat walking to the party ---------- */
body.waiting {
  position: relative;
  background: linear-gradient(#bfe0f4, #eaf5fb);
  /* quick taps on 凯文 only make him run: no double-tap zoom on phones (pinch-zoom still works) and no text
     getting selected by fast clicks */
  touch-action: manipulation;
  user-select: none;
}
/* Loading screen: a spinning ring over the sky color, until the garden is ready (js/garden.js fades it away) */
.waiting-loading {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  background: linear-gradient(#bfe0f4, #eaf5fb);
  transition: opacity 0.5s ease, visibility 0.5s;
}
.waiting-loading.done { opacity: 0; visibility: hidden; }
.waiting-spinner {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 4px solid rgb(59 74 114 / 0.2);
  border-top-color: #3b4a72;
  animation: waiting-spin 0.9s linear infinite;
}
@keyframes waiting-spin { to { rotate: 360deg; } }
/* The garden fills the screen; js/garden.js builds it (a stack of layers, back to front) and places the cat on its path */
.garden { position: absolute; inset: 0; overflow: hidden; }
.garden > * { position: absolute; top: 0; left: 0; display: block; }
.garden-sky { width: 100%; height: 100%; }
.garden-stars { width: 100%; opacity: 0; }
.garden-body { opacity: 0; will-change: transform; }
/* each scrolling layer slides on the graphics card (js/garden.js sets its size, place and transform) */
.garden-layer { will-change: transform; }
.garden-lights { opacity: 0; }
.garden-lawn { top: 86%; width: 100%; height: 14%; background: linear-gradient(#8cc47a, #78b566); }
.garden-warmth { width: 100%; height: 100%; background: #ff8a4c; opacity: 0; }
.garden-front { width: 100%; height: 100%; }
/* "On the way to your party": centered in the sky (the sky runs from the top down to the mountains,
   about 56% of the screen in js/garden.js, so its middle is at 28%), floating gently */
.waiting-text {
  position: absolute;
  top: 28svh;
  transform: translateY(-50%);
  left: 16px;
  right: 16px;
  text-align: center;
  /* navy with a white glow by day, warm cream with a dark glow at night (--night: 0 → 1, set by js/garden.js) */
  color: #3b4a72; /* (for browsers without color-mix) */
  color: color-mix(in srgb, #fff3d6 calc(var(--night, 0) * 100%), #3b4a72);
  text-shadow: 0 0 18px rgb(255 255 255 / 0.9);
  text-shadow:
    0 0 18px color-mix(in srgb, rgb(10 18 48 / 0.9) calc(var(--night, 0) * 100%), rgb(255 255 255 / 0.9)),
    0 1px 0 color-mix(in srgb, rgb(10 18 48 / 0.6) calc(var(--night, 0) * 100%), rgb(255 255 255 / 0.8));
  animation: waiting-float 4s ease-in-out infinite alternate;
  transition: opacity 0.5s ease;
}
/* from the 7th tap on (js/waiting.js), the title fades away: the scene belongs to 凯文's dash */
body.cat-dashing .waiting-text { opacity: 0; }
.waiting-text h1 {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: clamp(1.7rem, min(6vw, 7svh), 3.4rem);
  line-height: 1.2;
}
/* "Tap the cat to say hi": smaller, softly pulsing; fades away after the first tap (js/waiting.js) */
.waiting-hint {
  margin: 0.5em 0 0;
  font-size: clamp(0.95rem, min(3.2vw, 3.2svh), 1.25rem);
  letter-spacing: 0.04em;
  animation: waiting-hint-pulse 1.6s ease-in-out infinite alternate;
  transition: opacity 0.8s ease, visibility 0.8s;
}
@keyframes waiting-hint-pulse { from { opacity: 0.55; } }
body.cat-greeted .waiting-hint { opacity: 0; visibility: hidden; animation: none; }
.garden-twinkle { animation: garden-twinkle 3s ease-in-out infinite alternate; }
@keyframes garden-twinkle { to { opacity: 0.25; } }
@keyframes waiting-float {
  from { translate: 0 0; }
  to { translate: 0 -8px; }
}
/* The cat's frame: exactly the garden's box (so js/garden.js's left/top for him still line up), cutting off whatever
   passes its edges. The dash slides 凯文 past the screen's right edge; without this he made the page wider, and the
   page could then scroll sideways after him, showing the body's light-blue background beside the garden. Taps go
   through the frame to him only */
.waiting-stage { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
/* The walk itself (legs, tail, head) is drawn frame by frame in js/waiting.js */
.waiting-cat { position: absolute; cursor: pointer; pointer-events: auto; } /* tap it: it meows */
.waiting-cat .cat-shadow { fill: #000; opacity: 0.18; }
/* A quick blink every few seconds */
.waiting-cat .cat-eyes {
  transform-box: fill-box;
  transform-origin: center;
  animation: cat-blink 4.5s infinite;
}
@keyframes cat-blink {
  0%, 92%, 100% { transform: scaleY(1); }
  95% { transform: scaleY(0.1); }
}
/* 凯文 on the hall's rug blinks too (index.html): js/hall.js times it (every few seconds, now and then twice in a row);
   the eyes close toward their own middle */
.hall-cat-eyes {
  transform-box: fill-box;
  transform-origin: center;
}
/* The night's end: black over everything (0.6 s, FADE_MS in js/garden.js), then the entrance opens */
.waiting-fade {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
body.night-leaving .waiting-fade { opacity: 1; }

/* ---------- The Art Gallery (gallery.html) ---------- */
/* A Chinese palace hall filling the screen (body.single-screen: it never scrolls), all in the colors of the enchanted
   stone of its door's steps: violet, lilac and white light, gold for the metal. From the top: a carved deep indigo beam
   with a gold band, walls of glowing violet stone in tall panels (like the steps up to its door in the hall) lit warm in the
   middle by the lamps, the lower wall in the same stone but darker under a line of white light, and a stone floor
   edged in gold.
   The pieces in three bands, every size in screen units, so the room keeps its shape on any screen:
   hanging on cords, the 3 big ones (the center one the biggest), and the ones standing on pillars */
body.gallery-page { background: #7466aa; }
.gallery {
  --beam: 5svh;
  --frieze: 3svh; /* the carved band under the beam */
  --floor: 10svh;
  --dado: 20svh; /* the lower wall's height, above the floor */
  position: relative;
  isolation: isolate; /* (its own stack of layers: the light can go right in front of its walls, behind everything) */
  height: 100%;
  display: grid;
  /* one column exactly the screen's width (never stretched by what's in it): the title right under the beam, the
     pillars' band at the bottom, the other bands spread evenly between them */
  grid-template-columns: minmax(0, 1fr);
  align-content: space-between;
  justify-items: center;
  align-items: center;
  padding: calc(var(--beam) + var(--frieze) + 1.6svh) clamp(8px, 2vw, 32px) calc(var(--floor) * 0.45);
  overflow: hidden;
  background:
    linear-gradient(#1e1932, #3e3660 60%, var(--gold) 60% 68%, #241e44 68%) top / 100% var(--beam) no-repeat,
    /* the carved frieze under the beam: a gold meander (the key pattern) on deep violet, edged in gold, its shadow on
       the wall below */
    linear-gradient(var(--gold), var(--gold)) 0 var(--beam) / 100% 2px no-repeat,
    linear-gradient(var(--gold), var(--gold)) 0 calc(var(--beam) + var(--frieze) - 2px) / 100% 2px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M0 17H20M2.5 17V3.5H16.5V13.5H7V7.5H12.5V10.5' fill='none' stroke='%23e2b24e' stroke-width='1.6' stroke-linecap='square'/%3E%3C/svg%3E")
      0 calc(var(--beam) + 2px) / auto calc(var(--frieze) - 4px) repeat-x,
    linear-gradient(#4a3c80, #2e2656) 0 var(--beam) / 100% var(--frieze) no-repeat,
    linear-gradient(rgb(0 0 0 / 0.4), transparent) 0 calc(var(--beam) + var(--frieze)) / 100% 1.5svh no-repeat,
    /* the wall's two end panels, left and right, in wood instead of stone (like the stage's front), from under the
       frieze down to the floor: each a wooden pillar, its fine grain running down it, lit from the front so it looks
       round, a soft shadow along its inner edge where it meets the stone (the stone's seam of light just past it) */
    repeating-linear-gradient(90deg, rgb(70 35 10 / 0.18) 0 1px, transparent 1px 5px, rgb(70 35 10 / 0.12) 5px 6px,
      transparent 6px 11px, rgb(255 220 180 / 0.07) 11px 12px, transparent 12px 17px)
      left 0 top calc(var(--beam) + var(--frieze)) / 12.5% calc(100% - var(--beam) - var(--frieze) - var(--floor)) no-repeat,
    repeating-linear-gradient(90deg, rgb(70 35 10 / 0.18) 0 1px, transparent 1px 5px, rgb(70 35 10 / 0.12) 5px 6px,
      transparent 6px 11px, rgb(255 220 180 / 0.07) 11px 12px, transparent 12px 17px)
      right 0 top calc(var(--beam) + var(--frieze)) / 12.5% calc(100% - var(--beam) - var(--frieze) - var(--floor)) no-repeat,
    linear-gradient(to left, rgb(0 0 0 / 0.35), transparent 8px)
      left 0 top calc(var(--beam) + var(--frieze)) / 12.5% calc(100% - var(--beam) - var(--frieze) - var(--floor)) no-repeat,
    linear-gradient(to right, rgb(0 0 0 / 0.35), transparent 8px)
      right 0 top calc(var(--beam) + var(--frieze)) / 12.5% calc(100% - var(--beam) - var(--frieze) - var(--floor)) no-repeat,
    linear-gradient(90deg, #4a2a16, #7a4a28 40%, #8a5430 52%, #5e3a1e 78%, #3a2010)
      left 0 top calc(var(--beam) + var(--frieze)) / 12.5% calc(100% - var(--beam) - var(--frieze) - var(--floor)) no-repeat,
    linear-gradient(90deg, #3a2010, #5e3a1e 22%, #8a5430 48%, #7a4a28 60%, #4a2a16)
      right 0 top calc(var(--beam) + var(--frieze)) / 12.5% calc(100% - var(--beam) - var(--frieze) - var(--floor)) no-repeat,
    linear-gradient(var(--gold) 0 3px, #2c2b31 3px, #0f0e12) bottom / 100% var(--floor) no-repeat,
    /* the lower wall, in the same stone in the steps' darker riser shade, darker toward the floor (as their risers):
       a white line of light along its top instead of a rail, a soft lilac glow just under it, and white seams of
       light between its panels, like the wall's */
    linear-gradient(rgb(255 255 255 / 0.9) 0 2px, rgb(225 214 255 / 0.4) 2px 4px, transparent 9px)
      left 0 bottom var(--floor) / 100% var(--dado) no-repeat,
    repeating-linear-gradient(90deg, rgb(255 255 255 / 0.9) 0 1.5px, rgb(255 255 255 / 0.18) 4px, transparent 7px
      calc(16.66% - 7px), rgb(255 255 255 / 0.18) calc(16.66% - 4px), rgb(255 255 255 / 0.9) calc(16.66% - 1.5px) 16.66%)
      left 0 bottom var(--floor) / 100% var(--dado) no-repeat,
    linear-gradient(#6f62a3, #5a4e8c) left 0 bottom var(--floor) / 100% var(--dado) no-repeat,
    radial-gradient(ellipse 55% 45% at 50% 48%, rgb(255 200 130 / 0.25), transparent),
    /* the walls: the enchanted stone of the steps up to its door in the hall (js/hall.js, MAGIC_STONE), in tall
       panels: violet slate lit from inside down each panel's middle, a soft line of lilac light along both its sides
       (like the steps' front edges), and between panels a thin white seam of light with a faint glow around it (like
       the light from under the steps); a fine grain showing faintly through */
    repeating-linear-gradient(90deg, transparent 0 1.1%, rgb(225 214 255 / 0.5) 1.1% 1.25%, transparent 1.25% 11.25%,
      rgb(225 214 255 / 0.5) 11.25% 11.4%, transparent 11.4% 12.5%),
    /* (each seam's glow split across the repeat: its right half at the start, its left half at the end) */
    repeating-linear-gradient(90deg, rgb(255 255 255 / 0.9) 0 1.5px, rgb(255 255 255 / 0.18) 4px, transparent 7px
      calc(12.5% - 7px), rgb(255 255 255 / 0.18) calc(12.5% - 4px), rgb(255 255 255 / 0.9) calc(12.5% - 1.5px) 12.5%),
    repeating-linear-gradient(90deg, rgb(255 255 255 / 0) 0, rgb(255 255 255 / 0.2) 6.25%, rgb(255 255 255 / 0) 12.5%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='400'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9 0.012' numOctaves='3' seed='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.25 0 0 0 0 0.2 0 0 0 0 0.4 0 0 0 0.5 -0.18'/%3E%3C/filter%3E%3Crect width='200' height='400' filter='url(%23g)'/%3E%3C/svg%3E")
      0 0 / 200px 400px,
    linear-gradient(#a69ad6, #8f82c4 55%, #7466aa);
}

/* The room's light: two beams of white light, one from each top corner of the page, crossing the room diagonally to
   the opposite side so they make an X, their middles meeting on the lower part of the main piece (about 60% of the
   way down the room; brighter
   where they overlap): each narrow at its corner, opening wide as it goes (75% of the room's width by the floor; in
   proportions of the room, so the shapes show on any screen), brightest at the top and fading as they fall. Their edges blurred a little (the blur on the
   whole, so the cut shapes inside soften). On the walls only, right in front of them and behind everything else in
   the room (the pieces, the window, the stage), brightening what they fall on, never catching a click; no lamps
   shown */
.gallery-light {
  position: absolute;
  z-index: -1;
  inset: 0;
  pointer-events: none;
  filter: blur(22px);
  mix-blend-mode: screen;
}
.gallery-light::before,
.gallery-light::after {
  content: "";
  position: absolute;
  inset: 0;
}
.gallery-light::before,
.gallery-light::after {
  background: linear-gradient(rgb(255 255 255 / 0.3), rgb(255 255 255 / 0.13) 60%, rgb(255 255 255 / 0.05));
}
.gallery-light::before { clip-path: polygon(-6% 0, 8% 0, 120% 100%, 45% 100%); }
.gallery-light::after { clip-path: polygon(92% 0, 106% 0, 55% 100%, -20% 100%); }

/* The glow of the room's door in the hall, carried inside (gallery.html, js/gallery.js fills them). On the walls only,
   behind everything else like the light above, so her pieces keep their own colors, never catching a click:
   a warm halo over the middle of the room, breathing like the door's (gallery-pulse), and the door's own wash of
   saturated colors (its small drifting blobs, js/gallery.js), each hue showing only where it's brighter than the wall
   ("lighten", as on the door and its steps: pure, not pastel), slowly breathing too. The wash on the violet stone
   only (from under the frieze down to the floor, between the two wooden end panels) */
.gallery-aura-glow,
.gallery-aura-tint {
  position: absolute;
  z-index: -1;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.gallery-aura-glow {
  background: radial-gradient(ellipse 65% 55% at 50% 50%, rgb(255 214 150 / 0.32), rgb(255 190 120 / 0.1) 55%, transparent 80%);
  mix-blend-mode: screen;
  animation: gallery-pulse 3s ease-in-out infinite alternate;
}
.gallery-aura-tint {
  inset: calc(var(--beam) + var(--frieze)) 12.5% var(--floor);
  mix-blend-mode: lighten;
  opacity: 0.88;
  animation: gallery-aura-breathe 7s ease-in-out infinite alternate;
}
/* Its two parts (js/gallery.js, makeWash: the same drifting layers in each, each part's hues tuned in its pictures),
   the upper wall's and the lower wall's (--dado), each only as big as its own strip, its layers' drift kept inside it */
.gallery-aura-part { position: absolute; left: 0; right: 0; overflow: hidden; }
.gallery-aura-upper { top: 0; bottom: var(--dado); }
.gallery-aura-lower { bottom: 0; height: var(--dado); }
/* each of the wash's layers (js/gallery.js, makeWash: its place and size set there) */
.gallery-wash-layer { position: absolute; display: block; }
/* (test switch, gallery.html?nowash, js/gallery.js: the walls', the stage's and the title plaque's wash left out) */
.no-wash :is(.gallery-aura-tint, .gallery-stage-magic, .gallery-sign-tint) { display: none; }
@keyframes gallery-aura-breathe {
  from { opacity: 0.75; }
  to { opacity: 1; }
}
/* Motes of light rising through the whole room, like up the door's steps (mote-rise): in front of everything but the
   back sign and what opens over the room, faint, never catching a click */
.gallery-motes {
  position: absolute;
  z-index: 4;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0.75;
}
.gallery-mote {
  position: absolute;
  width: var(--size);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--hue);
  box-shadow: 0 0 calc(var(--size) * 2) calc(var(--size) * 0.6) var(--hue);
  opacity: 0;
  animation: mote-rise var(--dur, 8s) linear var(--delay, 0s) infinite;
}
@media (prefers-reduced-motion: reduce) {
  .gallery-motes { display: none; }
  .gallery-aura-glow,
  .gallery-aura-tint,
  .gallery-aura-tint .gallery-blob { animation: none; }
}

/* The curtains closed over the room when she arrives (gallery.html). In front of the whole room, its motes' layer and
   the back sign too (z-index 5): it only shows once they open. Two panels of bright violet velvet, pleated
   (light and dark folds), each going 9vw past the screen's edge like the balcony's, meeting in the middle at a gold
   seam, a gold fringe along the hem */
.gallery-curtains {
  position: absolute;
  z-index: 6;
  inset: 0;
  overflow: hidden;
  cursor: pointer;
}
.gallery-curtains:focus-visible { outline: none; }
/* Opening (js/gallery.js, 3.1s), never catching a click on the way: each panel drawn slowly a little toward the
   middle first (0.9s, over the other there, easing in and out of it), then pulled away off its side, slowly (2.2s),
   picking up speed, the top leading */
.gallery-curtains.open { pointer-events: none; }
.gallery-curtains.open .gallery-curtain-left { animation: gallery-curtain-pull-left 3.1s forwards; }
.gallery-curtains.open .gallery-curtain-right { animation: gallery-curtain-pull-right 3.1s forwards; }
@keyframes gallery-curtain-pull-left {
  0% { transform: none; animation-timing-function: ease-in-out; }
  29% { transform: translateX(1.2%) skewX(-1deg); animation-timing-function: ease-in; }
  100% { transform: translateX(-100%) scaleX(0.65) skewX(12deg); }
}
@keyframes gallery-curtain-pull-right {
  0% { transform: none; animation-timing-function: ease-in-out; }
  29% { transform: translateX(-1.2%) skewX(1deg); animation-timing-function: ease-in; }
  100% { transform: translateX(100%) scaleX(0.65) skewX(-12deg); }
}
/* What to do, in the middle of the curtains: the balcony's hint (.rope-hint), drifting up and down a little; a gold
   glow around it when the curtains have the keyboard's focus; gone as soon as they start to open */
.gallery-curtains-hint {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  width: max-content;
  max-width: 86vw;
  margin: 0;
  padding: 7px 16px;
  translate: -50% -50%;
  font-family: var(--font-title);
  font-size: clamp(0.85rem, 2.6vw, 1.1rem);
  text-align: center;
  color: var(--ink);
  background: var(--enamel);
  border: 3px solid var(--gold);
  border-radius: 12px;
  box-shadow: 0 10px 24px rgb(0 0 0 / 0.5), inset 0 0 0 2px rgb(217 164 65 / 0.3);
  transition: opacity 0.3s ease;
  animation: gallery-curtains-hint-drift 2.4s ease-in-out infinite;
}
@keyframes gallery-curtains-hint-drift { 50% { translate: -50% calc(-50% - 6px); } }
.gallery-curtains:focus-visible .gallery-curtains-hint {
  box-shadow: 0 10px 24px rgb(0 0 0 / 0.5), 0 0 0 3px var(--enamel), 0 0 18px 6px rgb(255 207 122 / 0.8);
}
.gallery-curtains.open .gallery-curtains-hint { opacity: 0; animation: none; }
.gallery-curtain {
  position: absolute;
  top: 0;
  bottom: 0;
  width: calc(50% + 9vw);
  /* the hem in shadow; the folds from a muted lilac up to, on their lit ridge, a dimmed version of the violet white
     the gallery's door ends in when she goes through it (js/hall.js, HUGE_HUE) */
  background:
    linear-gradient(to top, rgb(50 20 100 / 0.45), transparent 40%),
    repeating-linear-gradient(90deg, #9c84d4 0, #b5a3e2 2.5%, #cfc3ef 4.5%, #b5a3e2 6.5%, #9c84d4 9%);
  will-change: transform;
}
/* each panel's own gold edge where they meet: a rounded gold trim, its shine a little in from the
   edge, with a soft shadow beside it, so the middle reads as two edges meeting in a soft crease, not one bar */
.gallery-curtain-left {
  left: -9vw;
  transform-origin: left bottom;
  background-image:
    linear-gradient(to left, var(--gold) 0, #f1d596 3.5px, var(--gold) 6px, #8a6420 9px, rgb(50 20 100 / 0.5) 9px, transparent 70px),
    linear-gradient(to top, rgb(50 20 100 / 0.45), transparent 40%),
    repeating-linear-gradient(90deg, #9c84d4 0, #b5a3e2 2.5%, #cfc3ef 4.5%, #b5a3e2 6.5%, #9c84d4 9%);
}
.gallery-curtain-right {
  right: -9vw;
  transform-origin: right bottom;
  background-image:
    linear-gradient(to right, var(--gold) 0, #f1d596 3.5px, var(--gold) 6px, #8a6420 9px, rgb(50 20 100 / 0.5) 9px, transparent 70px),
    linear-gradient(to top, rgb(50 20 100 / 0.45), transparent 40%),
    repeating-linear-gradient(90deg, #9c84d4 0, #b5a3e2 2.5%, #cfc3ef 4.5%, #b5a3e2 6.5%, #9c84d4 9%);
}
/* the gold fringe along the hem */
.gallery-curtain::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 18px;
  background:
    repeating-linear-gradient(90deg, var(--gold) 0 3px, transparent 3px 6px) bottom / 100% 12px no-repeat,
    linear-gradient(#f1d596, var(--gold)) top / 100% 6px no-repeat;
}
/* The little stars twinkling on the curtains and all over the room (js/gallery.js places them): four-pointed, white,
   all the same size, with a soft white glow around them, fading in, then out, then gone a while. The star and its
   glow are one small picture (the star in the middle 14 of its 36, two blurred copies of it under it for the glow),
   drawn once by the browser: each star then only fades, no filter or clip redone on every frame */
.gallery-sparkle {
  position: absolute;
  width: 36px;
  aspect-ratio: 1;
  translate: -50% -50%;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 36'%3E%3Cfilter id='a' x='-1' y='-1' width='3' height='3'%3E%3CfeGaussianBlur stdDeviation='1.4'/%3E%3C/filter%3E%3Cfilter id='b' x='-1' y='-1' width='3' height='3'%3E%3CfeGaussianBlur stdDeviation='3'/%3E%3C/filter%3E%3Cpath id='s' d='M18 11L19.1 16.9L25 18L19.1 19.1L18 25L16.9 19.1L11 18L16.9 16.9Z' fill='white'/%3E%3Cuse href='%23s' filter='url(%23b)'/%3E%3Cuse href='%23s' filter='url(%23a)'/%3E%3Cuse href='%23s'/%3E%3C/svg%3E")
    center / 100% 100% no-repeat;
  opacity: 0;
  pointer-events: none;
  animation: gallery-sparkle-twinkle var(--dur, 2.5s) ease-in-out var(--delay, 0s) infinite;
}
/* The walls' own stars: over the violet stone and its two wooden end panels (from under the beam and the frieze down
   to the floor, the whole width), in front of everything in the room (the pieces, the window, the stage, the
   lanterns, the motes: the same layer as the motes, and after them), but under the curtains (after them in the
   page) and the back sign (a layer up), never catching a click. Bigger than the curtains' but thinner (22 across, its arms still
   only 1 wide at the middle), with a stronger glow along its arms: its wider blurred copy laid twice under it, then
   a tighter one (in a bigger picture, 48 wide). The title plaque's stars (.gallery-sign-star) the same */
.gallery-sparkles {
  position: absolute;
  z-index: 4;
  inset: calc(var(--beam) + var(--frieze)) 0 var(--floor);
  overflow: hidden;
  pointer-events: none;
}
.gallery-sparkles .gallery-sparkle,
.gallery-sign-star {
  width: 48px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cdefs%3E%3Cfilter id='a' x='-1' y='-1' width='3' height='3'%3E%3CfeGaussianBlur stdDeviation='0.8'/%3E%3C/filter%3E%3Cfilter id='b' x='-1' y='-1' width='3' height='3'%3E%3CfeGaussianBlur stdDeviation='2.2'/%3E%3C/filter%3E%3Cpath id='s' d='M24 13L24.5 23.5L35 24L24.5 24.5L24 35L23.5 24.5L13 24L23.5 23.5Z' fill='white'/%3E%3C/defs%3E%3Cuse href='%23s' filter='url(%23b)'/%3E%3Cuse href='%23s' filter='url(%23b)'/%3E%3Cuse href='%23s' filter='url(%23a)'/%3E%3Cuse href='%23s'/%3E%3C/svg%3E");
}
@keyframes gallery-sparkle-twinkle {
  0%, 60%, 100% { opacity: 0; }
  30% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .gallery-sparkle { animation: none; opacity: 0.7; }
  .gallery-curtains-hint { animation: none; }
}

/* The two palace lanterns, hanging near the room's sides, out of the bands' way, each on a long gold rope from the very
   top of the page, down in front of the beam: a gold cap, a violet silk body with lilac ribs lit from inside (brightest
   at its heart), a gold cap, a knot and a violet tassel (::after); behind it, its lilac light on the wall, flickering a
   little like a candle's (::before). --lw: the lantern's width, the rest in proportion; --rope: how far down from
   the top of the page the lantern's drawing starts (the rope runs on, through its top, down to its cap) */
.gallery-lantern {
  --lw: clamp(22px, 4.5vw, 72px);
  --rope: calc(var(--beam) + 4svh);
  position: absolute;
  z-index: 1;
  top: 0;
  width: var(--lw);
  height: calc(var(--rope) + var(--lw) * 2.5);
  pointer-events: none;
  /* swaying gently, as in a light breeze, from where its rope is tied at the top of the page */
  transform-origin: 50% 0;
  animation: lantern-sway 5.2s ease-in-out infinite alternate;
}
.gallery-lantern-left { left: calc(clamp(14px, 3vw, 44px) + 1.2vw); }
.gallery-lantern-right { right: calc(clamp(14px, 3vw, 44px) + 1.2vw); animation-delay: -2.9s; }
@keyframes lantern-sway {
  from { rotate: -0.8deg; }
  to { rotate: 0.8deg; }
}
.gallery-lantern::before {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(var(--rope) + var(--lw) * 1.13); /* (the lantern body's heart) */
  width: calc(var(--lw) * 5);
  height: calc(var(--lw) * 5);
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(circle, rgb(215 170 255 / 0.45), rgb(180 130 255 / 0.15) 40%, transparent 70%);
  animation: lantern-flicker 3.2s ease-in-out infinite alternate;
}
.gallery-lantern-right::before { animation-delay: -1.4s; }
.gallery-lantern::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 150'%3E%3Cdefs%3E%3CradialGradient id='b' cx='0.5' cy='0.45' r='0.55'%3E%3Cstop offset='0' stop-color='%23ffe2a0'/%3E%3Cstop offset='0.35' stop-color='%23c88cf0'/%3E%3Cstop offset='0.75' stop-color='%237a4ad0'/%3E%3Cstop offset='1' stop-color='%23361a6e'/%3E%3C/radialGradient%3E%3ClinearGradient id='au' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23f7e3a8'/%3E%3Cstop offset='0.5' stop-color='%23d9a441'/%3E%3Cstop offset='1' stop-color='%238a6420'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect x='17' y='28' width='26' height='7' rx='2' fill='url(%23au)' stroke='%238a6420' stroke-width='0.8'/%3E%3Cellipse cx='30' cy='68' rx='27' ry='34' fill='url(%23b)'/%3E%3Cg fill='none' stroke='%23e8c4ff' stroke-opacity='0.75' stroke-width='1.3'%3E%3Cellipse cx='30' cy='68' rx='18' ry='34'/%3E%3Cellipse cx='30' cy='68' rx='9' ry='34'/%3E%3Cpath d='M30 34V102'/%3E%3C/g%3E%3Cellipse cx='30' cy='68' rx='27' ry='34' fill='none' stroke='%234a2a80' stroke-width='1.2'/%3E%3Crect x='17' y='100' width='26' height='7' rx='2' fill='url(%23au)' stroke='%238a6420' stroke-width='0.8'/%3E%3Cpath d='M30 107V112' stroke='%23d9a441' stroke-width='1.6'/%3E%3Cpath d='M30 110L34 115L30 120L26 115Z' fill='url(%23au)' stroke='%238a6420' stroke-width='0.6'/%3E%3Crect x='25.5' y='120' width='9' height='4' rx='1' fill='url(%23au)'/%3E%3Cpath d='M25.5 124H34.5L38 149H22Z' fill='%237a5ad0'/%3E%3Cpath d='M27.5 125L26 148M30 125V148M32.5 125L34 148' stroke='%234a2a80' stroke-width='0.8'/%3E%3C/svg%3E")
      bottom / 100% calc(var(--lw) * 2.5) no-repeat,
    /* the rope, from the top of the page all the way down to the lantern's top cap (28/150 down its drawing) */
    linear-gradient(90deg, #b98a2e, #f7e3a8 50%, #b98a2e)
      center top / max(2.5px, calc(var(--lw) * 0.05)) calc(var(--rope) + var(--lw) * 0.47) no-repeat;
  filter: drop-shadow(0 0 0.6vw rgb(200 150 255 / 0.5));
}
@keyframes lantern-flicker {
  0% { opacity: 0.85; scale: 1; }
  40% { opacity: 1; scale: 1.04; }
  70% { opacity: 0.9; scale: 0.98; }
  100% { opacity: 1; scale: 1.02; }
}
@media (prefers-reduced-motion: reduce) {
  .gallery-lantern,
  .gallery-lantern::before { animation: none; }
}

/* The title: a palace plaque in violet lacquer. Its outline: the top a gentle arc from each corner up to a slight point
   at the middle, small tight corners, straight sides, and at the bottom the sides sweeping out in a curve to its full
   width, flat along the bottom. A heavy gold edge all along that outline, a band of deep violet inside it, then a fine
   gold line; the name in gold catching the light from above. Sizes in em, from its font size, so it all scales together (and fits a narrow phone).
   The outline is one SVG path stretched over the box (its size all in em, so its shape stays the same); the edge and
   bands are strokes along it, clipped to its inside, so they follow the curves */
.gallery-sign {
  position: relative;
  z-index: 3; /* (in front of the hanging pieces: their cords pass behind it, up to the beam) */
  padding: 0.82em 3.45em 0.57em; /* (the sides wide enough for the narrower top, see the outline below) */
  font-size: clamp(1rem, min(4svh, 4.8vw), 2.6rem); /* (with the scrolls and curls, still within a narrow phone) */
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 100' preserveAspectRatio='none'%3E%3Cdefs%3E%3Cpath id='p' d='M0 100C30 97 60 84 60 52V16Q60 6 72 5.5C150 4 230 2 300 0C370 2 450 4 528 5.5Q540 6 540 16V52C540 84 570 97 600 100Z'/%3E%3CclipPath id='c'%3E%3Cuse href='%23p'/%3E%3C/clipPath%3E%3ClinearGradient id='r' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%236a5ab0'/%3E%3Cstop offset='0.6' stop-color='%234a3c80'/%3E%3Cstop offset='1' stop-color='%232e2656'/%3E%3C/linearGradient%3E%3CradialGradient id='l' cx='0.5' cy='0.15' r='0.7'%3E%3Cstop offset='0' stop-color='%23ffd2a0' stop-opacity='0.18'/%3E%3Cstop offset='1' stop-color='%23ffd2a0' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Cg clip-path='url(%23c)' fill='none'%3E%3Cuse href='%23p' fill='url(%23r)'/%3E%3Cuse href='%23p' fill='url(%23l)'/%3E%3Cuse href='%23p' stroke='%23d9a441' stroke-width='33'/%3E%3Cuse href='%23p' stroke='%232e2656' stroke-width='28'/%3E%3Cuse href='%23p' stroke='%23f3d58a' stroke-width='15'/%3E%3Cuse href='%23p' stroke='%23d9a441' stroke-width='11'/%3E%3Cuse href='%23p' stroke='%238a6420' stroke-width='3'/%3E%3C/g%3E%3C/svg%3E")
    center / 100% 100% no-repeat;
  filter: drop-shadow(0 10px 12px rgb(0 0 0 / 0.55));
}
/* its ornaments: a carved gold scroll bracket on each side, centered on the plaque (two volutes springing from its
   side, one curling up and one down into a spiral, and between them a tapered leaf ending in a gold bead; gilded:
   lit from above, a dark carved outline, a thin highlight along the curls); and, in one box reaching out past its
   sides and below it, a base under it, as wide as the plaque's bottom (a gold top edge, a band of deep violet, a darker
   gold bottom edge), and from each end of the base a violet silk tassel hanging from a gold knot. The scrolls are drawn at a
   fixed shape for each side (not stretched with the plaque's width), the right one mirrored */
/* the scroll brackets, one on each side: their own box, from the outline's sides (10% in from the plaque's edges, see
   the outline above) out by a scroll's width, a hair less so they touch the gold edge */
.gallery-sign::before {
  --ear: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 52 90'%3E%3Cdefs%3E%3ClinearGradient id='au' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23fbecc0'/%3E%3Cstop offset='0.5' stop-color='%23e2b24e'/%3E%3Cstop offset='1' stop-color='%23a57d2e'/%3E%3C/linearGradient%3E%3Cpath id='s' d='M52 30C39 30 26 26 23.4 18C20.8 10 33.8 6 39 12C42.9 17 35.1 20 32.5 17M52 60C39 60 26 64 23.4 72C20.8 80 33.8 84 39 78C42.9 73 35.1 70 32.5 73' fill='none' stroke-linecap='round'/%3E%3C/defs%3E%3Cg%3E%3Cuse href='%23s' stroke='%236a4a14' stroke-width='9'/%3E%3Cuse href='%23s' stroke='url(%23au)' stroke-width='5.5'/%3E%3Cuse href='%23s' stroke='%23fff3c4' stroke-width='1.2' stroke-opacity='0.6'/%3E%3Cpath d='M52 39C40.3 40 32.5 42 27.3 45C32.5 48 40.3 50 52 51Z' fill='url(%23au)' stroke='%236a4a14' stroke-width='1.8' stroke-linejoin='round'/%3E%3Ccircle cx='20.8' cy='45' r='4.6' fill='url(%23au)' stroke='%236a4a14' stroke-width='1.8'/%3E%3C/g%3E%3C/svg%3E");
  --ear-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 52 90'%3E%3Cdefs%3E%3ClinearGradient id='au' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23fbecc0'/%3E%3Cstop offset='0.5' stop-color='%23e2b24e'/%3E%3Cstop offset='1' stop-color='%23a57d2e'/%3E%3C/linearGradient%3E%3Cpath id='s' d='M52 30C39 30 26 26 23.4 18C20.8 10 33.8 6 39 12C42.9 17 35.1 20 32.5 17M52 60C39 60 26 64 23.4 72C20.8 80 33.8 84 39 78C42.9 73 35.1 70 32.5 73' fill='none' stroke-linecap='round'/%3E%3C/defs%3E%3Cg transform='translate(52 0) scale(-1 1)'%3E%3Cuse href='%23s' stroke='%236a4a14' stroke-width='9'/%3E%3Cuse href='%23s' stroke='url(%23au)' stroke-width='5.5'/%3E%3Cuse href='%23s' stroke='%23fff3c4' stroke-width='1.2' stroke-opacity='0.6'/%3E%3Cpath d='M52 39C40.3 40 32.5 42 27.3 45C32.5 48 40.3 50 52 51Z' fill='url(%23au)' stroke='%236a4a14' stroke-width='1.8' stroke-linejoin='round'/%3E%3Ccircle cx='20.8' cy='45' r='4.6' fill='url(%23au)' stroke='%236a4a14' stroke-width='1.8'/%3E%3C/g%3E%3C/svg%3E");
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(10% - 0.93em);
  right: calc(10% - 0.93em);
  pointer-events: none;
  background:
    var(--ear) left center / 0.98em 1.7em no-repeat,
    var(--ear-right) right center / 0.98em 1.7em no-repeat;
  filter: drop-shadow(0 3px 3px rgb(0 0 0 / 0.45));
}
.gallery-sign::after {
  --tassel: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 60'%3E%3Cpath d='M10 0V13' stroke='%23d9a441' stroke-width='1.6'/%3E%3Cpath d='M10 11L15 17L10 23L5 17Z' fill='%23d9a441' stroke='%238a6420'/%3E%3Crect x='6.5' y='24' width='7' height='4' rx='1' fill='%23d9a441' stroke='%238a6420' stroke-width='0.6'/%3E%3Cpath d='M6.5 28H13.5L16 58H4Z' fill='%237a5ad0'/%3E%3Cpath d='M8 29L7 57M10 29V57M12 29L13 57' stroke='%234a2a80' stroke-width='0.8'/%3E%3C/svg%3E");
  content: "";
  position: absolute;
  /* (out 0.23em past its sides, so the base, 0.23em in from there, is exactly as wide as the plaque's bottom) */
  inset: -0.3em -0.23em -1.9em;
  pointer-events: none;
  background:
    var(--tassel) left 0.43em bottom 0 / 0.5em 1.6em no-repeat,
    var(--tassel) right 0.43em bottom 0 / 0.5em 1.6em no-repeat,
    linear-gradient(#f7e3a8, #d9a441 22%, #8a6420 26%, #4a3c80 30% 70%, #2e2656 74%, #d9a441 78%, #8a6420)
      center bottom 1.55em / calc(100% - 0.46em) 0.4em no-repeat;
  filter: drop-shadow(0 3px 3px rgb(0 0 0 / 0.45));
}
/* the small gold cloud curls on either side of the name (drawn for the left, mirrored for the right) */
.gallery-sign h1::before,
.gallery-sign h1::after {
  content: "";
  display: inline-block;
  width: 0.8em;
  height: 0.5em;
  vertical-align: 0.15em;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 20'%3E%3Cpath d='M2 15C2 6 12 3 15 9C17 13 12 15 10 12M15 9C19 3 28 5 28 12' fill='none' stroke='%23e7c26a' stroke-width='2.2' stroke-linecap='round'/%3E%3Ccircle cx='28' cy='14' r='1.6' fill='%23e7c26a'/%3E%3C/svg%3E")
    center / contain no-repeat;
}
.gallery-sign h1::before { margin-right: 0.35em; }
.gallery-sign h1::after { margin-left: 0.35em; scale: -1 1; }
.gallery-sign h1 {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 400;
  font-size: 1em;
  line-height: 1.1;
  letter-spacing: 0.06em;
  white-space: nowrap;
  color: #f3d58a; /* (where the gold fill below isn't supported) */
  background: linear-gradient(#fff3c4, #f3d58a 45%, #d9a441 70%, #a57d2e);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 2px 0 rgb(40 8 4 / 0.85)) drop-shadow(0 0 0.35em rgb(255 200 120 / 0.35));
}
/* A gleam of light sweeping across the whole plaque now and then, left to right, like light glancing off the gilding:
   a soft slanted band of white light (::before), slid across (a transform: the browser only moves it), in a box cut
   to the plaque's own outline (the same path, as a mask), so it never shows past its edges. Over the name too. It
   crosses in the first 1.2s of every 5s, then the plaque rests */
.gallery-sign-shine {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 100' preserveAspectRatio='none'%3E%3Cpath d='M0 100C30 97 60 84 60 52V16Q60 6 72 5.5C150 4 230 2 300 0C370 2 450 4 528 5.5Q540 6 540 16V52C540 84 570 97 600 100Z'/%3E%3C/svg%3E")
    center / 100% 100% no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 100' preserveAspectRatio='none'%3E%3Cpath d='M0 100C30 97 60 84 60 52V16Q60 6 72 5.5C150 4 230 2 300 0C370 2 450 4 528 5.5Q540 6 540 16V52C540 84 570 97 600 100Z'/%3E%3C/svg%3E")
    center / 100% 100% no-repeat;
}
.gallery-sign-shine::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 35%;
  background: linear-gradient(110deg, transparent 20%, rgb(255 250 230 / 0.55) 50%, transparent 80%);
  transform: translateX(-100%);
  animation: gallery-sign-gleam 5s ease-in-out 1s infinite;
}
/* (from just past its left edge to just past its right: its box 35% of the plaque, so 100/35 of its own width) */
@keyframes gallery-sign-gleam {
  0% { transform: translateX(-100%); }
  24%, 100% { transform: translateX(calc(100% / 0.35)); }
}
/* the two stars on its corners (gallery.html places them): the walls' thin glowing stars, a little smaller */
.gallery-sign-star { width: 1.6em; }
/* A soft glow of violet white light (the gallery door's own, js/hall.js HUGE_HUE) all around the plaque's outline,
   slowly breathing (brighter, dimmer, 3.5s each way). The plaque's outline itself filled with it (the same path,
   in the plaque's place: 1em in from this box's sides) and blurred out past its edges; then the plaque's own shape
   cut out of it (a mask: everything, minus the outline), so the glow only lights the wall around it, never its
   lacquer. Behind the name, the scroll brackets and the tassels (z-index -1: the first thing on the plaque) */
.gallery-sign-glow {
  --outline: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 100' preserveAspectRatio='none'%3E%3Cpath d='M0 100C30 97 60 84 60 52V16Q60 6 72 5.5C150 4 230 2 300 0C370 2 450 4 528 5.5Q540 6 540 16V52C540 84 570 97 600 100Z' fill='%23efe6ff'/%3E%3C/svg%3E");
  position: absolute;
  z-index: -1;
  inset: -1em;
  pointer-events: none;
  background: var(--outline) 1em 1em / calc(100% - 2em) calc(100% - 2em) no-repeat;
  filter: blur(0.45em);
  -webkit-mask: var(--outline) 1em 1em / calc(100% - 2em) calc(100% - 2em) no-repeat, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: var(--outline) 1em 1em / calc(100% - 2em) calc(100% - 2em) no-repeat exclude, linear-gradient(#000 0 0);
  opacity: 0.45;
  animation: gallery-sign-breathe 3.5s ease-in-out infinite alternate;
}
@keyframes gallery-sign-breathe {
  to { opacity: 1; }
}
/* Light moving over the plaque's lacquer, a simpler version of the walls' wash (js/gallery.js, makeWash): its own
   violet only, no other hue, lighter here and deeper there, changing all the time. Not hundreds of blurred blobs in
   drifting canvases, but a dozen soft patches of those shades (.gallery-sign-patch, below), each fading and wandering
   on its own, the browser only fading and moving them, laid plainly over the lacquer (not "lighten": the deeper ones
   have to show too).
   On its violet only, never its gold: cut by a mask drawn from the plaque's own outline and strokes (see
   .gallery-sign's background: from its edge in, dark, gold, light gold, then the band of deep violet, a fine gold
   line, the violet fill), keeping the violet band and the fill. Behind the name, the gleam and the scroll brackets
   (z-index -1, after the glow) */
.gallery-sign-tint {
  position: absolute;
  z-index: -1;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 100' preserveAspectRatio='none'%3E%3Cdefs%3E%3Cpath id='p' d='M0 100C30 97 60 84 60 52V16Q60 6 72 5.5C150 4 230 2 300 0C370 2 450 4 528 5.5Q540 6 540 16V52C540 84 570 97 600 100Z'/%3E%3CclipPath id='c'%3E%3Cuse href='%23p'/%3E%3C/clipPath%3E%3Cmask id='m'%3E%3Cuse href='%23p' fill='white'/%3E%3Cg clip-path='url(%23c)' fill='none'%3E%3Cuse href='%23p' stroke='black' stroke-width='33'/%3E%3Cuse href='%23p' stroke='white' stroke-width='28'/%3E%3Cuse href='%23p' stroke='black' stroke-width='15'/%3E%3C/g%3E%3C/mask%3E%3C/defs%3E%3Crect width='600' height='100' fill='white' mask='url(%23m)'/%3E%3C/svg%3E")
    center / 100% 100% no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 100' preserveAspectRatio='none'%3E%3Cdefs%3E%3Cpath id='p' d='M0 100C30 97 60 84 60 52V16Q60 6 72 5.5C150 4 230 2 300 0C370 2 450 4 528 5.5Q540 6 540 16V52C540 84 570 97 600 100Z'/%3E%3CclipPath id='c'%3E%3Cuse href='%23p'/%3E%3C/clipPath%3E%3Cmask id='m'%3E%3Cuse href='%23p' fill='white'/%3E%3Cg clip-path='url(%23c)' fill='none'%3E%3Cuse href='%23p' stroke='black' stroke-width='33'/%3E%3Cuse href='%23p' stroke='white' stroke-width='28'/%3E%3Cuse href='%23p' stroke='black' stroke-width='15'/%3E%3C/g%3E%3C/mask%3E%3C/defs%3E%3Crect width='600' height='100' fill='white' mask='url(%23m)'/%3E%3C/svg%3E")
    center / 100% 100% no-repeat;
}
/* Its patches (js/gallery.js scatters them: where, how big, which shade, their paces): each a soft spot of a lighter
   or a deeper shade of the lacquer's violet (--shade), fading in and out on its own (--pulse), and wandering a little
   on its own (--wander, by --dx / --dy of its size): so here the lacquer brightens, there it deepens, elsewhere it's
   back to itself, never all together */
.gallery-sign-patch {
  position: absolute;
  translate: -50% -50%;
  background: radial-gradient(closest-side, var(--shade), transparent);
  opacity: 0;
  animation:
    gallery-sign-patch-pulse var(--pulse, 2.5s) ease-in-out var(--pulse-delay, 0s) infinite alternate,
    gallery-sign-patch-wander var(--wander, 4s) ease-in-out var(--wander-delay, 0s) infinite alternate;
}
/* (never all the way: even at its strongest, the lacquer's violet shows through) */
@keyframes gallery-sign-patch-pulse {
  to { opacity: 0.6; }
}
@keyframes gallery-sign-patch-wander {
  from { transform: translate(calc(-1 * var(--dx, 0%)), calc(-1 * var(--dy, 0%))); }
  to { transform: translate(var(--dx, 0%), var(--dy, 0%)); }
}
@media (prefers-reduced-motion: reduce) {
  .gallery-sign-shine::before,
  .gallery-sign-glow,
  .gallery-sign-patch { animation: none; }
  .gallery-sign-patch { opacity: 0.3; }
}

/* The three bands. In each, --w (from the screen's width) and --h (from its height) are the most a piece can take: it
   keeps its own shape inside them, so on a wide screen the height decides its size, on a tall phone the width. Each
   band's widths add up to under the room's width, frames and gaps included */
.gallery-hanging,
.gallery-pillars { display: flex; justify-content: space-evenly; width: 100%; }
.gallery-hanging {
  --w: 16vw;
  --h: 10svh;
  /* in front of the centerpiece's layer, so where the window's arch rises behind them they hang in front of it */
  position: relative;
  z-index: 2;
  align-items: center;
  padding-top: 4svh; /* room for the cords */
}
/* the outer two hung higher than the middle two, like a salon wall (only moved, so nothing else shifts) */
.gallery-hanging > :first-child,
.gallery-hanging > :last-child { translate: 0 -5svh; }
.gallery-centerpiece {
  --w: 21vw;
  --h: 17svh;
  --frame: clamp(5px, 1.1vw, 14px); /* (the window's frame: the window and its sill use it) */
  --window-out: clamp(12px, 7vw, 110px); /* (how far the window reaches out past the pieces on each side) */
  /* the stage under it (::after): how deep its top looks (the deeper, the more it seems seen from above), and where
     its front starts, under the white edge */
  --stage-top: 7.5svh;
  --stage-front: calc(var(--stage-top) + 0.8svh);
  /* pinned to its cell, as the stage's letter shares it (pinned there too): otherwise the grid would move it to
     another row or column */
  grid-row: 3;
  grid-column: 1;
  position: relative;
  /* its own layer, with the window and the stage behind the pieces but in front of the wall */
  z-index: 1;
  display: flex;
  align-items: flex-end; /* (all 3 standing on the stage) */
  justify-content: center;
  gap: clamp(8px, 2.5vw, 48px);
  max-width: 100%;
}
/* The big window behind the 3 pieces (its own element, js/gallery.js puts it first in the centerpiece), the shape of
   the hall's mosaic window she came through (straight sides, an oval top): the palace's night outside (a moon and a
   few stars over far mountains). A little wider than the pieces and rising above them.
   Its frame, every layer following the arch: outside, a gold molding; the deep indigo frame itself (--frame); inside,
   a thin gold lip, a band of violet lacquer and a fine gold line around the sky. Gold fretwork (the key pattern of lattice
   windows) in its two lower corners, a gold arch over its top (.gallery-window-arch), and under it a molded sill
   (.gallery-centerpiece::before) */
.gallery-window {
  --fret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Cpath d='M3 3V37H37M9 37V15H17V31H25V37M9 9V11M15 9H31' fill='none' stroke='%23e2b24e' stroke-width='2.6' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/svg%3E");
  --fret-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Cg transform='translate(40 0) scale(-1 1)'%3E%3Cpath d='M3 3V37H37M9 37V15H17V31H25V37M9 9V11M15 9H31' fill='none' stroke='%23e2b24e' stroke-width='2.6' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/g%3E%3C/svg%3E");
  position: absolute;
  z-index: -1;
  inset: -3svh calc(-1 * var(--window-out)) 0;
  background:
    var(--fret) left 4px bottom calc(max(8px, 1.5svh) + 2px) / clamp(22px, 8svh, 72px) clamp(22px, 8svh, 72px) no-repeat,
    var(--fret-right) right 4px bottom calc(max(8px, 1.5svh) + 2px) / clamp(22px, 8svh, 72px) clamp(22px, 8svh, 72px) no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 60' preserveAspectRatio='none'%3E%3Cpath d='M0 40L50 18L90 34L150 8L210 30L260 14L320 36L360 22L400 32V60H0Z' fill='%23243258'/%3E%3Cpath d='M0 50L70 36L130 48L200 34L280 50L340 40L400 46V60H0Z' fill='%23161e3a'/%3E%3C/svg%3E")
      bottom / 100% 22% no-repeat,
    radial-gradient(circle at 80% 20%, #fbecc4 0 3.5%, rgb(251 236 196 / 0.25) 4% 8%, transparent 11%),
    radial-gradient(circle at 12% 18%, #fff 0 0.4%, transparent 0.6%),
    radial-gradient(circle at 28% 32%, #fff 0 0.3%, transparent 0.5%),
    radial-gradient(circle at 62% 12%, #fff 0 0.35%, transparent 0.55%),
    radial-gradient(circle at 90% 44%, #fff 0 0.3%, transparent 0.5%),
    radial-gradient(circle at 45% 26%, #fff 0 0.25%, transparent 0.45%),
    linear-gradient(#0c1534, #1d2b5a 65%, #4a4470);
  border: var(--frame) solid #241e44;
  border-bottom: 0;
  border-radius: 50% 50% 0 0 / 30% 30% 0 0;
  box-shadow:
    inset 0 0 0 1.5px #f3d58a,
    inset 0 0 0 3.5px #4a3c80,
    inset 0 0 0 4.5px rgb(217 164 65 / 0.75),
    inset 0 0 30px rgb(0 0 0 / 0.6),
    0 0 0 2px #d9a441,
    0 10px 30px rgb(0 0 0 / 0.5);
}
/* A gold arch over the window, on the curved top only: a gilded band (--band) just outside the frame's gold molding,
   following the arch, darker gold along both its edges and a pale highlight inside them (so it looks rounded), fading
   out where the arch turns into the straight sides */
.gallery-window-arch {
  --band: clamp(4px, 0.8vw, 10px);
  position: absolute;
  inset: calc(-1 * (var(--frame) + 2px + var(--band)));
  bottom: 0;
  border: var(--band) solid #e2b24e;
  border-bottom: 0;
  border-radius: 50% 50% 0 0 / 30% 30% 0 0;
  box-shadow:
    0 0 0 1px #8a6420,
    inset 0 0 0 1px #8a6420,
    0 -1px 0 1px rgb(255 243 196 / 0.55),
    0 3px 6px rgb(0 0 0 / 0.45);
  -webkit-mask-image: linear-gradient(#000 26%, transparent 36%);
  mask-image: linear-gradient(#000 26%, transparent 36%);
}
/* the window's sill (on the centerpiece, just under the window's bottom): a molded ledge a little wider than the
   window (a gold top edge, a band of deep violet, a darker gold bottom edge), its shadow on the wall. In front of the
   window, behind the pieces */
.gallery-centerpiece::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: calc(-1 * var(--window-out) - var(--frame) - 2%);
  right: calc(-1 * var(--window-out) - var(--frame) - 2%);
  bottom: 0;
  height: max(8px, 1.5svh);
  border-radius: 2px;
  background: linear-gradient(#f7e3a8, #d9a441 22%, #8a6420 30%, #4a3c80 36% 68%, #2e2656 74%, #d9a441 80%, #8a6420);
  box-shadow: 0 4px 8px rgb(0 0 0 / 0.5);
}
/* The stage the 3 pieces stand on, across the whole room and coming forward toward the viewer, in warm wood (like the
   wall's two end panels): its top seen from a little above (boards fanning out a little toward the front, lit warm
   toward its front edge), a white edge of light along the front, and its front in the walls' enchanted lilac stone with the
   steps' drifting colors over it, darker toward the floor. The pillars stand in front of it */
.gallery-centerpiece::after {
  content: "";
  position: absolute;
  z-index: -1;
  top: 100%;
  left: 50%;
  width: 100vw;
  height: 70svh;
  translate: -50% 0;
  /* (its top's depth, --stage-top: on .gallery-centerpiece. Its boards fan out 1.28 times as wide at the front edge as
     at the wall, meeting the front edge every 8% of the width, from 2%) */
  background:
    /* (the white edge's glow, up over the top: its halo below it is the front's first layer) */
    linear-gradient(transparent, rgb(255 255 255 / 0.15) calc(100% - 1.5svh), rgb(255 255 255 / 0.6))
      0 calc(var(--stage-top) - 2.5svh) / 100% 2.5svh no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath vector-effect='non-scaling-stroke' stroke='%23000' stroke-opacity='0.3' fill='none' d='M0 0L-14 10M6.25 0L-6 10M12.5 0L2 10M18.75 0L10 10M25 0L18 10M31.25 0L26 10M37.5 0L34 10M43.75 0L42 10M50 0L50 10M56.25 0L58 10M62.5 0L66 10M68.75 0L74 10M75 0L82 10M81.25 0L90 10M87.5 0L98 10M93.75 0L106 10M100 0L114 10'/%3E%3C/svg%3E")
      top / 100% var(--stage-top) no-repeat,
    linear-gradient(#4a2a16, #5e3a1e 45%, #8a5430) top / 100% var(--stage-top) no-repeat,
    linear-gradient(#fff, #fff) 0 var(--stage-top) / 100% 0.8svh no-repeat,
    /* (the front: the walls' enchanted lilac stone, the white edge's glow spilling down from under it (like the
       light from beneath the steps up to the room's door in the hall), its slabs, where the top's boards meet its
       front edge, parted by dark brown joints (the top's wood), darker toward the floor; the steps' drifting colors over it:
       .gallery-stage-magic) */
    linear-gradient(rgb(255 255 255 / 0.7), rgb(255 255 255 / 0.2) 1.5svh, transparent 4svh) 0 var(--stage-front) / 100% 100% no-repeat,
    repeating-linear-gradient(90deg, rgb(74 42 22 / 0.9) 0 1px, transparent 1px 8vw) 2vw var(--stage-front) / 100% 100% no-repeat,
    linear-gradient(#8a7fbd, #6f63a6 18svh, #574b8c 40svh) 0 var(--stage-front) / 100% 100% no-repeat,
    #574b8c;
}
/* The letter on the stage (gallery.html), lying on its top under the main piece's right half (--size: its
   width, also how far it's moved over), turned a little as if left there; at most 9svh wide, so it stays on the
   stage's top (7.5svh deep). Lifts a little under the pointer.
   In the centerpiece's grid cell, not inside it: the centerpiece's layer is behind the pillars', so a piece standing
   on a pillar could cover it; here it has its own layer in front of every band. Its bottom starts at the
   centerpiece's (the stage's top edge), then it's moved down by its own height (5/12 of its width) and 2.2svh more */
.stage-letter {
  --size: min(clamp(44px, 6vw, 90px), 9svh);
  grid-row: 3; /* (the room's bands: the sign, the hanging pieces, the centerpiece, the pillars) */
  grid-column: 1;
  align-self: end;
  position: relative;
  z-index: 3;
  top: calc(var(--size) * 5 / 12 + 2.2svh);
  left: calc(var(--size) * 0.8);
  width: var(--size);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  rotate: -7deg;
  filter: drop-shadow(0 0.4svh 0.4svh rgb(0 0 0 / 0.5));
  transition: translate 0.2s, filter 0.2s;
}
.stage-letter svg { display: block; width: 100%; height: auto; }
/* until it's been opened, a tight white glow breathing around it, so it isn't missed (like the reading room's photos,
   .cabin-pics); paused while pointed at, for its own lift (below). Once opened (.found, js/gallery.js), it goes away */
.stage-letter:not(.found) {
  filter: drop-shadow(0 0.4svh 0.4svh rgb(0 0 0 / 0.5)) drop-shadow(0 0 2px rgb(255 255 255 / 0.6))
    drop-shadow(0 0 4px rgb(255 255 255 / 0.4));
  animation: stage-letter-glow 3s ease-in-out infinite alternate;
}
/* lit from 80%: with alternate, it stays lit ~1.2s at each peak before dimming again */
@keyframes stage-letter-glow {
  80%, to {
    filter: drop-shadow(0 0.4svh 0.4svh rgb(0 0 0 / 0.5)) drop-shadow(0 0 3px rgb(255 255 255 / 1))
      drop-shadow(0 0 7px rgb(255 255 255 / 0.85)) brightness(1.08);
  }
}
@media (prefers-reduced-motion: reduce) {
  .stage-letter { animation: none; }
}
.stage-letter:hover,
.stage-letter:focus-visible {
  translate: 0 -0.6svh;
  filter: drop-shadow(0 1.2svh 0.8svh rgb(0 0 0 / 0.45)) drop-shadow(0 0 0.6svh rgb(255 220 150 / 0.5));
  animation: none;
}
.stage-letter:focus-visible { outline: 2px solid #f3d58a; outline-offset: 4px; }
/* The colored glow over the stage's front (js/gallery.js adds it), as on the steps up to the room's door in the hall:
   their drifting blobs of color (.gallery-blob), each hue showing only where it's brighter than the stone, vivid like
   the walls' and breathing, fading out toward the floor (its hues and its fade drawn into its pictures, js/gallery.js,
   makeWash). In front of the stage, under the pieces, never catching a click */
.gallery-stage-magic {
  position: absolute;
  z-index: 0;
  top: calc(100% + var(--stage-front));
  left: 50%;
  width: 100vw;
  height: 30svh;
  translate: -50% 0;
  overflow: hidden;
  pointer-events: none;
  mix-blend-mode: lighten;
  opacity: 0.88;
  animation: gallery-stage-breathe 7s ease-in-out infinite alternate;
}
/* the joints between the front's slabs keep their own color: the same lines (as on .gallery-centerpiece::after, right
   over them) in black on the wash, which with "lighten" leaves what's under them as it is */
.gallery-stage-magic::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(90deg, #000 0 1px, transparent 1px 8vw) 2vw 0 / 100% 100% no-repeat;
}
@keyframes gallery-stage-breathe {
  from { opacity: 0.75; }
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .gallery-stage-magic,
  .gallery-stage-magic .gallery-blob { animation: none; }
}
.gallery-main { --w: 24vw; --h: 20svh; }
.gallery-pillars { --w: 14vw; --h: 10svh; align-items: flex-end; position: relative; z-index: 1; /* (in front of the stage) */ }
/* the pillars in two pairs, a wider gap between them in the middle: room on the stage behind it for the letter, so
   no piece on a pillar reaches up over it */
.gallery-pillars > .gallery-piece:nth-child(2) { margin-right: 6vw; }
.gallery-pillars > .gallery-piece:nth-child(3) { margin-left: 6vw; }
/* The floor in front of the stage, from the foot of its front (where the pillars stand) to the bottom of the screen,
   across the whole room: polished stone slabs seen from a little above, their joints fanning out toward the viewer
   and the cross joints farther apart the closer they are, lit at the back, darker toward the front; a glowing white
   line where it meets the stage. Behind the pillars (below them in their band), in front of the stage */
.gallery-pillars::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: calc(100% - 1.2svh);
  left: 50%;
  width: 100vw;
  height: 100svh;
  translate: -50% 0;
  background:
    /* (its edge where it meets the stage: a white line of light, like the stage's own, glowing down over the floor,
       and up over the stage's front with the box-shadow) */
    linear-gradient(#fff, #fff) top / 100% 0.6svh no-repeat,
    linear-gradient(rgb(255 255 255 / 0.6), rgb(255 255 255 / 0.15) 1.2svh, transparent 3svh) 0 0.6svh / 100% 3svh no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 20' preserveAspectRatio='none'%3E%3Cpath vector-effect='non-scaling-stroke' stroke='%23000' stroke-opacity='0.35' fill='none' d='M0 0L-100 20M6.25 0L-81.25 20M12.5 0L-62.5 20M18.75 0L-43.75 20M25 0L-25 20M31.25 0L-6.25 20M37.5 0L12.5 20M43.75 0L31.25 20M50 0L50 20M56.25 0L68.75 20M62.5 0L87.5 20M68.75 0L106.25 20M75 0L125 20M81.25 0L143.75 20M87.5 0L162.5 20M93.75 0L181.25 20M100 0L200 20M0 1.2H100M0 3H100M0 6H100M0 10H100M0 15.5H100'/%3E%3Cpath vector-effect='non-scaling-stroke' stroke='%23fff' stroke-opacity='0.08' fill='none' d='M0 1.4H100M0 3.2H100M0 6.2H100M0 10.2H100'/%3E%3C/svg%3E")
      0 0.6svh / 100% 30svh no-repeat,
    linear-gradient(#3a3940, #26252b 12svh, #19181d 30svh, #0f0e12);
  box-shadow: 0 -0.3svh 1.6svh rgb(255 255 255 / 0.55);
}

.gallery-piece { display: flex; flex-direction: column; align-items: center; }

/* The frame: dark rosewood, a gold edge outside and a fine gold line inside, a cream mat around the art. Its
   thickness (--t) follows the piece's size, whichever of its two limits is smaller, so a narrow phone gets thin frames.
   The close-up's frame is the same (its own size and thickness, see .gallery-closeup-frame) */
.gallery-frame,
.gallery-closeup-frame {
  --t: min(var(--h), var(--w));
  position: relative;
  display: block;
  padding: calc(var(--t) * 0.06);
  background: linear-gradient(#f6eedb, #e8dcc0);
  border: calc(var(--t) * 0.045) solid #3a1a0e;
  border-image: linear-gradient(135deg, #5a2c16, #2a1208 50%, #4a2412) 1;
  outline: 2px solid var(--gold);
  /* (and its shadow on the wall behind it, lit from above on the left: a tight dark one right along its edge, and a
     wider, softer one cast down and to the right, in a deep violet, so it reads as shade on the lilac stone) */
  box-shadow:
    inset 0 0 0 1px rgb(217 164 65 / 0.7),
    inset 0 2px 6px rgb(0 0 0 / 0.25),
    0.3svh 0.5svh 0.6svh rgb(20 8 40 / 0.55),
    1.2svh 2svh 2.4svh rgb(20 8 40 / 0.5);
}
.gallery-frame img,
.gallery-closeup-frame img { display: block; width: auto; height: auto; max-width: var(--w); max-height: var(--h); }
/* each piece opens its close-up (js/gallery.js) */
.gallery-frame { cursor: pointer; }
.gallery-frame:focus-visible { outline: 3px solid #fff3c4; outline-offset: 3px; }
/* A piece still loading (.is-loading, js/gallery.js): its mat kept square at the frame's smaller limit, the image
   hidden until it's in (it fades in), and a ring spinning in the middle, like the loading screen's (.scene-spinner).
   The ring only fades in after a moment, so a piece that comes in quickly never shows it. Gold on the cream mat,
   pale gold on the main piece's black one */
.gallery-frame img,
.gallery-closeup-frame img { transition: opacity 0.3s ease-out; }
:is(.gallery-frame, .gallery-closeup-frame).is-loading img {
  min-width: min(var(--w), var(--h));
  min-height: min(var(--w), var(--h));
  opacity: 0;
}
.gallery-spinner {
  --size: clamp(18px, calc(var(--t) * 0.22), 48px);
  position: absolute;
  inset: 0;
  width: var(--size);
  height: var(--size);
  margin: auto;
  border-radius: 50%;
  border: calc(var(--size) * 0.09) solid rgb(58 26 14 / 0.15);
  border-top-color: var(--gold);
  pointer-events: none;
  display: none;
}
.is-loading > .gallery-spinner {
  display: block;
  animation: spin 0.9s linear infinite, gallery-spinner-in 0.3s ease-out 0.4s backwards;
}
@keyframes gallery-spinner-in {
  from { opacity: 0; }
}
:is(.gallery-main .gallery-frame, .gallery-closeup-frame.is-main) .gallery-spinner {
  border-color: rgb(255 255 255 / 0.2);
  border-top-color: #f3d58a;
}
/* A white halo around the piece, always faint, now and then glowing like a firefly (js/gallery.js adds it and says
   when: .is-glowing, never on more than 6 pieces at once nor on two side by side, each glow its own length, --dur): lighting up,
   staying lit a moment, slowly fading back to faint. Over the frame's whole outside (its border and gold outline
   too), the glow only outside it, so it never covers the art; never catching a click. Its size from the frame's
   (--g) */
.gallery-halo {
  --g: calc(var(--t) * 0.08);
  position: absolute;
  inset: calc(-1 * (var(--t) * 0.045 + 2px));
  pointer-events: none;
  /* (a bright white rim, a strong glow around it and a wide soft one) */
  box-shadow:
    0 0 calc(var(--g) * 0.6) calc(var(--g) * 0.3) #fff,
    0 0 calc(var(--g) * 1.5) calc(var(--g) * 0.5) rgb(255 255 255 / 0.9),
    0 0 calc(var(--g) * 4) calc(var(--g) * 1) rgb(255 255 255 / 0.55);
  opacity: 0.1; /* (always a faint halo, between its glows and without motion) */
}
.gallery-halo.is-glowing { animation: gallery-halo-glow var(--dur, 6s) ease-in-out; }
@keyframes gallery-halo-glow {
  0% { opacity: 0.1; }
  22% { opacity: 1; } /* (lighting up) */
  44% { opacity: 0.85; } /* (staying lit a moment) */
  100% { opacity: 0.1; } /* (fading slowly, down to its faint halo) */
}
/* A blink with the music (js/gallery.js: every note of the room's piano, 3 pieces at random): at its brightest the
   very moment the note strikes (no rise), as bright as it's played (--peak), then fading back to faint as it rings
   away, steeply at first and lingering a little at the end */
.gallery-halo.is-blinking { animation: gallery-halo-blink 1.6s cubic-bezier(0.05, 0.8, 0.3, 1); }
@keyframes gallery-halo-blink {
  0% { opacity: var(--peak, 0.9); }
  100% { opacity: 0.1; }
}
@media (prefers-reduced-motion: reduce) {
  .gallery-halo.is-glowing { animation: none; }
}

/* What opens over the whole room (gallery.html, js/gallery.js): a piece's close-up and the stage's letter. The room
   dimmed behind it, fading in, a close button in the corner */
.gallery-overlay {
  position: fixed;
  z-index: 50; /* (over the back sign too) */
  inset: 0;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgb(20 6 4 / 0.9);
  animation: gallery-overlay-in 0.3s ease-out both;
}
.gallery-overlay[hidden] { display: none; }
@keyframes gallery-overlay-in {
  from { opacity: 0; }
}

/* The close-up: the piece as big as fits with your note under it (in the same frame as on the wall, see
   .gallery-frame) */
/* (as thick as a frame on the wall about a third its size: the wall's thickness, scaled up all the way, would be heavy) */
.gallery-closeup-frame { --w: min(80vw, 900px); --h: 66svh; --t: min(24svh, 28vw); }
/* Its glow: violet white light all around the frame (the title plaque's, .gallery-sign-glow), always lit, slowly
   breathing like it (brighter, dimmer, 3.5s each way). Built like the pieces' halos on the wall (.gallery-halo): a
   bright rim, a strong glow and a wide soft one, over the frame's whole outside (its border and gold outline too), the
   light only outside it, so it never covers the art; behind the frame, never catching a click. Its size from the
   frame's (--g) */
.gallery-closeup-frame::after {
  --g: calc(var(--t) * 0.08);
  content: "";
  position: absolute;
  z-index: -1;
  inset: calc(-1 * (var(--t) * 0.045 + 2px));
  pointer-events: none;
  box-shadow:
    0 0 calc(var(--g) * 0.6) calc(var(--g) * 0.3) #efe6ff,
    0 0 calc(var(--g) * 1.5) calc(var(--g) * 0.5) rgb(239 230 255 / 0.85),
    0 0 calc(var(--g) * 4) calc(var(--g) * 1) rgb(239 230 255 / 0.5);
  opacity: 0.55;
  animation: gallery-closeup-breathe 3.5s ease-in-out infinite alternate;
}
@keyframes gallery-closeup-breathe {
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .gallery-closeup-frame::after { animation: none; opacity: 0.8; }
}
.gallery-closeup-piece {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2.4svh;
  margin: 0;
}
.gallery-closeup-note {
  max-width: min(34rem, 84vw);
  max-height: 18svh;
  overflow: auto;
  /* (room for the slanted handwriting's last letter on a line, which leans past its own box: without it, the
     scrolling box would cut it off) */
  padding: 0.1em 0.3em;
  font-family: "Caveat", cursive;
  font-size: clamp(1.1rem, min(3.2vw, 2.8svh), 1.6rem);
  line-height: 1.25;
  text-align: center;
  white-space: pre-line;
  color: #fbecc4;
}
.gallery-closeup-note:empty { display: none; }
/* the round gold close button, in the corner */
.gallery-overlay-close {
  position: absolute;
  z-index: 1;
  top: 14px;
  right: 14px;
  width: 44px;
  height: 44px;
  padding: 0;
  font-size: 1.3rem;
  line-height: 1;
  color: #f3d58a;
  background: rgb(58 26 14 / 0.9);
  border: 2px solid #d9a441;
  border-radius: 50%;
  cursor: pointer;
}
.gallery-overlay-close:hover,
.gallery-overlay-close:focus-visible { color: #3a1a0e; background: #f3d58a; }

/* The stage's letter, opened: the balcony's letter (.balcony-letter), centered over the dimmed room */
.gallery-letter { display: block; padding: 0; }
/* The burst of stars inked in the letter's top-right corner, by the end of the title: behind the words (the letter
   keeps it in its own layer, above the paper), its white background melting into the paper (multiply) */
/* (--starburst-w: its size, in the corner from the top down toward the letter's first line, about 36% of its width.
   The letter is a container, so "cqi" is 1% of its width: the title's size below is worked out from the same width) */
.gallery-letter .enamel {
  isolation: isolate;
  container-type: inline-size;
  --starburst-w: clamp(110px, 36cqi, 170px);
  /* the room the title and its divider leave free on their right: the stars' inked part (the drawing's left 15% is
     blank paper), their 14px off the edge and a 12px gap */
  --stars-room: calc(var(--starburst-w) * 0.85 + 26px);
}
.letter-starburst {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: -1;
  width: var(--starburst-w);
  mix-blend-mode: multiply;
  pointer-events: none;
}
/* The title to the left, keeping clear of the stars, the kapok divider under it. Always its 2 lines: it only breaks
   at the \n in its text (white-space: pre), sized so its longer line, "biggest fan" (about 5.3 times the font size in
   this typeface), fits in the room beside the stars, up to 2.2rem */
.gallery-letter h2 {
  padding-right: var(--stars-room);
  font-size: min(2.2rem, (100cqi - var(--stars-room)) / 5.3);
  white-space: pre;
  text-align: left;
}
/* The divider right under the title (its longer-lined kapok, "kapok-long" in js/ornaments.js, whose line starts right
   at the left edge): 200px wide, the flower its usual size, or smaller if the room beside the stars is */
/* The message justified, edge to edge like a printed letter (long words split at line ends, so a narrow phone
   doesn't open wide gaps between words) */
.gallery-letter .letter-message { text-align: justify; hyphens: auto; }
.gallery-letter .divider {
  width: min(200px, 100cqi - var(--stars-room));
  margin: 4px 0 14px;
}

/* the main piece: in gilded wood with a black mat, glowing under the lamps (in its close-up too) */
.gallery-main .gallery-frame,
.gallery-closeup-frame.is-main {
  border-image: linear-gradient(135deg, #a57d2e, #f7e3a8 30%, #b98a2e 55%, #f1d596 80%, #8a6420) 1;
  outline-color: #f3d58a;
  background: linear-gradient(#1a1614, #0c0a09); /* (a black mat around the art, not the others' cream) */
  box-shadow:
    inset 0 0 0 1px rgb(217 164 65 / 0.7),
    inset 0 2px 6px rgb(0 0 0 / 0.25),
    0.3svh 0.5svh 0.6svh rgb(20 8 40 / 0.6),
    1.4svh 2.4svh 2.8svh rgb(20 8 40 / 0.55),
    0 0 90px rgb(255 210 140 / 0.45);
}
/* its art painted rather than filled: brushed edges, ink bleeding into the paper, uneven pigment and paper grain (the
   #painterly filter, js/gallery.js), the colors a little softer and warmer, like paint that has dried. Once its
   sharper version is ready (.is-painted: the filter already painted into the image), only the colors */
.gallery-main img,
.gallery-closeup-frame.is-main img { filter: url(#painterly) saturate(0.88) sepia(0.08) contrast(1.04); }
.gallery-main img.is-painted,
.gallery-closeup-frame.is-main img.is-painted { filter: saturate(0.88) sepia(0.08) contrast(1.04); }
.gallery-filters { position: absolute; }

/* The hanging pieces: two antique bronze cords from the frame's top up to a bronze nail above it (darker than the
   frames' gold, so they don't draw the eye) */
.gallery-hanging .gallery-frame::before {
  --cord: transparent calc(50% - 1px), #6e5223 calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px);
  content: "";
  position: absolute;
  left: 15%;
  right: 15%;
  bottom: calc(100% + var(--t) * 0.045);
  height: 4svh;
  background:
    linear-gradient(to top left, var(--cord)) left / 50% 100% no-repeat,
    linear-gradient(to top right, var(--cord)) right / 50% 100% no-repeat;
}
.gallery-hanging .gallery-frame::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: calc(100% + var(--t) * 0.045 + 4svh - 4px);
  width: 8px;
  height: 8px;
  translate: -50% 0;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #b8955a, #5a3f16 60%, #3a280c);
}

/* The pillars the lower pieces stand on: a violet stone shaft, lit from the front, a pale stone cap edged in gold
   under the piece and a wider stone base on the floor */
/* Round, seen from a little above like the stage and the floor: the cap and the base are stone drums whose tops show
   as ellipses (--lid: half an ellipse's height), the piece standing on the cap's, the shaft's foot on the base's.
   Everything lit from the front left like the shaft */
.gallery-pillars .gallery-frame { z-index: 1; } /* (in front of the back half of the cap's top: standing on it) */
/* each set down a little askew on its cap, turning on its bottom middle so it still stands on it (the pillar stays
   straight); about as much as the floor's leaning ones, alternating sides */
.gallery-pillars > .gallery-piece .gallery-frame { transform-origin: 50% 100%; rotate: var(--tilt, -3.5deg); }
.gallery-pillars > .gallery-piece:nth-child(2) .gallery-frame { --tilt: 3deg; }
.gallery-pillars > .gallery-piece:nth-child(3) .gallery-frame { --tilt: -2deg; }
.gallery-pillars > .gallery-piece:nth-child(4) .gallery-frame { --tilt: 4deg; }
.gallery-pillar {
  --cap: 2.4svh;
  --lid: 0.8svh;
  position: relative;
  width: clamp(26px, 5vw, 70px);
  height: 10svh;
  margin-top: calc(var(--cap) - var(--lid));
  border-radius: 0 0 50% 50% / 0 0 var(--lid) var(--lid);
  background:
    linear-gradient(rgb(0 0 0 / 0.45), transparent 2svh), /* (the cap's shadow) */
    linear-gradient(90deg, #2a2248, #6a5aa8 40%, #8a7ac4 50%, #4a3c80 75%, #241e44);
}
/* On an upright phone the room is tall for its width: taller shafts fill the room's height, raising their pieces
   (the cap and the base follow the shaft's ends) */
@media (max-width: 700px) {
  .gallery-pillar { height: 13svh; }
}
.gallery-pillar::before,
.gallery-pillar::after {
  content: "";
  position: absolute;
  height: var(--cap);
  border-radius: 50% / var(--lid);
  background:
    radial-gradient(50% var(--lid) at 50% var(--lid), #fbf6ec 0 78%, #e4d8c0 90%, var(--gold) 91% 100%, transparent 100%),
    linear-gradient(90deg, #8a7c64, #e9e0cc 40%, #fbf6ec 50%, #cfc3aa 75%, #7a6c54);
}
/* the cap, on top of the shaft */
.gallery-pillar::before { left: -24%; right: -24%; top: calc(-1 * var(--cap)); }
/* The pieces propped up on the floor, in front of the pillars: their own row, centered on the screen (3 even places
   across its middle 90%, the middle one at the very center, the others well out to the sides), standing on the floor
   in front of the bases, each tipped a little its own way; their shadows on the floor.
   About the size of the pillars' pieces (closer to the viewer), out of the band's flow, so the pillars stay exactly
   as they are */
.gallery-floor {
  --w: 11vw;
  --h: 10svh;
  position: absolute;
  z-index: 2;
  left: 5%;
  right: 5%;
  bottom: -2.2svh;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  justify-items: center;
  align-items: end;
}
/* On an upright phone the narrow width held them smaller than the pillars' pieces behind them: wider and a little
   taller, so they look closer */
@media (max-width: 700px) {
  .gallery-floor { --w: 17vw; --h: 11svh; }
}
.gallery-leaning {
  transform-origin: 50% 100%;
  rotate: var(--lean, -4deg);
  filter: drop-shadow(1svh 1.4svh 1.2svh rgb(0 0 0 / 0.65));
}
.gallery-leaning:nth-child(2) { --lean: 3deg; }
.gallery-leaning:nth-child(3) { --lean: -5deg; }
/* the base, wider, behind the shaft's foot, its shadow on the floor */
.gallery-pillar::after {
  z-index: -1;
  left: -36%;
  right: -36%;
  bottom: calc(var(--lid) * 1.5 - var(--cap));
  box-shadow: 0 0.6svh 1.2svh rgb(0 0 0 / 0.55);
}

/* ---------- The hidden room key (js/room-key.js) ----------
   A gold key glinting in the middle of its container (the TV screen, the platter, the book page; the bill's
   backdrop places it itself), springing in; picked up, a copy of it swells and flies off upward (.room-key-flying) */
.room-key {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 5;
  translate: -50% -50%;
  width: clamp(56px, 18%, 96px);
  padding: 6px;
  border: none;
  background: none;
  cursor: pointer;
  filter: drop-shadow(0 0 10px rgb(255 210 120 / 0.85));
  animation: room-key-appear 0.6s cubic-bezier(0.3, 1.4, 0.5, 1) both, room-key-glint 1.8s ease-in-out 0.6s infinite;
}
.room-key svg { display: block; width: 100%; height: auto; }
.room-key:focus-visible { outline: 3px solid var(--enamel); outline-offset: 2px; border-radius: 8px; }
@keyframes room-key-appear { from { opacity: 0; transform: scale(0.3) rotate(-25deg); } }
@keyframes room-key-glint { 50% { filter: drop-shadow(0 0 18px rgb(255 225 150 / 1)); } }
/* The kitchen key (js/food.js): a little hole punched in the receipt's bottom-right corner, a red silk cord threaded
   through it, and the key hanging ring-up from the cord's end; cord and key swing in from the hole together and keep
   swaying. Once the key is taken, the empty cord stays in the hole. */
.bill-hole {
  position: absolute;
  right: 22px;
  bottom: calc(var(--serration) + 12px);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #2a2420;
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.7);
}
/* the cord's top is the hole's center (placed by js/food.js); --cord: its length */
.bill-key-cord {
  position: absolute;
  z-index: 2;
  width: 0;
  height: 0;
  transform-origin: 0 0;
  animation: bill-key-swing-in 1.2s cubic-bezier(0.3, 0.6, 0.4, 1) both, bill-key-sway 2.6s ease-in-out 1.2s infinite;
}
/* (in front of the key, whose z-index is 5: the cord ends on the ring) */
.bill-key-cord > svg { position: absolute; top: 0; left: -5px; z-index: 6; overflow: visible; pointer-events: none; }
@keyframes bill-key-swing-in {
  from { opacity: 0; rotate: -40deg; }
  45% { opacity: 1; rotate: 18deg; }
  75% { rotate: -8deg; }
  to { rotate: 0deg; }
}
@keyframes bill-key-sway { 0%, 100% { rotate: -5deg; } 50% { rotate: 5deg; } }
/* the key: tied by the top of its ring (the cord's end on the middle of the ring's gold band, 9.5px above the
   ring's center), turned ring-up around the ring's center. At 80px wide, the ring's center is 20.875px from the key's top
   and left (its 6px padding + the ring's center at 14 of the drawing's 64 × 40, drawn 68px wide) */
.bill-key-cord > .room-key {
  top: calc(var(--cord) + 9.5px);
  left: 0;
  width: 80px;
  translate: -20.875px -20.875px;
  transform-origin: 20.875px 20.875px;
  transform: rotate(90deg);
  animation: room-key-glint 1.8s ease-in-out infinite;
}
/* The TV key (js/tv.js): a pixel key printed in the picture's bottom-right corner, part of the broadcast. It's inside
   the picture, so the TV's grain and glass lie over it and it closes with the picture when the TV turns off. It scans
   in line by line, its colors slightly split like on a tube, and now and then the signal glitches */
.show > .room-key {
  position: absolute; /* (the picture's other contents are in its column: see .show > :not(…)) */
  top: auto;
  left: auto;
  right: 6%;
  bottom: 8%;
  z-index: 1;
  translate: none;
  width: clamp(60px, 21%, 104px);
  padding: 4px;
  filter: drop-shadow(-1.5px 0 0 rgb(255 40 60 / 0.6)) drop-shadow(1.5px 0 0 rgb(40 220 255 / 0.6))
    drop-shadow(0 0 6px rgb(255 224 102 / 0.8));
  /* (the scan-in's mask only holds before and during it: kept after, it would cut the glow off) */
  animation: tv-key-in 0.5s steps(5) backwards, tv-key-glitch 2.6s steps(1) 0.5s infinite;
}
@keyframes tv-key-in {
  from { clip-path: inset(0 0 100% 0); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  to { clip-path: inset(0); transform: none; }
}
@keyframes tv-key-glitch {
  0%, 94% { opacity: 1; transform: none; }
  90% { opacity: 0.55; transform: translateX(3px); }
  92% { opacity: 0.85; transform: translateX(-2px); }
}
/* The library key (js/reading.js): a key doodled in ballpoint on the 3rd page of a book, under the lower lines of
   its passage, a little askew. It doesn't spring in: it's simply there, uncovered as the page before it turns, and
   carried off with its own page (the leaf: under the leaf's shade, it darkens with the paper as it lifts). Ink in the
   paper: no glow, no shadow, the paper's grain showing through it (multiply) */
.book-page > .room-key {
  top: 76%;
  z-index: auto;
  width: clamp(80px, 30%, 140px);
  rotate: -8deg;
  filter: none;
  opacity: 0.92;
  mix-blend-mode: multiply;
  animation: none;
}
/* picked up: a copy of it flies off upward over everything on the page (js/room-key.js moves it) */
.room-key-flying {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 70;
  pointer-events: none;
  filter: drop-shadow(0 0 10px rgb(255 210 120 / 0.85));
}
.room-key-flying svg { display: block; width: 100%; height: auto; }
/* the line saying which key she found: always a single line, its text a little smaller on narrow phones to fit */
.room-key-found {
  position: fixed;
  left: 50%;
  bottom: 12%;
  z-index: 60;
  translate: -50% 0;
  max-width: calc(100% - 32px);
  margin: 0;
  padding: 0.6em 1.2em;
  font-size: min(1rem, 3.8vw);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border: 2px solid var(--gold);
  border-radius: 999px;
  background: #7a1c14;
  color: #fff3d0;
  font-weight: 700;
  text-align: center;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.5);
  animation: room-key-found 3.2s ease both;
}
@keyframes room-key-found {
  from { opacity: 0; transform: translateY(16px); }
  12%, 85% { opacity: 1; transform: none; }
  to { opacity: 0; }
}
/* A found key flying across the hall into the gate's padlock (js/hall.js moves it, with the Web Animations API) */
.flying-key {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 30;
  width: 56px;
  pointer-events: none;
  filter: drop-shadow(0 0 10px rgb(255 210 120 / 0.85));
}
.flying-key svg { display: block; width: 100%; height: auto; }
@media (prefers-reduced-motion: reduce) {
  .room-key { animation-duration: 0.01s; animation-iteration-count: 1; }
  .bill-key-cord, .bill-key-cord > .room-key, .show > .room-key, .book-page > .room-key { animation: none; }
}
