/* FC Yearbook browser client — themed to the "Editorial Keepsake" design system.
   Tokens come from ds/styles.css (vendored — colors/typography/spacing/depth-motion).
   Rules of the world (docs-md/Design_North_Star.md): warm paper + warm ink, ONE cyan
   action per view, marigold only for memories, Fraunces for names/headings, Instrument
   Sans for UI, Shantell Sans only inside message content, warm-ink shadows, no emoji
   as chrome (icon swap lands with the IA pass). Layout/structure is unchanged from the
   pre-design build — this pass restyles, it does not move anything. */

* { box-sizing: border-box; margin: 0; padding: 0; }
/* the hidden attribute must beat ID-specificity display rules (#login{display:flex} etc.) */
[hidden] { display: none !important; }
html, body { height: 100%; }

/* 1vh that the QA phone frame can override (vh units always mean the REAL window, so
   frame-mode remaps them to frame height — see the phone preview block at the bottom) */
:root { --vh1: 1vh; --shelf-cover: 150px; }   /* one cover width, hero and shelf alike */

body {
  font-family: var(--font-ui);
  display: flex; flex-direction: column; height: 100vh;
  background: var(--bg); color: var(--text); overflow: hidden;
}
#bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 14px; background: var(--surface);
  border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
#bar .grp { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
#title { font-size: var(--body-sm); color: var(--text-secondary); flex: 1; text-align: center; min-width: 120px; }
#bar button {
  width: 34px; height: 34px; border: 1px solid var(--border); background: var(--surface);
  color: var(--ink-700); border-radius: var(--radius-sm); font-size: 18px; cursor: pointer; line-height: 1;
  box-shadow: var(--shadow-sm);
  flex: 0 0 auto;   /* wrap to the next line instead of squishing on narrow screens */
}
#bar button:hover { background: var(--surface-tint); }
/* text buttons size to content (otherwise the 34px icon width truncates + overlaps) */
#inbox, #signout, #history { width: auto !important; padding: 0 12px; font-size: var(--body-sm); white-space: nowrap; }
#pagesel {
  height: 34px; background: var(--surface); color: var(--ink-700); border: 1px solid var(--border);
  border-radius: var(--radius-sm); font-size: var(--body-sm); padding: 0 6px; cursor: pointer;
}
#zlbl { font-size: var(--body-sm); color: var(--text-secondary); min-width: 54px; text-align: center; }
.chk { font-size: var(--body-sm); color: var(--text-secondary); display: flex; align-items: center; gap: 5px; cursor: pointer; }

/* the reader is the one warm-DARK surface (design: "reading room" — pages shine on it) */
#viewport {
  flex: 1; position: relative; overflow: hidden; background: #1E1A14;
  touch-action: none; cursor: grab;
}
/* which door am I in, at a glance (RD 2026-08-19): the reader background says the side —
   QA = 75% gray (75% black, #404040), PROD = all black. The warm #1E1A14 above survives
   only until the /api/health probe answers (or when it fails, e.g. local static serve). */
html.side-qa #viewport { background: #404040; }
html.side-prod #viewport { background: #000; }
#page { position: absolute; left: 0; top: 0; transform-origin: 0 0; z-index: 1; }
#pageimg { display: block; user-select: none; -webkit-user-drag: none; background: #fff; }
/* the page revealed under a turn — same geometry as #page, one layer down (page-turn
   animation, RD 2026-07-30). Never interactive: pointer-events off so a drag that ends
   over it can't hit-test a hotspot on a page you haven't landed on yet. */
#page-under { position: absolute; left: 0; top: 0; transform-origin: 0 0; z-index: 0; pointer-events: none; }
#pageimg-under { display: block; user-select: none; -webkit-user-drag: none; background: #fff; }
#pageimg:not([src]), #pageimg[src=""] { display: none; }   /* no book loaded = no broken-image alt */
#overlay { position: absolute; inset: 0; }

/* hotspot boxes (admin visibility) — brand cyan, quiet fill */
.hs {
  position: absolute; border: 2px solid rgba(0,174,239,.85);
  border-radius: 3px; cursor: pointer; background: rgba(0,174,239,.06);
}
.hs:hover { background: rgba(0,174,239,.16); }
.hs.hide { border-color: transparent; background: transparent; }

/* ---------- portrait message badges (RD spec — semantics + 100%-zoom rule unchanged) ----
   they-signed-me = marigold (the memory accent), photo top-right; I-signed-theirs = quiet
   ink gray, photo bottom-right. INSIDE the photo with a small inset; thin white border so
   they read on any page background. --bscale (set in apply()) counter-scales to a constant
   screen size; shown at 100% zoom only (#page.nobadges, toggled in apply()). */
.pbadge {
  position: absolute; display: flex; align-items: center; justify-content: center;
  font-weight: 700; line-height: 1; color: #fff;
  border: 1.5px solid rgba(255,255,255,.92);
  box-shadow: 0 1px 3px rgba(34,30,21,.4);
  transform-origin: 0 0;
  pointer-events: none;   /* informational — taps belong to the hotspot beneath */
}
/* MICRO SPEECH BUBBLES (RD 2026-07-15): the received markers are little speech bubbles, not
   dots — unread(blue) / read(gray) at top-right, tail pointing down-left toward the face;
   sent = a check bottom-right. Width/height/font are set per-portrait in renderHotspots so
   the bubble scales with the photo (was oversized on the FC book's tiny portraits).
   --bscale counter-scales to keep a crisp border; anchored at a photo corner, translated
   inside with a small inset; shown at 100% zoom only. */
.pbadge.pb-top { border-radius: 50% 50% 50% 8%; transform: scale(var(--bscale, 1)) translate(calc(-100% - 3px), 3px); }
.pbadge.pb-bot { border-radius: 38%; transform: scale(var(--bscale, 1)) translate(calc(-100% - 3px), calc(-100% - 3px)); }
.pbadge.pb-unread { background: var(--blue-500); }
.pbadge.pb-read   { background: var(--ink-400); }
.pbadge.pb-sent   { background: var(--ink-500); }
#page.nobadges .pbadge { display: none; }
/* the eye toggle (RD): hide ALL portrait marks for a clean read */
#page.nomarks .pbadge { display: none; }

#status {
  padding: 10px 14px; background: var(--surface); border-top: 1px solid var(--border);
  font-size: var(--body-md); text-align: center; min-height: 40px; color: var(--text-secondary);
}
#status.hit { color: var(--marigold-700); font-family: var(--font-display); font-weight: var(--fw-semibold); }

/* login */
#login { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: center; justify-content: center; z-index: 30; }
/* Landing = the design splash (Fanny's welcome screen, the last off-design view — RD
   2026-08-11): open on the paper, no card chrome. Fraunces display title + tagline, one
   glowing cyan pill, secondaries as blue links. Structure (login form fields) is the
   built function master; only the dress changes. */
#landingtitle { font-family: var(--font-display); font-weight: var(--fw-semibold);
  font-size: var(--display-lg); line-height: var(--display-lg-lh); letter-spacing: var(--display-lg-ls);
  color: var(--text); text-align: center; margin: calc(var(--vh1) * 6) 0 10px; }
#loginform { width: min(320px, 100%); display: flex; flex-direction: column; gap: 12px; }
#loginform input { padding: 10px 12px; border: 1px solid var(--border); background: var(--surface-tint); color: var(--text); border-radius: var(--radius-sm); font-size: var(--body-md); box-shadow: var(--inset-well); }
#loginform input:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
#loginform button { padding: 13px; border: 0; background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; border-radius: var(--radius-full); font-size: var(--button); font-weight: var(--fw-semibold); cursor: pointer; box-shadow: var(--highlight-top), var(--glow-primary); }
#loginform button:disabled { opacity: .5; }
#loginerr { color: var(--error-600); font-size: var(--body-sm); min-height: 16px; }

/* inbox button + unread (count pill = blue, never red) */
#inbox { position: relative; padding: 0 12px; width: auto; font-size: var(--body-sm); }
#inboxn { display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 3px; background: var(--blue-500); color: #fff; border-radius: var(--radius-full); font-size: 11px; }

/* message panel — scrims are warm ink, never black */
#panel { position: fixed; inset: 0; background: rgba(25,21,16,.35); display: flex; align-items: flex-end; justify-content: center; z-index: 25; }
.sheet { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-autograph) var(--radius-autograph) 0 0; width: 100%; max-width: 520px; padding: 16px; display: flex; flex-direction: column; gap: 10px; max-height: calc(var(--vh1) * 70); box-shadow: var(--shadow-lg); }
#panelhead { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); color: var(--text); }
#thread { overflow-y: auto; display: flex; flex-direction: column; gap: 6px; max-height: calc(var(--vh1) * 40); }
/* message content speaks in the handwriting voice (full autograph-card treatment lands
   with the IA pass; bylines stay in the UI voice) */
.bubble { padding: 8px 11px; border-radius: var(--radius-sm); font-size: 15px; max-width: 80%; font-family: var(--font-hand); }
.bubble.in { background: var(--surface-warm); border: 1px solid var(--border); align-self: flex-start; }
.bubble.out { background: var(--blue-050); color: var(--text); align-self: flex-end; }
.bubble .who { display: block; font-family: var(--font-ui); font-size: 11px; color: var(--text-muted); margin-bottom: 2px; }
.bubble.out .who { color: var(--blue-700); }
#composeform { display: flex; gap: 8px; }
#msgtext { flex: 1; padding: 10px 12px; border: 1px solid var(--border); background: var(--surface-tint); color: var(--text); border-radius: var(--radius-sm); font-size: var(--body-md); box-shadow: var(--inset-well); }
#msgtext:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
#sendbtn { padding: 0 16px; border: 0; background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; border-radius: var(--radius-md); font-weight: var(--fw-semibold); cursor: pointer; box-shadow: var(--highlight-top); }
#panelclose { padding: 8px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); border-radius: var(--radius-md); cursor: pointer; }

