/* src/client/panel.css — the DOM overlay: home screen, top bar, detail panel.
 * Needs brand.css. Markup is produced by panel.mjs (createChrome / createPanel).
 *
 * Layers: the renderer's surface sits at z-index 0 (position:absolute; top/left 0, sized by the
 * app); .mirror (a renderer's accessibility mirror, if it has one) at 1; .chrome at 2; .panel at 3;
 * .stats (harness) at 4. The page never scrolls, so absolute and fixed place alike; absolute is used
 * for anything viewport-sized or bottom-anchored on a phone, because iOS 26 Safari clips fixed layers
 * at the viewport's edge and paints a sampled colour around its floating bottom bar instead of them
 * (index.html has the note). Only small things that never reach the bottom edge stay fixed.
 */

/* A phone on its side asks to be turned back (app.mjs sets .turned), over everything open, which waits beneath it.
 * Above Dark Star's layers (its top is 90). Fixed, unlike the chrome (index.html has why): a phone turned with its keyboard
 * up is still scrolled by the keyboard, which would carry a part of the document off the top; Safari's bar is at the top then */
.turn { display: none; position: fixed; inset: 0; z-index: 100; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 16px; background: var(--void); text-align: center; touch-action: none; }
.turned .turn { display: flex; }
.turn p { margin: 0; }
.turn .turn-title { font: 500 24px/1.2 var(--display); letter-spacing: -0.02em; color: var(--text); }
.turn .turn-sub { font: 300 15px/1.4 var(--body); color: var(--grey); }

.chrome { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.chrome > * { pointer-events: auto; touch-action: none; } /* a drag from a button never scrolls the page (its band below the view) */

/* Home: the wordmark and the way in. */
.home {
  position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-58%);
  display: flex; flex-direction: column; align-items: center; gap: 34px;
  transition: opacity 600ms ease;
}
.home .wordmark { font-size: clamp(52px, 8.6vw, 104px); }
.home .home-title { margin: 0; font: inherit; }
.home { pointer-events: none; }
.home .search { pointer-events: auto; }

/* The search (search.mjs, B1): a hairline field under the wordmark. Its rows drop over the links below it, out
   of the flow, so the wordmark never moves as they come and go (the home is centred on its own height) */
.search { position: relative; width: min(400px, calc(100vw - 40px)); }
.search-input {
  display: block; width: 100%; box-sizing: border-box; margin: 0; padding: 11px 2px 10px;
  border: 0; border-bottom: 1px solid rgba(var(--vega-rgb), 0.28); border-radius: 0; background: transparent;
  color: var(--text); font: 300 16px/1.3 var(--body); letter-spacing: 0.01em; text-align: center; /* 16 px: iOS zooms into anything smaller */
  outline: none; transition: border-color 160ms ease; -webkit-appearance: none; appearance: none;
}
.search-input::placeholder { color: var(--grey); opacity: 1; }
.search-input:focus { border-bottom-color: rgba(var(--vega-rgb), 0.7); }
.search.busy .search-input { border-bottom-color: rgba(var(--vega-rgb), 0.45); }
.search-drop {
  position: absolute; left: 0; right: 0; top: calc(100% + 8px); z-index: 1;
  max-height: var(--search-room, 60vh); overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y;
  padding: 6px 0; box-sizing: border-box; border: 1px solid rgba(var(--vega-rgb), 0.16); border-radius: 4px;
  background: var(--void); /* solid: the links it drops over mustn't show through */
  /* It grows out of the field and goes back into it (docs/design/motion.md, tips 2, 10–12, 15): in, from 0.95 about
     its top edge, a little above, easing out over a menu's time; out, the same way back a fifth quicker, display
     waiting for it (allow-discrete), the closing list taking no taps */
  transform-origin: 50% 0;
  transition: opacity var(--menu-in) var(--ease-out), scale var(--menu-in) var(--ease-out), translate var(--menu-in) var(--ease-out), display var(--menu-out) allow-discrete;
}
@starting-style { .search-drop { opacity: 0; scale: 0.95; translate: 0 -6px; } }
.search-drop[hidden] {
  display: none; opacity: 0; scale: 0.95; translate: 0 -6px; pointer-events: none;
  transition: opacity var(--menu-out) var(--ease-in), scale var(--menu-out) var(--ease-in), translate var(--menu-out) var(--ease-in), display var(--menu-out) allow-discrete;
}
/* The rows that open the list come down after it one after another (tip 3; search.mjs gives each its delay), and
   wait, held at their start, while a phone's home is still gliding up (below) */
.search-drop .settling { animation: search-row-in var(--settle) var(--ease-out) backwards; }
@keyframes search-row-in { from { opacity: 0; translate: 0 calc(-1 * var(--settle-px)); } }
/* The links under the list step aside while it's open (they showed through it as it faded) */
.home .home-links { transition: opacity 280ms ease; }
.home:has(.search-drop:not([hidden])) .home-links { opacity: 0; pointer-events: none; }
.search-head, .search-empty { margin: 0; padding: 6px 14px 4px; font: 400 11px/1.2 var(--body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--grey); }
.search-empty { text-transform: none; letter-spacing: 0.02em; font-size: 13px; padding: 8px 14px; }
.search-list { list-style: none; margin: 0; padding: 0; }
.search-row { display: flex; align-items: baseline; gap: 10px; padding: 9px 14px; cursor: pointer; }
.search-title { flex: 1; min-width: 0; font: 300 15px/1.3 var(--body); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.search-meta { flex: none; font: 300 12.5px/1.3 var(--body); color: var(--grey); letter-spacing: 0.02em; }
.search-row:hover, .search-row[aria-selected="true"] { background: rgba(var(--vega-rgb), 0.08); }
.search-row[aria-selected="true"] .search-title { color: var(--vega); }
.search.stale .search-list, .search.stale .search-head { opacity: 0.45; transition: opacity var(--pane) var(--ease-out); } /* an earlier query's rows, while the new one's come */
/* The field's ghost for the phone's glide (app.mjs rise; shown by the phone block below, body.search-gliding): the field's
   box, its hairline in the focused colour and its text. Hidden everywhere else, whatever width a rotation brings (Codex) */
.search-ghost {
  display: none; position: absolute; inset: 0; box-sizing: border-box; padding: 11px 2px 10px; pointer-events: none;
  border-bottom: 1px solid rgba(var(--vega-rgb), 0.7); color: var(--grey); font: 300 16px/1.3 var(--body); letter-spacing: 0.01em;
  text-align: center; white-space: nowrap; overflow: hidden;
}
.search-ghost.typed { color: var(--text); }
body.open .home { opacity: 0; }
body.vega-lifted .home .wordmark .vega::before { visibility: hidden; } /* the canvas has the dot (app.mjs vegaFrom) */
body.open.vega-lifted .home { transition-duration: 320ms; } /* the letters clear before the constellation gathers round the dot */
body.returning .home { transition-duration: 750ms, 620ms; transition-delay: 400ms, 0s; } /* the return: the letters come as the star nears the dot (app.mjs close); a phone's glide (its transform, second) keeps its own timing (Codex) */
body.open .home > * { pointer-events: none; }

/* Top bar, present once the constellation is open. */
.topbar {
  position: absolute; left: 0; right: 0; top: 0;
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(18px + env(safe-area-inset-top, 0px)) max(22px, env(safe-area-inset-right, 0px)) 18px max(22px, env(safe-area-inset-left, 0px));
  opacity: 0; transition: opacity 400ms var(--ease-in); /* leaving: a fifth quicker, at once (motion.md, tip 15) */
}
.topbar .wordmark { font-size: 20px; opacity: 0.92; }
.topbar .actions { display: flex; gap: 8px; }
body.open .topbar { opacity: 1; transition: opacity 500ms var(--ease-out) 250ms; }
body:not(.open) .topbar > * { pointer-events: none; }

/* Detail panel. */
.panel {
  position: fixed; z-index: 3;
  top: 72px; right: 0; bottom: 0; width: min(380px, 100vw); /* below the top bar (scene TOP_INSET.desktop): the controls stay exposed while details are open */
  background: rgb(5, 5, 7); /* opaque: at 0.96 the stars' labels showed through beside a name (owner, 2026-10-30) */
  border-left: 1px solid rgba(var(--vega-rgb), 0.16);
  border-top: 1px solid rgba(var(--vega-rgb), 0.16);
  padding: 26px 28px 32px;
  box-sizing: border-box;
  overflow-y: auto;
  transform: translateX(14px); opacity: 0; visibility: hidden;
  /* Leaving, a fifth quicker and easing in; arriving, easing out (motion.md, tips 1 and 15) */
  transition: transform var(--panel-out) var(--ease-in), opacity var(--panel-out) var(--ease-in), visibility 0s linear var(--panel-out);
}
.panel.is-open { transform: none; opacity: 1; visibility: visible; transition: transform var(--panel-in) var(--ease-out), opacity var(--panel-in) var(--ease-out), visibility 0s; }
.panel .close {
  position: absolute; top: 14px; right: 14px; width: 34px; height: 34px; padding: 0;
  border: 1px solid transparent; border-radius: 3px; background: none; color: var(--grey);
  font: 300 20px/1 var(--body); cursor: pointer;
}
.panel .close:hover { color: var(--text); border-color: rgba(var(--vega-rgb), 0.28); }
.panel h2 {
  font: 500 30px/1.08 var(--display); letter-spacing: -0.025em; margin: 10px 40px 8px 0;
}
.panel .meta { color: var(--grey); font-size: 13.5px; margin: 0 0 16px; }
.panel .tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 20px; padding: 0; list-style: none; }
.panel .tags li {
  font: 400 11.5px/1 var(--body); letter-spacing: 0.02em; color: var(--text);
  border: 1px solid rgba(var(--vega-rgb), 0.22); border-radius: 3px; padding: 5px 8px;
}
.panel .why {
  color: var(--vega); font-size: 13.5px; margin: 0 0 18px; padding-left: 12px;
  border-left: 1px solid rgba(var(--vega-rgb), 0.45);
}
.panel dl { margin: 0 0 18px; display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; font-size: 13.5px; }
.panel dt { color: var(--grey); font-weight: 300; }
.panel dd { margin: 0; }
/* A fact that opens something (a film's collection): the text itself, a hairline under it, brighter on hover */
.panel .fact-link { appearance: none; background: none; border: 0; padding: 0; margin: 0; font: inherit; color: var(--text); cursor: pointer;
  text-decoration: underline; text-decoration-color: rgba(var(--vega-rgb), 0.35); text-decoration-thickness: 1px; text-underline-offset: 3px; transition: text-decoration-color 160ms ease; }
