/* ───────────────── design tokens ───────────────── */
/* Cabinet palette — same set of token names, retuned to the warm
   amber / oxblood / sepia register that the homepage and other
   drawer pages share. Light mode is intentionally suppressed below
   (it now mirrors dark) so /media reads as part of the same cabinet
   regardless of the visitor's OS preference. */
:root{
  --bg:#0e0905;
  --bg-2:#160d05;
  --surface:#160d05;
  --surface-2:#221608;
  --surface-3:#2b2418;
  --border:rgba(160,122,50,.18);
  --border-hi:rgba(217,154,60,.45);
  --text:#f0e2bf;
  --text-2:#d4c4a0;
  --muted:#968762;
  --accent:#d99a3c;
  --accent-2:#a07a32;
  --accent-warm:#f0b454;
  --good:#a3c47a;
  --bad:#a83a36;
  --gold:#a07a32;
  --bar-rgb:14,9,5;          /* rgb() backing for the appbar / filterbar / sticky head wash */
  --surface-overlay:rgba(217,154,60,.04);
  --surface-overlay-hi:rgba(217,154,60,.08);
  --scrim:rgba(0,0,0,.65);
  --hairline:rgba(160,122,50,.16);
  /* Popover wash + dialog hero fade — recoloured in cabinet tones.
     The same hero gradient logic still applies: a near-opaque dark
     wash sells the imagery against the dark surface. */
  --popover-bg:rgba(22,13,5,.96);
  --hero-shade-1:rgba(14,9,5,.65);
  --hero-shade-2:rgba(14,9,5,.95);
  --dialog-edge:var(--border-hi);
  --hero-action-bg:rgba(0,0,0,.32);
  --hero-action-bg-hover:rgba(0,0,0,.5);
  --hero-action-text:#fff;
  /* Filter chip text colours when the chip carries an active filter. */
  --filter-chip-active-key:#f0e2bf;
  --filter-chip-active-value:#f0b454;
  --radius:14px;
  --radius-sm:8px;
  --shadow-1:0 10px 30px rgba(0,0,0,.45);
  --shadow-2:0 24px 60px rgba(0,0,0,.55);
  --tab-h:52px;
  --bar-h:64px;
  --filter-h:56px;
  --mode-pad-x:24px;
  --section-head-h:46px;
  --ease:cubic-bezier(.4,0,.2,1);
  --ease-back:cubic-bezier(.34,1.56,.64,1);
  --sticky-blur-backing-alpha:0.5;
}

/* Cabinet has no light mode — the visual register is intentionally
   always dim, warm, and lamp-lit. We keep these selectors intact
   (so any data-theme="light" or OS auto-light setting still
   matches) but redirect them to the same cabinet tokens above so
   the theme stays consistent across the whole site.
   The light-mode overrides from upstream are preserved below as a
   single comment block in case we ever want to bring them back. */
:root[data-theme="light"],
:root:not([data-theme]):where(:root) {
  /* All cabinet tokens — same values as the dark default above.
     Listed individually rather than via the older light-mode block
     so each light-only token (e.g. --shadow-1 with light-tinted
     rgba) maps to the cabinet equivalent. */
  --bg:#0e0905;
  --bg-2:#160d05;
  --surface:#160d05;
  --surface-2:#221608;
  --surface-3:#2b2418;
  --border:rgba(160,122,50,.18);
  --border-hi:rgba(217,154,60,.45);
  --text:#f0e2bf;
  --text-2:#d4c4a0;
  --muted:#968762;
  --bar-rgb:14,9,5;
  --surface-overlay:rgba(217,154,60,.04);
  --surface-overlay-hi:rgba(217,154,60,.08);
  --scrim:rgba(0,0,0,.65);
  --hairline:rgba(160,122,50,.16);
  --popover-bg:rgba(22,13,5,.96);
  --hero-shade-1:rgba(14,9,5,.65);
  --hero-shade-2:rgba(14,9,5,.95);
  --dialog-edge:var(--border-hi);
  --hero-action-bg:rgba(0,0,0,.32);
  --hero-action-bg-hover:rgba(0,0,0,.5);
  --hero-action-text:#fff;
  --filter-chip-active-key:#f0e2bf;
  --filter-chip-active-value:#f0b454;
}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
*{-webkit-tap-highlight-color:transparent}
/* `display` from class rules outranks the UA `[hidden]{display:none}` rule,
   so any `hidden` attribute on a flex/grid element silently fails. This
   blanket override restores the expected behaviour. */
[hidden]{display:none !important}
/* No focus rings on clickable shells; keep them for real form fields where
   tracking caret/insertion matters. */
button,a,[tabindex],.card,.list-row,.dialog-nav,.dialog-close,
.icon-btn,.brand-trigger,.tab,.chip,.filter-chip{outline:none !important}
input:focus-visible,textarea:focus-visible,select:focus-visible{
  outline:2px solid var(--accent);outline-offset:1px;
}
/* Prevent double-tap-to-zoom on iOS while allowing manual pinch zoom. */
html,body,button,a,.tab,.chip,.card,.list-row,.icon-btn,.brand-trigger{touch-action:manipulation}
html,body{height:100%}
html{scroll-behavior:smooth}
body{
  /* Cabinet ambient: a warm amber lamp pool at top and a fainter
     gilt wash mid-page, sitting on the deep ink-black base. The
     two upstream radials (purple at top-left, cyan at top-right)
     are replaced with cabinet-tone radials in the same positions. */
  background:
    radial-gradient(1200px 800px at 20% -10%, rgba(217,154,60,.18), transparent 60%),
    radial-gradient(900px 700px at 90% 10%, rgba(160,122,50,.10), transparent 55%),
    var(--bg);
  color:var(--text);
  /* Body type stays in the cabinet stack so the app reads as part
     of the same site. UI elements (buttons, chips) inherit, and the
     sans-serif fallback keeps form controls legible. Noto Serif SC
     is prepended for cross-platform Chinese rendering — without it
     the CJK fell back to Noto Sans on Android. */
  font-family:"Iowan Old Style","Charter","Cormorant Garamond",
    "Noto Serif SC","PingFang SC","Songti SC","Hiragino Sans GB",
    "Microsoft YaHei",Georgia,serif;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow:hidden; /* mode containers manage their own scroll */
  font-feature-settings:"ss01","cv11","tnum";
  text-autospace: normal;
}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}
input,select,textarea{font:inherit;color:inherit}
a{color:inherit;text-decoration:none}
img{display:block;max-width:100%}

/* film-grain overlay */
body::after{
  content:"";position:fixed;inset:0;pointer-events:none;z-index:9999;opacity:.05;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  mix-blend-mode:overlay;
}

/* ───────────────── top app bar ───────────────── */
.appbar{
  position:fixed;inset:0 0 auto 0;height:var(--bar-h);
  display:flex;align-items:center;gap:14px;
  /* Honour the notch / Dynamic Island in iPhone landscape — without the
     safe-area-inset padding the brand pill is clipped under the notch. */
  padding:0 max(18px, env(safe-area-inset-right)) 0 max(18px, env(safe-area-inset-left));
  background:linear-gradient(180deg, rgba(var(--bar-rgb),.92), rgba(var(--bar-rgb),.74) 70%, rgba(var(--bar-rgb),0));
  backdrop-filter:saturate(140%) blur(14px);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  z-index:50;
}
.brand{display:flex;align-items:center;gap:8px;letter-spacing:.3px;min-width:0;flex:0 1 auto}
.brand-prefix + .brand-kind-pill{margin-left:-2px}
/* The brand icon is the site's apple-touch-icon, served with a small
   rounded-rect mask so it reads as the macOS/iOS-style app glyph at the
   left of the appbar. Replaces the old abstract conic-gradient dot. */
.brand-icon{
  width:30px;height:30px;border-radius:7px;flex-shrink:0;
  object-fit:cover;
  box-shadow:0 0 0 1px var(--border), 0 4px 12px rgba(0,0,0,.18);
}
/* Dark mode: the apple-touch-icon is mostly dark blue, which dissolves
   into the appbar's near-black background. A brighter hairline ring
   restores the icon's edge without making it feel like a card. The
   "auto" theme is just the absence of data-theme, so we match it via
   the system color-scheme query as well. */
:root[data-theme="dark"] .brand-icon{
  box-shadow:0 0 0 1px rgba(255,255,255,.18), 0 4px 12px rgba(0,0,0,.32);
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme]) .brand-icon{
    box-shadow:0 0 0 1px rgba(255,255,255,.18), 0 4px 12px rgba(0,0,0,.32);
  }
}
@media (max-width:760px){
  .brand-icon{width:26px;height:26px;border-radius:6px}
}
.brand-title{font-weight:600;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}

/* Brand dropdown trigger: the whole "[logo] KK's [kind] [chevron]" area is
   clickable, but only the kind+chevron pill carries the button visuals. The
   prefix text and dot stay quiet so the header still reads as branding. */
.brand-trigger{
  background:transparent;border:none;padding:0;cursor:pointer;color:inherit;
  border-radius:10px;
  /* hairline focus ring lives on the whole hit zone for keyboard users */
}
.brand-trigger:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.brand-trigger .chip-chevron{margin-left:2px}
.brand-prefix{
  font-weight:600;font-size:15px;color:var(--text);
  white-space:nowrap;
}
.brand-kind-pill{
  display:inline-flex;align-items:center;gap:6px;
  padding:0 10px;height:32px;border-radius:7px;
  background:var(--surface-overlay);
  border:1px solid var(--border);
  color:var(--text);font-weight:600;font-size:13px;
  transition:.18s var(--ease);
  white-space:nowrap;
}
.brand-kind-pill .brand-kind-label{
  overflow:hidden;text-overflow:ellipsis;max-width:200px;
}
.brand-kind-pill .brand-kind-text{
  overflow:hidden;text-overflow:ellipsis;max-width:240px;
}
/* The kind icon sits at the head of the pill so the chip reads as
   "[icon] KK's Game Library [chevron]" — a single composite label
   rather than icon + prefix + pill. Sized to the same metric as the
   chevron so the eye sees them as balanced bookends. */
.brand-kind-pill .brand-kind-icon{
  display:inline-flex;align-items:center;
  width:14px;height:14px;flex-shrink:0;
  margin-right:1px;
  color:var(--accent-2, var(--accent));
}
.brand-kind-pill .brand-kind-icon svg{width:14px;height:14px;display:block}
.brand-trigger:hover .brand-kind-pill{
  background:rgba(124,92,255,.16);
  border-color:rgba(124,92,255,.45);
}
.brand-trigger:active .brand-kind-pill{
  background:rgba(124,92,255,.28);
  border-color:rgba(124,92,255,.65);
  transform:translateY(0.5px);
}
.brand-trigger[aria-expanded="true"] .brand-kind-pill{
  background:rgba(124,92,255,.22);
  border-color:rgba(124,92,255,.55);
}
.brand-trigger[aria-expanded="true"] .brand-kind-pill .chip-chevron{transform:rotate(180deg)}

