:root {
  --bg: #f7f1e8;
  --surface: #fffaf3;
  --text: #3b2f2a;
  --muted: #7a6a60;
  --accent: #c0703f;
  --accent-text: #ffffff;
  --border: #e4d8c8;
  --danger: #b3382c;
  --ok: #3c7a3f;
  --shadow: rgba(59, 47, 42, 0.22);
  --star: #e9a516; /* the host's star over the head (scene/bubbles.js) */
  /* Scene backdrop and fallback picture colors, one set per cafe theme (models.THEMES). */
  --cozy-sky-top: #8fbfdc;
  --cozy-sky-bottom: #f3e7cf;
  --cozy-wall: #ead6b2;
  --cozy-floor: #84502d;
  --cozy-wood: #83491f;
  --cozy-accent: #e8667f;
  --cozy-glass: #fff6dc;
  --neon-sky-top: #6f7fc4;
  --neon-sky-bottom: #e6c9de;
  --neon-wall: #1d1b33;
  --neon-floor: #2b2a45;
  --neon-wood: #3b3158;
  --neon-accent: #ff4fa3;
  --neon-glass: #4aa8d8;
  --garden-sky-top: #a9d6e6;
  --garden-sky-bottom: #f1f5df;
  --garden-wall: #e5f0d6;
  --garden-floor: #d6d2a4;
  --garden-wood: #a67c52;
  --garden-accent: #e9738c;
  --garden-glass: #cdeef0;
  --scene-sky-top: var(--cozy-sky-top);
  --scene-sky-bottom: var(--cozy-sky-bottom);
  --scene-wall: var(--cozy-wall);
  --scene-floor: var(--cozy-floor);
  --scene-wood: var(--cozy-wood);
  --scene-accent: var(--cozy-accent);
  --scene-glass: var(--cozy-glass);
  /* Time-of-day tints for the scene backdrop (docs/M5.md 2.8): the daypart picks the tint. The sky
     is the cafe's environment, so dark mode leaves it alone (only the HUD and chat change). */
  --sky-dawn: #f4b39a;
  --sky-dusk: #c96f6a;
  --sky-night: #101833;
  --sky-tint: var(--sky-night);
  --sky-tint-mix: 0%;
  --sky-tint-low: 0%; /* the same tint at the bottom of the sky (night darkens all of it) */
  /* Weather tints for the backdrop (docs/M8.md 2.11), read by .scene-panel[data-weather=...] and
     only while the viewer's Weather effects switch isn't off. --weather-flash is the lightning
     flash (reduced motion never sets it); --weather-dim softens every tint at night. */
  --weather-rain: #7f93a8;
  --weather-snow: #eef3f8;
  --weather-fog: #c3c8ce;
  --weather-petals: #f6b8cc;
  --weather-flash: #f4f7ff;
  --weather-tint: transparent;
  --weather-mix: 0%;
  --weather-dim: 1;
  /* The moon in the backdrop (docs/M9a.md 6.5): ui/moon-sky.js reads these three colours (hex only)
     and draws them into a canvas. A warm cream disc over a pale gold halo reads on every sky. */
  --moon-lit: #fff4d2;
  --moon-shade: #c9b987;
  --moon-glow: #ffe9a8;
  --moon-size: 5.5rem; /* the disc's diameter */
  --moon-top: 3.75rem; /* from the top of the page to the top of the disc */
  --moon-right: clamp(1.25rem, 12vw, 9rem); /* from the right edge to the disc's right edge */
  --moon-opacity: 0; /* ui/moon-sky.js sets it on the canvas: the stars' weight from the clock */
  --fireworks-opacity: 0; /* ui/fireworks-sky.js sets it on its canvas, by the moon's rule */
  /* The game-style HUD over the cafe scene (docs/M6.md 2.1). Text on a HUD surface meets 4.5:1
     against --hud-bg alone, so it never depends on the blur or on what is behind. */
  --hud-bg: rgba(255, 250, 243, 0.9);
  --hud-fg: #3b2f2a;
  --hud-muted: #6a5a50;
  --hud-link: #8a4a22;
  --brand-x-mark: #000; /* X asks for its mark in black or white only */
  --hud-border: rgba(59, 47, 42, 0.2);
  --hud-shadow: 0 6px 22px rgba(59, 47, 42, 0.28);
  --hud-primary: #9a4f22;
  --hud-primary-fg: #ffffff;
  /* The quiet site footer (terms, privacy, contact) and the long-form legal pages. */
  --footer-fg: #6a5a50;
  --footer-link: #8a4a22;
  --footer-border: #e4d8c8;
  /* Stacking: the scene, then the HUD, the chat log panel and finally toasts. Popovers sit in the
     browser's top layer and need no z-index. */
  --z-stage: 0;
  --z-hud: 10;
  --z-panel: 20;
  --z-toast: 30;
  /* The on-screen keyboard's height on browsers that don't resize the page (ui/hud.js sets it). */
  --kb-inset: 0px;
  /* Where the emote sheet opens: .emote-sheet sets it per layout, ui/chat.js just above the
     message box. */
  --emote-bottom: 4.25rem;
  /* Special events (docs/M9a.md): the night sky behind the riverside and the lantern ground. */
  --event-sky-top: #0a1032;
  --event-sky-mid: #1d2a5c;
  --event-sky-bottom: #4a3a6e;
  --event-sky-glow: #7a4a58;
  --event-star: #fff7dc;
  --event-gold: #f2b632;
  --event-gold-fg: #3b2a05;
  --event-card-bg: #fffaf3;
  /* Sign in with Google (docs/google-login.md 10): Google's light and dark button colours */
  --google-bg: #ffffff;
  --google-fg: #1f1f1f;
  --google-border: #747775;
  --google-hover: rgba(31, 31, 31, 0.08);
  --radius: 14px;
  --font: ui-rounded, "SF Pro Rounded", system-ui, -apple-system, "Segoe UI", "Noto Sans Thai", "Sarabun",
    "Leelawadee UI", "Thonburi", sans-serif;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1f1a17;
    --surface: #2a231f;
    --text: #f1e7dc;
    --muted: #b3a397;
    --accent: #e39a68;
    --accent-text: #1f1a17;
    --border: #44382f;
    --danger: #ef7d6f;
    --ok: #8fcf92;
    --shadow: rgba(0, 0, 0, 0.5);
    --star: #ffd45e;
    --cozy-wall: #d6c29d;
    --cozy-floor: #74462a;
    --cozy-wood: #6d3f20;
    --cozy-accent: #ea7d91;
    --cozy-glass: #e8d9b4;
    --neon-wall: #16142a;
    --neon-floor: #201f38;
    --neon-wood: #2e2547;
    --neon-accent: #ff6cb4;
    --neon-glass: #3a86b0;
    --garden-wall: #b9ceaa;
    --garden-floor: #a9a577;
    --garden-wood: #84603f;
    --garden-accent: #ee8aa0;
    --garden-glass: #7fb1b4;
    --google-bg: #131314;
    --google-fg: #e3e3e3;
    --google-border: #8e918f;
    --google-hover: rgba(227, 227, 227, 0.1);
    --hud-bg: rgba(42, 35, 31, 0.9);
    --hud-fg: #f1e7dc;
    --hud-muted: #c2b3a7;
    --hud-link: #f0b08a;
    --brand-x-mark: #fff;
    --hud-border: rgba(241, 231, 220, 0.22);
    --hud-shadow: 0 6px 22px rgba(0, 0, 0, 0.55);
    --hud-primary: #e39a68;
    --hud-primary-fg: #1f1a17;
    --footer-fg: #b3a397;
    --footer-link: #f0b08a;
    --footer-border: #44382f;
    --event-gold: #f0c050;
    --event-gold-fg: #2a1c02;
    --event-card-bg: #2a231f;
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg: #1f1a17;
  --surface: #2a231f;
  --text: #f1e7dc;
  --muted: #b3a397;
  --accent: #e39a68;
  --accent-text: #1f1a17;
  --border: #44382f;
  --danger: #ef7d6f;
  --ok: #8fcf92;
  --shadow: rgba(0, 0, 0, 0.5);
  --star: #ffd45e;
  --cozy-wall: #d6c29d;
  --cozy-floor: #74462a;
  --cozy-wood: #6d3f20;
  --cozy-accent: #ea7d91;
  --cozy-glass: #e8d9b4;
  --neon-wall: #16142a;
  --neon-floor: #201f38;
  --neon-wood: #2e2547;
  --neon-accent: #ff6cb4;
  --neon-glass: #3a86b0;
  --garden-wall: #b9ceaa;
  --garden-floor: #a9a577;
  --garden-wood: #84603f;
  --garden-accent: #ee8aa0;
  --garden-glass: #7fb1b4;
  --google-bg: #131314;
  --google-fg: #e3e3e3;
  --google-border: #8e918f;
  --google-hover: rgba(227, 227, 227, 0.1);
  --hud-bg: rgba(42, 35, 31, 0.9);
  --hud-fg: #f1e7dc;
  --hud-muted: #c2b3a7;
  --hud-link: #f0b08a;
  --brand-x-mark: #fff;
  --hud-border: rgba(241, 231, 220, 0.22);
  --hud-shadow: 0 6px 22px rgba(0, 0, 0, 0.55);
  --hud-primary: #e39a68;
  --hud-primary-fg: #1f1a17;
  --footer-fg: #b3a397;
  --footer-link: #f0b08a;
  --footer-border: #44382f;
  --event-gold: #f0c050;
  --event-gold-fg: #2a1c02;
  --event-card-bg: #2a231f;
  color-scheme: dark;
}

[hidden] {
  display: none !important;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  line-height: 1.5;
}

.page {
  max-width: 720px;
  margin: 0 auto;
  padding: 48px 16px;
}

.hero {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 40px 24px;
  text-align: center;
}