.panel .fact-link:hover, .panel .fact-link:focus-visible { text-decoration-color: var(--vega); outline: none; }
/* The sheet's head: the name and its line, and a person's portrait (B6) beside them, standing on the same baseline */
.panel .head { display: flex; align-items: flex-end; gap: 18px; margin: 0 0 16px; }
.panel .head-text { flex: 1; min-width: 0; }
.panel .head .meta { margin: 0; }
.panel .face {
  flex: none; width: 112px; aspect-ratio: 2 / 3; border-radius: 4px; overflow: hidden; background: rgba(var(--vega-rgb), 0.06);
  border: 1px solid rgba(var(--vega-rgb), 0.2); box-shadow: 0 0 28px rgba(var(--vega-rgb), 0.14);
}
.panel .face[hidden] { display: none; }
.panel .face img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center 20%; opacity: 0; transform: scale(1.04); transition: opacity 420ms ease, transform 620ms cubic-bezier(.22, 1, .36, 1); }
.panel .face.loaded img { opacity: 1; transform: none; }
.panel dl[hidden], .panel .rule[hidden] { display: none; }
/* The Lists' Loved heading carries "fly your sky" once there are three loved films or shows (B7) */
.panel.lists .list-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.panel.lists .sky-btn { appearance: none; background: none; border: 0; padding: 4px 0; cursor: pointer; font: 400 11px/1 var(--body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text); transition: color 160ms ease; }
.panel.lists .sky-btn:hover, .panel.lists .sky-btn:focus-visible { color: var(--vega); outline: none; }
.panel.lists .sky-btn[hidden] { display: none; }
.panel p.overview { font-size: 14.5px; line-height: 1.55; margin: 0; color: rgba(242, 242, 240, 0.86); }
.home .home-link { pointer-events: auto; } /* .home itself lets pointer input through to the canvas */
.home-link { background: none; border: 0; padding: 4px 6px; font: 300 13px/1 var(--body); color: var(--grey); cursor: pointer; letter-spacing: 0.02em; }
.home-link:hover, .home-link:focus-visible { color: var(--text); outline: none; }
.topbar .wordmark-btn { background: none; border: 0; padding: 0; margin: 0; color: inherit; cursor: pointer; }
.topbar .wordmark-btn:focus-visible { outline: 1px solid var(--vega); outline-offset: 4px; }
.panel.about h2 { margin-bottom: 10px; }
.panel.about p { font-size: 14.5px; line-height: 1.55; margin: 0; color: rgba(242, 242, 240, 0.86); }
.panel.about .credit { display: flex; flex-direction: column; gap: 6px; margin-top: 22px; padding-top: 18px; border-top: 1px solid rgba(var(--vega-rgb), 0.14); }
.panel.about .credit img { display: block; align-self: flex-start; height: 11px; width: auto; margin-bottom: 8px; opacity: 0.85; }
.panel.about .credit p { font-size: 12.5px; color: var(--grey); }
.panel .rule { border: 0; border-top: 1px solid rgba(var(--vega-rgb), 0.14); margin: 0 0 18px; }

/* Touch screens: no keyboard hints on the buttons (they are meaningless there and the phone's top bar has no room for them) */
@media (hover: none), (max-width: 699px) {
  .topbar .ui-btn .key { display: none; }
  .topbar .ui-btn { padding: 8px 11px; }
  .topbar .actions { gap: 6px; }
}
@media (max-width: 699px) {
  .topbar { padding: calc(14px + env(safe-area-inset-top, 0px)) max(16px, env(safe-area-inset-right, 0px)) 14px max(16px, env(safe-area-inset-left, 0px)); gap: 10px; }
  .topbar .wordmark { font-size: 18px; }
  .topbar .wordmark-btn { flex: none; }
  /* Searching, the home glides up to the top of the view (9vh under the safe area; app.mjs rise sets --rise), so the
     keyboard leaves the rows room (live mobile sized its rows to the visible viewport too; app.mjs sets
     --search-room from it). A transform only, eased in and out, so the phone composites it: animating `top` juddered
     (owner's iPhone, 2026-10-28). `translate` takes --vv-top, the browser's pan of the view (iPhone Safari, as its
     keyboard moves; app.mjs holdHome), untransitioned, so the pan never shows in the home */
  .home {
    gap: 26px; will-change: transform; translate: 0 var(--vv-top, 0px);
    transition: opacity 600ms ease, transform 620ms cubic-bezier(.65, 0, .35, 1);
  }
  body.searching .home { transform: translateY(calc(-58% - var(--rise, 0px))); }
  /* During the glide the field is lifted, unseen, to the top of the safe area (app.mjs rise sets --lift), so iPhone
     Safari, which takes the focused field's rect as it focuses, has nothing to pan the view for; its ghost glides in its
     place with its hairline (the focused colour: it is focused) and its text. Both untransitioned: the ghost's place
     is always the field's own, so the swap back shows nothing. The caret is iPhone UI drawn where the field is: hidden */
  body.search-gliding .search-input { transform: translateY(calc(-1 * var(--lift, 0px))); opacity: 0; caret-color: transparent; transition: none; }
  body.search-gliding .search-ghost { display: block; }
  /* The rows wait for the glide: shown as it began, they were sized to a field still on its way up, lost their room
     to the keyboard sliding in, and came back once it arrived (owner's iPhone, 2026-10-28). They fade in once the
     field has arrived, sized to where it is (app.mjs endGlide), growing out of it as anywhere (above), and the rows
     that open it, held at their start meanwhile, come down after it */
  body.search-gliding .search-drop { opacity: 0; scale: 0.95; translate: 0 -6px; transition: none; }
  body.search-gliding .search-drop .settling { animation-play-state: paused; }
  /* A sheet from the bottom. Document content, not a fixed layer, and its bottom sits --below-bar px under the
     view (app.mjs resize), behind a translucent bottom bar where there is one; what's visible above the bar is
     unchanged, since that band is padding. Its own scroll never chains to the page. */
  .panel {
    position: absolute;
    top: auto; left: 0; right: 0; width: auto; max-height: 62vh;
    bottom: calc(-1 * var(--below-bar, 0px));
    border-left: 0; border-top: 1px solid rgba(var(--vega-rgb), 0.16);
    border-radius: 10px 10px 0 0; padding: 22px 20px calc(24px + env(safe-area-inset-bottom) + var(--below-bar, 0px));
    transform: translateY(18px);
    overscroll-behavior: contain;
  }
  .panel h2 { font-size: 26px; }
}

