/* 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 comes and goes gently (owner, 2026-10-28: trending appeared harshly): in, a fade with a slight settle from
     above; out, a quicker fade, display waiting for it (allow-discrete), the closing list taking no taps */
  transition: opacity 280ms ease-out, translate 280ms cubic-bezier(.22, 1, .36, 1), display 160ms allow-discrete;
}
@starting-style { .search-drop { opacity: 0; translate: 0 -6px; } }
.search-drop[hidden] { display: none; opacity: 0; pointer-events: none; transition: opacity 160ms ease-in, display 160ms allow-discrete; }
/* 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 160ms ease; } /* 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 500ms ease 250ms;
}
.topbar .wordmark { font-size: 20px; opacity: 0.92; }
.topbar .actions { display: flex; gap: 8px; }
body.open .topbar { opacity: 1; }
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;
  transition: transform 240ms ease, opacity 240ms ease, visibility 0s linear 240ms;
}
.panel.is-open { transform: none; opacity: 1; visibility: visible; transition-delay: 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), with the list's own fade (above) */
  body.search-gliding .search-drop { opacity: 0; transition: none; }
  /* 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, .panel, .search-drop, .search-drop[hidden], .home .home-links { transition-duration: 0s; transition-delay: 0s; }
}

/* ── Added 2026-10-26 with the parity port (docs/design/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); }
.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); }
.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; }
.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; }
.panel.settings .switch .track::after { content: ''; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: var(--grey); transition: transform 160ms ease, background 160ms ease; }
.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 { transform: translateX(16px); background: var(--vega); }
.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; }

@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 .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/design/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; }
}
