/* =========================================================
   DESIGN TOKENS
   A warm study, three years in. Walnut, brass, lamp-glow.
   Edit these variables to re-theme the entire site.
========================================================= */
:root{
  --color-wall: #2b1d14;
  --color-wall-dark: #1c1209;
  --color-shelf-wood: #4a2f1a;
  --color-desk: #402615;
  --color-brass: #c9a15a;
  --color-brass-bright: #e6c789;
  --color-parchment: #ede0c4;
  --color-parchment-dark: #d8c69f;
  --color-ink: #241a12;
  --color-window-glow: #f7c876;
  --color-leather-green: #1f3324;
  --color-danger: #8a3a2e;

  /* Wood texture used by the navbar and the bookshelf page background.
     By default this is drawn entirely with CSS (no image needed).

     To use a real photo of wood grain instead:
       1) Save your image as assets/wood-texture.jpg (a square, roughly
          seamless/tileable photo works best — it repeats as a tile rather
          than stretching across the whole screen, which keeps it sharp
          and keeps the grain lines a sensible size instead of huge).
       2) Change the line below from "none" to:
            url('assets/wood-texture.jpg')
       3) --wood-texture-tile controls how big each repeating tile is on
          screen. Smaller = finer/subtler grain, larger = bolder grain.
          A good starting point is roughly the photo's own pixel width,
          or a bit smaller.
     A soft dark tint sits on top of your photo so it stays moody and
     consistent with the rest of the site rather than looking too bright
     or busy. */
  --wood-texture-image: url('assets/Texture/wood_texture.jpg');
  --wood-texture-tile: 600px;
  --wood-dark: #2a1810;
  --wood-mid: #4a2f1a;

  --font-display: 'Playfair Display', Georgia, serif;
  --font-body: 'Cormorant Garamond', Georgia, serif;
  --font-mono: 'Space Mono', monospace;
  --font-ui: 'Inter', -apple-system, system-ui, sans-serif;

  /* Editor theme: a simpler, modern dark UI — separate from the warm
     wood/parchment look used for the scene, shelf, and reader, since the
     editor is a work surface rather than part of the keepsake itself. */
  --editor-bg: #000;
  --editor-panel: #151517;
  --editor-panel-raised: #1c1c1f;
  --editor-border: rgba(255,255,255,0.09);
  --editor-text: #f2ede4;
  --editor-muted: rgba(242,237,228,0.55);
  --editor-accent: #e8c789;
  --editor-accent-strong: #f0d7a3;
  --editor-danger: #ec7a6d;

  --ease: cubic-bezier(.22,.61,.36,1);
  --dur: 650ms;
  /* Deliberately NOT collapsed by the prefers-reduced-motion rule below —
     unlike view-switch fades, the page-flip is a core, expected part of
     reading the book here (this is a closed-audience personal gift, not a
     general-public site), so it keeps its own real duration regardless of
     the visitor's OS/browser motion setting. */
  --page-flip-dur: 650ms;
}

/* Reusable wood-grain background — shared by the navbar and the bookshelf
   page. The photo (or, if none is set, the CSS-drawn grain lines) tiles at
   --wood-texture-tile size rather than stretching to fill the element, so
   it stays crisp and the grain reads at a natural, subtle scale. */
.wood-grain, .panel-header, .view-shelf{
  background-color: var(--wood-dark);
  background-image:
    linear-gradient(180deg, rgba(18,10,6,0.32) 0%, rgba(18,10,6,0.58) 100%),
    var(--wood-texture-image),
    repeating-linear-gradient(91deg, rgba(0,0,0,0.22) 0px, rgba(0,0,0,0.22) 1px, transparent 1.5px, transparent 4px),
    repeating-linear-gradient(89deg, rgba(255,231,186,0.05) 0px, rgba(255,231,186,0.05) 3px, transparent 3px, transparent 70px),
    linear-gradient(180deg, var(--wood-mid) 0%, var(--wood-dark) 100%);
  background-size: 100% 100%, var(--wood-texture-tile) var(--wood-texture-tile), auto, auto, auto;
  background-repeat: no-repeat, repeat, repeat, repeat, no-repeat;
  background-blend-mode: normal, normal, overlay, soft-light, normal;
}

@media (prefers-reduced-motion: reduce){
  :root{ --dur: 1ms; }
}

*{ box-sizing: border-box; }

html, body{
  margin:0; padding:0; height:100%;
  background: var(--color-wall-dark);
  color: var(--color-parchment);
  font-family: var(--font-body);
  overflow: hidden;
}

button{ font-family: var(--font-mono); cursor:pointer; }
input, textarea, select{ font-family: var(--font-body); }

#app{
  position: relative;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
}