/* ---------- search (live student finder) ---------- */
#searchwrap { position: relative; flex: 0 1 240px; min-width: 140px; }
#search {
  width: 100%; height: 34px; padding: 0 10px; border: 1px solid var(--border);
  background: var(--surface-tint); color: var(--text); border-radius: var(--radius-full); font-size: var(--body-sm);
  box-shadow: var(--inset-well);
}
#search:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
#searchresults {
  position: absolute; top: 38px; left: 0; right: 0; z-index: 40;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  max-height: calc(var(--vh1) * 50); overflow-y: auto; box-shadow: var(--shadow-lg);
}
.sr-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 9px 12px; cursor: pointer; font-size: var(--body-md); border-bottom: 1px solid var(--paper-200);
}
.sr-row:last-child { border-bottom: 0; }
.sr-row:hover, .sr-row.active { background: var(--surface-tint); }
.sr-name { font-family: var(--font-display); font-weight: var(--fw-semibold); }
.sr-row.me .sr-name { color: var(--marigold-700); }
.sr-pages { display: flex; gap: 4px; flex-wrap: wrap; }
.sr-page {
  font-size: 11px; color: var(--text-secondary); border: 1px solid var(--border); border-radius: var(--radius-xs);
  padding: 2px 7px; white-space: nowrap;
}
.sr-page:hover { background: var(--blue-050); color: var(--blue-700); border-color: var(--blue-300); }
.sr-none { padding: 10px 12px; color: var(--text-muted); font-size: var(--body-sm); }

/* found-portrait locator — the locate affordance is brand cyan. REDESIGNED (RD 2026-08-02):
   the old version pulsed the hotspot itself, so it scaled with the PORTRAIT and vanished
   into dense pages ("far too subtle"). Now a dedicated ring starts at a share of the PAGE
   and contracts onto the portrait — geometry + animation are computed per-target in
   flashLocator() (app.js); this is just the constant look. */
.locator {
  position: absolute; pointer-events: none; z-index: 6;
  border-style: solid; border-color: rgba(0,174,239,.95);
  background: rgba(0,174,239,.30);
}

/* ---------- history panel ---------- */
#histpanel { position: fixed; inset: 0; background: rgba(25,21,16,.35); display: flex; align-items: flex-end; justify-content: center; z-index: 25; }

/* ---------- school video: full-screen overlay ---------- */
#videopanel { position: fixed; inset: 0; background: #000; display: flex; align-items: center; justify-content: center; z-index: 30; }
#videopanel[hidden] { display: none; }
#videoel { width: 100%; height: 100%; object-fit: contain; }
#videoframe { width: min(100%, 177.8vh); height: min(100%, 56.25vw); border: 0; background: #000; }  /* 16:9 letterbox */
#videoclose {
  position: absolute; top: 12px; right: 12px; z-index: 31;
  width: 36px; height: 36px; border-radius: 50%; border: 1px solid rgba(255,255,255,.4);
  background: rgba(0,0,0,.55); color: #fff; font-size: 16px; cursor: pointer;
}
#histhead { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); color: var(--text); }
#histlist { overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.hist-row {
  display: flex; align-items: baseline; gap: 8px; padding: 9px 11px;
  background: var(--surface-tint); border-radius: var(--radius-sm); cursor: pointer; font-size: var(--body-sm);
}
.hist-row:hover { background: var(--paper-200); }
.hist-dir { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
.hist-row.out .hist-dir { color: var(--blue-700); }
.hist-name { font-family: var(--font-display); font-weight: var(--fw-semibold); white-space: nowrap; }
.hist-text { color: var(--text-secondary); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hist-ts { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
#histclose { padding: 8px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); border-radius: var(--radius-md); cursor: pointer; }

/* ---------- redeem (code + name challenge) ---------- */
/* Redeem wears the splash ethos (RD 2026-08-11 — "the login requires the same design
   ethos"): open on the paper, no card chrome; Fanny's unlock screen is the model —
   left-aligned Fraunces display title, pill primary, links in the splash style. */
#redeem { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: center; justify-content: center; z-index: 28; overflow-y: auto; }
#redeemform { width: min(330px, 100%); padding: 16px; display: flex; flex-direction: column; gap: 12px; }
#redeemform h1 { font-family: var(--font-display); font-weight: var(--fw-semibold);
  font-size: var(--display-lg); line-height: var(--display-lg-lh); letter-spacing: var(--display-lg-ls); }
#redeemform .sub { font-size: var(--body-md); color: var(--text-secondary); margin-bottom: 4px; }
#redeemform input { padding: 10px 12px; border: 1px solid var(--border); background: var(--surface-tint); color: var(--text); border-radius: var(--radius-sm); font-size: var(--body-md); box-shadow: var(--inset-well); }
#redeemform input:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
#redeemform #redeembtn { padding: 13px; border: 0; background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; border-radius: var(--radius-full); font-size: var(--button); font-weight: var(--fw-semibold); cursor: pointer; box-shadow: var(--highlight-top), var(--glow-primary); }
#redeemform #redeembtn:disabled { opacity: .5; }
#redeemerr { color: var(--error-600); font-size: var(--body-sm); min-height: 16px; }
.linkish { background: none; border: 0; color: var(--text-secondary); font-size: var(--body-sm); cursor: pointer; text-decoration: underline; }
.linkish:hover { color: var(--ink-700); }
/* .linkish must survive the #form button primary styling (ID specificity would otherwise
   turn quiet text links into cyan pills — one primary action per view) */
#loginform button.linkish, #redeemform button.linkish, #sustep button.linkish {
  background: none; border: 0; box-shadow: none; padding: 2px 4px;
  color: var(--blue-700); font-weight: var(--fw-regular); font-size: var(--body-sm);
}
#loginform button.linkish:hover, #redeemform button.linkish:hover, #sustep button.linkish:hover { color: var(--blue-500); }
/* auth-flow secondary actions are LINKS in the design's sense: bold blue, no underline
   ("I already have an account" on Fanny's welcome screen). Applies across the whole
   login/signup/redeem ethos (RD 2026-08-11), not to the utility menu. */
#loginform button.linkish, #redeemform button.linkish, #sustep button.linkish {
  color: var(--blue-700); font-weight: var(--fw-semibold); text-decoration: none; }
#loginform button.linkish:hover, #redeemform button.linkish:hover, #sustep button.linkish:hover { color: var(--blue-500); }

/* ---------- book picker ---------- */
#bp-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
#bookpickhead { flex: 1; white-space: nowrap; }
#bp-search {
  flex: 1 1 140px; min-width: 110px; height: 30px; padding: 0 9px;
  border: 1px solid var(--border); background: var(--surface-tint); color: var(--text); border-radius: var(--radius-xs); font-size: var(--body-sm);
  box-shadow: var(--inset-well);
}
#bp-search:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
#bp-year {
  height: 30px; background: var(--surface); color: var(--ink-700); border: 1px solid var(--border);
  border-radius: var(--radius-xs); font-size: var(--body-sm); padding: 0 6px; cursor: pointer;
}
#bp-dash {
  height: 30px; padding: 0 12px; border: 0; background: var(--blue-500); color: #fff;
  border-radius: var(--radius-xs); font-size: var(--body-sm); cursor: pointer;
}
#bp-dash:hover { background: var(--blue-600); }
#bp-signout {
  height: 30px; padding: 0 12px; border: 1px solid var(--border); background: transparent;
  color: var(--text-secondary); border-radius: var(--radius-xs); font-size: var(--body-sm); cursor: pointer;
}
#bp-signout:hover { background: var(--surface-tint); color: var(--ink-700); }
#bp-none, .bp-none { padding: 10px 12px; color: var(--text-muted); font-size: var(--body-sm); }
#bookpick { position: fixed; inset: 0; background: rgba(25,21,16,.4); display: flex; align-items: center; justify-content: center; z-index: 27; }
#bookpickhead { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); color: var(--text); }
#bookpick .sheet { border-radius: var(--radius-lg); max-width: 440px; }
#bookpicklist { overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.bp-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: var(--surface-tint); border-radius: var(--radius-sm); cursor: pointer; font-size: var(--body-md); }
.bp-row:hover { background: var(--paper-200); }
.bp-title { font-family: var(--font-display); font-weight: var(--fw-semibold); flex: 1; }
.bp-id { font-size: 11px; color: var(--text-muted); font-family: ui-monospace, monospace; }

/* admin impersonation bullet + clickable zoom label */
.sr-dot { cursor: pointer; color: var(--text-muted); font-size: 15px; padding: 0 2px; }
.sr-dot:hover { color: var(--blue-500); }
.sr-dot.on { color: var(--blue-500); }
#zlbl { cursor: pointer; }

/* impersonation exit — always visible while active, outside the dropdown; this is a
   "you are acting as someone else" state, so it wears the warning tone, not brand cyan */
#searchclear {
  position: absolute; right: 4px; top: 4px; width: 26px; height: 26px;
  border: 0; border-radius: var(--radius-xs); background: var(--warning-600); color: #fff;
  font-size: 15px; line-height: 1; cursor: pointer;
}
#searchclear:hover { background: var(--marigold-700); }

/* ---------- hotspot editor (admin) ---------- */
#editbtn { width: auto !important; padding: 0 12px; font-size: var(--body-sm); white-space: nowrap; }
/* the add button stays a 34px square like zoom +, but filled = action, not zoom.
   Admin state color = PROD red (RD 2026-08-19): + red when available, Edit red while
   editing, hotspots rect red while the layer is on — one danger hue for "live tools". */
#bar #addhs { background: #c0392b; border-color: #93261a; color: #fff; font-weight: 600; }
#bar #addhs:hover { background: #a93226; }
#editbtn.on { background: #c0392b; border-color: #93261a; color: #fff; }
/* the hotspots toggle IS a hotspot-shaped rectangle: appearance:none checkbox, outline
   when off, PROD-red fill when on (same id/checked semantics — editor wiring untouched).
   PORTRAIT orientation like a real hotspot — the default + box is taller than wide
   (0.10 x 0.13 of the page); landscape read as a generic toggle, not a hotspot (RD 2026-08-23) */