/* Segmented control: tabs sit edge-to-edge with no outer capsule. Only
   the outermost visible tab corners are rounded, and the seam between
   adjacent tabs is a single hairline (we collapse the duplicate border
   with `.tab + .tab`'s missing left border). */
.tabs{
  margin-left:auto;display:flex;gap:0;
}
.tab{
  display:flex;align-items:center;gap:8px;
  height:32px;padding:0 12px;border-radius:0;
  color:var(--text-2);font-size:12px;font-weight:500;
  background:var(--surface);
  border:1px solid var(--border);
  transition:all .15s var(--ease);
  white-space:nowrap;
}
.tab + .tab{border-left:none}
.tab:first-child{border-top-left-radius:7px;border-bottom-left-radius:7px}
.tab:last-child{border-top-right-radius:7px;border-bottom-right-radius:7px}
/* When the manage tab is hidden (visitor mode), the list tab becomes the
   visually right-most segment and inherits the outer-right rounding. */
.tab[data-mode="list"]:has(+ .tab[hidden]){border-top-right-radius:7px;border-bottom-right-radius:7px}
.tab:hover{color:var(--text);background:var(--surface-overlay);border-color:var(--border-hi)}
.tab[aria-current="true"]{
  color:#fff;background:linear-gradient(180deg, rgba(124,92,255,.7), rgba(124,92,255,.5));
  box-shadow:0 6px 18px rgba(124,92,255,.35), inset 0 1px 0 rgba(255,255,255,.18);
  border-color:transparent;
}
.tab svg{width:16px;height:16px;opacity:.85}

.bar-right{display:flex;align-items:center;gap:8px}
/* Every appbar control (brand pill, segmented tabs, add button, lang
   globe) matches the brand-icon's metric — 30px tall, 7px radius on
   wider layouts; 26px / 6px on phones — so the row reads as one
   continuous rail with the favicon as the visual anchor. */
.icon-btn{
  width:32px;height:32px;border-radius:7px;
  display:grid;place-items:center;
  background:var(--surface-overlay);border:1px solid var(--border);
  color:var(--text-2);transition:.2s var(--ease);
}
.icon-btn:hover{color:var(--text);border-color:var(--border-hi);background:var(--surface-overlay-hi)}
.icon-btn svg{width:14px;height:14px}
.lang-pill{
  display:inline-flex;align-items:center;gap:6px;
  padding:0 10px;height:32px;border-radius:7px;
  background:var(--surface-overlay);border:1px solid var(--border);
  color:var(--text-2);font-size:12px;font-weight:500;
  transition:.2s var(--ease);
}
.lang-pill:hover{color:var(--text);border-color:var(--border-hi)}
.lang-pill svg:first-child{width:13px;height:13px}
.lang-pill .chip-value{color:var(--text);font-weight:600}
.lang-pill .chip-chevron{width:10px;height:10px;opacity:.55;margin-left:2px}
.lang-pill[aria-expanded="true"] .chip-chevron{transform:rotate(180deg)}

/* ───────────────── filter bar ───────────────── */
/* Solid base + heavier blur so the next-down sticky section header can sit
   flush against it without a visible fade-to-transparent strip in between. */
.filterbar{
  position:fixed;top:var(--bar-h);left:0;right:0;height:var(--filter-h);
  display:flex;align-items:center;gap:10px;
  padding:0 max(18px, env(safe-area-inset-right)) 0 max(18px, env(safe-area-inset-left));
  background:rgba(var(--bar-rgb),.6);
  border-bottom:1px solid var(--border);
  z-index:45;overflow-x:auto;scrollbar-width:none;
}
.filterbar::-webkit-scrollbar{display:none}
.search{
  display:flex;align-items:center;gap:6px;
  background:var(--surface-overlay);border:1px solid var(--border);
  /* Match the .chip pill rounding + 30px target height so the filter bar
     reads as one consistent row of controls. The 30px matches .chip
     visually so both kinds of pill align flush. */
  border-radius:999px;padding:4px 10px;min-width:140px;flex:0 1 200px;
  height:30px;
}
.filterbar .search{height:30px}
.search:focus-within{border-color:var(--border-hi);background:var(--surface-overlay-hi)}
/* In the suggest dialog (and other column-stacked forms) the search input
   shouldn't be a flex item — it should fill the field width. */
.suggest-form .search,
.panel .search{flex:1 1 auto;min-width:0;width:100%;padding:8px 12px;height:auto}
.suggest-form .search input,
.panel .search input{font-size:13px;line-height:1.3;padding:0;height:auto}
.search svg{width:13px;height:13px;color:var(--muted)}
.search input{background:transparent;border:none;outline:none;width:100%;font-size:12px;min-width:0}
.search input::placeholder{font-size:12px;color:var(--muted)}
.search-clear{
  width:18px;height:18px;border-radius:50%;
  background:var(--surface-overlay-hi);color:var(--text-2);
  display:grid;place-items:center;flex-shrink:0;
  border:none;cursor:pointer;padding:0;
  transition:.15s var(--ease);
}
.search-clear svg{width:10px;height:10px;color:currentColor}
.search-clear:hover{background:var(--border-hi);color:var(--text)}
.chip{
  display:inline-flex;align-items:center;gap:6px;
  padding:7px 12px;border-radius:999px;
  background:var(--surface-overlay);border:1px solid var(--border);
  color:var(--text-2);font-size:12px;font-weight:500;
  transition:.2s var(--ease);white-space:nowrap;
}
.chip:hover{color:var(--text);border-color:var(--border-hi)}
.chip[aria-pressed="true"]{
  background:rgba(124,92,255,.18);border-color:rgba(124,92,255,.6);
  color:#fff;
}
.chip select{
  appearance:none;background:transparent;border:none;outline:none;color:inherit;font:inherit;
  padding-right:14px;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2 4l3 3 3-3' fill='none' stroke='%23a6acd2' stroke-width='1.4'/></svg>");
  background-repeat:no-repeat;background-position:right 0 center;
}
.chip select option{background:var(--surface);color:var(--text)}
.spacer{flex:1}

/* multi-select filter chips */
.filter-chip{cursor:pointer}
.filter-chip .chip-key{color:var(--text-2)}
.filter-chip .chip-divider{color:var(--muted);font-weight:400}
.filter-chip .chip-value{color:var(--text);font-weight:600}
.filter-chip .chip-chevron{width:10px;height:10px;opacity:.55;margin-left:2px;transition:.2s var(--ease)}
.filter-chip[aria-expanded="true"] .chip-chevron{transform:rotate(180deg)}
.filter-chip[data-active="true"]{
  background:rgba(124,92,255,.22);border-color:rgba(124,92,255,.6);
}
/* Active-state text colours pick from a theme-aware var so the chip stays
   readable in both modes: dark mode keeps the pale-purple/white pairing,
   light mode flips to deep accent text so it doesn't read as white-on-
   lavender. */
.filter-chip[data-active="true"] .chip-key{color:var(--filter-chip-active-key)}
.filter-chip[data-active="true"] .chip-value{color:var(--filter-chip-active-value)}

/* shared popover */
.filter-popover{
  position:fixed;z-index:80;
  min-width:220px;max-width:320px;max-height:60vh;overflow-y:auto;
  background:var(--popover-bg);backdrop-filter:saturate(140%) blur(14px);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  border:1px solid var(--border-hi);border-radius:12px;
  box-shadow:var(--shadow-2);padding:6px;
  animation:popoverIn .18s var(--ease);
}
@keyframes popoverIn{from{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:none}}
.popover-actions{
  display:flex;justify-content:space-between;gap:6px;padding:4px 4px 6px;
}
/* Full-row Show All affordance at the top of each filter popover. */
.popover-row-all{cursor:pointer}
.popover-row-all:hover{background:var(--hairline)}
.popover-actions button{
  font-size:11px;color:var(--text-2);padding:4px 8px;border-radius:6px;
  background:transparent;border:1px solid var(--border);
}
.popover-actions button:hover{color:var(--text);background:var(--surface-overlay);border-color:var(--border-hi)}
.popover-divider{height:1px;background:var(--border);margin:2px 6px 4px}
.popover-row{
  display:flex;align-items:center;gap:10px;
  padding:7px 10px;border-radius:8px;cursor:pointer;
  font-size:13px;color:var(--text);user-select:none;
}
.popover-row:hover{background:var(--hairline)}
.popover-row input[type=checkbox]{
  appearance:none;-webkit-appearance:none;
  width:16px;height:16px;border-radius:4px;
  border:1.5px solid var(--border-hi);background:transparent;
  display:grid;place-items:center;flex-shrink:0;cursor:pointer;margin:0;
  transition:.15s var(--ease);
}
.popover-row input[type=checkbox]:checked{background:var(--accent);border-color:var(--accent)}
.popover-row input[type=checkbox]:checked::after{
  content:"";width:9px;height:5px;border:1.5px solid #fff;
  border-top:0;border-right:0;transform:rotate(-45deg) translate(1px,-1px);
}
.popover-row:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.popover-cb{
  width:16px;height:16px;border-radius:4px;flex-shrink:0;
  border:1.5px solid var(--border-hi);background:transparent;
  display:grid;place-items:center;transition:.15s var(--ease);
}
.popover-cb[data-checked="true"]{background:var(--accent);border-color:var(--accent)}
.popover-cb[data-checked="true"]::after{
  content:"";width:9px;height:5px;border:1.5px solid #fff;
  border-top:0;border-right:0;transform:rotate(-45deg) translate(1px,-1px);
}
.popover-radio{
  width:16px;height:16px;border-radius:50%;flex-shrink:0;
  border:1.5px solid var(--border-hi);background:transparent;
  display:grid;place-items:center;transition:.15s var(--ease);
}
.popover-radio[data-checked="true"]{border-color:var(--accent)}
.popover-radio[data-checked="true"]::after{
  content:"";width:8px;height:8px;border-radius:50%;background:var(--accent);
}
.kind-icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;color:var(--text-2);flex-shrink:0}
.kind-icon svg{width:16px;height:16px}
/* When a popover row carries a leading avatar (friend filter dropdown),
   size the avatar to the row metric so it visually parallels the detail-
   dialog person chips. */
.popover-row .person-avatar{
  width:22px;height:22px;border-radius:50%;flex-shrink:0;
  font-size:11px;color:#fff;font-weight:600;
  display:inline-flex;align-items:center;justify-content:center;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.18);
  position:relative;overflow:hidden;line-height:1;
}
.popover-row .person-avatar[data-cjk="1"]{font-size:10px}
.popover-row .person-avatar .person-avatar-img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:inherit;
}
.popover-row[aria-checked="true"] .kind-icon{color:var(--accent)}
/* Single-select rows (kind / sort) — the selected entry gets a subtle
   accent-tinted background instead of a leading radio bullet, with the
   label text tinted purple so the row reads as one coherent selection
   cue. Uses `--filter-chip-active-key` (mode-aware: brighter lavender in
   dark, deeper purple in light) rather than `--accent` directly — the
   raw accent (#7c5cff) reads as faint against the lavender wash. */
