  :root {
    --ink: #0d1f17;
    --panel: #16301f;
    --panel-edge: #0a2011;
    --moon: #eef1e6;
    --lantern: #e8b24d;
    --berry: #c13f3f;
    --mist: #9db2a3;
  }

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

  html, body {
    margin: 0;
    height: 100%;
    background: var(--ink);
    color: var(--moon);
    font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
    overflow-x: hidden;
  }

  body {
    min-height: 100vh;
    position: relative;
    cursor: crosshair;
  }

  .world {
    position: fixed;
    inset: 0;
    z-index: 0;
  }

  .zone {
    position: relative;
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    /* The room painting is object-fit: contain, so whatever sits here shows
       around it. Stone rather than a flat green wash, so the letterbox reads
       as the wall the room is a window in. */
    background:
      linear-gradient(rgba(8, 11, 9, 0.55), rgba(5, 7, 6, 0.74)),
      url("assets/ui/wall-plain.png") center / cover;
    -webkit-user-select: none; user-select: none;
    -webkit-touch-callout: none;
    touch-action: none;
  }

  .zone-under {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    z-index: 0;
    -webkit-user-drag: none;
    user-select: none;
    pointer-events: none;
    /* Follows the contained painting's real edges rather than the element
       box, so the room sits in a recess in the stone instead of being pasted
       flat onto it. The transition is here for the spirit desaturation below,
       which animates on this same property. */
    filter: drop-shadow(0 0 26px rgba(0, 0, 0, 0.85));
    transition: filter 1600ms ease;
  }

  .zone-under.fallback {
    filter: hue-rotate(150deg) saturate(1.5) contrast(1.05) drop-shadow(0 0 26px rgba(0, 0, 0, 0.85));
  }

  /* ---- before there is anyone to be ----
     A spirit has no character yet, so the world is held at arm's length:
     nearly colourless, a shade dimmer. Colour floods back when a character
     is chosen (undoSpiritModeUi drops the class). */
  body.is-spirit .zone-under {
    filter: grayscale(0.88) brightness(0.84) contrast(1.04)
      drop-shadow(0 0 26px rgba(0, 0, 0, 0.85));
  }

  /* The scratch canvas sits above .zone-under and is what's actually on
     screen most of the time, so desaturating the img alone changes nothing
     visible. The firefly cursor canvas is deliberately left in colour — it is
     the spirit's own light, not the world's. */
  .zone-scratch { transition: filter 1600ms ease; }

  body.is-spirit .zone-scratch {
    filter: grayscale(0.88) brightness(0.84) contrast(1.04);
  }

  body.is-spirit .zone-under.fallback {
    filter: grayscale(0.88) brightness(0.84) contrast(1.04)
      drop-shadow(0 0 26px rgba(0, 0, 0, 0.85));
  }

  .zone-scratch {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
    pointer-events: none;
  }

  .egg {
    position: absolute;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: transparent;
    border: none;
    cursor: pointer;
    transform: translate(-50%, -50%);
  }

  .egg:hover, .egg:focus-visible {
    box-shadow: 0 0 18px 8px rgba(232, 178, 77, 0.35);
    outline: none;
  }

  .egg.found { cursor: default; }
  .egg.found:hover { box-shadow: none; }

  .portal {
    position: absolute;
    z-index: 3; /* above .zone-scratch (1) and .portalMist (2) — a beacon is
                   always visible from arrival even in a misted room; only
                   the ground between you and it is meant to be obscured */
    width: 46px;
    height: 46px;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    background: rgba(13, 31, 23, 0.55);
    border: 2px solid var(--lantern);
    color: var(--moon);
    font-size: 1.3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 0 16px 4px rgba(232, 178, 77, 0.35);
    animation: portal-pulse 2.4s ease-in-out infinite;
  }

  /* Drawn SVG icon set (see ICON_PATHS/iconSvgMarkup in app.js) replacing
     raw emoji on portal/character/item badges — single-weight gold
     strokes via currentColor, sized down a step for the two smaller
     hotspot kinds so they keep the same visual weight relative to their
     badge. Centering is the flex rules above/on those badges — a plain
     text emoji auto-wraps as a flex item the same way, so this is safe
     for the emoji-fallback case too. */
  .drawnIcon { width: 22px; height: 22px; color: var(--lantern); display: block; }
  .character-hotspot .drawnIcon, .loot-hotspot .drawnIcon { width: 18px; height: 18px; }
  .portal:hover { transform: translate(-50%, -50%) scale(1.08); }
  .portal:focus-visible { outline: 2px solid var(--moon); outline-offset: 3px; }

  /* Dwell-to-commit ring (see public/dwell.js + createPortalButton in
     app.js) — same conic-gradient-clipped-to-a-ring technique as
     .hallRing/.hallRingWrap, just anchored as a child of the beacon itself
     rather than a separately-positioned element, since a portal (unlike a
     hall arch) is already a simple round hotspot. Core functional feedback
     for how much of the hold is done, so — like .hallRing — it is not
     suppressed under prefers-reduced-motion, only left un-eased. Never
     appears on .portal-origin (the way back — see createPortalButton, which
     never attaches a beacon/ring to that one at all). */
  .portalRing {
    position: absolute;
    inset: -9px;
    border-radius: 50%;
    pointer-events: none;
    opacity: 0;
    background: conic-gradient(from -90deg, var(--lantern) 0deg, var(--lantern) calc(var(--pct, 0) * 3.6deg), transparent calc(var(--pct, 0) * 3.6deg));
    -webkit-mask-image: radial-gradient(closest-side, transparent 78%, black 81%, black 94%, transparent 98%);
    mask-image: radial-gradient(closest-side, transparent 78%, black 81%, black 94%, transparent 98%);
    filter: drop-shadow(0 0 8px var(--lantern));
  }

  /* First-visit fog: obscures the ROOM (art, ambient hotspots) between a
     visitor and a portal, never the portal beacons themselves (z-index 3 vs
     this layer's 2) — see setupPortalMist in app.js for when this is
     created at all (only a room's first-ever visit, and never under
     prefers-reduced-motion, which gets an always-lit room instead). */
  .portalMist {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background: #05060a;
  }
  .portal.unexplored { border-style: dashed; border-color: var(--mist); box-shadow: none; opacity: 0.75; }
  .portal.loading { opacity: 0.6; cursor: wait; animation: none; }
  .lore-object {
    position: absolute; width: 22px; height: 22px; border-radius: 50%;
    transform: translate(-50%, -50%); background: rgba(238,241,230,0.06);
    border: 1px solid rgba(238,241,230,0.2); color: var(--mist); font-size: 1rem;
    cursor: pointer; opacity: 0.55;
  }
  .lore-object:hover { opacity: 0.9; }
  .lore-object.discovered { border-color: var(--mist); opacity: 0.35; }
  .lore-object { animation: lore-bob 3.6s ease-in-out infinite; }
  @keyframes lore-bob {
    0%, 100% { transform: translate(-50%, -50%) translateY(0); }
    50% { transform: translate(-50%, -50%) translateY(-4px); }
  }
  .lore-object.investigating, .loot-hotspot.investigating {
    animation: investigate-pulse 0.55s ease-in-out infinite;
    border-color: var(--lantern) !important; opacity: 1 !important;
  }
  @keyframes investigate-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(232,178,77,0.5); }
    50% { box-shadow: 0 0 0 8px rgba(232,178,77,0); }
  }

  /* A patch worn clean through by scratching — click opens the ad-hoc
     scratch-investigate ceremony, same visual language as a portal (a real
     find) but paler and rougher-edged, since it's still an unknown. */
  .scratch-hotspot {
    position: absolute; width: 30px; height: 30px; border-radius: 50%;
    transform: translate(-50%, -50%); background: rgba(238,241,230,0.08);
    border: 2px dashed var(--mist); color: var(--mist); font-size: 1.1rem;
    cursor: pointer; opacity: 0.8;
    animation: scratch-glint 2.2s ease-in-out infinite;
  }
  .scratch-hotspot:hover { opacity: 1; border-color: var(--lantern); color: var(--lantern); }
  @keyframes scratch-glint {
    0%, 100% { box-shadow: 0 0 6px 2px rgba(238,241,230,0.15); }
    50% { box-shadow: 0 0 14px 5px rgba(238,241,230,0.3); }
  }

  /* A rare, unmarked find in rooms deep enough to signal a genuinely
     dedicated explorer — deliberately not styled like a normal gameplay
     hotspot (no gold/mist), so it reads as something else entirely. */
  .creator-rift-hotspot {
    position: absolute; width: 26px; height: 26px; border-radius: 50%;
    transform: translate(-50%, -50%); background: rgba(138, 111, 209, 0.12);
    border: 2px solid rgba(180, 150, 240, 0.7); color: #b496f0; font-size: 1.1rem;
    cursor: pointer; opacity: 0.85;
    animation: rift-shimmer 3.2s ease-in-out infinite;
  }
  .creator-rift-hotspot:hover { opacity: 1; box-shadow: 0 0 18px 6px rgba(138, 111, 209, 0.45); }
  @keyframes rift-shimmer {
    0%, 100% { box-shadow: 0 0 8px 2px rgba(138, 111, 209, 0.25); }
    50% { box-shadow: 0 0 16px 6px rgba(138, 111, 209, 0.45); }
  }

  .portal.unexplored.armed {
    border-style: solid; border-color: var(--lantern); opacity: 1;
    animation: portal-arming 1.1s ease-in-out infinite;
  }
  .portal.unexplored.ready {
    border-style: solid; border-color: var(--lantern); opacity: 1;
    box-shadow: 0 0 26px 10px rgba(232, 178, 77, 0.65);
    animation: portal-pulse 1s ease-in-out infinite;
  }
  .portal.unexplored.ready::after {
    content: "✦"; position: absolute; top: -8px; right: -8px;
    font-size: 0.75rem; background: var(--lantern); color: var(--ink);
    border-radius: 50%; width: 16px; height: 16px; line-height: 16px;
  }
  @keyframes portal-arming {
    0%, 100% { box-shadow: 0 0 10px 2px rgba(232, 178, 77, 0.3); }
    50% { box-shadow: 0 0 20px 6px rgba(232, 178, 77, 0.6); }
  }
  .portal.visited { animation: none; border-color: var(--mist); box-shadow: 0 0 10px 2px rgba(157,178,163,0.25); filter: saturate(0.75); }
  .portal.portal-origin { border-color: var(--mist); border-width: 3px; }
  .portal.portal-origin::after {
    content: "↩"; position: absolute; top: -8px; right: -8px;
    font-size: 0.7rem; background: var(--ink); border: 1px solid var(--mist);
    border-radius: 50%; width: 16px; height: 16px; line-height: 14px; color: var(--mist);
  }

  @keyframes portal-pulse {
    0%, 100% { box-shadow: 0 0 16px 4px rgba(232, 178, 77, 0.35); }
    50% { box-shadow: 0 0 22px 8px rgba(232, 178, 77, 0.55); }
  }
  @media (prefers-reduced-motion: reduce) {
    .portal, .lore-object, .omen-hotspot, .omen-hotspot::after, .loot-hotspot, .scratch-hotspot, .creator-rift-hotspot { animation: none; }
  }

  .close-drawer {
    position: absolute;
    top: 10px;
    right: 12px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: rgba(238, 241, 230, 0.06);
    border: 1px solid var(--panel-edge);
    color: var(--mist);
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
  }
  .close-drawer:hover { color: var(--lantern); border-color: var(--lantern); }

  .reopen-drawer {
    position: fixed;
    bottom: 20px;
    right: 16px;
    z-index: 6;
    width: 46px;
    height: 46px;
    font-size: 1.3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--panel);
    border: 1px solid var(--panel-edge);
    border-radius: 50%;
    cursor: pointer;
    box-shadow: 0 8px 20px -8px rgba(0, 0, 0, 0.6);
  }
  .reopen-drawer:hover { border-color: var(--lantern); transform: scale(1.06); }

  .info-btn {
    top: 14px;
    right: 16px;
    bottom: auto;
    width: 36px;
    height: 36px;
    font-size: 1rem;
  }

  #presence-layer {
    position: fixed;
    inset: 0;
    z-index: 4;
    pointer-events: none;
  }

  .presence-dot {
    --flare: 0;
    position: absolute;
    width: calc(12px + var(--flare) * 2px);
    height: calc(12px + var(--flare) * 2px);
    border-radius: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 0 0 calc(6px + var(--flare) * 4px) calc(1px + var(--flare) * 1px) currentColor;
    transition: left 0.12s linear, top 0.12s linear, width 0.3s ease, height 0.3s ease;
    pointer-events: auto;
    cursor: pointer;
  }

  .nudge-picker {
    position: fixed;
    z-index: 7;
    display: flex;
    gap: 4px;
    background: var(--panel);
    border: 1px solid var(--panel-edge);
    border-radius: 20px;
    padding: 5px;
    transform: translate(-50%, -120%);
    box-shadow: 0 10px 24px -10px rgba(0, 0, 0, 0.6);
  }
  .nudge-picker button {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: none;
    background: rgba(238, 241, 230, 0.06);
    font-size: 0.95rem;
    cursor: pointer;
  }
  .nudge-picker button:hover { background: rgba(232, 178, 77, 0.2); }

  .action-wheel {
    position: fixed; z-index: 19; width: 0; height: 0;
  }
  .action-wheel-center {
    position: absolute; left: 0; top: 0; width: 10px; height: 10px;
    transform: translate(-50%, -50%); border-radius: 50%;
    background: var(--lantern); box-shadow: 0 0 16px 6px rgba(232, 178, 77, 0.5);
  }
  .action-wheel-btn {
    position: absolute; left: 0; top: 0;
    width: 52px; height: 52px; margin: -26px 0 0 -26px;
    border-radius: 50%; border: 2px solid var(--panel-edge);
    background: var(--panel); color: var(--moon); font-size: 1.3rem;
    cursor: pointer; display: flex; align-items: center; justify-content: center;
    box-shadow: 0 8px 20px -8px rgba(0,0,0,0.6);
    transition: transform 0.12s ease, border-color 0.12s ease;
  }
  .action-wheel-btn:hover { border-color: var(--lantern); transform: scale(1.1); }
  .action-wheel-btn:disabled { opacity: 0.35; cursor: not-allowed; }
  .action-wheel-btn .wheel-label {
    position: absolute; bottom: -18px; left: 50%; transform: translateX(-50%);
    font-size: 0.62rem; color: var(--mist); white-space: nowrap;
  }

  .presence-dot .tag {
    position: absolute;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.62rem;
    white-space: nowrap;
    color: var(--moon);
    background: rgba(13, 31, 23, 0.75);
    padding: 1px 5px;
    border-radius: 4px;
  }

  .presence-dot.self { display: none; }

  .presence-dot.ghost {
    opacity: 0.6;
    border: 1px dashed rgba(238, 241, 230, 0.6);
    cursor: default;
    animation: ghost-drift 4s ease-in-out infinite;
  }
  @keyframes ghost-drift { 0%, 100% { opacity: 0.45; } 50% { opacity: 0.75; } }
  @media (prefers-reduced-motion: reduce) {
    .presence-dot.ghost { animation: none; opacity: 0.6; }
  }

  .chat-bubble {
    position: absolute;
    transform: translate(-50%, -130%);
    background: var(--moon);
    color: var(--ink);
    font-size: 0.72rem;
    padding: 5px 9px;
    border-radius: 8px;
    white-space: nowrap;
    animation: bubble-life 3.6s ease forwards;
  }

  @keyframes bubble-life {
    0% { opacity: 0; transform: translate(-50%, -110%); }
    10% { opacity: 1; transform: translate(-50%, -130%); }
    80% { opacity: 1; }
    100% { opacity: 0; transform: translate(-50%, -150%); }
  }

  .badge {
    position: fixed;
    top: 14px;
    right: 16px;
    z-index: 5;
    max-width: 42vw;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.72rem;
    font-family: inherit;
    color: var(--mist);
    background: rgba(22, 48, 31, 0.8);
    border: 1px solid var(--panel-edge);
    padding: 5px 10px;
    border-radius: 20px;
    cursor: pointer;
  }
  .badge:hover { border-color: var(--lantern); color: var(--lantern); }

  @media (max-width: 400px) {
    .badge { max-width: 34vw; font-size: 0.68rem; padding: 4px 8px; }
    #avatarBtn { width: 34px; height: 34px; }
  }

  #rosterPanel {
    position: fixed; top: 44px; right: 16px; z-index: 6;
    width: min(280px, 80vw); max-height: 60vh; overflow-y: auto;
    background: linear-gradient(rgba(13,31,23,0.7), rgba(13,31,23,0.82)), url("assets/ui-texture.png") center / cover;
    border: 1px solid var(--panel-edge); border-radius: 10px;
    box-shadow: 0 20px 50px -20px rgba(0,0,0,0.7);
    padding: 10px 12px 12px;
  }
  #rosterPanel .onboard-label { display: flex; justify-content: space-between; align-items: center; }
  #rosterCloseBtn {
    width: 22px; height: 22px; border-radius: 50%; font-size: 0.8rem; line-height: 1;
    background: rgba(238,241,230,0.06); border: 1px solid var(--panel-edge); color: var(--mist); cursor: pointer;
  }
  #rosterCloseBtn:hover { color: var(--lantern); border-color: var(--lantern); }
  #rosterList { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
  .roster-empty { color: var(--mist); font-size: 0.76rem; font-style: italic; padding: 6px 0; }
  .roster-row {
    display: flex; align-items: center; gap: 8px;
    background: rgba(238,241,230,0.03); border-radius: 7px; padding: 6px 8px;
  }
  .roster-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
  .roster-avatar { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; flex-shrink: 0; border: 1px solid var(--panel-edge); }
  .roster-info { flex: 1; min-width: 0; }
  .roster-name { font-size: 0.8rem; color: var(--moon); }
  .roster-name.self { color: var(--lantern); }
  .roster-room { font-size: 0.68rem; color: var(--mist); }
  .roster-go {
    font-family: inherit; font-size: 0.7rem; font-weight: 600;
    background: var(--lantern); color: var(--ink); border: none;
    border-radius: 6px; padding: 5px 9px; cursor: pointer; flex-shrink: 0;
  }
  .roster-go:hover { filter: brightness(1.08); }

  #selfBadge {
    top: 46px;
    color: var(--lantern);
    font-weight: 600;
  }

  .music-controls {
    position: fixed;
    top: 14px;
    left: 16px;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: 8px;
    /* Same stone as the surround, cut small so the coursing reads at this
       size — the bar is a block set into the wall, not a floating chip. */
    background:
      linear-gradient(rgba(10, 12, 10, 0.7), rgba(5, 7, 6, 0.84)),
      url("assets/ui/wall-plain.png") center / 260px;
    border: 1px solid rgba(232, 178, 77, 0.28);
    border-radius: 20px;
    padding: 5px 12px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
  }

  #musicToggle {
    font-family: inherit;
    font-size: 0.8rem;
    background: none;
    border: none;
    color: var(--moon);
    cursor: pointer;
    padding: 0;
    line-height: 1;
  }
  #musicToggle:disabled { opacity: 0.4; cursor: default; }
  #musicToggle:not(:disabled):hover { color: var(--lantern); }

  #musicVolume { width: 70px; accent-color: var(--lantern); cursor: pointer; }

  @media (max-width: 480px) {
    .music-controls { gap: 6px; padding: 5px 8px; }
    #musicVolume { width: 44px; }
  }

  #narratorToggle, #narratorSkip, #subtitleToggle {
    font-family: inherit; font-size: 0.8rem; background: none; border: none;
    color: var(--moon); cursor: pointer; padding: 0; line-height: 1;
  }
  /* Text rather than a glyph: this is the one control in the bar whose
     meaning changes with state ("Log in" vs "Keep this"), and an icon
     cannot say which of the two it currently is. */
  .bar-text-btn {
    font-family: inherit; font-size: 0.72rem; letter-spacing: 0.04em;
    background: none; border: none; cursor: pointer; padding: 0 0 0 9px;
    line-height: 1; color: var(--lantern); white-space: nowrap;
    border-left: 1px solid rgba(232, 178, 77, 0.25); margin-left: 2px;
  }
  .bar-text-btn:hover { color: #f2c266; }

  #narratorToggle.muted, #subtitleToggle.off { color: var(--mist); opacity: 0.6; }
  #narratorToggle:hover, #narratorSkip:hover, #subtitleToggle:hover { color: var(--lantern); }
  #narratorSkip:disabled { opacity: 0.3; cursor: default; }

  #subtitleBar {
    position: fixed; left: 50%; bottom: 74px; transform: translateX(-50%);
    z-index: 18; max-width: min(560px, 86vw);
    background: rgba(5,16,10,0.82); border: 1px solid var(--panel-edge); border-radius: 10px;
    padding: 8px 16px; color: var(--moon); font-size: 0.86rem; text-align: center;
    opacity: 0; pointer-events: none; transition: opacity 0.3s ease;
  }
  #subtitleBar.visible { opacity: 1; }

  main.plaque {
    position: relative;
    z-index: 2;
    width: min(620px, 92vw);
    margin: 70px auto 40px;
    background:
      linear-gradient(rgba(13, 31, 23, 0.4), rgba(13, 31, 23, 0.55)),
      url("assets/ui-texture.png") center / cover;
    border: 1px solid var(--panel-edge);
    border-radius: 10px;
    box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(238, 241, 230, 0.04);
    padding: 30px clamp(18px, 5vw, 40px) 28px;
    text-align: center;
    cursor: auto;
  }

  /* The inner content scrolls, not .plaque itself — otherwise the
     close button (absolutely positioned against .plaque) would scroll
     away with a tall drawer (bard console, music list, world tools),
     since it shares the same containing block as the scrolled content. */
  .plaque-scroll {
    max-height: calc(100vh - 160px);
    overflow-y: auto;
    padding-right: 4px;
  }

  h1 {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    font-variant: small-caps;
    letter-spacing: 0.06em;
    font-size: clamp(1.4rem, 3.6vw, 1.9rem);
    font-weight: 600;
    color: var(--lantern);
    text-shadow: 0 0 18px rgba(232, 178, 77, 0.35);
    margin: 0 0 4px;
  }

  p.sub { margin: 0 0 20px; color: var(--mist); font-size: 0.9rem; }

  /* ---- auth gate ---- */

  #authGate {
    position: fixed;
    inset: 0;
    z-index: 30;
    background: #0a0806;
    overflow: hidden;
  }

  /* World select still wears the old Stonecarver's Glade crop via
     ::before/::after — it's an unused path today (see primeWorldSelection()
     in auth.js), so it wasn't worth re-plumbing onto the gate's new
     backdrop. The gate itself now uses a real <img> instead of a
     pseudo-element background (see .gate-backdrop below) because the
     lantern hotspot has to be positioned against that image's actual
     on-screen rect, which only a real element exposes. */
  #worldSelect::before,
  #worldSelect::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
  }

  /* Sepia alone lands on olive, not gold — it desaturates the blues without
     putting warmth back. The overlay-blended amber wash is what actually
     supplies the gold; sepia is dialled back to just enough to kill the blue
     cast, and the saturation lift keeps the regrade from going flat. */
  #worldSelect::before {
    background:
      linear-gradient(rgba(255, 178, 74, 0.30), rgba(255, 146, 42, 0.24)),
      url("assets/rooms/right-door-room-fwd-1-room-fwd-1-room-fwd-0-room-fwd-1-room.png") center / cover no-repeat;
    background-blend-mode: overlay, normal;
    filter: sepia(0.38) saturate(1.5) hue-rotate(-24deg) brightness(0.76) contrast(1.09);
  }

  #worldSelect::after {
    background:
      radial-gradient(ellipse at 50% 38%, rgba(255, 194, 96, 0.34), rgba(255, 194, 96, 0) 58%),
      radial-gradient(ellipse at 50% 48%, rgba(8, 6, 4, 0) 38%, rgba(8, 6, 4, 0.82) 100%);
  }

  #worldSelect .world-select-box {
    position: relative;
    z-index: 1;
  }

  /* The gate's backdrop: assets/gate/stone.png (a leaning standing stone on
     a misty moor, moon upper-left, an oil lantern burning at its foot) —
     commissioned entrance art. No color-grade filter here: this asset was
     painted with the correct deep-blue-black night palette and a single
     warm light source already. A sepia/hue-rotate/brightness/contrast stack
     used to sit here, inherited from the old cold-blue Stonecarver's Glade
     backdrop it replaced — on THIS art it just double-warmed the whole
     frame into a brown-olive haze and killed the contrast that makes the
     lantern read as the only light in the scene. */
  .gate-backdrop {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
  }
  .gate-backdrop img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Biased down to the lantern's own row (LANTERN_FRAC.y in auth.js is
       0.761) instead of centered. object-fit: cover crops whichever axis
       overflows; on a wide/short viewport that's the vertical axis, and a
       centered crop pushes the lantern — already low in the source image —
       out beneath the visible area entirely, taking the one clickable thing
       on the screen with it. Anchoring the crop here instead keeps the
       lantern in frame at any realistic aspect ratio (losing the moon/sky
       off the top first). positionLanternBtn() in auth.js reads this back
       via getComputedStyle rather than assuming 50/50, so the two values
       can't drift apart. */
    object-position: 50% 76%;
  }
  .gate-backdrop-wash {
    position: absolute;
    inset: 0;
    /* Built for the old cold-blue backdrop, which needed real warmth added
       back after de-sepia-ing — this art doesn't; left at a bare fraction
       of its old strength just to unify the grain of the PNG with the
       wordmark's gold, not to retint the scene. */
    background: linear-gradient(rgba(255, 178, 74, 0.06), rgba(255, 146, 42, 0.05));
    mix-blend-mode: overlay;
  }

  /* Depth-layer parallax (see gate-parallax.js). Sits between the flat
     backdrop img and the wash/vignette above it, so the wash+vignette still
     grade the composited result rather than just the flat image. Each
     layer img MUST use the exact same object-fit/object-position as
     .gate-backdrop img above — they're RGBA slices of that same source
     photo, and positionLanternBtn() in auth.js computes the lantern hotspot
     purely from the flat img's box, so any mismatch here makes the layers
     visibly disagree with each other and/or with the hotspot's assumptions. */
  .gate-parallax-layers {
    position: absolute;
    inset: 0;
  }
  .gate-parallax-layer {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 76%;
    pointer-events: none;
    will-change: transform;
  }

  /* Unfiltered, painted on top of the graded backdrop rather than through
     it — same relationship the old ::after had to ::before. Warm highlight
     knocked down from the old cold-backdrop value (it was adding its own
     haze over the sky, on top of the filter/wash); the dark vignette is
     kept at full strength since it's what actually earns the wordmark's
     legibility against a busy sky. */
  .gate-vignette {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
      radial-gradient(ellipse at 50% 38%, rgba(255, 194, 96, 0.10), rgba(255, 194, 96, 0) 58%),
      radial-gradient(ellipse at 50% 48%, rgba(8, 6, 4, 0) 38%, rgba(8, 6, 4, 0.82) 100%);
  }

  /* The wordmark, lantern and login link are scene elements positioned
     directly over the backdrop, not a card floating on top of it. Engraved
     art, not live type — the glow and texture are baked into the PNG
     (it's RGBA, keyed to transparency around the letters), so this is just
     sizing/placement, no color or text-shadow to set. */
  .gate-wordmark {
    position: absolute;
    top: 6%;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1;
    width: clamp(220px, 42vw, 520px);
    height: auto;
    display: block;
  }

  /* Dragging any of the gate's images produces the browser's native drag
     ghost — a translucent copy of the wordmark trailing the cursor, which
     looks exactly like the logo has duplicated itself. These are scenery, not
     content: nothing should be draggable or selectable, and none of them
     should swallow pointer events that belong to the lantern button. The
     draggable="false" attributes in index.html cover browsers that ignore
     -webkit-user-drag. */
  .gate-wordmark,
  .gate-backdrop img,
  .gate-parallax-layers img {
    -webkit-user-drag: none;
    user-select: none;
    -webkit-user-select: none;
    pointer-events: none;
  }

  /* World select is the first screen a visitor sees and carried no branding at
     all — the wordmark only appeared once you reached the gate behind it. This
     sets it above that screen's own heading as a quiet eyebrow rather than a
     second competing title: a <p>, not an <h1>, so the screen keeps one real
     heading and screen readers aren't handed two. */
  .brand-mark {
    margin: 0 0 10px;
    font-size: 0.72rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--lantern);
    opacity: 0.62;
    text-align: center;
  }

  #authCredentials input {
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(232, 178, 77, 0.3);
    border-radius: 6px;
    color: var(--mist);
    padding: 8px 10px;
    font: inherit;
  }

  .auth-buttons {
    display: flex;
    gap: 8px;
  }

  .auth-buttons button {
    flex: 1;
  }

  .auth-error {
    color: #e08a8a;
    font-size: 0.85rem;
    min-height: 1.1em;
    margin: 0;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85);
  }

  /* Small and quiet by design — a side door, not a competing call to action.
     Reused as-is for the world-select back link and the (normally hidden)
     other-worlds link, all three being the same "minor exit from the main
     flow" affordance. */
  .auth-link-btn {
    font-family: inherit; font-size: 0.78rem; background: none; border: none;
    color: var(--mist); text-decoration: underline; cursor: pointer; padding: 6px 0;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85);
  }
  .auth-link-btn:hover { color: var(--lantern); }

  .auth-credentials {
    display: flex; flex-direction: column; gap: 10px;
  }

  /* ---- the lantern: the entrance itself ---- */

  /* No drawn widget — the painted lantern in .gate-backdrop's <img> IS the
     affordance. This button is invisible at rest; its size and position are
     set inline from JS (positionLanternBtn() in auth.js), computed from
     that image's actual on-screen rect, since object-fit: cover crops
     differently at every viewport size and aspect ratio. left/top are the
     circle's center — translate(-50%,-50%) does the rest. */
  .lantern-btn {
    position: fixed;
    transform: translate(-50%, -50%);
    border: none;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(232, 178, 77, 0.95), rgba(232, 178, 77, 0.45) 45%, rgba(232, 178, 77, 0) 72%);
    opacity: 0;
    cursor: pointer;
    z-index: 2;
    transition: opacity 0.2s ease;
  }
  /* Bloom on hover AND focus (not hover alone) — keyboard users can't
     hover, so focus-visible has to trigger the same swelling glow or the
     lantern is undiscoverable without a mouse. */
  .lantern-btn:hover,
  .lantern-btn:focus-visible {
    opacity: 1;
  }
  .lantern-btn:focus-visible {
    outline: 2px solid var(--lantern);
    outline-offset: 6px;
  }

  /* top/left here are just the pre-JS fallback (a beat before the backdrop
     image decodes, or if it never does) — positionLanternBtn() in auth.js
     overwrites both inline once it can measure the lantern button's actual
     on-screen rect, so this caption tracks the hotspot instead of sitting
     at a fixed guess that only lined up with the lantern at one aspect
     ratio. transform stays translateX(-50%) either way: the JS sets `left`
     to the hotspot's horizontal center, same as this default does. */
  .lantern-caption-group {
    position: absolute;
    top: 52%;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1;
    width: min(260px, 84vw);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    text-align: center;
  }

  .lantern-caption {
    margin: 0;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--moon);
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85);
  }

  .lantern-note {
    margin: 0;
    font-size: 0.74rem;
    color: var(--mist);
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85);
  }

  /* Hidden unless primeWorldSelection() in auth.js finds more than one
     world in /api/presence — see the comment on #authOtherWorldsBtn in
     index.html. */
  .gate-other-worlds {
    position: absolute;
    top: 14px;
    right: 16px;
    z-index: 1;
    max-width: 220px;
    text-align: right;
  }

  .gate-login-corner {
    position: absolute;
    left: 16px;
    bottom: 16px;
    z-index: 1;
    max-width: min(280px, 80vw);
  }

  /* Revealed by auth.js only while the gate wind loop is primed-but-muted,
     waiting on the gesture that unlocks audible playback (see
     unlockGateWind). Top-left — the one corner not already claimed by
     .gate-other-worlds (top-right) or .gate-login-corner (bottom-left), and
     clear of the lantern hotspot, which sits lower-right (see LANTERN_FRAC
     in auth.js). Low-opacity so it reads as ambient detail rather than a
     banner — the lantern is still the only thing here that has to compete
     for attention. */
  .gate-sound-hint {
    position: absolute;
    top: 16px;
    left: 16px;
    z-index: 1;
    max-width: 200px;
    margin: 0;
    font-size: 0.72rem;
    color: var(--lantern);
    opacity: 0.55;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85);
    pointer-events: none;
  }

  /* A spirit reopening #authGate after scratching a slab in the hall of
     alcoves (see setupHallOverlay in hall.js / openSpiritAuth in auth.js) —
     stripped of the backdrop scene entirely so the room, and the hall
     overlay sitting behind this one, both stay visible. Reuses #authGate/
     #authCredentials verbatim rather than a second gate/form. */
  #authGate.gate-in-place { background: rgba(5, 16, 10, 0.4); }
  #authGate.gate-in-place .gate-backdrop,
  #authGate.gate-in-place .gate-vignette,
  #authGate.gate-in-place #gateWordmark,
  #authGate.gate-in-place .lantern-btn,
  #authGate.gate-in-place .lantern-caption-group,
  #authGate.gate-in-place #authOtherWorldsBtn { display: none; }
  #authGate.gate-in-place .gate-login-corner {
    left: 50%;
    bottom: auto;
    top: 50%;
    transform: translate(-50%, -50%);
  }

  /* Floating over the backdrop with no wrapping panel, unlike the old
     .auth-box — needs its own background/border to stay legible. */
  .gate-login-corner .auth-credentials {
    margin-top: 8px;
    background:
      linear-gradient(rgba(13, 31, 23, 0.55), rgba(13, 31, 23, 0.7)),
      url("assets/ui-texture.png") center / cover;
    border: 1px solid rgba(232, 178, 77, 0.25);
    border-radius: 10px;
    padding: 12px;
  }

  /* ---- contextual verify-email prompt (shown/hidden on demand by app.js) ---- */
  .verify-email-prompt {
    position: fixed; bottom: 120px; left: 50%; transform: translateX(-50%);
    z-index: 25; display: flex; align-items: center; gap: 10px;
    max-width: min(420px, 92vw);
    background: rgba(232, 178, 77, 0.14); border: 1px solid var(--lantern);
    border-radius: 20px; padding: 8px 14px;
    color: var(--moon); font-size: 0.8rem; line-height: 1.4;
    box-shadow: 0 10px 24px -10px rgba(0, 0, 0, 0.6);
  }
  .verify-email-text { flex: 1; }
  .verify-email-resend {
    font-family: inherit; font-size: 0.78rem; font-weight: 600;
    background: none; border: none; color: var(--lantern); text-decoration: underline;
    cursor: pointer; padding: 0;
  }
  .verify-email-resend:hover { color: var(--moon); }
  .verify-email-resend:disabled { opacity: 0.6; cursor: default; text-decoration: none; }
  .verify-email-close {
    flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%; line-height: 1;
    font-size: 0.75rem; background: rgba(238,241,230,0.08); border: 1px solid var(--panel-edge);
    color: var(--mist); cursor: pointer;
  }
  .verify-email-close:hover { color: var(--lantern); border-color: var(--lantern); }

  @media (max-width: 480px) {
    .verify-email-prompt { bottom: 180px; }
  }

  /* ---- "keep this" offer (shown/hidden on demand by app.js) ---- */
  /* Same shape as .verify-email-prompt above — both are dismissible,
     non-modal, bottom-anchored contexts, and never shown in the same
     session (one's for a linked account's unverified email, this one's for
     a character with no account at all). */
  .keep-character-prompt {
    position: fixed; bottom: 120px; left: 50%; transform: translateX(-50%);
    z-index: 25; display: flex; align-items: center; gap: 10px;
    max-width: min(420px, 92vw);
    background: rgba(232, 178, 77, 0.14); border: 1px solid var(--lantern);
    border-radius: 20px; padding: 8px 14px;
    color: var(--moon); font-size: 0.8rem; line-height: 1.4;
    box-shadow: 0 10px 24px -10px rgba(0, 0, 0, 0.6);
  }
  .keep-character-text { flex: 1; }
  .keep-character-cta {
    flex-shrink: 0;
    font-family: inherit; font-size: 0.78rem; font-weight: 600;
    background: none; border: 1px solid var(--lantern); border-radius: 10px;
    color: var(--lantern); cursor: pointer; padding: 4px 10px;
  }
  .keep-character-cta:hover { background: rgba(232, 178, 77, 0.18); }
  .keep-character-close {
    flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%; line-height: 1;
    font-size: 0.75rem; background: rgba(238,241,230,0.08); border: 1px solid var(--panel-edge);
    color: var(--mist); cursor: pointer;
  }
  .keep-character-close:hover { color: var(--lantern); border-color: var(--lantern); }

  @media (max-width: 480px) {
    .keep-character-prompt { bottom: 180px; }
  }

  /* Re-findable after the banner above is dismissed, without ever popping
     the banner itself back up (see maybeOfferKeepCharacter in app.js).
     Top-left mirrors #infoBtn's top-right so it doesn't collide with
     anything else already living in a screen corner (avatarBtn/hallBtn
     sit top-center, reopenDrawerBtn bottom-right). */
  .keep-character-reopen {
    top: 14px;
    left: 16px;
    bottom: auto;
    right: auto;
    width: 36px;
    height: 36px;
    font-size: 1rem;
  }

  /* ---- world selection (fake-liveness surface) ---- */
  #worldSelect {
    position: fixed; inset: 0; z-index: 19;
    background: #0a0806;
    display: flex; align-items: center; justify-content: center; padding: 20px;
    overflow: hidden;
  }
  .world-select-box {
    width: min(560px, 94vw); max-height: 88vh; overflow-y: auto;
    background: linear-gradient(rgba(13, 31, 23, 0.4), rgba(13, 31, 23, 0.55)), url("assets/ui-texture.png") center / cover;
    border: 1px solid var(--panel-edge); border-radius: 12px;
    padding: 26px clamp(18px, 5vw, 32px) 24px;
    box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.7);
    text-align: center;
  }
  .world-select-total { color: var(--lantern); font-weight: 600; }
  .world-select-list {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px;
    margin-top: 16px; text-align: left;
  }
  @media (max-width: 500px) {
    .world-select-list { grid-template-columns: 1fr; }
  }
  .world-card {
    display: flex; flex-direction: column; gap: 8px; align-items: flex-start; text-align: left;
    font-family: inherit; background: rgba(238, 241, 230, 0.04); border: 1px solid var(--panel-edge);
    border-radius: 10px; padding: 14px 16px; cursor: pointer; color: var(--moon);
  }
  .world-card:hover { border-color: var(--lantern); background: rgba(232, 178, 77, 0.08); }
  .world-card-title { font-weight: 600; font-size: 0.95rem; }
  .world-card-online {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 0.78rem; color: var(--mist); font-variant-numeric: tabular-nums;
  }
  .world-card-online-dot {
    width: 8px; height: 8px; border-radius: 50%; background: #5fbf6e;
    box-shadow: 0 0 6px 1px rgba(95, 191, 110, 0.6);
    animation: ghost-drift 2.4s ease-in-out infinite;
  }
  .world-card-join { margin-top: 2px; font-size: 0.8rem; font-weight: 600; color: var(--lantern); }
  @media (prefers-reduced-motion: reduce) {
    .world-card-online-dot { animation: none; }
  }

  /* Escape hatch back to the gate — this screen has no other entry today
     (see .gate-other-worlds), so a visitor who opens it needs a way out. */
  .world-select-back { display: block; margin: 14px auto 0; }

  /* ---- onboarding modal ---- */

  #onboarding {
    position: fixed;
    inset: 0;
    z-index: 20;
    background: rgba(10, 20, 14, 0.88);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
  }

  .onboard-card {
    width: min(480px, 94vw);
    max-height: 90vh;
    overflow-y: auto;
    background:
      linear-gradient(rgba(13, 31, 23, 0.4), rgba(13, 31, 23, 0.55)),
      url("assets/ui-texture.png") center / cover;
    border: 1px solid var(--panel-edge);
    border-radius: 12px;
    padding: 26px clamp(18px, 5vw, 32px) 24px;
    box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.7);
  }

  .onboard-fullscreen {
    position: absolute; inset: 0;
    display: flex; flex-direction: row;
    background: #05100a;
  }
  .onboard-hero {
    position: relative; flex: 1 1 52%; min-width: 0;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
  }
  .onboard-hero-portrait {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; object-position: center 15%;
  }
  .onboard-hero-nav {
    position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
    width: 48px; height: 48px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: rgba(5,16,10,0.85); border: 2px solid var(--lantern);
    color: var(--lantern); font-size: 1.4rem; font-weight: 700; cursor: pointer;
    box-shadow: 0 4px 16px rgba(0,0,0,0.6), 0 0 14px rgba(232,178,77,0.35);
  }
  .onboard-hero-nav:hover { background: rgba(232,178,77,0.18); }
  .onboard-hero-nav.prev { left: 16px; }
  .onboard-hero-nav.next { right: 16px; }

  .onboard-hero-thumbs { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
  .onboard-hero-thumb {
    width: 44px; height: 44px; border-radius: 8px; object-fit: cover;
    object-position: top center; cursor: pointer; opacity: 0.55;
    border: 2px solid transparent; background: var(--ink);
  }
  .onboard-hero-thumb:hover { opacity: 0.85; }
  .onboard-hero-thumb.active { opacity: 1; border-color: var(--lantern); }
  .onboard-hero-info {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
    padding: 28px clamp(20px, 6%, 60px) 26px;
    background: linear-gradient(to top, rgba(5,16,10,0.95), rgba(5,16,10,0.7) 60%, transparent);
  }
  .onboard-hero-info h2 {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    color: var(--lantern); font-size: clamp(1.3rem, 2.6vw, 1.8rem); margin: 0 0 6px;
  }
  .onboard-hero-info p {
    color: var(--moon); font-size: 0.88rem; line-height: 1.5; max-width: 46em;
    margin: 0 0 16px;
  }
  .onboard-hero-actions { display: flex; gap: 10px; flex-wrap: wrap; }
  .onboard-hero-actions button {
    font-family: inherit; font-size: 0.84rem; font-weight: 600; border-radius: 8px;
    padding: 10px 18px; cursor: pointer;
  }
  .onboard-form {
    flex: 1 1 48%; min-width: 300px; max-width: 560px;
    height: 100%; overflow-y: auto;
    background:
      linear-gradient(rgba(13, 31, 23, 0.5), rgba(13, 31, 23, 0.65)),
      url("assets/ui-texture.png") center / cover;
    padding: clamp(24px, 5vw, 48px) clamp(24px, 4vw, 40px);
    box-sizing: border-box;
  }
  .onboard-form h1 {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    font-variant: small-caps; letter-spacing: 0.06em;
    font-size: clamp(1.4rem, 2.6vw, 1.9rem); color: var(--lantern);
    text-align: left; margin: 0 0 6px;
  }
  .onboard-form p.sub { text-align: left; color: var(--mist); font-size: 0.85rem; margin: 0 0 26px; }

  @media (max-width: 860px) {
    .onboard-fullscreen { flex-direction: column; overflow-y: auto; }
    .onboard-hero { flex: 0 0 46vh; }
    .onboard-form { flex: 1 1 auto; max-width: none; height: auto; }
  }

  .onboard-card h1 {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    font-variant: small-caps;
    letter-spacing: 0.06em;
    font-size: clamp(1.3rem, 3.4vw, 1.7rem);
    color: var(--lantern);
    text-align: center;
    margin: 0 0 6px;
  }

  .onboard-card p.sub {
    text-align: center;
    color: var(--mist);
    font-size: 0.85rem;
    margin: 0 0 20px;
  }

  .onboard-section { margin-bottom: 20px; }

  .onboard-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--mist);
    margin-bottom: 8px;
  }

  .name-input-row { display: flex; gap: 8px; }
  #nameInput, #hallNameInput {
    flex: 1; min-width: 0; font-family: inherit; font-size: 0.9rem;
    background: rgba(238, 241, 230, 0.05); color: var(--moon);
    border: 1px solid var(--panel-edge); border-radius: 8px;
    padding: 9px 12px; box-sizing: border-box;
  }
  #nameInput:focus, #hallNameInput:focus { outline: none; border-color: var(--lantern); }
  #suggestNameBtn, #hallSuggestNameBtn {
    flex-shrink: 0; width: 40px; font-size: 1.1rem;
    background: rgba(238, 241, 230, 0.05); color: var(--moon);
    border: 1px solid var(--panel-edge); border-radius: 8px; cursor: pointer;
  }
  #suggestNameBtn:hover, #hallSuggestNameBtn:hover { border-color: var(--lantern); }

  .color-option {
    font-family: inherit; cursor: pointer;
    width: 30px; height: 30px; border-radius: 50%; padding: 0;
    background: rgba(238, 241, 230, 0.05); border: 1px solid var(--panel-edge);
  }
  .color-options { display: flex; gap: 8px; align-items: center; }
  #customColorInput {
    width: 30px; height: 30px; padding: 0; border-radius: 50%;
    border: 1px solid var(--panel-edge); background: none; cursor: pointer;
  }
  #customColorInput::-webkit-color-swatch-wrapper { padding: 0; border-radius: 50%; }
  #customColorInput::-webkit-color-swatch { border: none; border-radius: 50%; }
  #customColorInput::-moz-color-swatch { border: none; border-radius: 50%; }
  .color-option.selected { box-shadow: 0 0 0 2px var(--panel), 0 0 0 4px var(--lantern); }

  /* Marks the native color-input swatch as "pick your own" so it doesn't
     read as just another preset swatch — same corner-badge visual language
     as .portal.unexplored.ready::after. */
  .custom-color-wrap { position: relative; display: inline-block; }
  .custom-color-wrap::after {
    content: "🎨"; position: absolute; bottom: -4px; right: -4px;
    font-size: 0.65rem; background: var(--panel); border: 1px solid var(--panel-edge);
    border-radius: 50%; width: 14px; height: 14px; line-height: 13px; text-align: center;
    pointer-events: none;
  }

  .class-options { display: flex; flex-direction: column; gap: 8px; }

  .class-option {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    text-align: left;
    font-family: inherit;
    background: rgba(238, 241, 230, 0.04);
    border: 1px solid var(--panel-edge);
    border-radius: 8px;
    padding: 10px 12px;
    cursor: pointer;
    color: var(--moon);
  }
  .class-option.selected { border-color: var(--lantern); background: rgba(232, 178, 77, 0.08); }
  .class-option .class-portrait {
    width: 52px; height: 52px; border-radius: 8px; object-fit: cover;
    object-position: top center; flex-shrink: 0; background: var(--ink);
  }
  .class-option .class-name { font-weight: 600; font-size: 0.88rem; }
  .class-option .class-blurb { font-size: 0.76rem; color: var(--mist); margin-top: 2px; }

  .companion-options {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 12px;
    max-height: 52vh; overflow-y: auto; padding: 4px;
  }
  .companion-option {
    display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; font-family: inherit;
    background: rgba(238, 241, 230, 0.04); border: 1px solid var(--panel-edge);
    border-radius: 10px; padding: 12px 10px; cursor: pointer; color: var(--moon);
  }
  .companion-option.selected { border-color: var(--lantern); background: rgba(232, 178, 77, 0.08); }
  .companion-option img, .companion-option video { width: 100%; aspect-ratio: 3/4; border-radius: 8px; object-fit: cover; background: var(--ink); }
  .companion-option .companion-name { font-weight: 600; font-size: 0.86rem; }

  .companion-detail {
    text-align: left; font-size: 0.84rem; color: var(--mist); line-height: 1.5;
    min-height: 3.6em; margin: 12px 0 18px;
  }
  .companion-detail strong { color: var(--moon); }
  .companion-option .companion-blurb { font-size: 0.74rem; color: var(--mist); margin-top: 2px; line-height: 1.35; }

  #joinBtn, #step1NextBtn {
    display: block;
    width: 100%;
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    background: var(--lantern);
    color: var(--ink);
    border: none;
    border-radius: 8px;
    padding: 11px;
    cursor: pointer;
  }

  .step2-actions { display: flex; gap: 10px; align-items: center; }
  .step2-actions #joinBtn { flex: 1; }
  .ghost-btn {
    font-family: inherit; font-size: 0.86rem; font-weight: 600;
    background: transparent; color: var(--mist); border: 1px solid var(--panel-edge);
    border-radius: 8px; padding: 11px 18px; cursor: pointer; flex-shrink: 0;
  }
  .ghost-btn:hover { border-color: var(--mist); color: var(--moon); }

  #onboardStep2 .sub a { color: var(--mist); font-size: 0.82rem; }
  #onboardStep2 .sub a:hover { color: var(--lantern); }

  /* ---- avatar / profile ---- */

  #avatarBtn {
    position: fixed;
    top: 14px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 6;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 2px solid var(--panel-edge);
    cursor: pointer;
    font-size: 1.1rem;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 6px 14px -6px rgba(0, 0, 0, 0.6);
  }
  #avatarBtn:hover { border-color: var(--lantern); }
  #avatarBtn { padding: 0; overflow: hidden; }
  .avatar-btn-img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }

  /* ---- spirit "hall of alcoves" affordance + character creator ----
     See public/hall.js for the mechanic (ported from
     public/proto/make-hall.html); this is its rendering only. One room:
     five arches (calling) and six kin-stones (kin), both switchable at
     once, a side panel for ability points + specialization, and a claim
     beacon — no separate ceremony screen, no screen change on selection. */

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

  /* Sits where #avatarBtn would once there's a face to show there. Shown/
     hidden by app.js's applySpiritModeUi/undoSpiritModeUi, alongside the
     rest of the spirit-mode UI swap. Dashed border reads as "not yet
     solid," rather than borrowing avatarBtn's solid ring. */
  #hallBtn {
    position: fixed;
    top: 14px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 6;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 2px dashed var(--panel-edge);
    background: rgba(238, 241, 230, 0.06);
    color: var(--moon);
    font-size: 1.05rem;
    cursor: pointer;
    box-shadow: 0 6px 14px -6px rgba(0, 0, 0, 0.6);
  }
  #hallBtn:hover { border-color: var(--lantern); }

  #hallOverlay {
    position: fixed;
    inset: 0;
    z-index: 22; /* above .world/HUD, below the in-game spirit-cursor canvas (z-index 4 is BELOW this — see the note on #hallStage cursor below) and the gate-fade overlay's 30 */
    background: #05060a;
    overflow: hidden;
  }

  /* The hall itself — arches and kin-stones are appended/laid out into this
     by hall.js. Shown at full color from the start (unlike the old
     unlit-until-commit arrival): kin and calling are chosen openly here,
     not discovered, so there's nothing to keep dark. */
  #hallStage {
    position: fixed;
    inset: 0;
    background: url("assets/ui/hall-alcoves.png") center / cover no-repeat;
  }
  #hallVignette {
    position: fixed;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: radial-gradient(120% 85% at 50% 45%, transparent 40%, rgba(0, 0, 0, 0.62) 100%);
  }

  .hallArchWrap {
    position: fixed;
    z-index: 2;
    pointer-events: auto;
    overflow: visible;
  }
  .hallArchWrap:focus { outline: none; } /* the glow itself stands in for a focus ring */
  .hallArchGlow {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    background: radial-gradient(65% 65% at 50% 28%, var(--acol, #e8b24d) 0%, transparent 72%);
    mix-blend-mode: screen;
  }
  .hallSigil {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    font-size: 1.9rem;
    color: rgba(190, 198, 182, 0.30);
    text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.6), 0 1px 0 rgba(255, 235, 200, 0.08);
    pointer-events: none;
    transition: color 300ms ease, text-shadow 300ms ease;
  }
  .hallArchLabel {
    position: absolute;
    left: 0; right: 0; bottom: 8%;
    text-align: center;
    font: 12px Georgia, "Iowan Old Style", serif;
    letter-spacing: 0.02em;
    color: rgba(238, 241, 230, 0.55);
    pointer-events: none;
    text-shadow: 0 2px 6px #000;
    transition: color 300ms ease;
  }
  .hallArchWrap.current .hallArchLabel { color: var(--lantern); }
  .hallArchWrap.blocked { filter: grayscale(0.7) brightness(0.55); }
  .hallArchWrap.blocked .hallArchLabel { color: rgba(140, 80, 80, 0.55); }
  .hallArchPaint { position: fixed; z-index: 3; pointer-events: none; overflow: visible; }

  /* Blocked-pairing reason — shared by arches and kin-stones. Shown near
     whichever beacon is blocked, without touching the current selection. */
  .hallReason {
    position: fixed;
    z-index: 9;
    max-width: 15rem;
    text-align: center;
    font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
    color: #e0b3ac;
    background: rgba(20, 8, 8, 0.85);
    border: 1px solid rgba(210, 120, 110, 0.4);
    border-radius: 5px;
    padding: 7px 9px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 220ms ease;
  }
  .hallReason.show { opacity: 1; }

  /* Commitment ring — sits proud of the beacon (not a child of it) as a
     seal closing around it. Core functional feedback (how much of the
     dwell is done), so it isn't suppressed for reduced motion, only left
     un-eased. Shared by arches, kin-stones and the claim beacon. */
  .hallRingWrap { position: fixed; z-index: 4; pointer-events: none; border-radius: 50%; opacity: 0; }
  .hallRing {
    width: 100%; height: 100%; border-radius: 50%;
    background: conic-gradient(from -90deg, var(--acol, #e8b24d) 0deg, var(--acol, #e8b24d) calc(var(--pct, 0) * 3.6deg), transparent calc(var(--pct, 0) * 3.6deg));
    -webkit-mask-image: radial-gradient(closest-side, transparent 75%, black 78%, black 91%, transparent 95%);
    mask-image: radial-gradient(closest-side, transparent 75%, black 78%, black 91%, transparent 95%);
    filter: drop-shadow(0 0 10px var(--acol, #e8b24d));
  }

  .hall-close {
    position: fixed;
    top: 14px;
    right: 16px;
    z-index: 10;
  }

  /* ---- HUD: current kin + calling, read aloud in prose ---- */
  #hallHud {
    position: fixed;
    top: 3vh; left: 0; right: 0;
    z-index: 8;
    text-align: center;
    pointer-events: none;
    padding: 0 1rem;
  }
  #hallHudName {
    margin: 0;
    font: 400 1.25rem Georgia, "Iowan Old Style", serif;
    color: var(--lantern);
    text-shadow: 0 2px 10px #000, 0 0 22px #000;
  }
  #hallHudBlurb { margin: 0.3rem auto 0; max-width: 30rem; font-size: 0.8rem; color: var(--moon); text-shadow: 0 2px 8px #000; }
  .hall-court { margin: 0.25rem 0 0; font-size: 0.7rem; color: var(--mist); font-style: italic; text-shadow: 0 2px 8px #000; }
  /* Legible-default note (see hall.js's defaultsUntouched) — visible until
     the player actually dwells/clicks an arch or kin-stone, adjusts a
     point, or picks a specialization, so a decisive player who claims
     immediately still saw that this shape was chosen for them. */
  .hall-default-note {
    margin: 0.4rem auto 0;
    max-width: 26rem;
    font-size: 0.72rem;
    color: rgba(255, 214, 150, 0.8);
    text-shadow: 0 2px 8px #000;
  }

  /* ---- the standing figure — live portrait, crossfades on change ---- */
  #hallFigureStage {
    position: fixed;
    left: 50%; bottom: 14vh;
    z-index: 4;
    width: min(28vw, 300px); height: 48vh;
    transform: translateX(-50%);
    pointer-events: none;
  }
  #hallFigureGlow {
    position: absolute;
    left: 50%; bottom: 6%; width: 140%; height: 44%;
    transform: translateX(-50%);
    border-radius: 50%;
    background: radial-gradient(ellipse at center, rgba(255, 205, 140, 0.28), transparent 72%);
    filter: blur(2px);
  }
  .hall-fig-layer {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: contain; object-position: center bottom;
    opacity: 0;
    transition: opacity 420ms ease;
  }
  .hall-fig-layer.shown { opacity: 1; }

  /* ---- kin row ---- */
  #hallKinRow {
    position: fixed;
    left: 0; right: 0; bottom: 3vh;
    z-index: 5;
    display: flex;
    justify-content: center;
    gap: 1.4vw;
    flex-wrap: nowrap;
    padding: 0 1vw;
  }
  .hallKinStone {
    position: relative;
    width: clamp(52px, 7.5vw, 84px);
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    cursor: pointer; pointer-events: auto;
    padding: 6px 4px 8px;
    border-radius: 8px 8px 4px 4px;
    background: linear-gradient(180deg, rgba(60, 54, 46, 0.55), rgba(28, 24, 20, 0.7));
    border: 1px solid rgba(232, 178, 77, 0.16);
    transition: border-color 220ms ease, filter 260ms ease;
  }
  .hallKinStone:nth-child(2n) { transform: translateY(-4px); }
  .hallKinStone.current { border-color: var(--acol, #e8b24d); box-shadow: 0 0 14px -2px var(--acol, #e8b24d); }
  .hallKinStone.blocked { filter: grayscale(0.75) brightness(0.5); }
  .hallKinStone svg { width: 60%; height: 26px; }
  .hallKinStone path { fill: none; stroke: var(--acol, #9db2a3); stroke-width: 6; stroke-linejoin: round; stroke-linecap: round; opacity: 0.85; }
  .hallKinName { font: 10px Georgia, serif; color: rgba(238, 241, 230, 0.75); text-align: center; line-height: 1.2; }
  .hallKinStone.current .hallKinName { color: var(--lantern); }
  .hallKinStone:focus { outline: none; }
  .hallKinStone:focus-visible { border-color: var(--lantern); }

  /* ---- side panel: ability points + specialization ---- */
  #hallSidePanel {
    position: fixed;
    right: 1.2rem; top: 50%;
    transform: translateY(-50%);
    z-index: 8;
    width: 232px;
    background: rgba(10, 9, 7, 0.74);
    border: 1px solid rgba(232, 178, 77, 0.22);
    border-radius: 8px;
    padding: 12px 14px;
  }
  #hallSidePanel h2 {
    font: 400 0.8rem Georgia, serif;
    color: var(--lantern);
    margin: 0 0 8px;
    letter-spacing: 0.02em;
  }
  #hallSidePanel h2:not(:first-child) { margin-top: 10px; }
  .hall-attr-row { display: flex; align-items: center; gap: 6px; margin: 6px 0; font: 11px ui-monospace, monospace; color: var(--moon); }
  .hall-attr-row .hall-attr-name { width: 42px; text-transform: uppercase; letter-spacing: 0.04em; font-size: 10px; opacity: 0.85; }
  .hall-attr-row button {
    width: 20px; height: 20px; border-radius: 3px;
    border: 1px solid rgba(232, 178, 77, 0.35);
    background: rgba(232, 178, 77, 0.08);
    color: var(--lantern);
    font: 12px monospace; cursor: pointer; line-height: 1;
  }
  .hall-attr-row button:disabled { opacity: 0.25; cursor: default; }
  .hall-pips { display: flex; gap: 3px; }
  .hall-pip { width: 9px; height: 9px; border-radius: 50%; background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.18); }
  .hall-pip.filled { background: var(--lantern); border-color: var(--lantern); }
  .hall-pip.capped { border-color: rgba(210, 110, 100, 0.5); }
  .hall-attr-total { margin-left: auto; opacity: 0.6; font-size: 10px; }
  #hallPoolNote { font-size: 10.5px; color: rgba(255, 214, 150, 0.7); margin: 8px 0 2px; }
  .hall-spec-note { font-size: 10px; color: var(--mist); margin: 0 0 6px; line-height: 1.4; }
  .hall-spec-card {
    margin-top: 6px; padding: 7px 8px; border-radius: 5px;
    border: 1px solid rgba(232, 178, 77, 0.2);
    background: rgba(232, 178, 77, 0.05);
    cursor: pointer; font-size: 11px; color: var(--moon);
  }
  .hall-spec-card b { color: var(--lantern); font-weight: 600; }
  .hall-spec-card.chosen { border-color: var(--lantern); background: rgba(232, 178, 77, 0.16); }
  #hallStatLine {
    margin-top: 10px; padding-top: 8px;
    border-top: 1px solid rgba(232, 178, 77, 0.16);
    font: 11px ui-monospace, monospace;
    color: rgba(255, 214, 150, 0.75);
    line-height: 1.7;
  }

  /* ---- claim ---- */
  #hallClaimArea {
    position: fixed;
    left: 1.2rem; top: 50%;
    transform: translateY(-50%);
    z-index: 8;
    width: 200px;
    display: flex; flex-direction: column; align-items: center; gap: 10px;
  }
  #hallClaimArea .name-input-row { width: 100%; }
  #hallClaimBeacon {
    position: relative;
    width: 104px; height: 104px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; pointer-events: auto;
    background: radial-gradient(closest-side, rgba(232, 178, 77, 0.14), transparent 78%);
    border: 1px solid rgba(232, 178, 77, 0.35);
  }
  #hallClaimBeacon:focus { outline: none; }
  #hallClaimBeacon.invalid { opacity: 0.45; cursor: default; }
  #hallClaimGlyph { font-size: 1.8rem; color: var(--lantern); text-shadow: 0 0 14px rgba(232, 178, 77, 0.7); }
  #hallClaimLabel { font: 11px Georgia, serif; color: rgba(238, 241, 230, 0.75); text-align: center; max-width: 190px; }
  #hallClaimLabel.hall-claim-rejected { color: #e0b3ac; }

  @media (max-width: 900px) {
    #hallSidePanel, #hallClaimArea {
      position: fixed; left: 0; right: 0; width: auto; top: auto; transform: none;
    }
    #hallClaimArea { bottom: 11vh; flex-direction: row; justify-content: center; gap: 14px; }
    #hallSidePanel { bottom: 19vh; margin: 0 auto; max-width: 320px; left: 0; right: 0; }
    #hallKinRow { bottom: 1vh; }
    #hallFigureStage { display: none; }
  }

  @media (prefers-reduced-motion: reduce) {
    .hall-fig-layer { transition: none !important; }
  }

  #profilePopover {
    position: fixed;
    top: 60px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 6;
    background: var(--panel);
    border: 1px solid var(--panel-edge);
    border-radius: 10px;
    padding: 12px 16px;
    font-size: 0.8rem;
    color: var(--moon);
    text-align: center;
    box-shadow: 0 12px 24px -10px rgba(0, 0, 0, 0.6);
    white-space: nowrap;
  }
  #profilePopover .profile-rank { color: var(--mist); font-size: 0.74rem; margin-top: 2px; }
  #profilePopover button {
    font-family: inherit; font-size: 0.74rem; margin-top: 8px;
    background: rgba(238,241,230,0.06); color: var(--moon); border: 1px solid var(--panel-edge);
    border-radius: 6px; padding: 6px 12px; cursor: pointer;
  }
  #profilePopover button:hover { border-color: var(--lantern); }
  #signOutBtn {
    display: block; width: 100%; margin-top: 14px !important; padding-top: 10px;
    border-top: 1px solid var(--panel-edge); border-left: none; border-right: none; border-bottom: none;
    border-radius: 0; background: none; color: var(--mist);
  }
  #signOutBtn:hover { color: var(--berry); border-top-color: var(--berry); }
  #companionSwitcherList { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; max-height: 160px; overflow-y: auto; }
  .companion-switch-option {
    display: flex; align-items: center; gap: 8px; text-align: left;
    white-space: nowrap;
  }
  .companion-switch-option img, .companion-switch-option video { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }

  .vote-row {
    display: flex; align-items: center; justify-content: center; gap: 4px;
    margin-top: 4px; font-size: 0.68rem; color: var(--mist);
  }

  .vote-btn {
    background: none; border: 1px solid var(--panel-edge); color: var(--mist);
    border-radius: 5px; padding: 1px 5px; cursor: pointer; font-size: 0.7rem; line-height: 1.4;
  }

  .vote-btn:hover { color: var(--lantern); border-color: var(--lantern); }
  .vote-btn.down:hover { color: var(--berry); border-color: var(--berry); }
  .vote-btn.up.voted { color: var(--lantern); border-color: var(--lantern); background: rgba(232,178,77,0.12); }
  .vote-btn.down.voted { color: var(--berry); border-color: var(--berry); background: rgba(193,63,63,0.12); }

  .score { min-width: 1.6em; font-variant-numeric: tabular-nums; }

  .class-reminder {
    text-align: center;
    color: var(--mist);
    font-size: 0.78rem;
    margin: 0 0 4px;
  }
  .class-reminder strong { color: var(--lantern); }

  section.jokes {
    margin-top: 8px;
    border-top: 1px solid var(--panel-edge);
    padding-top: 18px;
    text-align: left;
  }

  .jokes h2 {
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--mist); text-align: center; margin: 0 0 12px; font-weight: 600;
  }

  .joke-list { max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding-right: 4px; }

  section.music-section {
    margin-top: 8px; border-top: 1px solid var(--panel-edge); padding-top: 18px; text-align: left;
  }
  .music-section h2 {
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--mist); text-align: center; margin: 0 0 12px; font-weight: 600;
  }
  #trackNameInput, #trackPromptInput {
    width: 100%; font-family: inherit; font-size: 0.82rem;
    background: rgba(238,241,230,0.05); color: var(--moon);
    border: 1px solid var(--panel-edge); border-radius: 8px;
    padding: 8px 10px; box-sizing: border-box; margin-top: 8px; resize: vertical;
  }
  #trackNameInput:focus, #trackPromptInput:focus { outline: none; border-color: var(--lantern); }

  .joke-empty { color: var(--mist); font-size: 0.8rem; font-style: italic; text-align: center; padding: 10px 0; }

  .joke-item {
    display: flex; align-items: center; gap: 10px;
    background: rgba(238,241,230,0.03); border-radius: 7px; padding: 8px 10px;
  }

  .joke-body { flex: 1; min-width: 0; }
  .joke-text { font-size: 0.82rem; color: var(--moon); }
  .joke-author { font-size: 0.68rem; color: var(--mist); margin-top: 2px; }

  .room-nameplate {
    position: absolute; top: 14%; left: 50%; transform: translate(-50%, -10px);
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    font-size: clamp(1.1rem, 2.4vw, 1.8rem); letter-spacing: 0.04em;
    color: var(--moon); text-shadow: 0 2px 14px rgba(0,0,0,0.65), 0 0 30px rgba(232,178,77,0.25);
    opacity: 0; pointer-events: none; z-index: 5;
    transition: opacity 1.1s ease, transform 1.1s ease;
    text-align: center; padding: 0 12px;
  }
  .room-nameplate.show { opacity: 1; transform: translate(-50%, 0); }

  .click-burst-particle {
    position: fixed; width: 5px; height: 5px; border-radius: 50%;
    pointer-events: none; z-index: 9999; transform: translate(-50%, -50%);
    animation: click-burst 0.55s ease-out forwards;
  }
  @keyframes click-burst {
    0% { opacity: 0.85; transform: translate(-50%, -50%) scale(1); }
    100% {
      opacity: 0;
      transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.5);
    }
  }

  .attr-row { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 10px; }
  .attr-chip {
    font-size: 0.78rem; text-transform: capitalize; color: var(--moon);
    background: rgba(238,241,230,0.06); border: 1px solid var(--panel-edge);
    border-radius: 999px; padding: 3px 8px; display: inline-flex; align-items: center; gap: 4px;
  }
  .attr-spend-btn {
    font-family: inherit; font-size: 0.74rem; line-height: 1; cursor: pointer;
    width: 16px; height: 16px; border-radius: 50%; padding: 0;
    background: var(--lantern); color: var(--ink); border: none;
  }
  .perk-row { font-size: 0.78rem; color: var(--mist); margin: 2px 0; }
  .flourish-row { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 10px; }
  .flourish-chip {
    width: 30px; height: 30px; border-radius: 50%; border: 1px solid rgba(238,241,230,0.15);
    background: rgba(238,241,230,0.05); font-size: 15px; cursor: pointer; color: var(--moon);
  }
  .flourish-chip.active { border-color: var(--lantern); background: rgba(232,178,77,0.18); }
  .flourish-chip.locked { opacity: 0.28; cursor: not-allowed; filter: grayscale(1); }

  #regenVisionPrompt {
    background: rgba(232,178,77,0.1); border: 1px solid var(--lantern); border-radius: 8px;
    padding: 10px 12px; margin: 0 0 10px; font-size: 0.82rem; color: var(--moon);
  }
  #regenVisionCountdown { color: var(--lantern); font-variant-numeric: tabular-nums; font-weight: 600; }
  #confirmRegenBtn {
    display: block; margin-top: 8px; font-family: inherit; font-size: 0.78rem; font-weight: 600;
    background: var(--lantern); color: var(--ink); border: none; border-radius: 6px;
    padding: 7px 14px; cursor: pointer;
  }

  .vision-list { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; max-height: 140px; overflow-y: auto; }
  .vision-item {
    display: flex; align-items: baseline; gap: 8px;
    background: rgba(232,178,77,0.08); border-radius: 7px; padding: 6px 10px;
    font-size: 0.78rem; color: var(--moon);
  }
  .vision-item .vision-author { color: var(--lantern); font-size: 0.68rem; flex-shrink: 0; }
  .vision-empty { color: var(--mist); font-size: 0.78rem; font-style: italic; text-align: center; padding: 6px 0; }

  .word-builder { margin-top: 10px; }

  .draft {
    min-height: 30px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    padding: 6px 0 10px; border-bottom: 1px dashed var(--panel-edge); margin-bottom: 8px;
  }

  .draft-empty { color: var(--mist); font-size: 0.72rem; font-style: italic; }

  .draft-word {
    font-family: inherit; font-size: 0.76rem; background: var(--lantern); color: var(--ink);
    border: none; border-radius: 12px; padding: 4px 10px; cursor: pointer; font-weight: 600;
  }

  .word-bank { display: flex; flex-wrap: wrap; gap: 6px; }
  .word-progress { font-size: 0.68rem; color: var(--mist); margin-top: 6px; }

  .word-chip {
    font-family: inherit; font-size: 0.76rem; background: rgba(238,241,230,0.06); color: var(--moon);
    border: 1px solid var(--panel-edge); border-radius: 14px; padding: 5px 10px; cursor: pointer;
  }
  .word-chip:hover { border-color: var(--lantern); color: var(--lantern); }

  .builder-actions { display: flex; justify-content: center; gap: 8px; margin-top: 10px; }

  .builder-actions button {
    font-family: inherit; font-size: 0.78rem; font-weight: 600; border-radius: 6px;
    padding: 7px 14px; cursor: pointer; border: 1px solid var(--panel-edge);
  }
  #clearDraftBtn { background: none; color: var(--mist); }
  #postJokeBtn { background: var(--lantern); color: var(--ink); border: none; }

  .say-builder { margin-top: 10px; }
  #sayingInput {
    width: 100%; resize: none; font-family: inherit; font-size: 0.82rem;
    background: rgba(238,241,230,0.04); color: var(--moon); border: 1px solid var(--panel-edge);
    border-radius: 8px; padding: 8px 10px; box-sizing: border-box;
  }
  #sayingInput:focus { outline: none; border-color: var(--lantern); }
  .say-builder .builder-actions { justify-content: space-between; align-items: center; }
  .say-count { font-size: 0.68rem; color: var(--mist); font-variant-numeric: tabular-nums; }
  .say-count.low { color: var(--berry); }

  section.world-status {
    margin-top: 8px; border-top: 1px solid var(--panel-edge); padding-top: 18px; text-align: center;
  }

  section.world-status h2 {
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--mist); margin: 0 0 10px; font-weight: 600;
  }

  #epochLabel { color: var(--moon); font-size: 0.85rem; margin-bottom: 10px; }

  .world-status-actions { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; }

  .world-status-actions button {
    font-family: inherit; font-size: 0.78rem; font-weight: 600; border-radius: 20px;
    padding: 7px 14px; cursor: pointer; border: 1px solid var(--panel-edge);
    background: rgba(238,241,230,0.05); color: var(--moon);
  }
  .world-status-actions button:disabled { opacity: 0.5; cursor: default; }
  .world-status-actions button:not(:disabled):hover { border-color: var(--lantern); color: var(--lantern); }

  .archive-panel {
    margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;
  }

  .archive-thumb {
    width: 72px; height: 72px; border-radius: 8px; border: 1px solid var(--panel-edge);
    background-size: cover; background-position: center; position: relative; cursor: pointer; padding: 0;
  }
  .archive-thumb span {
    position: absolute; bottom: 2px; left: 2px; right: 2px; font-size: 0.6rem;
    background: rgba(13,31,23,0.75); color: var(--moon); border-radius: 3px; padding: 1px 3px;
  }

  .world-toast {
    position: fixed; bottom: 24px; left: 50%; transform: translate(-50%, 20px);
    background: var(--panel); border: 1px solid var(--panel-edge); color: var(--moon);
    font-size: 0.8rem; padding: 9px 16px; border-radius: 8px; z-index: 9;
    opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; pointer-events: none;
  }
  .world-toast.show { opacity: 1; transform: translate(-50%, 0); }

  #regenOverlay {
    position: fixed; inset: 0; z-index: 8; display: flex; align-items: center; justify-content: center;
    background: rgba(13, 31, 23, 0.75); color: var(--lantern); font-family: Georgia, serif;
    font-variant: small-caps; letter-spacing: 0.08em; font-size: 1.1rem;
  }

  #transitionOverlay {
    position: fixed;
    inset: 0;
    z-index: 15;
    background: #000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 1.1s ease;
  }
  #transitionOverlay.visible { opacity: 1; pointer-events: all; }

  #transitionQuote {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    font-style: italic;
    color: var(--lantern);
    font-size: clamp(1rem, 2.6vw, 1.3rem);
    text-align: center;
    max-width: 480px;
    line-height: 1.5;
    text-shadow: 0 0 18px rgba(232, 178, 77, 0.25);
  }

  #transitionOverlay.materializing { background: #030a06; }
  #transitionOverlay.materializing #transitionQuote { color: var(--lantern); position: relative; z-index: 1; }
  .materialize-texture {
    /* A rotating rectangle sweeps its corners outside its own bounding box at
       diagonal angles — inset:-25% left the viewport's corners uncovered at
       45°/135°, showing the flat background as a visible border. Fixed by
       sizing it as a square well past the viewport's worst-case (diagonal)
       extent, so full coverage holds at every rotation angle with margin
       to spare for the scale pulse too. */
    position: absolute; top: 50%; left: 50%; width: 200vmax; height: 200vmax;
    margin-top: -100vmax; margin-left: -100vmax;
    background-size: cover; background-position: center;
    opacity: 0; filter: saturate(1.3) brightness(0.9);
    animation: materialize-spin 14s linear infinite, materialize-fade-in 1.6s ease-out forwards;
  }
  @keyframes materialize-spin {
    0% { transform: rotate(0deg) scale(1); }
    50% { transform: rotate(180deg) scale(1.06); }
    100% { transform: rotate(360deg) scale(1); }
  }
  @keyframes materialize-fade-in {
    0% { opacity: 0; } 100% { opacity: 0.55; }
  }
  .materialize-credits { margin-top: 18px; display: flex; flex-direction: column; gap: 4px; align-items: center; position: relative; z-index: 1; }
  .materialize-credit { font-size: 0.78rem; color: var(--mist); text-align: center; max-width: 420px; }
  .materialize-credit b { color: var(--lantern); }

  @media (prefers-reduced-motion: reduce) {
    #transitionOverlay { transition: none; }
    .materialize-texture { animation: none; display: none; }
  }

  /* Crossfade / depth push-through stage — see runCrossfadeTransition and
     runPushAnimation in app.js. perspective is always on; it's inert for the
     flat crossfade case (no 3D transforms applied) and only does anything
     once push's translateZ/scale transforms are in play. */
  #vtStage {
    position: fixed; inset: 0; z-index: 14; overflow: hidden; background: #0a0806;
    perspective: 1200px;
  }
  #vtStage[hidden] { display: none; }
  .vt-scene { position: absolute; inset: 0; transform-style: preserve-3d; }
  .vt-layer-wrap {
    position: absolute; inset: 0; will-change: transform, opacity; transform-style: preserve-3d;
  }
  .vt-img {
    position: absolute; top: 50%; left: 50%; width: 120%; height: 120%;
    /* oversize + center so scale/translate during push never reveals the DOM edge */
    transform: translate(-50%, -50%);
    object-fit: cover; -webkit-user-drag: none; user-select: none; pointer-events: none;
  }
  @media (prefers-reduced-motion: reduce) {
    #vtStage { display: none !important; }
  }

  /* Gate fade-to-black — see beginGateFade in auth.js. Text styling mirrors
     #transitionQuote below (app.js's own in-game fade transition, once it's
     loaded) so the two read as the same visual language even though this
     one runs before app.js exists. */
  #gateFadeOverlay {
    position: fixed; inset: 0; z-index: 30; overflow: hidden; background: #0a0806;
    display: flex; align-items: center; justify-content: center; padding: 40px;
    /* The overlay is the topmost element during the crossing, so its cursor
       wins. Without this the pointer picks up .world's `cursor: crosshair`
       the instant the gate hides behind it. */
    cursor: none;
  }
  #gateFadeOverlay[hidden] { display: none; }
  #gateFadeText {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    font-style: italic;
    color: var(--lantern);
    font-size: clamp(1rem, 2.6vw, 1.3rem);
    text-align: center;
    max-width: 480px;
    line-height: 1.5;
    text-shadow: 0 0 18px rgba(232, 178, 77, 0.25);
  }

  #ceremonyOverlay, #contributeOverlay {
    position: fixed; inset: 0; z-index: 24;
    background: rgba(2, 8, 5, 0.88);
    display: flex; align-items: center; justify-content: center;
  }
  .ceremony-box {
    width: min(420px, 92vw);
    background: linear-gradient(rgba(13,31,23,0.85), rgba(13,31,23,0.94)), url("assets/ui-texture.png") center / cover;
    border: 1px solid var(--panel-edge); border-radius: 14px;
    box-shadow: 0 40px 90px -30px rgba(0,0,0,0.8);
    padding: 28px 26px; text-align: center;
    max-height: 88vh; overflow-y: auto;
  }
  .ceremony-box-wide { width: min(560px, 94vw); }
  .ceremony-title {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    color: var(--lantern); font-size: 1.15rem; margin-bottom: 6px;
  }
  .ceremony-context {
    background: rgba(238,241,230,0.05); border: 1px solid var(--panel-edge); border-radius: 8px;
    padding: 10px 12px; margin-bottom: 14px; text-align: left;
  }
  .ceremony-context .context-trigger { color: var(--moon); font-size: 0.86rem; }
  .ceremony-context .context-lore { color: var(--mist); font-size: 0.78rem; font-style: italic; margin-top: 4px; }
  .ceremony-countdown { color: var(--mist); font-size: 0.8rem; margin-bottom: 16px; }
  .ceremony-progress { color: var(--mist); font-size: 0.8rem; margin-bottom: 16px; }
  .ceremony-prompt p { color: var(--moon); font-size: 0.92rem; margin: 0 0 12px; }
  .ceremony-suggest-status {
    display: inline-flex; align-items: center; gap: 8px;
    color: var(--mist); font-size: 0.8rem; font-style: italic;
  }
  .ceremony-suggest-status .ghost-btn { font-size: 0.76rem; font-style: normal; }
  .conjure-spinner {
    width: 12px; height: 12px; border-radius: 50%;
    border: 2px solid rgba(232,178,77,0.3); border-top-color: var(--lantern);
    animation: conjure-spin 0.8s linear infinite;
  }
  @keyframes conjure-spin { to { transform: rotate(360deg); } }
  @media (prefers-reduced-motion: reduce) {
    .conjure-spinner { animation: none; }
  }
  #ceremonyInput, #contributeInput {
    width: 100%; font-family: inherit; font-size: 0.9rem; line-height: 1.4; resize: vertical;
    background: rgba(238,241,230,0.05); color: var(--moon);
    border: 1px solid var(--panel-edge); border-radius: 8px; padding: 10px 12px; box-sizing: border-box;
    margin-bottom: 10px;
  }
  #ceremonyInput:focus, #contributeInput:focus { outline: none; border-color: var(--lantern); }
  .transition-picker {
    display: flex; align-items: center; flex-wrap: wrap; gap: 6px; justify-content: center;
    margin: 0 0 10px;
  }
  .transition-picker-label { color: var(--mist); font-size: 0.76rem; margin-right: 2px; }
  .transition-picker-btn {
    font-family: inherit; font-size: 0.78rem; font-weight: 600;
    background: transparent; color: var(--mist); border: 1px solid var(--panel-edge);
    border-radius: 14px; padding: 5px 12px; cursor: pointer;
  }
  .transition-picker-btn:hover { border-color: var(--mist); color: var(--moon); }
  .transition-picker-btn.selected { border-color: var(--lantern); color: var(--lantern); }
  .ceremony-button-row { display: flex; gap: 8px; justify-content: center; }
  #ceremonySubmitBtn, #contributeSubmitBtn {
    font-family: inherit; font-size: 0.82rem; font-weight: 600; white-space: nowrap;
    background: var(--lantern); color: var(--ink); border: none; border-radius: 8px; padding: 9px 14px; cursor: pointer;
  }
  #ceremonySkipBtn { font-size: 0.8rem; }
  .ceremony-waiting { color: var(--mist); font-size: 0.86rem; font-style: italic; }
  .ceremony-drafts { margin-top: 16px; display: flex; flex-direction: column; gap: 8px; text-align: left; }
  #ceremonyCancelBtn { margin-top: 16px; font-size: 0.76rem; color: var(--berry); }
  .ceremony-draft {
    background: rgba(238,241,230,0.04); border: 1px solid var(--panel-edge); border-radius: 8px;
    padding: 8px 10px; font-size: 0.82rem; color: var(--moon);
  }
  .ceremony-draft b { color: var(--lantern); }
  .ceremony-draft .draft-text { color: var(--mist); font-style: italic; }

  #lightbox {
    position: fixed; inset: 0; z-index: 10; background: rgba(10, 20, 14, 0.92);
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 20px;
  }
  #lightboxBody { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; max-width: 90vw; }
  #lightboxBody figure { margin: 0; width: min(280px, 40vw); }
  #lightboxBody img { width: 100%; border-radius: 8px; display: block; }
  #lightboxBody figcaption { font-size: 0.72rem; color: var(--mist); margin-top: 6px; }
  #lightboxClose {
    font-family: inherit; font-size: 0.8rem; background: var(--lantern); color: var(--ink);
    border: none; border-radius: 6px; padding: 8px 16px; cursor: pointer;
  }

  #characterDialog {
    position: fixed; inset: 0; z-index: 21; background: #05100a;
    display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  }
  #characterPortrait {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: contain; object-position: center 30%;
    background: #05100a;
    -webkit-mask-image: linear-gradient(to bottom, black 55%, transparent 92%);
    mask-image: linear-gradient(to bottom, black 55%, transparent 92%);
  }
  #characterDialogBody {
    position: relative; z-index: 1;
    width: min(620px, 94vw); margin: 0 0 90px;
    max-height: 60vh; overflow-y: auto;
    background: linear-gradient(rgba(13,31,23,0.55), rgba(13,31,23,0.85)), url("assets/ui-texture.png") center / cover;
    border: 1px solid var(--panel-edge); border-radius: 14px;
    box-shadow: 0 30px 60px -20px rgba(0,0,0,0.7);
    padding: 24px 28px; text-align: left;
  }
  #characterDialogBody h2 {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    color: var(--lantern); font-size: 1.3rem; margin: 0 0 4px;
  }
  .character-court { font-size: 0.7rem; color: var(--mist); font-weight: 400; text-transform: capitalize; }
  .character-line { color: var(--moon); font-size: 0.95rem; line-height: 1.5; margin: 12px 0 18px; min-height: 2.6em; }
  .character-options { display: flex; flex-direction: column; gap: 8px; }
  .character-options button {
    font-family: inherit; font-size: 0.85rem; text-align: left;
    background: rgba(238,241,230,0.05); color: var(--moon); border: 1px solid var(--panel-edge);
    border-radius: 8px; padding: 10px 14px; cursor: pointer;
  }
  .character-options button:hover { border-color: var(--lantern); }
  #characterDialogClose {
    position: absolute; top: 18px; right: 18px; z-index: 2;
    width: 34px; height: 34px; border-radius: 50%; line-height: 1;
    font-family: inherit; font-size: 1rem;
    background: rgba(13,31,23,0.7); color: var(--mist); border: 1px solid var(--panel-edge);
    cursor: pointer;
  }
  #characterDialogClose:hover { color: var(--moon); border-color: var(--lantern); }

  .character-hotspot {
    position: absolute; width: 36px; height: 36px; border-radius: 50%;
    transform: translate(-50%, -50%); background: rgba(13, 31, 23, 0.6);
    border: 2px solid var(--lantern); color: var(--moon); font-size: 1rem;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; box-shadow: 0 0 14px 4px rgba(232, 178, 77, 0.4);
    animation: portal-pulse 2.4s ease-in-out infinite;
  }
  .character-hotspot.discovered { animation: none; box-shadow: 0 0 8px 2px rgba(232, 178, 77, 0.2); opacity: 0.75; }

  .loot-hotspot {
    position: absolute; width: 34px; height: 34px; border-radius: 50%;
    transform: translate(-50%, -50%); background: rgba(13, 31, 23, 0.6);
    border: 2px solid var(--rarity-color, #6bc4e8); color: var(--moon); font-size: 1rem;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; box-shadow: 0 0 12px 3px var(--rarity-glow, rgba(107, 196, 232, 0.4));
    animation: loot-glow 2s ease-in-out infinite;
  }
  @keyframes loot-glow {
    0%, 100% { box-shadow: 0 0 12px 3px var(--rarity-glow, rgba(107, 196, 232, 0.4)); }
    50% { box-shadow: 0 0 20px 7px var(--rarity-glow, rgba(107, 196, 232, 0.6)); }
  }

  .mob-hotspot {
    position: absolute; width: 40px; height: 40px; border-radius: 50%;
    transform: translate(-50%, -50%); background: rgba(13, 31, 23, 0.6);
    border: 2px solid var(--berry); color: var(--moon); font-size: 1.1rem;
    cursor: pointer; box-shadow: 0 0 14px 4px rgba(193, 63, 63, 0.45);
    animation: portal-pulse 1.6s ease-in-out infinite;
  }
  .mob-hotspot.boss { width: 50px; height: 50px; font-size: 1.4rem; box-shadow: 0 0 22px 6px rgba(193, 63, 63, 0.6); }

  .omen-hotspot {
    position: absolute; width: 30px; height: 30px; border-radius: 50%;
    transform: translate(-50%, -50%); background: rgba(13, 31, 23, 0.55);
    border: 1px dashed var(--mist); color: var(--moon); font-size: 0.9rem;
    cursor: pointer; opacity: 0.7;
  }
  .omen-hotspot:hover { opacity: 1; border-style: solid; border-color: var(--lantern); }
  .omen-hotspot.discovered { opacity: 0.35; animation: none; }
  .omen-hotspot { animation: omen-shimmer 2.8s ease-in-out infinite; }
  @keyframes omen-shimmer {
    0%, 100% { box-shadow: 0 0 6px 1px rgba(232, 178, 77, 0.25); }
    50% { box-shadow: 0 0 14px 5px rgba(232, 178, 77, 0.5); }
  }
  .omen-hotspot::after {
    content: ""; position: absolute; top: -6px; right: -4px; width: 4px; height: 4px;
    border-radius: 50%; background: var(--lantern); pointer-events: none;
    animation: omen-mote 2.8s ease-in-out infinite;
  }
  @keyframes omen-mote {
    0% { opacity: 0; transform: translateY(0) scale(0.6); }
    40% { opacity: 0.9; }
    100% { opacity: 0; transform: translateY(-16px) scale(1.1); }
  }

  .profile-rank.quest { color: var(--lantern); }

  /* ---- HUD bar: bottom-left icon row, unified + responsive ---- */
  .hud-bar-bottom-left {
    position: fixed;
    left: 16px;
    right: 16px;
    bottom: 20px;
    z-index: 16;
    display: flex;
    flex-wrap: wrap-reverse;
    align-items: center;
    gap: 10px;
    pointer-events: none;
  }
  .hud-bar-bottom-left > * { pointer-events: auto; }

  #debugToggle {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(13, 31, 23, 0.7);
    border: 2px solid var(--panel-edge);
    color: var(--moon);
    font-size: 1rem;
    cursor: pointer;
    flex-shrink: 0;
  }
  #debugToggle:hover { border-color: var(--lantern); }

  #flagImageBtn {
    width: 40px; height: 40px; border-radius: 50%;
    background: rgba(13, 31, 23, 0.7); border: 2px solid var(--panel-edge);
    color: var(--mist); font-size: 0.95rem; cursor: pointer; opacity: 0.6;
    flex-shrink: 0;
  }
  #flagImageBtn:hover { border-color: var(--berry); color: var(--berry); opacity: 1; }

  #flagConfirmRow { display: flex; gap: 6px; flex-shrink: 0; }

  .resource-orb {
    width: 56px; height: 56px; border-radius: 50%;
    border: 2px solid var(--lantern); background: rgba(13, 31, 23, 0.75);
    overflow: hidden; box-shadow: 0 0 14px rgba(232, 178, 77, 0.35);
    position: relative; flex-shrink: 0;
  }
  .resource-fill {
    position: absolute; bottom: 0; left: 0; width: 100%; height: 0%;
    background: linear-gradient(180deg, #ffcf6b, var(--lantern));
    transition: height 0.5s ease;
  }
  .resource-label {
    position: absolute; inset: 0; z-index: 1;
    display: flex; align-items: center; justify-content: center;
    font-size: 0.72rem; font-weight: 700; color: var(--moon);
    text-shadow: 0 1px 3px rgba(0,0,0,0.8);
  }
  .resource-orb.empty { border-color: var(--berry); }
  .resource-orb.empty .resource-fill { background: linear-gradient(180deg, #7a2a2a, var(--berry)); }

  #hintBtn {
    position: relative;
    width: 40px; height: 40px; border-radius: 50%;
    background: rgba(13, 31, 23, 0.75); border: 2px solid var(--lantern);
    color: var(--moon); font-size: 1rem; cursor: pointer;
    box-shadow: 0 0 14px rgba(232, 178, 77, 0.35);
    flex-shrink: 0;
  }
  #hintBtn:disabled { border-color: var(--panel-edge); box-shadow: none; opacity: 0.45; cursor: not-allowed; }
  #hintCount {
    position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px;
    border-radius: 50%; background: var(--lantern); color: var(--ink);
    font-size: 0.62rem; font-weight: 700; line-height: 16px; padding: 0 3px;
  }

  #inventoryBtn {
    width: 40px; height: 40px; border-radius: 50%;
    background: rgba(13, 31, 23, 0.75); border: 2px solid var(--panel-edge);
    color: var(--moon); font-size: 1.1rem; cursor: pointer;
    flex-shrink: 0;
  }
  #inventoryBtn:hover { border-color: var(--lantern); }

  #mapBtn {
    width: 40px; height: 40px; border-radius: 50%;
    background: rgba(13, 31, 23, 0.75); border: 2px solid var(--panel-edge);
    color: var(--moon); font-size: 1.1rem; cursor: pointer;
    flex-shrink: 0;
  }
  #mapBtn:hover { border-color: var(--lantern); }

  .potion-belt { display: flex; gap: 6px; flex-shrink: 0; }

  #bugReportBtn {
    width: 40px; height: 40px; border-radius: 50%;
    background: rgba(13, 31, 23, 0.75); border: 2px solid var(--panel-edge);
    color: var(--moon); font-size: 1.1rem; cursor: pointer;
    flex-shrink: 0;
  }
  #bugReportBtn:hover { border-color: var(--berry); color: var(--berry); }

  @media (max-width: 640px) {
    .hud-bar-bottom-left { gap: 9px; }
    .hud-bar-bottom-left #debugToggle,
    .hud-bar-bottom-left #flagImageBtn,
    .hud-bar-bottom-left #hintBtn,
    .hud-bar-bottom-left #inventoryBtn,
    .hud-bar-bottom-left #mapBtn,
    .hud-bar-bottom-left #bugReportBtn {
      width: 42px; height: 42px;
    }
    .hud-bar-bottom-left .potion-slot { width: 42px; height: 42px; }
  }

  @media (max-width: 480px) {
    .hud-bar-bottom-left { gap: 8px; }
    .hud-bar-bottom-left #debugToggle,
    .hud-bar-bottom-left #flagImageBtn,
    .hud-bar-bottom-left #hintBtn,
    .hud-bar-bottom-left #inventoryBtn,
    .hud-bar-bottom-left #mapBtn,
    .hud-bar-bottom-left #bugReportBtn {
      width: 44px; height: 44px;
    }
    .hud-bar-bottom-left .potion-slot { width: 44px; height: 44px; }
  }

  #bugReportOverlay { position: fixed; inset: 0; z-index: 26; background: rgba(2, 8, 5, 0.88); display: flex; align-items: center; justify-content: center; }

  #creatorRiftOverlay { position: fixed; inset: 0; z-index: 27; background: rgba(2, 8, 5, 0.88); display: flex; align-items: center; justify-content: center; }
  #creatorRiftOverlay .rift-intent-option {
    display: block; text-align: left; margin: 8px 0; color: var(--mist); font-size: 0.9rem;
  }
  #creatorRiftOverlay .rift-intent-option input { margin-right: 8px; }
  #creatorRiftOverlay textarea {
    width: 100%; margin-top: 10px; background: rgba(0,0,0,0.35); border: 1px solid var(--panel-edge);
    border-radius: 8px; color: var(--moon); padding: 8px 10px; font: inherit; resize: vertical;
  }
  .bug-report-state { color: var(--mist); font-size: 0.78rem; text-align: left; margin: 0 0 12px; line-height: 1.5; }
  .bug-report-state b { color: var(--moon); }
  .potion-slot {
    position: relative; width: 40px; height: 40px; border-radius: 8px;
    background: rgba(13, 31, 23, 0.75); border: 2px solid var(--panel-edge);
    color: var(--moon); font-size: 1.05rem; cursor: pointer;
  }
  .potion-slot:hover:not(:disabled) { border-color: var(--lantern); }
  .potion-slot:disabled { opacity: 0.35; cursor: not-allowed; }
  .potion-slot .potion-count {
    position: absolute; bottom: -6px; right: -6px; min-width: 16px; height: 16px;
    border-radius: 50%; background: var(--lantern); color: var(--ink);
    font-size: 0.6rem; font-weight: 700; line-height: 16px; padding: 0 3px;
  }

  .potion-hotspot {
    position: absolute; width: 30px; height: 30px; border-radius: 50%;
    transform: translate(-50%, -50%); background: rgba(232,178,77,0.15);
    border: 1px solid var(--lantern); color: var(--lantern); font-size: 0.9rem;
    cursor: pointer; display: flex; align-items: center; justify-content: center;
    animation: potion-glow 1.4s ease-in-out infinite;
  }
  @keyframes potion-glow {
    0%, 100% { box-shadow: 0 0 4px 0 rgba(232,178,77,0.4); }
    50% { box-shadow: 0 0 10px 4px rgba(232,178,77,0.25); }
  }

  #mapPanel {
    position: fixed; inset: 0; z-index: 27;
    background: rgba(2, 8, 5, 0.85);
    display: flex; align-items: center; justify-content: center;
  }
  #mapPanelBox {
    width: min(900px, 94vw); max-height: 86vh; overflow: auto;
    background: linear-gradient(rgba(30,20,8,0.4), rgba(20,13,5,0.55)), url("assets/ui/map-bg.png") center / cover;
    border: 1px solid var(--panel-edge); border-radius: 14px;
    box-shadow: 0 40px 100px -20px rgba(0,0,0,0.85);
    padding: 26px 44px 40px;
  }
  #mapPanel .onboard-label { display: flex; justify-content: space-between; align-items: center; color: var(--ink); text-shadow: none; }
  #mapPanel .onboard-label b { font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif; font-size: 1.2rem; }

  #minimapWidget {
    position: fixed; top: 14px; right: 160px; z-index: 5;
    width: 130px; height: 100px;
    background: linear-gradient(rgba(30,20,8,0.55), rgba(20,13,5,0.7)), url("assets/ui/map-bg.png") center / cover;
    border: 1px solid var(--panel-edge); border-radius: 8px;
    box-shadow: 0 10px 24px -10px rgba(0,0,0,0.6);
    overflow: hidden; cursor: pointer;
  }
  #minimapWidget svg { width: 100%; height: 100%; }
  #minimapWidget .map-node-label { display: none; }
  #mapCloseBtn {
    width: 22px; height: 22px; border-radius: 50%; font-size: 0.8rem; line-height: 1;
    background: rgba(238,241,230,0.06); border: 1px solid var(--panel-edge); color: var(--mist); cursor: pointer;
  }
  #mapCloseBtn:hover { color: var(--lantern); border-color: var(--lantern); }

  /* Below this width the minimap sits under the centered avatar button —
     drop it; #mapBtn in the HUD bar still opens the full #mapPanel. */
  @media (max-width: 640px) {
    #minimapWidget { display: none; }
  }

  @media (max-width: 480px) {
    #mapPanelBox { padding: 18px 16px 24px; }
  }

  .map-graph { display: block; margin-top: 8px; }
  .map-edge { stroke: var(--panel-edge); stroke-width: 2; }
  .map-node-bg { fill: rgba(13,31,23,0.85); }
  .map-node-ring { fill: none; stroke: var(--panel-edge); stroke-width: 2; }
  .map-node { cursor: pointer; }
  .map-node:hover .map-node-ring { stroke: var(--lantern); }
  .map-node.current .map-node-ring { stroke: var(--lantern); stroke-width: 3; cursor: default; }
  .map-node image { pointer-events: none; }
  .map-node-badge {
    text-anchor: middle; font-size: 13px; pointer-events: none;
    paint-order: stroke; stroke: var(--ink); stroke-width: 3px;
  }
  .map-node-label {
    text-anchor: middle; font-size: 10px; fill: var(--moon); pointer-events: none;
    font-family: system-ui, sans-serif;
  }

  #inventoryModal {
    position: fixed; inset: 0; z-index: 30;
    background: rgba(2, 8, 5, 0.82);
    display: flex; align-items: center; justify-content: center;
  }
  .inventory-fullscreen {
    position: relative; width: min(1100px, 94vw); height: min(720px, 88vh);
    display: flex; gap: 0;
    background: linear-gradient(rgba(13,31,23,0.82), rgba(13,31,23,0.92)), url("assets/ui-texture.png") center / cover;
    border: 1px solid var(--panel-edge); border-radius: 14px;
    box-shadow: 0 40px 90px -30px rgba(0,0,0,0.8);
    overflow: hidden;
  }
  .inventory-close {
    position: absolute; top: 14px; right: 14px; z-index: 2;
    width: 30px; height: 30px; border-radius: 50%; font-size: 0.9rem; line-height: 1;
    background: rgba(238,241,230,0.06); border: 1px solid var(--panel-edge); color: var(--mist); cursor: pointer;
  }
  .inventory-close:hover { color: var(--lantern); border-color: var(--lantern); }
  .inventory-doll-pane {
    flex: 1 1 42%; min-width: 0; padding: 28px 20px; border-right: 1px solid var(--panel-edge);
    display: flex; flex-direction: column; align-items: center;
  }

  @media (max-width: 700px) {
    .inventory-fullscreen { flex-direction: column; height: min(88vh, 640px); }
    .inventory-doll-pane { border-right: none; border-bottom: 1px solid var(--panel-edge); flex: 0 0 auto; padding: 16px; }
    .inventory-backpack-pane { padding: 16px; }
    .paper-doll-full { height: min(38vh, 320px); }
  }
  .inventory-doll-pane h2, .inventory-backpack-pane h2 {
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
    color: var(--lantern); font-size: 1.2rem; margin: 0 0 14px; text-align: center;
  }
  .inventory-gear-score { color: var(--mist); font-size: 0.86rem; margin-top: 14px; text-align: center; }
  .inventory-backpack-pane {
    flex: 1 1 58%; min-width: 0; padding: 28px 24px; overflow-y: auto;
    display: flex; flex-direction: column;
  }
  .backpack-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 12px;
    align-content: start; flex: 1 1 auto; min-height: 220px; padding: 18px;
    border-radius: 10px; border: 1px solid var(--panel-edge);
    background: linear-gradient(rgba(5,16,10,0.35), rgba(5,16,10,0.55)), url("assets/ui/backpack-bg.png") center / cover;
  }
  .backpack-grid.drag-over { outline: 2px dashed var(--lantern); outline-offset: -2px; }
  .trinket-shelf-title { margin-top: 18px; flex: 0 0 auto; }
  .trinket-shelf {
    display: flex; flex-wrap: wrap; gap: 8px; align-content: flex-start;
    flex: 0 0 auto; min-height: 52px; padding: 12px;
    border-radius: 10px; border: 1px solid var(--panel-edge);
    background: rgba(238,241,230,0.03);
  }
  .trinket-chip {
    display: flex; align-items: center; gap: 5px; font-size: 0.76rem; color: var(--moon);
    background: rgba(238,241,230,0.04); border: 1px solid var(--panel-edge); border-radius: 999px;
    padding: 5px 11px;
  }
  .backpack-item {
    display: flex; flex-direction: column; align-items: center; gap: 4px;
    background: rgba(238,241,230,0.04); border: 1px solid var(--panel-edge); border-radius: 8px;
    padding: 8px 6px 6px; cursor: grab; position: relative;
  }
  .backpack-item:hover { border-color: var(--lantern); }
  .backpack-item.equipped { border-color: var(--lantern); background: rgba(232,178,77,0.08); }
  .backpack-item .backpack-item-icon {
    width: 44px; height: 44px; border-radius: 6px; object-fit: cover; background: var(--ink);
    border: 2px solid var(--rarity-color, var(--panel-edge));
  }
  .item-tooltip-compare { font-size: 0.72rem; margin-top: 6px; }
  .item-tooltip-compare .better { color: #5fbf6e; }
  .item-tooltip-compare .worse { color: var(--berry); }
  .backpack-item .backpack-item-label { font-size: 0.68rem; color: var(--moon); text-align: center; line-height: 1.2; }
  .backpack-item .backpack-item-power { font-size: 0.62rem; color: var(--mist); }
  .item-tooltip {
    display: none; position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
    width: 190px; z-index: 40; text-align: left; pointer-events: none;
    background: linear-gradient(rgba(13,31,23,0.95), rgba(5,16,10,0.98));
    border: 1px solid var(--panel-edge); border-radius: 8px; padding: 10px 12px;
    box-shadow: 0 12px 30px -10px rgba(0,0,0,0.8);
  }
  .backpack-item:hover .item-tooltip, .gear-slot:hover .item-tooltip { display: block; }
  .item-tooltip-name { font-weight: 700; font-size: 0.84rem; margin-bottom: 2px; }
  .item-tooltip-rarity { font-size: 0.68rem; margin-bottom: 6px; opacity: 0.85; }
  .item-tooltip-stat { color: var(--moon); font-size: 0.76rem; }
  .item-tooltip-affix { color: var(--lantern); font-size: 0.76rem; margin-top: 2px; }
  .item-tooltip-desc { color: var(--mist); font-size: 0.7rem; font-style: italic; margin-top: 6px; line-height: 1.35; }
  .item-tooltip-provenance { color: var(--mist); font-size: 0.66rem; margin-top: 6px; opacity: 0.75; }

  #itemPreviewOverlay {
    position: fixed; inset: 0; z-index: 45; pointer-events: none;
    display: flex; align-items: center; justify-content: center; gap: 48px;
    background: rgba(2,8,5,0.8); opacity: 0; transition: opacity 0.15s ease;
  }
  #itemPreviewOverlay.visible { opacity: 1; }
  #itemPreviewImg {
    width: min(52vh, 460px); height: min(52vh, 460px); object-fit: cover; border-radius: 18px;
    border: 3px solid var(--rarity-color, var(--panel-edge));
    box-shadow: 0 40px 100px -20px rgba(0,0,0,0.85), 0 0 60px -10px var(--rarity-color, transparent);
  }
  #itemPreviewInfo { max-width: 340px; }
  #itemPreviewInfo .item-tooltip-name { font-size: 1.4rem; }
  #itemPreviewInfo .item-tooltip-rarity { font-size: 0.9rem; }
  #itemPreviewInfo .item-tooltip-stat { font-size: 1rem; }
  #itemPreviewInfo .item-tooltip-affix { font-size: 1rem; }
  #itemPreviewInfo .item-tooltip-desc { font-size: 0.9rem; }
  #itemPreviewInfo .item-tooltip-provenance { font-size: 0.8rem; }
  #itemPreviewInfo .item-tooltip-compare { font-size: 0.9rem; }

  .backpack-item .equip-btn {
    font-family: inherit; font-size: 0.62rem; font-weight: 600;
    background: var(--lantern); color: var(--ink); border: none;
    border-radius: 6px; padding: 3px 7px; cursor: pointer;
  }

  .paper-doll-full {
    position: relative;
    width: min(340px, 100%); height: min(52vh, 520px);
    margin: 4px 0 2px;
    background: linear-gradient(rgba(5,16,10,0.3), rgba(5,16,10,0.5)), url("assets/ui/doll-bg.png") center / cover;
    border: 1px solid var(--panel-edge);
    border-radius: 10px;
  }
  /* The old clip-path silhouette read as a broken cross/arrow (see prior
     screenshot), not a body — replaced with the player's own portrait,
     full-body where the preset art already is. Gear slots overlay on top and
     toggle instantly on equip/unequip (see renderInventory); the portrait
     art itself doesn't redraw per item — see SESSION_NOTES for that tradeoff. */
  .doll-figure { position: absolute; inset: 8px; border-radius: 8px; overflow: hidden; z-index: 0; }
  .doll-figure img {
    width: 100%; height: 100%; object-fit: cover; object-position: top center;
    opacity: 0; transition: opacity 0.25s ease;
  }
  .doll-figure img.loaded { opacity: 0.92; }
  .gear-slot {
    position: absolute; width: 42px; height: 42px; margin: -21px 0 0 -21px;
    border-radius: 8px; border: 2px solid var(--panel-edge);
    background: rgba(13, 31, 23, 0.7);
    display: flex; align-items: center; justify-content: center;
    font-size: 1.15rem; opacity: 0.4; cursor: default;
    z-index: 1;
  }
  .gear-slot.filled { opacity: 1; border-color: var(--lantern); background: rgba(232,178,77,0.12); cursor: grab; }
  .slot-icon { filter: grayscale(1) brightness(1.6); opacity: 0.5; }
  .gear-slot .slot-power {
    position: absolute; bottom: -6px; right: -6px; min-width: 16px; height: 16px;
    border-radius: 50%; background: var(--lantern); color: var(--ink);
    font-size: 0.6rem; font-weight: 700; line-height: 16px; padding: 0 3px;
  }
  .gear-slot .slot-icon-img {
    width: 100%; height: 100%; object-fit: cover; border-radius: 6px;
    box-shadow: inset 0 0 0 2px var(--rarity-color, transparent);
  }
  .paper-doll-full .gear-slot { width: 66px; height: 66px; margin: -33px 0 0 -33px; font-size: 1.6rem; }
  .gear-slot.drag-target { border-color: var(--lantern); box-shadow: 0 0 0 3px rgba(232,178,77,0.35); }
  .inventory-empty { color: var(--mist); font-size: 0.76rem; font-style: italic; padding: 6px 0; }
  .backpack-grid .inventory-empty {
    grid-column: 1 / -1; align-self: center; justify-self: center; text-align: center;
  }

  .hint-ring {
    position: absolute; width: 60px; height: 60px; border-radius: 50%;
    transform: translate(-50%, -50%); border: 3px solid var(--lantern);
    pointer-events: none; z-index: 4;
    animation: hint-pulse 1s ease-out 3;
  }
  @keyframes hint-pulse {
    0% { opacity: 1; transform: translate(-50%, -50%) scale(0.6); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(1.4); }
  }
  #flagImageBtn.armed { opacity: 1; border-color: var(--berry); color: var(--berry); }

  #flagConfirmRow button {
    font-family: inherit; font-size: 0.72rem; font-weight: 600;
    border-radius: 8px; padding: 8px 12px; cursor: pointer; border: 1px solid var(--panel-edge);
  }
  #flagConfirmYes { background: var(--berry); color: var(--moon); border: none; }
  #flagConfirmNo { background: rgba(238,241,230,0.06); color: var(--moon); }

  #debugPanel {
    position: fixed;
    bottom: 64px;
    left: 16px;
    z-index: 16;
    width: min(480px, 90vw);
    max-height: 60vh;
    overflow-y: auto;
    background: rgba(8, 16, 11, 0.95);
    border: 1px solid var(--panel-edge);
    border-radius: 8px;
    padding: 12px 14px;
    /* read-only display — never block clicks to the world underneath it */
    pointer-events: none;
    user-select: text;
  }

  #debugContent {
    margin: 0;
    font-family: ui-monospace, "SF Mono", "Cascadia Code", Menlo, monospace;
    font-size: 0.68rem;
    line-height: 1.5;
    color: #9fd89f;
    white-space: pre-wrap;
    word-break: break-word;
  }

  /* Quick reaction phrases — a shared mechanic every visitor gets regardless
     of class, so this section is never hidden by applyClassVisibility. */
  section.chat-phrases-section {
    margin-top: 8px; border-top: 1px solid var(--panel-edge); padding-top: 18px;
  }
  .chat-phrases-section h2 {
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--mist); text-align: center; margin: 0 0 12px; font-weight: 600;
  }

  .chat-phrases-row {
    display: flex; flex-wrap: wrap; justify-content: center; gap: 6px;
  }

  .chat-phrases-row button {
    font-family: inherit; font-size: 0.74rem; background: rgba(238,241,230,0.05);
    color: var(--moon); border: 1px solid var(--panel-edge); border-radius: 20px;
    padding: 6px 11px; cursor: pointer;
  }

  .chat-phrases-row button:hover { border-color: var(--lantern); color: var(--lantern); }

  /* Bard-only console — its own section, only shown to bards, distinct from
     the world-wide music list (.music-section) below it. */
  section.bard-console-section {
    margin-top: 8px; padding-top: 18px; border-top: 1px solid var(--panel-edge);
    text-align: center;
  }
  .bard-console-section h2 {
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--mist); text-align: center; margin: 0 0 12px; font-weight: 600;
  }

  .instrument-progress {
    font-size: 0.78rem; color: var(--mist); margin-bottom: 10px;
  }
  .instrument-progress strong { color: var(--lantern); }

  #generateTrackBtn {
    background: var(--lantern); color: var(--ink); border: none; font-weight: 600;
  }
  #generateTrackBtn:disabled { opacity: 0.5; cursor: default; }

  .track-list {
    display: flex; flex-direction: column; gap: 8px; margin-top: 4px; max-height: 260px; overflow-y: auto;
  }

  .track-pill {
    display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
    font-family: inherit; text-align: left; width: 100%;
    background: rgba(238,241,230,0.04); color: var(--mist);
    border: 1px solid var(--panel-edge); border-radius: 8px;
    padding: 8px 12px; cursor: pointer;
  }
  .track-pill.active { border-color: var(--lantern); background: rgba(232,178,77,0.1); }
  .track-pill .track-name { font-size: 0.82rem; color: var(--moon); font-weight: 600; }
  .track-pill.active .track-name { color: var(--lantern); }
  .track-pill .track-meta { font-size: 0.68rem; color: var(--mist); }
  .track-pill .track-prompt { font-size: 0.68rem; color: var(--mist); font-style: italic; }

  footer { position: relative; z-index: 2; text-align: center; color: var(--mist); font-size: 0.7rem; padding-bottom: 24px; }