.chk input#showhs {
  appearance: none; -webkit-appearance: none; margin: 0; cursor: pointer;
  width: 18px; height: 28px; border: 1.5px solid rgba(255,255,255,.55); border-radius: 4px;
  background: transparent;
}
.chk input#showhs:checked { background: #c0392b; border-color: #93261a; }
.hs.editable { cursor: move; background: rgba(0,174,239,.22); }  /* edit mode: ~25% fill, view mode stays 6% */
.hs.selected { border-color: var(--marigold-500); background: rgba(242,169,59,.16); }
.hs .tag {
  position: absolute; left: 0; bottom: -18px; font-size: 11px; line-height: 1.2;
  background: rgba(38,33,23,.85); color: var(--paper-100); padding: 1px 5px; border-radius: 4px;
  white-space: nowrap; pointer-events: none; max-width: 200px; overflow: hidden; text-overflow: ellipsis;
}
.hs .grip {
  position: absolute; right: -7px; bottom: -7px; width: 14px; height: 14px;
  background: var(--blue-500); border: 2px solid #fff; border-radius: 3px; cursor: nwse-resize;
}
.hs.selected .grip { background: var(--marigold-500); }
#edittools {
  display: flex; align-items: center; gap: 10px; padding: 8px 14px;
  background: var(--surface-tint); border-top: 1px solid var(--border); font-size: var(--body-sm);
}
#etname { color: var(--marigold-700); font-family: var(--font-display); font-weight: var(--fw-semibold); min-width: 120px; }
#assignwrap { position: relative; flex: 0 1 260px; }
#assigninput { width: 100%; height: 30px; padding: 0 9px; border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: var(--radius-xs); font-size: var(--body-sm); }
#assignlist {
  position: absolute; bottom: 34px; left: 0; right: 0; z-index: 40; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-sm); max-height: calc(var(--vh1) * 40); overflow-y: auto; box-shadow: var(--shadow-lg);
}
#assignlist div { padding: 7px 10px; cursor: pointer; font-size: var(--body-sm); border-bottom: 1px solid var(--paper-200); }
#assignlist div:hover { background: var(--surface-tint); }
#etdelete { height: 30px; padding: 0 12px; border: 1px solid var(--error-600); background: transparent; color: var(--error-600); border-radius: var(--radius-xs); cursor: pointer; font-size: var(--body-sm); }
#etdirty { color: var(--text-muted); font-size: 12px; margin-left: auto; }

/* assign panel: create-new-member action row */
#assignlist /* near-duplicate marker in the assign list (RD 2026-08-21) */
.assign-twin { font-size: 11px; color: var(--marigold-500, #f2a93b); white-space: nowrap; }

.assign-add { color: var(--blue-700); font-weight: 600; border-top: 1px solid var(--border); }

/* ---------- member actions: invite / block / report (utility row; color -> gray when used,
   the tap-to-action state convention — Member_Actions_Spec.md) ---------- */
#memberactions { display: flex; gap: 8px; flex-wrap: wrap; }
#memberactions button {
  padding: 7px 12px; border: 0; border-radius: var(--radius-md); cursor: pointer;
  font-size: var(--body-sm); background: var(--blue-500); color: #fff;
}
/* every action is FULL COLOR when available — gray is reserved for the used state
   (a ghost style here read as "used" and confused the convention; RD 2026-07-08) */
#memberactions button.used,
#memberactions button:disabled { background: var(--paper-200); color: var(--ink-400); cursor: default; }

/* first-contact permission gate + inbox invite lines + blocked state */
.gate-row {
  display: flex; align-items: center; gap: 8px; padding: 9px 11px;
  background: var(--surface-tint); border-radius: var(--radius-sm); font-size: var(--body-md);
}
.gate-row span { flex: 1; }
.gate-row button { padding: 6px 12px; border: 0; border-radius: var(--radius-md); cursor: pointer; font-size: var(--body-sm); }
.gate-row .gate-accept { background: var(--blue-500); color: #fff; }
.gate-row .gate-block { background: transparent; border: 1px solid var(--error-600); color: var(--error-600); }
.invite-row {
  padding: 9px 11px; background: var(--success-050); border-radius: var(--radius-sm); font-size: var(--body-md);
  cursor: pointer; color: var(--success-600);
}
.invite-row:hover { background: #DEEDE2; }
.blocked-note { color: var(--ink-400); font-size: var(--body-md); padding: 8px 2px; }

/* used-up compose = receipt: gray Sent button (tap -> full-message popover), read-only echo */
#sendbtn.used { background: var(--paper-200); color: var(--ink-400); box-shadow: none; }
#msgtext:read-only { color: var(--ink-400); }
#sentpop {
  padding: 10px 12px; background: var(--surface-warm); border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: var(--font-hand); font-size: 15px; white-space: pre-wrap; word-break: break-word; cursor: pointer;
  max-height: calc(var(--vh1) * 40); overflow-y: auto;
}

/* inbox bubbles jump to the sender's portrait */
.bubble.clickable { cursor: pointer; }
.bubble.clickable:hover { background: var(--paper-200); }

/* ---------- signup wizard (Signup_Flow_Spec.md) — mobile-first single card ---------- */
#suflow { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: flex-start; justify-content: center; z-index: 32; overflow-y: auto; }
/* Signup wizard wears the splash ethos too (RD 2026-08-11): open paper, no card chrome,
   Fraunces display titles per Fanny's unlock screen. Steps swap inside as before. */
#sucard { padding: 16px; width: 100%; max-width: 380px; margin: calc(var(--vh1) * 8) 14px 14px; }
#sustep { display: flex; flex-direction: column; gap: 12px; }
#sustep h1 { font-family: var(--font-display); font-weight: var(--fw-semibold);
  font-size: var(--display-lg); line-height: var(--display-lg-lh); letter-spacing: var(--display-lg-ls); }
.su-sub { font-size: var(--body-sm); color: var(--text-secondary); }
.su-hint { font-size: 12px; color: var(--text-muted); margin-top: -6px; }
#sustep input[type="text"], #sustep input[type="password"], #sustep input[type="email"], #sustep select {
  padding: 12px; border: 1px solid var(--border); background: var(--surface-tint); color: var(--text); border-radius: var(--radius-sm); font-size: 16px; /* 16px stops iOS zoom-on-focus */
  box-shadow: var(--inset-well);
}
#sustep input:focus, #sustep select:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
#sustep button:not(.linkish) { padding: 13px; border: 0; background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; border-radius: var(--radius-full); font-size: var(--button); font-weight: var(--fw-semibold); cursor: pointer; box-shadow: var(--highlight-top), var(--glow-primary); }
#sustep button:disabled { opacity: .5; }
.su-err { color: var(--error-600); font-size: var(--body-sm); min-height: 16px; }
.su-radio { display: flex; align-items: center; gap: 8px; font-size: var(--body-md); color: var(--ink-700); cursor: pointer; }
.su-dev { font-size: var(--body-sm); color: var(--warning-600); background: var(--warning-050); border-radius: var(--radius-sm); padding: 9px 11px; }
/* the cover + overlaid download ribbon (RD concept: dark diagonal corner band + glyph) */
.su-coverwrap { position: relative; width: 210px; margin: 0 auto; border-radius: var(--radius-xs); overflow: hidden; box-shadow: var(--shadow-book); }
.su-cover { display: block; width: 100%; }
.su-ribbon {
  position: absolute; top: 0; right: 0; width: 120px; height: 120px;
  background: linear-gradient(45deg, transparent 50%, rgba(38,33,23,.88) 50%);
}
.su-ribbon span { position: absolute; top: 22px; right: 16px; font-size: 26px; color: #fff; }

/* wizard refinements (RD test feedback): gray disabled buttons consistently, links, help */
#sustep button:not(.linkish):disabled { background: var(--paper-200); color: var(--ink-400); opacity: 1; cursor: default; box-shadow: none; }
#sustep a { color: var(--blue-700); }
#sucard { position: relative; }
#su-help {
  position: absolute; top: 12px; right: 14px; width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 50%; color: var(--text-secondary); text-decoration: none; font-size: 14px;
}
#su-help:hover { background: var(--surface-tint); }
#su-foot { margin-top: 16px; font-size: 12px; color: var(--text-muted); text-align: center; }
#su-foot a { color: var(--text-secondary); }

/* contact / report-an-issue overlay + dimmed login button */
#contact { position: fixed; inset: 0; background: rgba(25,21,16,.4); display: flex; align-items: center; justify-content: center; z-index: 45; }
#contact .sheet { border-radius: var(--radius-lg); max-width: 420px; margin: 0 14px; }
#contacthead { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); }
#ct-msg { padding: 10px 12px; border: 1px solid var(--border); background: var(--surface-tint); color: var(--text); border-radius: var(--radius-sm); font-size: 16px; resize: vertical; font-family: inherit; box-shadow: var(--inset-well); }
#ct-email { padding: 10px 12px; border: 1px solid var(--border); background: var(--surface-tint); color: var(--text); border-radius: var(--radius-sm); font-size: 16px; box-shadow: var(--inset-well); }
#ct-msg:focus, #ct-email:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
#ct-send { padding: 11px; border: 0; background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; border-radius: var(--radius-md); font-size: var(--button); font-weight: var(--fw-semibold); cursor: pointer; box-shadow: var(--highlight-top); }
#ct-send:disabled { background: var(--paper-200); color: var(--ink-400); cursor: default; box-shadow: none; }
#ct-close { padding: 8px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); border-radius: var(--radius-md); cursor: pointer; }
#loginform button[type="submit"]:disabled { background: var(--paper-200); color: var(--ink-400); opacity: 1; cursor: default; box-shadow: none; }
/* the disabled admin compose (RD 2026-08-19): impersonating admins see the whole sheet
   but the button is a gray, inert "Admin can't send" — sends as a student are fake
   signings, refused server-side too. Class outlives :disabled so the label stays gray. */
#sendbtn.admin-block, #sendbtn.admin-block:disabled {
  background: var(--ink-400); color: #fff; box-shadow: none; cursor: default; transform: none;
}
/* the QA process's sign-in goes GRAY and says so (RD 2026-08-19): with the admin server
   on :8090 and the student/prod server on :8092 both open, two identical blue "Sign in"
   screens invited signing into the wrong one. ID+class outranks the blue pill rules. */
#loginbtn.qa { background: var(--ink-400); box-shadow: none; }
#loginbtn.qa:hover { background: var(--ink-700); box-shadow: none; }

/* tap-to-zoom: animate only the programmatic zoom move (gestures stay transition-free) */
#page.zooming { transition: transform var(--dur-base) var(--ease-settle); }

/* book picker rows: cover thumbnail + name, NO book ids (RD 2026-07-09) */
.bp-cover { width: 34px; height: 46px; object-fit: cover; border-radius: 4px; background: var(--paper-200); flex: 0 0 auto; box-shadow: var(--shadow-sm); }
.bp-cover.ph { display: flex; align-items: center; justify-content: center; font-size: 18px; }

/* cart stub (Stripe purchase = W3 m5; this is the entry point) */
#cartpanel { position: fixed; inset: 0; background: rgba(25,21,16,.35); display: flex; align-items: flex-end; justify-content: center; z-index: 26; }
#cartpanel .sheet { border-radius: var(--radius-autograph) var(--radius-autograph) 0 0; }
#carthead { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); }
#cart-price { font-size: var(--body-md); color: var(--text-secondary); }
#cart-buy { padding: 12px; border: 0; background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; border-radius: var(--radius-md); font-size: var(--button); font-weight: 600; cursor: pointer; box-shadow: var(--highlight-top), var(--glow-primary); }
#cart-buy:disabled { background: var(--paper-200); color: var(--ink-400); cursor: default; font-weight: 400; box-shadow: none; }
#cart-close { padding: 8px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); border-radius: var(--radius-md); cursor: pointer; }

