/* ============================================================
   ROOM — the page itself: drag surface, canvas, wall mounts, the
   clickable glow, nav arrows, and the boot-failure banner.
   Overlays are in overlays.css; the chatbot frame + input bar are in
   chatbot.css.
   ============================================================ */
* { box-sizing: border-box; }
html, body {
  margin:0; padding:0; overflow:hidden; width:100%; height:100%; background:#0a0a0a;
  /* The whole page is a drag surface. Without this the browser runs its
     own gesture recognition (scroll, rubber-band, pull-to-refresh) in
     parallel with every touch drag and the two fight — which feels like
     the room stuttering. Same reasoning extends to long-press callout
     (iOS's copy/save "peek" menu) and text/image selection — both are
     browser-native gestures that can fire independently of pointer-events,
     and both are exactly the kind of thing that could make a held-down
     press look like it's doing something to the content underneath it. */
  touch-action: none;
  overscroll-behavior: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
/* absolute, NOT fixed — the live site's mounting. position:fixed inside
   iOS browser wrappers (Chrome especially) can get repositioned by the
   wrapper's viewport handling; when the element owns the 3D perspective,
   the whole scene paints offset while reported geometry stays 'correct'.
   The body never scrolls, so absolute placement is visually identical. */
canvas { position:absolute; top:0; left:0; }

/* Each item's mount spans its whole wall; flex-end puts the artwork's
   bottom edge flat on the real floor. */
.wall {
  display:flex; justify-content:center; pointer-events:none;
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
}
.wall.floor   { align-items:flex-end; }
.wall.center  { align-items:center; }
.wall.ceiling { align-items:flex-start; }   /* hangs from the ceiling — top edge flush */
.wall.flat   { display:block; }   /* lying on the floor — no standing needed */
/* The artwork must never swallow the mouse. CSS3DObject forces
   pointer-events:auto inline on the mount, so that's overridden in JS;
   these stop the browser's native image-drag from stealing the mouseup. */
.wall img {
  display:block;
  pointer-events:none;
  user-select:none;
  -webkit-user-drag:none;
  -webkit-touch-callout: none;
  /* Didn't fix the disappear-on-hold bug on its own (that's a native
     gesture layer, and the bug reproduces on desktop mouse too, which
     rules out an iOS-only cause) — kept anyway since it's still correct
     for a non-interactive background image, just not the whole story.
     Actual suspect: bookcase and fireplace are the two biggest things
     on screen by rendered size (fireplace ~95% of its wall, bookcase
     the tallest item in the room) — some engines demote/re-promote a
     3D-transformed element's GPU compositing layer on pointer-down hit-
     testing (checking what's under the cursor, even though nothing
     there listens for clicks), and re-rasterizing a BIG transformed
     layer from scratch is slow enough to show as a blank frame, where
     a small one finishes before it's visible. will-change keeps it on
     its own persistent layer instead of being promoted/demoted around
     interaction. */
  will-change: transform;
}
/* PAINT items — CSS-drawn room elements with no image file (room.js
   `paint:` items). The lantern's light on the wall is one: a disc whose
   radial gradient (colour set inline from CONFIG) fades to transparent by
   70% of its radius, so there's no hard edge and no filter:blur to re-
   rasterise every frame. Sized by JS in room units like any other item. */
.wall .paint {
  display:block;
  pointer-events:none;
  user-select:none;
  will-change: transform;
}
.wall .paint-glow {
  border-radius:50%;
}
/* Clickable glow. Traces the silhouette's own alpha via drop-shadow —
   not a box-shadow, so it hugs fingers and gaps correctly. Icy palette:
   pale cyan-blue core + soft blue bloom, pulsing continuously via one
   ALWAYS-RUNNING animation. Hover doesn't swap to a different animation
   (which would restart the rhythm and cause a visible jump) — it just
   overrides the custom properties the keyframes read, so the same pulse
   continues uninterrupted at a brighter level.

   Radius is FIXED (7px / 21px) across both keyframe states — only alpha
   cycles. Animating blur-radius forces the browser to regenerate a
   differently-sized blur each frame; holding it constant lets it reuse
   the same blurred buffer and just recomposite opacity, which is the
   cheap path. This was contributing to jank on mobile alongside the
   nav arrows' old radius-animating glow. */
img.glow {
  --core:       rgba(255, 255, 255, 0.55);
  --bloom:      rgba(255, 255, 255, 0.30);
  --core-peak:  rgba(255, 255, 255, 0.80);
  --bloom-peak: rgba(255, 255, 255, 0.48);
  /* 5.5s: a genuine slow breath, not a shimmer. ease-in-out already
     biases the motion toward its endpoints, which is what makes a slow
     pulse read as "breathing" rather than a health-bar cycling in slow
     motion — most of the time is spent near full-in / full-out, and the
     transitions between them stay swift. */
  animation: icyPulse 5.5s ease-in-out infinite;
}
img.glow.hot {
  --core:       rgba(255, 255, 255, 0.82);
  --bloom:      rgba(255, 255, 255, 0.50);
  --core-peak:  rgba(255, 255, 255, 1.00);
  --bloom-peak: rgba(255, 255, 255, 0.68);
}
@keyframes icyPulse {
  0%, 100% {
    filter:
      drop-shadow(0 0 7px var(--core))
      drop-shadow(0 0 21px var(--bloom));
  }
  50% {
    filter:
      drop-shadow(0 0 7px var(--core-peak))
      drop-shadow(0 0 21px var(--bloom-peak));
  }
}

/* ============================================================
   LOADING SCREEN — first thing painted, last thing to leave. boot.js
   adds .loaded once fonts, the plaque/frame art, and the room's own
   assets are in, never sooner than its minimum display time. Above
   everything, including the boot banner (which lifts it on its way in).
   ============================================================ */
#loading-screen {
  position: fixed; inset: 0; z-index: 99999;
  background: #0a0a0a;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  transition: opacity 0.6s ease, visibility 0.6s ease;
}
#loading-screen.loaded {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
/* Phones: pin the loading screen to the SMALL viewport height. With
   inset:0 it tracks the browser's toolbar collapsing during load, and the
   centred logo visibly re-centres — the "logo jump". 100svh never changes.
   Any strip the toolbar uncovers below it is body background, same black. */