@media (prefers-reduced-motion: reduce) {
  .home, .topbar, body.open .topbar, .panel, .panel.is-open, .search-drop, .search-drop[hidden], .home .home-links { transition-duration: 0s; transition-delay: 0s; }
  .search-drop .settling { animation: none; }
  /* :root, to outweigh the rules they turn off (the pulse on the pressed state, the switch's later rules) */
  :root .panel .act.watchlist .glyph > span, :root .panel .act.share .glyph, :root .panel .act.share.done .glyph, :root .panel.settings .switch .track::after { transition-duration: 0s; }
  :root .panel .act.watchlist[aria-pressed="true"] .glyph, :root .panel .act.share.done .glyph, :root .panel.settings .switch .track::after, :root .panel.settings .switch[aria-checked="true"] .track::after { animation: none; }
}

/* ── Added 2026-10-26 with the parity port (docs/history/next-parity.md) ── */
.home .home-links { display: flex; gap: 18px; }
.topbar .exit-btn {
  width: 32px; height: 32px; padding: 0; margin-left: 6px; border: 1px solid transparent; border-radius: 3px;
  background: none; color: var(--grey); font: 300 20px/1 var(--body); cursor: pointer;
  transition: color 160ms ease, border-color 160ms ease;
}
.topbar .exit-btn:hover { color: var(--text); border-color: rgba(var(--vega-rgb), 0.28); }
.ui-btn[hidden] { display: none; }

/* Detail panel additions: the art strip, where to watch, the action row, the phone handle. */
.panel.detail { padding: 0; display: flex; flex-direction: column; }
.panel.detail .body { padding: 22px 28px 32px; }
.panel .art { position: relative; height: 150px; flex: none; background: var(--void); overflow: hidden; }
.panel .art img { display: block; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 400ms ease; }
.panel .art.loaded img { opacity: 0.92; }
/* The shade into the sheet is a mask on the image itself (over the void behind it), not an overlay: an overlay
   could come in after the fading image on an iPhone (owner, 2026-10-27), and a mask can't part from it */
.panel .art img {
  -webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,0.75), #000 35%, rgba(0,0,0,0.04));
  mask-image: linear-gradient(to bottom, rgba(0,0,0,0.75), #000 35%, rgba(0,0,0,0.04));
}
.panel .art[hidden] { display: none; }
.panel.detail h2 { margin-top: 4px; }
.panel.detail .close { z-index: 2; background: rgba(5, 5, 7, 0.6); }
/* The old details fading out over the new as they come in (panel.mjs swap; motion tip 16): what was seen, in place */
.panel .swap-ghost { position: absolute; top: 0; left: 0; right: 0; overflow: hidden; pointer-events: none; z-index: 1; background: none; }
.panel .handle { display: none; }
.panel .providers { margin: 20px 0 0; }
.panel .providers .label { margin: 0 0 8px; font: 400 11px/1 var(--body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--grey); }
.panel .providers-link { display: flex; gap: 6px; text-decoration: none; }
.panel .providers-link img { width: 22px; height: 22px; border-radius: 4px; display: block; }
.panel .providers-link:focus-visible { outline: 1px solid var(--vega); outline-offset: 3px; }
.panel .providers .credit { margin: 6px 0 0; font: 300 9px/1.3 var(--body); color: var(--grey); }
/* Your copy (library.mjs): a link of its own above where to watch, the one way to play it */
.panel .play {
  display: inline-flex; align-items: center; gap: 8px; margin: 20px 0 0; padding: 9px 14px; border-radius: 999px;
  border: 1px solid rgba(var(--vega-rgb), 0.45); color: var(--text); text-decoration: none;
  font: 400 13px/1 var(--body); letter-spacing: 0.02em; transition: border-color 160ms ease, background 160ms ease;
}
.panel .play .glyph { font-size: 10px; color: var(--vega); }
.panel .play:hover, .panel .play:focus-visible { border-color: var(--vega); background: rgba(var(--vega-rgb), 0.1); outline: none; }
.panel .play[hidden] { display: none; }
.panel .actions { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; margin: 22px 0 0; padding-top: 16px; border-top: 1px solid rgba(var(--vega-rgb), 0.14); }
.panel .act {
  appearance: none; background: none; border: 0; padding: 4px 0; cursor: pointer; text-decoration: none;
  font: 400 12.5px/1 var(--body); letter-spacing: 0.02em; color: var(--grey); transition: color 160ms ease;
  display: inline-flex; align-items: center; gap: 5px;
}
.panel .act .glyph { font-size: 11px; }
.panel .act:hover, .panel .act:focus-visible { color: var(--text); }
.panel .act[aria-pressed="true"] { color: var(--vega); }
.panel .act[hidden] { display: none; }
.panel .act[disabled] { opacity: 0.35; cursor: default; }
.panel .act.share { margin-left: auto; position: relative; }
/* Watchlist's "+" and "✓" share one place and crossfade, with a slight blur (docs/design/motion.md, tip 18) */
.panel .act.watchlist .glyph { display: inline-grid; place-items: center; }
.panel .act.watchlist .glyph > span { grid-area: 1 / 1; transition: opacity var(--pane) var(--ease-out), filter var(--pane) var(--ease-out), scale var(--pane) var(--ease-out); }
.panel .act.watchlist .g-on, .panel .act.watchlist[aria-pressed="true"] .g-off { opacity: 0; filter: blur(2px); scale: 0.6; }
.panel .act.watchlist[aria-pressed="true"] .g-on { opacity: 1; filter: none; scale: 1; }
/* Share: a link copied, a "✓" comes in beside the word (outside its box, so nothing moves) while the star says so */
.panel .act.share .glyph { position: absolute; right: calc(100% + 5px); opacity: 0; filter: blur(2px); scale: 0.6;
  transition: opacity var(--menu-out) var(--ease-in), filter var(--menu-out) var(--ease-in), scale var(--menu-out) var(--ease-in); }
.panel .act.share.done { color: var(--vega); }
.panel .act.share.done .glyph { opacity: 1; filter: none; scale: 1; transition-duration: var(--pane); transition-timing-function: var(--ease-out); }
/* The pulse that confirms (tip 8), love's own (idea-heart, below): the watchlist kept, the link copied */
.panel .act.watchlist[aria-pressed="true"] .glyph, .panel .act.share.done .glyph { animation: idea-heart 460ms cubic-bezier(.22, 1, .36, 1); }
.panel.swiping { transition: none; }

/* Games panel: a row per game (darkstar.css styles Dark Star's) */
.panel.games h2 { margin-bottom: 10px; }

/* Settings panel: a switch per setting */
.panel.settings h2 { margin-bottom: 18px; }
.panel.settings .switch {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; padding: 10px 0;
  background: none; border: 0; border-bottom: 1px solid rgba(var(--vega-rgb), 0.14); cursor: pointer;
  font: 300 15px/1.3 var(--body); color: var(--text); text-align: left;
}
.panel.settings .switch .track { position: relative; flex: none; width: 38px; height: 22px; border-radius: 11px; border: 1px solid rgba(var(--vega-rgb), 0.35); transition: background 160ms ease, border-color 160ms ease; }
/* The knob slides across (`translate`, so its stretch is about its own centre) and stretches on the way (tips 5, 9);
   two names for one stretch, so each change of side starts it afresh */