/* ---------- landing (legacy opening screen): caption, tour card, footer ---------- */
#login { overflow-y: auto; }
#landing { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: calc(var(--vh1) * 5) 16px 24px; }
#codecaption { font-size: 12px; color: var(--text-muted); }
#tourcard { position: relative; width: 180px; cursor: pointer; border-radius: var(--radius-sm); overflow: hidden;
            box-shadow: var(--shadow-book); }
#tourcard:hover { transform: translateY(-2px); }
#tourcover { display: block; width: 100%; }
#tourbtn { position: absolute; left: 10%; right: 10%; top: 38%; background: var(--blue-500); color: #fff;
           border-radius: var(--radius-sm); text-align: center; padding: 8px 4px; font-weight: 700; font-size: 15px; }
#tourbtn span { font-weight: 400; font-size: 11px; }
#landingfoot { display: flex; flex-direction: column; align-items: center; gap: 6px; margin-top: 8px; }
#landingfoot a, #landingfoot .linkish { color: var(--blue-700); font-size: var(--body-sm); }

/* help sheets */
#helpmenu, #helptopic, #pushask { position: fixed; inset: 0; background: rgba(25,21,16,.4); display: flex; align-items: center; justify-content: center; z-index: 46; }
#helpmenu .sheet, #helptopic .sheet, #pushask .sheet { border-radius: var(--radius-lg); max-width: 340px; margin: 0 14px; text-align: center; }
#helphead, #helptopic-title { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); padding-bottom: 4px; }
#helpmenu button, #helptopic button, #pushask button { padding: 11px; border: 1px solid var(--border); background: var(--surface); color: var(--ink-700); border-radius: var(--radius-md); font-size: var(--body-md); cursor: pointer; }
#helpmenu button:hover, #helptopic button:hover, #pushask button:hover { background: var(--surface-tint); }
#helpmenu #help-cancel { background: transparent; color: var(--text-secondary); border-color: transparent; }
/* the first help item is the navigation how-to (Design ruling #5) — emphasize it */
#helpmenu #help-nav { border-color: var(--brand-600, var(--ink-700)); font-weight: var(--fw-semibold); }
#helptopic-body { text-align: left; white-space: pre-wrap; font-size: var(--body-md); color: var(--ink-700); line-height: 1.5; max-height: calc(var(--vh1) * 55); overflow-y: auto; }

/* practice change-person picker */
#personpick { position: fixed; inset: 0; background: rgba(25,21,16,.4); display: flex; align-items: center; justify-content: center; z-index: 27; }
#personpick .sheet { border-radius: var(--radius-lg); max-width: 360px; margin: 0 14px; }
#personhead { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); }
#personlist { overflow-y: auto; max-height: calc(var(--vh1) * 50); display: flex; flex-direction: column; gap: 4px; }
.pp-row { padding: 9px 11px; background: var(--surface-tint); border-radius: var(--radius-sm); cursor: pointer; font-size: var(--body-md); }
.pp-row:hover { background: var(--paper-200); }
.pp-row.on { background: var(--blue-500); color: #fff; }
#personclose { padding: 8px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); border-radius: var(--radius-md); cursor: pointer; }

/* required-update banner — above every overlay (z 46 is the utility menu); a REQUIRED
   update outranks whatever the user is doing, but doesn't block the page under it */
#updatebanner { position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  padding: 10px 14px; background: var(--error-600); color: #fff; font-size: var(--body-md); font-weight: 600;
  box-shadow: var(--shadow-md); }
#updatebanner-btn { padding: 7px 14px; border: 0; border-radius: var(--radius-md); background: #fff;
  color: var(--error-600); font-size: var(--body-md); font-weight: 700; cursor: pointer; white-space: nowrap; }

/* HARD update gate (native): full-screen opaque blocker above everything (z 70 > banner 60
   > utility menu 46). No dismiss — the app is unusable until the store update lands. */
#updategate { position: fixed; inset: 0; z-index: 70; background: var(--bg);
  display: flex; align-items: center; justify-content: center; padding: 24px; }
#updategate .gate-card { max-width: 340px; text-align: center; display: flex; flex-direction: column; gap: 14px; }
#updategate .gate-title { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-md); color: var(--text); }
#updategate-text { font-size: var(--body-md); color: var(--text-secondary); line-height: 1.45; }
#updategate-btn { padding: 12px; border: 0; border-radius: var(--radius-md); background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; font-size: var(--button); font-weight: 700; cursor: pointer; box-shadow: var(--highlight-top), var(--glow-primary); }

/* legal links row on the landing footer (Privacy / Guidelines / Terms / Safety) */
#legallinks { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 14px; }
#legallinks a { color: var(--blue-700); font-size: 12px; }

/* admin utility row under the legal links (RD 2026-08-22): the FC-logo menu's buttons,
   rehomed when the logo left the splash. Quiet by design — small ghost buttons in a
   wrapped row, revealed by app.js only on a device holding fc.admin.token. */
#adminutil { display: flex; flex-direction: column; align-items: center; gap: 6px; margin-top: 10px; }
/* the device's own build — diagnostic, so quiet and monospaced enough to read a sha off */
#adminbuild, #landingbuild { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--text-secondary); user-select: text; }
#adminbtns { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }
#adminbtns button { padding: 5px 10px; border: 1px solid var(--border); background: var(--surface); color: var(--ink-700); border-radius: var(--radius-md); font-size: 12px; cursor: pointer; }
#adminbtns button:hover { background: var(--surface-tint); }
#adminbtns button.danger { color: var(--error-600); border-color: var(--error-600); }

/* contact form: the visible diagnostics sent with the report (version / device / login) */
.ct-diag { background: var(--surface-tint); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 9px 11px;
  font-size: 12px; color: var(--text-muted); line-height: 1.6; }
.ct-diag-head { color: var(--text-secondary); font-weight: 600; margin-bottom: 2px; }
.ct-diag span { color: var(--ink-700); }

/* ============================================================================
   QA PHONE PREVIEW (Design_North_Star / RD ruling #15)
   body itself becomes the phone: its transform makes every position:fixed
   overlay anchor to the frame instead of the window, so the whole app —
   sheets, scrims, banners — lives inside the bezel. The exit chip is the one
   window-anchored control (a direct child of <html>, created at bootstrap).
   body.phone (set for real phone-sized windows AND this frame) is the hook
   phone-IA styles key off — never raw media queries.
   ============================================================================ */
html.phonestage {
  background: var(--paper-200);
  display: grid; place-items: center; overflow: hidden;
}
body.phoneframe {
  width: min(390px, 96vw); height: min(844px, 94vh);
  --vh1: calc(min(844px, 94vh) / 100);   /* vh-based rules mean the FRAME, not the window */
  position: relative; overflow: hidden;
  border-radius: 44px;
  transform: translateZ(0);              /* contains position:fixed descendants */
  box-shadow: 0 0 0 11px #241F17, 0 0 0 13px #3A3328, 0 30px 80px rgba(34,30,21,.45);
}
/* iPad frames (RD 2026-08-18): same mechanism as the phone frame, portrait LOCKED by
   decision (no landscape until real-hardware verification). Squarer bezel than the phone.
   The app inside runs the PHONE IA — RD 2026-08-16: there is no iPad design, only phone —
   so these frames preview exactly what a native tablet shows, at tablet logical size. */
body.ipadframe, body.ipadproframe {
  position: relative; overflow: hidden;
  border-radius: 26px;
  transform: translateZ(0);
  box-shadow: 0 0 0 12px #241F17, 0 0 0 14px #3A3328, 0 30px 80px rgba(34,30,21,.45);
}
body.ipadframe {
  width: min(820px, 96vw); height: min(1180px, 94vh);
  --vh1: calc(min(1180px, 94vh) / 100);
}
body.ipadproframe {
  width: min(1024px, 96vw); height: min(1366px, 94vh);
  --vh1: calc(min(1366px, 94vh) / 100);
}
#phoneexit {
  position: fixed; top: 12px; right: 14px; z-index: 80;
  padding: 7px 13px; border: 1px solid var(--border); border-radius: var(--radius-full);
  background: var(--surface); color: var(--text-secondary);
  font-family: var(--font-ui); font-size: 12px; cursor: pointer; box-shadow: var(--shadow-md);
}
#phoneexit:hover { color: var(--ink-700); background: var(--surface-tint); }

/* ============================================================================
   DESIGN-MATCH PASS (RD 2026-07-14 — match the phone samples, function for function)
   Reference: docs/FC Yearbook App Design/uploads/pasted-*.png
   - Glowy cyan primary buttons
   - Floating glass bottom tab bar (Book · Autographs · Search · Buy)
   - Reader top bar as a floating dark-glass pill (phone)
   - Autograph-card message rendering (cream, Fraunces byline, Shantell body)
   - Quieter member-action buttons so Send is the one cyan action per view
   Phone-scoped chrome keys off body.phone (the QA frame + real phone widths);
   desktop keeps the flat top bar (browser-basic allowance).
   ============================================================================ */

/* ---- glowy primary buttons (the sample's cyan bloom) ---- */
#loginform button[type="submit"], #redeemform #redeembtn, #sustep button:not(.linkish),
#sendbtn, #cart-buy, #ct-send, #updategate-btn, .gate-row .gate-accept, #bp-dash {
  box-shadow: var(--highlight-top), 0 8px 22px rgba(0,174,239,.5), 0 3px 8px rgba(0,174,239,.32);
  transition: box-shadow var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
#loginform button[type="submit"]:hover, #redeemform #redeembtn:hover, #sustep button:not(.linkish):hover,
#sendbtn:hover, #cart-buy:hover, #ct-send:hover, #updategate-btn:hover, #bp-dash:hover {
  box-shadow: var(--highlight-top), 0 10px 28px rgba(0,174,239,.62), 0 4px 10px rgba(0,174,239,.4);
  transform: translateY(-1px);
}
#loginform button[type="submit"]:active, #sendbtn:active, #cart-buy:active, #ct-send:active { transform: none; }
/* used/disabled never glow (the .used + :disabled rules already zero the shadow via specificity) */

