/* ============================================
   ROOM VIEW — Immersive Horizontal Gallery Wall
   ============================================ */

/* ── Root container ──────────────────────────────────────────────────────── */

.room {
  position: fixed;
  inset: 0;
  overflow: hidden;
  background: var(--wall-color, #0a0a0c);
}

/* ── Full-height scene (ceiling / wall / floor) ───────────────────────────── */

.room__scene {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.room__ceiling {
  height: 13vh;
  flex-shrink: 0;
  position: relative;             /* needed for ::after fixture dots */
  background: linear-gradient(to bottom,
    #030304 0%,
    var(--wall-color, #0a0a0c) 100%
  );
  /* Cornice shadow line at wall junction */
  border-bottom: 1px solid rgba(0, 0, 0, 0.40);
}

/* Ceiling track + fixtures.
 * ::before = the track rail — thin horizontal bar the fixtures hang from.
 * ::after  = the 4 fixture bodies — narrow vertical ovals viewed from below.
 *            Each oval: bright lamp opening at centre → warm halo → dark housing.
 * X positions match wall cone positions (12 / 37 / 63 / 88%). */

/* Track rail */
.room__ceiling::before {
  content: '';
  position: absolute;
  left: 4%;
  right: 4%;
  top: 52%;
  height: 4px;
  transform: translateY(-50%);
  background: rgba(8, 6, 4, 0.92);
  border-radius: 2px;
  pointer-events: none;
}

/* Fixture ovals: ellipse 5px 14px = 10px wide × 28px tall vertical oval.
 * Stops (as % of the 14px vertical radius):
 *   0–14% = bright lamp core  (~2 px)
 *  14–28% = warm amber halo   (~2–4 px)
 *  30–96% = dark metal housing (~4–13.5 px)
 *  96–100% = feathered edge */
.room__ceiling::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse 5px 14px at 12% 52%,
      rgba(255, 255, 250, 1.0)   0%,
      rgba(255, 255, 250, 1.0)  14%,
      rgba(255, 225, 110, 0.90) 28%,
      rgba(12,  10,   7, 1.0)   30%,
      rgba(12,  10,   7, 1.0)   96%,
      transparent               100%),
    radial-gradient(ellipse 5px 14px at 37% 52%,
      rgba(255, 255, 250, 1.0)   0%,
      rgba(255, 255, 250, 1.0)  14%,
      rgba(255, 225, 110, 0.90) 28%,
      rgba(12,  10,   7, 1.0)   30%,
      rgba(12,  10,   7, 1.0)   96%,
      transparent               100%),
    radial-gradient(ellipse 5px 14px at 63% 52%,
      rgba(255, 255, 250, 1.0)   0%,
      rgba(255, 255, 250, 1.0)  14%,
      rgba(255, 225, 110, 0.90) 28%,
      rgba(12,  10,   7, 1.0)   30%,
      rgba(12,  10,   7, 1.0)   96%,
      transparent               100%),
    radial-gradient(ellipse 5px 14px at 88% 52%,
      rgba(255, 255, 250, 1.0)   0%,
      rgba(255, 255, 250, 1.0)  14%,
      rgba(255, 225, 110, 0.90) 28%,
      rgba(12,  10,   7, 1.0)   30%,
      rgba(12,  10,   7, 1.0)   96%,
      transparent               100%);
}

/* ── Floor ──────────────────────────────────────────────────────────────── */
/* The floor is built from two layers:
 *   1. .room__floor-surface  — perspective-transformed inner plane, holds the
 *      plank pattern and shifts with --floor-offset when the viewer scrolls.
 *   2. .room__baseboard      — absolute overlay at top:0, not transformed.
 *
 * .room__floor is now position:absolute inside .room__wall-area, appearing
 * below the scrolling wall content. Floor addons are inline flex items in
 * .room__wall — they scroll natively with artwork and paint above the floor.
 *
 * overflow: hidden clips the oversized surface element to the visible floor area.
 * The floor-style class (e.g. floor-hardwood1) is set on .room__floor from JS.
 */

.room__floor {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: var(--floor-h, 25vh);
  overflow: hidden;
}

/* Perspective-transformed floor surface.
 * height: 200% extends the element to 50vh so the near edge (viewer's feet)
 * fills the full 25vh container after rotateX compression.
 * left/right: -100% widens the element to 300vw so the pattern continues
 * horizontally through large scroll offsets without visible edge gaps.
 * transform-origin: top center pins the far edge (wall junction) in place.
 * background-position-x: var(--floor-offset) shifts the plank lines.
 */
.room__floor-surface {
  position: absolute;
  top: 0;
  left: -100%;
  right: -100%;
  height: 200%;
  transform-origin: top center;
  transform: perspective(600px) rotateX(65deg);
  background-position-x: var(--floor-offset, 0px);
}

/* ── hardwood1 ─────────────────────────────────────────────────────────── */
/* Simple perpendicular-plank floor, warm honey-oak, pure CSS gradients.
 *
 * Plank orientation: perpendicular to wall (running toward viewer).
 * In the flat pre-transform space, planks are vertical strips separated by
 * thin dark vertical lines every 120 px (≈ 4" wide boards in scale).
 * After perspective(600px) rotateX(65deg):
 *   – the vertical lines converge toward the wall vanishing point → 1-point perspective
 *   – shifting background-position-x scrolls the lines left/right → walking motion
 *
 * Two CSS layers:
 *   background-color + background-image on .room__floor-surface
 *     → plank joints, shift with --floor-offset
 *   ::after on .room__floor-surface
 *     → static depth/lighting overlay, not affected by scroll offset
 */

.floor-hardwood1 .room__floor-surface {
  background-color: #9A7228;            /* warm honey-oak base */
  background-image: repeating-linear-gradient(
    to right,
    transparent          0px,
    transparent          34px,
    rgba(0, 0, 0, 0.30)  34px,
    rgba(0, 0, 0, 0.30)  36px           /* 2px plank side joint; period = 36px ≈ 30% of original 120px */
  );
}

/* Depth and lighting — separate from the scrolling pattern.
 * In the perspective view:
 *   top of element  = far (near wall)  → lighter, catches warm ceiling light
 *   bottom of element = near (viewer)  → darker, deeper shadow
 */
.floor-hardwood1 .room__floor-surface::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* Warm spotlight spill from baseboard/wall junction */
    linear-gradient(to bottom,
      rgba(255, 200, 80, 0.22)  0%,
      rgba(255, 200, 80, 0.06)  30%,
      transparent               55%
    ),
    /* Depth: lighter near wall (ceiling light bounce), darker near viewer */
    linear-gradient(to bottom,
      rgba(210, 165, 60, 0.28)  0%,
      rgba(100,  65, 10, 0.12)  45%,
      rgba(0,    0,  0, 0.30)  100%
    );
}