.panel.settings .switch .track::after { content: ''; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: var(--grey); transition: translate var(--move) var(--ease-move), background var(--move) var(--ease-move); animation: knob-off var(--move) var(--ease-move); }
.panel.settings .switch[aria-checked="true"] .track { background: rgba(var(--vega-rgb), 0.22); border-color: var(--vega); }
.panel.settings .switch[aria-checked="true"] .track::after { translate: 16px 0; background: var(--vega); animation-name: knob-on; }
@keyframes knob-on { 50% { scale: var(--stretch) 1; } }
@keyframes knob-off { 50% { scale: var(--stretch) 1; } }
.panel.settings .switch:focus-visible { outline: 1px solid var(--vega); outline-offset: 3px; }
.panel.settings .note { margin: 10px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--grey); }

/* Lists panel */
.panel.lists h3 { margin: 22px 0 8px; font: 400 11px/1 var(--body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--grey); }
.panel.lists .item { display: flex; align-items: center; justify-content: space-between; gap: 10px; border-bottom: 1px solid rgba(var(--vega-rgb), 0.1); }
.panel.lists .item-title {
  flex: 1; min-width: 0; text-align: left; background: none; border: 0; padding: 9px 0; cursor: pointer;
  font: 300 14px/1.3 var(--body); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.panel.lists .item-title:hover, .panel.lists .item-title:focus-visible { color: var(--vega); }
.panel.lists .item-title[disabled] { color: var(--grey); cursor: default; }
.panel.lists .item-remove { background: none; border: 0; padding: 6px 2px 6px 10px; cursor: pointer; font: 300 16px/1 var(--body); color: var(--grey); }
.panel.lists .item-remove:hover, .panel.lists .item-remove:focus-visible { color: var(--text); }
.panel.lists .empty { margin: 0; padding: 8px 0; font-size: 13.5px; color: var(--grey); }
.panel.lists .note { margin: 26px 0 0; font-size: 12px; color: var(--grey); letter-spacing: 0.02em; }
/* Your library's panel (libpanel.mjs): connect Plex or Jellyfin, then open the canvas, read again or disconnect */
.panel.library .lib-lead, .panel.library .lib-progress { margin: 0 0 16px; font: 300 14.5px/1.55 var(--body); color: rgba(242, 242, 240, 0.86); }
.panel.library .lib-choices { display: flex; gap: 10px; }
.panel.library .lib-choice {
  appearance: none; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; flex: 1; min-width: 0; padding: 14px 16px; cursor: pointer; text-align: left;
  border-radius: 10px; border: 1px solid rgba(var(--vega-rgb), 0.22); background: rgba(var(--vega-rgb), 0.03); color: var(--text);
  transition: border-color 160ms ease, background 160ms ease;
}
.panel.library .lib-choice:hover, .panel.library .lib-choice:focus-visible { border-color: var(--vega); background: rgba(var(--vega-rgb), 0.08); outline: none; }
.panel.library .lib-choice-name { font: 500 19px/1 var(--display); letter-spacing: -0.02em; }
.panel.library .lib-choice-line { font: 300 12.5px/1.3 var(--body); color: var(--grey); }
.panel.library .lib-error { margin: 12px 0 0; font: 300 13px/1.45 var(--body); color: #f2b8b5; }
.panel.library .lib-note { margin: 14px 0 0; font-size: 12px; line-height: 1.5; color: var(--grey); letter-spacing: 0.02em; }
.panel.library .lib-links { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; margin-top: 16px; }
.panel.library .lib-btn { appearance: none; background: none; border: 0; padding: 4px 0; cursor: pointer; font: 400 11px/1 var(--body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text); transition: color 160ms ease; }
.panel.library .lib-btn:hover, .panel.library .lib-btn:focus-visible { color: var(--vega); outline: none; }
.panel.library .lib-go {
  appearance: none; display: inline-block; padding: 10px 18px; border-radius: 999px; border: 1px solid rgba(var(--vega-rgb), 0.5); background: none;
  font: 400 14px/1 var(--body); letter-spacing: 0.02em; color: var(--text); text-decoration: none; cursor: pointer; transition: border-color 160ms ease, background 160ms ease;
}
.panel.library .lib-go:hover, .panel.library .lib-go:focus-visible { border-color: var(--vega); background: rgba(var(--vega-rgb), 0.1); outline: none; }
.panel.library .lib-code { margin: 4px 0 8px; font: 500 34px/1 var(--display); letter-spacing: 0.18em; color: var(--text); }
.panel.library .lib-form { display: flex; flex-direction: column; gap: 8px; }
.panel.library .lib-form h3 { margin: 0 0 4px; }
.panel.library .lib-input {
  width: 100%; box-sizing: border-box; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(var(--vega-rgb), 0.25); background: rgba(var(--vega-rgb), 0.04);
  font: 300 16px/1.2 var(--body); color: var(--text); /* 16px: iPhone Safari zooms into a smaller field */
}
.panel.library .lib-input:focus { outline: none; border-color: var(--vega); }

/* Your library's page (libview.mjs): the home steps aside, as for Dark Star, and the page scrolls over the sky. Plex's
 * and Jellyfin's poster grid in the brand: the void, hairlines, DM Sans, a display face for the shelves */
body.lib-active .chrome .home { opacity: 0 !important; pointer-events: none; }
body.lib-active .chrome .home * { pointer-events: none !important; }
.libview[hidden] { display: none; }
.libview {
  position: absolute; inset: 0; z-index: 2; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  background: rgba(5, 5, 7, 0.94); color: var(--text); transform-origin: 50% 40%;
  padding: 0 max(28px, env(safe-area-inset-right, 0px)) calc(64px + env(safe-area-inset-bottom, 0px)) max(28px, env(safe-area-inset-left, 0px));
}
.libview.away { pointer-events: none; }
.libview .lib-top {
  display: flex; align-items: center; justify-content: space-between; gap: 20px; max-width: 1280px; margin: 0 auto;
  padding: calc(18px + env(safe-area-inset-top, 0px)) 0 0;
}
.libview .wordmark-btn { background: none; border: 0; padding: 0; margin: 0; color: inherit; cursor: pointer; }
.libview .wordmark-btn .wordmark { font-size: 20px; opacity: 0.92; }
.libview .wordmark-btn:focus-visible { outline: 1px solid var(--vega); outline-offset: 4px; }
.libview .lib-tabs { position: relative; display: flex; gap: 22px; }
.libview .lib-tab {
  position: relative; background: none; border: 0; padding: 4px 0 7px; cursor: pointer;
  font: 300 15px/1 var(--body); letter-spacing: 0.01em; color: var(--grey); transition: color 0.3s;
}
/* One line under the chosen tab, sliding from one to the other and stretching on the way (libview.mjs → lined; tip 20) */
.libview .lib-tab-line { position: absolute; left: 0; bottom: 0; width: 0; height: 1px; background: var(--vega); pointer-events: none; }
.libview .lib-tabs.slide .lib-tab-line { transition: translate var(--move) var(--ease-move), width var(--move) var(--ease-move); }
.libview .lib-tab[aria-selected="true"] { color: var(--text); }
.libview .lib-tab:focus-visible { outline: 1px solid var(--vega); outline-offset: 4px; }
.libview .lib-source { max-width: 1280px; margin: 14px auto 0; font: 300 13px/1.5 var(--body); color: var(--grey); }
.libview .lib-manage { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; color: var(--grey); text-decoration: underline; text-decoration-color: rgba(var(--vega-rgb), 0.25); text-underline-offset: 3px; }
.libview .lib-manage:hover, .libview .lib-manage:focus-visible { color: var(--text); outline: none; }
/* The filters: under the source line, then at the top as the page scrolls */
.libview [hidden] { display: none !important; }
.libview .lib-bar {
  position: sticky; top: 0; z-index: 2; margin: 14px calc(-1 * max(28px, env(safe-area-inset-right, 0px))) 0 calc(-1 * max(28px, env(safe-area-inset-left, 0px)));
  padding: 12px max(28px, env(safe-area-inset-right, 0px)) 12px max(28px, env(safe-area-inset-left, 0px));
  background: #060608; border-bottom: 1px solid rgba(var(--vega-rgb), 0.08); /* edge to edge, solid: nothing scrolls through it */
}
.libview .lib-bar-in { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; max-width: 1280px; margin: 0 auto; }
.libview .lib-find {
  flex: 0 1 260px; min-width: 0; height: 34px; box-sizing: border-box; padding: 0 14px 0 34px; border-radius: 17px;
  border: 1px solid rgba(var(--vega-rgb), 0.16); color: var(--text); font: 300 14px/1 var(--body); outline: none; -webkit-appearance: none; appearance: none;
  background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none' stroke='%236a6a70' stroke-width='1.4'%3E%3Ccircle cx='6' cy='6' r='4.6'/%3E%3Cpath d='M9.4 9.4 13 13'/%3E%3C/svg%3E") no-repeat 13px 50%;
  transition: border-color 0.25s;
}
.libview .lib-find::placeholder { color: var(--grey); opacity: 0.8; }
.libview .lib-find:focus { border-color: rgba(var(--vega-rgb), 0.5); }
.libview .lib-find::-webkit-search-cancel-button { filter: invert(0.6); }
.libview .lib-picks { display: flex; align-items: center; gap: 8px; min-width: 0; }
.libview .lib-segs, .libview .lib-pick, .libview .lib-chip {
  display: inline-flex; align-items: center; flex: none; height: 34px; box-sizing: border-box; border-radius: 17px;
  border: 1px solid rgba(var(--vega-rgb), 0.16); background: none; color: var(--grey); font: 300 13px/1 var(--body); transition: border-color 0.25s, color 0.25s, background-color 0.25s;
}
.libview .lib-segs { position: relative; padding: 2px; gap: 2px; }
.libview .lib-seg { position: relative; z-index: 1; height: 28px; padding: 0 12px; border: 0; border-radius: 14px; background: none; color: inherit; font: inherit; cursor: pointer; transition: color 0.25s, transform 0.12s ease-out; }
.libview .lib-seg[aria-pressed="true"] { color: var(--text); }
/* The chosen one's pill, sliding to it (libview.mjs → pilled); until it's placed, the chosen one is lit itself */
.libview .lib-seg-pill { position: absolute; top: 2px; bottom: 2px; left: 0; width: 0; border-radius: 14px; background: rgba(var(--vega-rgb), 0.13); pointer-events: none; }
.libview .lib-segs:not(.placed) .lib-seg-pill { display: none; }
.libview .lib-segs:not(.placed) .lib-seg[aria-pressed="true"] { background: rgba(var(--vega-rgb), 0.13); }
.libview .lib-segs.slide .lib-seg-pill { transition: translate var(--move) var(--ease-move), width var(--move) var(--ease-move); }
.libview .lib-genres.closing { pointer-events: none; }
.libview .lib-pick { gap: 6px; padding: 0 10px 0 14px; cursor: pointer; }
.libview .lib-pick > span, .libview .lib-sort label > span { font-size: 12px; }
/* Sort: the menu, then the arrow that reverses it (pointing down the way it usually goes, up reversed) */
.libview .lib-sort { padding-right: 3px; }
.libview .lib-sort label { display: inline-flex; align-items: center; gap: 6px; height: 100%; cursor: pointer; }
.libview .lib-dir {
  flex: none; width: 28px; height: 28px; margin-left: 4px; padding: 0; border: 0; border-radius: 14px; cursor: pointer; color: inherit;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='12' fill='none' stroke='%23a8a8b0' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 1v10M1.5 7.5L5 11l3.5-3.5'/%3E%3C/svg%3E") no-repeat 50% 50%;
}
.libview .lib-dir[data-rev="1"] { transform: rotate(180deg); }
.libview .lib-dir:focus-visible { outline: 1px solid var(--vega); outline-offset: 0; }
@media (hover: hover) { .libview .lib-dir:hover { background-color: rgba(var(--vega-rgb), 0.1); } }
.libview .lib-genre-btn { padding-right: 12px; }
.libview .lib-pick-v {
  max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-right: 16px; font-weight: 300; color: var(--text);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' fill='none' stroke='%236a6a70' stroke-width='1.3'%3E%3Cpath d='M1 1l3.5 3.5L8 1'/%3E%3C/svg%3E") no-repeat 100% 55%;
}
.libview .lib-genre-btn.on { border-color: rgba(var(--vega-rgb), 0.55); background: rgba(var(--vega-rgb), 0.1); }
.libview .lib-genre-btn[aria-expanded="true"] { border-color: rgba(var(--vega-rgb), 0.55); }
.libview .lib-genre-btn:focus-visible { outline: 1px solid var(--vega); outline-offset: 2px; }
/* The genres' menu: tick any number; match any or all of them */
.libview .lib-genres {
  position: absolute; top: calc(100% + 8px); z-index: 3; width: 300px; max-height: min(60vh, 440px); overflow-y: auto; overscroll-behavior: contain;
  padding: 12px; box-sizing: border-box; border-radius: 10px; border: 1px solid rgba(var(--vega-rgb), 0.16); background: #0b0b0f;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
}
.libview .lib-genres-top { display: flex; align-items: center; gap: 10px; padding: 0 2px 10px; margin-bottom: 6px; border-bottom: 1px solid rgba(var(--vega-rgb), 0.08); font: 300 12px/1 var(--body); color: var(--grey); }
.libview .lib-genres-clear { margin-left: auto; background: none; border: 0; padding: 4px 2px; cursor: pointer; font: 300 12px/1 var(--body); color: var(--grey); text-decoration: underline; text-decoration-color: rgba(var(--vega-rgb), 0.25); text-underline-offset: 3px; }
.libview .lib-genres-clear:hover, .libview .lib-genres-clear:focus-visible { color: var(--text); outline: none; }
.libview .lib-genre-list { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 8px; }
.libview .lib-genre {
  display: flex; align-items: center; gap: 9px; width: 100%; min-width: 0; padding: 7px 6px; border: 0; border-radius: 6px; background: none; cursor: pointer;
  font: 300 13px/1.2 var(--body); color: var(--text); text-align: left;
}
@media (hover: hover) { .libview .lib-genre:hover { background: rgba(var(--vega-rgb), 0.06); } }
.libview .lib-genre:focus-visible { outline: 1px solid var(--vega); outline-offset: 0; }
.libview .lib-genre.none { color: var(--grey); } /* ticking it would leave nothing: a press leaves it out */
.libview .lib-genre.none .lib-genre-n, .libview .lib-genre.none small { opacity: 0.45; }
.libview .lib-genre-n { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.libview .lib-genre small { flex: none; font: 300 11px/1 var(--body); color: var(--grey); font-variant-numeric: tabular-nums; }
/* Its box: empty; ticked (filled, a tick); left out (a cross in muted red, the name struck through and dimmed) */
.libview .lib-box { flex: none; position: relative; width: 14px; height: 14px; box-sizing: border-box; border-radius: 3px; border: 1px solid rgba(var(--vega-rgb), 0.34); transition: background-color 0.15s, border-color 0.15s; }
.libview .lib-genre[data-state="in"] .lib-box { border-color: var(--vega); background: var(--vega); }
.libview .lib-genre[data-state="in"] .lib-box::after { content: ''; position: absolute; left: 3px; top: 3.5px; width: 7px; height: 4px; border: 1.5px solid #060608; border-top: 0; border-right: 0; transform: rotate(-45deg); }
.libview .lib-genre[data-state="out"] .lib-box { border-color: rgba(222, 120, 120, 0.75); background: rgba(222, 120, 120, 0.14); }
.libview .lib-genre[data-state="out"] .lib-box::before, .libview .lib-genre[data-state="out"] .lib-box::after {
  content: ''; position: absolute; left: 50%; top: 50%; width: 8px; height: 1.5px; margin: -0.75px 0 0 -4px; border-radius: 1px; background: rgb(232, 136, 136);
}
.libview .lib-genre[data-state="out"] .lib-box::before { transform: rotate(45deg); }
.libview .lib-genre[data-state="out"] .lib-box::after { transform: rotate(-45deg); }
.libview .lib-genre[data-state="out"] .lib-genre-n { color: var(--grey); text-decoration: line-through; text-decoration-color: rgba(222, 120, 120, 0.7); }
.libview .lib-genre[data-state="out"] small { color: rgb(222, 140, 140); }
.libview .lib-genres-hint { margin: 8px 2px 0; padding-top: 9px; border-top: 1px solid rgba(var(--vega-rgb), 0.08); font: 300 11.5px/1.3 var(--body); color: var(--grey); }
.libview .lib-select {
  -webkit-appearance: none; appearance: none; border: 0; outline: none; cursor: pointer; padding: 0 18px 0 0; max-width: 190px; text-overflow: ellipsis;
  field-sizing: content; /* as wide as what's chosen (where supported; elsewhere as wide as the longest choice) */
  color: var(--text); font: 300 13px/1 var(--body);
  background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' fill='none' stroke='%236a6a70' stroke-width='1.3'%3E%3Cpath d='M1 1l3.5 3.5L8 1'/%3E%3C/svg%3E") no-repeat 100% 55%;
}
.libview .lib-select option { background: #0d0d11; color: var(--text); }
.libview .lib-chip { padding: 0 14px; cursor: pointer; }
.libview .lib-chip[aria-pressed="true"] { border-color: rgba(var(--vega-rgb), 0.55); background: rgba(var(--vega-rgb), 0.1); color: var(--text); }
@media (hover: hover) { .libview .lib-seg:hover, .libview .lib-chip:hover, .libview .lib-pick:hover { color: var(--text); } .libview .lib-pick:hover, .libview .lib-chip:hover { border-color: rgba(var(--vega-rgb), 0.34); } }
.libview .lib-seg:focus-visible, .libview .lib-chip:focus-visible { outline: 1px solid var(--vega); outline-offset: 2px; }
.libview .lib-pick:focus-within { border-color: rgba(var(--vega-rgb), 0.55); }
.libview .lib-bar[data-tab="recommended"] .lib-find, .libview .lib-bar[data-tab="recommended"] .lib-sort, .libview .lib-bar[data-tab="recommended"] .lib-unwatched,
.libview .lib-bar[data-tab="collections"] .lib-kinds, .libview .lib-bar[data-tab="collections"] .lib-sort, .libview .lib-bar[data-tab="collections"] .lib-unwatched { display: none; }
.libview .lib-more { height: 1px; }
.libview .lib-empty { padding: 72px 0; text-align: center; font: 300 14px/1.5 var(--body); color: var(--grey); }
.libview .lib-empty p { margin: 0 0 16px; }
.libview .lib-empty p:has(+ .lib-empty-on) { margin-bottom: 4px; }
.libview .lib-empty .lib-empty-on { font-size: 12.5px; opacity: 0.8; }
.libview .lib-empty .lib-empty-on[hidden] { display: none; }
.libview .lib-pane { max-width: 1280px; margin: 0 auto; }
.libview .lib-pane > .lib-shelf:first-child { margin-top: 26px; }
.libview .lib-pane[hidden] { display: none; }
.libview .lib-shelf { margin-top: 40px; }
.libview .lib-shelf h2 { display: flex; align-items: baseline; gap: 10px; margin: 0; font: 500 17px/1.2 var(--display); letter-spacing: -0.02em; color: var(--text); }
.libview .lib-shelf h2 small { font: 300 13px/1 var(--body); letter-spacing: 0; color: var(--grey); }
.libview .lib-lede { margin: 4px 0 0; max-width: 62ch; font: 300 13px/1.45 var(--body); color: var(--grey); }
.libview .lib-lede[hidden] { display: none; }
.libview .lib-grid { display: grid; align-items: start; gap: 22px 14px; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); margin-top: 16px; } /* start: a button centres what's in it, so a short card beside a long one sat lower */
.libview .lib-grid > * { min-width: 0; }
.libview .card { display: block; width: 100%; padding: 0; text-align: left; background: none; border: 0; color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.libview .poster { display: block; aspect-ratio: 2 / 3; overflow: hidden; border-radius: 4px; background: #0d0d11; border: 1px solid rgba(var(--vega-rgb), 0.14); transition: transform 0.45s cubic-bezier(.2,.7,.2,1), border-color 0.3s; }
.libview .poster { position: relative; }
.libview .poster img { display: block; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 0.4s ease; }
.libview .poster.in img[src] { opacity: 1; }
.libview .poster img:not([src]) { visibility: hidden; }
/* Loading: a poster's frame with a slow sweep of light across it, the same moment on every poster */
.libview .poster:not(.in)::before, .libview .card.unnamed .poster::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, transparent 30%, rgba(var(--vega-rgb), 0.07) 50%, transparent 70%) 0 0 / 300% 100% no-repeat;
  animation: libShimmer 1.6s linear infinite;
}
@keyframes libShimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }
@media (hover: hover) { .libview .card:hover .poster, .libview .part:hover .poster { transform: translateY(-3px); border-color: rgba(var(--vega-rgb), 0.34); } }
/* Pressed: everything you can press gives a little (0.97, about its centre, so a poster's centre, the star's mark, stays put) */
@media (prefers-reduced-motion: no-preference) {
  .libview .lib-chip, .libview .lib-pick, .libview .lib-all, .libview .lib-back, .libview .lib-tab { transition-property: color, border-color, background-color, transform; transition-duration: 0.25s, 0.25s, 0.25s, 0.12s; }
  .libview .lib-dir { transition: transform var(--move) var(--ease-move), background-color 0.25s; }
  .libview .lib-chip:active, .libview .lib-pick:active, .libview .lib-seg:active, .libview .lib-all:active, .libview .lib-back:active, .libview .lib-tab:active { transform: scale(var(--press)); }
  .libview .card:active .poster, .libview .part:active .poster, .libview .lib-more-cols:active .poster { transform: scale(var(--press)); transition-duration: 0.1s; }
}
@media (prefers-reduced-motion: no-preference) and (hover: hover) {
  .libview .card:hover:active .poster, .libview .part:hover:active .poster { transform: translateY(-3px) scale(var(--press)); }
}
/* Just landed on: still, whatever the pointer resting on it, until the pointer moves (libview.mjs → resume) */
.libview .card.landed:hover .poster, .libview .part.landed:hover .poster { transform: none; }
.libview .card:focus-visible, .libview .part:focus-visible { outline: none; }
.libview .card:focus-visible .poster, .libview .part:focus-visible .poster { border-color: var(--vega); box-shadow: 0 0 0 1px var(--vega); }
.libview .card .name { display: flex; align-items: baseline; gap: 6px; margin-top: 9px; font: 400 13.5px/1.25 var(--body); }
.libview .card .name .t { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.libview .card .name .year { flex: none; font: 300 12px/1 var(--body); color: var(--grey); }
.libview .card .meta { display: block; margin-top: 2px; font: 300 12px/1.35 var(--body); color: var(--grey); font-variant-numeric: tabular-nums; }
/* two lines at most, so a long "Pairs with…" never makes its row uneven (the whole of it is the line's title) */
.libview .card .meta { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
.libview .ghost .poster, .libview .ghost.part .poster { visibility: hidden; }
/* Any poster until its details say it can be shown (never an adult title): a placeholder, with bars where its name and
   line go, and nothing of the title itself */
.libview .card.unnamed { cursor: default; }
.libview .card.unnamed .poster img, .libview .card.unnamed .name > *, .libview .card.unnamed .meta { visibility: hidden; }
.libview .card.unnamed .name, .libview .card.unnamed .meta { position: relative; }
.libview .card.unnamed .name { min-height: 1.25em; }
.libview .card.unnamed .meta { min-height: 1.35em; }
.libview .card.unnamed .name::after, .libview .card.unnamed .meta::after {
  content: ''; position: absolute; left: 0; top: 50%; height: 9px; margin-top: -4.5px; border-radius: 2px; background: rgba(var(--vega-rgb), 0.08);
}
.libview .card.unnamed .name::after { width: 72%; }
.libview .card.unnamed .meta::after { width: 48%; height: 7px; margin-top: -3.5px; background: rgba(var(--vega-rgb), 0.05); }
/* Finish your collections: a row of collections that scrolls on its own, each its films in order, the missing dimmed */
.libview .lib-cols { display: flex; gap: 28px; overflow-x: auto; margin: 0 -28px; padding: 16px 28px 8px; scrollbar-width: thin; scrollbar-color: rgba(var(--vega-rgb), 0.14) transparent; }
.libview .lib-col { flex: 0 0 auto; width: min-content; }
/* The row's head: See all at its end */
.libview .lib-shelf-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.libview .lib-shelf-head h2 { flex: none; }
/* the collections' status: on the heading's row, one line, cut short rather than wrapped, so it never moves anything */
.libview .lib-checking { flex: 1 1 auto; min-width: 0; margin: 0; font: 300 12px/1.2 var(--body); color: var(--grey); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.libview .lib-checking[hidden] { display: block !important; visibility: hidden; }
.libview .lib-checking .short { display: none; }
.libview .lib-all { flex: none; background: none; border: 0; padding: 4px 0; cursor: pointer; font: 300 13px/1 var(--body); color: var(--grey); transition: color 0.25s; }
.libview .lib-all::after { content: ' ›'; }
.libview .lib-all:hover, .libview .lib-all:focus-visible { color: var(--text); outline: none; }
/* The row's last tile: how many more, a poster's height */
.libview .lib-more-cols { flex: none; align-self: flex-start; width: 96px; padding: 0; background: none; border: 0; cursor: pointer; text-align: left; color: var(--grey); -webkit-tap-highlight-color: transparent; }
.libview .lib-more-cols .poster { display: grid; place-items: center; border-style: dashed; border-color: rgba(var(--vega-rgb), 0.22); background: none; }
.libview .lib-more-cols .poster::after { content: '›'; font: 300 28px/1 var(--body); color: var(--grey); transition: color 0.25s, transform 0.25s; }
.libview .lib-more-cols .name { display: block; margin-top: 9px; font: 300 12.5px/1.3 var(--body); }
@media (hover: hover) { .libview .lib-more-cols:hover { color: var(--text); } .libview .lib-more-cols:hover .poster { border-color: rgba(var(--vega-rgb), 0.45); } .libview .lib-more-cols:hover .poster::after { color: var(--text); transform: translateX(2px); } }
.libview .lib-more-cols:focus-visible { outline: none; }
.libview .lib-more-cols:focus-visible .poster { border-color: var(--vega); }
/* Their own page: every collection, wrapping down the page */
.libview .lib-cols.lib-cols-all { flex-wrap: wrap; overflow: visible; margin: 0; padding: 16px 0 8px; gap: 36px 40px; }
/* each as wide as its row of posters (its name and line never widen it), wrapping only when that's wider than the page */
.libview .lib-cols-all .lib-col { width: fit-content; max-width: 100%; }
.libview .lib-cols-all .lib-col .parts { flex-wrap: wrap; }
.libview .lib-cols-all .lib-col .name, .libview .lib-cols-all .lib-col .meta { width: 0; min-width: 100%; }
.libview .lib-back { display: block; margin-top: 26px; background: none; border: 0; padding: 4px 0; cursor: pointer; font: 300 13px/1 var(--body); color: var(--grey); transition: color 0.25s; }
.libview .lib-back:hover, .libview .lib-back:focus-visible { color: var(--text); outline: none; }
.libview .lib-back + .lib-shelf { margin-top: 18px; }
.libview .lib-col .parts { display: flex; gap: 8px; }
.libview .part { width: 96px; padding: 0; background: none; border: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.libview .part.missing .poster { opacity: 0.38; border-color: rgba(var(--vega-rgb), 0.34); }
.libview .part.missing .poster img { filter: grayscale(0.6); }
@media (hover: hover) { .libview .part.missing:hover .poster { opacity: 0.7; } }
.libview .lib-col .name { display: block; margin-top: 9px; font: 400 13.5px/1.25 var(--body); }
.libview .lib-col .meta { display: block; margin-top: 2px; font: 300 12px/1.35 var(--body); color: var(--grey); }
/* The poster becoming a star, and the star becoming the poster: a copy over the page */
.lib-clone { position: fixed; z-index: 3; overflow: hidden; border-radius: 4px; pointer-events: none; transform-origin: 0 0; background: #0d0d11; }
.lib-clone img { display: block; width: 100%; height: 100%; object-fit: cover; }
.lib-clone.star { overflow: visible; background: #fff; border-radius: 50%; }
.lib-clone.star img { display: none; }
.lib-clone.star::after {
  content: ''; position: absolute; left: 50%; top: 50%; width: var(--ring); height: var(--ring); transform: translate(-50%, -50%);
  border: 1px solid rgba(var(--vega-rgb), 0.55); border-radius: 50%; animation: libBreath 1.4s ease-in-out infinite;
}
@keyframes libBreath { 50% { transform: translate(-50%, -50%) scale(1.12); opacity: 0.7; } }
@media (prefers-reduced-motion: reduce) { .lib-clone.star::after { animation: none; } }
@media (max-width: 699px) {
  .libview { padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px)); }
  .libview .lib-top { padding-top: calc(14px + env(safe-area-inset-top, 0px)); }
  .libview .wordmark-btn .wordmark { font-size: 18px; }
  .libview .lib-tabs { gap: 16px; }
  .libview .lib-tab { font-size: 14px; }
  .libview .lib-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px 10px; }
  .libview .card .name { font-size: 12.5px; }
  .libview .card .meta { font-size: 11px; }
  /* a name has two lines before it's cut, and its year goes to the start of the line under it */
  .libview .card .name { display: block; }
  .libview .card .name .t { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; white-space: normal; overflow-wrap: anywhere; }
  .libview .card .name .year { display: none; }
  .libview .card .meta::before { content: attr(data-y) ' · '; }
  .libview .card .meta:empty::before { content: attr(data-y); }
  .libview .card .meta:not([data-y])::before, .libview .card .meta[data-y=""]::before { content: none; }
  .libview .lib-shelf { margin-top: 32px; }
  .libview .lib-bar { margin-left: calc(-1 * max(16px, env(safe-area-inset-left, 0px))); margin-right: calc(-1 * max(16px, env(safe-area-inset-right, 0px)));
    padding: 10px max(16px, env(safe-area-inset-right, 0px)) 10px max(16px, env(safe-area-inset-left, 0px)); }
  .libview .lib-bar-in { gap: 10px; }
  .libview .lib-genres { left: 0 !important; right: 0; width: auto; }
  .libview .lib-find { flex: 1 1 100%; font-size: 16px; }
  /* the status's short form; the long one is still there for a screen reader */
  .libview .lib-checking .short { display: inline; }
  .libview .lib-checking .long { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } /* under 16px, iPhone's Safari zooms the page into a field (Codex) */
  .libview .lib-picks { width: calc(100% + 32px); margin: 0 -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
  .libview .lib-picks::-webkit-scrollbar { display: none; }
  /* faded at an edge with more of the row past it (libview.mjs → edges) */
  .libview .lib-picks.fade-r { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent); }
  .libview .lib-picks.fade-l { -webkit-mask-image: linear-gradient(to right, transparent, #000 36px); mask-image: linear-gradient(to right, transparent, #000 36px); }
  .libview .lib-picks.fade-l.fade-r { -webkit-mask-image: linear-gradient(to right, transparent, #000 36px, #000 calc(100% - 36px), transparent); mask-image: linear-gradient(to right, transparent, #000 36px, #000 calc(100% - 36px), transparent); }
  .libview .lib-cols { margin: 0 -16px; padding-left: 16px; padding-right: 16px; }
  .libview .lib-cols.lib-cols-all { margin: 0; padding-left: 0; padding-right: 0; gap: 28px 24px; }
  .libview .lib-more-cols { width: 84px; }
  .libview .part { width: 84px; }
}
@media (prefers-reduced-motion: reduce) {
  .libview .poster, .libview .poster img, .libview .lib-tab, .libview .lib-tab-line { transition: none; }
  .libview .poster::before { animation: none !important; background: rgba(var(--vega-rgb), 0.03) !important; }
}

@media (max-width: 699px) {
  .panel .handle { display: block; position: absolute; top: 0; left: 0; right: 0; height: 28px; z-index: 2; touch-action: none; }
  .panel .handle::after { content: ''; position: absolute; top: 8px; left: 50%; width: 38px; height: 4px; margin-left: -19px; border-radius: 2px; background: rgba(var(--vega-rgb), 0.62); box-shadow: 0 0 6px rgba(5, 5, 7, 0.5); } /* clear over bright art too (owner: too faint) */
  .panel.detail .body { padding: 18px 20px calc(24px + env(safe-area-inset-bottom)); }
  .panel .art { height: 118px; }
  .panel .face { width: 84px; }
  .panel .head { gap: 14px; }
  /* The sheet's three heights (panel.mjs attachSheet): each is a translateY, --sheet-y, of a sheet whose top
     is the page's top, so one transition carries opening, every height change and a drag's settle; the closed
     sheet waits just below its height (the peek, further: it rises into view). Its content sets its height,
     at least full's top (14 % of the small view, SHEET.fullVh) to the large view's bottom: at full the page
     scrolls through it (body.sheet-reading), and its bottom runs on behind a bottom bar at every height */
  .panel.detail {
    top: 0; bottom: auto; max-height: none; overflow: visible; overscroll-behavior: auto;
    min-height: 86vh; min-height: max(100lvh - 14svh, 86svh + var(--below-bar, 0px)); /* the band as measured too (app.mjs resize) */
    transform: translateY(calc(var(--sheet-y, 0px) + var(--sheet-enter, 18px)));
    transition: transform 320ms cubic-bezier(.22, 1, .36, 1), opacity 240ms ease, visibility 0s linear 240ms;
  }
  .panel.detail.is-open { transform: translateY(var(--sheet-y, 0px)); transition-delay: 0s; }
  .panel.detail.rising.is-open { transition: transform 640ms cubic-bezier(.22, 1, .36, 1), opacity 520ms ease, visibility 0s; }
  .panel.detail.swiping, .panel.detail.rising.swiping { transition: none; } /* a drag follows the finger (the transitions above would otherwise win) */
  .panel.detail[data-detent="peek"].is-open { opacity: 0.78; } /* a hint, not a panel (SHEET.peekOpacity) */
  /* The whole sheet is a handle: a finger resting on the synopsis starts no text selection (owner, 2026-10-27) */
  .panel.detail { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
  /* Below full a drag moves the sheet, not the text, and never the page (only the full sheet scrolls it) */
  .panel.detail:not([data-detent="full"]) { overflow-y: hidden; touch-action: none; }
  /* At full too, no browser scroll starts on the handle, the art or a control (attachSheet's `grab`): the drag
     slop decides there, so a wobbly tap on a button still clicks, and a drag from one goes all the way. On the
     text the browser's scroll stays possible, and the first touchmove decides (panel.mjs, 2026-10-27) */
  .panel.detail .art, .panel.detail .face, .panel.detail .close, .panel.detail .act, .panel.detail .play, .panel.detail .providers-link, .panel.detail .fact-link { touch-action: none; } /* the facts' links too: a person (B6), a collection (Codex) */
  .panel.detail[data-detent="peek"] .close { visibility: hidden; }
  /* At half the actions sit under the reason, in reach; the rest follows */
  .panel.detail .body { display: flex; flex-direction: column; }
  .panel.detail .body > * { order: 2; }
  .panel.detail .body > .head, .panel.detail .body > .tags, .panel.detail .body > .why { order: 0; }
  .panel.detail .body > .actions { order: 1; margin: 0 0 18px; padding: 0 0 14px; border-top: 0; border-bottom: 1px solid rgba(var(--vega-rgb), 0.14); }
}
.sab-probe { position: absolute; top: 0; left: 0; width: 0; height: env(safe-area-inset-bottom, 0px); visibility: hidden; pointer-events: none; }
.svh-probe { position: fixed; top: 0; left: 0; width: 0; height: 100vh; height: 100svh; visibility: hidden; pointer-events: none; }
/* The full sheet scrolls the page, so a phone browser retracts its bars as you read (panel.mjs attachSheet) */
body.sheet-reading { overflow: visible; }
@media (prefers-reduced-motion: reduce) {
  .panel.detail, .panel.detail.rising.is-open { transition-duration: 0s; transition-delay: 0s; }
}

/* ── The living touches (docs/history/next-ideas.md, round 2, kept by the owner): app.mjs sets body.idea-<name> for
   each. Composited properties only (transform, opacity). ── */
/* 'drawn': the interface's hairlines are drawn with the same pen as the constellation's lines (fx 'lines'). The search
   field's focused hairline draws out from its centre; a panel's rules draw out from the left as it opens; a home link
   underlines itself as you reach it. Each is a transform on a 1 px pseudo-element, so the phone composites it. */
.idea-drawn .search::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; pointer-events: none;
  background: rgba(var(--vega-rgb), 0.7); transform: scaleX(0); transform-origin: 50% 50%;
  transition: transform 460ms cubic-bezier(.22, 1, .36, 1);
}
.idea-drawn .search:focus-within::before, .idea-drawn .search.busy::before { transform: scaleX(1); }
.idea-drawn .search-input, .idea-drawn .search-input:focus { border-bottom-color: rgba(var(--vega-rgb), 0.28); transition: none; } /* the rest hairline stays; the drawn one carries the focus */
.idea-drawn .panel .rule { transform: scaleX(0); transform-origin: 0 50%; transition: transform 520ms cubic-bezier(.22, 1, .36, 1) 140ms; }
.idea-drawn .panel.is-open .rule { transform: scaleX(1); }
.idea-drawn .panel .why { border-left-color: transparent; position: relative; }
.idea-drawn .panel .why::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 1px; background: rgba(var(--vega-rgb), 0.45); transform: scaleY(0); transform-origin: 50% 0; transition: transform 420ms cubic-bezier(.22, 1, .36, 1) 60ms; }
.idea-drawn .panel.is-open .why::before { transform: scaleY(1); }
.idea-drawn .panel .actions { border-top-color: transparent; border-bottom-color: transparent; position: relative; }
.idea-drawn .panel .actions::before { content: ''; position: absolute; left: 0; right: 0; top: -1px; height: 1px; background: rgba(var(--vega-rgb), 0.14); transform: scaleX(0); transform-origin: 0 50%; transition: transform 520ms cubic-bezier(.22, 1, .36, 1) 220ms; }
@media (max-width: 699px) { .idea-drawn .panel.detail .actions::before { top: auto; bottom: -1px; } }
.idea-drawn .panel.is-open .actions::before { transform: scaleX(1); }
.idea-drawn .panel:not(.is-open) .rule, .idea-drawn .panel:not(.is-open) .why::before, .idea-drawn .panel:not(.is-open) .actions::before { transition: none; } /* closed, they reset at once, so the next open draws them again */
.idea-drawn .home-link { position: relative; }
.idea-drawn .home-link::after { content: ''; position: absolute; left: 6px; right: 6px; bottom: 0; height: 1px; background: rgba(var(--vega-rgb), 0.6); transform: scaleX(0); transform-origin: 0 50%; transition: transform 300ms cubic-bezier(.22, 1, .36, 1); }
.idea-drawn .home-link:hover::after, .idea-drawn .home-link:focus-visible::after { transform: scaleX(1); }
/* 'ripple': the heart answers the press (the star's ring is the canvas's, draw.mjs) */
.idea-ripple .panel .act.love[aria-pressed="true"] .glyph { animation: idea-heart 460ms cubic-bezier(.22, 1, .36, 1); display: inline-block; }
@keyframes idea-heart { 0% { transform: scale(1); } 35% { transform: scale(1.45); } 100% { transform: scale(1); } }
/* 'firstlight': the first frame is the dot alone; the letters come as the sky kindles round it (draw.mjs), then the
   search and the links. body.first-light is set before the first paint and lifted a frame after it, and body.lighting
   carries the slow, late fade-in only while it runs (app.mjs boot), so the links' own transitions (stepping aside for
   the search's list) are their usual ones after it */
.idea-firstlight.lighting .home .wordmark { transition: color 780ms ease 420ms; }
.idea-firstlight.first-light .home .wordmark { color: transparent; transition: none; }
.idea-firstlight.lighting .home .search, .idea-firstlight.lighting .home .home-links { transition: opacity 640ms ease 1000ms; }
.idea-firstlight.first-light .home .search, .idea-firstlight.first-light .home .home-links { opacity: 0; transition: none; }
@media (prefers-reduced-motion: reduce) {
  .idea-drawn .search::before, .idea-drawn .panel .rule, .idea-drawn .panel .why::before, .idea-drawn .panel .actions::before, .idea-drawn .home-link::after,
  .idea-firstlight .home .wordmark, .idea-firstlight .home .search, .idea-firstlight .home .home-links { transition: none; }
  .idea-ripple .panel .act.love[aria-pressed="true"] .glyph { animation: none; }
}
@media (prefers-reduced-motion: reduce) {
  .panel .face img { transition-duration: 0s; transform: none; }
}