/* ---- floating glass bottom tab bar ---- */
#fctabs {
  position: fixed; left: 50%; bottom: 14px; transform: translateX(-50%);
  z-index: 24; display: none; align-items: stretch; gap: 2px;
  padding: 7px 10px; border-radius: var(--radius-full);
  background: rgba(30,26,20,.62);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: 0 10px 30px rgba(10,8,4,.42), var(--glass-highlight);
}
body.inbook #fctabs { display: flex; }   /* reader nav on phone AND desktop (RD 2026-07-15) */
body.editing #fctabs { display: none; }  /* admin editing: clear the way for the editor tools */
/* the admin editor tools read on the dark reader; float above where the tab bar was */
body.inbook #edittools { position: relative; z-index: 25; background: var(--surface); border-top: 1px solid var(--border); }
.fctab {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 3px;
  min-width: 66px; padding: 6px 8px; border: 0; background: transparent; cursor: pointer;
  color: rgba(255,255,255,.6); font-family: var(--font-ui); font-size: 11px; border-radius: var(--radius-full);
}
.fctab svg { width: 22px; height: 22px; }
.fctab .lb { line-height: 1; }
.fctab.active { color: #fff; }
.fctab.active::before {
  content: ''; position: absolute; inset: 2px; z-index: -1;
  background: rgba(255,255,255,.15); border-radius: var(--radius-full);
}
.fctab .dot {
  position: absolute; top: 3px; right: 12px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--marigold-500); border: 1.5px solid rgba(30,26,20,.9);
}
.fctab[hidden] { display: none; }

/* ============================================================================
   READER v2 (RD 2026-07-15): dark "reading room" + minimal glass bar (back-to-library
   left; search / thumbnails / eye / bookmark right) + thumbnail page strip. Universal
   in-book (phone AND desktop). Old bar controls retire to #bar-legacy (state kept).
   ============================================================================ */
body.inbook { background: #191510; }
body.inbook #status { background: transparent; color: rgba(255,255,255,.55); border-top: 0; padding: 8px 14px 0; }
body.inbook #status.hit { color: var(--marigold-500); }

/* the bar is a transparent band; its controls are floating glass pills */
body.inbook #bar { background: transparent; border-bottom: 0; padding: 10px 12px 4px; gap: 8px; align-items: center; }
#bar-legacy { display: none !important; }
.bar-spacer { flex: 1 1 auto; }
/* nowrap (RD 2026-08-19): the admin control row must stay ONE line on a phone — shrink
   below instead of wrapping the + onto a new row */
.bar-right { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; justify-content: flex-end; }
body.inbook #bar .chk { color: rgba(255,255,255,.72); }
@media (max-width: 520px) {
  .bar-right { gap: 5px; }
  .bar-right .icobtn { width: 29px; height: 29px; font-size: 15px; }
  #editbtn { padding: 0 8px; }
  .chk input#showhs { width: 16px; height: 24px; }
}
/* the admin phone-preview FRAME is narrow without a narrow viewport — same shrink,
   keyed off body.phone (set for the QA frame AND real phone widths) */
body.phone .bar-right { gap: 5px; }
body.phone.inbook #bar .icobtn { width: 32px; height: 32px; font-size: 16px; }
body.phone.inbook #bar .icobtn svg { width: 18px; height: 18px; }
body.phone #editbtn { padding: 0 8px; }
body.phone .chk input#showhs { width: 16px; height: 24px; }

/* round glass icon button — the design's floating control */
body.inbook #bar .icobtn {
  width: 40px; height: 40px; flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-full); border: 1px solid rgba(255,255,255,.16);
  background: rgba(45,39,29,.6); color: #fff; cursor: pointer; font-size: 20px; padding: 0;
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: 0 4px 14px rgba(10,8,4,.35), var(--glass-highlight);
}
body.inbook #bar .icobtn:hover { background: rgba(70,60,46,.72); }
body.inbook #bar .icobtn svg { width: 22px; height: 22px; }
body.inbook #bar .icobtn.active { background: var(--blue-500); border-color: var(--blue-500); }
body.inbook #bar .icobtn.on { color: var(--marigold-500); }          /* bookmark set */
body.inbook #bar #addhs { background: #c0392b; border-color: #93261a; }   /* admin red, matches PROD·LIVE */
/* + stays PUT when hotspots are off and greys out instead of unmounting: removing it
   re-flowed every other control in the bar, and a toolbar that moves under your thumb is
   worse than a dim button (RD 2026-08-04). #addhs is an ID rule, so the disabled state
   has to be one too or the blue fill wins on specificity. */
body.inbook #bar .icobtn:disabled,
body.inbook #bar #addhs:disabled,
body.inbook #bar #addhs:disabled:hover {
  background: rgba(45,39,29,.35); border-color: rgba(255,255,255,.08);
  color: rgba(255,255,255,.35); cursor: default; box-shadow: none;
}
/* width:auto is load-bearing — the base `#bar button` rule pins every bar button to the
   34px ICON width, so a text pill renders as a circle with its label spilling out (RD
   2026-08-19, the Dashboard button). #editbtn/#inbox/#signout/#history each carry their
   own `width:auto !important` escape; putting it on the CLASS means any future text
   button in this bar is correct by construction. No !important needed: this selector
   (1 id + 2 classes) already outranks `#bar button` (1 id + 1 type). */
body.inbook #bar .icobtn-txt {
  width: auto; white-space: nowrap;
  height: 40px; padding: 0 14px; border-radius: var(--radius-full); border: 1px solid rgba(255,255,255,.16);
  background: rgba(45,39,29,.6); color: #fff; cursor: pointer; font-size: var(--body-sm);
}
body.inbook #bar .icobtn-txt.on { background: var(--blue-500); border-color: var(--blue-500); }

/* In a WIDE browser window the whole reader toolbar clusters into a centered ~1280px band
   instead of shooting across the monitor (RD 2026-08-11) — the glass bar still spans the
   window; only its CONTENT is capped, via symmetric padding. Phones/tablets unaffected. */
body.inbook #bar { padding-left: max(14px, calc((100% - 1280px) / 2)); padding-right: max(14px, calc((100% - 1280px) / 2)); }
/* expandable "find a classmate" field (revealed by the search icon) — its own row, full
   width on a phone but CAPPED and centered in a wide browser window (RD 2026-08-11: the
   field and its results dropdown were stretching the whole desktop width) */
body.inbook #bar #searchwrap { position: relative; flex: 1 1 100%; order: 9; max-width: 560px; margin: 0 auto; }
body.inbook #bar #search {
  width: 100%; height: 40px; border-radius: var(--radius-full); border: 1px solid rgba(255,255,255,.18);
  background: rgba(45,39,29,.65); color: #fff; padding: 0 16px; font-size: 15px; box-shadow: none;
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
}
body.inbook #bar #search::placeholder { color: rgba(255,255,255,.55); }
body.inbook #bar #searchresults { background: var(--surface); }

/* thumbnail page strip — floats above the tab bar; page nav + video-first + bookmark filter */
#thumbs {
  position: fixed; left: 0; right: 0; bottom: 82px; z-index: 23;
  display: flex; align-items: stretch; justify-content: center; gap: 8px; padding: 0 10px;
}
.thumb-filter {
  width: 42px; flex: 0 0 auto; border-radius: var(--radius-md); border: 1px solid rgba(255,255,255,.16);
  background: rgba(30,26,20,.66); color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center;
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
}
.thumb-filter svg { width: 20px; height: 20px; }
.thumb-filter.on { color: var(--marigold-500); border-color: var(--marigold-500); }
.thumb-strip {
  display: flex; gap: 8px; overflow-x: auto; padding: 7px; border-radius: var(--radius-lg);
  background: rgba(30,26,20,.66); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(255,255,255,.14); box-shadow: 0 8px 24px rgba(10,8,4,.42); min-width: 0;
  /* BUG FIX 2026-07-30: without a flex basis the strip collapsed to ~16px when its only
     child was the empty-state message, clipping it to nothing. It must hold the row. */
  flex: 1 1 auto;
}
/* the empty-filter escape hatch — a real button, not a label */
.thumb-empty {
  flex: 0 0 auto; padding: 10px 16px; border: 1px solid rgba(255,255,255,.22);
  border-radius: var(--radius-md); background: rgba(70,60,46,.55); color: rgba(255,255,255,.86);
  font-size: var(--body-sm); cursor: pointer; white-space: nowrap;
}
.thumb-empty:hover { background: rgba(90,78,60,.7); }
.thumb {
  position: relative; width: 42px; height: 56px; flex: 0 0 auto; border-radius: 6px; overflow: hidden;
  cursor: pointer; background: var(--paper-200); border: 2px solid transparent;
}
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb.current { border-color: var(--blue-500); }
.thumb-num { position: absolute; bottom: 0; left: 0; right: 0; font-size: 9px; line-height: 1.4; text-align: center; background: rgba(0,0,0,.5); color: #fff; }
.thumb.video { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; background: var(--blue-900); color: #fff; }
.thumb.video svg { width: 20px; height: 20px; }
.thumb.video .vlbl { font-size: 10px; font-weight: var(--fw-semibold); letter-spacing: .06em; text-transform: uppercase; }
.thumb-ribbon { position: absolute; top: 0; right: 3px; width: 8px; height: 13px; background: var(--marigold-500); clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 76%, 0 100%); }

/* ---- autograph-card message rendering (cream card, Fraunces byline, Shantell body) ---- */
.bubble.in {
  align-self: stretch; max-width: 100%;
  background: var(--cream); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 12px 15px; box-shadow: var(--shadow-sm);
  font-family: var(--font-hand); font-size: 17px; line-height: 1.5; color: var(--ink-900);
}
.bubble.in .who {
  display: block; font-family: var(--font-display); font-weight: var(--fw-semibold);
  font-size: 15px; color: var(--ink-900); margin-bottom: 5px;
}
.bubble.out {
  background: var(--blue-050); border: 1px solid var(--blue-100); color: var(--ink-900);
  border-radius: var(--radius-lg); padding: 10px 13px; font-family: var(--font-hand); font-size: 16px;
}
.bubble.out .who { font-family: var(--font-ui); color: var(--blue-700); }

/* ---- member actions: tint style so Send stays the one cyan action ---- */
#memberactions button { background: var(--blue-050); color: var(--blue-700); }
#memberactions button:hover { background: var(--blue-100); }
#memberactions button.used, #memberactions button:disabled { background: var(--paper-200); color: var(--ink-400); }

/* ---- panel sheet reads as paper over the dark reader ---- */
body.phone #panel .sheet, body.phone #histpanel .sheet, body.phone #cartpanel .sheet { padding-bottom: 18px; }

/* ============================================================================
   FULL-SCREEN PHONE TABS: Autographs + Search (RD 2026-07-14 — match samples)
   Reached from the floating tab bar; sit at z 20 (under the tab bar 24, under
   sheets 25). Paper surface over the reader. Avatars are marigold initials
   squircles this pass — face-crop thumbnails need a thumbnail image tier
   (Mobile_Client_Spec open item), noted as the next enhancement.
   ============================================================================ */
.fcscreen {
  position: fixed; inset: 0; z-index: 20; background: var(--bg); overflow-y: auto;
  padding: 22px 20px 96px; display: flex; flex-direction: column; gap: 18px;
}
/* full screens read as a COLUMN, not wallpaper (RD 2026-08-02: the cart was "infinitely
   wide in a browser") — every section caps at a readable width and centres; phones are
   untouched (they never reach the cap) */
.fcscreen > * { width: 100%; max-width: 860px; margin-inline: auto; }
.scr-head { display: flex; flex-direction: column; gap: 12px; }
.scr-head h1 { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--display-lg); color: var(--text); }
.scr-sub { font-size: var(--body-md); color: var(--text-secondary); margin-top: -6px; }
.scr-body { display: flex; flex-direction: column; gap: 14px; }