/* ── hardwood2 ─────────────────────────────────────────────────────────── */
/* True brick-stagger hardwood: each column's plank-end joints are offset by
 * half a plank length (≈180px) from its neighbour — classic running-bond pattern.
 *
 * Plank width: half of hardwood1 → 18px period (16px face + 2px side joint).
 * Plank length: 360px per column; adjacent columns staggered by 180px.
 *
 * CSS repeating-linear-gradient cannot restrict horizontal marks to specific
 * X columns, so true per-column stagger is impossible with gradients alone.
 * Solution: SVG data-URI tile (36px × 360px) encodes both vertical side joints
 * and the per-column staggered end joints in a single repeatable image.
 *
 * Tile anatomy (36px = 2 plank widths, one full stagger cycle):
 *   Plank 1  x  0–16px  →  4 end joints at y = 0, 90, 180, 270  (~88px segments, split in 4)
 *   Plank 2  x 18–34px  →  3 end joints at y = 45, 165, 285     (~118px segments, split in 3)
 *   Minimum spacing between adjacent-column joints: 45px.
 *
 * The entire tile (side joints + end joints) shifts with background-position-x
 * = --floor-offset — physically correct: the whole floor moves as you walk.
 */

.floor-hardwood2 .room__floor-surface {
  background-color: #9A7228;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='360'%3E%3Crect x='16' y='0' width='2' height='360' fill='%23000' fill-opacity='0.30'/%3E%3Crect x='34' y='0' width='2' height='360' fill='%23000' fill-opacity='0.30'/%3E%3Crect x='0' y='0' width='18' height='3' fill='%23000' fill-opacity='0.28'/%3E%3Crect x='0' y='90' width='18' height='3' fill='%23000' fill-opacity='0.28'/%3E%3Crect x='0' y='180' width='18' height='3' fill='%23000' fill-opacity='0.28'/%3E%3Crect x='0' y='270' width='18' height='3' fill='%23000' fill-opacity='0.28'/%3E%3Crect x='18' y='45' width='18' height='3' fill='%23000' fill-opacity='0.28'/%3E%3Crect x='18' y='165' width='18' height='3' fill='%23000' fill-opacity='0.28'/%3E%3Crect x='18' y='285' width='18' height='3' fill='%23000' fill-opacity='0.28'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 36px 360px;
}

.floor-hardwood2 .room__floor-surface::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to bottom,
      rgba(255, 200, 80, 0.22)  0%,
      rgba(255, 200, 80, 0.06)  30%,
      transparent               55%
    ),
    linear-gradient(to bottom,
      rgba(210, 165, 60, 0.28)  0%,
      rgba(100,  65, 10, 0.12)  45%,
      rgba(0,    0,  0, 0.30)  100%
    );
}

/* ---------------------------------------------------------------------------
 * hardwood3 — Photorealistic wood-grain texture (Polyhaven wood_floor, CC0)
 *
 * The 1k diffuse map (1024×1024 px) tiles at 512×512 to show ~2–3 plank
 * widths per tile, giving visible grain without the texture feeling tiny.
 * background-position-x: var(--floor-offset) scrolls the tile in sync with
 * the wall — same physical-scroll illusion as hardwood1/2.
 *
 * ::after overlay is identical to hardwood1/2: warm amber top-light fading
 * to dark near-viewer shadow.
 */

.floor-hardwood3 .room__floor-surface {
  background-color: #9A7228;
  background-image: url('../assets/images/textures/wood_floor_diff_1k.jpg');
  background-repeat: repeat;
  background-size: 256px 256px;
}

.floor-hardwood3 .room__floor-surface::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to bottom,
      rgba(255, 200, 80, 0.22)  0%,
      rgba(255, 200, 80, 0.06)  30%,
      transparent               55%
    ),
    linear-gradient(to bottom,
      rgba(210, 165, 60, 0.28)  0%,
      rgba(100,  65, 10, 0.12)  45%,
      rgba(0,    0,  0, 0.30)  100%
    );
}