@media (max-width: 599px) {
  #loading-screen { bottom: auto; height: 100svh; }
}
.loading-logo {
  width: 80px; height: auto;
  animation: logoGlow 5s ease-in-out infinite;
}
.loading-label {
  margin-top: 24px;
  font: 11px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}
/* One slow breath: 5s in and out, brass-coloured bloom. */
@keyframes logoGlow {
  0%, 100% {
    filter: drop-shadow(0 0 8px rgba(205, 150, 80, 0.3));
    opacity: 0.7;
  }
  50% {
    filter: drop-shadow(0 0 20px rgba(205, 150, 80, 0.7)) drop-shadow(0 0 40px rgba(180, 120, 60, 0.4));
    opacity: 1;
  }
}

/* Boot-failure banner — shown by boot.js if the room module never
   reports in (file://, unpkg unreachable, or a script error). */
#err {
  display:none; position:fixed; inset:0; z-index:30000;   /* above the plaque (20000) */
  background:#2a0a0a; color:#ffbcbc; padding:40px;
  font:14px/1.6 -apple-system, BlinkMacSystemFont, sans-serif;
}
#err h2 { color:#ff6b6b; margin:0 0 12px; }
#err code { background:rgba(255,255,255,0.1); padding:2px 6px; border-radius:3px; }

/* Nav arrows: minimal UI chrome, not disguised room hardware — a
   secondary affordance behind swipe/drag, so it stays quiet at rest.
   Static, not pulsing (the clickable's pulse is the room's one
   "tap me" signal). The tap target (.nav-arrow) and the visible mark
   (.nav-arrow-visual) are DECOUPLED: the hit area stays a generous
   ~56px for reliable tapping, but the glyph you actually see can be
   much smaller/quieter without shrinking what you can tap.

   Solid dark circle backing on BOTH breakpoints, not just mobile — a
   bare ghost icon (color + drop-shadow, no fill) tested fine on a flat
   background but got lost against the wallpaper's damask pattern on
   desktop, which has motifs at a similar visual scale to the chevron
   itself. A flat opaque disc reads regardless of what's behind it.

   Eye level (top:38%) on desktop, NOT the bottom corners. Tried the
   bottom once: the mantel fills 95% of the fireplace wall, so the dark
   disc landed on dark mahogany and vanished. At eye level there's wall
   behind the arrows on every wall, which is what makes them legible.
   Wall art just has to stay inside them — about 10 units off centre at
   the furniture distance. */
.nav-arrow {
  position:fixed; top:38%; z-index:900;
  width:56px; height:56px; margin-top:-28px;
  display:flex; align-items:center; justify-content:center;
  cursor:pointer; pointer-events:auto;
  background:transparent; border:none;
  -webkit-tap-highlight-color:transparent; user-select:none;
  touch-action:manipulation;   /* no double-tap zoom on a tapped arrow (as on the live site) */
}
.nav-arrow-visual {
  display:flex; align-items:center; justify-content:center;
  width:44px; height:44px;
  border-radius:50%;
  background: rgba(6,5,4,0.96);
  border:1px solid rgba(255,255,255,0.25);
  color: rgba(255,255,255,0.85);
  box-shadow: 0 0 12px 4px rgba(255,255,255,0.16), 0 0 24px 10px rgba(255,255,255,0.08);
  transition: color .3s ease, border-color .3s ease, transform .15s ease;
}
.nav-arrow-visual svg { width:22px; height:22px; display:block; }
.nav-arrow-left  { left:  max(56px, 8%); }
.nav-arrow-right { right: max(56px, 8%); }
/* Scoped to devices with a real cursor. Touchscreens have no true hover —
   iOS Safari in particular can latch a tapped element's :hover state
   indefinitely (nothing ever moves off it to clear it), which is exactly
   what made "whichever button I tapped last" stay brighter. */
@media (hover: hover) and (pointer: fine) {
  .nav-arrow:hover .nav-arrow-visual {
    color: rgba(255,255,255,1);
    border-color: rgba(255,255,255,0.5);
  }
}
.nav-arrow:active .nav-arrow-visual { transform:scale(0.88); }
@media (max-width: 899px) {
  /* Bottom inset respects the home-indicator/gesture area rather than
     a flat vh value, so the buttons don't end up under it on notched
     phones. Side insets do the same for edge-swipe-back zones. */
  .nav-arrow {
    top:auto;
    bottom: max(3vh, calc(env(safe-area-inset-bottom, 0px) + 14px));
    margin-top:0;
  }
  .nav-arrow-left  { left:  max(16px, calc(env(safe-area-inset-left, 0px) + 12px)); }
  .nav-arrow-right { right: max(16px, calc(env(safe-area-inset-right, 0px) + 12px)); }
  /* Same solid-circle treatment as desktop now — size/position is all
     that changes for mobile's thumb-zone placement. */
}