/* segmented Received / Signed-by-me (one cyan) */
.seg { display: inline-flex; gap: 8px; }
.seg-btn {
  padding: 9px 18px; border-radius: var(--radius-full); border: 1px solid var(--border);
  background: var(--surface); color: var(--text-secondary);
  font-family: var(--font-ui); font-size: var(--body-md); cursor: pointer;
}
.seg-btn.active {
  background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; border-color: transparent;
  box-shadow: var(--highlight-top), 0 6px 16px rgba(0,174,239,.42);
}

/* marigold initials squircle avatar */
.avatar {
  width: 44px; height: 44px; flex: 0 0 auto; border-radius: var(--radius-squircle);
  background: var(--marigold-100); color: var(--marigold-700);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: 18px;
}

/* autograph card (cream, Fraunces byline, Shantell body) */
.acard {
  background: var(--cream); border: 1px solid var(--border); border-radius: var(--radius-autograph);
  box-shadow: var(--shadow-md); padding: 16px 18px; display: flex; flex-direction: column; gap: 12px;
}
.acard.tap { cursor: pointer; transition: transform var(--dur-fast) var(--ease-standard); }
.acard.tap:active { transform: scale(.99); }
.ac-head { display: flex; align-items: center; gap: 12px; }
.ac-avatar-wrap { position: relative; flex: 0 0 auto; }
.ac-dot { position: absolute; top: -2px; right: -2px; width: 12px; height: 12px; border-radius: 50%;
  background: var(--marigold-500); border: 2px solid var(--cream); }
.ac-meta { display: flex; flex-direction: column; }
.ac-name { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); color: var(--ink-900); }
.ac-date { font-size: var(--body-sm); color: var(--text-secondary); }
.ac-body { font-family: var(--font-hand); font-size: var(--autograph); line-height: 1.45; color: var(--ink-900); }
/* first-contact gate rendered as a card in the Received list */
.acard .ac-gate { display: flex; gap: 8px; }
.acard .ac-gate button { padding: 8px 14px; border: 0; border-radius: var(--radius-md); cursor: pointer; font-size: var(--body-sm); }
.acard .ac-gate .gate-accept { background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; box-shadow: var(--highlight-top), 0 6px 16px rgba(0,174,239,.4); }
.acard .ac-gate .gate-block { background: transparent; border: 1px solid var(--error-600); color: var(--error-600); }

/* search screen: field + rows */
.scr-searchwrap { position: relative; display: flex; align-items: center; }
.scr-searchwrap svg { position: absolute; left: 16px; width: 20px; height: 20px; color: var(--ink-400); pointer-events: none; }
#ss-input {
  width: 100%; height: 52px; padding: 0 16px 0 46px; border: 1px solid var(--border);
  border-radius: var(--radius-full); background: var(--surface); color: var(--text);
  font-size: 16px; box-shadow: var(--shadow-sm);
}
#ss-input:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
.ss-row {
  display: flex; align-items: center; gap: 14px; padding: 12px 14px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm); cursor: pointer; transition: transform var(--dur-fast) var(--ease-standard);
}
.ss-row:active { transform: scale(.99); }
.ss-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.ss-name { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); color: var(--ink-900); }
.ss-name .ss-me {
  margin-left: 8px; font-family: var(--font-ui); font-weight: var(--fw-semibold); font-size: 11px; letter-spacing: .04em;
  color: var(--marigold-700); border: 1px solid var(--marigold-500); border-radius: var(--radius-xs); padding: 1px 6px; vertical-align: middle;
}
.ss-sub { font-size: var(--body-sm); color: var(--text-secondary); }
.ss-chev { flex: 0 0 auto; color: var(--ink-300); }

/* shared empty state (fanning-pages spirit; one line + one hint) */
.empty { text-align: center; padding: 48px 20px; display: flex; flex-direction: column; gap: 8px; align-items: center; }
.empty .et { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-md); color: var(--ink-700); }
.empty .em { font-size: var(--body-md); color: var(--text-secondary); max-width: 280px; }

/* ============================================================================
   BUY SCREEN + SIGNED MOMENT + SIGNATURE REVEAL (RD 2026-07-14 full pass)
   ============================================================================ */

/* ---- Buy (full-screen phone tab) ---- */
.buy-product {
  display: flex; align-items: center; gap: 16px; padding: 16px 18px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
}
.buy-product .bp-cover { width: 56px; height: 74px; border-radius: var(--radius-sm); }
.buy-product .buy-pmeta { flex: 1; min-width: 0; }
.buy-product .buy-ptitle { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); color: var(--ink-900); }
.buy-product .buy-psub { font-size: var(--body-sm); color: var(--text-secondary); margin-top: 2px; }
.buy-product .buy-price { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-md); color: var(--ink-900); }

.buy-opts { display: flex; gap: 12px; }
.buy-opt {
  flex: 1; text-align: left; padding: 14px 15px; cursor: pointer;
  background: var(--surface); border: 1.5px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
}
.buy-opt.active { border-color: var(--primary); box-shadow: var(--ring-focus); }
.buy-opt-t { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); color: var(--ink-900); margin-bottom: 5px; }
.buy-opt-d { font-size: var(--body-sm); color: var(--text-secondary); line-height: 1.4; }

.buy-preview-label { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.buy-preview-label span { font-family: var(--font-ui); font-weight: var(--fw-semibold); font-size: var(--label); letter-spacing: var(--label-ls); text-transform: uppercase; color: var(--text-secondary); }
#buy-email {
  width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: var(--radius-full); background: var(--surface); color: var(--blue-700); cursor: pointer; box-shadow: var(--shadow-sm);
}
#buy-email:hover { background: var(--blue-050); }
.buy-preview-sheet { display: flex; gap: 0; background: var(--surface-tint); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; min-height: 120px; }
.buy-preview-spine {
  writing-mode: vertical-rl; text-orientation: mixed; transform: rotate(180deg);
  background: var(--ink-500); color: #fff; padding: 12px 8px; font-family: var(--font-ui); font-weight: var(--fw-semibold);
  font-size: var(--label); letter-spacing: .12em; display: flex; align-items: center; justify-content: center;
}
.buy-preview-cards { flex: 1; padding: 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-content: start; }
.buy-pcard { background: var(--paper-0); border: 1px solid var(--border); border-radius: 8px; padding: 8px 9px; }
.buy-pcard .pc-msg { font-family: var(--font-hand); font-size: 12px; line-height: 1.35; color: var(--ink-900);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.buy-pcard .pc-from { font-size: 10px; color: var(--text-secondary); margin-top: 4px; }
.buy-preview-empty { padding: 20px; font-size: var(--body-sm); color: var(--text-muted); text-align: center; }
.buy-qtyrow .su-radio { color: var(--ink-700); }
.buy-qtyrow input { width: 4em; padding: 8px 10px; border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: var(--radius-sm); font-size: 16px; box-shadow: var(--inset-well); }
.buy-checkout {
  padding: 16px; border: 0; border-radius: var(--radius-md); cursor: pointer;
  background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff;
  font-family: var(--font-ui); font-weight: var(--fw-semibold); font-size: var(--body-lg);
  box-shadow: var(--highlight-top), 0 8px 24px rgba(0,174,239,.5), 0 3px 8px rgba(0,174,239,.32);
  transition: box-shadow var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
.buy-checkout:hover { transform: translateY(-1px); box-shadow: var(--highlight-top), 0 10px 28px rgba(0,174,239,.62), 0 4px 10px rgba(0,174,239,.4); }
.buy-checkout:disabled { background: var(--paper-200); color: var(--ink-400); box-shadow: none; cursor: default; }

/* ---- Signed moment (marigold celebration; settles then auto-dismisses) ---- */
#signedmoment {
  position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at center, rgba(253,242,219,.96), rgba(253,251,246,.98));
}
.sm-burst { text-align: center; animation: smpop var(--dur-slow) var(--ease-settle) both; }
.sm-title { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--display-xl); color: var(--marigold-700); }
.sm-sub { font-family: var(--font-ui); font-size: var(--body-lg); color: var(--ink-700); margin-top: 6px; }
@keyframes smpop { 0% { transform: scale(.86); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }

/* ---- Signature reveal ---- */
#reveal {
  position: fixed; inset: 0; z-index: 48; display: flex; align-items: center; justify-content: center;
  background: rgba(25,21,16,.5); padding: 24px;
}
.reveal-card {
  width: 100%; max-width: 400px; background: var(--cream); border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg); padding: 26px 24px 18px; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  animation: smpop var(--dur-base) var(--ease-settle) both;
}
.reveal-avatar { width: 96px; height: 112px; border-radius: var(--radius-squircle); background: var(--marigold-100);
  color: var(--marigold-700); display: flex; align-items: center; justify-content: center; overflow: hidden;
  font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: 34px; margin-bottom: 8px; }
.reveal-name { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-md); color: var(--ink-900); }
.reveal-meta { font-size: var(--body-sm); color: var(--text-secondary); }
.reveal-body { font-family: var(--font-hand); font-size: var(--autograph); line-height: 1.5; color: var(--ink-900); margin: 14px 0 6px; }
.reveal-sign { font-family: var(--font-hand); font-size: var(--autograph); color: var(--ink-700); align-self: flex-end; margin-right: 6px; }
.reveal-close { margin-top: 14px; align-self: stretch; padding: 12px; border: 1px solid var(--border);
  background: transparent; color: var(--text-secondary); border-radius: var(--radius-md); cursor: pointer; font-size: var(--body-md); }