/* ── stone1 ────────────────────────────────────────────────────────────── */
/* Large square stone tiles — photorealistic texture (Polyhaven large_grey_tiles, CC0).
 *
 * The 1k diffuse map (1024×1024 px) tiles at 320×320 so each tile spans
 * roughly one large stone slab. After perspective(600px) rotateX(65deg)
 * the squares foreshorten into wide rectangles — realistic for polished
 * stone or large-format porcelain floor tiles.
 *
 * background-position-x: var(--floor-offset) scrolls in sync with the wall
 * for the physical-scroll parallax illusion (same technique as hardwood3).
 *
 * ::after overlay: cool blue-white light spill near wall + depth shadow
 * near viewer (cooler tone than hardwood — stone reflects indirect light).
 */

.floor-stone1 .room__floor-surface {
  background-color: #7a7870;
  background-image: url('../assets/images/textures/large_grey_tiles_diff_1k.jpg');
  background-repeat: repeat;
  background-size: 320px 320px;
}

.floor-stone1 .room__floor-surface::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* Cool blue-white light spill from wall/baseboard junction */
    linear-gradient(to bottom,
      rgba(200, 210, 225, 0.20)   0%,
      rgba(200, 210, 225, 0.07)   28%,
      transparent                 52%
    ),
    /* Depth: lighter near wall, darker near viewer */
    linear-gradient(to bottom,
      rgba(160, 155, 145, 0.22)   0%,
      rgba(60,  55,  50,  0.12)   45%,
      rgba(0,   0,   0,   0.38)  100%
    );
}

/* ── stone2 ────────────────────────────────────────────────────────────── */
/* Granite tile floor — photorealistic texture (Polyhaven granite_tile_02, CC0).
 *
 * Tiled at 480×480 px so each tile reads as a large polished granite slab.
 * Cooler overlay lets the stone grain show — elegant and gallery-appropriate.
 *
 * background-position-x: var(--floor-offset) scrolls in sync with the wall.
 */

.floor-stone2 .room__floor-surface {
  background-color: #8a8278;
  background-image: url('../assets/images/textures/granite_tile_02_diff_1k.jpg');
  background-repeat: repeat;
  background-size: 480px 480px;
}

.floor-stone2 .room__floor-surface::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* Soft cool reflection from wall near baseboard */
    linear-gradient(to bottom,
      rgba(220, 225, 235, 0.18)   0%,
      rgba(220, 225, 235, 0.05)   25%,
      transparent                 48%
    ),
    /* Depth: very subtle fade to black near viewer */
    linear-gradient(to bottom,
      rgba(180, 175, 165, 0.15)   0%,
      rgba(40,  38,  35,  0.08)   50%,
      rgba(0,   0,   0,   0.30)  100%
    );
}

/* Baseboard — painted moulding at the wall/floor junction.
 * Rendered only when room data specifies a baseboard_style.
 * Style variants added here as rooms.csv introduces new values.
 */
.room__baseboard {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}

/* simple — same wall colour, separated by a dark groove + bright highlight.
 *
 * Profile (top → bottom):
 *   1.5 px  bright line   top edge catching overhead light (visible specular)
 *   5.0 px  dark groove   wall-tinted shadow at the junction (depth illusion)
 *   28.5px  face          same colour as the wall
 *
 * background-color is set separately so the element is fully opaque — this
 * blocks the floor gradient (amber + herringbone) from bleeding through the
 * semi-transparent gradient stops above it.
 */