.popover-row-single[aria-checked="true"]{
  background:rgba(124,92,255,.16);
  color:var(--filter-chip-active-key);
  font-weight:600;
}
.popover-row-single[aria-checked="true"] .kind-icon{
  color:var(--filter-chip-active-key);
}
.popover-row-single[aria-checked="true"]:hover{
  background:rgba(124,92,255,.22);
}

/* ───────────────── modes container ───────────────── */
main{
  position:fixed;
  top:calc(var(--bar-h) + var(--filter-h));left:0;right:0;bottom:0;
}
.mode{position:absolute;inset:0;display:none;animation:fadeIn .35s var(--ease)}
.mode.is-active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* ───────────────── grid mode (PS5-like) ───────────────── */
/* Top padding is 0 so the first sticky section header lands flush against
   the filter bar; the section header itself carries vertical padding.
   Side padding honours the notch in iPhone landscape. */
#mode-grid{
  overflow-y:auto;overflow-x:hidden;
  padding:0 max(var(--mode-pad-x), env(safe-area-inset-right))
            calc(80px + env(safe-area-inset-bottom))
            max(var(--mode-pad-x), env(safe-area-inset-left));
}
.section-block{margin-bottom:38px}
.section-block:first-child{margin-top:0}
.section-head{
  display:flex;align-items:baseline;justify-content:space-between;
  /* Negative horizontal margin extends the head edge-to-edge across the
     viewport, cancelling out the mode container's padding. Internal padding
     re-establishes the text alignment. */
  margin:0 calc(var(--mode-pad-x) * -1) 14px;
  padding:10px var(--mode-pad-x);
  position:sticky;top:0;z-index:5;
  background:transparent;
  /* No background/backdrop-filter transition: a rerender (e.g. toggling a
     game's Set Aside) inserts a fresh section-head without `.stuck`, and
     the observer adds `.stuck` synchronously — but the engine still runs
     the transition against the just-computed transparent value, which
     reads as a flicker of the backing material. Snap on / snap off. */
  border-bottom:1px solid transparent;
}
/* Material exactly matches the filterbar above (rgba + blur 14px) so
   pinned chrome reads as one continuous strip. The .stuck class is gated by
   `.is-scrolled` on the scroll container so a fresh load never shows
   material at the very first section's header. */
.mode.is-scrolled .section-head.stuck{
  background:rgba(var(--bar-rgb),var(--sticky-blur-backing-alpha));
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  backdrop-filter:saturate(140%) blur(14px);
  border-bottom-color:transparent;
}
.section-title{font-size:18px;font-weight:600;letter-spacing:.2px;display:flex;align-items:center;gap:10px}
.section-pill{
  font-size:11px;color:var(--text-2);padding:3px 8px;border-radius:999px;
  background:var(--surface-overlay);border:1px solid var(--border);
}
.section-count{font-size:12px;color:var(--muted)}
.grid{
  display:grid;
  /* Beyond the phone breakpoint we guarantee at least 4 posters per row.
     The `min(170px, 19vw)` ceiling means a 760–900px window still fits
     4 cards (vs. the previous 22vw which could fall to 3 at ~860–960px
     widths), and wider windows expand to 5–6+ via auto-fill. */
  grid-template-columns:repeat(auto-fill,minmax(min(170px,19vw),1fr));
  gap:16px;
}
.card{
  position:relative;border-radius:12px;overflow:hidden;
  background:var(--surface);
  border:1px solid var(--border);
  cursor:pointer;transition:transform .25s var(--ease), box-shadow .25s var(--ease), border-color .2s var(--ease);
  aspect-ratio:600/900;
  isolation:isolate;
}
/* Hover effect: a small symmetric scale instead of translateY. The
   prior lift moved the card's bottom edge up by 4px, so a cursor sitting
   near that bottom edge ended up outside the new bounds the moment hover
   triggered — the card snapped back down, the cursor was inside again,
   and the loop cycled ("pulse"). Scaling outward keeps the cursor inside
   the larger bounds whichever edge it sits near. */
.card:hover{transform:scale(1.02);border-color:rgba(124,92,255,.6);box-shadow:var(--shadow-2)}

/* Final tile in each grid section — a poster-sized "Recommend a X"
   add-button. Same aspect-ratio + corner radius as a real card so the
   row reads as continuous; visually distinct via a dashed border + a
   centred plus glyph instead of artwork. */
.card-suggest{
  /* Override the base .card paint: no inner artwork, no veil. */
  background:linear-gradient(180deg, rgba(124,92,255,.10), rgba(124,92,255,.04));
  border:1.5px dashed rgba(124,92,255,.45);
  color:var(--text);
  cursor:pointer;
  padding:0;
}
.card-suggest:hover{
  background:linear-gradient(180deg, rgba(124,92,255,.18), rgba(124,92,255,.08));
  border-color:rgba(124,92,255,.7);
  transform:scale(1.02);
  box-shadow:var(--shadow-2);
}
.suggest-tile-inner{
  position:absolute;inset:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;padding:14px;text-align:center;
}
.suggest-tile-plus{
  display:grid;place-items:center;
  width:54px;height:54px;border-radius:50%;
  background:linear-gradient(180deg, var(--accent), rgba(124,92,255,.6));
  color:#fff;
  box-shadow:0 6px 18px rgba(124,92,255,.35);
}
.suggest-tile-plus svg{width:24px;height:24px}
.suggest-tile-label{
  font-size:13px;font-weight:600;line-height:1.25;
  color:var(--text);
  /* Keep multi-line wrapping for the longer CN labels. */
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
@media (max-width:760px){
  .suggest-tile-plus{width:38px;height:38px}
  .suggest-tile-plus svg{width:18px;height:18px}
  .suggest-tile-inner{gap:8px}
  .suggest-tile-label{font-size:11px}
}
/* List view variant: same affordance as the grid tile, but flowed as a
   short pill-row so it doesn't break the list cadence. Sits at the end
   of every chronology section right next to the data rows. */
.list-row-suggest{
  display:flex;align-items:center;gap:10px;
  margin-top:6px;padding:9px 12px;border-radius:8px;
  background:transparent;
  border:1px dashed var(--border-hi);
  color:var(--text-2);cursor:pointer;
  font-size:13px;font-weight:500;
  transition:background .15s var(--ease), border-color .15s var(--ease), color .15s var(--ease);
  width:100%;
}
.list-row-suggest:hover{
  background:rgba(124,92,255,.08);
  border-color:rgba(124,92,255,.55);
  color:var(--text);
}
.list-row-suggest-plus{
  display:inline-flex;align-items:center;justify-content:center;
  width:24px;height:24px;border-radius:50%;
  background:rgba(124,92,255,.18);color:var(--accent);flex-shrink:0;
}
.list-row-suggest-plus svg{width:14px;height:14px}
.card .art{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transition:transform .6s var(--ease);
}
.card:hover .art{transform:scale(1.05)}
.card .veil{
  position:absolute;inset:0;
  background:linear-gradient(180deg,transparent 30%, rgba(0,0,0,.5) 75%, rgba(0,0,0,.85) 100%);
}
.card .meta{
  position:absolute;left:10px;right:10px;bottom:10px;display:flex;flex-direction:column;gap:4px;
  /* Poster overlays always sit on the dark veil, never the page background,
     so the text colour is independent of theme. */
  color:#fff;
}
.card .title{font-size:13px;font-weight:600;line-height:1.25;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  text-shadow:0 1px 4px rgba(0,0,0,.5);
}
.card .sub{font-size:11px;color:rgba(255,255,255,.78);display:flex;align-items:center;gap:8px}
.card .rating{
  position:absolute;top:8px;left:8px;
  background:rgba(0,0,0,.55);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  border-radius:8px;padding:4px 8px;font-size:11px;font-weight:600;color:#fff;
  display:flex;align-items:center;gap:3px;
}
.card .rating svg{width:11px;height:11px;color:var(--gold)}
.card .platform-badge{
  position:absolute;top:8px;right:8px;
  background:rgba(0,0,0,.55);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  border-radius:8px;padding:4px 7px;font-size:10px;font-weight:500;
  color:rgba(255,255,255,.78);
}
.card .rec-row{display:flex;flex-wrap:wrap;gap:3px;margin-top:4px}
.rec-avatar{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:18px;height:18px;padding:0 6px;border-radius:9px;
  background:linear-gradient(135deg, var(--accent), var(--accent-2));
  color:#fff;font-size:10px;font-weight:600;line-height:1;
  border:1.5px solid var(--surface);
  letter-spacing:0;white-space:nowrap;
}
.rec-avatar.is-cjk{font-size:9.5px;padding:0 5px}

/* Person chip used in the detail dialog's "recommended by" / "played with"
   cells. A circled initial + the full name; circle size is driven by the
   inherited font-size and an em-based square so it scales for CJK without
   hard-coded pixel widths. */
.person-chip{
  display:inline-flex;align-items:center;gap:6px;
  margin:2px 4px 2px 0;font-size:13.5px;line-height:1;
  white-space:nowrap;
  /* Subtle pill fill + hairline so each person reads as a distinct
     chip rather than free-floating text + avatar. */
  background:var(--surface-overlay);
  border:1px solid var(--border);
  border-radius:999px;
  padding:3px 10px 3px 3px;
}
.person-chip .person-avatar{
  width:1.7em;height:1.7em;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  color:#fff;font-weight:600;font-size:.78em;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.18);
  flex-shrink:0;line-height:1;
  position:relative;overflow:hidden;
}
.person-chip[data-cjk="1"] .person-avatar{font-size:.7em}
/* The .jpg overlay covers the gradient + initial when the image exists; on
   404 the onerror handler removes the <img>, revealing the fallback. */
.person-chip .person-avatar-img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  border-radius:inherit;
}
.person-chip .person-name{color:var(--text);font-weight:500}
/* "and N others" / "等N名玩家" trailing label sits inline with the chips —
   tight against the last chip so it reads as a continuation rather than a
   separate field. */
.person-tail{
  display:inline-block;margin-left:2px;
  font-size:13px;color:var(--text-2);font-style:italic;
}

/* Footer affordance: a big tappable "View KK's <kind> library →"
   button that sits below the last section of the current library
   kind. Cycles through kinds so visitors who reach the bottom of one
   library never dead-end. */
.library-switch-wrap{
  display:flex;justify-content:center;padding:40px 0 12px;
}
.library-switch-btn{
  display:inline-flex;align-items:center;gap:10px;
  padding:14px 22px;border-radius:14px;
  background:linear-gradient(180deg, rgba(124,92,255,.20), rgba(124,92,255,.08));
  border:1px solid rgba(124,92,255,.35);
  color:var(--text);font-size:14px;font-weight:600;
  box-shadow:0 6px 22px rgba(124,92,255,.20);
  cursor:pointer;transition:.2s var(--ease);
}
.library-switch-btn:hover{
  background:linear-gradient(180deg, rgba(124,92,255,.30), rgba(124,92,255,.14));
  border-color:rgba(124,92,255,.55);
  transform:translateY(-1px);
}
.library-switch-btn svg{width:16px;height:16px;color:var(--accent)}
.library-switch-btn .library-switch-kind-icon{
  display:inline-flex;align-items:center;width:18px;height:18px;
  color:var(--accent);margin-right:2px;
}
.library-switch-btn .library-switch-kind-icon svg{width:18px;height:18px}