/* ============================================================================
   LOGIN SCROLL FIX + LIBRARY / BOOKSHELF HOME (RD 2026-07-14, one-screen pass)
   ============================================================================ */

/* login: top-align so a tall landing scrolls from the top and the logo is never
   clipped by flex-centering (the cut-off-logo report). */
#login { align-items: flex-start; }
#landing { padding-top: 26px; min-height: 100%; }

/* the real FC logo (assets/fc-logo.png — the cyan rounded square, corners baked in) */
.fcmark-img { width: 64px; height: 64px; display: block; }
.fcmark-img.sm { width: 40px; height: 40px; flex: 0 0 auto; }
/* library header mark — RD 2026-07-30: bigger, now that the hero cover no longer eats the
   space above the fold */
.fcmark-img.lg { width: 96px; height: 96px; flex: 0 0 auto; }
/* the bookshelf logo opens the utility menu, same as the landing one (RD 2026-08-01) */
#liblogo { cursor: pointer; user-select: none; }

/* library support footer — a mailto link AND the spelled-out address (RD) */
.lib-support { text-align: center; font-size: var(--body-sm); color: var(--text-secondary); padding: 6px 0 2px; line-height: 1.6; }
.lib-support a { color: var(--blue-700); }
.lib-support a:hover { color: var(--blue-500); }

/* ---- Library home ---- */
#library { position: fixed; inset: 0; z-index: 29; background: var(--bg); overflow-y: auto; }
.lib-inner { max-width: 760px; margin: 0 auto; padding: 26px 20px 44px; display: flex; flex-direction: column; gap: 22px; }
.lib-head { display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; }
.fcmark.sm { width: 40px; height: 40px; font-size: 19px; border-radius: 10px; flex: 0 0 auto; }
.lib-who { display: flex; flex-direction: column; align-items: center; }
#lib-title { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-lg); color: var(--text); line-height: 1.1; }
/* Change user is a LINK in the auth-flow sense (RD 2026-08-11): the splash's bold blue,
   no underline — one link language everywhere an account action lives. */
.lib-change { align-self: center; margin-top: 4px; font-size: var(--body-sm);
  color: var(--blue-700); font-weight: var(--fw-semibold); text-decoration: none; }
.lib-change:hover { color: var(--blue-500); }

/* RD 2026-07-30: hero CENTRED, and the same size as a shelf cover by construction — both
   are now a literal 150px rather than the shelf stretching to 1fr while the hero guessed a
   number. The shelf centres its tracks too, so the two blocks share an axis. */
.lib-hero { display: flex; flex-direction: column; align-items: center; gap: 10px; }
/* RD 2026-07-30: the newest book is no longer oversized — same width as a shelf cell, so
   the header (and the larger logo) get the space back. The hero KEEPS its own block for the
   title + "New messages for you" line, which the grid cells don't carry. */
.lib-hero .lib-cover { width: var(--shelf-cover); }
.lib-hero-title { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-md); color: var(--text); text-align: center; line-height: 1.15; }
.lib-hero-sub { font-size: var(--body-md); color: var(--text-secondary); text-align: center; min-height: 1.3em; }
.lib-hero-sub.unread { color: var(--marigold-700); font-weight: var(--fw-medium); }

.lib-rest-wrap { display: flex; flex-direction: column; gap: 14px; border-top: 1px solid var(--border); padding-top: 22px; }
.lib-rest-label { font-family: var(--font-ui); font-weight: var(--fw-semibold); font-size: var(--label); letter-spacing: var(--label-ls); text-transform: uppercase; color: var(--text-secondary); }
.lib-rest { display: grid; grid-template-columns: repeat(auto-fill, var(--shelf-cover)); gap: 18px; justify-content: center; }
.lib-cell { display: flex; flex-direction: column; }

/* a book cover as a physical object (shadow-book + ribbon), tap to open */
.lib-cover { position: relative; cursor: pointer; transition: transform var(--dur-base) var(--ease-settle); }
.lib-cover:hover { transform: translateY(-3px); }
.lib-cover img, .lib-cover .lib-ph {
  display: block; width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  border-radius: var(--radius-sm); box-shadow: var(--shadow-book); background: var(--paper-200);
}
.lib-cover .lib-ph { display: flex; align-items: center; justify-content: center; font-size: 30px; color: var(--ink-300); }
.lib-ribbon {
  position: absolute; top: -1px; right: 16px; width: 22px; height: 34px; background: var(--marigold-500);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 80%, 0 100%); box-shadow: var(--shadow-sm);
}
.lib-card-title { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--body-md); color: var(--ink-900); margin-top: 9px; text-align: center; line-height: 1.2; }
.lib-card-year { font-size: var(--body-sm); color: var(--text-secondary); text-align: center; }

/* Add a book — dashed placeholder tile matching the cover footprint */
.lib-add {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  aspect-ratio: 3 / 4; border: 1.5px dashed var(--ink-300); border-radius: var(--radius-sm);
  cursor: pointer; color: var(--text-secondary); font-size: var(--body-sm);
}
.lib-add:hover { border-color: var(--primary); color: var(--blue-700); background: var(--blue-050); }
.lib-add svg { width: 26px; height: 26px; }

/* ============================================================================
   PORTRAIT LIFT + block dialog + toast (RD 2026-07-15)
   Tap a portrait → the photo lifts over a dimmed page with a glass menu beneath.
   No zoom (a transition comes later). Menu entries are dynamic (see renderLiftMenu).
   ============================================================================ */
#lift {
  position: fixed; inset: 0; z-index: 40; background: rgba(25,21,16,.5);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; padding: 24px;
}
.lift-portrait {
  border-radius: var(--radius-squircle); background-size: cover; background-position: center;
  background-color: var(--paper-200); box-shadow: var(--shadow-lg);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: 56px; color: var(--marigold-700);
}
.lift-name {
  padding: 8px 18px; border-radius: var(--radius-full);
  background: rgba(45,39,29,.66); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(255,255,255,.2); color: #fff;
  font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm);
}
.lift-menu {
  width: 100%; max-width: 340px; border-radius: var(--radius-lg); overflow: hidden;
  background: rgba(45,39,29,.62); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(255,255,255,.16); box-shadow: var(--glass-shadow);
}
.lift-item {
  display: flex; align-items: center; gap: 14px; width: 100%; padding: 16px 18px; border: 0;
  background: transparent; color: #fff; cursor: pointer; text-align: left;
  font-family: var(--font-ui); font-size: var(--body-lg); border-bottom: 1px solid rgba(255,255,255,.1);
}
.lift-item:last-child { border-bottom: 0; }
.lift-item:hover { background: rgba(255,255,255,.08); }
.lift-item svg { width: 22px; height: 22px; flex: 0 0 auto; }
.lift-item.emph { font-weight: var(--fw-semibold); }
.lift-item.muted { color: rgba(255,255,255,.72); }
.lift-item.disabled { color: rgba(255,255,255,.4); cursor: default; }
.lift-item.disabled:hover { background: transparent; }

/* block confirm dialog (shell shared by the lazy-render Preparing overlay) */
#blockdlg, #agreedlg, #preparing {
  position: fixed; inset: 0; z-index: 47; background: rgba(25,21,16,.5);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
/* generic designed confirm — same card shell; above every menu/sheet it can pop over */
#confirmdlg {
  position: fixed; inset: 0; z-index: 60; background: rgba(25,21,16,.5);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
#cf-body { white-space: pre-line; }   /* copy uses \n paragraph breaks, like native confirm did */
#cf-body:empty { display: none; }     /* title-only info cards don't leave a gap */
#cf-input { padding: 10px 12px; border: 1px solid var(--border); background: var(--surface-tint); color: var(--text); border-radius: var(--radius-sm); font-size: var(--body-md); box-shadow: var(--inset-well); }
#cf-input:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring-focus); }
#cf-ok.primary {
  background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot));
  box-shadow: var(--highlight-top), var(--glow-primary);
}
#cf-ok.primary:hover { background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); }
/* AGREE / "BE NICE" (ruling #7) — reuses the block dialog's card shell */
.agree-card { text-align: center; gap: 14px; padding-bottom: 22px; }
.agree-line { font-size: var(--body-md); color: var(--ink-700); line-height: 1.5; }
.agree-big { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-md); color: var(--ink-900); }
.agree-link { font-size: var(--body-md); font-weight: var(--fw-semibold); color: var(--primary, #3b82f6); text-decoration: none; }
.agree-link:hover { text-decoration: underline; }
#agree-btn { margin-top: 4px; padding: 13px; border: 0; border-radius: var(--radius-md); cursor: pointer;
  background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff;
  font-size: var(--button); font-weight: var(--fw-semibold); letter-spacing: .04em;
  box-shadow: var(--highlight-top), var(--glow-primary); }
.bd-card {
  width: 100%; max-width: 360px; background: var(--surface); border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg); padding: 24px 22px 16px; display: flex; flex-direction: column; gap: 12px;
}
.bd-title { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-md); color: var(--ink-900); }
.bd-warn { font-size: var(--body-md); color: var(--text-secondary); line-height: 1.5; }
.bd-report { display: flex; align-items: flex-start; gap: 10px; font-size: var(--body-md); color: var(--ink-700); cursor: pointer; padding: 4px 0; }
.bd-report input { margin-top: 3px; }
.bd-confirm { margin-top: 6px; padding: 13px; border: 0; border-radius: var(--radius-md); cursor: pointer;
  background: var(--error-600); color: #fff; font-size: var(--button); font-weight: var(--fw-semibold); }
.bd-confirm:hover { background: #9c332e; }
.bd-cancel { padding: 11px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); border-radius: var(--radius-md); cursor: pointer; }

/* toast */
#toast {
  position: fixed; left: 50%; bottom: 96px; transform: translateX(-50%); z-index: 55;
  max-width: 84%; padding: 12px 18px; border-radius: var(--radius-full);
  background: rgba(38,33,23,.94); color: #fff; font-family: var(--font-ui); font-size: var(--body-md);
  box-shadow: var(--shadow-lg); text-align: center;
}

/* ============================================================================
   SIGN COMPOSER (RD 2026-07-15) — live autograph-card preview + "sign with" picker
   ============================================================================ */