.room__baseboard.baseboard-simple {
  height: 35px;
  background-color: var(--wall-color, #0a0a0c); /* opaque base — floor cannot bleed through */
  background-image: linear-gradient(to bottom,
    rgba(255, 255, 255, 0.55)  0px,     /* bright highlight line */
    rgba(255, 255, 255, 0.55)  1.5px,
    rgba(0,   0,   0,   0.28)  1.5px,   /* wall-tinted dark groove */
    rgba(0,   0,   0,   0.28)  6.5px,
    transparent                6.5px,   /* face — background-color (wall) shows through */
    transparent                100%
  );
}

/* ── Wall area: the scrollable horizontal strip ───────────────────────────── */

.room__wall-area {
  flex: 1;
  min-height: 0;
  overflow-x: clip;    /* clip horizontal overflow (frame shadows etc.); 'clip' allows overflow-y:visible */
  overflow-y: visible;
  position: relative;
}

/* ── Wall textures ─────────────────────────────────────────────────────────
 * Applied as background-image directly on .room__wall (the scrolling element).
 *
 * background-attachment: local  → texture scrolls with the artwork ✓
 * background-color: var(--wall-color) + background-blend-mode: soft-light
 *   → blends the texture with the per-room wall colour.
 *   soft-light: bright texture pixels lighten the wall, dark pixels darken it.
 *   Works on both dark and light walls — visible contrast in both cases.
 *   background-blend-mode affects ONLY the background layers of .room__wall,
 *   never the foreground artwork children. ✓
 *
 * Textures: Polyhaven CC0 plaster/wall 1k diffuse maps.
 */

[class*="wall-texture-"] .room__wall {
  background-color: var(--wall-color);
  background-attachment: local;
  background-repeat: repeat;
  background-blend-mode: soft-light;
}

/* plaster1 — smooth beige painted plaster (Polyhaven beige_wall_001) */
.wall-texture-plaster1 .room__wall {
  background-image: url('../assets/images/textures/beige_wall_001_diff_1k.jpg');
  background-size: 512px 512px;
}

/* plaster2 — grey rough plaster, more visible grain (Polyhaven grey_plaster_02) */
.wall-texture-plaster2 .room__wall {
  background-image: url('../assets/images/textures/grey_plaster_02_diff_1k.jpg');
  background-size: 480px 480px;
}

/* plaster3 — medium plastered wall (Polyhaven plastered_wall_02) */
.wall-texture-plaster3 .room__wall {
  background-image: url('../assets/images/textures/plastered_wall_02_diff_1k.jpg');
  background-size: 496px 496px;
}

.room__wall {
  display: flex;
  flex-direction: row;
  align-items: center;        /* eye-level hanging — centers in content zone (wall-h minus floor padding) */
  height: 100%;
  overflow-x: scroll;
  /* padding-bottom = floor height: creates a floor zone within the scroll container.
   * Floor addons use align-self:flex-end + negative margin-bottom to sit in this zone.
   * overflow-x:scroll coerces overflow-y to auto, which is fine — nothing overflows vertically. */
  padding-bottom: var(--floor-h, 25vh);
  padding-left: 12vw;
  padding-right: 12vw;
  gap: clamp(80px, 6vw, 140px);
  scrollbar-width: none;      /* Firefox */
  -ms-overflow-style: none;   /* IE/Edge */
}

.room__wall::-webkit-scrollbar {
  display: none;              /* Chrome/Safari */
}

/* ── Artwork piece ────────────────────────────────────────────────────────── */

.artwork-piece {
  position: relative;
  flex-shrink: 0;
  width: var(--art-w);
  cursor: pointer;
  outline: none;
  /* Per-piece vertical offset on the wall.
     --wall-y is a number (no unit); 1 unit = 1vh.
     Positive = shift down, negative = shift up. Default 0 = centered.
     Example: wall_y=10 moves the piece 10vh below center. */
  top: calc(var(--wall-y, 0) * 1vh);

  /* Entrance animation — stagger via inline style or default */
  opacity: 0;
  animation: pieceReveal 0.7s var(--ease-out-expo, cubic-bezier(0.16,1,0.3,1)) forwards;
  animation-delay: calc(var(--piece-index, 0) * 0.06s + 0.1s);
}

@keyframes pieceReveal {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0);    }
}

/* ── Spotlight on each piece ──────────────────────────────────────────────── */
/*
 * Centred on the artwork frame. The div is proportional (art-w × art-h × mult)
 * so it never extends outside the artwork's bounding box — no visible edges.
 *
 * Halo fix: use `circle` (not `ellipse`) so the gradient is always round in
 * screen coordinates regardless of the artwork's aspect ratio. A landscape
 * piece's spotlight stays circular, not stretched to match the wide frame.
 *
 * Ceiling-light feel: hotspot biased slightly upward to 50% 40% (above center)
 * so light concentrates on the upper part of the painting and fades toward the
 * bottom — as if a track light above is casting down.
 *
 * transparent at 70%: ensures the glow fades out well before the div corners
 * so no bright artefacts appear at the edges for any aspect ratio.
 *
 * Size + brightness driven by data-spotlight-size / data-spotlight-brightness
 * on the parent .artwork-piece element (set by room.js from CSV or hash).
 *
 * Size 1 = 0.9× (focused, barely beyond frame)
 * Size 2 = 1.3× (modest wall bleed — default)
 * Size 3 = 1.8× (generous flood)
 * Brightness 1 = dim · 2 = normal · 3 = punchy
 */
.artwork-piece__spotlight {
  position: absolute;
  top: calc(var(--art-h) / 2);
  left: 50%;
  transform: translate(-50%, -50%);
  /* Defaults (size 2 / brightness 2) — overridden by data-attribute rules below */
  width:  calc(var(--art-w) * var(--spot-mult, 1.3));
  height: calc(var(--art-h) * var(--spot-mult, 1.3));
  pointer-events: none;
  z-index: 0;
  background: radial-gradient(circle at 50% 40%,
    rgba(255,255,255,var(--spot-peak, 0.60)) 0%,
    rgba(255,255,255,var(--spot-mid,  0.22)) 40%,
    transparent 70%);
  opacity: var(--spot-opacity, 0.90);
  /* blur erases all gradient edge artefacts regardless of aspect ratio */
  filter: brightness(1) blur(20px);
  transition: opacity 0.4s ease, filter 0.4s ease;
}

.artwork-piece:hover .artwork-piece__spotlight {
  opacity: 1;
  filter: brightness(1.35) blur(20px);
}

/* Size variants */
[data-spotlight-size="1"] .artwork-piece__spotlight { --spot-mult: 0.9; }
[data-spotlight-size="2"] .artwork-piece__spotlight { --spot-mult: 1.3; }
[data-spotlight-size="3"] .artwork-piece__spotlight { --spot-mult: 1.8; }