/* =========================================================
   VIEW SWITCHING
   Each "view" is absolutely stacked; only .is-active shows.
   The scene->shelf/desk transition zooms from the click point.
========================================================= */
.view{
  position:absolute; inset:0;
  opacity:0;
  visibility:hidden;
  pointer-events:none;
  transform: scale(1.06);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility 0s linear var(--dur);
}
.view.is-active{
  opacity:1;
  visibility:visible;
  pointer-events:auto;
  transform: scale(1);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.view.is-leaving{
  opacity:0;
  transform: scale(0.96);
}
/* The view the reader was opened from (study scene or shelf carousel) sits
   behind it, faded right down but still visible — so the other books that
   belong there (spines on the shelf, cards in the carousel) stay in view at
   the edges instead of vanishing. Non-interactive: the reader view sitting
   on top of it captures all clicks. */
.view.view-peek{
  opacity: var(--peek-opacity, 0.14);
  visibility: visible;
  pointer-events: none;
  transform: scale(1);
  transition: opacity 500ms ease;
}

/* =========================================================
   SCENE VIEW
========================================================= */
.view-scene{ background: var(--color-wall-dark); }
.scene-wrap{
  position:relative; width:100%; height:100%; overflow:hidden; background: var(--color-wall-dark);
  user-select:none; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none;
}
.scene-img{ position:absolute; inset:0; width:100%; height:100%; object-fit:contain; display:block; user-select:none; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; -webkit-user-drag:none; }
/* The crossfade layer sits on top of the base scene-img (both are
   object-fit:contain over the same box, so they line up), starting fully
   transparent. app.js fades its opacity 0->1 to change scenes, then swaps
   scene-img's own src to match and resets this back to 0 — see
   crossfadeSceneTo() in app.js. */
.scene-img-fade{ opacity:0; z-index:1; pointer-events:none; }

/* The neck pillow, while being dragged between the desk and Puma. Follows
   the pointer (app.js sets left/top/width/height directly during the
   drag) rather than living in the normal hotspot layout flow. */
.pillow-drag-sprite{
  position:absolute;
  z-index: 85;
  pointer-events:none;
  object-fit: contain;
  filter: drop-shadow(0 10px 22px rgba(0,0,0,0.55));
  user-select:none; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none;
  -webkit-user-drag:none;
}
.pillow-drag-sprite[hidden]{ display:none; }

.hotspot.pillow-hotspot{ cursor: default; }
.pillow-hotspot.is-armed{ cursor: grabbing; }

/* ---------------- intro overlay: pitch black, then a soft circle of
   light grows outward from the candle once the match sound plays.
   app.js drives --glow-x / --glow-y / --glow-r; this file just describes
   the look. At --glow-r:0px it reads as solid black (the starting state). ---------------- */
#intro-overlay{
  position:absolute; inset:0; z-index: 90; pointer-events:auto;
  background: radial-gradient(circle at var(--glow-x, 50%) var(--glow-y, 55%),
    transparent 0px,
    transparent var(--glow-r, 0px),
    rgba(5,3,2,1) calc(var(--glow-r, 0px) + var(--glow-feather, 0px))
  );
}
#intro-overlay.intro-done{ display:none; }

/* ---------------- candle blowout: darkness spreading OUT of the candle,
   rather than the normal rule's light spreading out of it. Same --glow-x/
   --glow-y/--glow-r/--glow-feather vars, just with black and transparent
   swapped — so growing --glow-r here grows a dark patch outward instead of
   a lit one. Toggled on for the blowout only (see expandDarknessFromCandle
   in app.js) and removed again before the relight animation, which uses
   the normal rule above. ---------------- */
#intro-overlay.overlay-inverse{
  background: radial-gradient(circle at var(--glow-x, 50%) var(--glow-y, 55%),
    rgba(5,3,2,1) 0px,
    rgba(5,3,2,1) var(--glow-r, 0px),
    transparent calc(var(--glow-r, 0px) + var(--glow-feather, 0px))
  );
}

/* ---------------- weather layer: rain (Sudden Shower) / snow (Christmas
   song) — app.js spawns individual drops/flakes over time, each one falling
   from above the frame to below it exactly once, then removing itself.
   Starting = new ones begin entering from the top; stopping = spawning
   just stops, so whatever's already mid-fall finishes naturally instead of
   vanishing or fading. Sits above the scene photo, below the intro overlay,
   and never blocks clicks (pointer-events:none). ---------------- */
.weather-layer{
  position:absolute; inset:0;
  z-index: 6;
  pointer-events:none;
  overflow:hidden;
}

.rain-drop{
  position:absolute; top:-5%;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(to bottom, rgba(210,225,255,0), rgba(210,225,255,0.6));
  /* A static tilt of the drop's OWN shape, kept as the separate `rotate`
     property rather than folded into the `transform` list below. The
     individual translate/rotate/scale properties always compose in that
     fixed order (translate happens first, in plain unrotated screen space,
     THEN rotate is applied) no matter which order they're written in — so
     the keyframe's translate below still travels in a straight, correctly-
     angled line, and this just spins the visible streak in place to match
     that direction, instead of the translate itself getting dragged off at
     an angle by a rotate that ran first (which is what chaining rotate()
     inside one `transform` list would do). Negated because a positive
     (clockwise) CSS rotation on a vertical bar tilts it top-right/bottom-
     left — the opposite lean from a streak that's translating rightward as
     it falls, which needs to lean top-left/bottom-right to match. */
  rotate: calc(-1 * var(--rain-angle-deg, 15deg));
  animation-name: rain-fall;
  animation-timing-function: linear;
  animation-iteration-count: 1;
  animation-fill-mode: forwards;
}
/* Rain now falls at a consistent angle (like it's being blown by a light
   wind) instead of straight down. --rain-angle-x is set once in app.js from
   --rain-angle-deg (15°) worked out against the same 115vh of vertical
   travel used below, so the lean stays geometrically correct regardless of
   window size; --drift (already existed) just adds each drop's own small
   natural wobble on top of that consistent lean. */