#composer { position: fixed; inset: 0; z-index: 44; background: rgba(25,21,16,.4); display: flex; align-items: flex-end; justify-content: center; }
.composer-sheet {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  width: 100%; max-width: 520px; padding: 8px 18px 20px; box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 10px; max-height: calc(var(--vh1) * 92); overflow-y: auto;
}
.cmp-grip { width: 44px; height: 5px; border-radius: 3px; background: var(--paper-300); align-self: center; margin: 6px 0 4px; }
.cmp-head { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-md); color: var(--text); }
.cmp-sub { font-size: var(--body-md); color: var(--text-secondary); margin-top: -4px; }
.cmp-card { background: var(--cream); border: 1px solid var(--border); border-radius: var(--radius-autograph); box-shadow: var(--shadow-sm); padding: 16px 18px; margin-top: 4px; }
.cmp-card-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.cmp-avatar { width: 42px; height: 42px; flex: 0 0 auto; border-radius: var(--radius-squircle);
  background: var(--marigold-100); color: var(--marigold-700); background-size: cover; background-position: center;
  display: flex; align-items: center; justify-content: center; font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: 18px; }
.cmp-me { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); color: var(--ink-900); }
.cmp-date { font-size: var(--body-sm); color: var(--text-secondary); }
.cmp-text {
  width: 100%; min-height: 84px; border: 0; background: transparent; resize: none; outline: none;
  font-family: var(--sign-font, var(--font-hand)); font-size: var(--autograph); line-height: 1.5; color: var(--ink-900);
}
.cmp-text::placeholder { color: var(--ink-300); }
.cmp-rule { border-top: 1px dashed var(--ink-200); margin: 12px 0; }
.cmp-signrow { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.cmp-sign { font-family: var(--sign-font, var(--font-hand)); font-size: 26px; color: var(--ink-900); line-height: 1.1; }
.cmp-count { font-size: var(--body-sm); color: var(--text-muted); flex: 0 0 auto; }
.cmp-fontlabel { font-family: var(--font-ui); font-weight: var(--fw-semibold); font-size: var(--label); letter-spacing: var(--label-ls); text-transform: uppercase; color: var(--text-secondary); margin-top: 6px; }
.cmp-fonts { display: flex; gap: 10px; }
.cmp-font {
  flex: 1; padding: 12px 8px; border: 1.5px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); color: var(--ink-900); cursor: pointer; font-size: 20px; text-align: center; overflow: hidden;
}
.cmp-font.active { border-color: var(--primary); box-shadow: var(--ring-focus); }
.cmp-actions { display: flex; align-items: center; gap: 12px; margin-top: 8px; }
.cmp-notnow { flex: 0 0 auto; padding: 14px 18px; border: 0; background: transparent; color: var(--blue-700); font-size: var(--button); font-weight: var(--fw-semibold); cursor: pointer; }
.cmp-signit { flex: 1; padding: 15px; border: 0; border-radius: var(--radius-full); cursor: pointer;
  background: linear-gradient(var(--blue-grad-top), var(--blue-grad-bot)); color: #fff; font-size: var(--button); font-weight: var(--fw-semibold);
  box-shadow: var(--highlight-top), 0 8px 24px rgba(0,174,239,.5), 0 3px 8px rgba(0,174,239,.32); }
.cmp-signit:disabled { background: var(--paper-200); color: var(--ink-400); box-shadow: none; cursor: default; }

/* reveal: the person's PHOTO at the top (RD 2026-07-15) — crop, else initials */
.reveal-avatar.photo { background-size: cover; background-position: center; color: transparent; }

/* Autographs: name links to the portrait page (RD 2026-07-15); avatar is a face crop */
.ac-namelink { cursor: pointer; }
.ac-namelink:hover { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; }
.avatar { overflow: hidden; }

/* editor-at-prod env badge (Runbook item 6 B2b): always-visible QA/PROD indicator for admins.
   QA = calm blue, PROD·LIVE = loud red. Switchable when the book exists in both environments.
   A bar flex item next to the library icon (RD 2026-08-01) — its old fixed top-right perch
   sat exactly on the edit toolbar's add-hotspot "+". */
#envbadge {
  font: 700 11px/1 system-ui, sans-serif; letter-spacing: .1em; text-transform: uppercase;
  padding: 7px 11px; border-radius: 999px; user-select: none; cursor: default;
  flex: 0 0 auto; box-shadow: 0 2px 8px rgba(0,0,0,.28); }
#envbadge.qa   { background:#e8f1fb; color:#15528f; border:1px solid #a3c6ec; }
#envbadge.prod { background:#c0392b; color:#fff;    border:1px solid #93261a; }
#envbadge.switchable { cursor: pointer; }
#envbadge.switchable::after { content:" ⇄"; opacity:.75; margin-left:2px; }

/* ---- safe-area insets — the native shell ---------------------------------------------
   env(safe-area-inset-*) reports 0 in a normal browser and non-zero inside the Capacitor
   WebView on a notched device, so every rule here is INERT ON WEB and only bites natively.
   Requires viewport-fit=cover on the viewport meta (index.html) — without it the insets are
   always 0 and none of this applies.

   We own this rather than the WebView because capacitor.config.json sets
   ios.contentInset:"never" — deliberate, since the reader is full-bleed and an automatic
   inset would letterbox the page image. The cost of that choice is exactly these rules.

   Observed on the first iOS build (2026-07-30, iPhone 16 Pro sim) without them: the book
   bar's icon row sat underneath the Dynamic Island, and the floating tab bar collided with
   the home indicator with the status line clipped behind it. */
#bar { padding-top: calc(8px + env(safe-area-inset-top)); }
body.inbook #bar { padding-top: calc(10px + env(safe-area-inset-top)); }

/* the floating glass tab bar clears the home indicator */
#fctabs { bottom: calc(14px + env(safe-area-inset-bottom)); }

/* The status/hint line has to clear the FLOATING bottom chrome, which overlays normal flow.
   --bottomchrome is MEASURED in app.js (syncBottomChrome) from whichever chrome is on screen —
   the tab bar, or the taller page strip in thumb mode — because the hardcoded 72px here was
   wrong: the bar is 64px tall at bottom:14px, reaching 112px, while the text cleared only 106.
   Six pixels inside, which is exactly the overlap RD photographed. +18px is the breathing gap.
   Falls back to 106px if the variable is somehow unset, i.e. never worse than before. */
body.inbook #status { padding-bottom: calc(var(--bottomchrome, 88px) + 18px + env(safe-area-inset-bottom)); }

/* full-screen overlays (login, signup wizard, redeem, autographs) run edge to edge */
#login, #redeem, #suflow, .fcscreen {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

/* ---- no text-selection outside real text entry (RD on device, 2026-07-30) -------------
   An inadvertent LONG PRESS on the page image raised iOS's selection callout on the image
   itself — "really hard to get out of". -webkit-touch-callout kills the callout;
   user-select:none kills selection everywhere EXCEPT where typing happens, which keeps
   copy/paste working in the sign box, search, login and the signup wizard.
   Deliberate side effect: message text in the thread is no longer selectable — opt a
   specific element back in with `user-select: text` if that's ever wanted. */
html, body { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
/* belt to the gesture-event guard (index.html): manipulation kills DOUBLE-TAP zoom — the
   other way Safari inflates the viewport — without touching the app's own pan/drag
   surfaces, whose pan-x / none declarations override this at the element level */
html, body { touch-action: manipulation; }
input, textarea, [contenteditable="true"] {
  -webkit-user-select: text; user-select: text; -webkit-touch-callout: default;
}

/* keyboard-aware sheets: --kb (set from app.js — native Keyboard plugin or visualViewport)
   lifts every bottom sheet above the keyboard so the primary button stays reachable.
   0px everywhere a keyboard isn't covering the page. */
#panel, #histpanel, #cartpanel { padding-bottom: var(--kb, 0px); transition: padding-bottom .18s ease-out; }

/* #library was missing from the safe-area block, which is why the user name and Change user
   sat under the Dynamic Island on a notched phone (RD 2026-07-30). */
#library { padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); }

/* ---- thumb mode (RD 2026-07-30): "Pages" lives in the tab bar; opening it hands the
   bottom edge to the strip. ALL motion here is transform/opacity — compositor-only, no
   layout, no JS timers — so the animation cost is ~zero and no bitmaps are created. ---- */
#fctabs { transition: transform .24s ease, opacity .2s ease; }
body.thumbmode #fctabs { transform: translate(-50%, 160%); opacity: 0; pointer-events: none; }

/* the [x] that takes the bar's slot — center bottom, just below the strip */
#thumbclose {
  position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 24;
  width: 40px; height: 40px; border-radius: 50%; transform: translateX(-50%);
  display: none; align-items: center; justify-content: center; cursor: pointer;
  font-size: 15px; line-height: 1; color: rgba(255,255,255,.85);
  background: rgba(30,26,20,.62); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(255,255,255,.16); box-shadow: 0 10px 30px rgba(10,8,4,.42);
}
#thumbclose:hover { background: rgba(70,60,46,.72); }
body.thumbmode #thumbclose { display: flex; animation: rise-in .22s ease-out; }
@keyframes rise-in { from { transform: translate(-50%, 12px); opacity: 0; } to { transform: translateX(-50%); opacity: 1; } }

/* the strip: sits just above the [x] (the bar is gone in thumb mode), enters with a small
   rise+fade; pan-x preserves native horizontal scrolling while vertical pull-to-dismiss
   reaches the pointer handlers */
#thumbs { bottom: calc(72px + env(safe-area-inset-bottom)); touch-action: pan-x; }
#thumbs:not([hidden]) { animation: thumbs-in .22s ease-out; }
@keyframes thumbs-in { from { transform: translateY(12px); opacity: 0; } to { transform: none; opacity: 1; } }
#thumbs.thumb-snap { transition: transform .18s ease; }

/* push pre-permission ask (RD 2026-08-02) — utility-sheet styling + a distinct primary */
#pushask-head { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: var(--heading-sm); }
#pushask-body { font-size: var(--body-sm); color: var(--text-secondary); margin: 4px 0 6px; }
#pushask #pushask-yes { background: var(--primary, #3b82f6); color: #fff; border-color: transparent; font-weight: var(--fw-semibold); }

/* the unread subtitle is a LINK into Autographs/Received (RD 2026-08-03) */
.lib-hero-sub.unread { cursor: pointer; }
.lib-hero-sub.unread:hover { text-decoration: underline; text-underline-offset: 3px; }