/* Brightness variants — gradient peak + mid + opacity all scale together */
[data-spotlight-brightness="1"] .artwork-piece__spotlight {
  --spot-peak: 0.30; --spot-mid: 0.10; --spot-opacity: 0.70;
}
[data-spotlight-brightness="2"] .artwork-piece__spotlight {
  --spot-peak: 0.60; --spot-mid: 0.22; --spot-opacity: 0.90;
}
[data-spotlight-brightness="3"] .artwork-piece__spotlight {
  --spot-peak: 0.92; --spot-mid: 0.38; --spot-opacity: 1.00;
}

/* ── Frame base ───────────────────────────────────────────────────────────── */
/*
 * Frame styles are driven by the CSS class on .artwork-piece:
 *   .frame-gallery-wrap  — no visible frame; canvas-wrap depth illusion (default)
 *   .frame-float         — thin blonde-wood frame, black void between canvas + wood
 *   .frame-mat           — wide mat board + amber bevel + dark walnut frame (paper/prints)
 *   .frame-ornate        — (future) gold/metallic ornate frame
 *
 * Shadow uses 0 x-offset on all styles — light source is directly overhead,
 * so shadow falls straight down. Heavy below, minimal on sides, none on top.
 */

.artwork-piece__frame {
  position: relative;
  z-index: 1;
  width: var(--art-w);
  height: var(--art-h);
  transition: box-shadow 0.4s ease;
}

/* ── Gallery wrap (default — canvas with no external frame) ───────────────── */
/*
 * Simulates the look of a gallery-wrapped canvas:
 * the image continues around the stretcher bars, so the visible face
 * has no frame — just the painting, slightly lifted off the wall.
 * Inset shadows darken the right and bottom edges (canvas wrap depth).
 */
.frame-gallery-wrap .artwork-piece__frame {
  box-shadow:
    inset -4px 0 8px  rgba(0, 0, 0, 0.40),   /* right-edge canvas depth */
    inset  0 -4px 8px rgba(0, 0, 0, 0.25),   /* bottom-edge canvas depth */
    0 8px 4px 2px rgba(0, 0, 0, 0.90);       /* downward wall shadow; spread 2px fills canvas edges */
}

.artwork-piece.frame-gallery-wrap:hover .artwork-piece__frame {
  box-shadow:
    inset -4px 0 8px  rgba(0, 0, 0, 0.35),
    inset  0 -4px 8px rgba(0, 0, 0, 0.20),
    0 10px 5px 2px rgba(0, 0, 0, 0.95);
}

/* ── Floating frame (canvas with thin blonde-wood float frame) ────────────── */
/*
 * A thin natural/blonde wood frame surrounds the canvas with a deliberate
 * black void (gap) between the canvas edge and the wood — the "floating" effect.
 * 5px void + 3px wood = 8px total spread.
 * No hard outer ring — the wood fades into the wall naturally.
 */
.frame-float .artwork-piece__frame {
  box-shadow:
    0 0 0 5px #0a0806,                        /* black void between canvas and frame */
    0 0 0 8px #c4a06a,                        /* blonde/natural wood, 3px wide */
    0 8px 5px 9px rgba(0, 0, 0, 0.92);       /* cast shadow; 9px spread covers full frame width */
}

.artwork-piece.frame-float:hover .artwork-piece__frame {
  box-shadow:
    0 0 0 5px #0a0806,
    0 0 0 8px #d4b07a,                        /* wood brightens on hover */
    0 10px 6px 9px rgba(0, 0, 0, 0.96);
}

/* ── Dark float frame (same void/wood structure as float, espresso brown) ──── */
/*
 * Identical geometry to frame-float (5px void + 3px wood = 8px, spread 9px)
 * but dark espresso/mahogany wood instead of blonde — suits darker moods.
 */
.frame-float-dark .artwork-piece__frame {
  box-shadow:
    0 0 0 5px #0a0806,                        /* black void between canvas and frame */
    0 0 0 8px #7a3820,                        /* dark walnut wood, 3px wide */
    0 8px 5px 9px rgba(0, 0, 0, 0.92);       /* cast shadow; 9px spread covers full frame width */
}

.artwork-piece.frame-float-dark:hover .artwork-piece__frame {
  box-shadow:
    0 0 0 5px #0a0806,
    0 0 0 8px #924428,                        /* wood warms slightly on hover */
    0 10px 6px 9px rgba(0, 0, 0, 0.96);
}

/* ── Mat frame (paper works — wide mat + amber bevel + walnut frame) ──────── */
.frame-mat .artwork-piece__frame {
  box-shadow:
    0 0 0  1px rgba(255, 210, 130, 0.12),     /* faint warm glow at art edge */
    0 0 0 14px var(--wall-color, #0a0a0c),    /* mat board — matches wall */
    0 0 0 15px #6b4418,                       /* 1px amber bevel */
    0 0 0 26px #2e1e0c,                       /* dark walnut frame face, 11px */
    0 0 0 27px #0c0705,                       /* outer edge */
    0 12px 7px 27px rgba(0, 0, 0, 0.90);     /* cast shadow; spread 27px = frame spread; small y keeps it close like float */
}

.artwork-piece.frame-mat:hover .artwork-piece__frame {
  box-shadow:
    0 0 0  1px rgba(255, 220, 140, 0.20),
    0 0 0 14px var(--wall-color, #0a0a0c),
    0 0 0 15px #8a5822,                       /* bevel brightens */
    0 0 0 26px #3e2814,                       /* frame face warms */
    0 0 0 27px #0c0705,
    0 14px 9px 27px rgba(0, 0, 0, 0.95);
}

/* ── No frame (triptychs / merged PNGs — zero shadow, zero treatment) ───────── */
/*
 * Use frame_style=none for any artwork that manages its own visual presentation:
 * triptychs merged into a single PNG, pieces with baked-in shadows or borders,
 * or any work where CSS shadow would look wrong applied to a rectangular box.
 */
.frame-none .artwork-piece__frame,
.artwork-piece.frame-none:hover .artwork-piece__frame {
  box-shadow: none;
}

/* ── Artwork image / video ────────────────────────────────────────────────── */

.artwork-piece__img {
  display: block;
  width: var(--art-w);
  height: var(--art-h);
  object-fit: fill; /* fill = no crop, no letterbox; width/height are set from real inch dimensions */
  filter: brightness(0.90);
  transition: filter 0.45s ease;
}

.artwork-piece:hover .artwork-piece__img {
  filter: brightness(1.0);
}

/* ── Placeholder (no image) ───────────────────────────────────────────────── */

.artwork-piece__img--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-sm, 12px);
  filter: none;
}