@keyframes rain-fall{
  0%   { translate: 0 -10%; }
  100% { translate: calc(var(--rain-angle-x, 30.8vh) + var(--drift, 0px)) 115vh; }
}

.snow-flake{
  position:absolute; top:-5%;
  border-radius: 50%;
  background: rgba(255,255,255,0.85);
  box-shadow: 0 0 4px rgba(255,255,255,0.5);
  animation-name: snow-fall;
  animation-timing-function: linear;
  animation-iteration-count: 1;
  animation-fill-mode: forwards;
}
@keyframes snow-fall{
  0%   { transform: translateY(-10%) translateX(0) rotate(0deg); }
  100% { transform: translateY(115vh) translateX(var(--drift, 0px)) rotate(360deg); }
}

.hotspot{ position:absolute; cursor:pointer; border-radius: 6px; user-select:none; -webkit-user-select:none; }
.hotspot:focus{ outline:none; }

/* Shape-hugging highlight for the book (desk) and gramophone hotspots — an
   SVG polygon traced to the object's own silhouette (see HOTSPOT_OUTLINES in
   app.js), so the glow follows the item's actual outline instead of sitting
   in a rectangle around it. Hotspots without a traced outline (the shelf)
   simply have no .hotspot-outline-svg child, so this has no effect on them. */
.hotspot-outline-svg{ position:absolute; inset:0; width:100%; height:100%; overflow:visible; pointer-events:none; }
.hotspot-outline-shape{
  fill: rgba(247,200,118,0.10);
  opacity: 0;
  transition: opacity 220ms ease, filter 220ms ease;
  filter: drop-shadow(0 0 0px rgba(247,200,118,0));
}
.hotspot:hover .hotspot-outline-shape,
.hotspot:focus-visible .hotspot-outline-shape{
  opacity: 1;
  filter:
    drop-shadow(0 0 8px rgba(247,200,118,0.6))
    drop-shadow(0 0 20px rgba(247,200,118,0.35));
}

.shelf-row{
  position:absolute;
  z-index: 4; /* above .scene-img-fade (1), so a scene crossfade happens underneath the spines instead of washing over them */
  display:flex; align-items:flex-end; flex-wrap:wrap;
  gap: 0.15%;
  pointer-events:none; /* only the individual book spines inside are clickable */
}
.book-spine{
  pointer-events:auto;
  cursor:pointer;
  border-radius: 2px 3px 3px 2px;
  box-shadow: -3px 4px 8px rgba(0,0,0,0.45), inset -4px 0 6px rgba(0,0,0,0.25);
  border-top: 2px solid rgba(255,255,255,0.15);
  transition: transform 180ms ease, filter 180ms ease, opacity 320ms ease;
  transform-origin: bottom center;
  display:flex; align-items:center; justify-content:center;
  overflow:hidden;
}
.book-spine:hover{ transform: translateY(-8%); filter: brightness(1.25); }
/* The book currently being read is "in your hands", not on the shelf. */
.book-spine.is-in-hand{ opacity: 0; }

