/* v87: ONE fixed palette for everyone, constant in the stylesheet, so
   nothing ever snaps in late on refresh. Per-user color/transparency
   prefs are gone (applyTheme only does wallpaper).
   v88: the palette is "Midnight Indigo", picked by Thomas from
   rendered swatches — an indigo accent over a pure blue-black back
   (deeper and cleaner, the violet lives in the accent alone). The light
   scheme is its pale blue-gray counterpart; its accent deepens a
   shade so accent-colored text stays readable on the pale ground
   (the same judgment call the v87 light scheme made). */
:root {
  --bg: #eef0f6;
  --surface: #ffffff;
  --surface2: #e3e7f2;
  --text: #171b28;
  --muted: #5d6480;
  --border: #ccd2e2;
  --accent: #5560b8;
  --accent-text: #ffffff;
  --surface-solid: #ffffff;
  --danger: #d43a3a;
  --done: #15803d;
  --radius: 14px;
  --k-anime: #7c5cff;
  --k-show: #0ea5e9;
  --k-movie: #f59e0b;
  --k-manga: #ec4899;
  --k-comic: #22a35a;
  --k-webcomic: #0d9488;
  --k-game: #e60012;
  --k-music: #a855f7;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0c0f13;
    --surface: #14181e;
    --surface2: #1a1f27;
    --text: #e9edf2;
    --muted: #93a0b4;
    --border: #29313d;
    --accent: #7c86d8;
    --accent-text: #10131f;
    --surface-solid: #14181e;
    --done: #4ade80;
  }
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.45;
}
main { max-width: 860px; margin: 0 auto; padding: 12px 14px 48px; }
h1 { font-size: 1.4rem; margin: 8px 0 4px; }
h2 { font-size: 1.15rem; margin: 4px 0 12px; }
.muted { color: var(--muted); font-size: .9rem; }
a { color: var(--accent); }

/* top bar */
.demo-banner {
  background: #fff3cd; color: #664d03; padding: 8px 14px; font-size: .88rem;
  text-align: center; border-bottom: 1px solid #e6d48a;
}
@media (prefers-color-scheme: dark) {
  .demo-banner { background: #3a3212; color: #f2dfa0; border-color: #6b5c1e; }
}
.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 14px; background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 35; /* above .main-nav (30) so the user menu paints over it; still below modals (40) */
  transition: transform .3s ease;
}
/* v61: while scrolling down the topbar slides up out of sight; any real
   scroll back up — or reaching the top — brings it back. The slide moves
   only the bar itself (no margin trick): shrinking the page as it hid made
   the browser yank the scroll position back and forth on short pages, which
   read as shaking. A stuck bar's space is already above the viewport once
   scrolled, so nothing is left gaping behind it. */
body.topbar-hidden .topbar { transform: translateY(-110%); }
.brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 1.1rem; }
.brand-icon { width: 28px; height: 28px; border-radius: 7px; }
.topbar-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* v96: the topbar identity (avatar + name) is the Profile door — the
   Profile nav tab is gone, so the whole cluster is one button. */
.me-btn { display: flex; align-items: center; gap: 6px; background: none; border: none; padding: 4px 10px 4px 4px; border-radius: 999px; cursor: pointer; font: inherit; color: inherit; }
.me-btn:hover { background: var(--surface2); }
.user-email { color: var(--muted); font-size: .85rem; max-width: 38vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* buttons, inputs */
.btn {
  appearance: none; border: 1px solid var(--border); background: var(--surface2);
  color: var(--text); border-radius: 10px; padding: 8px 13px; font-size: .92rem;
  cursor: pointer; min-height: 40px;
}
.btn:hover { filter: brightness(1.06); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); font-weight: 600; }
.btn-ghost { background: transparent; border-color: transparent; color: var(--accent); min-height: 36px; padding: 6px 8px; }
.btn-danger { background: transparent; border-color: var(--danger); color: var(--danger); }
.btn-small { min-height: 34px; padding: 5px 10px; font-size: .85rem; }
.btn-block { width: 100%; }
.linklike { background: none; border: none; color: var(--accent); cursor: pointer; font-size: inherit; padding: 0; text-decoration: underline; }
input[type="text"], input[type="email"], input[type="password"], input[type="url"],
input[type="search"], select, textarea {
  width: 100%; padding: 10px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--text); font-size: 1rem; min-height: 42px;
}
textarea { resize: vertical; }
label { display: block; margin: 10px 0; font-size: .92rem; font-weight: 600; }
label input, label select, label textarea { margin-top: 5px; font-weight: 400; }
fieldset { border: 1px solid var(--border); border-radius: var(--radius); margin: 14px 0; padding: 10px 12px 12px; }
legend { font-weight: 700; font-size: .92rem; padding: 0 4px; }
.form-error { color: var(--danger); font-size: .9rem; }
.form-ok { color: #4ade80; font-size: .95rem; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* tabs — v92: the family bar (Watch / Read / Play / Listen / All,
   on My List and on the shared profile view) wears the shared
   segmented-pill language defined with .vtab-seg below: an inline
   pill track, borderless pills, the active family accent-filled.
   Only the bar's own spacing and pill sizing live here; the track,
   pill chrome and active fill are the one shared definition. */
/* v99: the family bar rides ONE row at the Guides seg scale —
   it used to wrap into a two-row stadium blob with oversized
   pills (Thomas circled it: "clean up the top tabs and make them
   look like they do in guides"). One row, chip-btn sizing; on a
   narrow screen the row scrolls sideways like the nav carousel
   instead of wrapping. */
.tabs { margin: 6px 0 12px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; max-width: 100%; gap: 3px; }
.tabs::-webkit-scrollbar { display: none; }
.tabs .tab { font-size: .85rem; font-weight: 600; padding: 6px 9px; flex: 0 0 auto; }