/* Subtle availability disclaimer that sits between the catalog content
   and the library-switch CTA. Quiet enough to read as a legal footnote
   rather than competing with the section content above or the CTA below. */
.region-footer{
  text-align:center;padding:28px 16px 0;
  font-size:11.5px;line-height:1.5;color:var(--muted);
  letter-spacing:.01em;
}

.empty-state{
  text-align:center;padding:60px 20px;color:var(--text-2);
}
.empty-state h3{font-size:18px;margin-bottom:8px;color:var(--text)}
.empty-state p{font-size:13px;margin-bottom:16px}

/* SwiftUI-style ContentUnavailableView variant: muted glyph above the
   heading, tighter copy, no CTA. Used by the private-library lockout
   in grid + list. */
.empty-state-private .empty-icon{
  width:56px; height:56px;
  margin:0 auto 14px;
  color:var(--text-2);
  opacity:.55;
}
.empty-state-private .empty-icon svg{ width:100%; height:100%; display:block; }
.empty-state-private h3{ font-weight:600; }
.empty-state-private p{ max-width:32ch; margin-left:auto; margin-right:auto; }
.empty-state .cta{
  display:inline-flex;align-items:center;gap:8px;
  padding:10px 18px;border-radius:10px;
  background:linear-gradient(180deg,var(--accent),rgba(124,92,255,.7));
  color:#fff;font-weight:500;font-size:13px;
  box-shadow:0 6px 18px rgba(124,92,255,.4);
}

/* ───────────────── list mode ─────────────────
   A dense, table-like layout where every row uses the same grid template
   so star ratings, time-to-beat, platform, and completed-date columns line
   up vertically across the entire section. */
#mode-list{
  overflow-y:auto;overflow-x:hidden;
  padding:0 max(var(--mode-pad-x), env(safe-area-inset-right))
            calc(80px + env(safe-area-inset-bottom))
            max(var(--mode-pad-x), env(safe-area-inset-left));
}
.list-section{margin-bottom:22px}
.list-section:first-child{margin-top:0}
.list-head-wrap{
  position:sticky;top:0;z-index:6;
  margin:0 calc(var(--mode-pad-x) * -1);
  background:transparent;
  /* No background/backdrop-filter transition — see .section-head above
     for why (rerender flicker on Set Aside toggle). */
  border-bottom:1px solid var(--border);
}
.mode.is-scrolled .list-head-wrap.stuck{
  background:rgba(var(--bar-rgb),var(--sticky-blur-backing-alpha));
  backdrop-filter:saturate(140%) blur(14px);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
}
.list-head-wrap .section-head{
  position:static;top:auto;z-index:auto;
  margin:0;padding:10px var(--mode-pad-x) 0;
  background:transparent;border-bottom:0;
  -webkit-backdrop-filter:none;backdrop-filter:none;
  margin-bottom: 9px;
}
.list-head-wrap .list-head{
  position:static;top:auto;z-index:auto;
  margin:0;
  /* Match the inner column origin of each .list-row, which sits inside the
     mode container's padding and then adds 12px chip-style inset of its own. */
  padding-left:calc(var(--mode-pad-x) + 12px);
  padding-right:calc(var(--mode-pad-x) + 12px);
  background:transparent;border-bottom:0;
  -webkit-backdrop-filter:none;backdrop-filter:none;
}

/* Shared template for headers + rows. Tighten everything. */
.list-grid{
  --col-thumb: 34px;
  --col-rating: 56px;
  --col-hours:  64px;
  --col-platform: 130px;
  --col-release: 110px;
  --col-date:   110px;
  --col-people: 150px;
  display:grid;
  /* Column order: thumb, title, rating, hours, platform, released,
     last-played-date, recommender, played-with. The two "people"
     columns sit at the trailing edge so the chronology-first scan
     (released → played) reads naturally on the wide layout. */
  grid-template-columns:
    var(--col-thumb)
    minmax(0,1fr)
    var(--col-rating)
    var(--col-hours)
    var(--col-platform)
    var(--col-release)
    var(--col-date)
    var(--col-people)
    var(--col-people);
  align-items:center;
  column-gap:12px;
}
/* Mid-width window: keep platform / release / date visible but drop
   the two trailing people columns — they need genuinely wide windows
   to read without truncation. */