.scene-caption{
  position:absolute; left:0; right:0; bottom:4%;
  text-align:center; pointer-events:none;
  font-family: var(--font-display);
  font-style: italic; font-weight:500;
  font-size: clamp(13px, 1.8vw, 19px);
  color: var(--color-brass-bright);
  text-shadow: 0 2px 12px rgba(0,0,0,0.8);
  letter-spacing: 0.02em;
  z-index: 5;
}
.cap-line strong{ font-style:normal; color:#fff2d6; }

.now-playing{
  position:absolute; top:3%; right:3%;
  font-family: var(--font-mono); font-size:12px;
  color: var(--color-brass-bright);
  background: rgba(0,0,0,0.35);
  padding: 6px 12px;
  border-radius: 3px;
  border: 1px solid rgba(201,161,90,0.3);
  pointer-events:none;
  max-width: 40%;
  text-align:right;
  white-space: nowrap;
  overflow:hidden;
  text-overflow: ellipsis;
  user-select:none; -webkit-user-select:none;
}

.volume-popover{
  position:absolute;
  transform: translate(-50%, -50%);
  z-index: 60;
  display:none; align-items:center; gap:10px;
  background: rgba(20,13,8,0.92);
  border: 1px solid rgba(201,161,90,0.4);
  border-radius: 999px;
  padding: 10px 16px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.volume-popover.is-open{ display:flex; }
.volume-popover .volume-icon{
  color: var(--color-brass-bright);
  font-family: var(--font-mono);
  font-size: 13px;
}
.volume-popover input[type="range"]{
  width: 120px;
  accent-color: var(--color-brass-bright);
}

/* =========================================================
   PANEL HEADER (shared by shelf / reader / editor)
========================================================= */
.panel-header{
  display:flex; align-items:center; gap:20px;
  padding: 22px 32px;
  border-bottom: 1px solid rgba(201,161,90,0.25);
  box-shadow: 0 2px 14px rgba(0,0,0,0.4);
}
.panel-header h1{
  font-family: var(--font-display);
  font-size: 26px; font-weight:600; margin:0;
  color: var(--color-brass-bright);
  flex:1;
}
.btn-back, .btn-primary, .editor-toolbar button{
  background: transparent;
  border: 1px solid rgba(201,161,90,0.5);
  color: var(--color-parchment);
  padding: 9px 16px;
  border-radius: 3px;
  font-size: 13px;
  letter-spacing: 0.04em;
  transition: background 160ms ease, border-color 160ms ease, transform 160ms ease;
}
.btn-back:hover, .btn-primary:hover, .editor-toolbar button:hover{
  background: rgba(201,161,90,0.15);
  border-color: var(--color-brass-bright);
}
.btn-primary{
  background: var(--color-brass);
  color: var(--color-wall-dark);
  border-color: var(--color-brass);
  font-weight:700;
}
.btn-primary:hover{ background: var(--color-brass-bright); transform: translateY(-1px); }
.editor-toolbar button.danger{ border-color: rgba(138,58,46,0.6); color:#e8b6a9; }
.editor-toolbar button.danger:hover{ background: rgba(138,58,46,0.25); border-color:#c8695a; }

/* =========================================================
   SHELF VIEW (carousel of yearbooks)
========================================================= */
.view-shelf{
  display:flex; flex-direction:column;
}

/* Shelf page's header floats over the carousel and auto-hides, so the
   carousel below can use the full height of the screen. */
.view-shelf .panel-header{
  position:absolute; top:0; left:0; right:0; z-index:70;
  transition: transform 420ms var(--ease), opacity 420ms var(--ease);
}
.view-shelf .panel-header.navbar-hidden{
  transform: translateY(-100%);
  opacity:0;
  pointer-events:none;
}

.shelf-carousel{
  flex:1; position:relative;
  display:flex; align-items:center; justify-content:center;
  overflow:hidden;
  padding: 10px 60px;
}
.carousel-track{
  position:relative;
  width:100%; height:100%;
  max-height: 760px;
}
.carousel-nav{
  position:absolute; top:50%; transform:translateY(-50%);
  z-index:50;
  background: rgba(0,0,0,0.35);
  border: 1px solid rgba(201,161,90,0.4);
  color: var(--color-brass-bright);
  width:48px; height:48px; border-radius:50%;
  padding:0; margin:0;
  font-size:22px; line-height:1;
  display:flex; align-items:center; justify-content:center;
  transition: background 160ms ease;
}
.carousel-nav:hover{ background: rgba(201,161,90,0.25); }
.carousel-nav.prev{ left: 24px; }
.carousel-nav.next{ right: 24px; }

.shelf-card{
  position:absolute; left:50%; top:50%;
  width: 380px; height: 640px;
  margin-left: -190px; margin-top: -320px;
  cursor:pointer;
  border-radius: 14px;
  overflow:hidden;
  background: transparent;
  box-shadow: 0 24px 60px rgba(0,0,0,0.5);
  transition: transform 480ms var(--ease), opacity 480ms var(--ease), filter 480ms var(--ease);
  will-change: transform, filter, opacity;
}
.shelf-card.is-center{ cursor:default; }
.shelf-card .card-cover{
  position:absolute; inset:0;
  overflow: hidden;
}
.shelf-card .card-cover img{
  width:100%; height:100%;
  object-fit: cover;
  object-position: top center;
  display:block;
}

.carousel-dots{
  display:flex; justify-content:center; gap:8px; padding: 6px 0 20px;
  flex-wrap:wrap; max-width:80%; margin:0 auto;
}
.carousel-dots .page-dot{ width:7px; height:7px; }

.shelf-empty{
  position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  text-align:center; width:80%;
  font-family: var(--font-display); font-style:italic; font-size:20px;
  color: rgba(237,224,196,0.6);
}

/* =========================================================
   READER + EDITOR STAGE (the open page)
========================================================= */
.view-reader{
  /* A soft dark veil, not an opaque background — the actual view the reader
     was opened from (study scene or shelf carousel) peeks through live from
     behind at low opacity (see .view-peek), so the real books sitting there
     stay visible instead of being replaced by a flat image. */
  background-color: rgba(0,0,0,0.45);
  display:flex; flex-direction:column;
}
.view-reader .panel-header{
  position:absolute; top:0; left:0; right:0; z-index:70;
  transition: transform 420ms var(--ease), opacity 420ms var(--ease);
}
.view-reader .panel-header.navbar-hidden{
  transform: translateY(-100%);
  opacity:0;
  pointer-events:none;
}
.view-editor{
  background: var(--editor-bg);
  display:flex; flex-direction:column;
  font-family: var(--font-ui);
  /* Already position:absolute via the shared .view rule above — that's a
     positioned element too, so #editor-preview-block's position:absolute;
     inset:0 sizes against this view correctly without needing to add or
     override position here (doing so would fight the view-switching/peek
     system, which depends on .view's absolute positioning). */
}
.view-editor .panel-header{
  background: var(--editor-panel);
  border-bottom: 1px solid var(--editor-border);
  box-shadow: none;
  padding: 12px 28px;
}
.view-editor .panel-header h1{ font-family: var(--font-ui); }

/* Viewer-only preview mode: sits on top of every control in the editor —
   toolbar buttons, the title/year fields, sliders, color pickers, the
   cover-photo dropzone, the page itself — so none of it can actually be
   clicked, dragged, focused, or typed into. The Back button is excepted via
   its own higher z-index below, so leaving the preview still works. */
.editor-preview-block{
  position: absolute; inset: 0;
  z-index: 500;
  cursor: default;
}
.editor-preview-block[hidden]{ display:none; }

/* Buttons: simple flat pills that read clearly on black. Neutral for
   general actions, a soft gold outline for "add" actions, solid gold for
   the primary Save action, and a muted red for anything destructive. */
.view-editor .btn-back,
.view-editor .btn-primary,
.view-editor .book-meta-bar button,
.view-editor .editor-toolbar button{
  background: rgba(255,255,255,0.05);
  border: 1px solid var(--editor-border);
  color: var(--editor-text);
  font-family: var(--font-ui);
  font-weight: 500;
  padding: 9px 16px;
  border-radius: 8px;
  font-size: 13px;
  letter-spacing: 0;
  transition: background 150ms ease, border-color 150ms ease, transform 150ms ease;
}
/* Stays clickable through the preview-block overlay above (z-index 500) —
   this is the one control still allowed to do anything in preview mode. */
.view-editor .btn-back{
  position: relative;
  z-index: 501;
}
.view-editor .btn-back:hover,
.view-editor .book-meta-bar button:hover,
.view-editor .editor-toolbar button:hover{
  background: rgba(255,255,255,0.1);
  border-color: rgba(255,255,255,0.22);
}
.view-editor .btn-primary{
  background: var(--editor-accent);
  border-color: var(--editor-accent);
  color: #241a12;
  font-weight: 700;
}
.view-editor .btn-primary:hover{ background: var(--editor-accent-strong); border-color: var(--editor-accent-strong); transform: translateY(-1px); }
.view-editor .editor-toolbar button[data-action="add-text"],
.view-editor .editor-toolbar button[data-action="add-image"],
.view-editor .editor-toolbar button[data-action="add-page"],
.view-editor .book-meta-bar button[data-action="add-cover-photo"]{
  border-color: rgba(232,199,137,0.45);
  color: var(--editor-accent);
}
.view-editor .editor-toolbar button[data-action="add-text"]:hover,
.view-editor .editor-toolbar button[data-action="add-image"]:hover,
.view-editor .editor-toolbar button[data-action="add-page"]:hover,
.view-editor .book-meta-bar button[data-action="add-cover-photo"]:hover{
  background: rgba(232,199,137,0.12);
  border-color: var(--editor-accent);
}
.view-editor .editor-toolbar button.danger,
.view-editor .book-meta-bar button.danger{
  border-color: rgba(236,122,109,0.4);
  color: var(--editor-danger);
  background: transparent;
}
.view-editor .editor-toolbar button.danger:hover,
.view-editor .book-meta-bar button.danger:hover{
  background: rgba(236,122,109,0.14);
  border-color: var(--editor-danger);
}

.book-stage{
  flex:1; display:flex; align-items:center; justify-content:center;
  gap: 28px; padding: 16px; min-height:0;
}
.editor-stage{
  flex:1; display:flex; align-items:center; justify-content:center;
  gap: 14px; padding: 12px; min-height:0;
}
.page-nav{
  background: rgba(0,0,0,0.25);
  border: 1px solid rgba(201,161,90,0.4);
  color: var(--color-brass-bright);
  width:44px; height:44px; border-radius:50%;
  padding:0; margin:0;
  font-size:20px; line-height:1; flex-shrink:0;
  display:flex; align-items:center; justify-content:center;
  transition: background 160ms ease;
}
.page-nav:hover{ background: rgba(201,161,90,0.2); }

.book-frame.two-page{
  position: relative;
  perspective: 2400px;
  /* Actual width/height are set by sizeBookFrame() in app.js, matching the
     book artwork's own aspect ratio — these are just a pre-JS fallback. */
  height: 94%;
  max-height: 900px;
  background-image: url('assets/Book/BookOpen.png');
  background-repeat: no-repeat;
  /* Safe because sizeBookFrame() always sizes this box to the SAME aspect
     ratio as the artwork itself, so stretching to 100%/100% never distorts
     it — it's really just "fill the box", not an actual stretch. */
  background-size: 100% 100%;
}
/* Reader-only: lets our JS swipe handler (setupReaderSwipe in app.js) own
   horizontal drags instead of the browser's native edge-swipe back/forward
   gesture fighting it — vertical touch behavior is left alone. Scoped to
   the reader specifically since the editor's frame needs normal touch
   behavior for dragging text/image elements around the page. */
#view-reader .book-frame.two-page{ touch-action: pan-y; }
.page{
  position:absolute;
  overflow:hidden;
  transform-style: preserve-3d;
}
/* Percentages measured directly against assets/Book/BookOpen.png's own
   printed-page area (the actual white paper, not the covers/spine/shadow
   around it) — see the comment above IMG_W/IMG_H-style constants in app.js
   for how these were derived. Because these are percentages of the frame
   (which always matches the artwork's own proportions), they stay correctly
   aligned with the printed pages at any size, with no JS math needed here. */
.page-left{
  left: 5%; top: 2.5%;
  width: 37%; height: 94%;
  transform-origin: right center;
}
.page-right{
  left: 51.1%; top: 2.5%;
  width: 45.3%; height: 94%;
  transform-origin: left center;
}
.page-blank{ background: var(--color-parchment) !important; }
.page.is-active-page{
  outline: 3px solid var(--editor-accent);
  outline-offset: 3px;
}
.page-flip-layer{
  z-index: 5;
  pointer-events: none;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.page-flip-layer.flip-next{
  transform-origin: left center; /* hinges at the spine, same edge page-right pivots on */
  animation: flipNext var(--page-flip-dur) var(--ease) forwards;
}
.page-flip-layer.flip-prev{
  transform-origin: right center; /* hinges at the spine, same edge page-left pivots on */
  animation: flipPrev var(--page-flip-dur) var(--ease) forwards;
}
@keyframes flipNext{
  0%{ transform: rotateY(0deg); filter:brightness(1); }
  55%{ filter:brightness(0.55); }
  100%{ transform: rotateY(-180deg); filter:brightness(1); }
}
@keyframes flipPrev{
  0%{ transform: rotateY(0deg); filter:brightness(1); }
  55%{ filter:brightness(0.55); }
  100%{ transform: rotateY(180deg); filter:brightness(1); }
}

.page-el{
  position:absolute;
  cursor: grab;
  user-select:none;
  line-height:1.3;
}
.page-el.text-el{ font-family: var(--font-body); white-space: pre-wrap; }
.page-el.image-el img{
  width:100%; height:100%; object-fit:cover; border-radius:2px; display:block;
  -webkit-user-drag: none;
  user-select: none; -webkit-user-select: none;
  pointer-events: none; /* clicks/drags pass through to the parent .page-el div instead of the browser's native image handling */
}
.page-el.is-selected{ outline: 2px dashed var(--color-leather-green); outline-offset:3px; }
.page-el .resize-handle{
  position:absolute; right:-6px; bottom:-6px;
  width:12px; height:12px; border-radius:50%;
  background: var(--color-leather-green);
  border: 2px solid #fff;
  cursor: nwse-resize;
  display:none;
}
.page-el.is-selected .resize-handle{ display:block; }

.page-dots{
  display:flex; justify-content:center; gap:8px; padding: 4px 0 18px;
}
.page-dot{
  width:8px; height:8px; border-radius:50%;
  background: rgba(237,224,196,0.25);
  border:none; padding:0; cursor:pointer;
}
.page-dot.is-active{ background: var(--color-brass-bright); }

/* =========================================================
   EDITOR TOOLBAR
========================================================= */
.editor-toolbar{
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  padding: 8px 28px;
  background: var(--editor-panel);
  border-bottom: 1px solid var(--editor-border);
}

.book-meta-bar{
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding: 8px 28px;
  background: var(--editor-panel);
  border-bottom: 1px solid var(--editor-border);
}
.cover-preview{
  width: 34px; height: 48px;
  border-radius: 4px 6px 6px 4px;
  border: 1px solid rgba(255,255,255,0.18);
  box-shadow: 0 4px 12px rgba(0,0,0,0.5);
  background-size: cover;
  background-position: center;
  background-color: #fff; /* shows through around a contain-fitted photo, like blank page space */
  position: relative;
  overflow: hidden;
  flex-shrink:0;
}
.cover-preview img{
  width:100%; height:100%;
  object-fit: contain;
  display:block;
}
.cover-preview img[hidden]{ display:none; }
.divider{ width:1px; height:20px; background: var(--editor-border); margin:0 4px; }
.bg-label{ display:flex; align-items:center; gap:6px; font-family: var(--font-ui); font-size:12px; font-weight:500; color: var(--editor-muted); }
.bg-label input[type=color]{ width:24px; height:24px; border:none; border-radius:6px; overflow:hidden; background:none; padding:0; }

.spine-texture-picker{ display:flex; align-items:center; gap:6px; }
.texture-swatch{
  width: 22px; height: 30px;
  border-radius: 3px 4px 4px 3px;
  border: 1px solid rgba(255,255,255,0.18);
  background-size: 100% 100%;
  background-position: center;
  cursor: pointer;
  padding: 0;
  box-shadow: 0 2px 6px rgba(0,0,0,0.4);
  transition: transform 150ms ease, box-shadow 150ms ease;
}
.texture-swatch:hover{ transform: translateY(-2px); }
.texture-swatch.is-selected{
  box-shadow: 0 0 0 2px var(--color-brass-bright), 0 2px 6px rgba(0,0,0,0.4);
}

.cover-reposition{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
.cover-reposition[hidden]{ display:none; }
.cover-reposition-label{
  font-family: var(--font-ui); font-size:11px; color: var(--editor-muted);
  max-width: 90px; line-height:1.3;
}
.cover-reposition-frame{
  position:relative;
  width: 100px; height: 168px; /* same ratio as the shelf carousel card, so this preview matches what the gift actually looks like */
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--editor-border);
  background: #fff; /* shows through wherever the photo doesn't reach — never a crop, just blank space like a page */
  cursor: grab;
  flex-shrink:0;
  touch-action: none;
}
.cover-reposition-frame:active{ cursor: grabbing; }
.cover-reposition-frame img{
  position:absolute; top:0; left:0; width:100%; height:100%;
  object-fit: contain;
  user-select:none; -webkit-user-select:none;
  -webkit-user-drag:none;
}
.cover-zoom-label{ min-width:150px; }
.cover-zoom-label input[type=range]{ width:120px; }
.cover-reset-btn{
  background: rgba(255,255,255,0.05);
  border: 1px solid var(--editor-border);
  color: var(--editor-text);
  padding: 7px 12px;
  border-radius: 8px;
  font-family: var(--font-ui); font-size:12px;
  transition: background 150ms ease, border-color 150ms ease;
}
.cover-reset-btn:hover{ background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.22); }

.title-input, .year-input{
  background: rgba(255,255,255,0.06);
  border: 1px solid var(--editor-border);
  color: var(--editor-text);
  padding: 7px 12px; border-radius:8px;
  font-family: var(--font-display); font-size:17px;
}
.title-input{ flex:1; min-width:120px; }
.year-input{ width:90px; font-family: var(--font-ui); font-size:14px; }
.title-input:focus, .year-input:focus{ outline:none; border-color: var(--editor-accent); }

.element-inspector{
  display:flex; gap:22px; align-items:center; flex-wrap:wrap;
  padding: 8px 28px;
  background: var(--editor-panel-raised);
  border-top: 1px solid var(--editor-border);
  font-family: var(--font-ui); font-size:12px;
}
.element-inspector[hidden]{ display:none; }
.element-inspector label{ display:flex; flex-direction:column; gap:5px; color: var(--editor-muted); font-weight:500; }
.element-inspector textarea{
  width:220px; resize:vertical;
  background: rgba(255,255,255,0.06);
  border: 1px solid var(--editor-border);
  border-radius: 6px;
  color: var(--editor-text);
  font-family: var(--font-ui); font-size:13px;
  padding: 6px 8px;
}
.element-inspector select{
  background: rgba(255,255,255,0.06);
  border: 1px solid var(--editor-border);
  border-radius: 6px;
  color: var(--editor-text);
  font-family: var(--font-ui); font-size:12px;
  padding: 5px 6px;
}

/* =========================================================
   ACHIEVEMENTS VIEW
   A dimmed scrim (same peek-fade treatment as the reader) with a wooden
   picture-frame centered on screen. Rough placeholder version: every slot
   is just a plain black box until real icon art is added — unlocked ones
   get a brass outline and a checkmark, locked ones a faint "?".
========================================================= */
.view-achievements{
  background-color: rgba(0,0,0,0.55);
  display:flex; align-items:center; justify-content:center;
  padding: 20px;
}
.achievements-frame{
  position:relative;
  width: min(600px, 92vw);
  max-width: 92vw;
  max-height: 90vh;
  display:flex; flex-direction:column; gap:20px;
  padding: 38px 32px 30px;
  border-radius: 10px;
  border: 14px solid var(--color-shelf-wood);
  box-shadow: 0 30px 80px rgba(0,0,0,0.6), inset 0 0 40px rgba(0,0,0,0.35);
}
.achievements-close{
  position:absolute; top:6px; right:10px;
  background:transparent; border:none;
  color: var(--color-brass-bright);
  font-size:32px; line-height:1;
  cursor:pointer; padding:4px 10px;
  font-family: var(--font-ui);
}
.achievements-close:hover{ color:#fff; }
.achievements-title{
  margin:0; text-align:center;
  font-family: var(--font-display);
  font-size:28px; font-weight:600;
  color: var(--color-brass-bright);
  text-shadow: 0 2px 10px rgba(0,0,0,0.6);
}
.achievements-grid{
  display:grid;
  grid-template-columns: repeat(3, 130px);
  gap: 22px;
  justify-content:center;
  align-content:start;
  /* Exactly 3 rows (9 boxes) visible before scrolling kicks in for more. */
  max-height: calc(130px * 3 + 22px * 2);
  overflow-y:auto;
  padding: 4px 6px 8px;
}
.achievement-box{
  position: relative;
  z-index: 1;
  width:130px; height:130px;
  border-radius: 12px;
  background: #0a0a0a;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06), inset 0 4px 10px rgba(0,0,0,0.6);
  display:flex; align-items:center; justify-content:center;
  color: rgba(255,255,255,0.18);
  font-family: var(--font-ui); font-size:34px;
  transition: transform 160ms ease, box-shadow 160ms ease;
}
.achievement-box:hover{ transform: translateY(-2px); z-index: 50; }
/* Soft shine radiating out from behind the box on hover — sits behind
   everything (z-index -1) so it reads as a glow the box is sitting in
   front of, not an outline on the box itself. */
.achievement-box::before{
  content: '';
  position: absolute;
  inset: -16px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,244,214,0.4) 0%, rgba(255,244,214,0) 72%);
  opacity: 0;
  transition: opacity 220ms ease;
  z-index: -1;
  pointer-events: none;
}
.achievement-box:hover::before{ opacity: 1; }
.achievement-box.is-unlocked{
  color: var(--color-brass-bright);
  box-shadow: inset 0 0 0 2px var(--color-brass-bright), 0 0 16px rgba(230,199,137,0.35);
}

/* ---------------- custom hover tooltip: bold title + smaller description ---------------- */
.achievement-tooltip{
  position: absolute;
  top: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  min-width: 150px;
  max-width: 220px;
  background: rgba(20,13,8,0.96);
  border: 1px solid rgba(201,161,90,0.5);
  border-radius: 7px;
  padding: 9px 13px;
  text-align: center;
  box-shadow: 0 10px 24px rgba(0,0,0,0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease;
}
.achievement-box:hover .achievement-tooltip{ opacity: 1; }
.achievement-tooltip strong{
  display:block;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  color: var(--color-brass-bright);
  margin-bottom: 3px;
}
.achievement-tooltip span{
  display:block;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 400;
  color: rgba(255,255,255,0.78);
  line-height: 1.35;
}

/* ---------------- achievement unlock toast: bottom-left, Steam-style ---------------- */
.achievement-toast{
  position: fixed;
  left: 20px; bottom: 20px;
  z-index: 200;
  display:flex; align-items:center; gap:14px;
  background: rgba(20,13,8,0.95);
  border: 1px solid rgba(201,161,90,0.5);
  border-radius: 8px;
  padding: 12px 18px 12px 12px;
  box-shadow: 0 14px 40px rgba(0,0,0,0.55);
  max-width: min(340px, 80vw);
  transform: translateX(-120%);
  opacity: 0;
  transition: transform 380ms var(--ease), opacity 380ms var(--ease);
  pointer-events:none;
}
.achievement-toast.is-visible{
  transform: translateX(0);
  opacity: 1;
}
.achievement-toast-icon{
  flex-shrink:0;
  width:44px; height:44px;
  border-radius: 6px;
  background-color: #0a0a0a;
  background-size: cover;
  background-position: center;
  box-shadow: inset 0 0 0 2px var(--color-brass-bright), 0 0 12px rgba(230,199,137,0.4);
  display:flex; align-items:center; justify-content:center;
  color: var(--color-brass-bright);
  font-size:20px;
}
.achievement-toast-label{
  font-family: var(--font-mono); font-size:10px; letter-spacing:0.08em;
  text-transform: uppercase;
  color: var(--color-brass-bright);
  opacity:0.85;
}
.achievement-toast-title{
  font-family: var(--font-display); font-size:16px; font-weight:600;
  color: #fff2d6;
  margin-top: 2px;
}

/* ---------------- achievements toolbar: reset (testing) + copy progress ---------------- */
.achievements-toolbar{
  display:flex; justify-content:center; gap:10px;
  padding-top: 4px;
}
.achievements-toolbar-btn{
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(201,161,90,0.4);
  color: var(--color-parchment);
  font-family: var(--font-ui); font-size:11px;
  padding: 6px 12px;
  border-radius: 6px;
  cursor:pointer;
  transition: background 150ms ease, border-color 150ms ease;
}
.achievements-toolbar-btn:hover{ background: rgba(201,161,90,0.15); border-color: var(--color-brass-bright); }
.achievements-toolbar-btn.is-danger{ border-color: rgba(138,58,46,0.6); color:#e8b6a9; }
.achievements-toolbar-btn.is-danger:hover{ background: rgba(138,58,46,0.2); border-color:#c8695a; }

/* ---------------- achievement detail overlay: full icon + title + description ---------------- */
.achievement-detail{
  position:absolute; inset:0;
  z-index: 60;
  border-radius: inherit;
  background: rgba(8,5,3,0.97);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap: 14px;
  padding: 36px 26px;
}
.achievement-detail[hidden]{ display:none; }
.achievement-detail-back{
  position:absolute; top:10px; left:14px;
  background:transparent; border:none;
  color: var(--color-brass-bright);
  font-family: var(--font-ui); font-size:14px; font-weight:600;
  cursor:pointer; padding:6px 10px;
}
.achievement-detail-back:hover{ color:#fff; }
.achievement-detail-img{
  max-width: 78%;
  max-height: 55%;
  border-radius: 10px;
  box-shadow: 0 20px 50px rgba(0,0,0,0.6), inset 0 0 0 2px var(--color-brass-bright);
  object-fit: cover;
}
.achievement-detail-title{
  font-family: var(--font-display); font-size:22px; font-weight:600;
  color: var(--color-brass-bright);
  text-align:center;
}
.achievement-detail-desc{
  font-family: var(--font-ui); font-size:14px;
  color: rgba(255,255,255,0.8);
  text-align:center;
  max-width: 80%;
}


/* =========================================================
   RESPONSIVE
========================================================= */
@media (max-width: 760px){
  .panel-header{ padding:14px 16px; flex-wrap:wrap; }
  .panel-header h1{ font-size:20px; order:1; width:100%; }
  .shelf-carousel{ padding: 6px 40px; }
  .carousel-nav{ width:38px; height:38px; font-size:18px; }
  .carousel-nav.prev{ left:8px; }
  .carousel-nav.next{ right:8px; }
  .shelf-card{ width:230px; height:420px; margin-left:-115px; margin-top:-210px; }
  .editor-toolbar{ padding:10px 14px; }
  .element-inspector{ padding:10px 14px; }
  .book-stage{ gap: 8px; padding: 8px; }
  .achievements-frame{ width: min(600px, 95vw); max-width: 95vw; padding: 28px 18px 22px; }
  .achievements-grid{
    grid-template-columns: repeat(3, 100px);
    max-height: calc(100px * 3 + 16px * 2);
    gap: 16px;
  }
  .achievement-box{ width:100px; height:100px; font-size:28px; }
}

/* Focus visibility for accessibility */
a:focus-visible, button:focus-visible, [tabindex]:focus-visible{
  outline: 2px solid var(--color-brass-bright);
  outline-offset: 2px;
}