/* toolbar */
.toolbar { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
.toolbar input[type="search"] { grid-column: 1 / -1; }
@media (min-width: 640px) {
  .toolbar { grid-template-columns: 2fr 1fr 1fr 1fr 1fr; }
  .toolbar input[type="search"] { grid-column: auto; }
}
.count-line { color: var(--muted); font-size: .9rem; margin: 8px 2px; }
.empty { color: var(--muted); text-align: center; padding: 32px 10px; }

/* cards */
.list { display: flex; flex-direction: column; gap: 10px; }
.card-row {
  display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 0; overflow: hidden; cursor: pointer; text-align: left; width: 100%;
  color: var(--text); font: inherit;
}
.card-row.read-type { border-left: 5px solid var(--k-manga); }
.card-row.play-type { border-left: 5px solid var(--k-game); }
.card-row.listen-type { border-left: 5px solid var(--k-music); }
.chip.platform { border-color: var(--k-game); }
.chip.coll { background: var(--surface2); font-weight: 600; }
.price-line { font-weight: 700; font-size: .85rem; }
.cover {
  width: 62px; height: 88px; flex: 0 0 62px; border-radius: 8px; object-fit: cover;
  background: var(--surface2);
}
.cover-placeholder {
  width: 62px; height: 88px; flex: 0 0 62px; border-radius: 8px; background: var(--surface2);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  color: var(--muted); text-align: center; padding: 4px;
}
.cover-placeholder .initial { font-size: 1.5rem; font-weight: 800; color: var(--accent); line-height: 1; }
.cover-placeholder .no-cover { font-size: .55rem; text-transform: uppercase; letter-spacing: .04em; }
.card-main { flex: 1; min-width: 0; }
.card-title { font-weight: 700; font-size: 1.02rem; margin-bottom: 3px; }
.meta-row { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin: 3px 0; }
.badge {
  font-size: .7rem; font-weight: 700; padding: 2px 8px; border-radius: 999px; color: #fff;
  text-transform: uppercase; letter-spacing: .03em;
}
.badge.kind-anime { background: var(--k-anime); }
.badge.kind-show { background: var(--k-show); }
.badge.kind-movie { background: var(--k-movie); color: #231a00; }
.badge.kind-manga { background: var(--k-manga); }
.badge.kind-comic { background: var(--k-comic); }
.badge.kind-webcomic { background: var(--k-webcomic); }
.badge.kind-game { background: var(--k-game); }
.badge.kind-music { background: var(--k-music); }
.badge.status { background: var(--surface2); color: var(--muted); text-transform: none; letter-spacing: 0; font-weight: 600; }
.rating-star { font-size: .85rem; font-weight: 700; color: var(--accent); }
.rating-star .cat-avg { color: var(--muted); font-weight: 500; }
.chip-row { display: flex; flex-wrap: wrap; gap: 5px; margin: 3px 0; }
.chip {
  font-size: .72rem; padding: 2px 8px; border-radius: 999px; background: var(--surface2);
  color: var(--text); border: 1px solid var(--border);
}
.chip.source { background: transparent; border-color: var(--accent); color: var(--accent); }
a.chip.source { text-decoration: none; }

/* auth */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; }
.auth-card { max-width: 420px; margin: 30px auto; }
.auth-switch { text-align: center; color: var(--muted); }

/* modals */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(8, 8, 16, .55); z-index: 40;
  display: flex; align-items: flex-start; justify-content: center; overflow-y: auto; padding: 14px;
}
.modal {
  background: var(--surface-solid); border-radius: var(--radius); padding: 18px; width: 100%;
  max-width: 560px; margin: auto 0; position: relative;
}
.modal-close {
  position: absolute; top: 10px; right: 10px; background: var(--surface2); border: 1px solid var(--border);
  color: var(--text); width: 36px; height: 36px; border-radius: 50%; cursor: pointer; font-size: 1rem;
}
.modal-actions { display: flex; gap: 8px; align-items: center; margin-top: 14px; }
.modal-actions .spacer { flex: 1; }
.rating-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 3px 0; }
.rating-row span { font-size: .92rem; }
.rating-select { width: 110px; }
.genre-group-title { font-size: .8rem; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; margin: 8px 0 4px; }
.check-grid { display: flex; flex-wrap: wrap; gap: 6px; }
.check-chip {
  display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--border);
  border-radius: 999px; padding: 5px 10px; font-size: .82rem; font-weight: 500; margin: 0; cursor: pointer;
  background: var(--surface);
}
.check-chip input { accent-color: var(--accent); margin: 0; }
.check-chip:has(input:checked) { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.source-row { display: grid; grid-template-columns: 1fr 1fr auto; gap: 6px; margin: 6px 0; align-items: center; }
.source-row input { min-height: 38px; }
.toggle-row { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.toggle-row input { width: 20px; height: 20px; accent-color: var(--accent); }

/* lightbox */
.lightbox {
  position: fixed; inset: 0; background: rgba(5, 5, 10, .88); z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.lightbox figure { margin: 0; text-align: center; max-width: min(92vw, 440px); }
.lightbox img {
  max-width: 100%; max-height: 72vh; border-radius: 10px; object-fit: contain;
  box-shadow: 0 10px 40px rgba(0,0,0,.5);
}
.lightbox figcaption { color: #f2f2f8; margin-top: 12px; }
.lb-title { font-weight: 700; font-size: 1.1rem; }
.lb-meta { color: #c9c9da; font-size: .9rem; margin: 3px 0 6px; }
.lightbox .chip { background: rgba(255,255,255,.14); color: #fff; border-color: rgba(255,255,255,.3); }
.lightbox .chip-row { justify-content: center; }
.lightbox-close {
  position: absolute; top: 14px; right: 14px; width: 42px; height: 42px; border-radius: 50%;
  background: rgba(255,255,255,.16); color: #fff; border: 1px solid rgba(255,255,255,.35);
  font-size: 1.1rem; cursor: pointer;
}

.toast {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 80;
  background: var(--text); color: var(--bg); padding: 10px 16px; border-radius: 999px;
  font-size: .92rem; box-shadow: 0 6px 24px rgba(0,0,0,.3); max-width: 92vw; text-align: center;
}
.shared-title { margin-top: 14px; }
[hidden] { display: none !important; }

.avatar-sm { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; vertical-align: middle; }
.avatar-lg { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; }
.avatar-fallback { display: inline-flex; align-items: center; justify-content: center; background: var(--accent, #7c86d8); color: var(--accent-text, #10131f); font-weight: 700; }
span.avatar-fallback.avatar-sm { font-size: .95rem; }
span.avatar-fallback.avatar-lg { font-size: 1.5rem; }
.profile-bar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.profile-meta { flex: 1 1 auto; min-width: 140px; }
.profile-name { font-weight: 700; font-size: 1.05rem; }
.profile-actions { display: flex; gap: 8px; align-items: center; }
#sharedAvatar { margin-bottom: 6px; }

.security-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 8px; flex-wrap: wrap; }
.mfa-qr { width: 180px; height: 180px; background: #fff; padding: 8px; border-radius: 10px; }
.mfa-secret code { font-size: 1rem; letter-spacing: 1px; word-break: break-all; }

.linked-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.linked-list:empty { margin-top: 0; }
.linked-row { display: flex; align-items: center; gap: 10px; background: var(--surface2); border: 1px solid var(--border, rgba(255, 255, 255, .08)); border-radius: 10px; padding: 8px 10px; }
.linked-row .avatar-sm { flex: 0 0 auto; }
.linked-meta { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.linked-row .g-mark { font-weight: 800; color: #4285F4; font-size: 1.05rem; flex: 0 0 auto; }

.friend-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; }
.friend-row .friend-name { flex: 1 1 auto; font-weight: 600; }
.friend-row .avatar-sm { flex: 0 0 auto; }
#friendsCard h4 { margin: 14px 0 4px; }

.main-nav { position: sticky; top: 8px; z-index: 30; display: flex; gap: 6px; padding: 6px; margin: 6px 0 16px; background: var(--surface); border: 2px solid var(--accent); border-radius: 999px; box-shadow: 0 6px 18px rgba(90, 60, 10, .25); }
.nav-btn { flex: 1 1 0; padding: 11px 2px; border: none; border-radius: 999px; background: var(--surface2); color: var(--text); font-weight: 700; font-size: .98rem; cursor: pointer; }
.nav-btn.active { background: var(--accent); color: var(--accent-text); box-shadow: 0 2px 8px rgba(0, 0, 0, .3); }
.nav-tip { display: none; } /* v83: the desktop rail's hover name label — only exists at 1024px+ */
.app-section textarea { width: 100%; box-sizing: border-box; }
.friend-meta { flex: 1 1 auto; display: flex; flex-direction: column; }
/* v94: a friend row carries several actions (Call/Video/Remove now).
   On a phone they wrap onto their own line under the name instead of
   crushing it; the name keeps a floor so it never squeezes to nothing. */
#friendsCard .friend-row { flex-wrap: wrap; }
#friendsCard .friend-meta { min-width: 130px; }
#friendsCard .friend-row .btn { flex: 0 0 auto; }
.friend-bio { font-size: .85rem; }
/* v95: the Friends hub — rich contact rows (big avatar, presence dot on
   its corner, bold name, one soft second line) that open the
   conversation on tap, and the conversation header inside the hub. */
.hub-row { cursor: pointer; padding: 10px 0; border-top: 1px solid var(--border); }
#friendList .hub-row:first-child { border-top: none; }
.hub-row .avatar-wrap { flex: 0 0 auto; }
.hub-row .friend-name { font-size: 1.05rem; font-weight: 700; }
.hub-row .friend-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.convo-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.convo-head .avatar-wrap { flex: 0 0 auto; }
.convo-head .friend-meta { flex: 1 1 auto; min-width: 0; }
.convo-head .spacer { flex: 1; }
#dmThreadTitle { font-size: 1.08rem; }
#dmThreadTitle .profile-door, .convo-head .profile-door { cursor: pointer; }
/* v95: call records sit in the thread as centered log cards (the
   reference style), not as chat bubbles. */
.dm-log { align-self: center; max-width: 88%; background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 8px 18px; text-align: center; }
.dm-log-body { font-weight: 700; }
.dm-log-date { font-size: .78em; opacity: .75; margin-top: 2px; }
#bioCard .profile-actions { justify-content: flex-end; margin-top: 8px; }

.divider { display: flex; align-items: center; gap: 10px; margin: 14px 0 10px; color: var(--muted); font-size: .85rem; }
.divider::before, .divider::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
.btn-google { background: #fff; color: #1f1f1f; border: 1px solid #dadce0; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 10px; }
.btn-google .g-mark { font-weight: 800; color: #4285F4; font-size: 1.05rem; }

/* v21 tidy-up: filters panel, more menu, toast action, chip cap */
.toolbar-main { grid-template-columns: 1fr auto; align-items: start; }
.toolbar-main input[type="search"] { grid-column: auto; }
.filters-panel { margin-top: -2px; }
.filter-badge {
  display: inline-block; min-width: 1.25rem; padding: 0 .3rem; margin-left: .3rem;
  border-radius: 999px; background: var(--accent); color: var(--accent-text);
  font-size: .75rem; font-weight: 700; text-align: center;
}
.more-wrap { position: relative; }
/* v91: the More menu joins the tab family — an attached panel in
   the shared language (surface2 fill, shared border, no floating
   card shadow), hung flush under its tab and anchored to the
   tab's right edge, so its square corner is the top-right one.
   v92: its items join the pill language too — full-width pill
   buttons stacked in the panel, on the card colour so they read
   as siblings of the Watch order panel's pills (the primary Add
   keeps its accent fill). */
.more-menu {
  right: 0; left: auto; border-radius: 12px 0 12px 12px;
  display: flex; flex-direction: column; gap: 6px; min-width: 210px;
  padding: 8px;
}
.more-menu .btn { justify-content: flex-start; border-radius: 999px; }
.more-menu .btn:not(.btn-primary) { background: var(--surface); }
.chip.more { border-style: dashed; font-weight: 700; }
.toast .toast-btn {
  margin-left: 10px; padding: 2px 12px; border-radius: 999px; border: none;
  background: var(--accent); color: var(--accent-text); font-weight: 700; cursor: pointer;
}
#lightboxAddBtn { margin-top: 10px; }

/* v22 profile style: wallpaper layer, headers, photo gallery, comments */
#bgLayer {
  position: fixed; inset: 0; z-index: -1; background-size: cover;
  background-position: center; background-repeat: no-repeat;
}
#bgLayer.has-wallpaper::after {
  content: ""; position: absolute; inset: 0; background: rgba(16, 11, 6, .45);
}
.field-label { font-weight: 600; margin: 12px 0 6px; }
.header-preview, .profile-header {
  width: 100%; height: 150px; object-fit: cover; border-radius: 12px; display: block;
}
.header-preview { margin: 4px 0 8px; }
.profile-header { margin-bottom: 10px; }
.photo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 8px 0; }
/* v68: on a viewed profile the gallery sits up top in modest tiles (the
   owner's own management grid below keeps the big 3-across tiles). */
.shared-photo-grid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); }
.song-frame { width: 100%; border: 0; border-radius: 12px; display: block; margin: 6px 0 2px; background: var(--surface2); }
/* v70: names and pictures are doors to profiles, everywhere. */
.profile-door { cursor: pointer; }
.friend-name.profile-door:hover, .post-author.profile-door:hover, .comment-name.profile-door:hover { text-decoration: underline; }
/* v70: a locked profile hides the list furniture, keeping the lock note. */
#sharedView.locked #sharedFamilyTabs,
#sharedView.locked .toolbar,
#sharedView.locked .shared-note { display: none; }
/* v68 dome gallery: with 4+ photos the shared-profile gallery wraps its
   tiles around a slow cylinder in two curved rows (drag to spin). */
.photo-grid.dome {
  display: block; position: relative; height: 300px; margin: 8px 0;
  overflow: hidden; perspective: 1100px; perspective-origin: 50% 42%;
  touch-action: pan-y; cursor: grab;
}
.photo-grid.dome:active { cursor: grabbing; }
.photo-grid.dome .photo-tile {
  position: absolute; left: 50%; top: 50%; width: 168px; height: 126px;
  aspect-ratio: auto; backface-visibility: hidden; will-change: transform;
}
.photo-grid.dome .photo-tile.dome-hover {
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .65), 0 0 28px rgba(255, 255, 255, .35);
}
.photo-tile {
  position: relative; border: none; padding: 0; border-radius: 10px; overflow: hidden;
  cursor: pointer; background: var(--surface2); aspect-ratio: 1;
}
.photo-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-tile .photo-tile-label {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 10px 6px 4px;
  font-size: .72rem; color: #fff; text-align: left;
  background: linear-gradient(transparent, rgba(0,0,0,.75));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.photo-modal { max-width: 560px; }
.photo-view-img { width: 100%; max-height: 55vh; object-fit: contain; border-radius: 10px; background: #000; }
.photo-label-row { display: flex; align-items: center; gap: 12px; margin: 8px 0; flex-wrap: wrap; }
.photo-label-row .lb-title { flex: 1; }
.comments { display: flex; flex-direction: column; gap: 10px; margin: 12px 0; max-height: 40vh; overflow-y: auto; }
.comment { display: flex; gap: 8px; align-items: flex-start; }
.comment .avatar-sm { flex: none; }
.comment .avatar-fallback-sm {
  width: 32px; height: 32px; border-radius: 50%; flex: none; display: inline-flex;
  align-items: center; justify-content: center; background: var(--surface2);
  font-weight: 700; font-size: .85rem;
}
.comment-body { flex: 1; min-width: 0; }
.comment-head { display: flex; gap: 8px; align-items: baseline; }
.comment-name { font-weight: 700; font-size: .88rem; }
.comment-time { font-size: .72rem; }
.comment-text { font-size: .92rem; overflow-wrap: break-word; }
.comment-media { max-width: 240px; max-height: 240px; border-radius: 8px; display: block; margin-top: 4px; }
.comment-del { margin-left: auto; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* v23 appearance studio */
.appearance-modal { max-width: 520px; }

/* v23 profile video + card layout tools */
/* v69: the profile video is back, but capped — on a wide PC screen the old
   full-width player filled the whole monitor. 640px is a comfortable size
   and it stays centred; on phones the cap never binds. */
.video-wrap { position: relative; width: 100%; max-width: 640px; aspect-ratio: 16 / 9; margin: 8px auto; border-radius: 12px; overflow: hidden; background: #000; }
.video-wrap iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.card-tools { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
.card-tools:active { cursor: grabbing; }
.card-tools .card-name { font-weight: 700; margin-right: auto; font-size: .95rem; }
.card-grip { color: var(--muted); font-size: .8rem; letter-spacing: 1px; }
.card-chevron { color: var(--muted); width: 1em; text-align: center; }
.card.dragging { opacity: .85; box-shadow: 0 10px 30px rgba(0, 0, 0, .35); }
/* v65: the little shake that says "I'm movable now" once the hold arms. */
@keyframes drag-shake { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(.7deg); } 75% { transform: rotate(-.7deg); } }
.card.drag-armed { animation: drag-shake .3s ease-in-out 2; }
/* v66: while dragged, the tile floats with the pointer (slightly tilted,
   like it was picked up) and a dashed slot holds its place. */
.drag-float { position: fixed; z-index: 60; pointer-events: none; transform: rotate(1.2deg) scale(1.02); box-shadow: 0 18px 40px rgba(0, 0, 0, .45); transition: transform .15s ease; }
.drag-placeholder { border: 2px dashed var(--border); border-radius: 14px; background: var(--surface2); opacity: .55; box-sizing: border-box; flex: none; }
/* v67: the nav buttons themselves are the drag surface now. */
.nav-btn { touch-action: none; user-select: none; -webkit-user-select: none; }
.nav-btn:active { cursor: grabbing; }
.card.collapsed > *:not(.card-tools) { display: none !important; }
.card.collapsed { padding-bottom: 10px; }

/* v24 the Feed */
.feed-filters { display: flex; gap: 6px; flex-wrap: wrap; margin: 10px 0; }
.chip-btn {
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  border-radius: 999px; padding: 6px 12px; cursor: pointer; font-size: .88rem;
}
.chip-btn.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.feed-list { display: flex; flex-direction: column; gap: 12px; }
.post-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.post-head .avatar-sm { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; }
.post-head .avatar-fallback-sm { width: 38px; height: 38px; font-size: 1rem; }
.post-author { font-weight: 700; }
.post-time { font-size: .75rem; }
.post-head .follow-btn { margin-left: auto; }
.post-body { white-space: pre-wrap; overflow-wrap: break-word; margin: 4px 0 8px; }
.post-body a { color: var(--accent); }
.post-media { width: 100%; max-height: 480px; object-fit: contain; border-radius: 10px; background: #000; display: block; }
.video-facade { position: relative; cursor: pointer; border-radius: 10px; overflow: hidden; }
.video-facade img { width: 100%; display: block; }
.video-facade .play-badge {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 3rem; color: #fff; text-shadow: 0 2px 12px rgba(0,0,0,.8);
}
.link-card {
  display: block; border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
  color: var(--text); text-decoration: none; background: var(--surface2);
}
.link-card .link-domain { font-weight: 700; font-size: .85rem; color: var(--accent); }
.link-card .link-url { font-size: .8rem; overflow-wrap: anywhere; }
.post-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.post-actions button, .post-actions a {
  border: 1px solid var(--border); background: var(--surface2); color: var(--text);
  border-radius: 999px; padding: 5px 12px; cursor: pointer; font-size: .86rem; text-decoration: none;
}
.post-actions button.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.post-comments { margin-top: 10px; border-top: 1px solid var(--border); padding-top: 10px; }
.post-comment-form { display: flex; gap: 6px; margin-top: 8px; }
.post-comment-form input[type="text"] { flex: 1; }


/* v26 studio: bottom sheet + live theme preview */
#appearanceModal { align-items: flex-end; padding: 0; }
#appearanceModal .modal {
  margin: 0; width: 100%; max-height: 84vh; overflow-y: auto;
  border-radius: 18px 18px 0 0;
}
.theme-preview {
  position: relative; border-radius: 12px; overflow: hidden; padding: 14px;
  margin: 10px 0 4px; min-height: 118px; display: flex; align-items: center;
  background-size: cover; background-position: center; background-color: var(--bg);
  border: 1px solid var(--border);
}
.theme-preview-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; width: 100%;
}
.theme-preview-card p { margin: 4px 0 8px; }

/* v30 showcase */
.show-group { margin: 10px 0; }
.show-label { font-weight: 700; margin-bottom: 6px; }
.show-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.show-chip {
  display: inline-flex; align-items: center; gap: 7px; padding: 4px 10px 4px 4px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface2);
  font-size: .9rem;
}
.show-chip img { width: 26px; height: 36px; object-fit: cover; border-radius: 6px; }
.show-chip .show-x {
  border: none; background: none; color: var(--muted); cursor: pointer; font-size: .95rem; padding: 0 2px;
}
.show-fav-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; flex-wrap: wrap; }
.picker-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; max-height: 46vh; overflow-y: auto; }
.picker-row {
  display: flex; align-items: center; gap: 10px; text-align: left; width: 100%;
  background: var(--surface2); border: 1px solid var(--border); border-radius: 10px;
  padding: 6px 10px; color: var(--text); font: inherit; cursor: pointer;
}
.picker-row img { width: 30px; height: 42px; object-fit: cover; border-radius: 5px; }

/* v78: feed photo viewer + the poster's download choice */
.post-media.zoomable { cursor: zoom-in; }
.feed-photo-modal { max-width: 680px; }
.feed-photo-img { width: 100%; max-height: 62vh; object-fit: contain; background: #0d0a24; border-radius: 10px; margin: 8px 0; }
.check-row { display: flex; gap: 8px; align-items: center; margin-top: 8px; flex-wrap: wrap; }

/* v76: My List pagination — 40 to a page, controls above and below */
.list-pager { display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; margin: 10px 0; }
.list-pager[hidden] { display: none; }
.list-pager .btn.active { background: var(--accent); color: var(--accent-text); }
.list-pager .pager-info { color: var(--muted); font-weight: 700; padding: 0 4px; }

/* v75: search-and-add zone at the top of the entry editor */
.add-search { background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; margin-bottom: 14px; }
.add-search .add-search-hint { margin: 8px 0 0; font-size: .88rem; }
.add-search .picker-list { max-height: 38vh; }
#sharedShowcase { margin: 12px 0; }

/* v31 invites */
.invite-preview {
  background: var(--surface2); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; font-size: .92rem; white-space: pre-wrap; word-break: break-word;
}
.invite-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.invite-grid .btn { text-decoration: none; }

/* v32 welcome gate: black backdrop, animated logo hero, agreement checks */
body.auth-mode { background: #000; }
.welcome-hero { text-align: center; margin-bottom: 4px; }
.welcome-logo {
  width: 92px; height: 92px; border-radius: 22px;
  filter: drop-shadow(0 8px 28px rgba(124, 58, 237, .5));
  animation: logoIn .7s cubic-bezier(.2, 1.4, .4, 1) both;
}
.welcome-name { font-size: 2rem; font-weight: 800; letter-spacing: .5px; margin-top: 8px; animation: riseIn .6s .15s ease both; }
.welcome-tag { color: var(--muted); margin: 2px 0 10px; animation: riseIn .6s .3s ease both; }
#authView { animation: riseIn .5s ease both; }
@keyframes logoIn { from { transform: scale(.5) translateY(16px); opacity: 0; } to { transform: scale(1) translateY(0); opacity: 1; } }
@keyframes riseIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.agree-row { display: flex; gap: 9px; align-items: flex-start; margin: 9px 0; font-size: .95rem; cursor: pointer; }
.agree-row input { margin-top: 2px; width: 19px; height: 19px; accent-color: var(--accent); flex: none; }
#authSubmit:disabled { opacity: .45; cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) {
  .welcome-logo, .welcome-name, .welcome-tag, #authView { animation: none; }
}

/* v33 password eyes */
.pw-wrap { position: relative; display: block; }
.pw-wrap input { padding-right: 46px; }
.pw-eye {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  background: none; border: none; cursor: pointer; font-size: 1.05rem;
  padding: 7px; line-height: 1;
}

/* v34 topbar menu, notifications, grid view, install hint */
.menu-wrap { position: relative; display: inline-block; }
.user-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 46;
  display: flex; flex-direction: column; gap: 6px; min-width: 200px;
  background: var(--surface-solid); border: 1px solid var(--line); border-radius: 12px;
  padding: 8px; box-shadow: 0 12px 32px rgba(0,0,0,.35);
}
.user-menu .btn { justify-content: flex-start; text-align: left; }
.notif-btn { position: relative; }
.notif-badge {
  position: absolute; top: -4px; right: -4px; min-width: 19px; height: 19px;
  border-radius: 999px; background: #e0245e; color: #fff; font-size: .7rem;
  font-weight: 700; line-height: 19px; text-align: center; padding: 0 4px;
}
.notif-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; max-height: 55vh; overflow-y: auto; }
.notif-item {
  display: flex; gap: 10px; align-items: flex-start; text-align: left; width: 100%;
  background: var(--surface2); border: 1px solid var(--border); border-radius: 10px;
  padding: 9px 11px; color: var(--text); font: inherit; cursor: pointer;
}
.notif-item.unread { border-color: var(--accent); }
.ios-hint {
  margin: 10px auto 0; max-width: 640px; background: var(--surface-solid);
  border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px;
  display: flex; gap: 8px; align-items: center; justify-content: space-between;
}
/* v92: .view-toggle is a hook class only — the three view switches
   are .vtab-seg pill tracks now, and the shared block below carries
   their track, pill and active-fill styling. */
.list.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(108px, 1fr)); gap: 12px; }
.grid-tile {
  display: flex; flex-direction: column; gap: 6px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 12px; padding: 8px;
  color: var(--text); font: inherit; cursor: pointer; text-align: left; width: 100%;
}
.grid-tile .cover, .grid-tile .cover-placeholder {
  width: 100%; height: auto; aspect-ratio: 2 / 3; object-fit: cover; border-radius: 8px;
}
.grid-tile .cover-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; }
.grid-title { font-weight: 600; font-size: .9rem; line-height: 1.25; }
.grid-meta { color: var(--muted); font-size: .78rem; }
.starter-card { text-align: left; }
.starter-card .starter-steps { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }

/* v37 release alerts */
.track-chip {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 10px; padding: 8px 10px; margin-bottom: 8px;
}
.anilist-result { display: flex; gap: 10px; align-items: center; width: 100%; text-align: left; }
.anilist-result img { width: 40px; height: 56px; object-fit: cover; border-radius: 6px; }

/* v39 sign-up about box */
.about-box {
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 12px; padding: 12px 14px; margin: 0 0 14px; text-align: left;
}
.about-box p { margin: 6px 0; }
.about-list { margin: 6px 0; padding-left: 2px; list-style: none; }
.about-list li { margin: 4px 0; }

/* v40 danger zone, reports, moderation */
.btn-danger { background: #b3261e; border-color: #b3261e; color: #fff; }
.btn-danger:disabled { opacity: .45; }
.danger-card { border-color: #b3261e66; }
.mod-row { border-top: 1px solid var(--border); padding: 8px 0; }
.mod-row .profile-actions { margin-top: 6px; }

/* v42 presence */
.presence-picker { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
.presence-picker button {
  background: var(--surface2); color: var(--text); border: 1px solid var(--border);
  border-radius: 999px; padding: 7px 12px; cursor: pointer; font: inherit;
}
.presence-picker button.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; font-weight: 700; }
.pdot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; flex: none; }
.pdot.p-online { background: #2ecc71; }
.pdot.p-away { background: #f1c40f; }
.pdot.p-dnd { background: #e74c3c; }
.pdot.p-offline { background: #8a8f98; }
.avatar-wrap { position: relative; display: inline-flex; flex: none; }
.avatar-wrap .pdot { position: absolute; right: -1px; bottom: -1px; border: 2px solid var(--surface-solid); width: 11px; height: 11px; }
.friend-presence { display: inline-flex; align-items: center; gap: 6px; font-size: .85em; }

/* v43 direct messages */
.nav-badge {
  background: var(--accent); color: var(--accent-text); border-radius: 999px;
  font-size: .72em; padding: 1px 7px; margin-left: 2px; vertical-align: 2px;
}
/* v95: the old conversation-list rows (.dm-convo/.dm-preview) are gone
   with the Messages tab — the Friends hub rows carry conversations now. */
.dm-unread { background: var(--accent); color: var(--accent-text); border-radius: 999px; font-size: .72em; padding: 1px 7px; }
.dm-messages { display: flex; flex-direction: column; gap: 8px; max-height: 55vh; overflow-y: auto; padding: 8px 0; }
.dm-bubble { max-width: 78%; padding: 8px 12px; border-radius: 16px; background: var(--surface2); align-self: flex-start; }
.dm-bubble.mine { align-self: flex-end; background: var(--accent); color: var(--accent-text); }
.dm-bubble img { max-width: 100%; border-radius: 10px; display: block; }
.dm-time { font-size: .72em; opacity: .75; margin-top: 3px; }
#dmInput { flex: 1; }

/* v47 watch-order guides, restyled in v58 so every layer reads apart:
   labeled sections, a segmented order switch, spaced cards, callout
   notes, and state chips that no longer borrow the accent highlight. */
.guide-label { font-size: .8rem; font-weight: 700; opacity: .6; margin: 16px 0 4px; }
.guide-label:first-of-type { margin-top: 10px; }
/* v91: ONE tab + attached-panel family, shared by every surface
   that hides a choice behind a labeled tab — the pattern Thomas
   loved in v90's Watch order tab. Strip variant (.vtab on a
   .vtab-strip, panel in flow under it): Guides' Franchises and
   Watch order tabs. Drop variant (.vtab-btn on a .btn, panel
   hanging attached under it): the More drop in the list toolbar.
   A tab reads as OPEN while its panel shows — strip tabs fill
   with the panel colour, drop tabs take the same fill via .open —
   and every panel shares the border + surface2 fill, so the
   pattern can't drift apart between surfaces.
   v92: ONE pill language inside (and beside) those panels, after
   the Watch order panel's look: every choice in the family is a
   pill — segmented tracks for Watch order, the three view
   switches and the family bars; a wrapped pill group for the
   Franchises panel; full-width pills for the More menu's items.
   The Layout TAB is retired (Thomas: two stubby tabs on their own
   row, and an extra click for a control he uses constantly): the
   Shelf | Grid | List pills sit inline in the toolbars again. */
.vtab-strip { display: flex; gap: 6px; margin-top: 12px; border-bottom: 1px solid var(--border); }
.vtab { background: transparent; border: 1px solid var(--border); border-bottom: none; border-radius: 10px 10px 0 0; padding: 7px 14px; color: var(--text); font: inherit; font-weight: 700; font-size: .88rem; cursor: pointer; }
.vtab.active { background: var(--surface2); color: var(--accent); }
.vtab-panel { background: var(--surface2); border: 1px solid var(--border); border-top: none; border-radius: 0 0 12px 12px; padding: 10px 12px; margin-bottom: 4px; }
/* THE segmented pill track — the one definition every pill choice
   in the app shares: the Watch order control (Story | Release),
   the three view switches (Shelf | Grid | List, wearing the
   .view-toggle hook class) and the family bars (.tabs, whose own
   spacing/sizing rules live with the tabs). A rounded-full track
   on the card colour, borderless pills inside it, and the active
   choice filled with the accent — defined once, here, so the
   surfaces can't drift apart again. */
.vtab-seg, .tabs { display: inline-flex; gap: 4px; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 4px; }
.vtab-seg .chip-btn, .vtab-seg .btn, .tabs .tab { border: none; background: transparent; border-radius: 999px; color: var(--text); cursor: pointer; white-space: nowrap; }
.vtab-seg .chip-btn.active, .vtab-seg .btn.active, .tabs .tab.active { background: var(--accent); color: var(--accent-text); }
/* The Franchises panel: a wrapped group of standalone pills (the
   shared .chip-btn pill; its .active fill is the same accent), with
   the selected franchise's stats line riding beneath the group. */
.guide-pills { display: flex; flex-wrap: wrap; gap: 6px; }
.guide-franchise-stats { margin: 10px 0 0; }
.vtab-drop { position: relative; }
.vtab-btn.open { background: var(--surface2); color: var(--accent); border-color: var(--border); }
.vtab-drop-panel { position: absolute; top: calc(100% + 2px); left: 0; z-index: 45; border-top: 1px solid var(--border); border-radius: 0 12px 12px 12px; margin-bottom: 0; white-space: nowrap; }
.toolbar-tabs { display: inline-flex; gap: 6px; align-items: flex-start; }
#discoverFilters .view-toggle, #shelfViewBar .view-toggle { margin-left: auto; }
/* v99: the view switches (all three surfaces) and the list
   toolbar's buttons wear the Guides seg scale (chip-btn sizing:
   .88rem, 6px 12px, ~34px tall) instead of the full .btn bulk. */
.view-toggle .btn, .toolbar-tabs .btn { min-height: 34px; padding: 6px 12px; font-size: .88rem; }
/* v99: Discover's filter chips + view switch ride ONE tidy row
   like the Guides strip — no ragged 2/2/1 wrap (Thomas circled
   the whole Discover header too). The row scrolls sideways on
   narrow screens; the season selects stay in their own row. */
/* v101: Discover's chips + view switch moved into the Guides
   strip/panel pattern (Charts ▾ / Layout ▾), so the old one-row
   flex rules for #discoverFilters are gone — the id now wraps the
   strip and its panels. Inside any panel, the family bar sits
   flush (its own margins belong to the standalone layout). */
.vtab-panel .tabs { margin: 0; }
/* v100: Discover search (a new member's suggestion, Thomas loved
   it) + Guides search — one shared search-row shape: the input
   grows, the buttons ride beside it. */
.discover-search, .guide-search { display: flex; gap: 8px; align-items: center; margin: 10px 0 2px; }
.discover-search input, .guide-search input { flex: 1 1 auto; min-width: 0; }
.guide-result { width: 100%; font: inherit; color: inherit; text-align: left; cursor: pointer; }
.guide-result .guide-row-main { flex: 1 1 auto; min-width: 0; }
.guide-result .guide-result-where { color: var(--muted); font-size: .85rem; }
#guideProgress { margin: 12px 0; padding-top: 12px; border-top: 1px solid var(--border); }
.guide-row { display: flex; gap: 10px; align-items: flex-start; background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 12px; margin-bottom: 10px; }
.guide-row:last-child { margin-bottom: 0; }
.guide-num { font-weight: 800; min-width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; }
.guide-main { flex: 1; min-width: 0; }
.guide-title { font-weight: 700; display: block; }
.guide-meta { font-size: .85em; margin-top: 2px; }
.guide-note { font-size: .85em; background: var(--surface); border-left: 3px solid var(--accent); border-radius: 0 8px 8px 0; padding: 8px 10px; margin-top: 8px; }
.guide-state { flex: none; }
.guide-state-chip { display: inline-block; border: 1px solid var(--border); border-radius: 999px; padding: 5px 11px; font-size: .85rem; font-weight: 600; }
.guide-state-chip.stash { color: var(--text); opacity: .85; }
.guide-state-chip.watching { border-color: var(--accent); color: var(--accent); }
.guide-state-chip.done { border-color: var(--done); color: var(--done); }
/* v90→v91: the franchise list lives in the Franchises tab's panel
   (the v90 index page is retired). v92: the entries are pills in
   the shared language (.guide-pills, in the tab-family block
   above); the per-franchise counts ride in the panel's stats line
   instead of on card faces. */
.guide-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.guide-head strong { font-size: 1.05rem; }
.guide-detail-state { flex: none; }
.shelf-detail .guide-note { background: var(--surface2); }
.discover-lane-title { margin: 20px 0 10px; font-size: 1.02rem; }
.discover-lane-title:first-child { margin-top: 8px; }
.discover-card { display: flex; gap: 12px; align-items: flex-start; background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 12px; margin-bottom: 10px; }
.discover-card .cover, .discover-card .cover-placeholder { flex: none; }
.discover-main { flex: 1; min-width: 0; }
.discover-desc { font-size: .85em; margin-top: 6px; }
.discover-error { margin-bottom: 10px; }

/* v50: editor sections collapse to keep the form calm. Tap the title to open. */
fieldset { min-width: 0; }
.grid2 > * { min-width: 0; }
.grid2 input, .grid2 select { width: 100%; box-sizing: border-box; }
fieldset.collapsible > legend { cursor: pointer; }
fieldset.collapsible > legend::before { content: "▸ "; color: var(--muted); }
fieldset.collapsible.open > legend::before { content: "▾ "; }
fieldset.collapsible:not(.open) > :not(legend) { display: none !important; }
.collapse-summary { color: var(--muted); font-weight: 400; font-size: .88em; }
.collapse-hint { display: block; color: var(--muted); font-weight: 400; font-size: .85em; margin-top: 2px; }

/* v50: Surprise Me reveal card */
.dice-head { text-align: center; font-weight: 700; font-size: 1.05rem; margin: 2px 0 10px; }
.dice-card h3 { text-align: center; margin: 8px 0 2px; }
.dice-card .muted { text-align: center; }
.dice-cover { display: block; margin: 0 auto; max-width: 220px; width: auto; max-height: 320px; border-radius: 12px; }
.dice-nocover { text-align: center; font-size: 4rem; padding: 10px 0; }
.dice-card .chip-row { justify-content: center; }
.dice-card .profile-actions { justify-content: center; flex-wrap: wrap; }

/* v51: empty-shelf explainer */
.empty-ways { text-align: left; margin: 8px auto; max-width: 540px; padding-left: 20px; }
.empty-ways li { margin: 7px 0; }

/* v52: on phones the six nav buttons stack icon-over-label instead of
   squeezing side-by-side and wrapping mid-word. Full words stay — nothing
   is renamed or hidden, the buttons just get narrower and calmer. */
.nav-btn { white-space: nowrap; min-width: 0; }
@media (max-width: 560px) {
  .main-nav { gap: 3px; padding: 4px; }
  .nav-btn { display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 7px 1px 6px; font-size: .64rem; line-height: 1.15; }
  .nav-btn .nav-ico { font-size: .95rem; line-height: 1; }
}

/* v74: on phones the nav strip is an endless swipe carousel — five tabs
   fit, the sixth peeks in from the edge, and the row loops forever.
   The inset edge shadows are painted on the strip itself, so they stay
   put while the buttons slide underneath: a standing "there's more"
   sign at both ends. The strip is dragged by pointer (see app.js), so
   no scrollbar shows. Desktop keeps the full row / sidebar. */
@media (max-width: 560px) {
  .main-nav.car-on { overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; overscroll-behavior-x: contain; cursor: grab;
    box-shadow: 0 6px 18px rgba(60, 40, 160, .25), inset 24px 0 16px -14px rgba(15, 10, 45, .5), inset -24px 0 16px -14px rgba(15, 10, 45, .5); }
  .main-nav.car-on::-webkit-scrollbar { display: none; }
  .main-nav.car-on .nav-btn { flex: 0 0 18.4%; }
}

/* v56: the courier runs in with your pick */
.courier-stage { position: relative; }
.courier-stage img { display: block; margin: 0 auto; max-height: 300px; max-width: 100%; width: auto; height: auto; }
.courier-stage .speed-line { position: absolute; height: 3px; border-radius: 3px; background: rgba(255,255,255,.85); animation: speed .45s linear infinite; }
.courier-stage .s1 { top: 22%; right: 14%; width: 64px; }
.courier-stage .s2 { top: 48%; right: 30%; width: 44px; animation-delay: .15s; }
.courier-stage .s3 { top: 70%; right: 8%; width: 54px; animation-delay: .3s; }
@keyframes speed { from { transform: translateX(34px); opacity: 0; } 30% { opacity: .9; } to { transform: translateX(-96px); opacity: 0; } }
.courier-run-in { animation: courier-dash 1.3s ease-out both, courier-bob .22s ease-in-out 6 alternate; }
@keyframes courier-dash { 0% { transform: translateX(-110%) rotate(-2deg); } 55% { transform: translateX(4%) rotate(1deg); } 70% { transform: translateX(0) translateY(-6px); } 85% { transform: translateX(0) translateY(2px); } 100% { transform: translateX(0) translateY(0); } }
@keyframes courier-bob { from { margin-top: 0; } to { margin-top: -4px; } }
#courierStage .muted { text-align: center; }
.dice-result-in { animation: dice-pop .35s ease-out; }
@keyframes dice-pop { from { opacity: 0; transform: scale(.94) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
#diceRolling .muted { text-align: center; }

/* v55: the More menu is right-anchored, so it can never slide off the
   left edge of a phone screen the way it did from the old left-side row. */
.more-menu { max-width: calc(100vw - 24px); }

/* v60: badges, mod sections, help queue, stats */
.badge { display: inline-flex; align-items: center; gap: 2px; font-size: 11px; font-weight: 700; line-height: 1; padding: 3px 7px; border-radius: 999px; margin-left: 6px; vertical-align: middle; white-space: nowrap; }
.badge-admin { background: #f59e0b; color: #3b2700; }
.badge-mod { background: var(--accent); color: var(--accent-text, #fff); }
.badge-founder { background: #fde68a; color: #713f12; border: 1px solid #f59e0b; }
.badge-starter { background: #dcfce7; color: #166534; }
.badge-member { background: var(--surface2); color: var(--muted); border: 1px solid var(--border); }
.badge-row { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.badge-row .badge { margin-left: 0; }
.badge-surfaces { display: flex; flex-wrap: wrap; gap: 10px; margin: 10px 0; }
.badge-surfaces label { display: inline-flex; gap: 4px; align-items: center; }
.mod-section { border-top: 1px solid var(--border); margin-top: 14px; padding-top: 12px; }
.mod-section:first-child { border-top: none; margin-top: 0; padding-top: 0; }
.perm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 6px 12px; margin: 8px 0; }
.perm-grid label { display: flex; gap: 6px; align-items: flex-start; font-size: 14px; }
.ticket-row { border-top: 1px solid var(--border); padding: 10px 0; }
.ticket-row:first-child { border-top: none; }
.ticket-replies { margin: 8px 0 4px 12px; }
.ticket-reply { border-left: 3px solid var(--border); padding: 4px 0 4px 10px; margin-bottom: 8px; }
.ticket-reply-form textarea { width: 100%; margin: 6px 0; }
.grant-row { display: flex; gap: 8px; align-items: center; padding: 6px 0; border-top: 1px solid var(--border); }
.stat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; margin-top: 8px; }
.stat-cell { background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 10px; text-align: center; }
.stat-num { font-size: 24px; font-weight: 800; }

/* v62: profile tidy-up row — one tap to collapse or expand every card. */
.profile-layout-tools { display: flex; align-items: center; gap: 8px; margin: 2px 0 14px; }
.profile-layout-tools .muted { font-size: .9rem; }

/* v62: desktop layout. Everything in here only applies at 1024px and up —
   the phone layout below that is exactly as it was. Discord-style shape:
   the nav becomes a left sidebar and the content fills the rest of the
   window instead of sitting in a narrow centered column. */
@media (min-width: 1024px) {
  main { max-width: none; padding: 16px 20px 48px; }
  #appView:not([hidden]) { display: flex; align-items: flex-start; gap: 18px; }
  /* v83: the rail is the slim icon rail, full stop — and it sits
     clean against the page: no panel background, no border, no
     shadow. The goo and the icons carry it. */
  .main-nav { flex-direction: column; width: 76px; flex-shrink: 0; position: sticky; top: var(--side-top, 68px); margin: 0; border: none; background: transparent; box-shadow: none; }
  .nav-btn { flex: none; display: flex; align-items: center; gap: 10px; justify-content: center; text-align: left; padding: 12px 6px; border-radius: 12px; }
  .nav-btn .nav-ico { font-size: 1.05rem; }
  .app-section { flex: 1 1 auto; min-width: 0; }
  /* v80: the rail + gooey indicator. Buttons go transparent in rail mode
     and the goo layer behind them IS the highlight: an anchor blob marks
     the active tab while a loose blob rides the hover, the two melting
     together through the SVG goo filter. Only transforms/heights move. */
  .nav-goo { display: block; position: absolute; inset: 6px; z-index: 0; filter: url(#vs-goo); pointer-events: none; }
  .nav-goo-blob { position: absolute; left: 0; right: 0; top: 0; height: 48px; background: var(--accent); border-radius: 12px; transition: transform .45s cubic-bezier(.6, .05, .2, 1), height .45s cubic-bezier(.6, .05, .2, 1); will-change: transform; }
  /* v88: background-COLOR only, never the background shorthand — the
     shorthand also resets background-image, and at this specificity
     (0-2-0) it silently wiped the v83 gradient-fill hover image
     (declared on bare .nav-btn at 0-1-0), which is why the family
     tabs popped on hover but the rail buttons never did. With only
     the colour zeroed, the gradient image survives on the rail too
     and sweeps in on hover; at rest its size is 0% so the button
     still reads transparent and the goo anchor carries the active
     state. */
  .main-nav .nav-btn { position: relative; z-index: 1; background-color: transparent; }
  .main-nav .nav-btn.active { background-color: transparent; box-shadow: none; }
  .main-nav .nav-btn.goo-hover { color: var(--accent-text); }
  /* Names hide, icons stay; the DM unread badge tucks onto the
     Messages icon so a count is never lost. */
  .main-nav .nav-txt { display: none; }
  .main-nav [data-nav="messages"] .nav-txt { display: block; font-size: 0; line-height: 0; }
  .main-nav .nav-badge { position: absolute; top: 2px; right: 2px; margin: 0; }
  /* v83: the hover name label — a solid chip that slides out to the
     right of the hovered icon, CodePen-style. wireNavRail toggles
     .tip-show on the hovered button; the chip never intercepts
     clicks, and its background is a flat colour (no blur). */
  .main-nav .nav-tip { display: block; position: absolute; left: calc(100% + 12px); top: 50%; z-index: 5; padding: 7px 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--text); box-shadow: 0 6px 18px rgba(0, 0, 0, .35); font-size: .9rem; font-weight: 700; line-height: 1.2; white-space: nowrap; pointer-events: none; opacity: 0; transform: translate(-6px, -50%); transition: opacity .18s ease, transform .18s ease; }
  .main-nav .nav-btn.tip-show .nav-tip { opacity: 1; transform: translate(0, -50%); }
}
/* v62: on very wide screens the list stops being one long column — two
   entry cards side by side. The grid view the user toggles is untouched. */
@media (min-width: 1280px) {
  #list:not(.grid) { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; align-items: start; }
}

/* v72: one card anatomy across My List, Guides, and Discover — inspired
   by seasonal-chart cards but skinned in VaultStash's own system: an
   accent title bar, a big poster with a ★ score badge on its corner,
   description, accent genre pills, a meta line, and a thin footer strip.
   Cards still sit on the appearance panels (--surface / --surface2 with
   the user's own colors and transparency) — no blur, no frosting. */
.card-head {
  display: flex; align-items: center; gap: 8px; background: var(--accent);
  color: var(--accent-text); padding: 8px 12px; font-weight: 700;
}
.card-head .card-title { margin: 0; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-head .guide-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-head .guide-state { flex: none; }
.card-head .guide-state-chip { background: var(--surface); }
.card-head .btn-ghost { color: var(--accent-text); border: 1px solid rgba(255, 255, 255, .55); border-radius: 8px; }
.card-body { display: flex; gap: 12px; align-items: flex-start; padding: 10px 12px; }
.card-poster { position: relative; flex: none; }
.card-poster .cover, .card-poster .cover-placeholder { width: 100px; height: 142px; flex: none; }
.card-poster .cover-placeholder .initial { font-size: 2.2rem; }
.score-badge {
  position: absolute; top: 6px; left: 6px; background: rgba(10, 10, 18, .85); color: #fff;
  font-size: .78rem; font-weight: 700; padding: 2px 8px; border-radius: 999px; pointer-events: none;
}
.score-badge .star { color: #ffc93c; }
.chip.genre { background: var(--accent); border-color: var(--accent); color: var(--accent-text); font-weight: 600; }
/* v84: kind label on combined Watch search rows (Anime / Show / Movie) */
.chip.kind { background: transparent; border-color: var(--accent); color: var(--accent); font-weight: 700; }
.card-desc { font-size: .9rem; margin-bottom: 4px; }
.card-foot { background: var(--surface2); border-top: 1px solid var(--border); padding: 7px 12px; }
.card-foot .meta-row { margin: 0; }
/* guide rows become the same stacked card: accent head (number + title +
   state), poster + details below. Base .guide-row above keeps its
   surface2 card shell and spacing. */
.guide-row { flex-direction: column; align-items: stretch; gap: 0; padding: 0; overflow: hidden; }
.guide-num { background: rgba(255, 255, 255, .25); border-color: transparent; color: var(--accent-text); min-width: 26px; height: 26px; }
.guide-main { flex: 1; min-width: 0; }
/* discover cards follow the same shape. */
.discover-card { flex-direction: column; align-items: stretch; gap: 0; padding: 0; overflow: hidden; }
.discover-main { flex: 1; min-width: 0; }
.discover-card .discover-desc { margin-top: 0; }
@media (max-width: 560px) {
  .card-poster .cover, .card-poster .cover-placeholder { width: 84px; height: 119px; }
}
/* v73: the Free Shelf — license chips, chapter rows, and the reader. */
.license-chip { display: inline-block; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 5px 11px; font-size: .85rem; font-weight: 600; white-space: nowrap; }
.shelf-setup { background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }
.shelf-chapter-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; margin-bottom: 8px; }
.affirm-row { display: flex; gap: 8px; align-items: flex-start; }
.affirm-row input { margin-top: 4px; }
.reader-stage { position: relative; max-width: 760px; margin: 0 auto; }
.reader-img { display: block; width: 100%; max-width: 760px; margin: 0 auto 10px; border-radius: 8px; background: var(--surface2); }
.reader-zone { position: absolute; top: 0; bottom: 0; width: 22%; background: transparent; border: 0; color: #fff; font-size: 2rem; opacity: 0; cursor: pointer; }
.reader-zone:hover, .reader-zone:focus-visible { opacity: .85; background: rgba(0, 0, 0, .18); }
.reader-zone.prev { left: 0; }
.reader-zone.next { right: 0; }
.reader-bar { display: flex; justify-content: space-between; align-items: center; gap: 10px; max-width: 760px; margin: 4px auto 10px; }
.reader-scroll .reader-img { margin-bottom: 12px; }
.reader-chapters { justify-content: space-between; }
/* v73: Discover — clickable covers/titles, grid tiles, detail modal. */
.dopen { cursor: pointer; }
.guide-title.dopen:hover { text-decoration: underline; }
.card-poster.dopen:hover { opacity: .88; }
.discover-tile { cursor: pointer; }
.discover-tile .card-poster .cover, .discover-tile .card-poster .cover-placeholder { width: 100%; height: auto; aspect-ratio: 2 / 3; object-fit: cover; border-radius: 8px; }
.discover-tile-state .btn-ghost { padding: 4px 10px; font-size: .85rem; }
.discover-modal-cover { text-align: center; margin: 8px 0; }
.discover-modal-cover .cover, .discover-modal-cover .cover-placeholder { width: 180px; height: 254px; border-radius: 10px; }
.discover-modal-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(105px, 1fr)); gap: 10px; margin: 12px 0; }
.entry-view-note { margin-top: 12px; }
/* v91: the segmented control now lives inside the Layout tab's
   panel; the drop itself takes the right-hand seat in these bars
   (see the .vtab-drop rule in the tab family block). */
/* v86: Free Shelf list view — compact series rows (thumb, title,
   creator/license, chapter count) instead of the full cards. */
.fs-row { display: flex; align-items: center; gap: 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 8px 12px; margin-bottom: 8px; cursor: pointer; }
.fs-row:hover { filter: brightness(1.12); }
.fs-row-cover { flex: none; display: block; }
.fs-row-cover .cover, .fs-row-cover .cover-placeholder { width: 40px; height: 56px; object-fit: cover; border-radius: 6px; }
.fs-row-cover .cover-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; }
.fs-row-cover .cover-placeholder .no-cover { display: none; }
.fs-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.fs-row-main b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* v80: below desktop width the rail furniture doesn't exist — the
   mid-width pill bar and the phone carousel keep their own shapes. */
@media (max-width: 1023px) {
  .nav-goo { display: none; }
}
/* v80: Shelf view — the filtered list as physical cases on a shelf. The
   selected title stands front-facing in a case frame (kind-coloured
   spine strip on its left edge, a gloss sheen drawn on the case itself);
   its neighbours stand as spines — a thin crop of their own cover art,
   or their kind's colour with the title written down the spine.
   v82: spines are full-colour art with pale case caps (no dark shade),
   the front case floats over a fixed spine-width slot, and the flick
   is free native momentum with no snap. */
.shelf-view { margin-top: 6px; outline: none; }
.shelf-stage { display: flex; align-items: flex-end; gap: 8px; }
/* v81: the strip scrolls natively sideways with a hidden scrollbar.
   v82: no snap at all — a flick glides as far as the flick takes it.
   The tall top padding is the front case's headroom: the case floats
   over its spine slot and rises (248-196)px above it, and the row
   clips at its padding box. pan-x pan-y leaves vertical page
   scrolling to the page and horizontal travel to the shelf. */
.shelf-row { flex: 1 1 auto; min-width: 0; display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; padding: 66px 0 0; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -webkit-overflow-scrolling: touch; touch-action: pan-x pan-y; cursor: grab; }
.shelf-row::-webkit-scrollbar { display: none; }
/* v83: while a mouse drag is walking the shelf, the whole strip is
   in the hand — grabbing cursor everywhere, no text selection. */
.shelf-row.shelf-dragging { cursor: grabbing; -webkit-user-select: none; user-select: none; }
.shelf-row.shelf-dragging * { cursor: grabbing !important; }
/* A shelf that fits stands centred; the side padding is where the
   front case overhangs the first and last slots. */
.shelf-row.shelf-fit { justify-content: center; padding-left: 76px; padding-right: 76px; }
/* v82: every entry owns one fixed-width slot in the scroll flow; the
   front case floats over its slot (see .shelf-case), so the strip's
   geometry never changes as the front moves. */
/* v97: slots narrow a step toward a real case spine's proportions
   (34px at 196px tall; a true Blu-ray spine would be ~13px — too
   thin to read or tap on a phone, so the look goes true-spine at a
   readable width). shelfMetrics() in app.js carries the same numbers. */
.shelf-slot { position: relative; flex: none; width: 34px; height: 196px; }
/* v82: spines show their cover art at full colour again (the v81 dark
   shade buried it). The bold white title stays legible on weight and
   shadow alone, and pale rounded caps at the top and bottom give
   each spine the reference app's clear-case look. */
/* v97: the spine is a PRINTED case spine now (Thomas's reference
   photos of his real Blu-ray shelves): edge shading baked into the
   box-shadow so the strip reads as a rounded printed spine, a band
   of case plastic across the top (see .shelf-spine-cap-top), the
   title down the spine, and the family mark at the foot where a
   studio logo would sit. */
.shelf-spine { position: relative; width: 100%; height: 100%; border: 0; border-radius: 5px 5px 2px 2px; background-color: var(--surface-solid); background-size: cover; background-position: center left; color: #fff; cursor: pointer; padding: 8px 0; display: flex; align-items: center; justify-content: center; box-shadow: inset 3px 0 6px rgba(0, 0, 0, .30), inset -3px 0 6px rgba(0, 0, 0, .30), inset 0 0 0 1px rgba(0, 0, 0, .25), 0 3px 8px rgba(0, 0, 0, .35); transition: transform .28s ease, box-shadow .28s ease; }
/* v97: hover pull-out, ON the spine — simplified on purpose.
   v92 turned the spine in 3D and v96 grew it a side plane, and
   Thomas's verdict on the result was that it still did not look
   like a real movie case: a narrow strip rotating reads as a card,
   however it is shaded. So the strip no longer pretends to be the
   case. The hovered spine simply slides a step out of the line
   toward the viewer (a scale, planted on the board under a deeper
   shadow) while the parked front case — a true 3D case since v97,
   see .shelf-case — crossfades to the hovered title in place. The
   ::before is the spine's permanent printed shading now (a soft
   top light and a grounded foot), no longer a popped-state effect,
   and the v96 ::after side plane is gone with the rotation it
   belonged to. Transforms only: slots, layout and scrollLeft
   never move. */
.shelf-spine.spine-out { transform: scale(1.09); box-shadow: inset 3px 0 6px rgba(0, 0, 0, .30), inset -3px 0 6px rgba(0, 0, 0, .30), inset 0 0 0 1px rgba(0, 0, 0, .25), 0 12px 24px rgba(0, 0, 0, .55); z-index: 8; }
.shelf-spine::before { content: ""; position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(180deg, rgba(255, 255, 255, .10) 0%, rgba(0, 0, 0, 0) 22%, rgba(0, 0, 0, 0) 78%, rgba(0, 0, 0, .22) 100%); z-index: 1; pointer-events: none; }
.shelf-slot.slot-out { z-index: 9; }
.shelf-spine-cap { position: absolute; left: 1px; right: 1px; height: 9px; z-index: 2; pointer-events: none; background: linear-gradient(180deg, #fdfdff, #d5d5dd); }
/* v97: the top cap is the case's plastic BAND now — the coloured
   strip across the top of every case in Thomas's shelf photo.
   --band-c is set per entry by the builders (Blu-ray blue for
   watch kinds, the game red for games, CD silver for music; books
   pass the page-block cream and wear .is-book, which shrinks the
   band to a book's top page edge). */
.shelf-spine-cap-top { top: 0; height: 13px; border-radius: 5px 5px 2px 2px; background: linear-gradient(180deg, color-mix(in srgb, var(--band-c, #2596e8) 68%, #ffffff), var(--band-c, #2596e8)); box-shadow: inset 0 -2px 3px rgba(0, 0, 0, .22), inset 0 0 0 1px rgba(0, 0, 0, .08); }
.shelf-spine.is-book .shelf-spine-cap-top { height: 5px; }
.shelf-spine-cap-bottom { bottom: 0; height: 6px; border-radius: 3px 3px 2px 2px; background: linear-gradient(180deg, #e8e8ee, #c9c9d2); box-shadow: inset 0 2px 3px rgba(0, 0, 0, .20), inset 0 0 0 1px rgba(0, 0, 0, .08); }
.shelf-spine:hover:not(.spine-out) { filter: brightness(1.15); }
.shelf-spine-title { position: relative; z-index: 1; writing-mode: vertical-rl; font-size: .72rem; font-weight: 800; letter-spacing: .02em; max-height: 148px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-shadow: 0 1px 3px rgba(0, 0, 0, .85), 0 0 8px rgba(0, 0, 0, .5); pointer-events: none; }
/* v97: the family mark at the foot of the spine, where a studio
   logo sits on a real case spine. */
.shelf-spine-mark { position: absolute; left: 0; right: 0; bottom: 9px; z-index: 2; text-align: center; font-size: .56rem; line-height: 1; text-shadow: 0 1px 2px rgba(0, 0, 0, .65); pointer-events: none; }
/* v82: the front case is a layer floated over its spine-width slot —
   centred on the slot, overlapping its neighbours, above them in
   z-order — framed like a pale clear case. Out of flow, so swapping
   which entry is fronted never shifts the strip. */
/* v85: the case is a physical object floating OVER the strip, so its
   face must be opaque — it used to sit on var(--surface2), which the
   appearance transparency slider turns into an rgba() and let the
   spines behind bleed through no-cover faces. --surface-solid is the
   same panel colour at full opacity in every theme state, and the
   no-cover face below mixes the kind colour into it — also opaque.
   (The spine rule's fallback backing makes the same swap: it only
   shows while cover art loads or if it fails.) */
/* v97: the front case is a true 3D CASE now, built from Thomas's
   product-shot reference (the Perfect Blue Blu-ray): a wide front
   face with the art inside a plastic rim, a narrow printed spine
   on its LEFT edge, and the top of the case showing, all tipped
   like a case pulled a step off the shelf toward you (rotateY
   turns the spine side into view; rotateX tips the top in). The
   button is just the 3D stage — clear, unclipped, preserve-3d —
   and each face (see shelfCaseFaceWrap) carries the three planes:
   .shelf-case-front at translateZ(d/2), .shelf-case-side hinged
   on the left at rotateY(-90deg), .shelf-case-top at
   rotateX(90deg). --case-w/--case-h/--case-d feed the plane math
   and change with the case at the small-screen breakpoint. */
.shelf-case { position: absolute; left: 50%; bottom: 0; transform: perspective(1050px) translateX(calc(-50% + var(--shelf-fx, 0px))) rotateY(19deg) rotateX(-4deg); transform-style: preserve-3d; transition: transform .28s ease; z-index: 6; width: 176px; height: 248px; --case-w: 176px; --case-h: 248px; --case-d: 20px; border: 0; border-radius: 0; overflow: visible; cursor: pointer; padding: 0; background: transparent; box-shadow: none; }
.shelf-case-front { position: absolute; inset: 0; transform: translateZ(calc(var(--case-d) / 2)); border-radius: 7px; overflow: hidden; background: var(--surface-solid); box-shadow: 0 14px 30px rgba(0, 0, 0, .45), inset 0 0 0 2px rgba(255, 255, 255, .28), inset 0 0 0 3px rgba(0, 0, 0, .10); }
.shelf-case-band { position: absolute; top: 0; left: 0; right: 0; height: 21px; z-index: 2; background: linear-gradient(180deg, color-mix(in srgb, var(--band-c, transparent) 72%, #ffffff), var(--band-c, transparent)); opacity: .94; box-shadow: inset 0 -2px 4px rgba(0, 0, 0, .20); pointer-events: none; }
/* v98: the side's front-top corner is rounded (local right = the
   front edge after rotateY(-90deg)) so it tucks into the top
   plane's rounded corner instead of spearing past it. */
.shelf-case-side { position: absolute; top: 0; bottom: 0; width: var(--case-d); left: calc(50% - var(--case-d) / 2); transform: rotateY(-90deg) translateZ(calc(var(--case-w) / 2)); background-color: var(--surface-solid); box-shadow: inset 0 0 0 40px rgba(0, 0, 0, .20); display: flex; align-items: center; justify-content: center; border-radius: 0 6px 0 0; }
/* v101: the side's band is 21px — the SAME height as the front's
   band. At 15px the corner join showed a notch of bare side color
   under the top plane (measured in Thomas's v100 screenshots);
   the case depth also went 14 → 20px so the corner has real
   material instead of a sub-pixel sliver. */
.shelf-case-side-band { position: absolute; top: 0; left: 0; right: 0; height: 21px; background: linear-gradient(180deg, color-mix(in srgb, var(--band-c, transparent) 72%, #ffffff), var(--band-c, transparent)); box-shadow: inset 0 -2px 3px rgba(0, 0, 0, .25); }
.shelf-case-side-title { position: relative; writing-mode: vertical-rl; font-size: .5rem; font-weight: 800; letter-spacing: .02em; color: #fff; max-height: 76%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-shadow: 0 1px 2px rgba(0, 0, 0, .8); pointer-events: none; }
/* v98: the top plane is inset 3px and rounded on every corner,
   and wears the front band's exact gradient mix. At v97's debut
   Thomas circled the case's top-left corner: the square top plane
   poked past the front face's rounded corner as a pale flap, and
   its lighter mix made it read as a separate lid. Inset + radius
   makes the corner read as the case's molded edge seam, and the
   shared gradient makes top + band one piece of plastic. */
.shelf-case-top { position: absolute; left: 3px; right: 3px; height: var(--case-d); top: calc(50% - var(--case-d) / 2); transform: rotateX(90deg) translateZ(calc(var(--case-h) / 2)); border-radius: 6px; background: linear-gradient(90deg, color-mix(in srgb, var(--band-c, #cfd3da) 72%, #ffffff), var(--band-c, #cfd3da)); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12); }
/* v89 hover-follow: --shelf-fx (whole spine pitches, set by the
   paint code) used to slide the case over the hovered spine's
   slot during a preview. v92 parks the case over the fronted slot
   for previews too (the paint code passes 0), so the case never
   covers the spine being pulled out; the snap path remains for
   when a paint re-parents the layer onto a different slot (the
   front changed) — the offset lands instantly, not slid. */
.shelf-case.shelf-fx-snap { transition: none; }
/* v97: the old ::after edge rim and the flat .shelf-case-spine
   strip are retired — the case has a real spine plane now (above). */
.shelf-case-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; -webkit-user-drag: none; }
.shelf-case-nocover { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 14px 12px; color: var(--muted); text-align: center; background: color-mix(in srgb, var(--face-c, var(--accent)) 32%, var(--surface-solid)); }
.shelf-case-nocover .initial { font-size: 3.4rem; font-weight: 800; color: var(--accent); line-height: 1; }
.shelf-case-title { font-weight: 700; color: var(--text); font-size: .95rem; }
.shelf-case-gloss { position: absolute; inset: 0; z-index: 3; pointer-events: none; background: linear-gradient(112deg, rgba(255, 255, 255, .34) 0%, rgba(255, 255, 255, .07) 34%, rgba(255, 255, 255, 0) 55%); }
/* v88: everything the front case shows lives on one face layer so
   a hover preview can crossfade faces — the incoming face fades in
   with a whisper of scale over the outgoing one, which is dropped
   once the fade lands. Plain fronting swaps carry no fade classes
   and stay instant. */
/* v97: a face is a whole 3D case (three planes), so the crossfade
   can no longer fade the face element itself — opacity on an
   ancestor flattens its 3D children into a picture of a case.
   Instead the face keeps only its whisper of scale, and the fade
   runs on the three planes, which are flat by nature. */
.shelf-case-face { position: absolute; inset: 0; transform-origin: 12% 50%; transform-style: preserve-3d; transition: transform .3s ease; }
.shelf-case-face .shelf-case-front, .shelf-case-face .shelf-case-side, .shelf-case-face .shelf-case-top { transition: opacity .26s ease; }
.shelf-case-face.shelf-face-in { transform: scale(1.03); }
.shelf-case-face.shelf-face-in .shelf-case-front, .shelf-case-face.shelf-face-in .shelf-case-side, .shelf-case-face.shelf-face-in .shelf-case-top { opacity: 0; }
.shelf-case-face.shelf-face-in.shelf-face-on { transform: scale(1); }
.shelf-case-face.shelf-face-in.shelf-face-on .shelf-case-front, .shelf-case-face.shelf-face-in.shelf-face-on .shelf-case-side, .shelf-case-face.shelf-face-in.shelf-face-on .shelf-case-top { opacity: 1; }
.shelf-case-face.shelf-face-out .shelf-case-front, .shelf-case-face.shelf-face-out .shelf-case-side, .shelf-case-face.shelf-face-out .shelf-case-top { opacity: 0; }
/* v91's case-face tip lived here; v92 retired it (it never read
   live — see the .spine-out note above) for the spine-level
   pull-out. The case no longer travels to the hovered slot either:
   parked over the fronted slot, it previews the hovered entry in
   place (the v88 crossfade), so it never covers the spine the
   cursor is pulling out. */
@media (prefers-reduced-motion: reduce) {
  .shelf-case-face.shelf-face-in, .shelf-case-face.shelf-face-out { transition: none; }
  .shelf-case-face .shelf-case-front, .shelf-case-face .shelf-case-side, .shelf-case-face .shelf-case-top { transition: none; }
  /* v89: no hover-follow slide either — the case repositions instantly. */
  .shelf-case { transition: none; }
  .shelf-case-face { transition: none; }
  /* v92: no pull-out either — spines stay in the line, still. */
  .shelf-spine { transition: none; }
  .shelf-spine.spine-out { transform: none; }
}
.shelf-board { height: 14px; margin: 0 4px; border-radius: 3px; background: linear-gradient(180deg, #9a6a44, #6e4a2c); box-shadow: 0 8px 16px rgba(0, 0, 0, .4); }
.shelf-detail { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; }
.shelf-detail-main { flex: 1 1 auto; min-width: 200px; display: flex; flex-direction: column; gap: 2px; }
.shelf-detail-title { font-size: 1.1rem; }
@media (max-width: 560px) {
  .shelf-row { padding-top: 58px; }
  .shelf-row.shelf-fit { padding-left: 66px; padding-right: 66px; }
  .shelf-case { width: 150px; height: 212px; --case-w: 150px; --case-h: 212px; }
  .shelf-slot { width: 29px; height: 168px; }
  .shelf-spine-title { max-height: 122px; }
  .shelf-board { margin: 0 8px; }
}
/* v80: onboarding — bubbly interest circles + people to follow. */
.onboard-modal { max-width: 560px; }
.onboard-q { margin: 4px 0 2px; }
.onboard-bubbles { display: flex; flex-wrap: wrap; gap: 12px; margin: 14px 0 10px; }
.ob-bubble { position: relative; width: 96px; height: 96px; border-radius: 50%; border: 3px solid var(--border); background: var(--surface2); color: var(--text); cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; font: inherit; transition: transform .15s ease, border-color .15s ease; }
.ob-bubble:hover { transform: scale(1.05); }
.ob-bubble.selected { border-color: var(--kcol, var(--accent)); background: var(--surface); box-shadow: 0 0 0 3px var(--kcol, var(--accent)); }
.ob-emoji { font-size: 1.7rem; line-height: 1.1; }
.ob-label { font-size: .8rem; font-weight: 700; }
.ob-check { position: absolute; top: -4px; right: -4px; width: 26px; height: 26px; border-radius: 50%; background: var(--kcol, var(--accent)); color: #fff; font-weight: 800; font-size: .9rem; display: none; align-items: center; justify-content: center; box-shadow: 0 2px 6px rgba(0, 0, 0, .35); }
.ob-bubble.selected .ob-check { display: flex; }
.ob-person { display: flex; align-items: center; gap: 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; margin-bottom: 8px; }
.ob-avatar { flex: none; }
.ob-person-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ob-empty { text-align: center; padding: 8px 0 4px; }

/* v83: Animated Gradient Fill hover (the menuhover.com effect),
   born for the app's tab-like controls. A gradient built from the
   accent family sweeps in to fill the tab on hover and sweeps back
   out when the pointer leaves (the transition lives on the resting
   state, so it animates both ways). The active tab is excluded: its
   solid accent is its own state and is never recolored by the hover
   layer. Gated behind (hover: hover) so touch devices never get a
   stuck fill.
   v92: the family bars and the view toggles left this treatment —
   they are segmented pills in the shared language now (see the
   .vtab-seg block), flat at rest with the accent-filled active
   pill, and a gradient sweep or drift over a pill would double-
   style it. What remains here is the main nav's alone (the rail
   hover Thomas asked for in v88), untouched. */
@media (hover: hover) {
  .nav-btn {
    background-image: linear-gradient(100deg, color-mix(in srgb, var(--accent) 76%, #ffffff) 0%, var(--accent) 52%, color-mix(in srgb, var(--accent) 74%, #000000) 100%);
    background-repeat: no-repeat;
    background-size: 0% 100%;
    transition: background-size .45s cubic-bezier(.5, 0, .15, 1), color .25s ease;
  }
  .nav-btn:not(.active):hover {
    background-size: 100% 100%;
    color: var(--accent-text);
  }
}

/* v83b: the touch companion — no hover exists on a phone, so the
   ACTIVE nav tab itself carries the life: the same accent-family
   gradient, oversized, drifting slowly back and forth under the
   active tab. Subtle on purpose — polish, not a light show. */
@media (hover: none) {
  .nav-btn.active {
    background-image: linear-gradient(100deg, color-mix(in srgb, var(--accent) 76%, #ffffff) 0%, var(--accent) 52%, color-mix(in srgb, var(--accent) 74%, #000000) 100%);
    background-size: 200% 100%;
    animation: tabActiveDrift 6s ease-in-out infinite alternate;
  }
  @keyframes tabActiveDrift {
    from { background-position: 0% 0; }
    to { background-position: 100% 0; }
  }
  /* v83c: the inactive nav tabs wear the same gradient, stilled
     and dimmed down, so the rail reads as one design — the active
     tab is simply the one that's awake.
     v100: a new member's first-run report killed the dimming —
     on a phone the unselected tabs were a guessing game, only
     the selected one readable. The inactive tabs now wear a
     pale, still wash of the same accent family over the surface
     colour, with full-strength text and NO filter: quiet next
     to the active tab's vivid drift, but every label legible. */
  .nav-btn:not(.active) {
    background-image: linear-gradient(100deg, color-mix(in srgb, var(--accent) 30%, var(--surface2)) 0%, color-mix(in srgb, var(--accent) 18%, var(--surface2)) 100%);
    color: var(--text);
    filter: none;
  }
}
/* Reduced motion: no drift, no gradient — the flat styling, active
   and inactive alike. */
@media (prefers-reduced-motion: reduce) {
  .nav-btn.active { animation: none; background-image: none; }
  .nav-btn:not(.active) { background-image: none; filter: none; }
}

/* ── v93: calls (voice / video / screen share in DMs) ─────────────────── */
.profile-actions .spacer, .call-controls .spacer { flex: 1; }
.call-modal { max-width: 560px; width: calc(100% - 32px); }
.call-head { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.call-head .avatar-sm, .call-head .avatar-fallback { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; flex: none; }
.call-head .avatar-fallback { display: inline-flex; align-items: center; justify-content: center; background: var(--surface2); border: 1px solid var(--border); font-weight: 700; font-size: 1.1rem; }
.call-who { min-width: 0; }
.call-who .modal-title { margin: 0; }
.call-who p { margin: 2px 0 0; }
.call-duration { margin-left: auto; font-variant-numeric: tabular-nums; font-weight: 700; color: var(--text); background: var(--surface2); border: 1px solid var(--border); border-radius: 999px; padding: 4px 12px; }
.call-stage { position: relative; background: #05070c; border: 1px solid var(--border); border-radius: 14px; overflow: hidden; aspect-ratio: 4 / 3; }
.call-stage video#callRemoteVideo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.call-remote-fallback { position: absolute; inset: 0; display: flex; flex-direction: column; gap: 10px; align-items: center; justify-content: center; color: var(--muted); }
.call-remote-fallback img { width: 96px; height: 96px; border-radius: 50%; object-fit: cover; }
.call-remote-fallback .call-big-initial { width: 96px; height: 96px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; background: var(--surface2); border: 1px solid var(--border); font-size: 2.2rem; font-weight: 700; color: var(--text); }
.call-stage video#callLocalVideo { position: absolute; right: 10px; bottom: 10px; width: 27%; aspect-ratio: 4 / 3; object-fit: cover; border: 1px solid var(--border); border-radius: 10px; background: var(--surface2); box-shadow: 0 8px 24px rgba(0, 0, 0, .45); }
.call-controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px; }
.call-btn { border-radius: 999px; }
.call-btn.off { background: var(--surface2); color: var(--muted); border-color: var(--border); text-decoration: line-through; }
.call-end { border-radius: 999px; }