@media (max-width:1200px){
  .list-grid{
    grid-template-columns:
      var(--col-thumb)
      minmax(0,1fr)
      var(--col-rating)
      var(--col-hours)
      var(--col-platform)
      var(--col-release)
      var(--col-date);
  }
  .list-grid > .hide-mid,
  .list-head .hide-mid{display:none}
}
.list-head{
  font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--muted);font-weight:600;
  padding-top:6px;padding-bottom:6px;
  border-bottom:1px solid var(--border);
}
.list-head > div:first-child{grid-column:1 / span 2}
.list-row{
  padding:5px 12px;border-radius:7px;cursor:pointer;
  transition:background .15s var(--ease);
  border:1px solid transparent;
}
.list-row + .list-row{margin-top:1px}
.list-row:hover{background:var(--surface-overlay);border-color:var(--border)}
.list-row .thumb{width:34px;height:48px;border-radius:4px;object-fit:cover;background:var(--surface-2)}
.list-row .info{display:flex;align-items:baseline;gap:8px;min-width:0;line-height:1.25}
.list-row .info .t{
  font-size:13px;font-weight:600;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.list-row .info .s{
  font-size:11.5px;color:var(--text-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  flex-shrink:1;
}
.list-row .col{
  font-size:11.5px;color:var(--text-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.list-row .col.numeric{text-align:right;font-variant-numeric:tabular-nums}
.list-row .col svg{width:11px;height:11px;vertical-align:-1px;color:var(--muted);margin-right:2px}
.list-row .rating-pill{
  display:inline-block;
  background:rgba(246,194,92,.12);border:1px solid rgba(246,194,92,.28);
  color:var(--gold);padding:1px 7px;border-radius:5px;
  font-size:11px;font-weight:600;font-variant-numeric:tabular-nums;
}
.list-row .rating-input{
  width:48px;padding:2px 6px;border-radius:5px;
  background:var(--surface-overlay);
  border:1px solid var(--border);color:var(--text);
  font-size:11.5px;font-variant-numeric:tabular-nums;
  outline:none;text-align:center;
  appearance:textfield;-moz-appearance:textfield;
}
.list-row .rating-input::-webkit-outer-spin-button,
.list-row .rating-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.list-row .rating-input:focus{
  background:rgba(124,92,255,.08);border-color:rgba(124,92,255,.55);
}
.list-row .inline-input{
  width:100%;max-width:100%;padding:2px 6px;border-radius:5px;
  background:var(--surface-overlay);
  border:1px solid var(--border);color:var(--text);
  font:inherit;font-size:11.5px;font-variant-numeric:tabular-nums;
  outline:none;
  appearance:textfield;-moz-appearance:textfield;
  box-sizing:border-box;
}
.list-row select.inline-input{
  appearance:auto;-moz-appearance:auto;
  padding-right:18px;
}
.list-row .inline-input::-webkit-outer-spin-button,
.list-row .inline-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.list-row .inline-input:focus{
  background:rgba(124,92,255,.08);border-color:rgba(124,92,255,.55);
}
.list-row .inline-input::placeholder{color:var(--muted);opacity:.7}
.list-row .inline-input.is-default{
  color:var(--muted);
  opacity:.46;
  border-color:transparent;
  background:transparent;
}
.list-row .inline-input.is-default:focus{
  opacity:1;
  color:var(--text);
  background:rgba(124,92,255,.06);
  border-color:rgba(124,92,255,.45);
}
.list-row .inline-input.is-manual{
  color:var(--text);
  background:rgba(124,92,255,.06);
  border-color:rgba(124,92,255,.28);
}
@media (max-width:780px){
  .list-grid{
    /* Narrower right-side date column on phone widths so the title gets
       more room. Rating pill keeps its column width; the date col is
       sized just to hold "MMM 'YY" without padding to spare. */
    --col-rating:50px;
    --col-date:72px;
    grid-template-columns: 34px minmax(0,1fr) var(--col-rating) var(--col-date);
    column-gap:8px;
  }
  .list-row{padding:5px 8px}
  .list-head-wrap .list-head{padding-left:calc(var(--mode-pad-x) + 8px);padding-right:calc(var(--mode-pad-x) + 8px)}
  .list-row .col.hide-narrow{display:none}
  .list-head .col.hide-narrow{display:none}
}

/* ───────────────── manage mode ───────────────── */
#mode-manage{overflow-y:auto;padding:24px var(--mode-pad-x) 80px}
.manage-wrap{max-width:1100px;margin:0 auto;display:grid;gap:20px}
.panel{
  background:var(--surface);border:1px solid var(--border);
  border-radius:14px;padding:18px 20px;
}
.panel h2{
  font-size:14px;letter-spacing:.05em;text-transform:uppercase;color:var(--text-2);
  margin-bottom:14px;font-weight:600;
}
.row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.btn{
  display:inline-flex;align-items:center;gap:6px;
  padding:9px 14px;border-radius:9px;font-size:13px;font-weight:500;
  background:var(--hairline);border:1px solid var(--border);
  color:var(--text);transition:.2s var(--ease);
}
.btn:hover{background:var(--surface-overlay-hi);border-color:var(--border-hi)}
.btn svg{width:14px;height:14px}
.btn.primary{
  background:linear-gradient(180deg, var(--accent), rgba(124,92,255,.7));
  border-color:transparent;box-shadow:0 4px 14px rgba(124,92,255,.35);
  /* Force white text regardless of theme — the purple gradient stays
     dark in both modes, so inheriting `var(--text)` produced black-on-
     purple in light mode. */
  color:#fff;
}
.btn.primary:hover{box-shadow:0 6px 22px rgba(124,92,255,.55)}
.btn.primary:hover,
.btn.primary:focus-visible{color:#fff}
.btn.ghost{background:transparent;border-color:var(--border)}
.btn.danger{
  background:rgba(255,107,122,.12);border-color:rgba(255,107,122,.35);color:var(--bad)
}
.btn.danger:hover{background:rgba(255,107,122,.2)}
.input,.textarea,.select{
  width:100%;padding:9px 12px;border-radius:8px;
  background:var(--surface-overlay);
  border:1px solid var(--border);color:var(--text);font-size:13px;outline:none;
  transition:.2s var(--ease);
}
.input:focus,.textarea:focus,.select:focus{
  border-color:rgba(124,92,255,.6);background:rgba(124,92,255,.06);
}
.textarea{resize:vertical;min-height:80px;font-family:inherit}
.select option{background:var(--surface);color:var(--text)}
.field{display:flex;flex-direction:column;gap:6px;min-width:0}
.field label{font-size:11px;color:var(--text-2);letter-spacing:.04em;text-transform:uppercase;font-weight:600}
:root[data-theme="light"] .field label,
:root[data-theme="light"] .panel h2,
:root[data-theme="light"] .manage-table th,
:root[data-theme="light"] .block-label,
:root[data-theme="light"] .person-row-label{color:#444a6e}
@media (prefers-color-scheme: light){
  :root:not([data-theme]) .field label,
  :root:not([data-theme]) .panel h2,
  :root:not([data-theme]) .manage-table th,
  :root:not([data-theme]) .block-label,
  :root:not([data-theme]) .person-row-label{color:#444a6e}
}
.field-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}

/* Segmented control above the add/suggest search bar — one tab per game
   storefront (Steam / PSN / Switch / Epic / App Store). Active tab tints
   to the accent; inactive tabs read as quiet pills. Hidden for kinds
   other than `game` via the data-kind-game gate in JS. */
.storefront-picker{
  display:flex;flex-wrap:wrap;gap:4px;
  padding:4px;border-radius:10px;
  background:var(--surface-overlay);border:1px solid var(--border);
  margin-bottom:8px;width:fit-content;max-width:100%;
}
.storefront-picker .sf-tab{
  display:inline-flex;align-items:center;gap:6px;
  padding:6px 12px;border-radius:7px;
  background:transparent;border:none;cursor:pointer;
  color:var(--text-2);font-size:12px;font-weight:500;
  transition:.15s var(--ease);white-space:nowrap;
}
.storefront-picker .sf-tab:hover{color:var(--text);background:var(--surface-overlay-hi)}
.storefront-picker .sf-tab[aria-selected="true"]{
  background:rgba(124,92,255,.18);color:var(--text);
  box-shadow:inset 0 0 0 1px rgba(124,92,255,.45);
}
.storefront-picker .sf-tab svg{width:13px;height:13px;flex-shrink:0}
/* The visitor suggest dialog's storefront picker always spans the form
   width, regardless of viewport size — the dialog has its own constrained
   width and a width:fit-content picker reads as awkwardly orphaned. */
#sg-storefront-picker{width:100%;justify-content:space-between;flex-wrap:nowrap}
#sg-storefront-picker .sf-tab{flex:1 1 0;justify-content:center}
/* On phone widths the labels burn through the row width, so the picker
   collapses to icon-only and spans the full dialog. The recommend sheet
   keeps the picker discoverable without crowding the form. */
@media (max-width:560px){
  .storefront-picker{
    width:100%;justify-content:space-between;
    flex-wrap:nowrap;
  }
  .storefront-picker .sf-tab{
    flex:1 1 0;justify-content:center;padding:8px 6px;
  }
  .storefront-picker .sf-tab .sf-label{display:none}
  .storefront-picker .sf-tab svg{width:16px;height:16px}
}

.manage-search-result{
  display:flex;gap:10px;align-items:center;padding:8px 10px;border-radius:9px;
  background:var(--surface-overlay);border:1px solid var(--border);cursor:pointer;
  transition:.15s var(--ease);
}
.manage-search-result:hover{border-color:rgba(124,92,255,.5);background:rgba(124,92,255,.06)}
.manage-search-result img{width:60px;height:28px;border-radius:4px;object-fit:cover;background:#222;flex-shrink:0}
/* Portrait poster (Epic + passive TMDB results): keep the row height
   the same as landscape rows but show the artwork in its native
   aspect, matching what those storefronts actually return. */
.manage-search-result[data-art-shape="portrait"] img{width:32px;height:48px}
.manage-search-result[data-art-shape="square"]   img{width:36px;height:36px;border-radius:7px}
/* PSN key art is 2880x2160 (4:3) — the default 60x28 landscape frame
   crops most of the top + bottom off. A 4:3 thumb shows the whole image. */
.manage-search-result[data-art-shape="wide"]     img{width:48px;height:36px}
.manage-search-result .t{font-size:13px;font-weight:500}
.manage-search-result .s{font-size:11px;color:var(--text-2)}

.manage-table{
  width:100%;border-collapse:collapse;
}
.manage-table th,.manage-table td{
  padding:9px 10px;font-size:12.5px;text-align:left;
  border-bottom:1px solid var(--border);
}
.manage-table th{font-weight:600;color:var(--text-2);font-size:11px;letter-spacing:.04em;text-transform:uppercase}
.manage-table tr:hover td{background:rgba(255,255,255,.02)}
.manage-table .row-thumb{width:38px;height:54px;border-radius:5px;object-fit:cover;background:#222}
.manage-table td .row-actions{display:flex;gap:4px;justify-content:flex-end}

.tag{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:999px;font-size:11px;
  background:var(--hairline);border:1px solid var(--border);color:var(--text-2)}
.tag.accent{background:rgba(124,92,255,.18);border-color:rgba(124,92,255,.5);color:#ddd0ff}
/* Light mode: the chip background is a near-white purple wash, so the
   default off-white text disappears. Use a deep purple that holds the
   accent without becoming illegible. */
:root[data-theme="light"] .tag.accent{color:#3d2da6}
@media (prefers-color-scheme: light){
  :root:not([data-theme]) .tag.accent{color:#3d2da6}
}

.toast{
  position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(20px);
  background:var(--popover-bg);backdrop-filter:saturate(140%) blur(12px);
  -webkit-backdrop-filter:saturate(140%) blur(12px);
  border:1px solid var(--border-hi);border-radius:12px;padding:12px 18px;
  font-size:13px;color:var(--text);box-shadow:var(--shadow-2);
  opacity:0;pointer-events:none;transition:.3s var(--ease);z-index:200;
}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
.toast.good{border-color:rgba(52,211,153,.5)}
.toast.bad{border-color:rgba(255,107,122,.5)}

/* ───────────────── dialogs ───────────────── */
dialog{
  background:transparent;border:none;padding:0;color:inherit;max-width:none;max-height:none;
  width:100%;height:100%;overflow:hidden;
}
dialog::backdrop{background:rgba(0,2,8,.7);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
/* When the user pulls the detail card down, fade the backdrop in step
   with the card translation. `--pull-fade` is driven from JS during
   the pull gesture; falls back to 1 (full opacity) when not set. */
#dlg-game::backdrop{opacity:var(--pull-fade,1);transition:opacity .15s linear}
/* Dismissal animation: when a dialog gets `.is-closing`, the card
   eases down + fades and the backdrop fades out. The .close() call
   is deferred until the animation completes so the user sees the
   exit. Applied via JS where it matters; doesn't affect the entry
   path which uses the existing popIn keyframe. */
dialog.is-closing .dialog-card{
  animation:popOut .22s var(--ease) forwards;
}
dialog.is-closing::backdrop{
  animation:backdropFadeOut .22s var(--ease) forwards;
}
@keyframes popOut{from{opacity:1;transform:none}to{opacity:0;transform:scale(.96) translateY(14px)}}
@keyframes backdropFadeOut{from{opacity:1}to{opacity:0}}
.dialog-frame{
  position:absolute;inset:0;display:grid;place-items:center;padding:24px;
  pointer-events:none;
}
.dialog-card{
  pointer-events:auto;
  width:100%;max-width:780px;max-height:90vh;overflow-y:auto;
  background:linear-gradient(180deg, var(--surface-2), var(--surface));
  /* No layout-affecting border — the box-shadow handles separation, and a
     1px border was creating a visible 1px gap at the top of the hero
     image (the image starts inside the card content box). The hairline
     ring is added back in dark mode only via a 0-spread box-shadow. */
  border:none;border-radius:18px;
  box-shadow:var(--shadow-2);
  animation:popIn .35s var(--ease-back);
  position:relative;
}
/* Dark mode keeps the hairline ring, just via shadow so it doesn't push
   the hero image inward. Light mode drops it entirely; the soft shadow
   under the card is enough on the white page. Hairline uses an explicit
   off-white tint rather than --border-hi (which is faint enough at
   ~rgba(255,255,255,.08) to disappear) so the card has a discernible
   edge against the dimmed backdrop. */
:root[data-theme="dark"] .dialog-card{
  box-shadow:var(--shadow-2), 0 0 0 1px rgba(255,255,255,.18);
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme]) .dialog-card{
    box-shadow:var(--shadow-2), 0 0 0 1px rgba(255,255,255,.18);
  }
}
@keyframes popIn{from{opacity:0;transform:scale(.95) translateY(20px)}to{opacity:1;transform:none}}
.dialog-close{
  position:absolute;top:14px;right:14px;z-index:10;
  width:36px;height:36px;border-radius:50%;
  background:rgba(0,0,0,.55);backdrop-filter:blur(8px);
  -webkit-backdrop-filter:blur(8px);
  display:grid;place-items:center;color:#fff;
  font-size:28px;font-weight:300;line-height:1;
  border:1px solid rgba(255,255,255,.1);
  cursor:pointer;
}
.dialog-close:hover{background:rgba(0,0,0,.78)}
/* When the close button is a sibling of the scrolling .dialog-card (rather
   than inside it), it stays planted in the viewport's top-right while card
   content scrolls underneath. Anchored to .dialog-shell. */
.dialog-close-floating{
  position:absolute;top:14px;right:14px;z-index:30;
  pointer-events:auto;
}
.dialog-hero{
  /* No corner radius here — the parent .dialog-card already clips its
     own rounded corners. Repeating the radius on the hero meant any tiny
     mismatch (e.g. when the card uses 14px on narrow screens) showed up
     as a visible step between the two. */
  height:240px;position:relative;overflow:hidden;background:var(--surface-3);
}
.dialog-hero img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* Dark mode: gentle fade from the lower third of the hero down to the
   card's surface tone, so the title row picks up a quiet vignette
   without the previous hard band (which read as a stripe). Light mode
   collapses --hero-shade-1/2 to transparent, so this gradient becomes
   a no-op and the artwork meets the title row cleanly. */
.dialog-hero::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,
    transparent 0%,
    transparent 55%,
    var(--hero-shade-1) 85%,
    var(--hero-shade-2) 100%);
}
.dialog-title{
  font-size:24px;font-weight:700;letter-spacing:-.01em;
  line-height:1.15;margin:0;
}
.dialog-body{padding:14px 24px 22px;display:flex;flex-direction:column;gap:18px}
.attrib-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:14px}
.attrib{
  background:var(--surface-overlay);border:1px solid var(--border);border-radius:10px;padding:10px 12px;
}
.attrib .label{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-2);margin-bottom:4px}
.attrib .value{font-size:14px;font-weight:600}
.attrib .value.muted{color:var(--muted);font-weight:400;font-style:italic;font-size:12px}
/* Icon-prefixed pill: small SVG sits left of the text, vertically centred. */
.attrib.attrib-icon-row .value{display:inline-flex;align-items:center;gap:7px}
.attrib-icon{
  width:15px;height:15px;flex-shrink:0;color:var(--text-2);
  display:inline-flex;align-items:center;justify-content:center;
}
.attrib-icon.platform-app-icon{
  width:18px;height:18px;border-radius:4px;object-fit:cover;
  background:var(--surface-overlay);
  box-shadow:inset 0 0 0 1px var(--border);
}
.attrib-icon-row .value.muted .attrib-icon{opacity:.6}
.attrib-icon-row .value > span{display:inline-block;line-height:1}
.attrib-icon-star{color:var(--gold)}
/* Rating pill visualises stars + numeric score side by side. */
.attrib-rating .value{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}
.rating-stars{display:inline-flex;gap:1px;color:var(--gold)}
.rating-star{width:14px;height:14px;flex-shrink:0}
.rating-star.is-empty{opacity:.25}
.rating-num{font-size:13px;font-variant-numeric:tabular-nums;color:var(--text)}
/* TMDB watch-provider row inside the platform pill: fixed-height square
   logos. Light-mode falls back to a subtle ring so white-on-white logos
   don't disappear. */
.watch-providers{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.watch-provider-logo{
  width:26px;height:26px;border-radius:6px;object-fit:cover;
  box-shadow:inset 0 0 0 1px var(--border);
  background:var(--surface-overlay);
}
.watch-provider-logo.platform-app-icon{background:var(--surface-overlay)}
.watch-provider-fallback{
  width:26px;height:26px;border-radius:6px;
  display:inline-flex;align-items:center;justify-content:center;
  font-size:10px;font-weight:600;color:var(--text-2);
  background:var(--surface-overlay);border:1px solid var(--border);
}
/* Person rows live outside the attrib-grid so the chips can wrap onto their
   own lines without ballooning a neighbouring pill. No border on the row —
   the heading label + chip flow alone should read as a related cluster.
   The wrapper itself flexes so two rows can sit side-by-side when they
   each fit a fair minimum; otherwise they wrap onto separate lines. */
.person-rows{
  display:flex;flex-wrap:wrap;gap:14px 28px;margin-top:-4px;
}
.person-row{
  display:flex;flex-direction:column;gap:6px;
  /* Lower flex-basis so iPhone Pro / Pro Max widths (≈393–430px) can
     fit two columns side-by-side when each row is short. Long lists
     still wrap to their own row thanks to flex-wrap. */
  flex:1 1 160px;min-width:0;
}
.person-row-label{
  font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--text-2);
}
.person-row-chips{
  display:flex;flex-wrap:wrap;align-items:center;gap:2px 4px;
  font-size:13.5px;
}
/* Review and description blocks render as plain prose under their label;
   no quotation bar, no inset background. The label keeps its uppercase
   eyebrow style so it still reads as a distinct section. */
.review-block{
  font-size:13.5px;line-height:1.6;
}
.review-block div {
  white-space: pre-wrap;
}
.review-block .from{font-size:11px;color:var(--text-2);margin-bottom:6px;letter-spacing:.05em;text-transform:uppercase}
.rec-block{
  display:flex;flex-direction:column;gap:8px;
}
.rec-block .heading{font-size:11px;color:var(--text-2);letter-spacing:.05em;text-transform:uppercase}
.rec-block .text{font-size:13.5px;line-height:1.5}
.screenshots{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;
}
.screenshots img{aspect-ratio:16/9;object-fit:cover;border-radius:8px;background:var(--surface-3)}
/* Phone widths: force exactly two screenshots per row, since auto-fit
   with a 180px min would otherwise collapse to a single full-width
   image at narrow viewports — which feels comically large for a
   gallery. A single-image gallery is a special case styled below. */
@media (max-width:560px){
  .screenshots{grid-template-columns:repeat(2,1fr)}
  .screenshots:has(img:only-child){grid-template-columns:1fr}
}
.embed-wrap{aspect-ratio:16/9;border-radius:10px;overflow:hidden;background:#000}
.embed-wrap iframe{width:100%;height:100%;border:none}

/* friend suggestion dialog tweaks */
/* Recommend sheet looks generic at full 780px width on a desktop — narrow
   it back to a more focused panel size. Phones still get the full width
   via the existing 760px breakpoint that overrides the dialog-card. */
#dlg-suggest .dialog-card{max-width:520px}
.suggest-form{display:flex;flex-direction:column;gap:14px;padding:24px}
.suggest-form h3{font-size:18px;font-weight:600;display:flex;align-items:center;gap:8px}
.suggest-form .field label{color:var(--text-2)}
.suggest-results{display:flex;flex-direction:column;gap:6px;max-height:280px;overflow-y:auto}
/* Recommend-dialog subtitle: a single paragraph that combines the
   "your recommendation will be delivered" line with the "need ideas?
   friends have recommended X, Y, Z" prompt. Both sentences share the
   same muted prose styling so the second isn't visually walled off. */
.sg-subtitle{color:var(--text-2);font-size:13px;line-height:1.55;margin:0}
.sg-examples:empty{display:none}
.sg-example-link{color:var(--accent);font-weight:500}
.sg-example-link[href]:hover{text-decoration:underline}

/* loader */
.loader{
  position:fixed;inset:0;display:grid;place-items:center;
  background:var(--bg);z-index:300;transition:opacity .5s var(--ease);
}
.loader.hide{opacity:0;pointer-events:none}
.loader-ring{
  width:46px;height:46px;border-radius:50%;
  border:3px solid var(--surface-overlay-hi);border-top-color:var(--accent);
  animation:spin 1s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}

/* admin dot */
.admin-dot{
  width:8px;height:8px;border-radius:50%;background:var(--muted);
  display:inline-block;margin-right:5px;
}
.admin-dot.ok{background:var(--good);box-shadow:0 0 8px var(--good)}

@media (max-width:760px){
  :root{--bar-h:54px;--filter-h:52px;--mode-pad-x:14px;--section-head-h:42px}
  .brand-title{font-size:14px}
  .brand-prefix{font-size:13px;flex-shrink:0}
  /* Every appbar control matches the same 30px / 6px metric on phones
     so the row reads as one consistent rail of controls. */
  .brand-kind-pill{font-size:12px;height:30px;padding:0 10px;border-radius:6px;min-width:0;flex:0 1 auto}
  .brand-kind-pill .brand-kind-label{
    max-width:none;min-width:0;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }
  /* brand-dot retired; .brand-icon handles its own phone-size override */
  .brand{gap:8px}
  .brand-trigger{min-width:0;flex:0 1 auto;}
  /* Single-line phone header: brand + tabs + lang share one row. */
  .tabs{margin-left:auto;order:0;width:auto;justify-content:flex-end;gap:0;flex-shrink:0}
  .tab{height:30px;padding:0 10px;font-size:11px;justify-content:center}
  .tab:first-child{border-top-left-radius:6px;border-bottom-left-radius:6px}
  .tab:last-child{border-top-right-radius:6px;border-bottom-right-radius:6px}
  .tab[data-mode="list"]:has(+ .tab[hidden]){border-top-right-radius:6px;border-bottom-right-radius:6px}
  .tab span{display:none}
  .appbar{flex-wrap:nowrap;height:var(--bar-h);padding:8px 12px;overflow:hidden}
  .bar-right{margin-left:6px;flex-shrink:0}
  /* Narrow viewport: collapse the language pill to just the globe icon,
     sized to match the rest of the appbar rail. */
  .lang-pill{padding:0;gap:0;width:30px;height:30px;border-radius:6px;justify-content:center}
  .icon-btn{width:30px;height:30px;border-radius:6px}
  .icon-btn svg{width:14px;height:14px}
  .lang-pill .chip-value,
  .lang-pill .chip-chevron{display:none}
  .lang-pill svg:first-child{flex-shrink:0}
  /* iOS auto-zooms when a focused input renders below 16px. Bump every
     focusable form control to 16px on phone-sized viewports so tap-to-
     focus doesn't pan the page, while leaving desktop typography
     unchanged. The viewport meta already pins maximum-scale=1 which
     also defeats double-tap-to-zoom. */
  .input,.textarea,.select,
  .search input,
  .suggest-form input,.suggest-form textarea,
  .edit-title-input,
  .edit-search-pill input,
  .friend-chip-search,
  .list-row .rating-input,
  input[type="text"],input[type="search"],input[type="number"],
  input[type="url"],input[type="date"],input[type="month"],input[type="email"]
  {font-size:16px}
  /* Denser phone grid: 3 columns at typical phone widths (≥360px). */
  .grid{grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px}
  .card .title{font-size:11.5px}
  #mode-grid,#mode-list{padding:0 var(--mode-pad-x) 60px}
  #mode-manage{padding:14px var(--mode-pad-x) 60px}
}
@media (min-width:480px) and (max-width:760px){
  /* On larger phones (414, 430, 480px wide), bump to ~4 cards per row. */
  .grid{grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:10px}
}

/* ───────────────── rating range slider (filter popover) ───────────────── */
.range-popover{padding:10px 12px 12px;min-width:260px}
.range-popover .range-readout{
  display:flex;justify-content:space-between;
  font-size:12px;color:var(--text-2);margin-bottom:14px;
}
.range-popover .range-readout strong{
  color:var(--text);font-weight:600;font-variant-numeric:tabular-nums;
  background:rgba(246,194,92,.14);border:1px solid rgba(246,194,92,.3);
  padding:2px 8px;border-radius:6px;color:var(--gold);
}
.range-track-wrap{
  position:relative;height:30px;margin:0 8px;
}
.range-track{
  position:absolute;left:0;right:0;top:13px;height:4px;
  background:var(--surface-overlay-hi);border-radius:2px;
}
.range-fill{
  position:absolute;top:13px;height:4px;border-radius:2px;
  background:linear-gradient(90deg, var(--accent), var(--accent-2));
}
.range-track-wrap input[type="range"]{
  position:absolute;top:0;left:0;width:100%;height:30px;margin:0;
  appearance:none;-webkit-appearance:none;background:transparent;outline:none;
  pointer-events:none;
}
.range-track-wrap input[type="range"]::-webkit-slider-thumb{
  appearance:none;-webkit-appearance:none;
  width:18px;height:18px;border-radius:50%;
  background:#fff;border:2px solid var(--accent);
  box-shadow:0 2px 6px rgba(0,0,0,.4);cursor:pointer;
  pointer-events:auto;margin-top:0;
}
.range-track-wrap input[type="range"]::-moz-range-thumb{
  width:18px;height:18px;border-radius:50%;
  background:#fff;border:2px solid var(--accent);
  box-shadow:0 2px 6px rgba(0,0,0,.4);cursor:pointer;pointer-events:auto;
}
.range-popover .range-unrated{
  margin-top:14px;padding:8px 10px;border-radius:8px;cursor:pointer;
  display:flex;align-items:center;gap:10px;font-size:13px;
  border:1px solid var(--border);
  user-select:none;
}
.range-popover .range-unrated:hover{background:var(--surface-overlay)}
.range-popover .range-unrated input[type=checkbox]{
  appearance:none;-webkit-appearance:none;
  width:16px;height:16px;border-radius:4px;
  border:1.5px solid var(--border-hi);background:transparent;
  display:grid;place-items:center;flex-shrink:0;cursor:pointer;margin:0;
}
.range-popover .range-unrated input[type=checkbox]:checked{background:var(--accent);border-color:var(--accent)}
.range-popover .range-unrated input[type=checkbox]:checked::after{
  content:"";width:9px;height:5px;border:1.5px solid #fff;
  border-top:0;border-right:0;transform:rotate(-45deg) translate(1px,-1px);
}

/* tab visibility helper used to gate the manage tab on ?manage=1 */
.tab[hidden]{display:none}

/* ───────────────── art slots (grid / list / hero) ───────────────── */
/* The mountArtwork() helper replaces innerHTML of each slot once Steam
   (or SteamGridDB, or the gradient fallback) resolves. Slot sizing comes
   from these classes — never from inline styles — so the eventual image
   inherits the right shape regardless of which source loads. */
.art-slot, .thumb-slot, .hero-slot{
  display:block;width:100%;height:100%;
  background:var(--surface-3);
}
.art-slot{position:absolute;inset:0}
.art-slot img, .hero-slot img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transition:transform .6s var(--ease);
}
.thumb-slot{
  width:34px;height:48px;border-radius:4px;overflow:hidden;
  background:var(--surface-2);
}
.thumb-slot img{width:100%;height:100%;object-fit:cover;display:block}
.hero-slot{position:absolute;inset:0;overflow:hidden}

.art-gradient{
  display:grid;place-items:center;text-align:center;
  padding:8px;
}
.art-gradient-title{
  font-size:13px;font-weight:600;color:rgba(255,255,255,.92);
  line-height:1.2;
  text-shadow:0 1px 3px rgba(0,0,0,.5);
  word-break:break-word;
  display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;
}
.thumb-slot.art-gradient .art-gradient-title{font-size:9px;-webkit-line-clamp:3}
.hero-slot.art-gradient .art-gradient-title{font-size:28px;font-weight:700;letter-spacing:-.01em;-webkit-line-clamp:3}

/* ───────────────── detail dialog: bigger + title row + nav buttons ───────────────── */
/* The shell exists so the nav buttons (positioned absolute) aren't clipped
   by the card's overflow:auto. Card scrolls; shell is overflow:visible. */
.dialog-shell{
  position:relative;width:100%;max-width:1100px;
  height:calc(100dvh - 48px);
  max-height:calc(100dvh - 48px);
  pointer-events:auto;
  display:flex;align-items:stretch;
}
@supports not (height: 100dvh){
  .dialog-shell{height:calc(100vh - 48px);max-height:calc(100vh - 48px)}
}
.dialog-card{
  /* Fill the shell so the dialog is always the max supported height with
     a reasonable margin — content scrolls inside this fixed frame. */
  flex:1 1 auto;width:100%;height:100%;max-width:none;max-height:none;
  -webkit-overflow-scrolling:touch;
  overscroll-behavior:contain;
}
.dialog-hero{height:340px;flex-shrink:0}
.dialog-body{padding:18px 28px 26px;gap:20px}

.dialog-title-row{
  display:flex;align-items:flex-start;justify-content:space-between;gap:14px;flex-wrap:wrap;
}
.dialog-title-row .dialog-title{flex:1 1 240px;min-width:0}
/* Hero variant: the title row floats over the bottom of the hero image so
   the banner artwork extends beneath the title and external-link button.
   Inner padding matches the dialog body so they line up vertically. */
.dialog-hero-with-title{display:flex;align-items:flex-end}
.dialog-hero-with-title .hero-slot{position:absolute;inset:0}
.dialog-hero-with-title .dialog-title-row{
  position:relative;z-index:3;width:100%;
  padding:18px 24px 16px;color:#fff;
}
.dialog-hero-with-title .dialog-title{
  text-shadow:0 2px 12px rgba(0,0,0,.55);
}
/* Buttons sitting on the hero image always need white text regardless
   of theme — the underlying artwork can be anything from a bright film
   poster to a moody backdrop. We alpha the *background* only (not the
   whole button) so the label stays full-opacity legible, and the alpha
   is softer than the previous .45 to feel less like a hard chip. */
.dialog-hero-with-title .dialog-title-row .btn{
  color:var(--hero-action-text);
  background:var(--hero-action-bg);
  border-color:transparent;
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
}
.dialog-hero-with-title .dialog-title-row .btn:hover{
  background:var(--hero-action-bg-hover);
}
.dialog-hero-with-title .dialog-title-row .btn.danger{
  color:#ffd0d5;
  background:rgba(120,30,40,.32);
  border-color:transparent;
}
.dialog-hero-with-title .dialog-title-row .btn.danger:hover{
  background:rgba(160,30,40,.5);
}
/* Action buttons (Edit / Delete / external) trailing-align on the right;
   the title fills the remaining space. */
.dialog-hero-with-title .dialog-title-row{
  flex-wrap:nowrap;gap:10px;align-items:center;
}
.dialog-hero-with-title .dialog-title-row .dialog-title{
  flex:1 1 auto;min-width:0;
}
.dialog-title-actions{
  margin-left:auto;justify-content:flex-end;
}
@media (max-width:560px){
  /* Phone width: keep title + Learn-more on one row when they fit. The
     row still has flex-wrap:wrap, so when the title is too long the
     button drops to its own row — but the button no longer claims 100%
     width unconditionally. Title still left-aligned, button right. */
  .dialog-hero-with-title .dialog-title-row{flex-wrap:wrap}
  .dialog-title-actions{margin-left:auto;flex-wrap:wrap;justify-content:flex-end}
}
.steam-btn{
  flex:0 0 auto;align-self:flex-start;
  background:linear-gradient(135deg, #1b2838 0%, #2a475e 100%);
  color:#fff;border:1px solid rgba(255,255,255,.14);
  padding:9px 16px;font-size:13.5px;font-weight:600;border-radius:10px;
  display:inline-flex;align-items:center;gap:8px;
}
.steam-btn:hover{filter:brightness(1.15);border-color:rgba(255,255,255,.28)}
.steam-btn svg{width:14px;height:14px}

.dialog-nav{
  position:absolute;top:50%;transform:translateY(-50%);
  width:44px;height:44px;border-radius:50%;
  background:rgba(0,0,0,.55);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  display:grid;place-items:center;color:#fff;z-index:4;
  border:1px solid rgba(255,255,255,.1);
  transition:background .15s var(--ease), transform .15s var(--ease);
}
.dialog-nav.prev{left:-58px}
.dialog-nav.next{right:-58px}
.dialog-nav:hover{background:rgba(0,0,0,.78);transform:translateY(-50%) scale(1.06)}
.dialog-nav:disabled{display:none}
.dialog-nav svg{width:18px;height:18px}
@media (max-width:1240px){
  /* When the shell hits the viewport edge, tuck nav buttons inside. */
  .dialog-nav.prev{left:10px}
  .dialog-nav.next{right:10px}
}
@media (max-width:780px){
  .dialog-shell{height:calc(100vh - 16px);border-radius:14px}
  .dialog-card{border-radius:14px}
  .dialog-hero{height:220px}
  .dialog-body{padding:18px 18px 22px}
  /* On phone widths the prev/next overlay buttons would always overlap
     the card content (they're tucked inside the viewport edge). Hide
     them entirely — the user can still swipe horizontally on the card
     to navigate. */
  .dialog-nav{display:none}
}

/* Description mirrors the review block visually — same flat prose layout,
   slightly muted body colour so the upstream synopsis reads as secondary
   to KK’s own voice. */
.desc-block{
  font-size:13.5px;line-height:1.6;
}
.desc-block div {
  white-space: pre-wrap;
}
.desc-block .from{font-size:11px;color:var(--text-2);margin-bottom:6px;letter-spacing:.05em;text-transform:uppercase}
/* Long synopses get clamped to 5 lines with an inline "Show more"
   hyperlink. The .is-collapsed class drives the line clamp; JS measures
   the natural height and only adds the toggle when the prose actually
   overflows that bound, so short blurbs never sprout a useless button. */
.desc-block [data-desc-body]{position:relative}
.desc-block [data-desc-body].is-collapsed{
  display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical;
  overflow:hidden;
}
.desc-block .desc-toggle{
  display:inline;
  background:transparent;border:none;padding:0;margin-top:4px;
  color:var(--accent);font:inherit;font-weight:500;cursor:pointer;
}
.desc-block .desc-toggle:hover{text-decoration:underline}
.screenshots-block{display:flex;flex-direction:column;gap:8px}
.screenshots-block .from{font-size:11px;color:var(--text-2);letter-spacing:.05em;text-transform:uppercase}
.screenshots img{cursor:zoom-in;transition:transform .2s var(--ease)}
.screenshots img:hover{transform:scale(1.02)}

/* ───────────────── lightbox ───────────────── */
#dlg-lightbox{
  background:transparent;border:none;padding:0;color:inherit;
  width:100%;height:100%;max-width:none;max-height:none;
}
#dlg-lightbox::backdrop{background:rgba(0,0,0,.88);backdrop-filter:blur(6px)}
.lightbox-img{
  position:absolute;inset:0;margin:auto;
  max-width:min(94vw, 1600px);max-height:90vh;
  object-fit:contain;display:block;
}
.lightbox-close{
  position:absolute;top:18px;right:22px;z-index:5;
  width:38px;height:38px;border-radius:50%;
  background:rgba(0,0,0,.55);color:#fff;font-size:22px;
  border:1px solid rgba(255,255,255,.15);
}
.lightbox-nav{
  position:absolute;top:50%;transform:translateY(-50%);
  width:48px;height:48px;border-radius:50%;
  background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.15);
  color:#fff;display:grid;place-items:center;z-index:5;
}
.lightbox-nav.prev{left:18px}
.lightbox-nav.next{right:18px}
.lightbox-nav:hover{background:rgba(0,0,0,.78)}
.lightbox-nav:disabled{opacity:.25;pointer-events:none}
.lightbox-nav svg{width:22px;height:22px}

/* The dialog needs touch-action:pan-y so iOS's gesture recognizer treats
   vertical drags as scroll and lets horizontal swipes pass through to our
   detail navigation handler. */
.dialog-card{touch-action:pan-y}
.lightbox-img{touch-action:pan-y pinch-zoom}
/* Bottom thumbnail strip in the lightbox. Auto-hides when there is one
   image. Each thumb scrolls itself into view as the user navigates. */
.lightbox-thumbs{
  position:absolute;left:0;right:0;bottom:0;z-index:6;
  display:flex;gap:6px;padding:10px 14px;
  /* `safe center` lets the strip centre when its thumbs fit the row, and
     fall back to start alignment when they overflow (otherwise the start
     would scroll off-screen and become unreachable). */
  justify-content:safe center;
  overflow-x:auto;scroll-behavior:smooth;
  background:linear-gradient(180deg, transparent, rgba(0,0,0,.55));
  scrollbar-width:none;
}
.lightbox-thumbs::-webkit-scrollbar{display:none}
.lightbox-thumb{
  flex:0 0 auto;width:84px;height:48px;border-radius:6px;overflow:hidden;
  background:transparent;border:2px solid transparent;padding:0;cursor:pointer;
  opacity:.55;transition:.18s var(--ease);
}
.lightbox-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.lightbox-thumb:hover{opacity:.85}
.lightbox-thumb.active{opacity:1;border-color:#fff}
@media (max-width:760px){
  .lightbox-thumb{width:64px;height:38px}
  .lightbox-thumbs{padding:8px 10px}
}

/* ───────────────── TipKit-style first-run popover ───────────────── */
/* Anchored to the kind picker on first visit; dismisses on any tap. */
.tipkit{
  position:fixed;z-index:90;
  background:var(--popover-bg);
  backdrop-filter:saturate(140%) blur(14px);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  border:1px solid var(--border-hi);
  border-radius:14px;
  padding:14px 16px;
  box-shadow:var(--shadow-2);
  color:var(--text);
  animation:tipkitIn .25s var(--ease-back);
}
.tipkit.is-out{opacity:0;transform:translateY(-4px);transition:.2s var(--ease)}
/* Arrow now anchors via inline-set left coord (centered on the trigger
   chevron) and points up so it visually reaches toward the kind picker
   it's introducing. */
.tipkit-arrow{
  position:absolute;top:-7px;left:24px;
  width:14px;height:14px;
  background:var(--popover-bg);
  border-top:1px solid var(--border-hi);
  border-left:1px solid var(--border-hi);
  transform:translateX(-50%) rotate(45deg);
}
.tipkit-body{display:flex;align-items:flex-start;gap:10px}
.tipkit-glyph{
  display:inline-flex;align-items:center;justify-content:center;
  width:28px;height:28px;border-radius:8px;flex-shrink:0;
  background:rgba(124,92,255,.18);color:var(--accent);
}
.tipkit-glyph svg{width:18px;height:18px}
.tipkit-title{font-size:14px;font-weight:600;margin-bottom:4px}
.tipkit-text{font-size:12.5px;color:var(--text-2);line-height:1.4}
@keyframes tipkitIn{
  from{opacity:0;transform:translateY(-8px) scale(.96)}
  to{opacity:1;transform:none}
}

/* ───────────────── language + theme popover ───────────────── */
.lang-popover{min-width:220px;padding:6px}
.lang-popover-section{display:flex;flex-direction:column;gap:2px;padding:2px}
.lang-popover-divider{height:1px;background:var(--border);margin:6px 4px}
.theme-seg{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;padding:4px}
.theme-seg-btn{
  display:flex;flex-direction:column;align-items:center;gap:4px;
  padding:8px 6px;border-radius:8px;
  background:var(--surface-overlay);border:1px solid var(--border);
  color:var(--text-2);font-size:11px;
  transition:.15s var(--ease);cursor:pointer;
}
.theme-seg-btn svg{width:16px;height:16px}
.theme-seg-btn:hover{color:var(--text);border-color:var(--border-hi)}
.theme-seg-btn[aria-checked="true"]{
  background:rgba(124,92,255,.18);border-color:rgba(124,92,255,.5);
  color:var(--text);
}

/* ───────────────── add/edit dialog (detail-sheet variant) ───────────────── */
/* The edit dialog reuses .dialog-frame/.dialog-shell/.dialog-card so it
   inherits the detail dialog's sizing, hero, and close-button positioning.
   These overrides shape the form-specific bits. */
.dialog-edit-card{display:flex;flex-direction:column}
.dialog-edit-card .edit-hero{height:200px;flex-shrink:0;position:relative}
.dialog-edit-card .edit-hero::after{
  /* heavier gradient so the title inputs sitting on top stay legible */
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg, rgba(0,0,0,0) 30%, rgba(16,20,42,.85) 100%);
}
.edit-body{padding:18px 24px 22px;display:flex;flex-direction:column;gap:14px}
.edit-title-row{display:flex;gap:10px;flex-wrap:wrap}
.edit-title-input{
  flex:1 1 200px;min-width:0;background:var(--surface-overlay);
  color:var(--text);border:1px solid var(--border);border-radius:10px;
  padding:10px 12px;font-size:18px;font-weight:600;letter-spacing:-.01em;
  outline:none;transition:border-color .15s var(--ease);
}
.edit-title-input.zh{font-weight:500}
.edit-title-input:focus{border-color:var(--accent)}

.edit-source-row{
  display:grid;grid-template-columns:auto auto 1fr auto auto;gap:8px;align-items:center;
}
.edit-source-row .select,
.edit-source-row .input{height:34px;font-size:13px}
.edit-search-pill{
  display:flex;align-items:center;gap:6px;
  background:var(--surface-overlay);border:1px solid var(--border);
  border-radius:10px;padding:0 10px;min-height:34px;
}
.edit-search-pill svg{width:14px;height:14px;color:var(--text-2);flex-shrink:0}
.edit-search-pill input{
  background:transparent;border:none;outline:none;color:var(--text);
  font-size:13px;width:100%;padding:6px 0;
}
#steam-search-results{
  display:flex;flex-direction:column;gap:6px;max-height:200px;overflow-y:auto;
}
#steam-search-results:empty{display:none}

/* Attribute grid inside the edit dialog: same visual cell as the detail
   attrib grid, but the value is an editable input instead of a static span. */
.attrib-grid.edit-grid{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
.attrib.edit-attrib{
  display:flex;flex-direction:column;gap:6px;cursor:text;
}
.attrib.edit-attrib > .label{margin-bottom:0}
.attrib.edit-attrib > .input,
.attrib.edit-attrib > .select{
  background:transparent;border:none;padding:0;height:auto;
  font-size:14px;font-weight:600;color:var(--text);outline:none;
}
.attrib.edit-attrib > .select{
  /* Bring back enough room for the native chevron without a heavy border. */
  padding-right:14px;
}
.attrib.edit-attrib:focus-within{
  border-color:var(--accent);background:rgba(124,92,255,.06);
}

.edit-block{display:flex;flex-direction:column;gap:6px}
.edit-block .block-label{
  font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--text-2);
}
.edit-block .textarea{min-height:64px}

.edit-toggle{
  display:flex;align-items:center;gap:10px;font-size:13.5px;cursor:pointer;
  padding:8px 12px;background:var(--surface-overlay);
  border:1px solid var(--border);border-radius:10px;align-self:flex-start;
}
.edit-toggle input[type=checkbox]{width:16px;height:16px;cursor:pointer}

.edit-footer{
  display:flex;align-items:center;gap:8px;
  padding-top:8px;border-top:1px solid var(--border);
}

/* Detail-dialog action buttons (Edit / Delete next to external link). */
.dialog-title-actions{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
}

/* ───────────────── friend chips + autocomplete ───────────────── */
.friend-chip-host{
  display:flex;flex-wrap:wrap;gap:6px;align-items:center;
  padding:8px;background:var(--surface-overlay);
  border:1px solid var(--border);border-radius:10px;min-height:44px;
}
.friend-chip{
  display:inline-flex;align-items:center;gap:4px;
  padding:4px 6px 4px 10px;font-size:13px;line-height:1;
  background:rgba(124,92,255,.18);border:1px solid rgba(124,92,255,.4);
  border-radius:999px;color:var(--text);
}
.friend-chip-x{
  width:18px;height:18px;border-radius:50%;border:none;
  background:transparent;color:inherit;cursor:pointer;
  font-size:16px;line-height:1;padding:0;display:grid;place-items:center;
}
.friend-chip-x:hover{background:rgba(255,255,255,.12)}
.friend-chip-input-wrap{position:relative;flex:1 1 140px;min-width:140px}
.friend-chip-search{
  width:100%;background:transparent;border:none;outline:none;color:var(--text);
  font-size:13px;padding:6px 4px;
}
.friend-autocomplete{
  position:absolute;top:100%;left:0;right:0;margin-top:4px;
  background:var(--surface-2);border:1px solid var(--border-hi);
  border-radius:10px;box-shadow:var(--shadow-2);
  max-height:220px;overflow-y:auto;z-index:60;
  display:flex;flex-direction:column;padding:4px;
}
.friend-autocomplete[hidden]{display:none}
.friend-ac-row{
  display:flex;align-items:baseline;gap:8px;
  padding:8px 10px;border-radius:6px;cursor:pointer;font-size:13.5px;
}
.friend-ac-row:hover{background:rgba(124,92,255,.18)}
.friend-ac-sub{color:var(--text-2);font-size:11px}
.friend-ac-empty{padding:10px;color:var(--muted);font-size:12px;text-align:center}

/* Friend ID badge in Settings. */
.friend-id-pill{
  font-size:10px;letter-spacing:.04em;color:var(--text-2);
  background:var(--hairline);border-radius:4px;
  padding:1px 4px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,monospace;
}

/* Phone overrides for the edit dialog: stack the source row, slim padding. */
@media (max-width:760px){
  .edit-body{padding:14px 14px 18px}
  .edit-source-row{grid-template-columns:1fr 1fr;}
  .edit-source-row .edit-search-pill,
  .edit-source-row #steam-search-btn,
  .edit-source-row #form-clear{grid-column:span 2}
  .dialog-edit-card .edit-hero{height:140px}
}

/* ───────────────── kind picker auth section ─────────────────
   The sign-in / signed-in-as status lives at the bottom of the kind
   picker popover, separated from the library list by a hairline
   divider. Rows reuse the standard .popover-row affordance and the
   single .popover-header band reads as a quiet status line. */
.popover-divider{
  height:1px; background:var(--hairline);
  margin:6px 0; pointer-events:none;
}
.popover-header{
  font-size:11.5px; color:var(--text-2);
  padding:6px 10px 2px; letter-spacing:.01em;
}
.popover-header strong{color:var(--text); font-weight:600}
.popover-row-auth{ font-size:13px; }

/* Auth dialog (login / register / setup / account). Reuses .dialog-frame
   and .dialog-card from the existing system; just tighter width and a
   small tab strip for the login/register toggle. */
.auth-card{ max-width:440px; }
#dlg-auth .auth-card,
#dlg-setup .auth-card,
#dlg-account .auth-card{
  padding:22px 22px 18px;
}
.auth-tabs{
  display:flex; gap:4px;
  background:var(--hairline); border-radius:10px;
  padding:3px; margin-bottom:14px;
}
.auth-tab{
  flex:1; appearance:none; border:0; background:transparent;
  color:var(--text-2); font:inherit; font-size:13px; font-weight:500;
  padding:7px 10px; border-radius:8px; cursor:pointer;
}
.auth-tab.is-active{ background:var(--bg); color:var(--text); box-shadow:0 1px 3px rgba(0,0,0,.18); }
.auth-form{display:flex;flex-direction:column;gap:12px}
.auth-form h3{ font-size:17px; font-weight:600; margin:0 0 2px; }
.auth-form .auth-hint{ font-size:12px; color:var(--text-2); margin:0 0 6px; line-height:1.5; }
.auth-form .field{ display:flex; flex-direction:column; gap:5px; }
.auth-form .field .label{ font-size:12px; color:var(--text-2); }
.auth-form .field .input{ width:100%; }
.auth-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text);margin-top:2px}
.auth-toggle input{transform:scale(1.05)}
.auth-pwchange summary{ font-size:13px; color:var(--text-2); cursor:pointer; padding:6px 0; }
.auth-pwchange[open] summary{ color:var(--text); }
.auth-pwchange .field{ margin-top:8px; }