.artwork-piece__placeholder-title {
  font-family: var(--font-display);
  font-size: var(--font-size-small, 0.75rem);
  font-weight: 300;
  color: var(--color-text-muted, #6b6560);
  opacity: 0.35;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* ── Label (title + meta — hover reveal) ─────────────────────────────────── */

.artwork-piece__label {
  position: relative;
  z-index: 2;
  margin-top: 14px;   /* gallery-wrap default: no frame shadow to clear */
  text-align: center;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.35s ease, transform 0.35s ease;
  pointer-events: none;
}

.artwork-piece:hover .artwork-piece__label {
  opacity: 1;
  transform: translateY(0);
}

.artwork-piece__title {
  display: block;
  font-family: var(--font-display, 'Cormorant Garamond', serif);
  font-size: var(--font-size-artwork-title, 1rem);
  font-weight: 400;
  color: var(--color-text-primary, #e8e4df);
  letter-spacing: 0.04em;
  line-height: 1.3;
}

.artwork-piece__meta {
  display: block;
  font-family: var(--font-body, 'Raleway', sans-serif);
  font-size: var(--font-size-small, 0.75rem);
  font-weight: 300;
  color: var(--color-text-muted, #6b6560);
  margin-top: 5px;
  letter-spacing: 0.06em;
}

/* Per-frame-style label clearance */
.artwork-piece.frame-float      .artwork-piece__label { margin-top: 17px; }
.artwork-piece.frame-float-dark .artwork-piece__label { margin-top: 17px; }
.artwork-piece.frame-mat        .artwork-piece__label { margin-top: 34px; }

/* Per-frame-style horizontal spacing compensation
 * CSS box-shadow has no effect on layout — the mat frame's 27px spread
 * bleeds into the adjacent piece's territory regardless of flex gap.
 * Adding an equal margin pushes the element boxes apart so the visible
 * gap between framed edges stays equal to the flex gap value.
 *
 *   Visual gap = flex gap + (margin × 2) − (shadow spread × 2)
 *   Mat:   gap + 54px − 54px = gap  ✓  (at every breakpoint)
 *   Float: 14px spread is fine at 80px+ gap; no compensation needed.
 *
 * Groups do NOT need this: their layout box already includes the outer frame
 * spread via CSS grid padding (--frame-spread), so their visual and layout
 * widths match exactly.
 */
.room__wall > .artwork-piece.frame-mat {
  margin-left:  27px;
  margin-right: 27px;
}

/* Story dot indicator */
.artwork-piece__story-dot {
  display: inline-block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--color-text-accent, #c9b99a);
  margin-left: 7px;
  vertical-align: middle;
  opacity: 0.7;
}

/* Focus ring (keyboard navigation) — added as outline; works for all frame styles */
.artwork-piece:focus-visible {
  outline: 2px solid rgba(200, 180, 130, 0.55);
  outline-offset: 4px;
}

/* ── End-of-room cap ──────────────────────────────────────────────────────── */

.room__wall-end {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 4vw;
  min-width: 200px;
}

.room__exit-btn {
  font-family: var(--font-body, 'Raleway', sans-serif);
  font-size: var(--font-size-small, 0.75rem);
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-text-muted, #6b6560);
  background: none;
  border: 1px solid rgba(255, 200, 120, 0.12);
  border-radius: 2px;
  padding: 12px 24px;
  cursor: pointer;
  transition: color 0.3s ease, border-color 0.3s ease;
  white-space: nowrap;
}

.room__exit-btn:hover {
  color: var(--color-text-accent, #c9b99a);
  border-color: rgba(255, 200, 120, 0.3);
}

/* ── Fixed overlay: back button + room title ─────────────────────────────── */

.room__overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-nav, 100);
  display: flex;
  align-items: center;
  gap: 2rem;
  padding: 1.4rem 2rem;
  background: linear-gradient(to bottom,
    rgba(3, 3, 4, 0.85) 0%,
    transparent 100%
  );
  pointer-events: none;
}

.room__back {
  pointer-events: all;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body, 'Raleway', sans-serif);
  font-size: var(--font-size-small, 0.75rem);
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-muted, #6b6560);
  background: none;
  border: none;
  cursor: pointer;
  transition: color 0.25s ease;
  padding: 0;
}

.room__back:hover {
  color: var(--color-text-accent, #c9b99a);
}

.room__title-block {
  flex: 1;
  text-align: center;
}

.room__title {
  font-family: var(--font-display, 'Cormorant Garamond', serif);
  font-size: var(--font-size-room-title, 1.6rem);
  font-weight: 300;
  letter-spacing: var(--letter-spacing-wide, 0.18em);
  text-transform: uppercase;
  color: var(--color-text-primary, #e8e4df);
  margin: 0;
  line-height: 1.2;
}

.room__subtitle {
  font-family: var(--font-body, 'Raleway', sans-serif);
  font-size: var(--font-size-small, 0.75rem);
  font-weight: 300;
  color: var(--color-text-muted, #6b6560);
  letter-spacing: 0.1em;
  margin: 4px 0 0;
}

/* ── On-screen navigation arrows ─────────────────────────────────────────── */

.room__nav {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: var(--z-nav, 100);
  font-size: 3rem;
  line-height: 1;
  color: var(--color-text-muted, #6b6560);
  background: none;
  border: none;
  cursor: pointer;
  padding: 1rem 1.2rem;
  transition: color 0.25s ease, opacity 0.35s ease;
  user-select: none;
}

.room__nav--prev { left: 0.5rem; }
.room__nav--next { right: 0.5rem; }

.room__nav:hover {
  color: var(--color-text-primary, #e8e4df);
}

.room__nav.is-hidden {
  opacity: 0;
  pointer-events: none;
}

/* ── Responsive ───────────────────────────────────────────────────────────── */

@media (max-width: 768px) {
  .room__wall {
    padding-left: 8vw;
    padding-right: 8vw;
    gap: clamp(60px, 5vw, 100px);
  }

  .room__nav {
    font-size: 2.2rem;
    padding: 0.8rem;
  }

  .room__overlay {
    padding: 1rem 1.2rem;
    gap: 1rem;
  }

  .room__title {
    font-size: 1.2rem;
  }

  .room__ceiling { height: 10vh; }
  :root { --floor-h: 14vh; }  /* .room__floor uses var(--floor-h), auto-updates */
}

@media (max-width: 480px) {
  .room__wall {
    padding: 0 6vw;
    gap: clamp(50px, 5vw, 80px);
  }

  .room__title-block { display: none; }
}

/* ── Artwork group (multi-piece grid on the wall) ────────────────────────── */
/*
 * The group div is a pure positioning container — no outer frame of its own.
 * Each cell is a full .artwork-piece with its own frame, hover label, and click.
 *
 * Layout geometry (all set as CSS vars from JS):
 *   --group-cols   number of grid columns
 *   --cell-w/h     cell dimensions (already scaled to fit wall if multi-row)
 *   --cell-gap     gap between cells; large enough to clear adjacent frame rings
 *   --frame-spread how far each cell's frame shadow extends outside its box
 *   --group-w/h    total visual footprint (cells + gaps + 2 × frame-spread)
 *   --wall-y       fine vertical offset (same as standalone pieces)
 *
 * The grid has padding = --frame-spread on all sides, so the outermost cells'
 * frame shadows are contained within the group's layout box.  No extra margin
 * compensation needed for wall-level flex gap.
 */

.artwork-group {
  flex-shrink: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  top: calc(var(--wall-y, 0) * 1vh);

  opacity: 0;
  animation: pieceReveal 0.7s var(--ease-out-expo, cubic-bezier(0.16,1,0.3,1)) forwards;
  animation-delay: 0.1s;
}

/* Shared spotlight on the whole group — proportional div, circle gradient,
 * hotspot at 50% 40% (mild ceiling-light feel), blur(20px) removes all edges */
.artwork-group__spotlight {
  position: absolute;
  top: calc(var(--group-h) / 2);
  left: 50%;
  transform: translate(-50%, -50%);
  width:  calc(var(--group-w) * var(--spot-mult, 1.3));
  height: calc(var(--group-h) * var(--spot-mult, 1.3));
  pointer-events: none;
  z-index: 0;
  background: radial-gradient(circle at 50% 40%,
    rgba(255,255,255,var(--spot-peak, 0.60)) 0%,
    rgba(255,255,255,var(--spot-mid,  0.22)) 40%,
    transparent 70%);
  opacity: var(--spot-opacity, 0.90);
  filter: brightness(1) blur(20px);
  transition: opacity 0.4s ease, filter 0.4s ease;
}

.artwork-group:hover .artwork-group__spotlight {
  opacity: 1;
  filter: brightness(1.35) blur(20px);
}

/* Size variants */
[data-spotlight-size="1"] .artwork-group__spotlight { --spot-mult: 0.9; }
[data-spotlight-size="2"] .artwork-group__spotlight { --spot-mult: 1.3; }
[data-spotlight-size="3"] .artwork-group__spotlight { --spot-mult: 1.8; }

/* Brightness variants */
[data-spotlight-brightness="1"] .artwork-group__spotlight {
  --spot-peak: 0.30; --spot-mid: 0.10; --spot-opacity: 0.70;
}
[data-spotlight-brightness="2"] .artwork-group__spotlight {
  --spot-peak: 0.60; --spot-mid: 0.22; --spot-opacity: 0.90;
}
[data-spotlight-brightness="3"] .artwork-group__spotlight {
  --spot-peak: 0.92; --spot-mid: 0.38; --spot-opacity: 1.00;
}

/* Grid of individually-framed cells.
 * padding = --frame-spread provides room for outer-edge cell frame shadows.
 * No background needed — the wall color shows through the gaps. */
.artwork-group__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(var(--group-cols, 2), var(--cell-w, 80px));
  grid-auto-rows: var(--cell-h, 80px);
  gap: var(--cell-gap, 16px);
  padding: var(--frame-spread, 0px);
}

/* Individual cell — a full artwork-piece sitting inside the grid.
 * overflow must be visible so frame shadows can extend into the padding zone. */
.artwork-group__cell {
  overflow: visible;
  /* All frame styles, hover effects, and label reveal come from the shared
     .artwork-piece / .frame-* rules — no overrides needed here.
     Suppress the entrance animation: the group container already animates in. */
  animation: none !important;
  opacity: 1 !important;
}

/* Group hover label — shown below the grid on any cell hover */
.artwork-group__label {
  margin-top: 16px;
  text-align: center;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.35s ease, transform 0.35s ease;
  pointer-events: none;
}

.artwork-group:hover .artwork-group__label {
  opacity: 1;
  transform: translateY(0);
}

/* ── Reduced motion ───────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .artwork-piece {
    animation: none;
    opacity: 1;
  }

  .artwork-piece__label,
  .artwork-piece__spotlight {
    transition: none;
  }
}

/* ── Room addon (decorative props: plants, benches, doors) ────────────────── */
/*
 * Non-clickable props placed on the wall via card_type=addon in the CSV.
 * No frame, no label — just the PNG with transparency.
 *
 * ALL addons (floor / wall / ceiling) are inline flex items in .room__wall —
 * they scroll 1:1 with artwork. align-self and transform set via inline style.
 *
 * Entrance animation matches artwork pieces so the whole wall reveals together.
 */

.room-addon {
  position: relative;
  flex-shrink: 0;
  /* align-self set inline via renderAddon() */
  top: calc(var(--wall-y, 0) * 1vh);
  overflow: visible;

  /* Same entrance animation as artwork pieces */
  opacity: 0;
  animation: pieceReveal 0.7s var(--ease-out-expo, cubic-bezier(0.16,1,0.3,1)) forwards;
  animation-delay: calc(var(--piece-index, 0) * 0.06s + 0.1s);
}

/* Floor shadow — CSS drop-shadow traces the PNG alpha channel.
 * Positive Y casts the shadow downward onto the visible floor surface in front
 * of the addon (between addon and viewer). Works for all floor_depth > 0 addons.
 * Depth=0 addons sit at the very front edge where no floor is visible below
 * them anyway, so clipping there is visually irrelevant. */
.room-addon.has-shadow img {
  filter: drop-shadow(0 15px 22px rgba(0, 0, 0, 0.65));
}

/* Ceiling spotlight — radial gradient at the top of the addon element.
 * Simulates an overhead light source hitting the top of the object
 * and bleeding onto the surrounding wall.
 * Brightness tiers mirror artwork spotlight levels (1=dim, 2=normal, 3=punchy). */
/* Addon spotlight — real child div (mirrors .artwork-piece__spotlight approach).
 * Positioned above the addon and sized to bleed onto the wall. z-index:1 ensures
 * it renders above the display:block img (z-index:0 lands behind it). */
.room-addon__spotlight {
  position: absolute;
  width: 200%;
  height: 150%;
  left: 50%;
  top: -30%;
  transform: translateX(-50%);
  pointer-events: none;
  z-index: 1;
  background: radial-gradient(ellipse 50% 45% at 50% 40%,
    rgba(255, 200, 120, var(--addon-spot-peak, 0.32)),
    transparent 70%
  );
  filter: blur(22px);
}

[data-spotlight-brightness="1"] .room-addon__spotlight { --addon-spot-peak: 0.18; }
[data-spotlight-brightness="2"] .room-addon__spotlight { --addon-spot-peak: 0.32; }
[data-spotlight-brightness="3"] .room-addon__spotlight { --addon-spot-peak: 0.52; }

/* ── Floor addon alignment ───────────────────────────────────────────────── */
/* Floor addons are inline flex items in .room__wall.                         */
/* align-self:flex-end puts bottom at the wall-floor junction (start of the   */
/* padding-bottom zone). margin-bottom (set inline by JS) pushes the addon    */
/* down into the padding zone: margin-bottom = depth - floor-h (always ≤ 0). */
/* No JS sync required — addons scroll natively with artwork.                 */
.room-addon--floor {
  align-self: flex-end;
}

/* ── Parallax door / window (addon with scenery background) ─────────────── */
/* The container uses background-image (the outdoor scenery JPG).            */
/* background-position-x updated each scroll tick via JS syncParallax(),     */
/* shifting the crop slightly to simulate a distant view through the glass.  */
/* The door PNG foreground (with alpha) sits on top — transparent glass      */
/* panes reveal the scenery beneath, opaque frame hides it.                  */
.room-addon--parallax {
  background-size: auto 100%;      /* natural tile width, full element height   */
  background-position: 50% 50%;   /* centered at rest; JS overrides bgPosX     */
  background-repeat: repeat-x;    /* tile horizontally so large offsets wrap    */
  overflow: hidden;                /* clip scenery to the door's bounding box   */
}
.room-addon--parallax img {
  position: relative;              /* stacks on top of background-image in DOM  */
  z-index: 1;
}