.hero-how {
  max-width: 34rem;
  margin: 0.5rem auto 0;
  color: var(--muted);
}

.sign {
  font-size: 3rem;
}

h1 {
  margin: 0.25em 0;
  color: var(--accent);
}

.lede {
  font-size: 1.15rem;
}

.muted {
  color: var(--muted);
}

a {
  color: var(--accent);
}

code {
  font-size: 0.9em;
}

.topbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.topbar nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: center;
}

.brand {
  font-weight: 700;
  text-decoration: none;
}

.who {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.page {
  max-width: 820px;
}

.page.page-wide {
  max-width: 1200px;
}

.card.narrow {
  max-width: 460px;
  margin-inline: auto;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  margin: 16px 0;
}

.cards {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}

.cards .card {
  margin: 0;
}

.cards h3 {
  margin-top: 0;
}

/* A home page cafe card opens with a look inside (UX review F9), edge to edge. */
.cafe-card {
  overflow: hidden;
  padding-top: 0;
}

.cafe-card-art {
  display: block;
  margin: 0 -20px 14px;
}

.cafe-card-art img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 960 / 504;
  object-fit: cover;
}

.danger {
  border-color: var(--danger);
}

.flash {
  border-radius: 10px;
  padding: 10px 14px;
  margin: 0 0 16px;
  background: var(--surface);
  border: 1px solid var(--border);
}

.flash-success {
  border-color: var(--ok);
}

.flash-error {
  border-color: var(--danger);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 14px;
  border: 0;
  padding: 0;
}

.field label,
.field legend {
  font-weight: 600;
}

.field.check label {
  font-weight: 400;
}

input[type="text"],
input[type="password"],
input[type="number"],
select,
textarea {
  font: inherit;
  color: inherit;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px;
  width: 100%;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible,
.swatch-option:has(input:focus-visible) {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.has-error input,
.has-error textarea {
  border-color: var(--danger);
}

.hint {
  color: var(--muted);
}

.error {
  color: var(--danger);
}

.button {
  display: inline-block;
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid var(--accent);
  border-radius: 10px;
  padding: 8px 16px;
  background: var(--accent);
  color: var(--accent-text);
}

.button.secondary {
  background: transparent;
  color: var(--accent);
}

.button.danger-button {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--accent-text);
}

/* Sign in with Google (docs/google-login.md 10). Google's branding: the multicolour G on a white
   (light) or #131314 (dark) button with a thin border, the label in the neutral text colour.
   Never the site's accent colour. */
.button.google-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 8px 14px;
  margin: 12px 0;
  background: var(--google-bg);
  border-color: var(--google-border);
  color: var(--google-fg);
  font-weight: 500;
  text-align: center;
}

.button.google-button:hover {
  background-image: linear-gradient(var(--google-hover), var(--google-hover));
}

.button.google-button:active {
  background-image: linear-gradient(var(--google-hover), var(--google-hover));
  filter: brightness(0.96);
}

.google-g {
  flex: none;
  width: 20px;
  height: 20px;
}

.google-note {
  margin-top: -4px;
  font-size: 0.9rem;
  text-align: center;
}

/* "or" between two ways in, with a line either side. */
.divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 0;
  font-size: 0.9rem;
}

.divider::before,
.divider::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--border);
}

/* A short note in a soft box (the in-app browser hint). */
.card-note {
  margin: 12px 0;
  padding: 12px 14px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.card-note p {
  margin: 0 0 10px;
}

.card-note > :last-child {
  margin-bottom: 0;
}

.google-hint .button {
  min-height: 44px;
}

.google-hint > a.button {
  display: flex;
  align-items: center;
  justify-content: center;
}

.copy-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.copy-row input {
  flex: 1 1 8rem;
  min-width: 0;
  font-size: 0.9rem;
  text-overflow: ellipsis;
}

.copy-row .button {
  flex: none;
}

.copy-status {
  margin: 8px 0 0;
  font-size: 0.9rem;
}

.copy-status:empty {
  display: none;
}

/* /account "Ways to log in": one block per way, a dot for whether it is set. */
.ways .way {
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.ways h2 + .way,
.ways h2 + p {
  margin-top: 0;
}

.way-line {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 8px;
  font-weight: 600;
}

.way-line::before {
  content: "";
  flex: none;
  width: 0.7rem;
  height: 0.7rem;
  border: 2px solid var(--muted);
  border-radius: 50%;
}

.way-line.way-on::before {
  background: var(--ok);
  border-color: var(--ok);
}

.way-form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-end;
}

.way-form .field {
  flex: 1 1 14rem;
  margin-bottom: 0;
}

.way-form .button,
.ways .button {
  min-height: 44px;
}

.ways .way > .button[disabled] {
  opacity: 0.6;
  cursor: not-allowed;
}

@media (forced-colors: active) {
  .button.google-button {
    forced-color-adjust: none;
    background: Canvas;
    border: 1px solid ButtonText;
    color: CanvasText;
  }

  .way-line::before {
    border-color: CanvasText;
  }

  .way-line.way-on::before {
    background: Highlight;
    border-color: Highlight;
  }
}

button.link {
  font: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--accent);
  text-decoration: underline;
}

form.inline {
  display: inline;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  text-align: left;
  padding: 8px 6px;
  border-bottom: 1px solid var(--border);
}

.badge {
  font-size: 0.75rem;
  font-weight: 600;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 8px;
  vertical-align: middle;
}

ul.plain {
  list-style: none;
  padding: 0;
}

.linkbox input {
  font-family: ui-monospace, monospace;
}

.dot {
  display: inline-block;
  width: 0.8em;
  height: 0.8em;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: baseline;
}

.dot-lg {
  width: 2rem;
  height: 2rem;
  margin: 0;
  border: 3px solid transparent;
  box-shadow: 0 0 0 1px var(--border);
}

.swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.swatch-option {
  cursor: pointer;
  border-radius: 50%;
}

.swatch-option:has(input:checked) .dot-lg {
  border-color: var(--text);
}

