/* cache-bust r2 — forces a fresh fingerprint so the CSS deploys under a new URL */
* { margin: 0; padding: 0; box-sizing: border-box; }

    html, body {
      width: 100%; height: 100%;
      background: #000;
      overflow: hidden;
      font-family: 'Courier New', Courier, monospace;
    }

    /* ── Background text layer ── */
    #bg-text {
      position: fixed;
      inset: 0;
      z-index: 0;
      overflow: hidden;
      padding: 12px 10px;
      line-height: 1.45;
      font-size: clamp(9px, 1.1vw, 13px);
      color: #c0181a;
      letter-spacing: 0.01em;
      user-select: none;
      pointer-events: none;
    }

    #bg-text .alt {
      font-style: italic;
    }

    /* Each row is a clipping container; the track span inside scrolls */
    .bg-line {
      display: block;
      overflow: hidden;
      white-space: nowrap;
    }

    /* Track is 2× viewport width (content duplicated); -50% = exactly one copy */
    .treadmill-track {
      display: inline-block;
      white-space: nowrap;
      will-change: transform;
      animation-name: bgTreadmill;
      animation-timing-function: linear;
      animation-iteration-count: infinite;
      /* animation-duration set per-element so each row scrolls at a different speed */
    }

    @keyframes bgTreadmill {
      from { transform: translateX(0); }
      to   { transform: translateX(-50%); }
    }

    /* slow fade-in so page doesn't feel like it slams open */
    #bg-text { animation: bgFadeIn 2.5s ease forwards; }
    @keyframes bgFadeIn {
      from { opacity: 0; }
      to   { opacity: 1; }
    }

    /* scan-line overlay */
    #scanlines {
      position: fixed;
      inset: 0;
      z-index: 1;
      background: repeating-linear-gradient(
        to bottom,
        transparent 0px,
        transparent 3px,
        rgba(0,0,0,0.18) 3px,
        rgba(0,0,0,0.18) 4px
      );
      pointer-events: none;
    }

    /* vignette */
    #vignette {
      position: fixed;
      inset: 0;
      z-index: 2;
      background: radial-gradient(
        ellipse at center,
        transparent 30%,
        rgba(0,0,0,0.75) 100%
      );
      pointer-events: none;
    }

    /* ── Three.js canvas ──
       Click-through (pointer-events: none) so it never grabs drags from
       anywhere on screen — #logo-hit-area below, sized to the logo's own
       rendered footprint, is the actual interactive surface. Matches the
       catalog.html/cart.html pattern (see "Logo layering" in CLAUDE.md). */
    #three-canvas {
      position: fixed;
      inset: 0;
      z-index: 3;
      pointer-events: none;
      opacity: 0;                       /* fades in once the WebGL logo is ready */
      transition: opacity 0.7s ease;
    }
    #three-canvas.is-ready { opacity: 1; }

    /* Static logo, shown immediately with zero JS (webp + png fallback via
       <picture>). It IS the hero on low-power / no-WebGL devices, and the
       placeholder the WebGL canvas cross-fades in over on capable ones. */
    #logo-static {
      position: fixed;
      left: 50%;
      top: 48vh;
      transform: translate(-50%, -50%);
      width: min(60vw, 560px);
      z-index: 3;
      pointer-events: none;
      opacity: 1;
      transition: opacity 0.6s ease;
    }
    #logo-static img { display: block; width: 100%; height: auto; }
    #logo-static.is-hidden { opacity: 0; }
    /* Gentle breathe so the fallback (no-WebGL / low-power) logo has life, not
       a dead frame. Echoes the 3D logo's ~1.5% scale pulse. Off when the WebGL
       canvas takes over (is-hidden) and off for reduced-motion users. */
    @media (prefers-reduced-motion: no-preference) {
      #logo-static:not(.is-hidden) { animation: logoBreathe 6s ease-in-out infinite; }
    }
    @keyframes logoBreathe {
      0%, 100% { transform: translate(-50%, -50%) scale(1); }
      50%      { transform: translate(-50%, -50%) scale(1.02); }
    }

    /* Invisible surface sized to the logo's own rendered footprint (measured
       empirically off a live render, not guessed — this logo is centered in
       the viewport and ~1.85x the size of catalog.html/cart.html's, unlike
       those pages' near-top-of-viewport placement). Carries the
       drag-to-rotate interaction instead of the (now click-through) canvas. */
    #logo-hit-area {
      position: fixed;
      top: 30vh;
      left: 20%;
      width: 60%;
      height: 38vh;
      z-index: 3;
      pointer-events: auto;
      cursor: grab;
      background: transparent;
    }

    /* ── the door — the decluttered landing layer over the spinning logo ──
       One bottom-anchored stack: the "enter." button, the "leave your mark"
       button directly below it, then the action row (the tagline). Anchored
       near the bottom so the whole cluster sits well clear of the centered
       logo. Fades in on a delayed, "assembling" cadence. Only the OF clue +
       console line are gated (PUZZLE_LIVE). */
    #door {
      position: fixed;
      left: 50%;
      bottom: clamp(6px, 1.6vh, 16px);             /* whole stack sits near the bottom, clear of the logo */
      transform: translateX(-50%);
      z-index: 4;
      width: min(400px, 92vw);
      text-align: center;
      font-family: 'Courier New', Courier, monospace;
      color: #b7c7e8;                              /* dim periwinkle ─ reads on black, in-voice */
      text-shadow: 0 0 7px #000, 0 0 3px #000;     /* legibility over the moving bg text + logo */
      opacity: 0;
      animation: doorFadeIn 2s ease 2.4s forwards;
    }
    @keyframes doorFadeIn { to { opacity: 1; } }
    /* [hidden] must win over the flex/block display rules below */
    #door [hidden] { display: none !important; }

    /* No state machine: the landing page (#door cluster, bg-text, logo) is ALWAYS
       fully visible; the email popup is just a plain overlay on top of it. */

    /* ── the persistent "leave your mark" button — sits directly below the "enter."
       button as the standing re-entry to the capture modal (the popup auto-opens
       only once per browser, fools_passed, so this is the way back in after "not
       yet"). Built on .pbtn for the shared terminal type/shape, but rendered as a
       GHOST (periwinkle outline, transparent fill) rather than a solid fill so the
       "enter." button above it stays the one bright focal point; it fills in on
       hover/focus. In normal flow inside #door, so it fades in with the whole
       cluster (no own animation) and dims behind the modal scrim while it's open. */
    #leave-mark {
      display: inline-block;
      width: 192px;                               /* shared with #enter-door — the two boxes are the same length */
      max-width: 100%;
      margin: clamp(13px, 2vh, 18px) auto 0;       /* same gap as #door-actions below — even rhythm off the enter button */
      background: none;                            /* override .pbtn's solid fill → ghost */
      color: #7B9ED9;
      padding: 8px 22px;
      letter-spacing: 0.14em;
      font-weight: 700;                            /* heavier "leave your mark" — monospace, so no width shift */
      opacity: 0.9;
      transition: background 0.18s ease, color 0.18s ease, opacity 0.18s ease;
    }
    #leave-mark:hover,
    #leave-mark:focus-visible { background: #7B9ED9; color: #000; opacity: 1; }
    #leave-mark:focus-visible { outline: 1px solid #7B9ED9; outline-offset: 3px; }

    /* framing copy above the email field, inside the popup. */
    #stakes {
      margin: 4px 0 16px;
      color: #8fa4c8;
      font-size: clamp(11px, 1.2vw, 13px);
      line-height: 1.55;
      letter-spacing: 0.03em;
      text-transform: lowercase;
    }

    /* the bottom action row: now just the glitch tagline, in the normal flow
       below "leave your mark". Its margin-top matches #leave-mark's so the
       "enter." → "leave your mark" → "stay foolish." gaps read as one even
       rhythm. ("leave your mark" sits higher up, directly below the "enter."
       button — see #leave-mark above — not in this bottom row.) */
    #door-actions {
      margin-top: clamp(13px, 2vh, 18px);
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 15px;
    }

    /* the "side door" — a Discord exit tucked in the true bottom-right screen
       corner (top-level, position:fixed — kept out of #door because its transform
       would trap a fixed child). Reads as an ambient corner detail, not part of
       the centered stack. Dimness is a muted color (not opacity) so the opacity
       channel is free to run the door's own fade-in cadence — a finished forwards
       opacity animation can't be overridden by :hover, so hover brightens color
       instead. Only `right` is pinned (no left/width), so the box grows leftward
       as the label expands — the glyph itself never moves. */
    #side-door {
      position: fixed;
      right: clamp(12px, 2.4vw, 20px);
      bottom: clamp(12px, 2.4vh, 20px);
      z-index: 5;
      display: inline-flex;
      align-items: center;
      color: #47597f;                              /* dim periwinkle ─ sensed, not shouted */
      text-decoration: none;
      -webkit-tap-highlight-color: transparent;
      opacity: 0;                                  /* fades in on the same cadence as #door */
      animation: doorFadeIn 2s ease 2.4s forwards;
      transition: color 0.2s ease;
    }
    /* the `hidden` attribute must win over the id-level display above (it's the
       shipped-until-URL-set state, and unlock's mirror re-applies it). #door's
       own `[hidden] !important` rule doesn't reach here — this element is
       top-level — so make it explicit; #side-door[hidden] out-specifies #side-door. */
    #side-door[hidden] { display: none; }
    #side-door:hover,
    #side-door:focus-visible { color: #9fbdec; }   /* brighten the emblem on approach */
    #side-door:focus-visible { outline: 1px solid #7B9ED9; outline-offset: 4px; border-radius: 2px; }
    #side-door-glyph { width: 20px; height: 20px; display: block; flex: 0 0 auto; }
    /* the label sits to the LEFT of the glyph and collapses to zero width at rest,
       so only the emblem shows until hover/focus reveals the line. */
    #side-door-label {
      max-width: 0;
      overflow: hidden;
      white-space: nowrap;
      opacity: 0;
      font-family: 'Courier New', Courier, monospace;
      font-size: 11px;
      letter-spacing: 0.1em;
      text-transform: lowercase;
      color: inherit;                              /* tracks the glyph's dim→bright color */
      transition: max-width 0.3s ease, opacity 0.3s ease, margin-right 0.3s ease;
    }
    #side-door:hover #side-door-label,
    #side-door:focus-visible #side-door-label {
      max-width: 16ch;
      opacity: 1;
      margin-right: 8px;
    }

    /* ── the "gallery" button ─ a boxed link to the photo wall (gallery.html),
       pinned bottom-LEFT (the mirror of #side-door's bottom-right emblem).
       Ghost box in the shared periwinkle button language (same outline +
       fill-on-hover as #leave-mark), fading in on the #door / #side-door
       cadence. Bigger than a bare text link, but the transparent fill keeps
       it from out-shouting the centered "stay foolish." tagline. ── */
    #gallery-link {
      position: fixed;
      left: clamp(12px, 2.4vw, 20px);
      bottom: clamp(12px, 2.4vh, 20px);
      z-index: 5;
      display: inline-block;
      font-family: 'Courier New', Courier, monospace;
      font-size: clamp(12px, 1.4vw, 15px);
      letter-spacing: 0.16em;
      text-transform: lowercase;
      color: #7B9ED9;
      text-decoration: none;
      background: none;
      border: 1px solid #7B9ED9;
      border-radius: 2px;
      padding: 10px 22px;
      -webkit-tap-highlight-color: transparent;
      opacity: 0;                                  /* fades in on the same cadence as #door / #side-door */
      animation: doorFadeIn 2s ease 2.4s forwards;
      transition: background 0.18s ease, color 0.18s ease;
    }
    #gallery-link:hover,
    #gallery-link:focus-visible { background: #7B9ED9; color: #000; }
    #gallery-link:focus-visible { outline: 1px solid #7B9ED9; outline-offset: 3px; }

    /* the riddle ─ dormant (PUZZLE_LIVE), kept only because that flag also gates
       the bg-text "OF" fragment. Deliberately quiet: dim muted red so it recedes. */
    #riddle {
      font-size: clamp(10px, 1.05vw, 12px);
      line-height: 1.55;
      letter-spacing: 0.03em;
      color: #c0181a;                              /* the bg-text red */
      opacity: 0.68;                               /* dim/muted ─ recedes */
      margin: 0 auto 13px;
    }

    /* ── the "become the fool" button (#enter-door) — direct entry into the shop
       (catalog.html). Replaced the password box: no gate, just a link. The
       filled twin of the "leave your mark" ghost button below it — same
       typeface + same box width (WIDTH is shared literally, see #leave-mark;
       both labels are 15 monospace chars, so they fill it identically) — but
       solid periwinkle so it stays the one bright focal point. Fades in with
       the #door cluster (no own animation, same as #leave-mark). ── */
    #enter-door {
      display: block;                              /* own line — "leave your mark" sits below it */
      width: 192px;                                /* == #leave-mark's width, so the two boxes match exactly */
      max-width: 100%;
      margin: 0 auto;
      padding: 8px 22px;                           /* == #leave-mark */
      background: #7B9ED9;
      color: #0a0a0a;
      border: 1px solid #7B9ED9;
      border-radius: 2px;
      font-family: 'Courier New', Courier, monospace;   /* == #leave-mark */
      font-size: clamp(11px, 1.15vw, 13px);             /* == #leave-mark (.pbtn) */
      font-weight: 700;                                 /* == #leave-mark */
      letter-spacing: 0.14em;                           /* == #leave-mark */
      text-transform: lowercase;
      text-align: center;
      white-space: nowrap;                         /* one line, like #leave-mark (.pbtn) */
      text-decoration: none;
      text-shadow: none;                          /* crisp on the filled button */
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      transition: background 0.18s ease, color 0.18s ease;
    }
    #enter-door:hover,
    #enter-door:focus-visible { background: #a0bce8; color: #000; }
    #enter-door:focus-visible { outline: 1px solid #7B9ED9; outline-offset: 3px; }

    /* shared input styling — the modal card's email / phone fields */
    .modal-card input[type="text"],
    .modal-card input[type="email"],
    .modal-card input[type="tel"] {
      width: 100%;
      background: rgba(0,0,0,0.55);
      border: 1px solid #2a2a2a;
      border-radius: 2px;
      outline: none;
      color: #c8afe1;                              /* lilac ─ what the fool types */
      font-family: 'Courier New', Courier, monospace;
      font-size: clamp(11px, 1.15vw, 13px);
      letter-spacing: 0.06em;
      text-align: center;
      padding: 8px 12px;
      caret-color: #c8afe1;
    }
    .modal-card input::placeholder { color: #55606f; opacity: 1; }
    .modal-card input:focus { border-color: #7B9ED9; }   /* periwinkle focus */
    .modal-card input:disabled { opacity: 0.5; }

    /* periwinkle buttons, black text (email submit / copy) */
    .pbtn {
      background: #7B9ED9;
      border: 1px solid #7B9ED9;
      color: #000;
      font-family: 'Courier New', Courier, monospace;
      font-size: clamp(11px, 1.15vw, 13px);
      letter-spacing: 0.12em;
      text-transform: lowercase;
      padding: 0 14px;
      border-radius: 2px;
      cursor: pointer;
      white-space: nowrap;
      transition: opacity 0.18s ease;
    }
    .pbtn:hover { opacity: 0.82; }
    .pbtn:disabled { opacity: 0.45; cursor: default; }

    /* doorBlink ─ blinking-cursor keyframe, still used by .lore-caret in the manifesto overlay */
    @keyframes doorBlink { 0%,50% { opacity: 1; } 50.01%,100% { opacity: 0; } }

    /* email + sms capture (now inside the modal card) ─ email + a collapsed
       "+ leave your number" toggle + enter */
    #capture-form {
      display: flex;
      flex-direction: column;
      gap: 9px;
    }
    #capture-form #email-submit { width: 100%; padding: 9px 14px; font-weight: 700; }  /* heavier "leave your mark" */
    /* phone toggle ─ a quiet text link, not a periwinkle button */
    #phone-toggle {
      align-self: center;
      background: none; border: none;
      color: #7dafe1;
      font-family: 'Courier New', Courier, monospace;
      font-size: clamp(10px, 1.05vw, 12px);
      letter-spacing: 0.04em; text-transform: lowercase;
      cursor: pointer; opacity: 0.72; padding: 2px 4px;
      transition: opacity 0.15s ease;
    }
    #phone-toggle:hover { opacity: 1; text-decoration: underline; }
    /* SMS opt-in — explicit consent, required before any number is sent (TCPA). */
    #sms-consent-label {
      display: flex;
      align-items: flex-start;
      gap: 8px;
      margin-top: 8px;
      text-align: left;
      font-family: 'Courier New', Courier, monospace;
      font-size: 10px;
      line-height: 1.45;
      letter-spacing: 0.01em;
      color: #6c6c76;
      cursor: pointer;
    }
    #sms-consent {
      flex: 0 0 auto;
      width: 14px; height: 14px;
      margin: 1px 0 0;
      accent-color: #7B9ED9;
      cursor: pointer;
    }
    #capture-status {
      display: block;
      min-height: 1.2em;
      margin-top: 2px;
      font-size: clamp(10px, 1.05vw, 12px);
      letter-spacing: 0.04em;
      color: #fa964b;
    }

    /* cycling tagline ─ glitch-flicker between two lines; now a button that opens
       the manifesto overlay. position/overflow give the pixel-glitch layers a clip
       host (see the glitch code in initDoor). */
    #tagline {
      position: relative;
      overflow: hidden;
      display: inline-block;
      text-align: center;                          /* center both cycled labels within the locked width */
      font-size: clamp(11px, 1.2vw, 14px);
      letter-spacing: 0.14em;
      font-weight: 700;                            /* heavier "stay foolish." — the glitch layers inherit it; Courier is monospace so the width lock is unaffected */
      text-transform: lowercase;
      color: #c8afe1;                              /* lilac */
      user-select: none;
      cursor: pointer;
    }
    #tagline:focus-visible { outline: 1px solid #7B9ED9; outline-offset: 4px; }

    /* ── overlays (shared base): the "leave your mark" modal and the manifesto.
       Full-screen, fade in, and keep a scanline texture running over them so they
       read as the same CRT surface as the page (::after, same gradient as
       #scanlines). ── */
    .overlay {
      position: fixed;
      inset: 0;
      z-index: 60;
      display: flex;
      align-items: center;
      justify-content: center;
      opacity: 0;
      transition: opacity 0.25s ease;
    }
    .overlay.show { opacity: 1; }
    .overlay[hidden] { display: none; }
    .overlay::after {
      content: '';
      position: absolute;
      inset: 0;
      z-index: 3;
      pointer-events: none;
      background: repeating-linear-gradient(
        to bottom,
        transparent 0px, transparent 3px,
        rgba(0,0,0,0.18) 3px, rgba(0,0,0,0.18) 4px);
    }
    /* keep [hidden] children hidden inside the card despite id display rules */
    .modal-card [hidden] { display: none !important; }

    /* modal scrim ─ dims + blurs the page behind so the door recedes but is sensed */
    .overlay-scrim {
      position: absolute;
      inset: 0;
      z-index: 0;
      background: rgba(0,0,0,0.5);
      backdrop-filter: blur(6px);
      -webkit-backdrop-filter: blur(6px);
    }

    /* the terminal card */
    .modal-card {
      position: relative;
      z-index: 1;
      width: min(380px, 92vw);
      max-height: 86vh;
      overflow-y: auto;
      background: rgba(10,10,10,0.96);
      border: 1px solid #2a2a2a;
      border-radius: 3px;
      box-shadow: 0 0 0 1px rgba(125,158,217,0.05), 0 0 44px rgba(0,0,0,0.6),
                  inset 0 0 34px rgba(125,158,217,0.03);   /* subtle inner glow */
      padding: 30px 26px 26px;
      text-align: center;
      font-family: 'Courier New', Courier, monospace;
      color: #b7c7e8;
      transform: scale(0.96);
      transition: transform 0.25s ease;
    }
    .overlay.show .modal-card { transform: scale(1); }

    .modal-close {
      position: absolute;
      top: 6px;
      right: 12px;
      z-index: 5;
      background: none;
      border: none;
      color: #7dafe1;
      font-family: 'Courier New', Courier, monospace;
      font-size: 22px;
      line-height: 1;
      cursor: pointer;
      padding: 4px 8px;
      opacity: 0.7;
      transition: opacity 0.15s ease;
    }
    .modal-close:hover { opacity: 1; }
    /* the door's dismiss is "not yet" (text, not ×) — kept small + quiet in the
       corner so it reads as the lesser choice and never crowds the heading. It
       becomes a small × once inside (see updateCloseAffordance). */
    #mark-close {
      font-size: 12px;
      text-transform: lowercase;
      letter-spacing: 0.04em;
      opacity: 0.5;
      top: 9px;
      right: 11px;
    }

    /* ── the manifesto overlay ─ auto-scrolling lore, black, its own scanlines ── */
    #manifesto {
      background: rgba(0,0,0,0.9);
      backdrop-filter: blur(2px);
      -webkit-backdrop-filter: blur(2px);
      transition: opacity 0.4s ease;              /* slower fade than the modal */
      cursor: pointer;                            /* click anywhere closes */
    }
    #manifesto-viewport {
      position: absolute;
      inset: 0;
      overflow: hidden;
      z-index: 1;
    }
    #manifesto-scroll {
      position: absolute;
      left: 0;
      right: 0;
      padding: 0 8vw;
      text-align: center;
      font-family: 'Courier New', Courier, monospace;
      will-change: transform;
      /* the reveal is a single, non-looping scroll driven in JS (see openManifesto):
         it rises from below and STOPS once the whole manifesto is shown, then the
         user closes with × / esc / click. */
    }
    .lore {
      color: #6b6b6b;                              /* dim by default */
      font-size: clamp(13px, 1.5vw, 16px);
      letter-spacing: 0.04em;
      line-height: 1.7;
      text-transform: lowercase;
      margin: 1.7em 0;
    }
    .lore-term {
      color: #c8afe1;
      font-size: clamp(20px, 2.6vw, 28px);
      letter-spacing: 0.12em;
      margin-bottom: 2.4em;
    }
    .lore-pos { color: #7dafe1; font-size: 0.55em; letter-spacing: 0.05em; }
    .lore-lilac  { color: #c8afe1; }
    .lore-green  { color: #64c864; }
    .lore-orange { color: #fa964b; }
    .lore-blue   { color: #7dafe1; }
    .lore-red    { color: #fa644b; }
    .lore-final  { font-size: clamp(15px, 1.9vw, 21px); letter-spacing: 0.14em; margin-top: 2.4em; }
    .lore-caret {
      display: inline-block;
      margin-left: 4px;
      color: inherit;
      animation: doorBlink 1.1s steps(1) infinite;
    }

    /* ── Mobile (≤768px) ──────────────────────────────────────────────
       Everything below only applies at ≤768px wide; the desktop rules
       above are left untouched. The 3D logo scale, sparkle clustering and
       glitch block-count have matching JS gated on the *same* breakpoint
       via window.matchMedia('(max-width: 768px)'). Addresses the
       "untested below 768px" note in CLAUDE.md. */
    @media (max-width: 768px) {
      /* Ensure touch drags on the logo reach our pointer handlers instead
         of being claimed by a browser touch gesture (pan/zoom). */
      #logo-hit-area { touch-action: none; }

      /* Keep the ambient background text small + dense on mobile, matching the
         packed look it has on desktop (a bigger fluid size read too sparse and
         fit too few lines on a narrow screen). buildBgText() reads the live
         computed font-size, so its per-line character counts and the seamless
         treadmill loop recompute from this automatically on load and resize. */
      #bg-text { font-size: clamp(6px, 1.5vw, 8px); }

      /* Narrow viewport: the door cluster must sit BELOW the (screen-centred) 3D
         logo — logo first, then the "enter." button, then "leave your mark". The logo
         is rendered centred by WebGL (its bottom edge is a function of
         window.innerHeight), so keying the door to CSS vh/% is unreliable: on a fixed
         element those don't equal innerHeight when a mobile browser toolbar is shown,
         and the door would drift over the logo. This rule is only the static fallback
         (near-bottom, matching the pre-mobile-JS behaviour); positionDoorMobile() —
         JS, gated on the same 768px breakpoint as the logo scale — then overrides
         `top` from the logo's ACTUAL rendered bottom edge, keyed to the exact
         innerHeight the canvas uses, so the door tracks the logo at every viewport
         height (short phones / toolbar heights, where a bottom-anchored door climbs
         up over the logo). Desktop (>768px) untouched. */
      #door { bottom: clamp(84px, 20vh, 150px); width: 94vw; max-width: 360px; }
      #riddle { font-size: 11px; line-height: 1.65; margin-bottom: 14px; }
      #door-actions { bottom: clamp(16px, 4vh, 30px); gap: 15px; }
      .modal-card { width: 92vw; max-width: 360px; padding: 28px 20px 22px; }
      .lore { font-size: 14px; }

      /* The "side door" glyph stays visually subtle (still 20px), but pad the
         anchor out to a ~44px touch target for fingers — the hover-reveal is a
         desktop nicety; a phone just taps the emblem to open Discord. The padding
         grows the box inward from the pinned corner, so trim the offset to keep
         the glyph snug in the corner rather than 24px in. */
      #side-door {
        padding: 12px;
        right: clamp(4px, 1.4vw, 10px);
        bottom: clamp(4px, 1.4vh, 10px);
      }

      /* Tuck the box a little closer to the corner on small screens. */
      #gallery-link {
        left: clamp(6px, 2vw, 12px);
        bottom: clamp(6px, 2vh, 12px);
        font-size: 13px;
        padding: 9px 18px;
      }
    }