.visually-hidden,
.chat-status:empty,
.chat-typing:empty,
.stage[data-chat="hidden"] .chat-typing,
.stage[data-chat="ticker"] .chat-panel,
.stage[data-chat="hidden"] .chat-panel {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Avatar colors: keep in sync with AVATAR_COLORS in services/users.py (a test checks this). */
.swatch-terracotta { background: #c0703f; }
.swatch-honey { background: #e0a43a; }
.swatch-sage { background: #7aa95c; }
.swatch-teal { background: #4f9d9d; }
.swatch-blue { background: #5b7fc7; }
.swatch-lavender { background: #9b6fc4; }
.swatch-rose { background: #d4668a; }
.swatch-mocha { background: #8c7b6b; }

/* Look swatches (M9c): keep in sync with LOOK_COLORS in static/js/looks.js and PARTS in
   services/looks.py (tests/test_looks.py checks both). */
.swatch-auto {
  background: repeating-linear-gradient(45deg, var(--surface) 0 4px, var(--border) 4px 8px);
}
.swatch-hair-black { background: #241b1a; }
.swatch-hair-dark_brown { background: #3b2a20; }
.swatch-hair-brown { background: #5a3a24; }
.swatch-hair-chestnut { background: #7a4f2e; }
.swatch-hair-caramel { background: #a06a3a; }
.swatch-hair-blonde { background: #e3c27a; }
.swatch-hair-red { background: #b5452c; }
.swatch-hair-grey { background: #d9d4cb; }
.swatch-hair-pink { background: #f0a6c2; }
.swatch-hair-light_blue { background: #9ccbe8; }
.swatch-hair-lavender { background: #b9a5de; }
.swatch-skin-skin1 { background: #f4d0ae; }
.swatch-skin-skin2 { background: #ecbd96; }
.swatch-skin-skin3 { background: #d29a6c; }
.swatch-skin-skin4 { background: #a97048; }
.swatch-skin-skin5 { background: #7c4e32; }
.swatch-skin-skin6 { background: #5a3826; }
.swatch-bottom-cocoa { background: #4a3a33; }
.swatch-bottom-charcoal { background: #3d3f4a; }
.swatch-bottom-walnut { background: #5a4634; }
.swatch-bottom-taupe { background: #6b6459; }
.swatch-bottom-denim { background: #5b6f8f; }
.swatch-bottom-olive { background: #4f5e48; }

/* The character card on /account (M9c, docs/M9c.md 10; one form with the profile, docs/UX-batch2.md
   B): the preview stays in view while the choices scroll. Sticky needs every ancestor to leave
   overflow visible (.card, main and body do) and the preview to share its parent with the choices.
   A phone gets the preview as a bar at the top of the screen, small enough (about a third of the
   height) to leave room for the choices under it; a wide window gets it beside them. */
.look-layout {
  display: block;
}

.look-preview {
  position: sticky;
  top: 0;
  z-index: var(--z-hud);
  display: grid;
  justify-items: center;
  gap: 6px;
  margin-bottom: 14px;
  padding: 8px 0;
  text-align: center;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.look-stage {
  position: relative;
  display: grid;
  place-items: center;
  height: min(240px, 24vh);
  aspect-ratio: 11 / 13;
  min-width: 150px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: radial-gradient(circle at 50% 38%, var(--surface) 0 35%, var(--bg) 75%);
  overflow: hidden;
}

.look-canvas {
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: grab;
}

.look-canvas:active {
  cursor: grabbing;
}

.look-canvas:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
}

.look-note {
  margin: 0;
  padding: 0 10px;
  color: var(--muted);
  font-size: 0.8rem;
}

.look-turn {
  display: flex;
  gap: 8px;
}

.look-turn .button,
.look-actions .button {
  min-height: 44px;
}

.look-form .field {
  margin-bottom: 14px;
}

.look-swatch-name:empty {
  display: none;
}

.look-auto-hint {
  color: var(--muted);
}

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

.account-actions {
  margin: 16px 0;
}

@media (min-width: 720px) {
  .look-layout {
    display: grid;
    grid-template-columns: 280px 1fr;
    gap: 20px;
    align-items: start;
  }

  .look-preview {
    top: 16px;
    margin-bottom: 0;
    padding: 0;
    background: none;
    border-bottom: 0;
  }

  .look-stage {
    height: 320px;
    min-width: 0;
  }
}

/* Rows: panel and account lists. Wrap instead of scrolling sideways on phones. */
ul.rows {
  list-style: none;
  padding: 0;
  margin: 0 0 12px;
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: flex-start;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
  overflow-wrap: anywhere;
}

.row:last-child {
  border-bottom: 0;
}

.row-main {
  flex: 1 1 14rem;
  min-width: 0;
}

.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-start;
}

.seat {
  display: inline-block;
  min-width: 4.5em;
  color: var(--muted);
}

/* A knocker's note, shown as a quote. It is user text, so it is only ever escaped. */
blockquote.note {
  margin: 6px 0 0;
  padding: 4px 12px;
  border-left: 3px solid var(--border);
  color: var(--muted);
  white-space: pre-line;
}

/* Confirm step without JS: <details><summary>Remove…</summary><form>…</form></details> */
details.confirm {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px 12px;
  background: var(--bg);
}

details.confirm summary {
  cursor: pointer;
  color: var(--danger);
  font-weight: 600;
}

details.confirm[open] {
  flex-basis: 100%;
  border-color: var(--danger);
}

details.confirm form {
  margin-top: 8px;
}

/* ---- The cafe page: one full-viewport stage with a HUD over it (docs/M6.md) --------------- */
/* DOM order is focus order: skip link, top bar, chat, actions, toasts, popovers, then the scene
   (which is drawn underneath everything). User text is only ever set with textContent. */
body.page-stage {
  min-height: 0;
  overflow: hidden;
  overscroll-behavior: none;
}

.stage {
  position: relative;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  isolation: isolate;
}

.stage #chat:focus {
  outline: none;
}

.skip-link {
  position: fixed;
  z-index: var(--z-toast);
  left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
  top: -5rem;
  padding: 0.5rem 1rem;
  border-radius: 999px;
  background: var(--hud-primary);
  color: var(--hud-primary-fg);
  font-weight: 700;
  text-decoration: none;
}

.skip-link:focus {
  top: calc(env(safe-area-inset-top, 0px) + 0.5rem);
}

/* HUD containers let drags through to the canvas; only their controls take the pointer. */
.hud-top,
.hud-right,
.hud-chat,
.hud-actions,
.hud-flash,
.hud-toasts {
  pointer-events: none;
}

.hud-top {
  position: fixed;
  z-index: var(--z-hud);
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
  padding: calc(env(safe-area-inset-top, 0px) + 0.5rem) calc(env(safe-area-inset-right, 0px) + 0.75rem) 0
    calc(env(safe-area-inset-left, 0px) + 0.75rem);
}

.hud-right {
  display: flex;
  flex: none;
  align-items: center;
  gap: 0.5rem;
}

.hud-chip,
.hud-btn,
.ticker-line,
.chat-typing,
.chat-status,
.hud-note,
.hud-banner,
.chat-panel {
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  box-shadow: var(--hud-shadow);
  backdrop-filter: blur(8px);
}

.hud-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  min-height: 2.25rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  pointer-events: auto;
}

.hud-chip .muted,
.hud-sheet .muted,
.hud-chat .muted {
  color: var(--hud-muted);
}

.cafe-chip {
  min-width: 0;
  padding-right: 0.75rem;
}

.chip-home {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  text-decoration: none;
}

.cafe-name {
  margin: 0;
  min-width: 0;
  overflow: hidden;
  color: var(--hud-fg);
  font-size: 1.05rem;
  line-height: 1.2;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cafe-chip .cafe-name-short {
  display: none; /* phones only (below) */
}

.cafe-seats {
  flex: none;
  margin-left: 0.4rem;
  color: var(--hud-muted);
  font-size: 0.85rem;
  white-space: nowrap;
}

.cafe-clock {
  flex: none;
  margin-left: 0.4rem;
  color: var(--hud-muted);
  font-size: 0.85rem;
  white-space: nowrap;
}

.hud-btn,
.hud-chip-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  min-width: 2rem;
  min-height: 2rem;
  padding: 0 0.6rem;
  font: inherit;
  line-height: 1;
  color: var(--hud-fg);
  border-radius: 999px;
  cursor: pointer;
}

.hud-chip-button {
  background: transparent;
  border: 0;
}

.hud-btn {
  pointer-events: auto;
}

.hud-btn:hover:not(:disabled),
.hud-chip-button:hover:not(:disabled) {
  background: color-mix(in srgb, var(--hud-fg) 10%, var(--hud-bg));
}

.hud-btn:disabled {
  opacity: 0.55;
  cursor: default;
}

.hud-btn-primary {
  color: var(--hud-primary-fg);
  background: var(--hud-primary);
  border-color: var(--hud-primary);
  font-weight: 700;
}

.hud-btn-primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--hud-primary) 88%, var(--hud-fg));
}

.hud-btn[aria-pressed="true"],
.hud-btn[aria-expanded="true"] {
  border-color: var(--hud-primary);
}

.chat-watchers {
  padding: 0 0.5rem 0 0.125rem;
  color: var(--hud-fg);
  font-size: 0.9rem;
  white-space: nowrap;
}

.ambience[data-state="unavailable"] .ambience-toggle {
  opacity: 0.5;
}

/* "Turn on sound" beside the muted icon in an event place (UX review F12); gone once it plays. */
.ambience-cta {
  margin-left: 0.3rem;
  font-size: 0.85rem;
  white-space: nowrap;
}

.ambience:is([data-state="on"], [data-state="unavailable"]) .ambience-cta {
  display: none;
}

/* The place's name wins on a narrow phone: the Thai word fits beside it, the English one doesn't. */
@media (max-width: 419px) {
  .ambience-cta:not(:lang(th)) {
    display: none;
  }
}

/* Flash messages and the closed-cafe banner (top centre, under the bar). */
.hud-flash {
  position: fixed;
  z-index: var(--z-toast);
  top: calc(env(safe-area-inset-top, 0px) + 3.5rem);
  left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
}

.stage.is-closed .hud-flash {
  top: calc(env(safe-area-inset-top, 0px) + 6.5rem);
}

.hud-flash .flash {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  max-width: 32rem;
  margin: 0;
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  box-shadow: var(--hud-shadow);
  backdrop-filter: blur(8px);
  pointer-events: auto;
}

.hud-flash .flash-success {
  border-color: var(--ok);
}

.hud-flash .flash-error {
  border-color: var(--danger);
}

.flash-close {
  flex: none;
  margin-left: auto;
  font: inherit;
  color: var(--hud-link);
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
}

.hud-banner {
  position: fixed;
  z-index: var(--z-toast);
  top: calc(env(safe-area-inset-top, 0px) + 3.5rem);
  left: 50%;
  max-width: calc(100vw - 1.5rem);
  margin: 0;
  padding: 0.4rem 1rem;
  border-radius: 999px;
  font-size: 0.9rem;
  text-align: center;
  transform: translateX(-50%);
}

/* The chat: log panel, ticker, typing and status lines, chat bar (bottom left). */
.hud-chat {
  position: fixed;
  z-index: var(--z-hud);
  left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 0.75rem + var(--kb-inset, 0px));
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.375rem;
}

.stage[data-chat="log"] .hud-chat {
  z-index: var(--z-panel);
}

.chat-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  border-radius: var(--radius);
  pointer-events: auto;
}

.stage.is-closed .chat-panel,
.stage.is-closed .chat-ticker,
.stage.is-closed .chat-bar {
  display: none;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.35rem 0.5rem 0.35rem 0.9rem;
  border-bottom: 1px solid var(--hud-border);
}

.panel-title {
  font-weight: 700;
}

.panel-head .hud-btn {
  box-shadow: none;
  border-color: transparent;
}

.chat-log {
  flex: 1 1 auto;
  min-height: 0;
  margin: 0;
  padding: 0.4rem 0.9rem;
  overflow-y: auto;
  list-style: none;
}

.chat-log:empty::before {
  content: attr(data-empty);
  display: block;
  padding: 0.6rem 0;
  color: var(--hud-muted);
  font-size: 0.9rem;
}

.chat-ticker {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
}

.stage[data-chat="log"] .chat-ticker,
.stage[data-chat="hidden"] .chat-ticker,
.stage[data-chat="hidden"] .chat-bar,
.stage[data-chat="hidden"] .hud-note {
  display: none;
}

.ticker-line {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  max-width: 100%;
  padding: 0.25rem 0.7rem;
  border-radius: 12px;
  font-size: 0.9rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
  cursor: pointer;
  pointer-events: auto;
}

.ticker-line .dot {
  flex: none;
  align-self: center;
  margin-right: 0;
}

.ticker-name {
  flex: none;
  font-weight: 700;
}

.ticker-text {
  display: -webkit-box;
  min-width: 0;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

.ticker-line.is-fading {
  opacity: 0;
}

.chat-typing,
.chat-status,
.hud-note {
  align-self: flex-start;
  max-width: 100%;
  margin: 0;
  padding: 0.2rem 0.75rem;
  border-radius: 999px;
  font-size: 0.85rem;
}

.chat-status.is-error {
  color: var(--danger);
}

.hud-note {
  pointer-events: auto;
}

.chat-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
}

.chat-form {
  display: flex;
  flex: 1 1 18rem;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  margin: 0;
}

.chat-form input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  border-radius: 999px;
  box-shadow: var(--hud-shadow);
  padding: 0.35rem 0.9rem;
  min-height: 2rem;
  pointer-events: auto;
}

.chat-form input[type="text"]::placeholder {
  color: var(--hud-muted);
  opacity: 1;
}

.chat-modes {
  display: flex;
  gap: 0.375rem;
  margin-left: auto;
}

.btn-label {
  display: none;
}

.chat-show {
  position: relative;
  align-self: flex-start;
  min-width: 2.5rem;
  min-height: 2.5rem;
  font-size: 1.15rem;
}

.chat-unread {
  position: absolute;
  top: -0.35rem;
  right: -0.35rem;
  min-width: 1.3rem;
  padding: 0 0.3rem;
  color: var(--hud-primary-fg);
  background: var(--hud-primary);
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1.3rem;
  text-align: center;
}

.msg-delete[tabindex="-1"] {
  pointer-events: none;
}

/* Knock button (watchers) and toasts for hosts. */
.hud-actions {
  position: fixed;
  z-index: var(--z-hud);
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 4.5rem + var(--kb-inset, 0px));
  display: flex;
  justify-content: flex-end;
}

.knock-open {
  min-height: 2.5rem;
  padding: 0 1.1rem;
}

.hud-toasts {
  position: fixed;
  z-index: var(--z-toast);
  top: calc(env(safe-area-inset-top, 0px) + 3.5rem);
  left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.stage:has(.hud-flash .flash) .hud-toasts {
  top: calc(env(safe-area-inset-top, 0px) + 7rem);
}

.toast {
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-primary);
  border-radius: var(--radius);
  padding: 12px 14px;
  box-shadow: var(--hud-shadow);
  backdrop-filter: blur(8px);
  overflow-wrap: anywhere;
  pointer-events: auto;
}

.toast p {
  margin: 4px 0 0;
}

.toast-note {
  color: var(--hud-muted);
}

.toast-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

/* Popovers: native, so they open and close without JavaScript. Positioned with fixed CSS, not
   anchor positioning. No `display` here: that would override the closed state. */
.hud-sheet {
  position: fixed;
  inset: auto;
  margin: 0;
  width: min(22rem, calc(100vw - 1.5rem));
  max-height: calc(100dvh - 6rem);
  padding: 0.9rem 1rem 1rem;
  overflow: auto;
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  border-radius: var(--radius);
  box-shadow: var(--hud-shadow);
  backdrop-filter: blur(8px);
}

.hud-sheet::backdrop {
  background: transparent;
}

.menu-sheet,
.people-sheet,
.sound-sheet,
.shortcuts-sheet {
  top: calc(env(safe-area-inset-top, 0px) + 3.5rem);
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
}

.knock-sheet {
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
}

.tools-sheet {
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
  width: min(26rem, calc(100vw - 1.5rem));
  /* It scrolls inside this height: the time-zone list and two radio groups are tall on a phone. */
  max-height: calc(100dvh - 5rem);
}

.tools-sheet select {
  max-width: 100%;
}

.tools-sheet .button:disabled {
  opacity: 0.55;
  cursor: default;
}

.radio-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.9rem;
}

.field legend {
  padding: 0;
}

.field .radio-option {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 2rem;
  font-weight: 400;
  cursor: pointer;
}

.emote-sheet {
  left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
  --emote-bottom: calc(env(safe-area-inset-bottom, 0px) + 4.25rem + var(--kb-inset, 0px));
  bottom: var(--emote-bottom);
  width: auto;
  max-width: calc(100vw - 1.5rem);
  padding: 0.5rem;
  /* It sits inside .hud-chat (pointer-events: none), which top-layer popovers still inherit. */
  pointer-events: auto;
}

.emote-sheet:popover-open {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.sheet-head h2 {
  margin: 0;
  font-size: 1.1rem;
}

.sheet-head .hud-btn {
  box-shadow: none;
}

.hud-sheet a,
.hud-sheet button.link {
  color: var(--hud-link);
}

.hud-sheet .button {
  color: var(--hud-primary-fg);
  background: var(--hud-primary);
  border-color: var(--hud-primary);
}

.hud-sheet .button.secondary {
  color: var(--hud-link);
  background: transparent;
  border-color: var(--hud-link);
}

.hud-sheet input[type="text"],
.hud-sheet textarea,
.hud-sheet select {
  color: var(--hud-fg);
  background: var(--bg);
  border-color: var(--hud-border);
}

.menu-section {
  padding: 0.5rem 0;
  border-top: 1px solid var(--hud-border);
}

.menu-section h3 {
  margin: 0.25rem 0;
  color: var(--hud-muted);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.menu-text {
  margin: 0.25rem 0;
  overflow-wrap: anywhere;
}

.menu-who {
  display: flex;
  align-items: center;
  font-weight: 700;
}

.menu-list {
  margin: 0;
  padding: 0;
}

.menu-foot {
  padding-top: 0.5rem;
  border-top: 1px solid var(--hud-border);
}

/* ---- M7: site footer and the legal pages (docs/M7.md 2.9) ---------------------------------- */

.site-footer {
  max-width: 820px;
  margin: 0 auto;
  padding: 16px 16px 32px;
  border-top: 1px solid var(--footer-border);
  color: var(--footer-fg);
  font-size: 0.875rem;
}

.site-footer ul {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-footer a {
  color: var(--footer-link);
}

.legal {
  max-width: 68ch;
  margin-inline: auto;
}

.legal h1 {
  margin-bottom: 0;
}

.legal h2 {
  margin: 1.75em 0 0.4em;
  font-size: 1.2rem;
}

.legal p,
.legal li {
  overflow-wrap: anywhere;
}

.legal li + li {
  margin-top: 0.35em;
}

.legal-updated {
  margin-top: 0.25em;
}

.agree-note {
  font-size: 0.9rem;
}

.menu-item {
  display: block;
  width: 100%;
  min-height: 2.25rem;
  padding: 0.45rem 0.5rem;
  font: inherit;
  color: var(--hud-link);
  text-align: left;
  text-decoration: none;
  background: none;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}

.menu-item:hover {
  background: color-mix(in srgb, var(--hud-fg) 8%, transparent);
}

/* The menu's switches look like switches, not links with an On/Off word (UX review F11): a track
   with a knob that slides right and takes the primary colour when on. aria-pressed tells assistive
   tech; the drawing is for the eye only. */
.menu-item[aria-pressed] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  color: var(--hud-fg);
}

.menu-item[aria-pressed]::after {
  content: "";
  flex: none;
  width: 2.4rem;
  height: 1.4rem;
  background-color: color-mix(in srgb, var(--hud-fg) 28%, transparent);
  background-image: radial-gradient(circle closest-side, #fff 86%, transparent 100%);
  background-position: left 0.15rem center;
  background-repeat: no-repeat;
  background-size: 1.1rem 1.1rem;
  border-radius: 999px;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.25);
}

.menu-item[aria-pressed="true"]::after {
  background-color: var(--hud-primary);
  background-position: right 0.15rem center;
}

@media (forced-colors: active) {
  .menu-item[aria-pressed]::after {
    forced-color-adjust: none;
    background-color: Canvas;
    background-image: radial-gradient(circle closest-side, CanvasText 86%, transparent 100%);
    border: 1px solid CanvasText;
  }

  .menu-item[aria-pressed="true"]::after {
    background-color: Highlight;
  }
}

.menu-field {
  padding: 0.25rem 0.5rem;
}

.menu-field label {
  display: block;
  font-weight: 600;
}

.menu-field select {
  margin-top: 0.25rem;
}

.sound-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 0.75rem;
}

.sound-field label {
  font-weight: 600;
}

.sound-sheet input[type="range"] {
  width: 100%;
  accent-color: var(--hud-primary);
}

.ambience-weather {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.shortcut-table th,
.shortcut-table td {
  padding: 0.3rem 0.4rem;
  border-bottom: 1px solid var(--hud-border);
}

.shortcut-table th {
  width: 5rem;
  white-space: nowrap;
}

kbd {
  display: inline-block;
  min-width: 1.6rem;
  padding: 0 0.4rem;
  font: inherit;
  font-size: 0.85rem;
  text-align: center;
  border: 1px solid var(--hud-border);
  border-bottom-width: 2px;
  border-radius: 6px;
}

#knock-live {
  margin: 8px 0 0;
  font-weight: 600;
}

.knock-invited {
  border-color: var(--ok);
}

/* Roster (in the People popover) */
.chat-roster {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: 0;
}

.roster-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--hud-muted);
}

.roster-item.is-online {
  color: var(--hud-fg);
}

.roster-item .dot {
  margin-right: 2px;
  opacity: 0.45;
}

.roster-item.is-online .dot {
  opacity: 1;
  box-shadow: 0 0 0 2px var(--ok);
}

.roster-tag {
  font-size: 0.85rem;
}

.dot-former {
  background: var(--muted);
}

/* Log entries */
.msg {
  display: flex;
  align-items: baseline;
  gap: 4px;
  padding: 4px 0;
}

.msg .dot {
  flex: none;
}

.msg-main {
  flex: 1 1 auto;
  min-width: 0;
}

.msg-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0 8px;
  align-items: baseline;
}

.msg-name {
  font-weight: 600;
}

.msg-time {
  font-size: 0.8rem;
}

.msg .msg-delete {
  font-size: 0.8rem;
  color: var(--danger);
}

.msg-body {
  margin: 0;
  overflow-wrap: anywhere;
}

.chat-form .hud-btn:disabled {
  cursor: default;
}

/* An emoji with its name under it (UX review F13): 🥂 alone doesn't say "Cheers". */
.emote-button {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  font: inherit;
  font-size: 1.25rem;
  line-height: 1;
  min-width: 3.1rem;
  min-height: 2.5rem;
  padding: 6px 4px 5px;
  cursor: pointer;
  color: var(--hud-fg);
  background: transparent;
  border: 1px solid var(--hud-border);
  border-radius: 10px;
}

.emote-name {
  color: var(--hud-muted);
  font-size: 0.7rem;
  white-space: nowrap;
}

.emote-button:hover:not(:disabled) {
  border-color: var(--hud-primary);
}

.emote-button:disabled {
  opacity: 0.5;
  cursor: default;
}

.msg-emote .msg-emote-text {
  color: var(--hud-muted);
}

.msg-emote .msg-emote-emoji {
  font-size: 1.1rem;
  line-height: 1;
}

/* ---- The 3D cafe (docs/M4.md), now the page backdrop --------------------------------------- */
/* Per-theme colors for the backdrop and the fallback picture. Dark mode comes from the
   --<theme>-* names, which are redefined in both dark blocks at the top of this file. */
.scene-panel[data-cafe-theme="neon"] {
  --scene-sky-top: var(--neon-sky-top);
  --scene-sky-bottom: var(--neon-sky-bottom);
  --scene-wall: var(--neon-wall);
  --scene-floor: var(--neon-floor);
  --scene-wood: var(--neon-wood);
  --scene-accent: var(--neon-accent);
  --scene-glass: var(--neon-glass);
}

.scene-panel[data-cafe-theme="garden"] {
  --scene-sky-top: var(--garden-sky-top);
  --scene-sky-bottom: var(--garden-sky-bottom);
  --scene-wall: var(--garden-wall);
  --scene-floor: var(--garden-floor);
  --scene-wood: var(--garden-wood);
  --scene-accent: var(--garden-accent);
  --scene-glass: var(--garden-glass);
}

/* The time of day (set by ui/scene-panel.js from the local clock) tints the backdrop, which is
   also what the static fallback shows. The theme, dark mode and the daypart are independent. */
.scene-panel[data-daypart="dawn"] {
  --sky-tint: var(--sky-dawn);
  --sky-tint-mix: 35%;
  --sky-tint-low: 24%;
}

.scene-panel[data-daypart="dusk"] {
  --sky-tint: var(--sky-dusk);
  --sky-tint-mix: 40%;
  --sky-tint-low: 30%;
}

.scene-panel[data-daypart="night"] {
  --sky-tint: var(--sky-night);
  --sky-tint-mix: 84%;
  --sky-tint-low: 80%;
}

/* The weather tints the same backdrop (docs/M8.md 2.11). data-weather is the cafe's real weather,
   so every rule leaves out a viewer whose Weather effects switch is off (2.13): they see clear.
   Night softens the tint, so a storm doesn't light the sky. */
.scene-panel[data-daypart="night"] {
  --weather-dim: 0.3;
}

.scene-panel[data-weather="rain"]:not([data-weather-effects="off"]) {
  --weather-tint: var(--weather-rain);
  --weather-mix: calc(30% * var(--weather-dim));
}

.scene-panel[data-weather="storm"]:not([data-weather-effects="off"]) {
  --weather-tint: var(--weather-rain);
  --weather-mix: calc(48% * var(--weather-dim));
}

.scene-panel[data-weather="snow"]:not([data-weather-effects="off"]) {
  --weather-tint: var(--weather-snow);
  --weather-mix: calc(34% * var(--weather-dim));
}

.scene-panel[data-weather="fog"]:not([data-weather-effects="off"]) {
  --weather-tint: var(--weather-fog);
  --weather-mix: calc(46% * var(--weather-dim));
}

.scene-panel[data-weather="petals"]:not([data-weather-effects="off"]) {
  --weather-tint: var(--weather-petals);
  --weather-mix: calc(22% * var(--weather-dim));
}

.scene-panel {
  position: fixed;
  z-index: var(--z-stage);
  inset: 0;
  overflow: hidden;
  background: linear-gradient(
    180deg,
    color-mix(
      in srgb,
      color-mix(in srgb, var(--scene-sky-top), var(--sky-tint) var(--sky-tint-mix)),
      var(--weather-tint) var(--weather-mix)
    ),
    color-mix(
      in srgb,
      color-mix(in srgb, var(--scene-sky-bottom), var(--sky-tint) var(--sky-tint-low)),
      var(--weather-tint) calc(var(--weather-mix) * 0.8)
    )
  );
}

/* The moon (docs/M9a.md 6.5), drawn by ui/moon-sky.js onto a canvas that is GLOW (2.4) times the
   disc's width, so the halo has room. It sits behind the (transparent) 3D canvas and the static
   picture, upper right under the HUD buttons. --moon-opacity is the stars' weight from the cafe's
   clock, 0 in rain, storm, fog and snow and at a new moon. It never moves. */
.moon {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + var(--moon-top) - var(--moon-size) * 0.7);
  right: calc(env(safe-area-inset-right, 0px) + var(--moon-right) - var(--moon-size) * 0.7);
  width: calc(var(--moon-size) * 2.4);
  height: calc(var(--moon-size) * 2.4);
  opacity: var(--moon-opacity);
  pointer-events: none;
}

/* The fireworks (docs/M9a.md 15.4): ui/fireworks-sky.js draws onto a canvas that fills the backdrop,
   behind the 3D view like the moon. --fireworks-opacity is the moon's rule: the stars' weight from
   the cafe's clock, 0 in rain, storm, fog and snow, when the switch is off and when no state is on. */
.fireworks {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: var(--fireworks-opacity);
  pointer-events: none;
}

/* The far land (ui/landscape.js): hills, a town, a city or Doi Suthep, painted to fit the panel
   behind the 3D view (whose window glass shows it) and the static picture. The softness, the haze,
   the time of day and the weather are painted in (the stars and lights stay sharp). */
.landscape {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

@media (max-width: 639.98px) {
  .moon {
    --moon-size: 3.5rem;
    --moon-top: 3.5rem;
    --moon-right: 1rem;
  }
}

/* The free area the HUD leaves for the room (read by scene/index.js in resize()): fractions of
   the height from the top and bottom, and a length from the left. */
.scene {
  --scene-inset-top: 0.01;
  --scene-inset-bottom: 0.02;
  --scene-inset-left: 0px;
  --scene-fit-zoom: 1; /* > 1: the room may outgrow the width fit when the height allows */
  --scene-focus-x: 0.5; /* where the room's centre lands across the free width */
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.scene canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The page doesn't scroll, so a finger on the canvas orbits and pinches. */
  touch-action: none;
}

.scene-labels {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.scene-panel[data-state="loading"] .scene::after {
  content: attr(data-loading);
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--hud-muted);
  pointer-events: none;
}

.scene-panel[data-state="fallback"] .scene,
.scene-panel[data-state="hidden"] .scene,
.scene-panel[data-state="hidden"] .scene-seats,
.scene-panel[data-state="loading"] .scene-fallback,
.scene-panel[data-state="ready"] .scene-fallback {
  display: none;
}

.scene-fallback {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: calc(env(safe-area-inset-top, 0px) + 4rem) 1rem 6rem;
}

/* The rendered room (img/scene, UX review F8). Width and height stay auto under both limits, so the
   picture keeps its shape whichever one binds. */
.scene-art {
  display: block;
  flex: 0 1 auto;
  width: auto;
  max-width: min(100%, 52rem);
  height: auto;
  min-height: 0;
  max-height: 100%;
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}

.scene-note {
  order: -1;
  max-width: 100%;
  margin: 0.5rem 0;
  padding: 0.3rem 1rem;
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  border-radius: 999px;
  box-shadow: var(--hud-shadow);
  font-size: 0.9rem;
  text-align: center;
}

/* The list of seats is for keyboards and screen readers. It shows, as a HUD card under the cafe
   chip, while focus is inside it. */
.scene-seats {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
}

.scene-seats:not(:focus-within) {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.scene-seats:focus-within {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 3.5rem);
  left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
  max-width: min(26rem, calc(100vw - 1.5rem));
  padding: 0.5rem;
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  border-radius: var(--radius);
  box-shadow: var(--hud-shadow);
}

.seat-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  color: var(--hud-fg);
  background: transparent;
  border: 1px solid var(--hud-border);
  border-radius: 999px;
  padding: 4px 12px;
  cursor: pointer;
}

.seat-chip .dot {
  margin-right: 0;
}

.seat-meta {
  color: var(--hud-muted);
  font-size: 0.82rem;
}

/* Bubbles and name tags live in the label layer. They are only ever filled with textContent. */
.bubble-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding-bottom: 8px;
}

/* A host's stack hangs right over the hair: the star sits a few pixels above it. */
.bubble-stack.has-star {
  padding-bottom: 2px;
}

.bubble,
.name-tag {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 2px 8px var(--shadow);
  max-width: 14rem;
  overflow-wrap: anywhere;
}

.bubble {
  padding: 4px 10px;
  font-size: 0.82rem;
  line-height: 1.3;
}

.bubble.is-fading {
  opacity: 0;
}

.bubble-name {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--accent);
}

/* The animated "..." over a head while someone types. It sits nearest the head (order: 1). */
.typing-bubble {
  order: 1;
  display: inline-flex;
  gap: 3px;
  padding: 2px 10px;
  font-size: 1rem;
  line-height: 1.2;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 2px 8px var(--shadow);
}

.typing-dot {
  display: inline-block;
}

/* The host's star: the bottom item of the stack (docs/host-bar.md 5). Bubbles rise above it. */
.host-star {
  order: 2;
  display: block;
  flex: none;
  width: 22px;
  height: 22px;
  overflow: visible;
  pointer-events: none;
  fill: var(--star);
  filter: drop-shadow(0 0 3px var(--star)) drop-shadow(0 1px 1px var(--shadow));
}

.name-tag {
  padding: 2px 10px;
  margin-top: 6px;
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
}

/* The emoji over a head while an emote plays. The anchor belongs to CSS2DRenderer (it sets its
   transform), so the pop-in animation is on the inner badge. */
.emote-badge-anchor {
  pointer-events: none;
}

.emote-badge {
  margin-left: 0.4rem;
  padding: 2px 6px;
  font-size: 1.4rem;
  line-height: 1.1;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: 0 2px 8px var(--shadow);
}

/* ---- Layout by size (docs/M6.md 2.2) -------------------------------------------------------- */

/* Phone (< 640px): the cafe chip shows the name only, the log is a bottom sheet, popovers are
   sheets, and the emote button joins the mode buttons in a row above the input. */
@media (max-width: 639px) {
  .cafe-chip {
    flex: 0 1 auto;
    padding-right: 0.5rem;
  }

  .hud-top,
  .hud-right {
    gap: 0.375rem;
  }

  .hud-chip-button {
    padding: 0 0.4rem;
  }

  #sound-settings-toggle {
    min-width: 2.25rem;
    padding: 0;
  }

  .cafe-seats,
  .chat-watchers,
  #cafe-clock-where {
    display: none;
  }

  /* The name keeps its room: the clock (time or label only) sits under it, not beside it. */
  .cafe-chip {
    display: inline-grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 0.25rem;
    align-items: center;
    padding-top: 0.1rem;
    padding-bottom: 0.1rem;
  }

  .cafe-chip .chip-home {
    grid-row: 1 / span 2;
  }

  .cafe-chip .cafe-name {
    grid-column: 2;
    font-size: 1rem;
    line-height: 1.1;
  }

  /* An event's short name replaces the full one, which stays for screen readers. */
  .cafe-chip .cafe-name-full {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .cafe-chip .cafe-name-short {
    display: block;
  }

  .cafe-clock {
    grid-column: 2;
    margin-left: 0;
    font-size: 0.75rem;
    line-height: 1.1;
  }

  .chat-panel {
    height: 60dvh;
    max-height: calc(100dvh - 9rem);
    border-radius: var(--radius) var(--radius) 0 0;
  }

  .chat-bar {
    display: grid;
    grid-template-columns: 1fr auto auto auto;
    gap: 0.375rem;
  }

  .chat-form,
  .chat-modes {
    display: contents;
  }

  .chat-form label {
    grid-row: 2;
  }

  .chat-form input[type="text"] {
    grid-row: 2;
    grid-column: 1 / 4;
  }

  .chat-form #chat-send {
    grid-row: 2;
    grid-column: 4;
  }

  #chat-emote-toggle {
    grid-row: 1;
    grid-column: 2;
  }

  #chat-log-toggle {
    grid-row: 1;
    grid-column: 3;
  }

  #chat-hide-toggle {
    grid-row: 1;
    grid-column: 4;
  }

  .stage:has(.hud-actions) .chat-ticker {
    margin-right: 6.5rem;
  }

  .scene {
    --scene-inset-bottom: 0.2; /* the ticker lines may overlap the front corner of the tile */
    --scene-fit-zoom: 1.25;
  }

  .stage[data-chat="log"] .scene {
    --scene-inset-bottom: 0.62;
  }

  .stage[data-chat="hidden"] .scene {
    --scene-inset-bottom: 0.12;
  }

  .menu-sheet,
  .people-sheet,
  .sound-sheet,
  .shortcuts-sheet {
    left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
    width: auto;
  }

  .knock-sheet,
  .tools-sheet {
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 1rem);
    border-radius: var(--radius) var(--radius) 0 0;
  }

  .emote-sheet {
    --emote-bottom: calc(env(safe-area-inset-bottom, 0px) + 7.25rem + var(--kb-inset, 0px));
  }

  .hud-actions {
    bottom: calc(env(safe-area-inset-bottom, 0px) + 7rem + var(--kb-inset, 0px));
  }
}

/* Tablet and up (>= 640px): the chat is a column bottom left, toasts sit top right. */
@media (min-width: 640px) {
  .hud-chat {
    right: auto;
    width: min(26rem, 60vw);
  }

  .stage[data-chat="log"] .hud-chat {
    top: calc(env(safe-area-inset-top, 0px) + 3.75rem);
    width: 22rem;
  }

  .stage[data-chat="log"] .chat-panel {
    flex: 1 1 auto;
  }

  .stage[data-chat="log"] .scene {
    --scene-inset-bottom: 0.14;
  }

  .scene {
    --scene-inset-bottom: 0.02; /* the HUD sits over the room's empty corners */
  }

  .hud-toasts {
    left: auto;
    width: min(22rem, calc(100vw - 1.5rem));
  }

  .stage:has(.hud-flash .flash) .hud-toasts {
    top: calc(env(safe-area-inset-top, 0px) + 3.5rem);
  }

  .hud-actions {
    bottom: calc(env(safe-area-inset-bottom, 0px) + 0.75rem + var(--kb-inset, 0px));
  }

  .emote-sheet {
    --emote-bottom: calc(env(safe-area-inset-bottom, 0px) + 3.75rem + var(--kb-inset, 0px));
  }

  .scene-fallback {
    justify-content: center;
  }

  .stage[data-chat="log"] .scene-seats:focus-within {
    left: calc(env(safe-area-inset-left, 0px) + 21rem);
  }
}

/* Desktop (>= 960px) */
/* Tablet portrait: the input gets its own row (Log and Hide wrap below it), so the placeholder
   fits in the narrower column. Short landscape phones keep one row to save height. */
@media (min-width: 640px) and (max-width: 959.98px) and (min-height: 521px) {
  .chat-form {
    flex-basis: 100%;
  }
}

/* Tablet held upright: grow the room into the spare height. */
@media (min-width: 640px) and (max-width: 959.98px) and (orientation: portrait) {
  .scene {
    --scene-fit-zoom: 1.15;
  }
}

@media (min-width: 960px) {
  .hud-chat {
    width: min(28rem, 40vw);
  }

  .stage[data-chat="log"] .hud-chat {
    width: 24rem;
  }

  .stage[data-chat="log"] .scene {
    --scene-inset-left: 25rem;
    --scene-inset-bottom: 0.02;
  }

  /* The static picture is centred in the area the log panel leaves free. */
  .stage[data-chat="log"] .scene-fallback {
    padding-left: 26rem;
  }

  .stage[data-chat="log"] .scene-seats:focus-within {
    left: calc(env(safe-area-inset-left, 0px) + 25rem);
  }

  .btn-label {
    display: inline;
  }
}

/* A phone on its side has little height: one-line ticker entries and a smaller margin. */
@media (orientation: landscape) and (max-height: 520px) {
  .ticker-text {
    -webkit-line-clamp: 1;
    line-clamp: 1;
  }

  .scene {
    --scene-inset-top: 0.14;
    --scene-inset-bottom: 0.2;
  }

  .stage[data-chat="log"] .scene {
    --scene-inset-bottom: 0.2;
  }

  .scene-fallback {
    padding-top: calc(env(safe-area-inset-top, 0px) + 3rem);
    padding-bottom: 4.5rem;
  }

  .scene-art {
    max-height: calc(100dvh - 9rem);
  }

  .sheet-head {
    margin-bottom: 0.25rem;
  }
}

/* Bigger targets for fingers. */
@media (pointer: coarse) {
  .hud-btn,
  .hud-chip-button,
  .menu-item,
  .emote-button,
  .chip-home {
    min-width: 2.75rem;
    min-height: 2.75rem;
  }

  .hud-chip {
    min-height: 2.75rem;
  }

  .chat-form input[type="text"] {
    min-height: 2.75rem;
    font-size: 1rem;
  }

  /* A finger-sized target for Delete that doesn't make the log rows taller. */
  .msg .msg-delete {
    min-width: 2.75rem;
    padding: 0.85rem 0.5rem;
    margin: -0.85rem -0.25rem;
  }
}

/* Without JavaScript the log panel would be an empty box: the noscript note says what's up. */
@media (scripting: none) {
  .chat-panel {
    display: none;
  }
}

/* Browsers without the Popover API: every card becomes a static card in a scrolling column under
   the stage, so nothing is lost. */
@supports not selector(:popover-open) {
  body.page-stage {
    overflow: auto;
  }

  .stage {
    height: auto;
    min-height: 100vh;
    overflow: visible;
    padding-top: 100vh;
  }

  .hud-sheet {
    position: relative;
    max-width: 30rem;
    width: auto;
    max-height: none;
    margin: 1rem auto;
  }

  [popovertarget] {
    display: none;
  }
}

@media (prefers-reduced-motion: no-preference) {
  /* The home page cafe cards' picture (UX review F9) and the menu switches (F11). */
  .cafe-card-art img {
    transition: transform 0.3s ease;
  }

  .cafe-card:hover .cafe-card-art img {
    transform: scale(1.03);
  }

  .menu-item[aria-pressed]::after {
    transition: background-color 0.15s ease, background-position 0.15s ease;
  }

  /* Lightning (docs/M8.md 2.9): the second lock. The scene sets data-flash for a spike only when
     motion is allowed, and this rule only exists when the browser allows motion too. No
     transition, so it is a spike and not a fade. It brightens, it doesn't go white. */
  .scene-panel[data-flash="on"]:not([data-weather-effects="off"]) {
    --weather-tint: var(--weather-flash);
    --weather-mix: 40%;
  }

  .button,
  .hud-btn,
  .hud-chip-button,
  .menu-item,
  details.confirm {
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
  }

  .button:hover {
    filter: brightness(1.05);
  }

  .toast,
  .hud-flash .flash {
    animation: toast-in 0.2s ease-out;
  }

  .hud-sheet:popover-open {
    animation: sheet-in 0.15s ease-out;
  }

  .stage[data-chat="log"] .chat-panel {
    animation: sheet-in 0.15s ease-out;
  }

  .ticker-line {
    animation: ticker-in 0.2s ease-out;
  }

  .ticker-line.is-fading {
    animation: ticker-out 0.6s ease forwards;
  }

  .bubble {
    transition: opacity 0.5s ease;
  }

  .typing-dot {
    animation: typing-pulse 1.2s ease-in-out infinite;
  }

  .typing-dot:nth-child(2) {
    animation-delay: 0.2s;
  }

  .typing-dot:nth-child(3) {
    animation-delay: 0.4s;
  }

  /* The star twinkles slowly: a breath of size and glow, two tiny sparks out of step. */
  .host-star {
    animation: star-glow 2.4s ease-in-out infinite;
  }

  .host-star-main {
    transform-box: fill-box;
    transform-origin: center;
    animation: star-twinkle 2.4s ease-in-out infinite;
  }

  .host-star-spark {
    transform-box: fill-box;
    transform-origin: center;
    animation: star-spark 2.4s ease-in-out infinite;
  }

  .host-star-spark-b {
    animation-delay: 1.2s;
  }

  @keyframes star-twinkle {
    0%,
    100% {
      transform: scale(0.92);
    }

    50% {
      transform: scale(1.06);
    }
  }

  @keyframes star-glow {
    0%,
    100% {
      filter: drop-shadow(0 0 2px var(--star)) drop-shadow(0 1px 1px var(--shadow));
    }

    50% {
      filter: drop-shadow(0 0 6px var(--star)) drop-shadow(0 1px 1px var(--shadow));
    }
  }

  @keyframes star-spark {
    0%,
    100% {
      opacity: 0.35;
      transform: scale(0.6);
    }

    50% {
      opacity: 1;
      transform: scale(1);
    }
  }

  .emote-badge {
    animation: badge-pop 0.25s ease-out;
  }

  @keyframes badge-pop {
    from {
      opacity: 0;
      transform: scale(0.3);
    }

    60% {
      transform: scale(1.15);
    }
  }

  @keyframes typing-pulse {
    0%,
    60%,
    100% {
      opacity: 0.35;
      transform: translateY(0);
    }

    30% {
      opacity: 1;
      transform: translateY(-3px);
    }
  }

  @keyframes toast-in {
    from {
      opacity: 0;
      transform: translateY(8px);
    }
  }

  @keyframes sheet-in {
    from {
      opacity: 0;
      transform: translateY(6px);
    }
  }

  @keyframes ticker-in {
    from {
      opacity: 0;
      transform: translateY(6px);
    }
  }

  @keyframes ticker-out {
    to {
      opacity: 0;
    }
  }
}

/* ---- M9a: the night sky of the event places (docs/M9a.md 6) --------------------------------- */
/* The canvas is transparent; the sky is this backdrop. Stars are tiny radial gradients, so there is
   no image file and nothing for the CSP. The daypart and weather tints don't apply here: an event
   place is always night, whatever the clock says. */
.scene-panel[data-place="riverside"],
.scene-panel[data-place="festival"] {
  background:
    radial-gradient(1.5px 1.5px at 8% 12%, var(--event-star), transparent),
    radial-gradient(1px 1px at 14% 31%, var(--event-star), transparent),
    radial-gradient(1.5px 1.5px at 23% 8%, var(--event-star), transparent),
    radial-gradient(1px 1px at 31% 22%, var(--event-star), transparent),
    radial-gradient(1.5px 1.5px at 42% 14%, var(--event-star), transparent),
    radial-gradient(1px 1px at 49% 5%, var(--event-star), transparent),
    radial-gradient(1.5px 1.5px at 58% 26%, var(--event-star), transparent),
    radial-gradient(1px 1px at 66% 11%, var(--event-star), transparent),
    radial-gradient(1.5px 1.5px at 73% 6%, var(--event-star), transparent),
    radial-gradient(1px 1px at 81% 20%, var(--event-star), transparent),
    radial-gradient(1.5px 1.5px at 89% 9%, var(--event-star), transparent),
    radial-gradient(1px 1px at 95% 28%, var(--event-star), transparent),
    radial-gradient(1px 1px at 4% 38%, var(--event-star), transparent),
    radial-gradient(1px 1px at 37% 36%, var(--event-star), transparent),
    radial-gradient(1px 1px at 77% 34%, var(--event-star), transparent),
    linear-gradient(180deg, var(--event-sky-top) 0%, var(--event-sky-mid) 46%, var(--event-sky-bottom) 78%, var(--event-sky-glow) 100%);
}

/* ---- M9a: special events (docs/M9a.md) ------------------------------------------------------ */

/* Thai: no letter-spacing (it pulls the stacked vowels and tone marks apart), no capitals. */
html[lang="th"] body {
  line-height: 1.6;
}

html[lang="th"] .menu-section h3 {
  letter-spacing: 0;
  text-transform: none;
}

/* The ไทย / EN switch: two small buttons in the header, the top bar and the menu. */
.lang-switch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin: 0;
}

.lang-switch button {
  min-width: 2.25rem;
  min-height: 2rem;
  padding: 0 0.5rem;
  color: inherit;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.lang-switch button[aria-current="true"] {
  color: var(--accent-text);
  background: var(--accent);
  font-weight: 700;
}

.hud-lang button[aria-current="true"],
.menu-lang button[aria-current="true"] {
  color: var(--hud-primary-fg);
  background: var(--hud-primary);
}

.lang-switch button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.lang-chip {
  padding: 0 0.15rem;
}

/* On a narrow phone the top bar has no room for it; the language switch is in the menu too. */
@media (max-width: 519.98px) {
  .hud-top .lang-chip {
    display: none;
  }
}

/* The event HUD: the launch button, the wish list, a seat, at the bottom right. */
.event-actions {
  position: fixed;
  z-index: var(--z-hud);
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 7rem + var(--kb-inset, 0px));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
  max-width: calc(100vw - 1.5rem);
  pointer-events: none;
}

.event-actions > * {
  pointer-events: auto;
}

.event-launch {
  min-height: 2.75rem;
  padding: 0 1.2rem;
  font-size: 1.05rem;
}

.event-launch:disabled {
  opacity: 0.6;
}

.event-count {
  display: inline-block;
  min-width: 1.5rem;
  padding: 0 0.4rem;
  color: var(--event-gold-fg);
  background: var(--event-gold);
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 700;
  text-align: center;
}

.event-notice,
.event-login {
  flex-basis: 100%;
  margin: 0;
  padding: 0.35rem 0.8rem;
  border-radius: var(--radius);
  font-size: 0.9rem;
  text-align: right;
}

.event-login {
  flex-basis: auto;
}

.event-login a {
  color: var(--hud-link);
  font-weight: 700;
}

@media (min-width: 640px) {
  .event-actions {
    bottom: calc(env(safe-area-inset-bottom, 0px) + 0.75rem + var(--kb-inset, 0px));
    flex-direction: column-reverse;
    align-items: flex-end;
    max-width: 22rem;
  }

  .event-notice,
  .event-login {
    flex-basis: auto;
  }
}

/* The overflow light mode (docs/M9a.md 14.3): over the launch button, never over the scene. */
.event-overflow {
  flex-basis: 100%;
  margin: 0;
  padding: 0.6rem 0.8rem;
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  border-radius: var(--radius);
  box-shadow: var(--hud-shadow);
  backdrop-filter: blur(8px);
  font-size: 0.9rem;
}

.event-overflow h2 {
  margin: 0 0 0.25rem;
  font-size: 1rem;
}

.event-overflow p {
  margin: 0 0 0.5rem;
}

@media (min-width: 640px) {
  .event-overflow {
    flex-basis: auto;
    width: 22rem;
  }
}

/* Sheets: bottom right, like the knock card; a bottom sheet on a phone. */
.wish-sheet,
.wishes-sheet,
.share-sheet,
.wish-card {
  right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
  width: min(24rem, calc(100vw - 1.5rem));
  max-height: calc(100dvh - 5rem);
}

@media (max-width: 639.98px) {
  .wish-sheet,
  .wishes-sheet,
  .share-sheet,
  .wish-card {
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 1rem);
    border-radius: var(--radius) var(--radius) 0 0;
  }
}

.wish-sheet textarea {
  width: 100%;
  resize: none;
}

.wish-sheet .field {
  margin-bottom: 0.6rem;
}

.wish-sheet input[readonly] {
  opacity: 0.8;
}

.wish-styles {
  margin: 0 0 0.6rem;
  padding: 0;
  border: 0;
}

.wish-styles legend {
  margin-bottom: 0.3rem;
  font-weight: 700;
}

.style-options {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.4rem;
}

.style-option {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  padding: 0.4rem 0.2rem;
  border: 2px solid var(--hud-border);
  border-radius: var(--radius);
  font-size: 0.8rem;
  text-align: center;
  cursor: pointer;
}

.style-option input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.style-option:has(input:checked) {
  border-color: var(--event-gold);
  background: color-mix(in srgb, var(--event-gold) 22%, transparent);
}

.style-option:has(input:focus-visible) {
  outline: 2px solid var(--hud-link);
  outline-offset: 2px;
}

.style-icon {
  font-size: 1.6rem;
  line-height: 1.2;
}

.wish-warning {
  margin: 0.4rem 0;
  padding: 0.5rem 0.65rem;
  color: var(--hud-fg);
  background: color-mix(in srgb, var(--event-gold) 20%, transparent);
  border-left: 4px solid var(--event-gold);
  border-radius: 8px;
  font-size: 0.9rem;
}

.wish-private {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.25rem;
  margin-bottom: 0.4rem;
}

.wish-error {
  min-height: 1.2em;
  margin: 0.2rem 0 0.4rem;
  color: var(--danger);
  font-weight: 700;
}

.wish-error:empty {
  display: none;
}

.wish-login {
  margin: 0.6rem 0 0;
}

#wish-counter.is-full {
  color: var(--danger);
  font-weight: 700;
}

.wish-list {
  margin: 0 0 0.5rem;
  padding: 0;
  list-style: none;
}

.wish-item {
  padding: 0.55rem 0;
  border-top: 1px solid var(--hud-border);
  overflow-wrap: anywhere;
}

.wish-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}

.wish-name {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-weight: 700;
}

.wish-star {
  color: var(--event-gold);
}

.wish-time {
  flex: none;
  font-size: 0.8rem;
}

.wish-text {
  margin: 0.15rem 0;
}

.wish-actions {
  display: flex;
  justify-content: flex-end;
}

.wish-actions .link {
  min-height: 2rem;
  padding: 0 0.25rem;
  font-size: 0.85rem;
  background: none;
  border: 0;
  text-decoration: underline;
  cursor: pointer;
}

.wish-card-name {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  overflow-wrap: anywhere;
}

.wish-card-body {
  margin: 0.4rem 0;
  font-size: 1.1rem;
  overflow-wrap: anywhere;
}

.wish-card-body.is-private {
  color: var(--hud-muted);
  font-style: italic;
}

.wish-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.5rem 0;
}

.wish-report fieldset {
  margin: 0 0 0.6rem;
  padding: 0;
  border: 0;
}

.wish-report legend {
  margin-bottom: 0.3rem;
  font-weight: 700;
}

.wish-report label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.1rem;
  cursor: pointer;
}

/* The queue and "still here?" cards sit in the middle, over the scene. */
.event-card {
  position: fixed;
  z-index: var(--z-panel);
  top: 50%;
  left: 50%;
  width: min(24rem, calc(100vw - 2rem));
  padding: 1.1rem 1.2rem;
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  border-radius: var(--radius);
  box-shadow: var(--hud-shadow);
  backdrop-filter: blur(8px);
  text-align: center;
  transform: translate(-50%, -50%);
}

.event-card h2 {
  margin: 0 0 0.4rem;
  font-size: 1.25rem;
}

.event-card .button {
  color: var(--hud-primary-fg);
  background: var(--hud-primary);
  border-color: var(--hud-primary);
}

.queue-text {
  font-size: 1.15rem;
  font-weight: 700;
}

/* A launched wish shows its name and words for a few seconds, drawn as DOM over the scene. The
   element CSS2DRenderer moves gets no transform of its own here; the card inside is styled. */
.wish-label {
  pointer-events: none;
}

.wish-label-card {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  max-width: 15rem;
  padding: 0.35rem 0.7rem;
  color: #fff7e6;
  background: rgba(24, 18, 40, 0.82);
  border: 1px solid var(--event-gold);
  border-radius: 14px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  font-size: 0.85rem;
  line-height: 1.35;
  text-align: center;
  overflow-wrap: anywhere;
}

.wish-label-name {
  color: var(--event-gold);
}

.wish-label.is-leaving .wish-label-card {
  opacity: 0;
}

.toast-error {
  border-color: var(--danger);
}

/* The event pictures fill the page like the cafe picture does. */
.scene-panel[data-place="riverside"] .scene-art,
.scene-panel[data-place="festival"] .scene-art {
  max-width: min(100%, 56rem);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}

/* A phone held upright: grow the tile into the spare height; the corners may crop. The riverside
   grows further and looks a little right of the jetty, so the river the krathongs drift along
   (left to right, from the jetty) fills the screen instead of an empty band (UX review F1). */
@media (max-width: 639.98px) {
  .scene-panel[data-place="riverside"] .scene {
    --scene-fit-zoom: 2;
    --scene-focus-x: 0.36;
    --scene-inset-top: 0.14; /* the room sits lower, closer to the launch button */
  }

  .scene-panel[data-place="festival"] .scene {
    --scene-fit-zoom: 1.35;
  }
}

/* The home page: the Special events band (docs/M9a.md 9). */
.events-band {
  margin: 0 0 1.5rem;
  padding: 1rem;
  color: #fff7e6;
  background: linear-gradient(180deg, var(--event-sky-top), var(--event-sky-mid));
  border: 1px solid color-mix(in srgb, var(--event-gold) 55%, transparent);
  border-radius: var(--radius);
}

.events-band h2 {
  margin: 0 0 0.2rem;
  color: var(--event-gold);
}

.events-band .events-lede {
  margin: 0 0 0.8rem;
  color: #e6dcc8;
}

.events-countdown {
  margin: 0 0 0.8rem;
  padding: 0.5rem 0.8rem;
  color: var(--event-gold-fg);
  background: var(--event-gold);
  border-radius: var(--radius);
  font-weight: 700;
}

.event-cards {
  display: grid;
  gap: 0.8rem;
  margin: 0;
  padding: 0;
  list-style: none;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.event-card-link {
  display: block;
  height: 100%;
  overflow: hidden;
  color: #fff7e6;
  background: rgba(8, 10, 30, 0.55);
  border: 1px solid rgba(255, 247, 230, 0.25);
  border-radius: var(--radius);
  text-decoration: none;
}

.event-card-link:hover,
.event-card-link:focus-visible {
  border-color: var(--event-gold);
}

.event-card-link:focus-visible {
  outline: 3px solid var(--event-gold);
  outline-offset: 2px;
}

.event-card-art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 960 / 504;
  object-fit: cover;
}

.event-card-body {
  padding: 0.7rem 0.9rem 0.9rem;
}

/* One event place alone (2026: only the riverside opens): on a wide screen the picture and the text
   sit side by side instead of a picture as wide as the band. */
@media (min-width: 640px) {
  .event-cards > li:only-child .event-card-link {
    display: grid;
    grid-template-columns: 3fr 2fr;
    align-items: center;
  }

  .event-cards > li:only-child .event-card-body {
    padding: 1rem 1.2rem;
  }
}

.event-card-body h3 {
  margin: 0;
  font-size: 1.1rem;
}

.event-card-sub {
  margin: 0;
  color: #e6dcc8;
  font-size: 0.9rem;
}

.event-card-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.9rem;
}

.event-phase {
  display: inline-block;
  margin-top: 0.4rem;
  padding: 0.1rem 0.6rem;
  color: var(--event-gold-fg);
  background: var(--event-gold);
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 700;
}

@media (prefers-reduced-motion: no-preference) {
  .wish-label-card {
    transition: opacity 0.5s ease;
  }
}

/* The only motion: it fades in and out as the stars do. Reduced motion changes it at once. */
@media (prefers-reduced-motion: no-preference) {
  .moon {
    transition: opacity 1.2s ease;
  }

  .fireworks {
    transition: opacity 1.2s ease;
  }
}

/* /admin/events control bar (docs/UX-batch2.md D): stays at the top while the page scrolls, so the
   Pause buttons and the live counts are always in reach. Sticky only works while no ancestor has
   an overflow other than visible (main.page has none). */
.admin-bar {
  position: sticky;
  top: 0;
  z-index: var(--z-hud);
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  align-items: center;
  margin: 0 -16px 16px;
  padding: 10px 16px;
  background: var(--bg);
  border-bottom: 2px solid var(--border);
}

.admin-bar > * {
  margin: 0;
}

.admin-bar form.inline {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

.admin-bar .admin-bar-group {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: center;
}

/* The state words (Running, Paused, Open, Off) carry a border, so they stay visible when forced
   colours remove the page's backgrounds. */
.admin-bar [data-live$=".launching"],
.admin-bar [data-live="signup.state"] {
  display: inline-block;
  padding: 0 8px;
  border: 1px solid currentcolor;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 700;
}

.admin-bar #live-status {
  flex-basis: 100%;
  font-size: 0.85rem;
}

@media (forced-colors: active) {
  .admin-bar {
    background: Canvas;
    border-bottom-color: CanvasText;
  }
}

/* The share picture sheet (docs/UX-batch2.md E.3): the finished picture is the canvas itself (a
   blob image would be blocked by the CSP's img-src), shown at most half the height so the
   buttons stay in reach on a phone. No animation of its own: the sheet's pop-in is already off
   under reduced motion. */
.share-canvas {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 50dvh;
  margin: 0 auto 0.6rem;
  aspect-ratio: 4 / 5;
  border-radius: 6px;
  background: var(--event-sky-top);
}

.share-sheet[data-format="story"] .share-canvas {
  aspect-ratio: 9 / 16;
}

.share-format {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin: 0 0 0.6rem;
  padding: 0;
  border: 0;
  font-size: 0.9rem;
}

.share-format legend {
  float: left;
  margin-right: 0.25rem;
  padding: 0;
  font-weight: 600;
}

.share-format label {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  min-height: 2rem;
}

.share-links-label {
  margin: 0.75rem 0 0.4rem;
  font-size: 0.85rem;
  font-weight: 600;
}

.share-links .button {
  text-decoration: none;
}

/* Each brand's own logo beside its name: Facebook's blue round mark and Instagram's gradient glyph
   read on light and dark alike; X's mark is a mask filled with --brand-x-mark, black on light and
   white on dark, as X asks. */
.share-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 2.5rem;
  padding: 6px 12px;
}

.share-links + .share-hint {
  margin-top: 0.5rem;
}

.brand-logo {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
}

.brand-x {
  background: var(--brand-x-mark);
  -webkit-mask: url("../img/brand/x.svg") center / contain no-repeat;
  mask: url("../img/brand/x.svg") center / contain no-repeat;
}

.share-sheet[data-state="busy"] .share-canvas {
  opacity: 0.5;
}

.share-status:empty {
  display: none;
}

.share-status,
.share-hint {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
}

.share-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.share-actions .button:disabled {
  opacity: 0.6;
}

/* The last visit in an empty cafe (docs/UX-batch2.md C): a small chip under the cafe's own, filled
   by ui/last-visit.js. It is plain HTML, so it shows without WebGL. */
.last-visit {
  position: fixed;
  z-index: var(--z-hud);
  top: calc(env(safe-area-inset-top, 0px) + 3.3rem);
  left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
  max-width: calc(100vw - 1.5rem);
  color: var(--hud-fg);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  box-shadow: var(--hud-shadow);
  backdrop-filter: blur(8px);
  pointer-events: none;
}
