/* ==========================================================================
   The Great Library — site layout
   Everything here is LAYOUT and RESPONSIVENESS. Colour, type, radius and
   elevation come from the design system tokens (/ds/_ds_bundle.css) and are
   referenced, never restated. The approved canvases were drawn at three frame
   widths — 390 (phone), 834 (tablet) and fluid (desktop) — so those are the
   breakpoints: < 640, 640–1023, ≥ 1024.
   ========================================================================== */

/* --- design-system corrections -------------------------------------------
   Host overrides that repair the canon bundle until the same rule ships in
   great-library-design-system. Delete each block when canon carries it. */

/* BUG-193 (member report 2026-09-22): a long modal — thirty shelves in the
   picker — grew past the viewport on BOTH ends of the centred fixed scrim, so
   the member could not reach the dismiss control at all and had to back out of
   the page. Let the scrim scroll as the outer escape hatch, cap the modal
   inside its padding as a flex column so the body alone scrolls, and keep
   margin:auto centring so even an oversized modal stays reachable. Becomes a
   harmless no-op once the canon bundle carries the same rules. */
.gl-modal__scrim { overflow-y: auto; }
.gl-modal {
  margin: auto;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 2 * var(--gl-space-5));
  max-height: calc(100dvh - 2 * var(--gl-space-5));
}
.gl-modal__body { min-height: 0; overflow-y: auto; overscroll-behavior: contain; }

/* The `hidden` attribute always hides. A class that sets `display` (or a state rule such as
   :focus-within) out-specifies the browser's own [hidden] rule, so an element the app has hidden
   could still show: the nav and account menus stayed open after being closed while focus was
   inside them, and after an in-app navigation (candidate f9e554e audit). One rule for every
   hidden toggle on the site. */
[hidden] { display: none !important; }
html { min-height: 100%; }
html[data-gl-theme="light"], html[data-gl-theme="light"] body { background: #FAF7FC; }
body { -webkit-text-size-adjust: 100%; }

/*
 * THE iOS SAFE AREA. Owner, 2026-09-17: "every single page displays too large on
 * iOS and has to be unzoomed", with screenshots showing the search field sliced
 * by the clock and a work's badge row sitting behind the battery.
 *
 * index.html sets `viewport-fit=cover`, which is what tells iOS to draw the page
 * UNDER the status bar and notch rather than letterboxing it. That is a fine
 * choice for a full-bleed page, but it is only half a contract: the layout then
 * owes itself the inset back. `env(safe-area-inset-bottom)` was honoured for the
 * toast and `safe-area-inset-top` appeared nowhere at all, so every page began
 * behind the clock.
 *
 * Padding the root rather than the topbar keeps it true on every page, including
 * ones with no topbar, and `env(..., 0px)` costs a browser without insets
 * nothing.
 */
#app { min-height: 100vh; }
.gl-root.site-root {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  padding-top: env(safe-area-inset-top, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  /* The viewport minus the inset, so a full-height page is still exactly the
     screen and does not gain the padding as overflow. */
  min-height: calc(100vh - env(safe-area-inset-top, 0px));
}
.site-main { flex: 1 0 auto; }
.site-main:focus { outline: none; }
/* Breadcrumbs (owner, 2026-09-25): drawn by the shell only when the site runs as an installed app
   (components/breadcrumbs.tsx), where there is no address bar and going back is difficult. In a
   browser tab the nav is not rendered at all, so nothing here touches the ordinary layout. */
.site-crumbs { max-width: 1180px; margin: 0 auto; padding: 10px 24px 0; box-sizing: border-box; font-size: 12.5px; color: var(--gl-muted); }
.site-crumbs__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; }
.site-crumbs__item { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.site-crumbs__sep { color: var(--gl-faint); }
.site-crumbs__link { color: var(--gl-muted); text-decoration: none; padding: 4px 0; }
.site-crumbs__link:hover, .site-crumbs__link:focus-visible { color: var(--gl-text); text-decoration: underline; }
.site-crumbs__here { color: var(--gl-text); font-weight: 600; overflow-wrap: anywhere; }
@media (max-width: 640px) { .site-crumbs { padding: 8px 16px 0; } }

/* Skip link: visible only when focused. */
.site-skip { position: absolute; left: 12px; top: -48px; z-index: 100; padding: 8px 14px; background: var(--gl-shelf); color: var(--gl-text); border: 1px solid var(--gl-rule-strong); border-radius: var(--gl-radius-sm); font-size: 13.5px; }
.site-skip:focus { top: 12px; }

.visually-hidden { position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* --- masthead ----------------------------------------------------------- */
.site-topbar { position: relative; z-index: 40; }
.site-topbar .gl-topbar { flex-wrap: nowrap; }
.site-topbar .gl-topbar__brand { white-space: nowrap; }
.site-topbar .gl-topbar__search { position: static; flex: 0 0 auto; width: auto; max-width: none; margin: 0; }
/* The same bar on every page (fixes-0925 item 68; owner, 2026-09-25: "the screens seem to slightly change
   sizes on desktop. clicking on the home page has the top bar slightly more spaced out than in settings,
   which is different than residents?"). The scrollbar's width is kept whether or not a page is long
   enough to scroll, so a short page is not 15px wider than a long one; Home, which has no search pill,
   holds the pill's place empty at the pill's own size (nav-0925's header rules govern everything else). */
@media (min-width: 640px) { html { scrollbar-gutter: stable; } }
.site-topbar__search-space { display: block; height: 36px; }
.site-topbar__search-space { width: 46px; }
@media (max-width: 639.98px) { .site-topbar__search-space { width: 40px; } }
@media (max-width: 419.98px) { .site-topbar__search-space { width: 36px; } }
.site-nav-inline { display: none; align-items: center; gap: 0; }
.site-nav-link { position: relative; display: flex; align-items: center; white-space: nowrap; padding: 10px 11px; font-size: 13.5px; color: var(--gl-muted); border-radius: var(--gl-radius-sm); }
.site-nav-link:hover { background: var(--gl-wash); color: var(--gl-text); }
.site-nav-link[aria-current="page"]::after { content: ""; position: absolute; left: 11px; right: 11px; bottom: 2px; height: 2px; border-radius: 2px; background: var(--gl-lamp); }
/* The owner's announcement strip (Settings › Site): one line under the menu, in the lamplight
   accent so it reads as the library speaking, not as an error. */
.site-announce { padding: 8px 20px; background: var(--gl-wash); border-bottom: 1px solid var(--gl-rule); color: var(--gl-text); font-size: 14px; text-align: center; }
.site-announce__link { color: var(--gl-amethyst); text-decoration: underline; text-underline-offset: 2px; }
/* The theme control in the header: a lantern glyph alone, drawn in the current text colour like
   the chevron on the selects. The wording stays written out in the drawer, where there is room. */
.gl-btn.site-lampbtn { padding: 6px; gap: 0; line-height: 0; }
/* The drawer writes the wording out BESIDE the lantern (owner, 2026-09-20: the text is additional on
   mobile, the icon is always there). Same glyph, same states; only room for the words. */
.gl-btn.site-lampbtn--labelled { gap: 8px; padding: 6px 10px 6px 6px; line-height: 1.2; }
.site-lampbtn__text { white-space: nowrap; }
/* The header lantern at every width, for everyone (the tablet's second nav row that used to carry
   it instead is gone). */
.site-topbar__lamp { display: flex; align-items: center; flex: 0 0 auto; }
.site-lampicon { display: block; }
.site-lampicon__wick, .site-lampicon__rays { transition: opacity .18s ease; }
/* Lamplight: the lamps are down, so the lantern is lit — the light the button offers. */
.site-lampbtn--up .site-lampicon__wick { opacity: 1; }
.site-lampbtn--up .site-lampicon__rays { opacity: .55; }
/* Daylight: the lamps are up, so the lantern is dark — the dimming the button offers. */
.site-lampbtn--down .site-lampicon__wick { opacity: .16; }
.site-lampbtn--down .site-lampicon__rays { opacity: 0; }
.site-lampbtn:hover .site-lampicon { color: var(--gl-lamp); }
@media (prefers-reduced-motion: reduce) { .site-lampicon__wick, .site-lampicon__rays { transition: none; } }
/* Parchment signs the top of its own settings panel, quietly. */
.reader__panel-row--name { align-items: baseline; gap: 8px; padding-bottom: 2px; }
.reader__panel-name { font-family: var(--gl-display); font-weight: 600; font-size: 16px; color: var(--gl-text); }
/* A checkbox with its explanation beside it, for settings that need a sentence. */
.site-check { display: flex; align-items: flex-start; gap: 10px; font-size: 14px; line-height: 1.5; cursor: pointer; }
.site-check input { margin-top: 3px; flex: 0 0 auto; }
/* Owner, P-26: untinted checkboxes filled near-black against Lamplight. Give every
   checkbox the house amethyst so the tick reads on both themes. */
input[type="checkbox"] { accent-color: var(--gl-amethyst); width: 16px; height: 16px; }
/* Owner, P-23: the browser paints autofilled password fields its own pale gold,
   which reads as a highlight that is not ours. Keep the field's own ink and
   background instead — the box-shadow trick is the only way past the UA fill. */
.gl-root .gl-input:-webkit-autofill, .gl-root .gl-input:-webkit-autofill:hover,
.gl-root .gl-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--gl-text);
  -webkit-box-shadow: 0 0 0 1000px var(--gl-ink-deep) inset;
  caret-color: var(--gl-text);
  transition: background-color 9999s ease-out;
}
.site-check .site-micro { margin-top: 3px; max-width: 62ch; }
/* The reading-speed passage: the reader's own measure, so it is set like the reader's prose. */
.site-speedtest { max-width: 64ch; max-height: 56vh; overflow: hidden auto; overscroll-behavior: contain; padding-right: 10px; font-family: 'Fraunces Text', var(--gl-display); font-size: 17px; line-height: 1.62; color: var(--gl-text); }
.site-speedtest p { margin: 0 0 14px; }
.site-speedtest p:last-child { margin-bottom: 0; }
.site-account-link { display: flex; align-items: center; gap: 8px; white-space: nowrap; padding: 4px 10px 4px 4px; border-radius: var(--gl-radius-pill); color: var(--gl-muted); font-size: 13px; }
.site-account-link:hover { background: var(--gl-wash); color: var(--gl-text); }
/* flex-shrink: 0 because every avatar is a fixed-size circle sitting in a flex
   row — .site-account-link in the header, member rows, the settings list. The
   width below is a flex BASIS, not a floor, so without this the image is the
   first thing squeezed when the row runs out of room: at 320px the header
   avatar rendered 17x28 instead of 28x28, which turns the circular frame and
   its ring into an oval. Height is untouched because only the main axis
   shrinks, which is the signature of this bug. Owner spotted it in a capture
   on 2026-09-21; it is measured now by the aspect check in the BUG-026 sweep. */
.site-avatar { flex-shrink: 0; border-radius: 50%; object-fit: contain; background: var(--gl-panel-2); box-shadow: 0 0 0 2px var(--gl-char, var(--gl-amethyst)); }
.site-avatar--28 { width: 28px; height: 28px; }
.site-avatar--34 { width: 34px; height: 34px; }
.site-avatar--44 { width: 44px; height: 44px; }
.site-avatar--52 { width: 52px; height: 52px; }
.site-avatar--72 { width: 72px; height: 72px; }
.site-menu-btn { display: inline-flex; }
.site-menu-btn-wrap { display: flex; flex: 0 0 auto; }

.site-navgroup { position: relative; display: flex; align-items: center; }
.site-navgroup__toggle { display: inline-flex; align-items: center; justify-content: center; width: 22px; min-height: 36px; margin-left: -6px; border: 0; background: none; color: var(--gl-muted); font: inherit; font-size: 10px; cursor: pointer; border-radius: var(--gl-radius-sm); }
.site-navgroup__toggle:hover, .site-navgroup__toggle[aria-expanded="true"] { background: var(--gl-wash); color: var(--gl-text); }
.site-submenu { position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; min-width: 210px; margin: 0; padding: 6px; list-style: none; background: var(--gl-shelf); border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); box-shadow: var(--gl-shadow-frame); animation: gl-drop .14s ease both; }
.site-submenu a { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 12px; border-radius: var(--gl-radius-sm); color: var(--gl-text); font-size: 13.5px; white-space: nowrap; }
.site-submenu a:hover, .site-submenu a:focus-visible { background: var(--gl-wash); }
/* A menu entry that does something here instead of going somewhere (Report a problem): the link's own look. */
.site-submenu .site-submenu__btn { display: flex; width: 100%; align-items: center; gap: 10px; padding: 9px 12px; border: 0; border-radius: var(--gl-radius-sm); background: none; color: var(--gl-text); font: inherit; font-size: 13.5px; white-space: nowrap; text-align: left; cursor: pointer; }
.site-submenu .site-submenu__btn:hover, .site-submenu .site-submenu__btn:focus-visible { background: var(--gl-wash); }
.site-drawer__btn { width: 100%; border: 0; background: none; font: inherit; text-align: left; cursor: pointer; }
.site-report__facts { margin: 0; font-size: 12px; color: var(--gl-faint); line-height: 1.5; }
.site-report__mailto--waiting { opacity: .55; pointer-events: none; }
.site-submenu a[aria-current="page"] { box-shadow: inset 3px 0 0 var(--gl-lamp); }
.site-submenu__head { padding: 8px 12px 4px; font-size: 10px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--gl-lamp); }
/* An invisible bridge across the gap under the link, so the pointer can travel into the menu. */
.site-submenu::before { content: ""; position: absolute; left: 0; right: 0; top: -10px; height: 10px; }
.site-submenu--right { left: auto; right: 0; }
button.site-account-link { background: none; border: 0; font: inherit; cursor: pointer; }
button.site-account-link[aria-expanded="true"] { background: var(--gl-wash); color: var(--gl-text); }

/* The phone drawer, from the approved canvas (drawerOpen). */
.site-drawer { display: flex; flex-direction: column; background: var(--gl-shelf); border-bottom: 1px solid var(--gl-rule); padding: 6px 10px 12px; animation: gl-drop .16s ease both; }
.site-drawer__link { display: flex; align-items: center; min-height: 46px; padding: 0 12px; color: var(--gl-text); font-size: 15px; border-radius: var(--gl-radius-sm); }
.site-drawer__link:hover, .site-drawer__link:focus-visible { background: var(--gl-wash); }
.site-drawer__link[aria-current="page"] { box-shadow: inset 3px 0 0 var(--gl-lamp); }
.site-drawer__link--sub { min-height: 40px; padding-left: 28px; font-size: 14px; color: var(--gl-muted); }
.site-drawer__search { padding: 10px 12px 0; }
.site-drawer__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 14px 12px 0; padding-top: 12px; border-top: 1px solid var(--gl-rule-soft); }

/* The header is one row at every width (owner, 2026-09-25: "I hate the tablet view?" and "please look
   at what you can do to not make it weird 2 column on tablets too"). The menu is inline from 1024 up;
   below that it is behind Menu. */
@media (min-width: 1024px) {
  .site-nav-inline { display: flex; flex: 0 0 auto; }
  .site-menu-btn-wrap { display: none; }
}
/* On a tablet the drawer is a drop-down from the right, over the page, rather than a phone-width list
   pushing the page down; it scrolls inside itself when it is taller than the screen. */
@media (min-width: 640px) and (max-width: 1023.98px) {
  .site-drawer { position: absolute; z-index: 45; top: 100%; right: 12px; width: min(360px, calc(100vw - 24px)); max-height: calc(100vh - 80px); overflow-y: auto; overscroll-behavior: contain; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); box-shadow: var(--gl-shadow-frame); }
}
/* The masthead search is a pill at every width (owner, 2026-09-25: "please make the search just a
   pill, it solvs issues" and "search icon everywhere"): the search icon in a small pill drawn like the
   box it replaces. Pressing it drops the field down under the header (components/HeaderSearch.tsx
   places it under the pill and inside the viewport). */
.site-topsearch { display: flex; align-items: center; }
.site-searchpill { display: inline-flex; align-items: center; justify-content: center; width: 46px; height: 34px; padding: 0; border: 1px solid var(--gl-rule-strong); border-radius: var(--gl-radius-pill); background: var(--gl-ink-deep); color: var(--gl-muted); cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease; }
.site-searchpill:hover, .site-searchpill[aria-expanded="true"] { border-color: var(--gl-link); }
.site-searchpill:focus-visible { outline: none; border-color: var(--gl-link); box-shadow: 0 0 0 3px rgba(169, 123, 232, .25); }
.site-searchicon { display: block; color: currentColor; }
.site-searchpill:hover .site-searchicon, .site-searchpill[aria-expanded="true"] .site-searchicon { color: var(--gl-lamp); }
[data-gl-theme="light"] .site-searchpill:hover .site-searchicon, [data-gl-theme="light"] .site-searchpill[aria-expanded="true"] .site-searchicon { color: var(--gl-amethyst); }
.site-topsearch__panel { position: absolute; top: calc(100% + 6px); left: 12px; z-index: 46; width: min(420px, calc(100vw - 24px)); box-sizing: border-box; padding: 10px; background: var(--gl-panel); border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); box-shadow: var(--gl-shadow-pop, 0 12px 32px rgba(0, 0, 0, .35)); }
/* The suggestions are exactly as wide as the field and end on screen. */
.site-topsearch__panel .site-suggest__list { width: 100%; min-width: 0; max-width: none; max-height: min(60vh, 420px, calc(100vh - 150px)); }
@media (pointer: coarse) { .site-topsearch__panel :is(.gl-input, input) { font-size: 16px; } }
/* Tighter, so the one row holds at every width (owner, 2026-09-25, on long names in the header:
   "tighter plus ..."): smaller gaps between the header's parts, and on a desk narrower than 1280 the
   menu's links sit closer. The account name gives way last, shortening with "…" (the full name is the
   control's tooltip); it never pushes anything past the edge. */
.site-topbar .gl-topbar { gap: 10px; }
.site-topbar .gl-topbar__nav { min-width: 0; flex: 0 1 auto; gap: 6px; }
.site-topbar .site-navgroup:has(> .site-account-link) { min-width: 0; flex: 0 1 auto; }
.site-topbar .site-account-link { min-width: 40px; max-width: 100%; }
@media (min-width: 1024px) and (max-width: 1279.98px) {
  .site-topbar .gl-topbar { gap: 8px; padding: 0 12px; }
  .site-topbar .gl-topbar__nav { gap: 4px; }
  .site-nav-inline .site-nav-link { padding-left: 7px; padding-right: 7px; }
  .site-nav-inline .site-nav-link[aria-current="page"]::after { left: 7px; right: 7px; }
  .site-nav-inline .site-navgroup__toggle { width: 18px; margin-left: -5px; }
  .site-topbar .site-account-link { padding-right: 6px; }
}
/* 1024 to 1179px with the scrollbar's width kept (html scrollbar-gutter, the "screens change size"
   fix): the same row, a few pixels closer again, so a name of up to twelve letters ("Meeko",
   "MeekoArchive") shows whole instead of "Me…" (owner: "never let small things like that happen").
   Longer names still shorten with "…" and the full name stays the tooltip. */
@media (min-width: 1024px) and (max-width: 1179.98px) {
  .site-topbar .gl-topbar { gap: 6px; padding: 0 10px; }
  .site-topbar .gl-topbar__nav { gap: 2px; }
  .site-nav-inline .site-nav-link { padding-left: 4px; padding-right: 4px; }
  .site-nav-inline .site-nav-link[aria-current="page"]::after { left: 4px; right: 4px; }
  .site-nav-inline .site-navgroup__toggle { width: 16px; margin-left: -4px; }
  .site-topbar .site-account-link { gap: 6px; padding-right: 2px; }
}
@media (max-width: 639.98px) {
  .site-topbar .gl-topbar { padding: 0 14px; gap: 8px; }
  .site-topbar .gl-topbar__nav { margin-left: auto; gap: 4px; }
  .site-searchpill { width: 40px; }
}
@media (max-width: 419.98px) {
  .site-topbar .gl-topbar { gap: 6px; }
  .site-searchpill { width: 36px; }
}

/* Report a problem in the phone drawer is a <button> (it opens the form); it takes the same text
   colour as its sibling links (owner, 2026-09-25: "Report a problem text is… a different color in the dropdown?"). */
/* The DS colours every <a> link-violet at specificity (0,1,1). The canvas draws
   its rows, tiles, chapter lists and navigation as links in the TEXT and MUTED
   colours (inline styles there); here those rules are scoped under .gl-root so
   they out-specify the DS rule and the words keep the canvas's colours. */
.gl-root a.site-nav-link, .gl-root a.site-strip__item, .gl-root a.site-account-link, .gl-root a.site-tagpill, .gl-root .site-footer a, .gl-root a.reader__back, .gl-root .site-drawer__link--sub { color: var(--gl-muted); }
.gl-root a.site-nav-link:hover, .gl-root a.site-strip__item:hover, .gl-root a.site-account-link:hover, .gl-root a.site-tagpill:hover, .gl-root .site-footer a:hover, .gl-root a.reader__back:hover { color: var(--gl-text); }
.gl-root a.site-tagpill--on { color: var(--gl-text); }
.gl-root a.site-drawer__link, .gl-root button.site-drawer__link, .gl-root .site-submenu a, .gl-root a.site-row, .gl-root a.site-listitem, .gl-root a.site-tile, .gl-root a.site-chapters__row, .gl-root a.site-shelfcard, .gl-root .site-magic-tree a, .gl-root .reader__toc a, .gl-root .reader__nav a { color: var(--gl-text); }
.gl-root a.site-drawer__link:hover, .gl-root button.site-drawer__link:hover, .gl-root .site-submenu a:hover, .gl-root a.site-row:hover, .gl-root a.site-listitem:hover, .gl-root a.site-tile:hover, .gl-root a.site-chapters__row:hover, .gl-root a.site-shelfcard:hover, .gl-root .site-magic-tree a:hover, .gl-root .reader__toc a:hover, .gl-root .reader__nav a:hover { color: var(--gl-text); }
.gl-root a.site-chip { color: #F3EEF9; }
.gl-root a.site-chip:hover { color: #F3EEF9; }
.gl-root .reader__nav--quiet a { color: var(--gl-link); }
.gl-root .reader__nav--quiet a:hover { color: var(--gl-link-hover); }

/* Links styled as buttons: the DS's own `.gl-root a` colour rule out-specifies
   the button variants, so an <a class="gl-btn"> would go link-violet. Give each
   variant its own ink back. */
.gl-root a.gl-btn--primary, .gl-root a.gl-btn--primary:hover { color: var(--gl-action-text); }
.gl-root a.gl-btn--secondary, .gl-root a.gl-btn--secondary:hover { color: var(--gl-text); }
.gl-root a.gl-btn--ghost { color: var(--gl-muted); }
.gl-root a.gl-btn--ghost:hover { color: var(--gl-text); }
.gl-root a.gl-btn--lamp, .gl-root a.gl-btn--lamp:hover { color: var(--gl-ink); }
.gl-root a.gl-btn--danger, .gl-root a.gl-btn--danger:hover { color: #fff; }

/* Selects: one chevron, drawn by us, that stays inside the box in every browser. */
.gl-root .gl-select { -webkit-appearance: none; appearance: none; padding-right: 36px; background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23A294BE' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 13px center; background-size: 12px 8px; }
[data-gl-theme="light"] .gl-root .gl-select { background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23574A72' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.gl-root .gl-select::-ms-expand { display: none; }

/* --- search suggestions ------------------------------------------------- */
.site-suggest { position: relative; }
.site-suggest__list { position: absolute; z-index: 20; top: calc(100% + 8px); left: 0; min-width: 100%; width: max(100%, 320px); max-width: min(92vw, 420px); max-height: min(60vh, 420px); background: var(--gl-shelf); border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); box-shadow: var(--gl-shadow-frame); overflow: hidden auto; overscroll-behavior: contain; animation: gl-drop .14s ease both; margin: 0; padding: 0; list-style: none; }
.site-suggest__item { display: flex; align-items: baseline; gap: 10px; padding: 10px 13px; color: var(--gl-text); font-size: 13.5px; cursor: pointer; }
/* P-07 (owner): separators hang off the TOP of each following row, never the
   bottom of the one before — "a new section should NOT add an extra border at
   the bottom, it should be at the top." The last entry can never carry a stray
   line, and a pager or an inline panel between rows never eats one. */
.site-suggest__item ~ .site-suggest__item,
.site-row ~ .site-row,
.site-listitem ~ .site-listitem,
.site-listitem ~ .site-listitem__withaction,
.site-listitem__withaction ~ .site-listitem,
.site-listitem__withaction ~ .site-listitem__withaction,
.site-chapters__row ~ .site-chapters__row,
.site-tracker-row ~ .site-tracker-row,
.site-gave__row ~ .site-gave__row,
.site-friend ~ .site-friend,
.gl-table tbody tr ~ tr td { border-top: 1px solid var(--gl-rule-soft); }
.gl-steps .gl-step, .gl-table tbody td { border-bottom: 0; }
.site-suggest__item:hover, .site-suggest__item[aria-selected="true"] { background: var(--gl-wash); }
.site-suggest__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.site-suggest__by { color: var(--gl-muted); font-size: 12.5px; }
.site-suggest__kind { flex: 0 0 auto; font-size: 10px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--gl-faint); }
.site-suggest__empty { margin: 0; padding: 12px 13px; font-size: 13px; color: var(--gl-faint); }

/* --- hero ---------------------------------------------------------------- */
/* The inner block spans the whole banner (it was shrink-wrapped to its content), so a second section can sit at the far edge. */
.site-hero .gl-hero__inner { display: flex; flex-direction: column; justify-content: flex-end; width: 100%; box-sizing: border-box; }
.site-hero__eyebrow { margin: 0; font-family: var(--gl-ui); font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: #E3B85C; }
.site-hero__title { margin: 0; font-family: var(--gl-display); font-optical-sizing: auto; font-weight: 600; font-size: clamp(30px, 5vw, 50px); line-height: 1.05; color: #FAF7FC; text-wrap: balance; }
.site-hero__lede { margin: 0; max-width: 52ch; font-size: 14.5px; line-height: 1.55; color: #E4DBF2; text-wrap: pretty; }
.site-hero__row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 20px; min-height: 224px; }
.site-hero__row > .site-hero__text { flex: 1 1 280px; min-width: 0; display: flex; flex-direction: column; justify-content: flex-end; gap: 12px; }
.site-hero__stack { display: flex; flex-direction: column; justify-content: flex-end; gap: 12px; min-height: 224px; }
.site-hero__card { flex: 0 1 250px; display: flex; flex-direction: column; gap: 10px; padding: 16px; border-radius: var(--gl-radius-lg); background: rgba(11, 7, 20, .62); border: 1px solid rgba(227, 184, 92, .34); }
/* A banner's second section (staff card, on-duty card, this-month card, shelf mark) sits at the banner's right edge whenever it shares the line with the text; on phones it takes the full width below. */
@media (min-width: 640px) { .site-hero__card { margin-left: auto; } }
@media (max-width: 639.98px) { .site-hero__card { margin-left: auto; margin-right: auto; } }
.site-hero__card-label { margin: 0; font-size: 10px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; color: #E3B85C; }
.site-hero__card-value { margin: 0; font-family: var(--gl-display); font-size: 30px; line-height: 1; color: #FAF7FC; font-variant-numeric: tabular-nums; }
.site-hero__card-note { margin: 0; font-size: 12px; color: #D3C9E6; }
.site-hero__chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
.site-chip { display: inline-flex; align-items: center; white-space: nowrap; padding: 7px 13px; border-radius: var(--gl-radius-pill); background: rgba(11, 7, 20, .55); border: 1px solid rgba(227, 184, 92, .38); font-size: 13px; color: #F3EEF9; cursor: pointer; font: inherit; }
.site-chip:hover { background: rgba(11, 7, 20, .78); border-color: #E3B85C; }
/* A phone: the chips at their 13px (the shorthand above resets it to the page's size) with a 6px gap, so
   Hockey RPF · Heated Rivalry · Everything share one line at 390 (final visual review: Everything alone). */
@media (max-width: 639.98px) { .site-hero__chips { gap: 6px; } .site-hero__chips .site-chip { font-size: 13px; padding: 7px 9px; } }
/* Staff only: keep the current filters as a chip (owner item 7). Dashed, so it reads as a control rather than a chip. */
.site-chip--make { border-style: dashed; opacity: .85; }
.site-chip[aria-pressed="true"] { border-color: #E3B85C; background: rgba(227, 184, 92, .22); }
/* Heading placement, chosen per banner so the painted sign stays clear. */
.site-hero--top-left .site-hero__row { align-items: flex-start; }
.site-hero--top-left .site-hero__row > .site-hero__text { justify-content: flex-start; }
.site-hero--bottom-right .site-hero__row { justify-content: flex-end; }
.site-hero--bottom-right .site-hero__row > .site-hero__text { flex: 0 1 560px; margin-left: auto; }
.site-hero--tall .site-hero__row { min-height: 300px; }
@media (max-width: 639.98px) {
  .site-hero__row, .site-hero__stack { min-height: 150px; }
  .site-hero--tall .site-hero__row { min-height: 230px; }
  .site-hero .gl-hero__inner { padding: 18px; }
  .site-hero__card { flex-basis: 100%; }
  .site-hero--bottom-right .site-hero__row > .site-hero__text { flex-basis: 100%; }
}
@media (min-width: 640px) and (max-width: 1023.98px) {
  .site-hero__row, .site-hero__stack { min-height: 200px; }
}

/* The card must not sit on the painted sign — and that is true well past a phone.

   The rule used to live in the 639.98px block, so between 640px and 1024px the
   card was shown and covered the sign anyway. The card is a fixed 250px pinned
   to the right of the hero, so what matters is the fraction of the art it takes:

     675px viewport  hero  627px  card starts at 60%  "READING TRA…"  badly cut
     900px viewport  hero  852px  card starts at 71%  "READING TRACKE"  cut
     1024px viewport hero  976px  card starts at 74%  "READING TRACKER"  clear
     1280px viewport hero 1132px  card starts at 75%  clear

   Measured on the live page at each width. 1024px is where the art actually has
   room, and it is already this file's desktop boundary. Nothing is lost below
   it: the same figures are repeated in the page underneath, which is why the
   original rule could hide the card in the first place. */
@media (max-width: 1023.98px) {
  .site-hero--top-left .site-hero__card { display: none; }
}

/* Home banner: the "Great Library" sign sits in the upper part of the art, so
   the search control keeps to the lower-left band and never grows over it. */
.site-home-hero .site-hero__stack { min-height: 296px; max-width: 620px; gap: 14px; }
.site-home-hero .gl-hero__art { object-position: 50% 40%; }
.site-home-hero__search { position: relative; display: flex; flex-wrap: wrap; gap: 10px; max-width: 468px; }
.site-home-hero__search .site-suggest { flex: 1 1 260px; min-width: 0; }
.site-home-hero__search .gl-input { font-size: 16px; padding: 15px 22px; width: 100%; }
.site-home-hero__stats { margin: 0; font-size: 13px; color: #D3C9E6; font-variant-numeric: tabular-nums; }
@media (max-width: 639.98px) {
  /* On a phone the art fills the frame by height, which pins the sign at
     roughly 55% of the hero's height. 520px keeps the whole sign above the
     search control instead of behind it. */
  .site-home-hero .gl-hero { min-height: 520px; display: flex; align-items: flex-end; }
  .site-home-hero .gl-hero__art { object-position: 50% 50%; }
  .site-home-hero .gl-hero__inner { width: 100%; }
  .site-home-hero .site-hero__stack { min-height: 0; max-width: 100%; }
  .site-home-hero__search { max-width: 100%; }
  .site-home-hero__search .gl-btn { flex: 1 1 100%; }
  .site-home-hero .site-hero__eyebrow { display: none; }
  .site-topbar .gl-topbar__brand { font-size: 17px; }
}
@media (min-width: 640px) and (max-width: 1023.98px) {
  .site-home-hero .site-hero__stack { min-height: 252px; }
}

/* --- section headers and common blocks ---------------------------------- */
.site-sechead { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; }
.site-sechead__text { flex: 1 1 260px; min-width: 0; }
.site-sechead__more { font-size: 13.5px; white-space: nowrap; }
/* A section heading with a control of its own (a list's Filters button, filters everywhere 2026-09-25): the
   control keeps beside the heading at every width, never on a line of its own ("I hate things on one line"). */
.site-sechead--action { flex-wrap: nowrap; }
.site-sechead--action > .site-sechead__text { flex: 1 1 auto; }
.site-sechead--action > :not(.site-sechead__text) { flex: 0 0 auto; }
.site-panel { background: var(--gl-panel); border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-lg); }
.site-panel--pad { padding: 16px 18px; }
.site-panel--pad-lg { padding: 20px; }
.site-panel--2 { background: var(--gl-panel-2); }
.site-stack { display: flex; flex-direction: column; gap: 10px; }
.site-stack--16 { gap: 16px; }
.site-cluster { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.site-muted { color: var(--gl-muted); }
.site-faint { color: var(--gl-faint); }
/* Settings > The shelves > How the Library is running: the failure text is an operator's line, kept whole and wrapping. */
.site-running { padding-top: 12px; }
.site-running__failure { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.92em; overflow-wrap: anywhere; color: var(--gl-danger, inherit); }
.site-small { font-size: 13.5px; }
.site-micro { font-size: 12px; }
.site-num { font-variant-numeric: tabular-nums; }
.site-label { font-size: 12px; font-weight: 600; color: var(--gl-muted); }
.site-kicker { margin: 0; font-size: 10px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--gl-lamp); }
.site-kicker--faint { color: var(--gl-faint); }
.site-prose { font-size: 15px; line-height: 1.65; color: var(--gl-muted); max-width: 66ch; text-wrap: pretty; }
.site-prose p { margin: 0 0 12px; }
.site-prose p:last-child { margin-bottom: 0; }
.site-grid { display: grid; gap: var(--gl-space-4); }
.site-grid--240 { grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.site-grid--260 { grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.site-grid--280 { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
.site-grid--300 { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: 10px; }
.site-grid--150 { grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); }
.site-grid--220 { grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); gap: 16px; }
/* Three fields that belong together (Change password): three across from 640px, stacked below. */
.site-grid--3up { gap: 16px; }
@media (min-width: 640px) { .site-grid--3up { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* An even count of fields: two across from 640px, so it never ends on a lone field. */
.site-grid--2up { gap: 16px; }
@media (min-width: 640px) { .site-grid--2up { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Four fields: four across on a desk, two by two from 640px, stacked below. */
.site-grid--4or2 { gap: 16px; }
@media (min-width: 640px) { .site-grid--4or2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .site-grid--4or2 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.site-fieldstack { display: flex; flex-direction: column; gap: 6px; }
.site-form-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-end; }
.site-bar { height: 4px; border-radius: 999px; background: var(--gl-rule-soft); overflow: hidden; }
.site-bar--5 { height: 5px; }
.site-bar--6 { height: 6px; }
.site-bar__fill { height: 100%; background: var(--gl-amethyst); transition: width .3s ease; }
.site-bar__fill--lamp { background: var(--gl-lamp); }
.site-hr { border: 0; margin: 4px 0; }
.site-empty { padding: 26px 20px; text-align: center; color: var(--gl-faint); font-size: 14px; border: 1px dashed var(--gl-rule); border-radius: var(--gl-radius-lg); }
.site-inline-actions { display: flex; flex-wrap: wrap; gap: 8px; }
/* Inbox › Notices filter pills on a phone: 8px sides, so they go three and two at 390 (final visual review: "Archived" alone). */
@media (max-width: 639.98px) { .site-filterpills > .site-tagpill { padding-left: 8px; padding-right: 8px; } }
/* Administrator settings › The Infinite Stacks, a phone: Hide from the Stacks and Take the shelf down share one line (final visual review: 2/1/1). */
@media (max-width: 639.98px) { .site-stacks-shelfacts { flex-wrap: nowrap; gap: 6px; } .site-stacks-shelfacts > .gl-btn { padding: 8px; font-size: 12px; white-space: nowrap; } }
/* A shelf's own controls (Shelves.tsx): Rename · Shared with [select] · Delete shelf on one row; on a phone
   Rename and Delete shelf on the first line, Shared with [select] on the second, so none is alone. */
.site-shelf-actions { align-items: center; }
.site-shelf-actions__vis { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.site-shelf-actions__vis .site-label { margin: 0; white-space: nowrap; }
.site-shelf-actions__vis .gl-select { width: auto; min-width: 0; }
@media (max-width: 639.98px) {
  .site-shelf-actions { display: grid; grid-template-columns: auto auto; justify-content: start; }
  .site-shelf-actions > :first-child { grid-row: 1; grid-column: 1; }
  .site-shelf-actions > :last-child { grid-row: 1; grid-column: 2; }
  .site-shelf-actions > .site-shelf-actions__vis { grid-row: 2; grid-column: 1 / -1; }
}
.site-link-btn { background: none; border: 0; padding: 0; font: inherit; color: var(--gl-link); cursor: pointer; }
.site-link-btn:hover { color: var(--gl-link-hover); }

/* WCAG 2.5.8 — standalone links and link-styled buttons keep a >=24px target
   line box on every viewport. Links inside running text keep the inline
   exception and are unchanged. */
.site-sechead__more,
a.site-small,
.site-staffnote__link,
.site-link-btn,
.site-work__take a,
.site-work__take button,
a.site-row__by,
a.site-work__source { line-height: 24px; }
.site-check { min-height: 24px; }
/* Inline anchors measure their font box, not the line box — these are
   standalone controls rendered as bare links, so they need a real box. */
.gl-character__name a,
.site-small > a:only-child { display: inline-block; line-height: 24px; }
/* Heading links are standalone controls (a heading never runs as prose), so
   each direct-child anchor gets a real hit box — padded a pixel past the text
   frame without moving the glyphs. */
.gl-root h2 > a,
.gl-root h3 > a { display: inline-block; line-height: 24px; padding: 1px; margin: -1px; }
/* Tag-cloud pills: the anchor inside the pill is the clickable part — pad its
   hit box to the pill height without changing the visual. */
span.site-tagpill > a { display: inline-block; padding-block: 3px; margin-block: -3px; }
/* A pill never widens the page: a tag longer than the line wraps inside its pill (phones) instead of pushing past the screen edge. */
.site-tagpill { flex: 0 1 auto; min-width: 0; max-width: 100%; white-space: normal; overflow-wrap: anywhere; text-align: left; display: inline-flex; align-items: center; gap: 6px; padding: 4px 11px; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-pill); font-size: 12.5px; line-height: 1.35; color: var(--gl-muted); font-family: inherit; background: transparent; cursor: pointer; }
.site-tagpill:hover { border-color: var(--gl-rule-strong); color: var(--gl-text); }
.site-tagpill--on { border-color: var(--gl-lamp); color: var(--gl-text); background: var(--gl-lamp-soft); }
/* A pill that only labels something (an applied filter, a saved search) is not itself a control; its buttons are. */
span.site-tagpill { cursor: default; }
span.site-tagpill > button { cursor: pointer; }
.site-tagpill__x { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; background: var(--gl-wash); color: var(--gl-muted); font-size: 12px; line-height: 1; border: 0; cursor: pointer; }
.site-tagpill__x:hover { color: var(--gl-text); }
/* Removal is always the warning red, so it is never mistaken for "clear this filter". */
.site-tagpill__x--danger { background: var(--gl-danger-ground); color: #fff; }
.site-tagpill__x--danger:hover { color: #fff; filter: brightness(1.1); }
.site-tagpill__block { display: inline-flex; align-items: center; padding: 1px 8px; border: 0; border-radius: var(--gl-radius-pill); background: var(--gl-danger-ground); color: #fff; font: inherit; font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; cursor: pointer; }
.site-tagpill__block:hover { filter: brightness(1.1); }
.site-alert { padding: 12px 14px; border-radius: var(--gl-radius); border: 1px solid var(--gl-rule); background: var(--gl-panel); color: var(--gl-text); font-size: 13.5px; }
.site-alert--danger { border-color: var(--gl-danger); color: var(--gl-danger); }
.site-alert--ok { border-color: var(--gl-success); }
.site-toast { position: fixed; left: 50%; bottom: calc(18px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%); z-index: 90; padding: 10px 16px; background: var(--gl-shelf); color: var(--gl-text); border: 1px solid var(--gl-rule-strong); border-radius: var(--gl-radius-pill); box-shadow: var(--gl-shadow-frame); font-size: 13.5px; max-width: 92vw; width: max-content; }
/* A toast is as wide as its words, up to 92% of the screen: fixed at left 50% it shrank to half the screen, so on a
   phone even a short toast broke onto two or three lines (owner, 2026-09-25: "never let small things like that
   happen, multiple lines or off screen"). */

/* --- badges ---------------------------------------------------------------
   NEW and UPDATED are gold; TODAY is wisteria. Each carries its word, so
   nothing relies on colour alone. On a cover the pill is solid — the soft
   translucent fills read as nothing over art — and sits where the canvas
   places it, top-left. */
.site-badge--today { background: var(--gl-wash); color: var(--gl-amethyst); border-color: var(--gl-amethyst); }
/* On a cover: a small icon at the top-left (the DS card positions it at 8px/8px). Part 3 item 7, owner
   2026-09-25: "your tracking options overlap with the updated and new? Maybe… just a little icon for
   the updated and new with the status staying". A round badge the height of the status pill, on the
   same fixed dark ground so it reads over any art in either theme: a gold sparkle for New, a gold
   turning arrow for Updated, the arrow in wisteria for Today. The word is its accessible name and
   tooltip (CoverBadgeIcon), so nothing relies on the picture alone. */
.site-cover-badge { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 999px; color: #E3B85C; background: rgba(20, 14, 28, .9); box-shadow: 0 1px 3px rgba(0, 0, 0, .45), 0 0 0 1px #E3B85C; }
.site-cover-badge svg { display: block; width: 14px; height: 14px; }
.site-cover-badge--today { color: #C9A9F5; box-shadow: 0 1px 3px rgba(0, 0, 0, .45), 0 0 0 1px #C9A9F5; }
/* P-09: the member's tracker status tagged on the cover, under the recency word.
   It sits over artwork, so the ground is a fixed translucent dark — legible on
   any cover in either theme. Accents reuse the badge palette: amethyst while
   the work is open, gold once it's finished. */
/* Owner, 2026-09-24: "the 'reading' should be in a different corner and MUCH more obvious and show ALL statuses".
   The member's tracker status is a solid pill in the cover's TOP-RIGHT corner — the opposite corner from the
   NEW/UPDATED word the DS card places top-left — one colour a status: reading amethyst, finished green,
   waiting gold, to-read / paused / dropped on dark grounds. It sits over artwork, so every ground is fixed
   and legible in either theme. Outside the DS card's own markup (a sibling in .site-book-wrap), so the DS
   card stays byte-for-byte; pointer-events off, so the card's link is what you press. Card-gated: a visitor
   has no tracker and gets no label (state/workStatuses.ts). */
.site-book-wrap { position: relative; display: block; min-width: 0; }
.site-cover-status { font-family: var(--gl-ui); font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; line-height: 1.2; padding: 5px 10px; border-radius: 999px; color: #FAF7FC; background: rgba(20, 14, 28, .9); box-shadow: 0 1px 3px rgba(0, 0, 0, .45), 0 0 0 1px rgba(255, 255, 255, .14); white-space: nowrap; }
.site-book-wrap > .site-cover-status { position: absolute; top: 8px; right: 8px; z-index: 3; pointer-events: none; }
.site-cover-status--reading { background: var(--gl-amethyst); }
.site-cover-status--finished { background: #2F7D4F; }
.site-cover-status--waiting { background: var(--gl-lamp); color: #1B1226; }
.site-cover-status--to-read { background: #3B2F55; }
.site-cover-status--paused { background: #5A4A70; }
.site-cover-status--dropped { background: #4C4652; }
/* The same label on a list row or a tile, inline after the title. */
.site-cover-status--inline { display: inline-block; vertical-align: middle; margin-left: 8px; font-size: 10.5px; padding: 3px 8px; }
/* The Irina Writer label (Part 3 item 7, owner-approved 2026-09-25), as mocked: a dark pill with a gold
   ring and a pen, so it is never taken for a status. Over a large cover it sits in the bottom-left
   corner — the one corner nothing else uses — inside an overlay the cover's size that lets presses
   through to the card but for the label itself, which goes to the writer's profile. Small covers never
   carry it ("not on the image if they're tiny"): rows, tiles and bylines put it inline after the
   author's name. Fixed colours over art, so it reads in either theme; the inline pill is the same. */
.site-writer-corner { position: absolute; top: 0; left: 0; right: 0; aspect-ratio: 2 / 3; pointer-events: none; z-index: 3; }
.site-writer-label { font-family: var(--gl-ui); font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; line-height: 1.2; padding: 4px 9px; border-radius: 999px; color: #F6E7C4; background: rgba(20, 14, 28, .9); box-shadow: 0 1px 3px rgba(0, 0, 0, .45), 0 0 0 1px #E3B85C; white-space: nowrap; text-decoration: none; }
.site-writer-label::before { content: '\2712\FE0E  '; }
.site-writer-corner > .site-writer-label { position: absolute; left: 8px; bottom: 8px; max-width: calc(100% - 16px); overflow: hidden; text-overflow: ellipsis; pointer-events: auto; }
.gl-root a.site-writer-label { color: #F6E7C4; }
.gl-root a.site-writer-label:hover, .site-writer-label[role="link"]:hover { color: #FFF3D6; box-shadow: 0 1px 3px rgba(0, 0, 0, .45), 0 0 0 1px #F2CF7E; }
a.site-writer-label:focus-visible, .site-writer-label[role="link"]:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 2px; }
.site-writer-label[role="link"] { cursor: pointer; }
.site-writer-label--inline { display: inline-block; vertical-align: middle; margin-left: 8px; font-size: 10.5px; padding: 3px 8px; }
.site-work__coverwrap { position: relative; display: block; width: 232px; max-width: 100%; }
.site-work__coverwrap > .site-work__cover { display: block; }
.gl-root { --site-cover-title-gap: 5px; }
.gl-book__cover { aspect-ratio: 2 / 3; background: var(--gl-panel-2); }
.gl-book__cover img { aspect-ratio: 2 / 3; }
/* Owner, 2026-09-24: "how close the title is below the cover ... needs like... 5px more".
   .site-book-link turns the DS card into a block, so its flex gap never applied and the
   title sat flush against the cover (measured 0px). One token, every cover-with-title. */
.gl-book > .gl-book__cover { margin-bottom: var(--site-cover-title-gap); }
.site-book-link { display: block; color: inherit; }
.site-book-link:hover, .site-book-link:focus-visible { color: inherit; }
.site-nocover { display: flex; align-items: flex-end; width: 100%; height: 100%; padding: 12px; overflow: hidden; box-sizing: border-box; background: linear-gradient(160deg, var(--gl-panel-2), var(--gl-ink-deep)); color: var(--gl-faint); font-family: var(--gl-display); font-size: 13px; line-height: 1.3; text-wrap: balance; }

/* --- work rows and tiles ------------------------------------------------- */
.site-rowlist { display: flex; flex-direction: column; border-top: 1px solid var(--gl-rule-soft); }
.site-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; padding: 13px 4px; color: var(--gl-text); }
.site-row:hover, .site-row:focus-within { background: var(--gl-wash); }
.site-row__title { margin: 0; flex: 1 1 220px; min-width: 0; font-family: var(--gl-display); font-weight: 600; font-size: 15px; color: var(--gl-text); text-wrap: pretty; }
/*
 * NOTHING IN A ROW MAY REFUSE TO WRAP. Owner, 2026-09-17: "The tracker/log is
 * causing overflow due to long text from ElevenReader stats."
 *
 * These carried `white-space: nowrap`, which is fine for "12 chapters read" and
 * ruinous for "Sent chapters 15-22 to ElevenReader". A flex child also defaults
 * to `min-width: auto`, so it refuses to shrink below its content: the two
 * together mean a long sentence pushes the row past the screen edge and the
 * whole page gains a horizontal scroll. Her own statistics spec carries "No
 * horizontal overflow" as an acceptance line.
 */
.site-row__by { margin: 0; flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--gl-muted); overflow-wrap: anywhere; }
.site-row__meta { margin: 0; flex: 0 1 auto; min-width: 0; font-size: 12px; color: var(--gl-faint); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.site-row__title { min-width: 0; overflow-wrap: anywhere; }
/* P-31, owner 2026-09-19: "more spread, nothing on one line". The request row
   wrapped the status select (width:100% in the DS) to its own line and printed
   the URL twice. Compact row: checkbox | title+meta | action | status. */
.site-reqrow { align-items: center; }
.site-reqrow__main { flex: 1 1 200px; min-width: 0; }
.site-reqrow__acts { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.site-reqrow .gl-select, .site-reqrow__status { width: auto; flex: 0 0 auto; padding-right: 30px; }
.site-row__when { margin: 0; flex: 0 0 84px; text-align: right; font-size: 12px; color: var(--gl-faint); white-space: nowrap; }
.site-listitem { display: flex; gap: 14px; padding: 14px 4px; color: var(--gl-text); }
.site-listitem:hover, .site-listitem:focus-within { background: var(--gl-wash); }
.site-listitem__cover { flex: 0 0 44px; width: 44px; height: 66px; object-fit: cover; border-radius: 4px; background: var(--gl-panel-2); }
/* A work with no cover at list size: its title stays inside the 44×66 well instead of spilling over the row beside and below it. */
.site-listitem__cover.site-nocover { overflow: hidden; padding: 4px; }
.site-listitem__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.site-listitem__title { margin: 0; font-family: var(--gl-display); font-weight: 600; font-size: 15.5px; color: var(--gl-text); text-wrap: pretty; }
.site-listitem__by { margin: 0; font-size: 13px; color: var(--gl-muted); }
.site-listitem__meta { margin: 0; font-size: 12px; color: var(--gl-faint); font-variant-numeric: tabular-nums; }
.site-listitem__side { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
/* "Your friend read this" (owner item 4, 2026-09-19): small portraits and a line under the byline; on a card it sits where the series line goes. */
.site-friends-read { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--gl-muted); }
.site-friends-read img { width: 16px; height: 16px; border-radius: 50%; object-fit: cover; background: var(--gl-panel-2); box-shadow: 0 0 0 1px var(--gl-rule); }
.site-friends-read img + img { margin-left: -8px; }
.site-friends-read--card { display: flex; margin-top: 4px; }
/* Friends currently reading (Part 3 item 8, owner 2026-09-25: "just do their dot ... in branding colors
   so purple or gold"): the house amethyst, with a soft ring of the same. Dot only on cards, rows and
   tiles; dot, portraits and the line where it fits (list rows, the tracker's rows, the work page). */
.site-reading-dot { display: inline-block; flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: var(--gl-amethyst); box-shadow: 0 0 0 3px var(--gl-wash); vertical-align: middle; }
/* Cards, rows and tiles: the dot and "Bea is reading" on one line that shortens with an ellipsis, never wraps. */
.site-reading-short { display: inline-flex; align-items: center; gap: 7px; max-width: 100%; min-width: 0; vertical-align: middle; font-family: var(--gl-ui); font-size: 11.5px; font-weight: 500; line-height: 1.3; color: var(--gl-text); }
.site-reading-short > .site-reading-dot { margin-left: 3px; }
.site-reading-short__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.site-reading-short--card { display: flex; margin: 2px 0 0; }
.site-reading-short--after { margin-left: 8px; }
.site-reading-now { display: flex; align-items: center; gap: 6px; margin-top: 4px; font-size: 12px; color: var(--gl-text); min-width: 0; }
.site-reading-now > .site-reading-dot { margin: 0 2px 0 3px; }
.site-reading-now img { width: 16px; height: 16px; border-radius: 50%; object-fit: cover; background: var(--gl-panel-2); box-shadow: 0 0 0 1px var(--gl-rule); flex: 0 0 auto; }
.site-reading-now img + img { margin-left: -8px; }
.site-reading-now > span:last-child { min-width: 0; overflow-wrap: anywhere; }
.site-reading-now--row { font-size: 13px; }
/* Settings › Privacy: every group on the same columns, so a group of one keeps one column's width. */
[data-privacy-group] .site-grid--220 { grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); }
/* The Notebook's sentence, "Share reviews rated [N★] and up with [Friends]": two halves that never break inside. */
.site-privacy-notebook__half { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
@media (min-width: 900px) { [data-privacy-group] .site-privacy-notebook { grid-column: span 2; } }
/* The work page line flows as text (like "Bea has finished this."), so it follows the box's own alignment at every width. */
.site-reading-now-work { margin: 0; }
.site-reading-now-work > .site-reading-dot { margin: 0 8px 0 3px; position: relative; top: -1px; }
.gl-root a.site-friend__shared { display: inline-flex; align-items: center; border-radius: var(--gl-radius-pill); text-decoration: none; }
.gl-root a.site-friend__shared:hover .gl-badge, .gl-root a.site-friend__shared:focus-visible .gl-badge { border-color: var(--gl-rule-strong); color: var(--gl-text); }
.gl-root a.site-compare__shared { color: var(--gl-muted); }
.gl-root a.site-compare__shared:hover { color: var(--gl-text); }
/* Friends lists (owner item 5, 2026-09-19): rows a page chosen by the member; the comparison opens under the clicked row. */
.site-pager { align-items: center; justify-content: center; margin-top: 12px; }
.site-pager__count { font-variant-numeric: tabular-nums; }
.site-compare--inline { margin: 8px 0 12px; }
.site-friend__note { padding: 6px 4px 12px 52px; }
/* Book page: friends and this work. */
.site-work__friends { display: flex; flex-direction: column; gap: 8px; }
.site-work__friends p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--gl-muted); }
.site-work__friends img { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; vertical-align: middle; margin-right: 4px; background: var(--gl-panel-2); box-shadow: 0 0 0 1px var(--gl-rule); }
@media (max-width: 479.98px) { .site-listitem__side { display: none; } }
.site-listitem__withaction { display: flex; align-items: stretch; }
.site-listitem__withaction > .site-listitem { flex: 1 1 auto; min-width: 0; border: 0; }
/* The row action slot (WorkListItem `action`: a shelf item's Remove, a bookmark's Remove): ONE place on every
   row — the top-right, level with the title's first line — whatever the row's own height. Owner, 2026-09-24:
   "The button position is weird for remove and looks odd. Same for bookmarks." Centred on a tall row the button
   floated at a different height on every row; the title line is 14px of padding plus ~20px of text and a small
   button is 29px tall, so 10px of top padding centres it on that line. The same rule at 390. */
.site-listitem__action { flex: 0 0 auto; display: flex; align-items: flex-start; gap: 6px; padding: 10px 4px 14px 10px; }
/* Manual shelf order (owner, 2026-09-24): the up/down pair stacks in the same slot, before Remove. */
.site-listitem__order { display: flex; flex-direction: column; gap: 2px; }
.site-listitem__order .gl-btn { min-height: 0; padding: 1px 8px; line-height: 1.2; font-size: 11px; }
.site-tile { display: flex; gap: 14px; padding: 14px; color: var(--gl-text); }
.site-tile:hover, .site-tile:focus-within { border-color: var(--gl-rule-strong); }
.site-tile__cover { flex: 0 0 52px; width: 52px; height: 78px; object-fit: cover; border-radius: 5px; box-shadow: var(--gl-shadow-card); background: var(--gl-panel-2); }
.site-tile__cover--sm { flex-basis: 34px; width: 34px; height: 51px; border-radius: 4px; box-shadow: none; }
.site-tile__cover--lg { flex-basis: 64px; width: 64px; height: 96px; }
.site-tile__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.site-tile__title { margin: 0; font-family: var(--gl-display); font-weight: 600; font-size: 14.5px; line-height: 1.25; color: var(--gl-text); text-wrap: pretty; }
.site-tile__note { margin: 0; font-size: 12px; color: var(--gl-faint); }
.site-tile__text { margin: 0; font-size: 14px; line-height: 1.55; color: var(--gl-muted); text-wrap: pretty; }
.site-tile--pick { gap: 16px; padding: 18px; }
/* Continue reading (Task D10): the tile, then one unmistakable lamp button straight into the reader. */
.site-continue { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.site-continue > .site-tile { flex: 1 1 auto; }
.site-tile--pick .site-tile__title { font-size: 17px; line-height: 1.2; }
.site-tile--digest { align-items: center; padding: 12px 14px; }
.site-tile--digest .site-tile__title { font-size: 14px; line-height: 1.3; }

/* Resident strip on Home. */
/* Centred at every width (owner, Task D5: no resident hanging by itself): when the strip wraps, the last row sits in the middle under the centred heading, not alone at the left edge. */
/* The strip is dealt into even rows by the page (Home.tsx stripRows), never one wrapping line that can leave one resident alone. */
.site-strip { display: flex; flex-direction: column; align-items: center; gap: 18px; }
/* Rows take the full content width and space the portraits across it — a centred 6-wide
   cluster on a wide desk left the strip squished in the middle of the page (owner,
   2026-09-22). The 8px gap is the floor; space-evenly shares whatever is left over. */
.site-strip__row { display: flex; flex-wrap: nowrap; justify-content: space-evenly; width: 100%; max-width: 1080px; gap: 8px 12px; }
.site-strip__item { display: flex; flex-direction: column; align-items: center; gap: 7px; width: 76px; color: var(--gl-muted); text-align: center; }
.site-strip__item:hover { color: var(--gl-text); }
.site-strip__item img, .site-strip__item .site-portrait-placeholder { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; background: var(--gl-panel-2); box-shadow: 0 0 0 2px var(--gl-panel), 0 0 0 3px var(--gl-rule); }
.site-strip__name { font-size: 12.5px; font-weight: 600; }
@media (max-width: 639.98px) { .site-strip { justify-content: center; } .site-strip + .site-sechead, .site-center-phone { text-align: center; justify-content: center; } }

/* --- catalogue -------------------------------------------------------- */
.site-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; padding: 16px; }
.site-filters__field { flex: 1 1 150px; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.site-filters__field--wide { flex-basis: 170px; }
.site-filters__actions { display: flex; gap: 6px; align-self: flex-end; }
.site-filters__actions .gl-btn { min-height: 38px; padding-top: 0; padding-bottom: 0; }
.site-filters .gl-select, .site-filters .gl-input { width: 100%; }
.site-filters__range { display: flex; align-items: center; gap: 6px; min-width: 0; }
.site-filters__range .gl-input { flex: 1 1 60px; width: auto; min-width: 0; max-width: 110px; padding-left: 10px; padding-right: 10px; }
/* A phone: min and max show whole (final visual review: "mir", "ma:"). Four characters and the padding;
   a phone has no spin arrows, so none is drawn to eat the box. */
@media (max-width: 639.98px) {
  .site-filters__range .gl-input { min-width: calc(4ch + 20px); -moz-appearance: textfield; appearance: textfield; }
  .site-filters__range .gl-input::-webkit-inner-spin-button, .site-filters__range .gl-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
}
.site-filters__range span { color: var(--gl-faint); font-size: 12px; }
/* A corner in the Stacks editor: its fields, then the line that says what the filter means. */
.site-corner { padding: 12px 0 14px; }
.site-corner:first-child { padding-top: 0; }
/* The masthead has to fit a phone. The brand, the account name and the Menu button together
   needed 378px — and the account name grows with the name, so a long one pushed Menu off the
   screen even on a 393px iPhone and the page could not be shrunk to reach it. Below 420px the
   portrait stands in for the name (the menu still says it), and the wordmark gives way first. */
.site-account-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* No room for the whole name: its first letter, never a shortened "Me…" (owner, 2026-09-25: "just do M for
   those names, the first letter?"; AccountName). The whole name stays for screen readers and in the tooltip. */
.site-account-name--initial { flex: 0 0 auto; min-width: auto; overflow: visible; text-overflow: clip; }
@media (max-width: 419.98px) {
  .site-topbar .site-account-name { display: none; }
  .site-topbar .gl-topbar__brand { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* With the name hidden, the lantern, portrait and Menu never shrink: the wordmark alone gives way
     (a 0 floor here let them slide under one another, and Menu 3px past the edge at 320). */
  .site-topbar .gl-topbar__nav { min-width: auto; flex: 0 0 auto; }
  .site-topbar .site-account-link { padding-left: 6px; padding-right: 6px; }
}
/* At 320px the lantern, the portrait and Menu need their whole width: the wordmark alone gives way
   ("The Great Li…"), so Menu no longer runs 3px past the right edge. */
@media (max-width: 359.98px) {
  .site-topbar .gl-topbar__nav { flex-shrink: 0; }
}
/* A portrait goes where its name goes. */
.site-person .gl-character__portrait { cursor: pointer; }
/* No art of their own yet: the same circle, blank, that says so rather than a
   collapsed card or a broken image (owner review, 2026-09-19). */
.site-portrait-placeholder { display: inline-flex; align-items: center; justify-content: center; text-align: center; border-radius: 50%; background: var(--gl-panel-2); flex: 0 0 auto; }
.site-portrait-placeholder__text { font-size: 8px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--gl-faint); line-height: 1.4; padding: 0 10px; }
.site-friend__portrait { display: inline-flex; line-height: 0; border-radius: 50%; }
.site-friend__portrait:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 2px; }
/* Two settings blocks side by side on a desk, with a rule between; one under the other on a phone. */
.site-two-col { display: grid; gap: 20px; }
/* No rule between the columns (owner, 2026-09-25: "remove the border between them… remove it throughout"): the 32px gap parts them. */
@media (min-width: 900px) { .site-two-col { grid-template-columns: 1fr 1fr; gap: 32px; align-items: start; } .site-two-col > .site-two-col__full { grid-column: 1 / -1; } }
/* A stack of settings panels laid out two columns on a desk and one on a phone (owner
   item 12, 2026-09-21). CSS columns, not a grid row pair, so panels of different heights
   pack tight instead of leaving gaps under the shorter one. */
.site-panelcols { columns: 2 460px; column-gap: var(--gl-space-6); }
.site-panelcols > * { break-inside: avoid; margin: 0 0 var(--gl-space-4); }
.site-panelcols > .site-panelcols__full { column-span: all; }
/* The main column while the session is still being checked: nothing drawn, no jump when it resolves. */
.site-gate { min-height: 40vh; }
/* Two lookup boxes side by side on a desk — one to require or allow a tag, one to leave a tag out —
   and stacked on a phone. */
.site-pickers { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; flex: 1 1 460px; min-width: 0; }
.site-pickers .site-tagpicker { min-width: 0; }
/* The two ranges on the lookup row keep their own size; the lookups take what is left. */
.site-filters__field--picker > .site-filters__field { flex: 0 0 auto; }
.site-filters__field--picker > .site-filters__field .site-filters__range .gl-input { max-width: 68px; }
.site-filters__field--picker { flex-wrap: wrap; }
@media (max-width: 760px) { .site-pickers { grid-template-columns: 1fr; flex-basis: 100%; } .site-filters__field--picker > .site-filters__field { flex: 1 1 140px; } }
/* A filter that holds more than one value: a button that says what is chosen, and a panel of
   checkboxes under it. One tap per value, no modifier keys, the same on a phone as on a desk. */
.site-multi { position: relative; }
.site-multi__button { width: 100%; text-align: left; cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.site-multi__button::after { content: ''; flex: 0 0 auto; width: 12px; height: 8px; background: no-repeat center / 12px 8px url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23A294BE' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.site-multi__button > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.site-multi__panel { position: absolute; z-index: 24; top: calc(100% + 6px); left: 0; min-width: 100%; width: max(100%, 260px); max-width: min(92vw, 380px); background: var(--gl-shelf); border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); box-shadow: var(--gl-shadow-frame); padding: 8px; display: flex; flex-direction: column; gap: 8px; animation: gl-drop .14s ease both; }
.site-multi__search { width: 100%; }
.site-multi__list { max-height: min(46vh, 300px); overflow: hidden auto; overscroll-behavior: contain; display: flex; flex-direction: column; }
.site-multi__option { display: flex; align-items: center; gap: 10px; padding: 9px 6px; border-radius: 8px; cursor: pointer; font-size: 14px; line-height: 1.35; min-height: 44px; }
.site-multi__option:hover { background: var(--gl-rule-soft); }
.site-multi__option input { flex: 0 0 auto; width: 17px; height: 17px; }
.site-multi__optlabel { flex: 1 1 auto; overflow-wrap: anywhere; }
.site-multi__mode { display: flex; gap: 6px; flex-wrap: wrap; border-top: 1px solid var(--gl-rule-soft); padding-top: 8px; }
.site-multi__mode .site-tagpill[aria-checked='true'] { background: var(--gl-lamp); border-color: var(--gl-lamp); color: var(--gl-ink-on-lamp, #2a1d3f); font-weight: 600; }
.site-multi__note { margin: 0; padding-top: 6px; border-top: 1px solid var(--gl-rule-soft); font-size: 12.5px; color: var(--gl-muted); }
.site-multi__foot { display: flex; justify-content: space-between; gap: 8px; border-top: 1px solid var(--gl-rule-soft); padding-top: 6px; }
/* The last row of a lookup list: how far through the matches you are, and a way to fetch more. */
.site-suggest__more { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 14px; font-size: 12.5px; color: var(--gl-muted); border-top: 1px solid var(--gl-rule-soft); position: sticky; bottom: 0; background: var(--gl-shelf); }
.site-linkbtn { background: none; border: 0; padding: 4px 2px; font: inherit; color: var(--gl-lamp); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; min-height: 32px; }
.site-linkbtn:disabled { color: var(--gl-muted); cursor: default; text-decoration: none; }
.site-filters__field--picker { flex: 1 1 100%; flex-direction: row; align-items: flex-end; gap: 12px; padding-top: 4px; margin-top: 4px; }
.site-tagpicker { flex: 1 1 320px; display: flex; flex-direction: column; gap: 6px; }
.site-tagpicker .gl-input { width: 100%; }
.site-tagpicker__list { width: 100%; max-width: none; }
.site-tagpicker__item { cursor: default; }
.site-tagpicker__acts { display: inline-flex; gap: 4px; flex: 0 0 auto; }
.site-tagpicker__acts .site-tagpill { padding: 2px 9px; font-size: 11.5px; }
.site-saved { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.site-saved__label { flex: 0 0 auto; font-size: 12px; font-weight: 600; color: var(--gl-faint); margin-right: 4px; white-space: nowrap; }
/* A phone: the label sits over its row, as a field label does, and Save this search stays on the row with
   the saved pills (final visual review: it wrapped alone). */
@media (max-width: 639.98px) { .site-saved > .site-saved__label { flex-basis: 100%; margin-right: 0; } }
.site-pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; padding-top: 6px; }
.site-pager .gl-pagination { flex-wrap: wrap; }
.site-pager .gl-pagination__link { min-height: 36px; display: inline-flex; align-items: center; }
/* The one pagination bar (owner items 9/20/21, 2026-09-19): Parchment's look, Meeko's options.
   The controls line is one row where it fits. Owner, 2026-09-25 ("first and last for all
   pagination options"): First and Last are page links in the canon frame, the same 36px as
   Previous/Next and the numbers, at the bar's own 6px; where the row does not fit it WRAPS —
   it never scrolls inside its own bar (the spacing sweep's "390 pager scrolls inside its bar"). */
.site-pagerbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; margin-top: 12px; padding-top: 6px; }
.site-pagerbar__count { margin: 0; }
.site-pagerbar__controls { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; max-width: 100%; min-width: 0; padding-bottom: 2px; }
.site-pagerbar__controls .gl-pagination { flex-wrap: wrap; row-gap: 6px; margin: 0; }
.site-pagerbar__controls .gl-pagination__link { min-height: 36px; display: inline-flex; align-items: center; }
/* Buttons (they are actions, not addresses) dressed exactly as the page links beside them: the
   links' colour comes from the DS's `.gl-root a` rule, so the buttons take the same tokens. */
.site-pagerbar__end { font-family: inherit; line-height: inherit; background: transparent; white-space: nowrap; color: var(--gl-link); }
.site-pagerbar__end:hover:not(:disabled) { color: var(--gl-link-hover); }
.site-pagerbar__end:disabled { opacity: .5; cursor: not-allowed; color: var(--gl-muted); }
.site-pagerbar__end:disabled:hover { background: transparent; }
/* A phone (owner, 2026-09-25: "never let small things like that happen, multiple lines or off
   screen"): with a count, the count and Per page share the first line, the numbered window the
   next, First · Last · Page [ ] of N the last. */
@media (max-width: 639.98px) {
  .site-pagerbar__controls .gl-pagination { order: -1; flex: 1 0 100%; }
  /* Per page rides the count line (final visual review: "Per page [10]" alone on the last line); the
     page controls take the lines under it. */
  .site-pagerbar:has(> .site-pagerbar__count) > .site-pagerbar__size { order: 1; }
  .site-pagerbar:has(> .site-pagerbar__count) > .site-pagerbar__controls { order: 2; flex: 1 0 100%; }
  /* No count (every Friends list): First · Last · Page [ ] of N · Per page are too wide for one phone
     line, so the numbered window, then First · Last, then Page [ ] of N with Per page, which never
     drops onto a line by itself (candidate 2026-09-25: the polish pager left it alone there). */
  .site-pagerbar:not(:has(> .site-pagerbar__count)):has(> .site-pagerbar__size) { justify-content: flex-start; }
  .site-pagerbar:not(:has(> .site-pagerbar__count)):has(> .site-pagerbar__size) > .site-pagerbar__controls { display: contents; }
  .site-pagerbar:not(:has(> .site-pagerbar__count)):has(> .site-pagerbar__size) .site-pagerbar__first { order: 1; }
  .site-pagerbar:not(:has(> .site-pagerbar__count)):has(> .site-pagerbar__size) .site-pagerbar__last { order: 2; }
  .site-pagerbar:not(:has(> .site-pagerbar__count)):has(> .site-pagerbar__size):has(.site-pagerbar__goto)::after { content: ""; order: 3; flex: 1 0 100%; height: 0; margin-top: -8px; }
  .site-pagerbar:not(:has(> .site-pagerbar__count)):has(> .site-pagerbar__size) .site-pagerbar__goto { order: 4; }
  .site-pagerbar:not(:has(> .site-pagerbar__count)):has(> .site-pagerbar__size) > .site-pagerbar__size { order: 5; }}
/* 640 to 1023px (owner: "never let small things like that happen, multiple lines or off screen"): the
   controls line did not fit beside the count and Per page, so each of those sat alone on a line (and a
   long "of 1,234" pushed Page [ ] of N alone too). The phone's arrangement, one step wider: the count
   and Per page share the first line, the numbered window the next, First · Last · Page [ ] of N the
   last; with no Per page the count ends that last line; with no count Per page does. */
@media (min-width: 640px) and (max-width: 1023.98px) {
  .site-pagerbar { justify-content: flex-start; }
  .site-pagerbar > .site-pagerbar__controls { display: contents; }
  .site-pagerbar__controls .gl-pagination { order: 1; flex: 1 0 100%; }
  .site-pagerbar__first { order: 2; }
  .site-pagerbar__last { order: 3; }
  .site-pagerbar__goto { order: 4; }
  .site-pagerbar:has(> .site-pagerbar__size) > .site-pagerbar__count { order: 0; flex: 1 1 0; min-width: 0; }
  .site-pagerbar:has(> .site-pagerbar__count) > .site-pagerbar__size { order: 0; }
  .site-pagerbar:has(.site-pagerbar__first):not(:has(> .site-pagerbar__size)) > .site-pagerbar__count { order: 5; margin-left: auto; }
  .site-pagerbar:not(:has(> .site-pagerbar__count)) > .site-pagerbar__size { order: 5; margin-left: auto; }
}
/* Under 360px (checked from 320): a long count ("287 works · page 5 of 12") wraps beside Per page
   instead of pushing it onto a line by itself, and the numbered window's buttons tighten so Next
   stays on its line. */
@media (max-width: 359.98px) {
  .site-pagerbar:has(> .site-pagerbar__size) > .site-pagerbar__count { flex: 1 1 0; min-width: 0; }
  .site-pagerbar__controls .gl-pagination { column-gap: 4px; }
  .site-pagerbar__controls .gl-pagination__link { padding-left: 7px; padding-right: 7px; }
}
.site-pagerbar__goto { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.site-pagerbar__page { width: 58px; padding: 4px 6px; font-size: 12.5px; text-align: center; }
.site-pagerbar__size { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.site-pagerbar__size .gl-select { width: auto; padding: 4px 24px 4px 8px; font-size: 12.5px; min-height: 0; }
.site-active-filters { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
.site-tagbrowser { display: grid; gap: 16px; grid-template-columns: 220px minmax(0, 1fr); }
@media (max-width: 799.98px) { .site-tagbrowser { grid-template-columns: minmax(0, 1fr); } }
.site-tagbrowser__list { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.site-tagbrowser__kind { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 10px; border-radius: var(--gl-radius-sm); color: var(--gl-muted); background: none; border: 0; font: inherit; text-align: left; cursor: pointer; }
.site-tagbrowser__kind:hover { background: var(--gl-wash); color: var(--gl-text); }
.site-tagbrowser__kind[aria-current="true"] { box-shadow: inset 3px 0 0 var(--gl-lamp); color: var(--gl-text); }
.site-tagbrowser__count { font-size: 11.5px; color: var(--gl-faint); font-variant-numeric: tabular-nums; }
.site-tagcloud { display: flex; flex-wrap: wrap; gap: 7px; }
.site-tagcloud .site-tagpill__n { color: var(--gl-faint); font-size: 11px; }
.site-alpha { display: flex; flex-wrap: wrap; gap: 3px; }
.site-alpha button { min-width: 28px; padding: 4px 6px; border: 1px solid transparent; border-radius: var(--gl-radius-sm); background: none; color: var(--gl-muted); font: inherit; font-size: 12.5px; cursor: pointer; }
.site-alpha button:hover { background: var(--gl-wash); color: var(--gl-text); }
.site-alpha button[aria-pressed="true"] { border-color: var(--gl-lamp); color: var(--gl-lamp); }

/* --- work detail --------------------------------------------------------- */
.site-work { display: flex; flex-wrap: wrap; gap: 32px; }
.site-work__aside { flex: 0 0 232px; min-width: 0; display: flex; flex-direction: column; gap: 14px; }
.site-work__cover { width: 232px; max-width: 100%; aspect-ratio: 2 / 3; object-fit: cover; border-radius: 8px; box-shadow: var(--gl-shadow-frame); background: var(--gl-panel-2); }
/* Tablet pass (owner, 2026-09-25: "test the reader on a tablet size?"): a work with no cover drew its
   placeholder at .site-nocover's height: 100% of the aside, which beside a long summary is the whole body
   column — Start reading sat 1,790px down an iPad mini (1,533px at 1440). It keeps the cover's 2:3. */
.site-work__cover.site-nocover { height: auto; }
.site-work__body { flex: 1 1 380px; min-width: 0; display: flex; flex-direction: column; gap: var(--gl-space-5); }
.site-work__head { display: flex; flex-direction: column; gap: 10px; }
.site-work__byline { margin: 0; font-size: 16px; color: var(--gl-muted); }
.site-work__badges { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 2px; align-self: stretch; }
/* The one length badge (words · time · chapters · pages), in a box that carries its spelled-out tooltip. */
.site-work__length { display: inline-flex; }
/* A line end chosen so no badge is left alone on a line (WordBadges, lib/no-lone-row.ts noLoneBreaks); gone while the row is measured. */
.site-linebreak { flex: 0 0 100%; height: 0; margin: 0; padding: 0; }
.site-linebreaks--measuring > .site-linebreak { display: none; }
.site-badge-link { display: inline-flex; border-radius: var(--gl-radius-pill); }
.site-badge-link:hover .gl-badge { border-color: var(--gl-rule-strong); }
/* Next in series (owner, 2026-09-25: "the left sidebar should not get huge for long series, have the
   series name go ... with the number for longer ones?"): one line; the title gives way with "…" and
   "Next in series:" and "(#N)" stay whole on either side of it. */
.site-work__next-series { gap: 0; white-space: nowrap; min-width: 0; }
.site-work__next-series-lead, .site-work__next-series-num { flex: 0 0 auto; }
.site-work__next-series-title { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.site-work__take { display: flex; flex-direction: column; gap: 8px; padding-top: 4px; }
.site-work__take a, .site-work__take button { font-size: 13.5px; text-align: left; }
.site-chapters { display: flex; flex-direction: column; border-top: 1px solid var(--gl-rule-soft); }
.site-chapters__row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; padding: 12px 4px; color: var(--gl-text); }
.site-chapters__row:hover, .site-chapters__row:focus-visible { background: var(--gl-wash); }
.site-chapters__n { flex: 0 0 34px; font-family: var(--gl-display); font-weight: 600; font-size: 15px; color: var(--gl-lamp); font-variant-numeric: tabular-nums; }
.site-chapters__title { flex: 1 1 0; min-width: 0; font-size: 14.5px; }
.site-chapters__words { flex: 0 0 auto; font-size: 12px; color: var(--gl-faint); font-variant-numeric: tabular-nums; }
.site-tags { display: flex; flex-wrap: wrap; gap: 7px; }
.site-taggroup { display: flex; flex-direction: column; gap: 6px; }
/* One column until two columns actually fit, which is NOT the shared 640px
   breakpoint.

   The two-column work layout needs the aside's basis, the gap and the body's
   basis side by side: 232 + 32 + 380 = 644px of container. `.gl-page` takes
   24px of gutter each side, so that is a 692px viewport. Between 640px and
   692px the flex line wrapped instead: the aside kept its rigid 232px basis and
   sat alone on its own row with the body pushed below it.

   Measured on the work page at a 648px viewport before this rule: container
   600px, aside 232px at top 219 with 368px of empty space beside it, body 600px
   at top 962. The cover, the actions and the Listen panel were all squeezed
   into a third of the width while two thirds of the row stood empty.

   So this boundary is the layout's real minimum, not a round number, and it is
   deliberately not 639.98px. The other 639.98px queries in this file govern the
   topbar and navigation, which have their own minimum and are left alone. */
@media (max-width: 691.98px) {
  /* Mobile: cover, header and primary actions centred; summary, metadata and tags stay left-aligned. */
  .site-work { gap: 22px; }
  .site-work__aside { flex: 1 1 100%; align-items: center; text-align: center; }
  .site-work__aside .gl-btn { max-width: 320px; }
  .site-work__take { align-items: center; width: 100%; max-width: 320px; }
  .site-work__take a, .site-work__take button { text-align: center; }
  .site-work__head { align-items: center; text-align: center; }
  .site-work__badges { justify-content: center; }
  .site-work__body { flex-basis: 100%; }
  .site-work__section { text-align: left; }
}

/* --- residents ----------------------------------------------------------- */
.site-residents { display: grid; gap: var(--gl-space-4); grid-template-columns: 1fr; }
@media (min-width: 900px) { .site-residents { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.site-residents .gl-character { align-items: flex-start; height: 100%; }
/* Cards in a row share a height; the action row sits at the bottom of every card, so buttons line up whatever the bio's length. */
.site-residents .gl-character > div { display: flex; flex-direction: column; align-self: stretch; flex: 1 1 auto; min-width: 0; }
.site-residents .gl-character__body { display: flex; flex-direction: column; flex: 1 1 auto; }
.site-residents .site-picks { margin-top: auto; padding-top: 16px; }
/* No orphan card (owner, Task D5): in the two-column layout the last card of an odd count takes the whole row, like the Infinite Stacks index. */
/* Odd counts are handled in the page itself (owner, 2026-09-18): a non-member staff card is left off rather than stretched or filled. */
/* Residents' Works (Kemii's request, 2026-09-19): one column on a phone, two from 640px, three from 1024px — fixed columns, so the
   page can count the slots a row is short and fill each with a Coming soon tile rather than leave a gap at any width. */
.site-works-grid { display: grid; gap: var(--gl-space-3); grid-template-columns: 1fr; }
@media (min-width: 640px) { .site-works-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .site-works-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.site-writer { display: flex; align-items: center; gap: 12px; margin: 0 0 12px; }
.site-writer img, .site-writer .site-portrait-placeholder { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; background: var(--gl-panel-2); box-shadow: 0 0 0 2px var(--gl-panel), 0 0 0 3px var(--gl-rule); }
.site-writer__name { margin: 0; font-family: var(--gl-display); font-weight: 600; font-size: 17px; line-height: 1.2; }
.site-writer__note { margin: 2px 0 0; font-size: 12.5px; color: var(--gl-faint); }
.site-writer + .site-works-grid { margin-bottom: var(--gl-space-5); }
.site-tile--soon { border-style: dashed; align-items: center; justify-content: center; text-align: center; min-height: 106px; color: var(--gl-faint); }
.site-tile--soon .site-tile__title { color: var(--gl-muted); }
/* Recruiting card on the Residents page (owner item 6): the same dashed tile, a full-height slot in the two-column grid. */
.site-residents .site-recruit { flex-direction: column; gap: 10px; padding: 28px 24px; min-height: 100%; align-items: center; justify-content: center; text-align: center; }
.site-residents .site-recruit img { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; opacity: .8; }
.site-residents .site-recruit .site-tile__note { max-width: 36ch; font-size: 13px; line-height: 1.55; }
/* Members' Works under Catalogue (owner, 2026-09-21): one shelf card per writing
   member — two columns on a desktop, one on a phone. An odd count gets the
   dashed recruiting card to complete the row. */
.site-member-shelves { display: grid; gap: var(--gl-space-4); grid-template-columns: 1fr; }
@media (min-width: 960px) { .site-member-shelves { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.site-member-shelf { min-width: 0; }
.site-member-shelf__head { min-width: 0; }
.site-member-shelf__head .site-badge-link { vertical-align: -1px; }
/* Inside a card the work tiles size themselves to the space, so a shelf stays tidy at every grid width. */
.site-works-grid--shelf { grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); }
.site-member-shelf--recruit { border-style: dashed; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; color: var(--gl-faint); }
/* Friends and inbox member-card lists (owner punch list 12, 2026-09-22): the
   portrait + name + action rows are self-contained cards — two columns on a
   desktop, one on a phone. The opened comparison and its note always take the
   full width, and the top grid row carries no separating rule. */
.site-friendgrid { display: grid; gap: 0 var(--gl-space-4); grid-template-columns: 1fr; }
.site-friendgrid > .site-compare, .site-friendgrid > .site-friend__note { grid-column: 1 / -1; }
@media (min-width: 960px) {
  .site-friendgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* No lines between the cards on any Friends tab (owner's standing rule; 2026-09-25 "weird here"). */
.site-friendgrid > .site-friend ~ .site-friend { border-top: 0; }
/* Settings › Residents & staff editor. */
.site-resident-editor { gap: 12px; }
.site-resident-editor .gl-h3 { font-size: 18px; margin: 0; }
/* Settings › Reading speed: the two speed panels side by side from 900px, one column below (owner, Task D4). */
.site-speed-columns { display: grid; gap: var(--gl-space-4); grid-template-columns: 1fr; align-items: stretch; }
@media (min-width: 900px) { .site-speed-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* The playback-speed stepper: the value reads as a multiplier between its two buttons. */
.site-stepper { display: inline-flex; align-items: center; gap: 8px; }
.site-stepper__value { min-width: 4.5ch; text-align: center; font-family: var(--gl-font-display, inherit); font-size: 20px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* Magic Shelves index: two columns from 640px. An odd count no longer widens the last shelf: the page fills the empty
   slot with a Coming soon tile, like every other grid (owner, 2026-09-24: "if there's an uneven number it does the coming soon"). */
.site-magic-grid { display: grid; gap: var(--gl-space-4); grid-template-columns: 1fr; }
@media (min-width: 640px) { .site-magic-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.site-magic-grid > .site-tile--soon { min-height: 160px; display: flex; }
/* Administrator settings > Appearance: the banner a slot currently shows, small. */
.site-appearance-banner { width: 240px; max-width: 100%; height: 72px; object-fit: cover; border-radius: var(--gl-radius-sm); box-shadow: 0 0 0 1px var(--gl-rule); display: block; }
.site-picks { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 16px; margin-top: 16px; }
.site-picks img { width: 36px; height: 54px; object-fit: cover; border-radius: 3px; box-shadow: var(--gl-shadow-card); display: block; background: var(--gl-panel-2); }
.site-picks > a + a { margin-left: 2px; }
.site-picks .gl-btn { margin-left: auto; }
@media (max-width: 479.98px) {
  .site-picks { gap: 10px 12px; }
  .site-picks img { width: 32px; height: 48px; }
  .site-picks .site-kicker { flex-basis: 100%; }
  .site-picks .gl-btn { margin-left: 0; flex-basis: 100%; }
}
.site-onduty { display: flex; flex-wrap: wrap; gap: 6px; }
.site-onduty img, .site-onduty .site-portrait-placeholder { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 2px rgba(227, 184, 92, .5); }
/* Staff on the banners: portrait, post, name and a line, inside the hero card. */
.site-staffnote { display: flex; align-items: flex-start; gap: 12px; }
.site-staffnote__portrait { width: 48px; height: 48px; border-radius: 50%; flex: 0 0 auto; object-fit: cover; background: rgba(11, 7, 20, .6); box-shadow: 0 0 0 2px rgba(227, 184, 92, .6); }
.site-staffnote__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.site-staffnote__name { margin: 0; font-family: var(--gl-display); font-weight: 600; font-size: 17px; line-height: 1.2; color: #FAF7FC; }
.site-staffnote__text { margin: 2px 0 0; font-size: 12.5px; line-height: 1.5; color: #E4DBF2; text-wrap: pretty; }
.site-staffnote__link { margin-top: 4px; font-size: 12px; font-weight: 600; color: #E3B85C; }
/* Cliff takes a request (owner, 2026-09-25, Part 3 item 3): "have the box go all the way across and maybe
   two lines", the button below it. The form is the hero card's own row under Cliff, so the box spans the
   card; the card keeps its size class. 16px on a phone so iOS does not zoom into the box. */
.site-cliff-find { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 0; }
.site-cliff-find .gl-textarea { width: 100%; min-width: 0; box-sizing: border-box; resize: vertical; font-size: 13.5px; line-height: 1.45; padding: 8px 12px; }
@media (pointer: coarse) and (max-width: 1035.98px), (max-width: 639.98px) { .site-cliff-find .gl-textarea { font-size: 16px; } }
/* Owner, on Cliff's Find a Fic card (phone home banner): "Can we make the box not have the… clickable
   looking weird border for the text? Otherwise a little more padding above the button and good".
   The box reads as part of Cliff's card: no hard border (the 1px stays, transparent, so nothing
   moves), a soft fill in the card's own colours (the card is dark in both themes), and a gold ring
   only while it has focus. The Find a Fic button sits 8px further down at every width. */
.gl-root .site-cliff-find .gl-textarea { background: rgba(250, 247, 252, .08); border-color: transparent; color: #FAF7FC; box-shadow: none; }
.gl-root .site-cliff-find .gl-textarea::placeholder { color: rgba(250, 247, 252, .55); }
.gl-root .site-cliff-find .gl-textarea:focus { border-color: transparent; box-shadow: 0 0 0 2px rgba(227, 184, 92, .7); outline: none; }
.site-cliff-find > .gl-btn { margin-top: 8px; }
/* A phone: Find a Fic takes the full width under its box, as Search does above it (final visual review). */
@media (max-width: 639.98px) { .site-cliff-find > .gl-btn { align-self: stretch; } }
.site-hero__card-rule { width: 100%; border: 0; margin: 2px 0; }
.site-resident-head { display: flex; flex-wrap: wrap; gap: 20px; align-items: center; }
.site-resident-head img, .site-resident-head .site-portrait-placeholder { width: 96px; height: 96px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 3px var(--gl-char, var(--gl-amethyst)); }
.site-ranked { display: flex; flex-direction: column; gap: 10px; }
.site-ranked__row { display: flex; gap: 12px; align-items: center; }
.site-ranked__n { flex: 0 0 28px; font-family: var(--gl-display); color: var(--gl-lamp); font-size: 18px; text-align: right; }
.site-tile__rank { flex: 0 0 auto; min-width: 16px; align-self: center; }

/* --- calendar ------------------------------------------------------------ */
.site-cal { padding: 14px; }
.site-cal__head { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; margin-bottom: 6px; }
.site-cal__head span { text-align: center; font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--gl-faint); }
.site-cal__grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.site-cal__cell { display: flex; flex-direction: column; align-items: center; gap: 4px; min-height: 68px; padding: 5px 2px; border: 1px solid var(--gl-rule-soft); border-radius: var(--gl-radius-sm); font-size: 12.5px; color: var(--gl-faint); font-variant-numeric: tabular-nums; background: none; font-family: inherit; }
.site-cal__cell--blank { border-color: transparent; }
.site-cal__cell--works { border-color: var(--gl-rule); background: var(--gl-panel); color: var(--gl-text); cursor: pointer; }
.site-cal__cell--works:hover { border-color: var(--gl-rule-strong); }
.site-cal__cell--today { border: 2px solid var(--gl-lamp); }
.site-cal__cell[aria-pressed="true"] { border: 2px solid var(--gl-amethyst); background: var(--gl-wash); }
.site-cal__count { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; padding: 1px 5px; border-radius: var(--gl-radius-pill); background: var(--gl-wash); font-size: 10.5px; font-weight: 700; color: var(--gl-amethyst); font-variant-numeric: tabular-nums; }
.site-cal__month { margin: 0; flex: 1 1 auto; font-family: var(--gl-display); font-size: 19px; color: var(--gl-text); white-space: nowrap; }
@media (max-width: 639.98px) {
  .site-cal { padding: 10px; }
  .site-cal__head, .site-cal__grid { gap: 4px; }
  .site-cal__cell { min-height: 46px; font-size: 11.5px; }
}
@media (min-width: 640px) and (max-width: 1023.98px) { .site-cal__cell { min-height: 58px; } }

/* --- shelves / bookmarks / magic ---------------------------------------- */
.site-shelfcard { display: flex; gap: 14px; padding: 16px; color: var(--gl-text); align-items: center; }
.site-shelfcard:hover, .site-shelfcard:focus-within { border-color: var(--gl-rule-strong); }
.site-shelfcard__covers { display: flex; flex: 0 0 auto; }
.site-shelfcard__covers img { width: 34px; height: 51px; object-fit: cover; border-radius: 3px; box-shadow: var(--gl-shadow-card); margin-left: -12px; background: var(--gl-panel-2); }
.site-shelfcard__covers img:first-child { margin-left: 0; }
.site-shelfcard__covers .site-shelfcard__empty { width: 34px; height: 51px; border-radius: 3px; border: 1px dashed var(--gl-rule); }
.site-magic-section { display: flex; flex-direction: column; gap: 10px; }
.site-magic-head { display: flex; align-items: center; gap: 12px; }
.site-magic-icon { width: 44px; height: 44px; border-radius: 10px; object-fit: contain; background: var(--gl-panel-2); box-shadow: 0 0 0 1px var(--gl-rule); flex: 0 0 auto; }
.site-magic-icon--hero { width: 96px; height: 96px; border-radius: 14px; background: rgba(11, 7, 20, .55); box-shadow: 0 0 0 1px rgba(227, 184, 92, .38); }
.site-magic-icon--title { width: 56px; height: 56px; border-radius: 12px; }
/* A page mark beside a heading, for pages without a banner. */
.site-titled { display: flex; align-items: flex-start; gap: 16px; }
.site-titled > .site-magic-icon { margin-top: 4px; }
.site-hero__card:has(.site-magic-icon--hero) { flex: 0 0 auto; padding: 10px; }
.site-magic-tree { display: flex; flex-direction: column; gap: 4px; padding-left: 4px; }
.site-magic-tree a { display: flex; justify-content: space-between; gap: 10px; padding: 8px 10px; border-radius: var(--gl-radius-sm); color: var(--gl-text); font-size: 14px; }
.site-magic-tree a:hover { background: var(--gl-wash); }
.site-magic-tree a .site-faint { font-variant-numeric: tabular-nums; }
/* The Stacks page load (owner, 2026-09-25: "it opened all the outers and then.... glitched the rest in"):
   the corner tree is drawn with the shelf, and only the counts arrive later, into a slot the height of the
   line and about as wide as a count, so no panel and no row changes size when they land. The first skeleton panels
   use the same head, line and tree shapes as a real shelf. */
.site-magic-count { display: inline-flex; align-items: center; justify-content: flex-end; flex: 0 0 auto; }
.site-magic-count__skel { display: inline-block; width: 2ch; height: 0.8em; min-height: 0; border-radius: 3px; }
.site-magic-section--skel .site-magic-icon { display: inline-block; width: 44px; height: 44px; flex: 0 0 auto; border-radius: var(--gl-radius-sm); }
.site-magic-section--skel .site-skel--line { display: block; flex: 0 0 auto; }
.site-magic-tree__skel { display: flex; align-items: center; padding: 8px 10px; min-height: calc(14px * 1.5 + 16px); }

/* --- tracker ------------------------------------------------------------- */
.site-tracker-new { border-bottom: 0; padding-top: 20px; gap: 14px; }
/* A phone: Status takes the room left beside Chapters read, so Add fits on that line instead of wrapping
   alone (final visual review). With Read on showing, Read on and Add share the next line. */
@media (max-width: 639.98px) { .site-tracker-new .site-form-row > .site-fieldstack:has(> #add-status) { flex: 1 1 0; min-width: 120px; } .site-tracker-new #add-status { width: 100%; } }
.site-tracker-row { display: flex; gap: 18px; padding: 22px 6px; align-items: flex-start; }
.site-tracker-row__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
/* P-12/P-34: one control line per work wherever it fits (the row is at its full 1132px from 1180px
   up, and the line fits there with no scroll). A date never drops away from its "today" (P-49).
   Every control stays on the line; only Started/Finished and the notes live in the row's panel (W3).
   Narrower than that, /tracker's line is the fixed grid below (--grid, owner 2026-09-25), never a
   sideways scroll. */
.site-tracker-row__controls { display: flex; flex-wrap: nowrap; gap: 6px 8px; align-items: center; padding-top: 2px; padding-bottom: 2px; min-width: 0; overflow-x: auto; }
.site-tracker-row__controls > * { flex: 0 0 auto; }
.site-tracker-row__controls .site-label { margin-right: -6px; }
.site-tracker-row__pair { display: inline-flex; align-items: center; gap: 4px; }
.site-tracker-row__pair .site-label { margin-right: 0; }
/* "of N" never breaks into "of" / "N" (final visual review, work page at 390). */
.site-tracker-row__pair > .site-num { white-space: nowrap; flex: 0 0 auto; }
/* The work page's number boxes hold four digits beside the spin arrows (final visual review: 265 showed as "26"). */
.site-tracker-row__controls.site-tracker-row__controls--wrap .site-medium-pace .gl-input { width: calc(4ch + 44px); max-width: none; }
@media (min-width: 640px) { .site-tracker-row__controls--wrap .site-tracker-row__pair .gl-input { width: calc(4ch + 44px); max-width: none; } }
/* The field labels stay in the accessibility tree but off the line — each control already reads
   on its own (the status select, the count beside "of N", the date). */
.site-tracker-row__controls .site-tracker-row__tag { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.site-tracker-row__controls .site-tracker-row__count { width: 64px; max-width: 64px; }
.site-tracker-row__controls .gl-input.site-tracker-row__date { width: 132px; max-width: 132px; }
/* One-tap "today" beside a date control — small enough to live on the tight tracker line. */
.site-today-btn { padding-inline: 8px; min-height: 30px; font-size: var(--gl-size-micro); }
/* Owner, 2026-09-24: "the button falls to the next row on some" — measured in Chromium at 1440, the line with +1
   and "Add notes" ran 9px past the card; the status and the count give up 18px so every row fits with no scroll. */
.site-tracker-row__controls .gl-select.site-tracker-row__status { max-width: 140px; }
.site-tracker-row__follow { display: inline-flex; align-items: center; gap: 6px; min-height: 38px; }
/* Waiting for updates (Part 3 item 6): Include To Be Read, beside the All / Tracked only / Followed choice. */
.site-waiting__tbr { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; margin-left: 8px; }
/* A date field and its one-tap "today": one unit that never wraps apart (P-49). */
.site-date-today { display: flex; flex-wrap: nowrap; gap: 6px; align-items: center; }
.site-date-today .gl-input { min-width: 0; }
.site-tracker-row__ending { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 14px; padding: 10px 12px; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-md, 10px); background: var(--gl-wash); }
/* The reading log's per-work line: stars, the review as text, one small button; the box only when asked. */
.site-log-take { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; padding: 2px 4px 10px; }
/* In the reading log the stars and review ride the entry line itself
   (BUG-092): no drop, no hanging padding — the row wraps them under the title
   only when the line genuinely runs out of room. */
.site-row > .site-log-take { flex: 1 1 260px; justify-content: flex-end; padding: 0; }
/* P-13: the date follows the stars, last on the line, and keeps to the right edge even when it
   wraps onto a line of its own — the log's dates read as one column. */
.site-row > .site-log-take ~ .site-row__when { margin-left: auto; }
/* A phone: the stars and notes share their line with the date, which no longer drops to a line of its own (final visual review, Reading log at 390). */
@media (max-width: 639.98px) { .site-row > .site-log-take { flex: 0 1 calc(100% - 100px); min-width: 0; } }
.site-log-take__text { flex: 1 1 240px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A reviewed entry keeps its stars, its review and Notes on one line at every width, the review cut
   short with an ellipsis (Notes opens it whole). It used to wrap: the stars alone on a line above the
   review, or, with the tighter stars, Notes alone on a line below (owner, 2026-09-25: "never let small
   things like that happen, multiple lines"). */
.site-log-take:has(> .site-log-take__text) { flex-wrap: nowrap; min-width: 0; }
.site-log-take:has(> .site-log-take__text) > .site-log-take__text { flex: 1 1 0; }
.site-log-take:has(> .site-log-take__text) > :not(.site-log-take__text) { flex: 0 0 auto; }
/* On a narrow phone the line has no room left for the review beside the stars and Notes, so the review
   takes a whole line under them, never a sliver. */
@media (max-width: 479.98px) {
  .site-log-take:has(> .site-log-take__text) { flex-wrap: wrap; }
  .site-log-take:has(> .site-log-take__text) > .site-log-take__text { order: 2; flex: 1 0 100%; }
}
/* At 320–359 the stars, "Add notes" and the date are up to 16px too wide for one line and "Add notes" dropped
   under the stars: the line's gaps and the button's sides give up those pixels, nothing else changes. */
@media (max-width: 359.98px) {
  .site-row > .site-log-take { flex-basis: calc(100% - 98px); column-gap: 6px; }
  .site-row > .site-log-take > .gl-btn { padding-inline: 6px; }
}
.site-log-take__edit { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; flex: 1 1 320px; }
.site-log-take__edit .gl-input { flex: 1 1 220px; min-width: 0; }
/* The relationship control on a resident card: one row of its own at the foot, full width, never inline with the picks. */
/* Owner, 2026-09-25, on the lines between a card's parts: "that border between looks odd in every page, remove it throughout"
   and "The weird bottom borders are still in residents". Card and panel subsections keep their gap (padding/margin) but draw
   no rule: the resident card's friend row, TBR card actions/OPDS, the work page's "Take it with you", the catalogue picker row,
   tracker "Add a work", Settings › Reading, the stats hero card, Admin corners/staff notes/lookup/diagnostics. Card outlines,
   list-row separators, tables and inputs keep theirs. */
.site-person__relationship { margin-top: 12px; padding-top: 12px; display: flex; }
.site-person__relationship > .site-friendstate { width: 100%; justify-content: space-between; }
.site-person__relationship > .gl-btn { flex: 0 0 auto; }
.site-person__relationship .site-friendstate .gl-btn { flex: 0 0 auto; }
/* Options bars are one row, canon-wide (owner item 6, 2026-09-19): the section pills and any
   other options bar scroll sideways on a narrow screen instead of wrapping to two rows. */
.site-tracker-nav { display: flex; flex-wrap: nowrap; gap: 6px; overflow-x: auto; padding-bottom: 2px; }
.site-tracker-nav .site-tagpill { flex: 0 0 auto; white-space: nowrap; }
.site-log-toggle { display: inline-flex; align-items: baseline; gap: 8px; background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; text-align: left; }
.site-log-toggle:hover { color: var(--gl-link-hover); }
.site-log-toggle__caret { color: var(--gl-muted); font-size: 12px; }
.site-tracker-row__controls .gl-select, .site-tracker-row__controls .gl-input { width: auto; }
.site-tracker-row__controls .gl-input { max-width: 76px; }
/* The WORK PAGE's tracker panel (routes/Work.tsx) keeps the candidate's shape: a wrapping line at desk widths
   and the label/field grid on a phone. Owner item 1 (2026-09-24) was about Tracked works, so the one-line
   rule above is /tracker's; this modifier hands the work page back exactly what it had. */
.site-tracker-row__controls--wrap { flex-wrap: wrap; overflow: visible; padding-bottom: 0; }
/* The candidate had no fixed-size rule on the work page's items: the pace note shrinks and wraps as it did (measured at 768: it ran to 929px under the /tracker rule). */
.site-tracker-row__controls--wrap > * { flex: 0 1 auto; min-width: 0; }
@media (max-width: 639.98px) {
  .site-tracker-row__controls--wrap {
    display: grid;
    grid-template-columns: max-content 1fr;
    align-items: center;
    gap: 8px 10px;
  }
  .site-tracker-row__controls--wrap > .site-tracker-row__pair { display: contents; }
  .site-tracker-row__controls--wrap > .site-label { grid-column: 1; justify-self: start; margin-right: 0; }
  .site-tracker-row__controls--wrap > :not(.site-label):not(.site-tracker-row__pair) { grid-column: 2; justify-self: start; }
  .site-tracker-row__controls--wrap .site-tracker-row__pair > .site-label { grid-column: 1; }
  .site-tracker-row__controls--wrap .site-tracker-row__pair > :not(.site-label) { grid-column: 2; }
  .site-tracker-row__controls--wrap .gl-select,
  .site-tracker-row__controls--wrap .gl-input { width: 100%; max-width: 100%; }
  .site-tracker-row__controls--wrap > .site-stars,
  .site-tracker-row__controls--wrap > label:not(.site-label),
  .site-tracker-row__controls--wrap > button { grid-column: 1 / -1; justify-self: start; }
  .site-tracker-row__controls--wrap .site-tracker-row__count, .site-tracker-row__controls--wrap .site-tracker-row__date { width: 100%; max-width: 100%; }
}

/*
 * THE TRACKER ROW ON A PHONE. Owner, 2026-09-17: "this layout is fucking AWFUL on
 * mobile now" — every item wrapped on its own, labels stranded from their fields.
 * A two-column grid below 640px followed, which turned each work into a screenful
 * of stacked fields and dropped "today" ~45px under its date (P-49). The later
 * rule (2026-09-19, P-12/P-34) made it one line that scrolled sideways, which the owner
 * called broken on her iPhone (2026-09-25); the control line's phone grid is the --grid
 * block below. This block keeps the row's own spacing and shape.
 */
@media (max-width: 639.98px) {
  .site-tracker-row { gap: 12px; padding: 18px 4px; }
  /* The control line at 390 (M3): the cover keeps beside the title and its bar, and the control
     line under both takes the row's full width, so the status and the chapter progress (Read N of
     M) sit together at its head; the rest of the controls follow in the --grid cells below. */
  .site-tracker-row { display: grid; grid-template-columns: 44px minmax(0, 1fr); column-gap: 12px; row-gap: 10px; }
  .site-tracker-row > a:first-child { grid-column: 1; grid-row: 1 / span 2; }
  .site-tracker-row__body { display: contents; }
  .site-tracker-row__body > * { grid-column: 1 / -1; }
  .site-tracker-row__body > :first-child, .site-tracker-row__body > .site-bar { grid-column: 2; }
  .site-tracker-row__body > .site-bar { align-self: start; }
  .site-tracker-row__ending { padding: 10px; }
  .site-tracker-row__ending .site-inline-actions { width: 100%; }
}

/*
 * THE /tracker CONTROL LINE ON A TABLET AND A PHONE. Owner, 2026-09-25 (beta 185be0a, iPhone):
 * "Wow that's awful lol, broken" — the one nowrap line ran off the right edge at 390 and scrolled
 * sideways, the day read cut off and Untrack out of sight. Earlier (2026-09-24): "Tracked works is
 * inconsistent as the button falls to the next row on some, can you make it look better?", and no
 * "More" menu (owner-layout Q6). So below 1180px — where the one line stops fitting — the same
 * controls, in the desktop order, sit in ONE fixed grid that is identical on every row:
 *   row 1: Status | Read [n] of N | (768) the day read and its today
 *   then the rest, each cell the same width as the others on its row, filling it. On a phone
 *   the day read has a row of its own: in half a row a date field cannot show a whole date.
 * Each control names its own cell, so a row with no +1 (read to the end) leaves that cell empty
 * instead of pulling Follow, the stars, Notes and Untrack out of line with the other rows.
 * The pairs stay units (a date keeps its "today", P-49); nothing is hidden, moved or removed.
 * 1180px and up is untouched: the one line above.
 */
@media (max-width: 1179.98px) {
  .site-tracker-row__controls.site-tracker-row__controls--grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-areas:
      "st st st st rd rd rd rd on on on on"
      "md md md pc pc pc nx nx nx fl fl fl"
      "sr sr sr sr nt nt nt nt ut ut ut ut";
    gap: 8px;
    align-items: center;
    overflow: visible;
    padding-bottom: 0;
  }
  .site-tracker-row__controls--grid > * { min-width: 0; }
  .site-tracker-row__controls--grid > .site-tracker-row__pair--status { grid-area: st; }
  .site-tracker-row__controls--grid > .site-tracker-row__pair--read { grid-area: rd; }
  .site-tracker-row__controls--grid > .site-tracker-row__pair--on { grid-area: on; }
  .site-tracker-row__controls--grid > .gl-select { grid-area: md; }
  .site-tracker-row__controls--grid > .site-medium-pace { grid-area: pc; }
  .site-tracker-row__controls--grid > .site-tracker-row__next { grid-area: nx; }
  .site-tracker-row__controls--grid > .site-tracker-row__follow { grid-area: fl; }
  .site-tracker-row__controls--grid > .site-tracker-row__stars { grid-area: sr; }
  .site-tracker-row__controls--grid > .site-tracker-row__notes { grid-area: nt; }
  .site-tracker-row__controls--grid > .site-tracker-row__untrack { grid-area: ut; }
  /* Every control fills its cell: the pairs as a flexible field + its fixed neighbour ("of N",
     "today", "wpm"), the selects and buttons edge to edge. */
  .site-tracker-row__controls--grid > .site-tracker-row__pair,
  .site-tracker-row__controls--grid > .site-medium-pace { display: flex; align-items: center; gap: 6px; }
  .site-tracker-row__controls.site-tracker-row__controls--grid .gl-select,
  .site-tracker-row__controls.site-tracker-row__controls--grid .gl-input { width: 100%; max-width: none; min-width: 0; flex: 1 1 auto; }
  .site-tracker-row__controls--grid .site-faint, .site-tracker-row__controls--grid .site-today-btn { flex: 0 0 auto; }
  /* A changed reading speed's Save wraps under the field inside its own cell, never past it. */
  .site-tracker-row__controls--grid > .site-medium-pace { flex-wrap: wrap; }
  .site-tracker-row__controls.site-tracker-row__controls--grid > .site-medium-pace .gl-input { flex: 1 1 0; min-width: 3em; }
  .site-tracker-row__controls--grid > .gl-btn { width: 100%; }
  .site-tracker-row__controls--grid > .site-tracker-row__follow,
  .site-tracker-row__controls--grid > .site-tracker-row__stars { justify-self: center; }
  /* On the narrowest phones (360) the five stars give up a pixel or two rather than spill out of their cell. */
  .site-tracker-row__controls--grid > .site-tracker-row__stars { max-width: 100%; }
  .site-tracker-row__controls--grid > .site-tracker-row__stars button { flex: 0 1 auto; min-width: 0; }
}
@media (max-width: 639.98px) {
  .site-tracker-row__controls.site-tracker-row__controls--grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    grid-template-areas:
      "st st st rd rd rd"
      "on on on on on on"
      "md md md pc pc pc"
      "nx nx fl fl sr sr"
      "nt nt nt ut ut ut";
  }
}
/*
 * A WIDE /tracker ROW THAT WOULD OVERFLOW GOES TO THE CARD LAYOUT. Owner, 2026-09-25, on the rows at
 * 1180px and up that ran ~25px past the line (set to Listened, whose speed list is wider than the wpm
 * box, or with a changed speed's Save showing): "go to the card layout". lib/fit-row.ts measures each
 * row and sets data-cards only when its one line would not fit; these are the phone rules above (the
 * row's grid, and the control line's six-column grid), keyed on that attribute instead of the width.
 * The row keeps its own padding and gap, so it lines up with the rows around it. A row that fits is
 * untouched.
 */
.site-tracker-row[data-cards] { display: grid; grid-template-columns: 44px minmax(0, 1fr); column-gap: 18px; row-gap: 10px; }
.site-tracker-row[data-cards] > a:first-child { grid-column: 1; grid-row: 1 / span 2; }
.site-tracker-row[data-cards] .site-tracker-row__body { display: contents; }
.site-tracker-row[data-cards] .site-tracker-row__body > * { grid-column: 1 / -1; }
.site-tracker-row[data-cards] .site-tracker-row__body > :first-child, .site-tracker-row[data-cards] .site-tracker-row__body > .site-bar { grid-column: 2; }
.site-tracker-row[data-cards] .site-tracker-row__body > .site-bar { align-self: start; }
.site-tracker-row[data-cards] .site-tracker-row__ending { padding: 10px; }
.site-tracker-row[data-cards] .site-tracker-row__ending .site-inline-actions { width: 100%; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  grid-template-areas:
    "st st st rd rd rd"
    "on on on on on on"
    "md md md pc pc pc"
    "nx nx fl fl sr sr"
    "nt nt nt ut ut ut";
  gap: 8px;
  align-items: center;
  overflow: visible;
  padding-bottom: 0;
}
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > * { min-width: 0; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__pair--status { grid-area: st; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__pair--read { grid-area: rd; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__pair--on { grid-area: on; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .gl-select { grid-area: md; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-medium-pace { grid-area: pc; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__next { grid-area: nx; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__follow { grid-area: fl; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__stars { grid-area: sr; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__notes { grid-area: nt; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__untrack { grid-area: ut; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__pair,
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-medium-pace { display: flex; align-items: center; gap: 6px; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid .gl-select,
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid .gl-input { width: 100%; max-width: none; min-width: 0; flex: 1 1 auto; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid .site-faint, .site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid .site-today-btn { flex: 0 0 auto; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-medium-pace { flex-wrap: wrap; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-medium-pace .gl-input { flex: 1 1 0; min-width: 3em; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .gl-btn { width: 100%; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__follow,
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__stars { justify-self: center; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__stars { max-width: 100%; }
.site-tracker-row[data-cards] .site-tracker-row__controls.site-tracker-row__controls--grid > .site-tracker-row__stars button { flex: 0 1 auto; min-width: 0; }
.site-chart { display: flex; flex-direction: column; gap: 14px; padding: 18px; }
.site-chart__bars { display: flex; align-items: flex-end; gap: 3px; height: 180px; }
.site-chart__bar { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.site-chart__bar span { width: 100%; min-height: 3px; border-radius: 3px 3px 0 0; background: var(--gl-amethyst); }
.site-chart__bar--now span { background: var(--gl-lamp); }
.site-chart__labels { display: flex; gap: 3px; }
.site-chart__labels span { flex: 1 1 auto; min-width: 0; text-align: center; font-size: 9.5px; color: var(--gl-faint); white-space: nowrap; overflow: hidden; }
.site-chart--monthly .site-chart__bars, .site-chart--monthly .site-chart__labels { gap: 6px; }
.site-mix { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--gl-rule-soft); }
.site-mix__key { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.site-mix__key span { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--gl-faint); white-space: nowrap; }
.site-mix__key i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
/* Owner, 2026-09-25: "tracked works, wip, anywhere with stars had too much padding on both sides". Every
   star row (Tracked works, Followed, the Reading log, the Notebook, the work page, the reader's end card)
   keeps its size and height but drops the space beside each star: 1px a side and no gap, where it had 2px
   a side plus a 2px gap (98px for five stars, now 80px). */
.site-stars { display: inline-flex; gap: 0; }
.site-stars button { background: none; border: 0; padding: 2px 1px; cursor: pointer; color: var(--gl-faint); font-size: 16px; line-height: 1; }
.site-stars button[aria-checked="true"] { color: var(--gl-lamp); }
/* Stars shown, not set: a Notebook page's rating (Part 3 item 10), the same marks as the row above. */
.site-stars--read > span { padding: 2px 1px; color: var(--gl-faint); font-size: 14px; line-height: 1; }
.site-stars--read > span[data-on="true"] { color: var(--gl-lamp); }
/* Notebooks (owner, Part 3 item 10): the approved mock's two-column grid of pages, the note in italic display type. */
.site-notebook { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr)); gap: 12px; }
.site-notebook__page { display: flex; gap: 14px; padding: 14px; align-items: flex-start; min-width: 0; }
.site-notebook__note { margin: 4px 0 0; font-family: var(--gl-display); font-style: italic; font-size: 15px; line-height: 1.45; color: var(--gl-text); }

/* --- settings ------------------------------------------------------------ */
.site-settings-head { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; }
/* The title block takes the room between the portrait and the medallion and wraps its own text, so a long
   lede (Administrator settings) keeps the medallion level with the title as on Account settings. */
.site-settings-head > div { flex: 1 1 280px; min-width: 0; }
.site-settings-head__icon { width: 72px; height: 72px; margin-left: auto; border-radius: 16px; object-fit: contain; background: var(--gl-panel-2); box-shadow: 0 0 0 1px var(--gl-rule); }
@media (max-width: 639.98px) { .site-settings-head__icon { display: none; } }
.site-avatar-grid { display: flex; flex-wrap: wrap; gap: 10px; }
.site-avatar-grid button { padding: 0; border: 0; background: none; border-radius: 50%; cursor: pointer; display: block; }
.site-avatar-grid img { width: 44px; height: 44px; border-radius: 50%; object-fit: contain; background: var(--gl-panel-2); box-shadow: 0 0 0 2px var(--gl-rule); display: block; }
.site-avatar-grid button[aria-pressed="true"] img { box-shadow: 0 0 0 3px var(--gl-lamp); }
.site-settings-nav { display: flex; flex-wrap: wrap; gap: 6px; }
/* On a phone the pills sit 4px apart, which is what lets Account settings' eight (Sign out included)
   pack 3/2/3 at 360px instead of leaving Sign out alone on the last line. */
@media (max-width: 419.98px) { .site-settings-nav { gap: 4px; } }
/* Below 360px the pills go compact, as the How To pills do there (smaller type and padding, the
   wording unchanged), so the eight still pack with none alone on a line at 320px. */
@media (max-width: 359.98px) {
  .site-settings-nav .site-tagpill { padding: 3px 8px; font-size: 11.5px; }
  .site-settings-nav .gl-btn { padding: 3px 8px; font-size: 11.5px; }
}
/* Owner, beta on her iPhone, 2026-09-25: "admin settings is weird"; then, checked on staging: Appearance
   › Home and Front door drew the whole page zoomed out (smaller text, the panel about 80% across, a
   dark band down the right) while Catalogue was fine. Safari on iPhone gives form controls 16px text
   (the D9 zoom rule below) and sizes a select by its longest option; "Library's own — Home — the lamplit
   hall…" is about 440px there, wider than the phone, the layout grew past the viewport and iOS shrank
   the page to fit. The form guard (Administrator settings, Account settings, Request a Fic, My Shelves;
   owner: "Yes to the fix"): no control, and no grid or flex step between a control and its panel, may
   be held wider than its column by its own contents. Each takes the column's width. Widths only. */
.site-formguard .gl-field > :is(.gl-select, .gl-input, .gl-textarea) { width: 100%; max-width: 100%; min-width: 0; }
.site-formguard :is(.gl-select, .gl-textarea) { max-width: 100%; min-width: 0; }
.site-formguard .site-grid > *,
.site-formguard .gl-field,
.site-formguard .site-fieldstack,
.site-formguard :is(.site-grid, .site-two-col, .site-speed-columns, .site-form-row, .site-sechead, .site-inline-actions, .site-cluster, .site-stack, .site-panelcols) > :has(.gl-select, .gl-input, .gl-textarea, .site-appearance-banner) { min-width: 0; }
.site-formguard :is(.site-appearance-banner, .site-magic-icon) { max-width: 100%; min-width: 0; }
@media (min-width: 900px) { .site-formguard .site-two-col { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.site-admin [data-testid^="appearance-"] .site-stack > .gl-btn { align-self: flex-start; }
/* A two-column term list (Diagnostics › How this Library is configured, Service keys › What each key
   may do): every value starts at one x, beside the widest term; on a phone each value sits under its
   term, so none hang at a different x from the rest. */
.site-deflist { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: baseline; gap: 6px 16px; margin: 0; }
.site-deflist--8 { row-gap: 8px; }
.site-deflist__row { display: contents; }
.site-deflist dt, .site-deflist dd { margin: 0; min-width: 0; }
.site-deflist dd { overflow-wrap: anywhere; }
@media (max-width: 639.98px) {
  .site-deflist { grid-template-columns: minmax(0, 1fr); row-gap: 2px; }
  .site-deflist dd + dt { margin-top: 8px; }
}
.site-member-table td, .site-member-table th { vertical-align: middle; }
/* Owner item 14, 2026-09-19: "the table is clipped on the left edge" — cells carry no left
   padding, so the first column sat flush on the wrap's edge and read as cut off. */
.site-member-table th:first-child, .site-member-table td:first-child { padding-left: 8px; }
/* Owner, on her phone: "No member remove and members settings is broken on mobile layout."
   Both symptoms were one cause. The table sits in .gl-tablewrap, which is overflow-x: auto,
   so on a narrow screen the whole table scrolled sideways and Actions — the last column —
   sat off the right edge. Remove was rendering the whole time and could not be reached.
   Below 640px each member becomes a labelled block instead, so every control is on screen
   and nothing scrolls sideways. Same breakpoint and the same tokens as the tracker rows. */
@media (max-width: 639.98px) {
  .site-member-table, .site-member-table tbody, .site-member-table tr, .site-member-table td { display: block; width: 100%; }
  .site-member-table thead { display: none; }
  .site-member-table tr {
    border: 1px solid var(--gl-rule);
    border-radius: var(--gl-radius-sm);
    background: var(--gl-panel);
    padding: 12px;
    margin-bottom: 12px;
  }
  .site-member-table td, .site-member-table tbody td, .site-member-table tbody tr ~ tr td { border: 0; padding: 6px 0; min-width: 0; overflow-wrap: anywhere; }
  /* Owner, beta on her iPhone, 2026-09-25 (Mail log): the first cell kept the wide table's 8px left
     padding, so the time sat 8px right of What/Who/Outcome, and the row's hover wash (sticky after a
     tap on iPhone) painted each cell, an inset box short of the card's edges. The newest (first) card
     also kept the DS cell padding (9px 12px 9px 0, whose selector outranked the fold's), so it was
     spaced unlike the rest. In the fold every cell starts at the card's padding, every card has the
     same cell spacing, and the wash fills the whole card. */
  .site-member-table th:first-child, .site-member-table td:first-child { padding-left: 0; }
  .site-member-table tbody tr:hover td { background: transparent; }
  .site-member-table tbody tr:hover { background-image: linear-gradient(var(--gl-wash), var(--gl-wash)); }
  .site-member-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 4px;
    font-size: 12px;
    color: var(--gl-muted);
  }
  .site-member-table select, .site-member-table .gl-input { width: 100%; }
  /* The members' email and "Writes as" boxes carry the wide table's inline 240px cap; in the fold
     they take the card's width like the role selects beside them. */
  .site-member-table .gl-input { max-width: none !important; }
  .site-member-table .site-inline-actions .gl-btn { flex: 1 1 auto; }
}
/* Owner, 2026-09-25, on the Members table at tablet width (768: the email box 72px, the role select
   cut off): "Fold into cards". Below 1036px the full table cannot show every row's controls whole
   (measured: the role select "Meeko (admin + resident)" is cut off at 1034px and fits at 1036px), so
   the Members table takes the same cards it uses on a phone. Only the Members table; the Mail log and
   the Service keys table fit at these widths and keep the 640px fold. */
/* The wide Members table: a row's actions two by two (Coach · Reset password / Disable · Remove), the coach
   line across both when it shows, so none is left alone on a line. */
@media (min-width: 1036px) {
  .site-member-actions { display: grid; grid-template-columns: auto auto; grid-auto-flow: row dense; justify-content: start; align-items: center; gap: 6px 8px; }
  .site-member-actions > input { grid-column: 1 / -1; }
}
@media (max-width: 1035.98px) {
  .site-member-table--members, .site-member-table--members tbody, .site-member-table--members tr, .site-member-table--members td { display: block; width: 100%; }
  .site-member-table--members thead { display: none; }
  .site-member-table--members tr {
    border: 1px solid var(--gl-rule);
    border-radius: var(--gl-radius-sm);
    background: var(--gl-panel);
    padding: 12px;
    margin-bottom: 12px;
  }
  .site-member-table--members td, .site-member-table--members tbody td, .site-member-table--members tbody tr ~ tr td { border: 0; padding: 6px 0; min-width: 0; overflow-wrap: anywhere; }
  .site-member-table--members th:first-child, .site-member-table--members td:first-child { padding-left: 0; }
  .site-member-table--members tbody tr:hover td { background: transparent; }
  .site-member-table--members tbody tr:hover { background-image: linear-gradient(var(--gl-wash), var(--gl-wash)); }
  .site-member-table--members td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 4px;
    font-size: 12px;
    color: var(--gl-muted);
  }
  .site-member-table--members select, .site-member-table--members .gl-input { width: 100%; }
  .site-member-table--members .gl-input { max-width: none !important; }
  .site-member-table--members .site-inline-actions .gl-btn { flex: 1 1 auto; }
}

/* --- auth pages ---------------------------------------------------------- */
.site-auth { max-width: 440px; margin: 0 auto; width: 100%; }
.site-auth .gl-input { width: 100%; }
.site-auth__brand { display: flex; align-items: center; gap: 12px; justify-content: center; margin-bottom: 8px; }
.site-auth__brand img { width: 52px; height: 52px; border-radius: 50%; object-fit: contain; background: var(--gl-panel-2); }

/* --- how to -------------------------------------------------------------- */
/* Owner, 2026-09-25: "remove the border between them… remove it throughout". The steps keep their
   16px padding as the gap; no rule under the jump pills (the DS's .gl-steps top edge) or between steps. */
.site-howto .gl-steps { margin-top: 6px; border-top: 0; }
/* How To: every on-screen control is simply bold, in the text colour — no boxes. */
.site-key { font-family: inherit; font-size: inherit; font-weight: 700; color: var(--gl-text); white-space: nowrap; }
.site-hero__lede .site-key { color: #FAF7FC; }
/* Coaches: a small gold line with the Library seal, on resident cards and profiles. */
.site-coach { display: inline-flex; align-items: center; gap: 6px; margin: 8px 0 0; font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--gl-lamp); }
.site-coach img, .site-member-head .site-coach img, .site-resident-head .site-coach img { width: 18px; height: 18px; border-radius: 4px; object-fit: cover; box-shadow: 0 0 0 1px rgba(227, 184, 92, .55); }
/* Inbox count in the navigation. */
.site-nav-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; margin-left: 6px; border-radius: 9px; background: var(--gl-lamp); color: #110B1D; font-size: 11px; font-weight: 700; line-height: 1; vertical-align: 1px; }
/* The Inbox entry's counts are one indicator (owner, 2026-09-25: "the icon for new messages showed
   twice"): kept together at the end of the row, never spread across it, and never wider than the menu
   already is. Messages keep the gold badge; the notices count is the quiet outline badge beside it. */
.site-nav-badges { display: inline-flex; flex: none; align-items: center; gap: 4px; margin-left: 6px; vertical-align: 1px; }
.site-nav-badges > .site-nav-badge { margin-left: 0; vertical-align: 0; }
.site-nav-badge.site-nav-badge--quiet, [data-gl-theme] .site-nav-badge.site-nav-badge--quiet { background: transparent; color: var(--gl-muted); box-shadow: inset 0 0 0 1px var(--gl-rule-strong); }
/* The Notion form keeps Notion's own colours: give it a paper frame so it reads as a form on the desk rather than a hole in the page. */
.site-embed-frame { padding: 10px; border-radius: var(--gl-radius-lg); background: #fff; border: 1px solid var(--gl-rule); }
.site-embed-frame .site-embed { display: block; border-radius: 10px; background: #fff; }
.site-howto strong { font-weight: 700; color: var(--gl-text); }
.site-howto__key-note { margin: 0 0 14px; font-size: 14px; color: var(--gl-muted); }
.site-howto__list { margin: 8px 0 0; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; }
/* Owner item 8, 2026-09-19: the walkthrough — a jump-nav of numbered pills, an anchor per
   step that clears the sticky header, and a live screenshot beside the words. */
/* Owner, 2026-09-25: "please never lone buttons". The rows are cut by components/JumpPills.tsx, each
   of at least two pills and never wrapping, and a pill's words never wrap either (owner: "don't wrap,
   shorten" — the labels are kept short enough to pair at 320px). .site-howto__jump-measure is the same pills at natural width, out of sight, for the cut. */
.site-howto__jump { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 0 0 22px; }
.site-howto__jump-row { display: flex; flex-wrap: nowrap; gap: 8px; max-width: 100%; }
.site-howto__jump-row a { flex: 0 1 auto; min-width: 0; white-space: nowrap; }
.site-howto__jump[data-fit="wrap"] .site-howto__jump-row a { white-space: normal; }
.site-howto__jump[data-numbers="off"] .site-howto__jump-row a { padding-left: 12px; }
.site-howto__jump-row .site-howto__jump-n { flex: none; }
.site-howto__jump-measure { position: absolute; left: 0; top: 0; width: 0; height: 0; overflow: hidden; visibility: hidden; display: flex; gap: 8px; white-space: nowrap; pointer-events: none; }
.site-howto__jump-measure .site-howto__jump-pill { flex: none; }
.site-howto__jump a, .site-howto__jump-pill { display: inline-flex; align-items: center; gap: 7px; padding: 5px 12px 5px 6px; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-pill); background: var(--gl-panel); color: var(--gl-text); font-size: var(--gl-size-small); font-weight: 600; text-decoration: none; }
.site-howto__jump a:hover { border-color: var(--gl-rule-strong); background: var(--gl-wash); text-decoration: none; }
.site-howto__jump a:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 2px; }
.site-howto__jump-n { display: inline-flex; align-items: center; justify-content: center; min-width: 24px; height: 24px; padding: 0 6px; border-radius: var(--gl-radius-pill); background: var(--gl-wash); color: var(--gl-muted); font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.site-howto__anchor { scroll-margin-top: 76px; }
.site-howto__shot { margin: 12px 0 0; }
.site-howto__shot img { display: block; width: 100%; max-width: 780px; height: auto; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-lg); box-shadow: var(--gl-shadow-card); }
/* Each step carries a Lamplight and a Daylight capture; the resolved theme shows its own. */
[data-gl-theme="light"] .site-howto__img--dark { display: none; }
[data-gl-theme="dark"] .site-howto__img--light { display: none; }
@media (max-width: 639.98px) { .site-howto__jump a, .site-howto__jump-pill { font-size: 13px; padding: 5px 10px 5px 5px; } .site-howto__jump[data-numbers="off"] .site-howto__jump-row a { padding-left: 10px; } }
/* The smallest phones (under 360px): a tighter pill, so two always pair and a row of three fits without a
   label wrapping (owner, 2026-09-25: "please never lone buttons"; "don't wrap, shorten"). */
@media (max-width: 359.98px) {
  .site-howto__jump a, .site-howto__jump-pill { gap: 5px; font-size: 12px; padding: 4px 8px 4px 4px; }
  .site-howto__jump[data-numbers="off"] .site-howto__jump-row a { padding-left: 8px; }
  .site-howto__jump .site-howto__jump-n, .site-howto__jump-measure .site-howto__jump-n { min-width: 20px; height: 20px; padding: 0 5px; font-size: 11px; }
}
/* About and Privacy (owner, 2026-09-25: "the same visual upgrade as Meeko and the how to"): the How To's
   hero and numbered pills over Meeko's even grid of boxes — two matched columns on a desk with no rule
   between them, one column below 960px (tablet and phone), the last box across the full row when the
   count is odd. Numbers and headings as the How To's steps draw them; spacing from the page tokens. */
.site-panelgrid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gl-space-4) var(--gl-space-6); align-items: stretch; }
.site-panelgrid > * { margin: 0; min-width: 0; }
.site-panelgrid > .site-panelgrid__full { grid-column: 1 / -1; }
@media (min-width: 960px) { .site-panelgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.site-pagebox { display: flex; flex-direction: column; gap: var(--gl-space-2); padding: var(--gl-space-5); }
.site-pagebox__title { display: flex; align-items: baseline; gap: var(--gl-space-4); margin: 0; font-size: var(--gl-size-h3); font-weight: 700; line-height: 1.5; color: var(--gl-text); }
.site-pagebox__n { flex: 0 0 34px; font-family: var(--gl-display); font-weight: 600; font-size: 17px; color: var(--gl-lamp); font-variant-numeric: tabular-nums; }
.site-pagebox__body { margin-left: calc(34px + var(--gl-space-4)); font-size: 15px; line-height: 1.65; color: var(--gl-muted); text-wrap: pretty; }
.site-pagebox__body p { margin: 0 0 var(--gl-space-3); }
.site-pagebox__body p:last-child { margin-bottom: 0; }
.site-pagebox__body strong { font-weight: 700; color: var(--gl-text); }
.site-pagebox__note { margin: 0; }
@media (max-width: 639.98px) {
  .site-pagebox { padding: var(--gl-space-4); }
  .site-pagebox__title { gap: var(--gl-space-3); }
  .site-pagebox__n { flex-basis: 22px; }
  .site-pagebox__body { margin-left: calc(22px + var(--gl-space-3)); }
}
.site-howto-banner { position: fixed; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom, 0px)); z-index: 80; max-width: 340px; display: flex; gap: 12px; align-items: flex-start; padding: 14px 16px; box-shadow: var(--gl-shadow-frame); }
.site-howto-banner img { width: 44px; height: 44px; border-radius: 8px; object-fit: cover; flex: 0 0 auto; }
@media (max-width: 639.98px) { .site-howto-banner { left: 12px; right: 12px; max-width: none; } }

/* --- request ------------------------------------------------------------- */
.site-embed { display: block; width: 100%; height: 1100px; min-height: 80vh; border: 0; border-radius: var(--gl-radius-lg); background: var(--gl-panel); }
@media (max-width: 639.98px) { .site-embed { height: 1300px; } }

/* --- footer -------------------------------------------------------------- */
.site-footer { border-top: 1px solid var(--gl-rule); background: var(--gl-shelf); }
.site-footer__inner { max-width: 1180px; margin: 0 auto; padding: 26px 24px; display: flex; flex-wrap: wrap; gap: 14px 28px; align-items: center; }
.site-footer__brand { margin: 0; font-family: var(--gl-display); font-size: 14px; color: var(--gl-lamp); }
.site-footer a { font-size: 13px; color: var(--gl-muted); white-space: nowrap; line-height: 24px; }
.site-footer a:hover { color: var(--gl-text); }
.site-footer__tag { margin: 0; margin-left: auto; font-size: 12px; color: var(--gl-faint); }
@media (max-width: 639.98px) { .site-footer__tag { margin-left: 0; } }

/* --- loading ------------------------------------------------------------- */
.site-skel { background: linear-gradient(90deg, var(--gl-panel) 25%, var(--gl-panel-2) 50%, var(--gl-panel) 75%); background-size: 200% 100%; animation: site-shimmer 1.4s linear infinite; border-radius: var(--gl-radius-sm); min-height: 14px; }
.site-skel--cover { aspect-ratio: 2 / 3; border-radius: 7px; }
.site-skel--line { height: 14px; margin: 6px 0; }
@keyframes site-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
.site-spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--gl-rule); border-top-color: var(--gl-lamp); border-radius: 50%; animation: site-spin .8s linear infinite; vertical-align: middle; }
@keyframes site-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .site-skel, .site-spinner { animation: none; } }

/* ==========================================================================
   READER
   Continuous scroll at every size; the document is the scroller. Body face is
   Fraunces (as drawn), sizes and measure are custom properties a member can
   change from the Aa panel, and chrome stays out of the way of the text.
   ========================================================================== */
.reader { --reader-size: 17px; --reader-leading: 1.72; --reader-measure: 70ch; --reader-family: var(--gl-display); min-height: 100vh; display: flex; flex-direction: column; background: var(--gl-ink); color: var(--gl-text); }
.reader__bar { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 12px; background: color-mix(in srgb, var(--gl-panel) 88%, transparent); backdrop-filter: blur(8px); border-bottom: 1px solid var(--gl-rule); transition: transform .2s ease; }
.reader__bar--hidden { transform: translateY(-100%); }
.reader__bar:focus-within { transform: none; }
.reader__back { display: inline-flex; align-items: center; gap: 6px; padding: 8px 10px; border-radius: var(--gl-radius-sm); color: var(--gl-muted); font-size: 13.5px; white-space: nowrap; min-height: 40px; }
.reader__back:hover { background: var(--gl-wash); color: var(--gl-text); }
.reader__worktitle { flex: 1 1 auto; min-width: 0; margin: 0; font-family: var(--gl-ui); font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--gl-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
.reader__tools { display: flex; align-items: center; gap: 4px; }
.reader__iconbtn { min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center; padding: 0 10px; border-radius: var(--gl-radius-sm); border: 1px solid transparent; background: none; color: var(--gl-muted); font: inherit; font-size: 14px; cursor: pointer; }
.reader__iconbtn:hover { background: var(--gl-wash); color: var(--gl-text); }
.reader__iconbtn[aria-expanded="true"] { border-color: var(--gl-rule-strong); color: var(--gl-text); }
.reader__iconbtn--aa { font-family: var(--gl-display); font-weight: 600; letter-spacing: .02em; }
.reader__column { width: 100%; max-width: min(var(--reader-measure), 760px); margin: 0 auto; padding: 26px 26px calc(80px + env(safe-area-inset-bottom, 0px)); flex: 1 0 auto; }
@media (min-width: 640px) { .reader__column { padding-left: 32px; padding-right: 32px; } }
@media (min-width: 1024px) { .reader__column { padding-left: 40px; padding-right: 40px; } }
.reader__chapter-kicker { margin: 0 0 6px; font-size: 11px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; color: var(--gl-lamp); }
.reader__chapter-title { margin: 0 0 18px; font-family: var(--gl-display); font-optical-sizing: auto; font-weight: 600; font-size: 20px; line-height: 1.2; color: var(--gl-text); text-wrap: balance; }
.reader__text { font-family: var(--reader-family); font-optical-sizing: auto; font-size: var(--reader-size); line-height: var(--reader-leading); color: var(--gl-text); text-wrap: pretty; overflow-wrap: break-word; }
.reader__text p { margin: 0 0 16px; }
.reader__text h1, .reader__text h2, .reader__text h3, .reader__text h4, .reader__text h5, .reader__text h6 { font-family: var(--gl-display); font-weight: 600; line-height: 1.25; margin: 26px 0 12px; }
.reader__text h1 { font-size: 1.35em; } .reader__text h2 { font-size: 1.25em; } .reader__text h3 { font-size: 1.15em; }
.reader__text blockquote { margin: 0 0 16px; padding: 2px 0 2px 16px; border-left: 2px solid var(--gl-rule-strong); color: var(--gl-muted); }
.reader__text hr { border: 0; border-top: 1px solid var(--gl-rule); margin: 22px auto; width: 40%; }
.reader__text img { max-width: 100%; height: auto; display: block; margin: 12px auto; border-radius: 4px; }
.reader__text a { color: var(--gl-link); text-decoration: underline; text-decoration-color: var(--gl-rule-strong); }
.reader__text ul, .reader__text ol { margin: 0 0 16px; padding-left: 1.4em; }
.reader__text pre { white-space: pre-wrap; }
.reader__text table { border-collapse: collapse; margin: 0 0 16px; }
.reader__text td, .reader__text th { border: 1px solid var(--gl-rule); padding: 4px 8px; }
.reader__text [style*="text-align:center"] { text-align: center; }
/* Meeko's "Image not found" box (owner, 2026-09-25: "If it can't find an image, could it do a placeholder box", and
   for the reader showing it with its frame: "yes"). The sanitisers keep only its class; this draws what the EPUB
   draws inline (meeko/ao3_work.py MISSING_IMAGE_*): the same frame, tint, gold icon and italic alt text. Colours are
   translucent and the words take the page's own colour, so it reads the same on light, sepia and dark paper. */
.meeko-image-missing { display: table; box-sizing: border-box; max-width: 22em; margin: 0.9em auto; padding: 0.75em 1.25em 0.8em; border: 1px dashed rgba(123,88,170,0.55); border-radius: 0.75em; background-color: rgba(123,88,170,0.07); text-align: center; text-indent: 0; line-height: 1.35; font-style: normal; break-inside: avoid; }
.meeko-image-missing::before { content: ""; display: block; width: 1.5em; height: 1.5em; margin: 0 auto 0.35em; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgb(184,140,40)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4.5' width='18' height='15' rx='2.5'/%3E%3Ccircle cx='8.75' cy='9.75' r='1.6'/%3E%3Cpath d='M20.5 15.5l-4.5-4.5-7.5 8'/%3E%3Cpath d='M4 3.5l16 17' stroke-opacity='0.65'/%3E%3C/svg%3E") center / contain no-repeat; }
.meeko-image-missing > span { font-size: 0.95em; letter-spacing: 0.01em; }
.meeko-image-missing > i { display: inline-block; margin-top: 0.25em; font-size: 0.85em; font-style: italic; opacity: 0.75; overflow-wrap: anywhere; }
.reader__nav { display: flex; justify-content: space-between; gap: 12px; margin-top: 36px; padding-top: 18px; border-top: 1px solid var(--gl-rule-soft); font-family: var(--gl-ui); font-size: 14px; }
.reader__nav a { display: inline-flex; flex-direction: column; gap: 3px; max-width: 48%; padding: 10px 12px; border-radius: var(--gl-radius); border: 1px solid var(--gl-rule); color: var(--gl-text); }
.reader__nav a:hover { border-color: var(--gl-rule-strong); background: var(--gl-wash); }
.reader__nav a small { font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--gl-faint); }
.reader__nav a.reader__nav--next { margin-left: auto; text-align: right; }
.reader__nav--head { margin-top: 0; padding-top: 0; border-top: 0; margin-bottom: 18px; }
/* With chapter buttons off, the foot keeps a plain text link so a work is never a dead end. */
.reader__nav--quiet a { border: 0; padding: 4px 0; background: none; color: var(--gl-link); }
.reader__nav--quiet a:hover { background: none; color: var(--gl-link-hover); }
.reader__aa { font-family: var(--gl-display); font-weight: 600; }
.reader__nav--end { width: 100%; text-align: center; color: var(--gl-faint); font-size: 13px; }
/* The end of the work (owner, 2026-09-25, the approved quality-of-life list): the end-of-work card and
   Next in series, in the end slot above. The card is the site's own panel; these rules only undo the
   chapter-link box that `.reader__nav a` would otherwise put on the card's own button, and set the
   card's width and text back to the panel's. */
.reader__endcard { max-width: 440px; margin: 14px auto 0; text-align: left; gap: 10px; color: var(--gl-text); }
.reader__nav--end:has(.reader__endcard) { min-width: 0; }
.reader__nav .reader__endcard a.gl-btn { display: inline-flex; flex-direction: row; max-width: 100%; border-radius: var(--gl-radius); padding: 7px 12px; white-space: normal; text-align: left; overflow-wrap: anywhere; }
.reader__nav .reader__endcard a.gl-btn--secondary { border: 1px solid var(--gl-rule-strong); }
.reader__nav-series { display: flex; justify-content: center; margin-top: 14px; }
.reader__nav .reader__nav-series a { max-width: 100%; text-align: left; }
.reader__progress { position: fixed; left: 0; right: 0; bottom: 0; z-index: 30; display: flex; align-items: center; gap: 12px; padding: 8px 14px calc(8px + env(safe-area-inset-bottom, 0px)); background: color-mix(in srgb, var(--gl-panel) 88%, transparent); backdrop-filter: blur(8px); border-top: 1px solid var(--gl-rule); transition: transform .2s ease; }
.reader__progress--hidden { transform: translateY(100%); }
.reader__progress-track { flex: 1 1 auto; height: 2px; background: var(--gl-rule); border-radius: 1px; }
.reader__progress-fill { height: 100%; background: var(--gl-lamp); border-radius: 1px; }
.reader__progress-label { flex: 0 0 auto; font-family: var(--gl-ui); font-size: 11.5px; color: var(--gl-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
.reader__panel { position: fixed; top: 56px; right: 12px; z-index: 35; width: min(340px, calc(100vw - 24px)); display: flex; flex-direction: column; gap: 14px; padding: 16px; background: var(--gl-shelf); border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-lg); box-shadow: var(--gl-shadow-frame); font-family: var(--gl-ui); font-size: 13.5px; }
/*
 * The work-page tracker panel. Owner, 2026-09-17: "The layout on the reader
 * tracker on book pages is weird."
 *
 * space-between with no wrap puts a label and its value at opposite edges and
 * then refuses to fold when the value is a sentence rather than a number -
 * "Estimated listening time" against a duration is fine, "Sent chapters 15-22 to
 * ElevenReader" is not. Allowing the wrap keeps the pairing and lets the second
 * half drop to its own line instead of widening the page.
 */
.reader__panel-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px 10px; }
.reader__panel-row > * { min-width: 0; overflow-wrap: anywhere; }
.reader__panel-row .gl-btn { min-width: 40px; }
.reader__panel-value { min-width: 56px; text-align: center; font-variant-numeric: tabular-nums; color: var(--gl-text); }
.reader__panel-label { color: var(--gl-muted); }
.reader__toc { position: fixed; top: 56px; left: 12px; z-index: 35; width: min(360px, calc(100vw - 24px)); max-height: calc(100vh - 80px); overflow: auto; display: flex; flex-direction: column; padding: 8px; background: var(--gl-shelf); border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-lg); box-shadow: var(--gl-shadow-frame); font-family: var(--gl-ui); }
.reader__toc a { display: flex; gap: 10px; align-items: baseline; padding: 9px 10px; border-radius: var(--gl-radius-sm); color: var(--gl-text); font-size: 13.5px; }
.reader__toc a:hover { background: var(--gl-wash); }
.reader__toc a[aria-current="true"] { box-shadow: inset 3px 0 0 var(--gl-lamp); }
.reader__toc a span:first-child { flex: 0 0 28px; color: var(--gl-lamp); font-family: var(--gl-display); font-variant-numeric: tabular-nums; }
.reader__state { max-width: 560px; margin: 40px auto; padding: 22px; display: flex; flex-direction: column; gap: 12px; align-items: flex-start; font-family: var(--gl-ui); }
.reader__skeleton { display: flex; flex-direction: column; gap: 10px; }
.reader__skeleton .site-skel--line:nth-child(3n) { width: 82%; }
.reader__skeleton .site-skel--line:nth-child(5n) { width: 64%; }
.reader__tapzones { position: fixed; inset: 48px 0 40px 0; z-index: 5; display: grid; grid-template-columns: 1fr 2fr 1fr; pointer-events: none; }
.reader__tapzones[data-on="true"] { pointer-events: auto; }
.reader__tapzones div { background: transparent; }
.reader__live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.reader__help kbd { display: inline-block; min-width: 22px; padding: 1px 6px; border: 1px solid var(--gl-rule-strong); border-bottom-width: 2px; border-radius: 4px; font-family: var(--gl-mono); font-size: 11.5px; text-align: center; color: var(--gl-text); }
.reader__help dl { display: grid; grid-template-columns: auto 1fr; gap: 8px 14px; margin: 0; }
.reader__help dt { margin: 0; } .reader__help dd { margin: 0; color: var(--gl-muted); }
@media (max-width: 639.98px) { .reader__panel, .reader__toc { top: auto; bottom: calc(48px + env(safe-area-inset-bottom, 0px)); left: 12px; right: 12px; width: auto; } }
/* Part 3 item 11 — the jump, as mocked and approved: "« First ‹ Chapter [ 4 ] of 12 Go › Last »" at the
   left of the reader's gold rule (and at the top of the Chapters panel, Q2: "Yeah"). At phone width the
   ‹ › and the rule's label fold away so the jump fits. Owner, on what "page" means: "For pages, have
   page. For scrolling, have percentage". */
.reader__jump { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; font-size: 12px; color: var(--gl-muted); }
.reader__jump-btn { font: inherit; font-size: 12px; color: var(--gl-text); background: transparent; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-pill); padding: 2px 9px; cursor: pointer; }
.reader__jump-btn:disabled { opacity: .4; cursor: default; }
.reader__jump-go { border-color: var(--gl-lamp); color: var(--gl-lamp); }
.reader__jump-label { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.reader__jump-label input { width: 46px; font: inherit; font-size: 12px; text-align: center; color: var(--gl-text); background: var(--gl-panel); border: 1px solid var(--gl-rule-strong); border-radius: 6px; padding: 2px 4px; }
/* Room for "100" (or a four-digit chapter) beside the spin arrows at the box's own text size, 16px on a phone (final visual review: "10C"). */
.reader__jump-label input { width: max(46px, calc(4ch + 26px)); }
.reader__progress--jump { gap: 14px; height: auto; padding-top: 6px; padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); flex-wrap: wrap; }
@media (max-width: 560px) { .reader__jump-btn--step { display: none; } .reader__progress--jump .reader__progress-label { display: none; } }
.reader__toc-jump { display: flex; padding: 6px 10px 10px; }
/* Page view (owner, 2026-09-25: "Scrolling and page view for being able to use buttons to navigate with a
   page turner"). The same column, laid out in pages the height of the screen: overflow columns in the
   inline direction, one on screen at a time, moved by a transform (Parchment.tsx). The column clips
   rather than scrolls, so focusing a link on another page cannot shift it. An image is never taller
   than a page, so it is never cut in two. */
.reader--pages { height: 100vh; height: 100dvh; min-height: 0; overflow: hidden; }
.reader--pages .reader__column { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; overflow: clip; padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); }
/* The page's height is set, not left to flex (Parchment.tsx layoutPages sets --reader-flow-h to the
   column's content height; the calc is the same size before the first measurement). A multicol whose
   height comes from flex hangs Chromium's layout outright when it holds a display:table box with a top
   border or padding, which work skins use (a chat's date line, a table with a rule), so the size is the
   same and only how it is reached changed. */
.reader--pages .reader__flow { flex: none; min-height: 0; height: calc(100vh - 130px); height: var(--reader-flow-h, calc(100dvh - 130px - env(safe-area-inset-bottom, 0px))); column-count: 1; column-fill: auto; }
.reader--pages .reader__text img { max-height: calc(var(--reader-page-h, 70vh) - 24px); width: auto; object-fit: contain; break-inside: avoid; }
.reader--pages .reader__text figure, .reader--pages .reader__nav a { break-inside: avoid; }

/* Work skins (owner, 2026-09-25: "a LOT of fics use work skins with special coding"; "Please test the
   HTML in reader"). While a skin is shown the chapter container is #reader-workskin and every skin
   rule is scoped under it (shared/skin-css.ts). These guards are the only !important rules a skin
   meets (the sanitiser removes the author's): the container is its own stacking context that clips
   what it holds, so nothing a skin does can paint over the bar, the panels or the footer, or reach
   past the column; no block is wider than the column (or, in page view, the page). It is an inline-size
   container, so the sanitiser's width holds (min(..., 100cqi)) mean the chapter's width. */
#reader-workskin { position: relative !important; isolation: isolate !important; overflow: clip !important; container-type: inline-size !important; max-width: 100% !important; float: none !important; transform: none !important; z-index: auto !important; }
:where(#reader-workskin) :where(div, table, p, blockquote, pre, figure, ul, ol, dl, details, summary, center, address, h1, h2, h3, h4, h5, h6, [class]) { max-width: 100%; }
/* A skin written for AO3's white page: text the skin coloured for white keeps the light paper, and
   text still in the reader's colour on a ground the skin set takes the ink that reads there
   (app/src/parchment/skin.ts applySkinInk). Zero-specificity middles, so the skin's own colours win. */
#reader-workskin :where([data-wsk-paper]) { background-color: #FAF7FC; color: #241A38; }
#reader-workskin :where([data-wsk-paper]) :where(a) { color: #6B3FA0; }
#reader-workskin :where([data-wsk-ink="dark"]) { color: #241A38; }
#reader-workskin :where(a[data-wsk-ink="dark"]) { color: #6B3FA0; }
#reader-workskin :where([data-wsk-ink="light"]) { color: #EDE6F7; }
#reader-workskin :where(a[data-wsk-ink="light"]) { color: #A97BE8; }
/* Page view: a skinned block that fits on a page stays on one page (skin.ts markSkinBlocks). */
.reader--pages #reader-workskin :where([data-wsk-keep]) { break-inside: avoid; }

/* ---- statistics: sparkline, heatmap, gave back, year in review ---- */
.site-spark { display: flex; align-items: flex-end; gap: 4px; height: 34px; }
.site-spark span { flex: 1 1 auto; min-height: 3px; border-radius: 2px; background: #A97BE8; }
/* 53 weekly columns share the panel's width; only a phone narrows them enough to scroll. */
.site-heat { overflow-x: auto; scrollbar-gutter: stable; padding-bottom: 6px; }
.site-heat__weeks { display: grid; grid-template-columns: repeat(53, minmax(min(9px, 100%), 1fr)); gap: 3px; min-width: 100%; }
.site-heat__week { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.site-heat__label { display: block; height: 13px; font-size: 9px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--gl-faint); white-space: nowrap; overflow: visible; }
.site-heat__day { display: block; width: 100%; aspect-ratio: 1 / 1; max-height: 14px; border-radius: 2px; }
.site-heat__key { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--gl-faint); }
.site-heat__key i { display: inline-block; width: 11px; height: 11px; border-radius: 2px; }
.site-gave { display: flex; flex-direction: column; gap: 9px; }
.site-gave__row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding-bottom: 8px; }
.site-gave__label { font-size: 13.5px; color: var(--gl-muted); }
.site-gave__value { font-family: var(--gl-display); font-size: 16px; color: var(--gl-text); font-variant-numeric: tabular-nums; white-space: nowrap; }
.site-panel--2 { background: var(--gl-panel-2); }
.site-wrapped { display: flex; flex-wrap: wrap; gap: 10px 26px; }
.site-wrapped__item { display: flex; flex-direction: column; gap: 2px; }
.site-wrapped__value { font-family: var(--gl-display); font-size: 20px; color: var(--gl-text); font-variant-numeric: tabular-nums; }
.site-wrapped__label { font-size: 11.5px; color: var(--gl-faint); }

/* ---- friends: rows, comparison, member profile ---- */
.site-friend { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; padding: 12px 4px; color: var(--gl-text); }
.site-friend:hover { background: var(--gl-wash); }
.site-friend--static:hover { background: transparent; }
.gl-root button.site-friend { border: 0; background: none; font: inherit; color: inherit; cursor: pointer; }
.gl-root button.site-friend:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: -2px; }
.site-friend img { flex: 0 0 34px; width: 34px; height: 34px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 2px var(--gl-rule); background: var(--gl-panel-2); }
.site-friend--on img { box-shadow: 0 0 0 2px var(--gl-amethyst); }
/* System notices (the library wrote) carry a canon icon instead of a member portrait. */
.site-friend .site-notice-icon { flex: 0 0 40px; width: 40px; height: 40px; border-radius: 10px; box-shadow: 0 0 0 1px var(--gl-rule); }
.site-friend__pick { display: inline-flex; align-items: center; gap: 14px; flex: 1 1 140px; min-width: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit; cursor: pointer; text-align: left; }
.site-friend__pick:disabled { cursor: default; }
.site-friend__name { flex: 1 1 90px; min-width: 0; font-family: var(--gl-display); font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gl-root a.site-friend__name { color: var(--gl-text); }
.site-friend__words { flex: 0 0 auto; font-size: 12.5px; color: var(--gl-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.site-friend__streak { flex: 0 0 auto; font-size: 12.5px; color: var(--gl-faint); white-space: nowrap; }
/* Friend cards (owner, 2026-09-25, "option A"): the same two rows in every card at every width.
   Row 1: the portrait and name, then the figures (Your friends) or the role (Find a friend, Friend
   requests). Row 2: "N shared" with "Profile →", or the friendship status with its button. Names are
   never cut short: a long one wraps inside its own column. The figures are one group; on a phone they
   stack under each other beside the name, so no card's row 1 spills onto a line of its own. */
.site-friend.site-friend--card { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 8px; }
.site-friend__top, .site-friend__foot { display: flex; align-items: center; gap: 8px 14px; min-width: 0; }
.site-friend__top > .site-friend__pick, .site-friend__top > .site-friend__name { flex: 1 1 auto; min-width: 0; }
.site-friend--card .site-friend__name { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
.site-friend__figures { display: inline-flex; flex: 0 0 auto; align-items: baseline; gap: 2px 14px; }
.site-friend__foot { flex-wrap: wrap; }
@media (max-width: 639.98px) {
  .site-friend__figures { flex-direction: column; align-items: flex-end; text-align: right; }
}
/* Who can see your stats: the field is 190px outright (not only a flex basis, which Safari leaves out
   when it sizes the row), and never wider than the row, so it stays inside the page. */
.site-friends-privacy { min-width: 0; max-width: 100%; }
.site-friends-privacy__field { flex: 0 1 190px; width: 190px; max-width: 100%; min-width: 0; }
.site-friends-privacy__field .gl-select { max-width: 100%; min-width: 0; }
/* Every width from 320px (owner, 2026-09-25: "never let small things like that happen, multiple
   lines or off screen"): the heading and its control share one row (Your friends, Messages); on a
   phone the card name is a size smaller with tighter gaps so it fits beside the figures; at 320-359px
   the Friends panels give their cards 6px more each side, and the status word, its button and the
   Inbox views keep to one line. */
.site-friends-head > .site-sechead__text { flex: 1 0 auto; }
.site-sechead--action > .site-friends-privacy { flex: 0 1 190px; }
.site-friend__words-short { display: none; }
@media (max-width: 479.98px) {
  .site-friend__words-full { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  .site-friend__words-short { display: inline; }
  .site-friend--card .site-friend__name { font-size: 14px; }
  .site-friend--card .site-friend__pick { gap: 10px; }
  .site-friend--card .site-friend__top { column-gap: 8px; }
  .site-friends-head .gl-h2 { font-size: 22px; }
}
@media (max-width: 359.98px) {
  .site-panel--pad:has(.site-friendgrid, [aria-label="Inbox views"]) { padding-left: 12px; padding-right: 12px; }
  .site-friend.site-friend--card { padding-left: 0; padding-right: 0; }
  .site-friend--card .site-friendstate { gap: 6px; flex-wrap: nowrap; }
  .site-friend--card .site-friendstate .site-kicker { letter-spacing: .06em; }
  [aria-label="Inbox views"] .gl-btn--sm { padding-left: 10px; padding-right: 10px; }
}
@media (max-width: 640px) {
  /* Phone: the row's action stays beside the name instead of dropping alone
     to a second line; the quiet details take the wrapped line(s) after it. */
  .site-friend > .site-friend__words,
  .site-friend > .site-friend__streak,
  .site-friend > .site-friend__shared,
  .site-friend > a.site-small { order: 4; }
  /* Find a friend: the whole name shows, on two lines if it must, beside its action (final visual review: "Bea Fixture (t…"). */
  .site-friend--directory > .site-friend__name { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
}
.site-compare { display: flex; flex-direction: column; gap: 16px; padding: 20px; }
.site-compare__head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.site-compare__head img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; background: var(--gl-panel-2); }
.site-compare__vs { font-family: var(--gl-display); font-size: 16px; color: var(--gl-text); white-space: nowrap; }
.site-compare__shared { margin-left: auto; font-size: 12.5px; color: var(--gl-faint); }
.site-compare__row { display: flex; flex-direction: column; gap: 6px; }
.site-compare__labels { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 13px; color: var(--gl-muted); }
.site-compare__labels span:last-child { font-size: 12.5px; color: var(--gl-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
.site-compare__bar { height: 6px; border-radius: 999px; background: var(--gl-rule-soft); overflow: hidden; }
.site-compare__bar div { height: 100%; }
.site-compare__bar--mine div { background: var(--gl-amethyst); }
.site-compare__bar--theirs div { background: var(--gl-lamp); }
.site-member-head { display: flex; flex-wrap: wrap; gap: 20px; align-items: center; }
.site-member-head img { width: 96px; height: 96px; border-radius: 50%; object-fit: cover; background: var(--gl-panel-2); box-shadow: 0 0 0 3px var(--gl-char, var(--gl-amethyst)); }
/* A long username in the heading (MeekoTheArchivist) is one unbreakable word —
   let it wrap instead of pushing past a phone viewport. */
.site-member-head .site-muted { overflow-wrap: anywhere; }
.site-demo-card { border-style: dashed; }

/* ---- plain pages (About, Privacy): the mark above a centred title, as the sign-in page does ---- */
.site-pagehead { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px; margin-bottom: 8px; }
.site-pagehead img { width: 72px; height: 72px; border-radius: 16px; object-fit: contain; background: var(--gl-panel-2); box-shadow: 0 0 0 1px var(--gl-rule); }
.site-pagehead + .site-prose { margin-left: auto; margin-right: auto; }

/* ---- friend controls sit with their neighbours: same button size, label text beside a button never a mismatched pill ---- */
.site-picks__actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-left: auto; }
.site-picks .gl-btn { margin-left: 0; }
.site-friendstate { gap: 8px; }
.site-friendstate .site-kicker { white-space: nowrap; }
@media (max-width: 479.98px) {
  .site-picks__actions { flex-basis: 100%; margin-left: 0; }
  .site-picks__actions .gl-btn { flex: 1 1 auto; justify-content: center; }
}

/* ---- controls that share a row share a height: small buttons beside a select or input rise to the field's 38px ---- */
.site-tracker-row__controls .gl-btn--sm, .site-form-row .gl-btn--sm { min-height: 38px; }
.site-tracker-row__controls .site-stars button { min-height: 0; }

/* Listen in ElevenReader — the chapter-selection sheet. Additive: these classes
   are new, and nothing above is changed. The panel gets its own height cap
   because a twelve-chapter grid plus the first-run helper can outgrow a phone. */
.reader__panel--listen { max-height: calc(100vh - 80px); overflow-y: auto; gap: 10px; }
.reader__panel--listen h2.reader__panel-name { margin: 0; font-size: inherit; }
.reader__panel-note { margin: 0; color: var(--gl-muted); font-size: 12.5px; line-height: 1.5; }
.reader__panel-note[role="alert"] { color: var(--gl-danger, #9E3B4E); }
.listen__range { width: 100%; min-height: 44px; padding: 8px 10px; border: 1px solid var(--gl-rule-strong); border-radius: var(--gl-radius-sm); background: var(--gl-panel); color: var(--gl-text); font-family: var(--gl-ui); font-size: 14px; font-variant-numeric: tabular-nums; }
.listen__range:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 1px; }
/* iOS (owner, Task D9 — the zoom regression): Safari on iPhone zooms the page into any control whose
   text is under 16px and does not zoom back out, so the listen sheet and every form on a phone ended
   up magnified after the first tap. Controls on iOS keep the canon's shape and colour and take 16px
   text; nothing else changes on any other platform. Product exception recorded in the Task D receipt. */
@supports (-webkit-touch-callout: none) {
  .gl-input, .gl-select, .gl-textarea, .listen__range { font-size: 16px; }
}
/* Owner, 2026-09-25, from staging (the sign-in page's boxes measured 14px, so iPhone Safari zoomed in on
   focus): "Yes fix the sign in". The signed-out forms (Sign in, Forgotten password, Choose a password
   for a setup or reset link) take 16px text on any touch screen or narrow window, not only where the
   rule above is recognised. The box keeps its 37px height (its line stays 17px tall) and its width;
   the desktop sizes are unchanged. */
@media (pointer: coarse), (max-width: 639.98px) {
  .site-signedout :is(.gl-input, .gl-select, .gl-textarea) { font-size: 16px; line-height: 17px; }
}
/* The same for the signed-in forms (staging on 3e477f3: every Appearance field 14px at 390, so iPhone
   Safari zooms on focus there too; the fix she approved for sign-in). Every text box, select and
   textarea on Administrator settings (all tabs), Account settings, Request a Fic and My Shelves (the
   form-guard pages), the Catalogue and its filters, and the reader (the Aa and Listen panels) takes
   16px text on a phone: below 640px, or on a touch screen up to 1035px (every phone turned sideways,
   an iPad upright). Desktop, touch or not, is unchanged. No box grows: the 14px controls keep their
   17px line; the boxes whose text was smaller keep their height (the pager's page box and per-page
   select, the members' "Writes as" box, the date boxes). !important because some boxes set their
   text size inline (the members' "Writes as"). */
@media (pointer: coarse) and (max-width: 1035.98px), (max-width: 639.98px) {
  :is(.site-formguard, .site-touch16, .reader) :where(input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=file]):not([type=hidden]):not([type=submit]):not([type=button]):not([type=color]), select, textarea) { font-size: 16px !important; line-height: 17px; }
  :is(.site-formguard, .site-touch16, .reader) .site-pagerbar__page,
  :is(.site-formguard, .site-touch16, .reader) .site-pagerbar__size .gl-select,
  :is(.site-formguard, .site-touch16, .reader) .site-member-table input[aria-label^="Author names"] { height: 24px; line-height: 14px; }
  :is(.site-formguard, .site-touch16, .reader) input.gl-input[type=date] { height: 39px; }
}
@media (max-width: 639.98px) {
  :is(.site-formguard, .site-touch16, .reader) .site-pagerbar__page,
  :is(.site-formguard, .site-touch16, .reader) .site-pagerbar__size .gl-select { height: 25px; line-height: 15px; }
}
.listen__range[aria-invalid="true"] { border-color: var(--gl-danger, #9E3B4E); }
.listen__chapters { display: flex; flex-wrap: wrap; gap: 6px; }
/* The panel outside Parchment's reading view.
   Owner item UIC-11 / P-43, 2026-09-21: "a real button, not a control opening
   halfway down the screen" — the inline panel materialised at ~81% of viewport
   height on the work page, below the fold. The work page now uses the floating
   presentation (fixed top-right on desktop, bottom sheet on phones). The inline
   rule is kept for any future embedded use. */
.reader__panel--inline { position: static; width: 100%; max-width: none; z-index: auto; box-shadow: none; }

/* Tablet pass (owner, 2026-09-25: "Also do me a favor and test the reader on a tablet size? I'm not sure
   anyone ever… has?"). Clear defects only, measured with touch emulation from iPad mini to iPad Pro 12.9"
   and an 800×1280 Android tablet (scratchpad READER-TABLET.md); the approved layout is unchanged.
   - A work's own table (an AO3 chat log) with a long address in a cell stood wider than the column, and a
     touch browser zoomed the whole reader out to fit it. A table that fits is drawn as before; one that
     does not scrolls sideways inside the column (wrapping every cell broke "Time" into "Ti/me").
   - The Aa panel had no height cap: with Keyboard shortcuts open it ran off a landscape iPad mini with
     nothing to scroll. Reader panels take the Listen panel's cap and scroll inside it.
   - On a touch screen the bar's Chapters / Listen / Aa and the panels' buttons were 29px tall, a panel
     switch row 22px and a Chapters row 40px. Touch only, each is at least 44px. */
.reader__text table { display: block; max-width: 100%; overflow-x: auto; }
.reader > .reader__panel { max-height: calc(100vh - 80px); max-height: calc(100dvh - 80px); overflow-y: auto; }
@media (pointer: coarse) {
  .reader__back, .reader__bar .gl-btn { min-height: 44px; }
  .reader__panel .gl-btn, .reader__panel .site-link-btn { min-height: 44px; min-width: 44px; }
  .reader__panel label.reader__panel-row { min-height: 44px; }
  .reader__toc a { padding-top: 11px; padding-bottom: 11px; }
}
/* The same pass after the reader-jump merge: the footer's and the Chapters panel's jump controls
   (« First, the number field, Go, Last ») were 25px tall on a touch screen. Their hit area grows to 44px
   tall (mostly upward: the footer sits 6px off the screen's edge) without moving anything that is drawn,
   so the approved footer is unchanged. The number field
   joins the iOS 16px form-text rule above (Task D9) so Safari does not zoom into it. */
@media (pointer: coarse) {
  .reader__jump-btn, .reader__jump-label { position: relative; }
  .reader__jump-btn::after, .reader__jump-label::after { content: ''; position: absolute; left: -2px; right: -2px; top: -15px; bottom: -8px; }
  .reader__jump-label input { position: relative; z-index: 1; }
}
@supports (-webkit-touch-callout: none) { .reader__jump-label input { font-size: 16px; } }

/* The mode choice, stacked. `.reader__panel-row` is space-between, which is right
   for a label and one value and wrong for three wrapping labels in a 340px panel. */
.listen__modes { display: flex; flex-direction: column; gap: 6px; }
.listen__modes-choices { display: flex; flex-wrap: wrap; gap: 6px; }
/* Canon TagPill is sized to be read; these are tapped, so they carry the same
   accessibility minimum as the chapter chips. Recorded on the receipt. */
.listen__modes-choices .site-tagpill { min-height: 44px; padding-top: 0; padding-bottom: 0; }

/* Chapter chips. The pill radius and the 12.5px/999px vocabulary are the canon
   TagPill (COMPONENT_CATALOG, great-library-design-v1). The 44px minimum is an
   accessibility exception recorded on the receipt: a tag is read, a chapter chip
   is tapped, and 4px 11px is not a touch target. */
.listen__chip { min-width: 44px; min-height: 44px; padding: 0 12px; border: 1px solid var(--gl-rule-strong); border-radius: var(--gl-radius-pill); background: var(--gl-panel); color: var(--gl-text); font-family: var(--gl-ui); font-size: 13.5px; font-variant-numeric: tabular-nums; cursor: pointer; }
.listen__chip[aria-pressed="true"] { background: var(--gl-lamp); border-color: var(--gl-lamp); color: var(--gl-ink, #1A1330); font-weight: 600; }
.listen__chip:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 3px; }
@media (prefers-reduced-motion: no-preference) { .listen__chip { transition: background .12s ease, border-color .12s ease; } }
.listen__debug { margin: 0; padding: 10px; background: var(--gl-panel); border: 1px solid var(--gl-rule-strong); border-radius: var(--gl-radius-sm); font-family: var(--gl-mono, ui-monospace, Menlo, monospace); font-size: 11px; line-height: 1.5; color: var(--gl-muted); white-space: pre-wrap; word-break: break-word; overflow-x: auto; }
.listen__paste-link { display: block; padding: 8px 10px; background: var(--gl-panel); border: 1px solid var(--gl-rule-strong); border-radius: var(--gl-radius-sm); font-family: var(--gl-mono, ui-monospace, Menlo, monospace); font-size: 12px; line-height: 1.5; color: var(--gl-text); word-break: break-all; user-select: all; }

/* The work page's listening detail: label and figure per row, wrapping to one
   column when the aside is narrow. Same row rhythm as `.site-gave`, at the
   micro size the surrounding member-state lines use. */
.site-listening-detail { display: flex; flex-direction: column; gap: 2px; margin: 0; }
.site-listening-detail > div { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.site-listening-detail dt { margin: 0; }
.site-listening-detail dd { margin: 0; color: var(--gl-text); font-variant-numeric: tabular-nums; }

/* The pace field beside the read/listened selector: number, unit, and a Save
   that appears only when the figure has actually been changed. */
.site-medium-pace { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.site-medium-pace .gl-input { width: 5.5em; }
.site-tracker-row__controls .site-medium-pace .gl-input { width: 4.5em; }

/* Owner item 10, 2026-09-19: "the small text is too small on mobile/iOS — not just one label."
   On a phone the canon's two smallest sizes move up a step (micro 12→13, small 13.5→14)
   through the tokens so every label, hint, error and table inherits it, and the hard-pixel
   stragglers — the all-caps kickers, pills, calendar and reader chrome — are lifted to a
   12px floor. Desktop sizes are untouched; iOS zoom stays available (the viewport never
   locked it out). */
@media (max-width: 639.98px) {
  :root { --gl-size-micro: 13px; --gl-size-small: 14px; }
  .site-navgroup__toggle { font-size: 13px; }
  .site-submenu__head, .site-suggest__kind, .site-hero__eyebrow, .site-hero__card-label,
  .site-kicker, .site-cal__head span { font-size: 12px; }
  .site-tagpill__block, .site-cal__count, .site-tagpicker__acts .site-tagpill,
  .site-tagbrowser__count, .site-tagcloud .site-tagpill__n, .site-mix__key span,
  .site-coach, .site-nav-badge, .reader__worktitle, .reader__chapter-kicker,
  .reader__nav a small, .reader__progress-label, .site-cal__cell { font-size: 12.5px; }
  .listen__chip { font-size: 14px; }
  .listen__debug { font-size: 12.5px; }
}

/* -------------------------------------------------------------------------
   Owner direction, 2026-09-20 (P-17 / P-18 / P-20): the Daylight amber reads
   as TAN on paper and it has to go — "purple or gold". Canon keeps #8A5A0B
   because bright gold #E3B85C cannot carry text on the paper ground, so the
   exception splits it by job rather than editing the token:

     ink + line accents (pager active, pills, kickers, numbers, underlines)
       -> amethyst  var(--gl-amethyst)  #6B3FA0 — purple, ~7:1 on paper
     filled lamp surfaces (lamp buttons, badges, checked pills, bars)
       -> true gold #E3B85C with the dark --gl-onlamp ink #160E27 (~10:1)
     focus ring
       -> amethyst, more contrast than the amber it replaces

   Scoped to Daylight only; Lamplight keeps the real gold everywhere.
   ------------------------------------------------------------------------- */
[data-gl-theme="light"] {
  --gl-focus: var(--gl-amethyst);
  /* P-19 / canon "character accents revert to canon": the lifted accents were
     tuned to clear 4.5:1 on dark --gl-ink; on paper the as-painted values are
     the correct ones. */
  --gl-char-meeko:  var(--gl-char-meeko-canon);
  --gl-char-kemii:  var(--gl-char-kemii-canon);
  --gl-char-saara:  var(--gl-char-saara-canon);
  --gl-char-maj:    var(--gl-char-maj-canon);
  --gl-char-bea:    var(--gl-char-bea-canon);
  --gl-char-cookie: var(--gl-char-cookie-canon);
  --gl-char-em:     var(--gl-char-em-canon);
  --gl-char-kate:   var(--gl-char-kate-canon);
  --gl-char-sara:   var(--gl-char-sara-canon);
  --gl-char-wadji:  var(--gl-char-wadji-canon);
}
/* The two warmest canon accents still measure under 4.5:1 at the role's micro
   size, so the role text mixes a fifth of the way toward the ink — hue kept,
   AA restored. Decorative uses (card edge, portrait ring) keep the accent. */
[data-gl-theme="light"] .gl-character__role {
  color: color-mix(in srgb, var(--gl-char, var(--gl-amethyst)) 78%, var(--gl-text));
}

/* text, line and small-accent uses -> amethyst */
[data-gl-theme="light"] .site-nav-link[aria-current="page"]::after { background: var(--gl-amethyst); }
[data-gl-theme="light"] .site-lampbtn:hover .site-lampicon { color: var(--gl-amethyst); }
[data-gl-theme="light"] .site-submenu a[aria-current="page"],
[data-gl-theme="light"] .site-drawer__link[aria-current="page"],
[data-gl-theme="light"] .site-tagbrowser__kind[aria-current="true"],
[data-gl-theme="light"] .reader__toc a[aria-current="true"] { box-shadow: inset 3px 0 0 var(--gl-amethyst); }
[data-gl-theme="light"] .site-submenu__head,
[data-gl-theme="light"] .site-kicker,
[data-gl-theme="light"] .site-coach,
[data-gl-theme="light"] .site-linkbtn,
[data-gl-theme="light"] .site-chapters__n,
[data-gl-theme="light"] .site-ranked__n,
[data-gl-theme="light"] .site-footer__brand,
[data-gl-theme="light"] .reader__chapter-kicker,
[data-gl-theme="light"] .reader__toc a span:first-child,
[data-gl-theme="light"] .site-stars button[aria-checked="true"],
[data-gl-theme="light"] .site-stars--read > span[data-on="true"],
[data-gl-theme="light"] .site-alpha button[aria-pressed="true"] { color: var(--gl-amethyst); }
[data-gl-theme="light"] .site-alpha button[aria-pressed="true"] { border-color: var(--gl-amethyst); }
[data-gl-theme="light"] .site-tagpill--on { border-color: var(--gl-amethyst); color: var(--gl-text); background: var(--gl-wash); }
[data-gl-theme="light"] .gl-pagination__link--active { border-color: var(--gl-amethyst); color: var(--gl-amethyst); background: var(--gl-wash); }
[data-gl-theme="light"] .gl-badge--gold { background: var(--gl-wash); color: var(--gl-amethyst); }
[data-gl-theme="light"] .site-cal__cell--today { border-color: var(--gl-amethyst); }
[data-gl-theme="light"] .site-avatar-grid button[aria-pressed="true"] img { box-shadow: 0 0 0 3px var(--gl-amethyst); }
[data-gl-theme="light"] .site-spinner { border-top-color: var(--gl-amethyst); }
[data-gl-theme="light"] .gl-root .gl-topbar__brand { color: var(--gl-amethyst); }
[data-gl-theme="light"] .site-friend__portrait:focus-visible,
[data-gl-theme="light"] .site-howto__jump a:focus-visible,
[data-gl-theme="light"] .listen__range:focus-visible,
[data-gl-theme="light"] .listen__chip:focus-visible { outline-color: var(--gl-amethyst); }

/* filled surfaces -> true gold with dark ink */
[data-gl-theme="light"] .gl-btn--lamp { background: #E3B85C; border-color: #E3B85C; color: #160E27; }
[data-gl-theme="light"] .gl-btn--lamp:hover:not(:disabled) { background: #D9A93F; }
[data-gl-theme="light"] .gl-root a.gl-btn--lamp, [data-gl-theme="light"] .gl-root a.gl-btn--lamp:hover { color: #160E27; }
[data-gl-theme="light"] .site-nav-badge { background: #E3B85C; color: #160E27; }
[data-gl-theme="light"] .site-multi__mode .site-tagpill[aria-checked='true'],
[data-gl-theme="light"] .listen__chip[aria-pressed="true"] { background: #E3B85C; border-color: #E3B85C; color: #160E27; }
[data-gl-theme="light"] .site-bar__fill--lamp,
[data-gl-theme="light"] .reader__progress-fill,
[data-gl-theme="light"] .site-compare__bar--theirs div,
[data-gl-theme="light"] .site-chart__bar--now span { background: #E3B85C; }

/* Sepia, Parchment's warm paper (parchment/sepia.ts holds the tokens, laid on .reader inline). The
   root carries data-gl-paper="sepia" while it is painted; these few rules cover what names a colour
   outright instead of a token, so the reader looks the same whichever lamp the site is under.
   Filled gold stays true gold with dark ink (10:1), as in Daylight; the kicker and the progress rule
   take the burnt amber, which is ink on the paper (6:1). */
[data-gl-paper="sepia"] .reader .gl-btn--lamp { background: #E3B85C; border-color: #E3B85C; color: #160E27; }
[data-gl-paper="sepia"] .reader .gl-btn--lamp:hover:not(:disabled) { background: #D9A93F; }
[data-gl-paper="sepia"] .gl-root .reader a.gl-btn--lamp, [data-gl-paper="sepia"] .gl-root .reader a.gl-btn--lamp:hover { color: #160E27; }
[data-gl-paper="sepia"] .reader .reader__chapter-kicker, [data-gl-paper="sepia"] .reader .reader__toc a span:first-child { color: var(--gl-lamp); }
[data-gl-paper="sepia"] .reader .reader__progress-fill { background: var(--gl-lamp); }
[data-gl-paper="sepia"] .reader .reader__text code { color: #5B338C; }
[data-gl-paper="sepia"] .reader .reader__text pre { color: #3B2A1A; }
[data-gl-paper="sepia"] .reader .reader__text mark { background: #EFD58F; color: #24170B; }

/* P-42 — iOS legibility floor: readable text never renders under 13px on a
 * phone, and small controls match the 13.5px base button size. Uppercase
 * eyebrows, badges and tracked labels keep their canonical sizes — they are
 * labels, not reading text. Accessibility exception: documented on the P-42
 * receipt; canon tokens themselves are untouched. */
@media (max-width: 639.98px) {
  .site-micro,
  .site-label,
  .site-listitem__meta,
  .site-tagpill,
  .site-staffnote__link,
  .site-friend__streak,
  .site-writer__note,
  .site-chapters__words,
  .site-tile__note,
  .site-hero__card-note,
  .site-saved__label,
  .site-footer__tag,
  .site-pagerbar__page,
  .site-pagerbar__size .gl-select,
  .gl-hint, .gl-error, .gl-label,
  .gl-book__author, .site-strip__name { font-size: 13px; }
  .gl-btn--sm,
  .site-staffnote__text,
  .site-listitem__by { font-size: 13.5px; }
  .gl-book__series { font-size: 12px; }
  .site-tagpicker__acts .site-tagpill { font-size: 12.5px; }
  .site-cal__cell { font-size: 12.5px; }
  .reader__worktitle, .reader__nav a small,
  .reader__chapter-kicker, .reader__progress-label { font-size: 13px; }
  .listen__debug { font-size: 13px; }
}

/* Continue reading and the TBR (owner, Part 3 item 1, approved 2026-09-25; the approved mock-up's
   styles, app/src/components/tbr.tsx). "the layout needs to have two fit next to that top box on
   desktop as it's off": on the home list the open card spans two grid rows and the grid packs
   densely, so the two closed cards after it stack in the column beside it with no gap. Rows stretch
   so the open card and its two neighbours end level. One column on narrow screens. */
.site-tbr-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(460px, 100%), 1fr)); gap: 12px; }
.site-tbr-list--home { grid-auto-flow: row dense; align-items: stretch; }
.site-tbr-list--home > .site-tbr--open { grid-row: span 2; }
.site-tbr-list--dash { grid-template-columns: 1fr; align-items: start; }
.site-tbr { padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.site-tbr--open { border-color: var(--gl-rule-strong); }
.site-tbr__main { display: flex; gap: 12px; align-items: flex-start; min-width: 0; }
.site-tbr__cover { width: 52px; height: 78px; flex: 0 0 52px; border-radius: 3px; object-fit: cover; box-shadow: var(--gl-shadow-card); }
.site-tbr__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.site-tbr__title, .site-tbr__by, .site-tbr__read { margin: 0; }
.site-tbr__title a { color: inherit; }
.site-tbr__update { margin: 0; font-size: 13px; color: var(--gl-muted); }
.site-tbr__update strong { color: var(--gl-lamp); font-weight: 700; }
.site-tbr__actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding-top: 10px; }
.site-tbr__pair { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.site-tbr__progress { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.site-tbr__progress .gl-input { width: 64px; flex: 0 0 64px; }
.site-tbr__progress .site-num { white-space: nowrap; }
.site-tbr__opds { display: flex; flex-direction: column; gap: 8px; padding-top: 10px; }
.site-tbr__opds-row { display: flex; gap: 8px; align-items: center; min-width: 0; }
.site-tbr__opds-row .gl-input { flex: 1 1 auto; min-width: 0; }

/* The work page's share row (owner, 2026-09-25: "the send to friend looks weird on mobile"): the same
   four buttons in the same order, as a grid the width of the column instead of a ragged wrap. The
   two friend buttons (and the open send/write form) take the full width; Share… and Copy link sit
   side by side at equal widths, and whichever button is left alone on the last line takes the full
   width. On phones the box is as wide as the other buttons in the centred column (320px). */
.site-work__share-box { width: 100%; }
.site-work__share { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.site-work__share > .gl-btn { width: 100%; min-width: 0; }
.site-work__share > .site-work__share-wide, .site-work__share > .gl-btn:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (max-width: 691.98px) { .site-work__share-box { max-width: 320px; } }

/* ---------------------------------------------------------------- random draw (Part 3 item 2)
   Owner, approved 2026-09-25. Your reading › Roll the dice: the approved mock's panel, the form on
   the left and the drawn fic on the right, one column under 820px. The drawn fic (also in the
   catalogue's Surprise me dialog) is the work page's top block at card size. Site tokens only. */
.site-random { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 24px; align-items: start; }
.site-random__form, .site-random__result { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.site-random__form .site-tagpicker { flex: 0 0 auto; }
@media (max-width: 820px) { .site-random { grid-template-columns: 1fr; } }
.site-drawn { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.site-drawn__top { display: flex; gap: 16px; align-items: flex-start; min-width: 0; }
.site-drawn__coverlink { flex: none; line-height: 0; }
.site-drawn__cover { width: 96px; height: auto; aspect-ratio: 2 / 3; object-fit: cover; border-radius: var(--gl-radius-sm); box-shadow: var(--gl-shadow-card); }
.site-drawn__cover.site-nocover { display: flex; height: 144px; font-size: 11px; overflow: hidden; }
.site-drawn__head { gap: 8px; min-width: 0; flex: 1 1 auto; }
.site-drawn__head h3 a { color: inherit; }
.site-drawn__head .site-work__byline { font-size: 14px; overflow-wrap: anywhere; }
/* A narrow drawn fic (a phone, or a narrow column): beside the 96px cover the badges had room for one
   a line, so they go under the cover and title, across the card, where they share lines with none alone
   (owner, 2026-09-25: "never let small things like that happen"). The title and byline stay beside the
   cover. Measured on the card's own width, wherever it is shown. */
.site-drawn { container: drawn / inline-size; }
@container drawn (max-width: 480px) {
  .site-drawn__top { display: grid; grid-template-columns: 96px minmax(0, 1fr); grid-template-rows: auto 1fr auto; column-gap: 16px; row-gap: 8px; align-items: start; }
  .site-drawn__coverlink { grid-column: 1; grid-row: 1 / span 2; }
  .site-drawn__head { display: contents; }
  .site-drawn__head > :not(.site-work__badges) { grid-column: 2; margin: 0; }
  .site-drawn__head > .site-work__badges { grid-column: 1 / -1; grid-row: 3; justify-content: flex-start; margin-top: 4px; }
}
.site-drawn__tags { gap: 10px; }
.site-random__row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; align-items: start; }
.site-random__row .site-filters__range .gl-input { max-width: none; }
/* Below 440px the two share no row: Length's two boxes would be cut too narrow to read. */
@media (max-width: 439.98px) { .site-random__row { grid-template-columns: minmax(0, 1fr); } }
/* The drawn fic's labelled lines (owner, 2026-09-25: "view more for each section so it's not so weirdly tall"). */
.site-drawn__facts { row-gap: 10px; }
.site-drawn__facts dt { white-space: nowrap; }
/* The pills wrap in the first column; View more keeps a column of its own at the end, so it never drops onto a line by itself. */
.site-drawn__line { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 6px 10px; min-width: 0; }
.site-drawn__line > .site-tags { min-width: 0; align-items: center; }
.site-drawn__line > .site-drawn__one { flex-wrap: nowrap; }
.site-drawn__one > .gl-badge { flex: none; }
.site-drawn__more { min-height: 0; padding: 3px 2px; white-space: nowrap; }
.site-drawn__summary { color: var(--gl-muted); line-height: 1.5; }
.site-drawn-modal { max-width: 640px; }
@media (max-width: 720px) {
  /* The work page centres its head on phones; beside a cover here it stays left. */
  .site-drawn__head { align-items: flex-start; text-align: left; }
  .site-drawn__head .site-work__badges { justify-content: flex-start; }
}
/* ==================================================================== the visual bookshelf (Part 3 item 9)
   Owner, approved 2026-09-25: "Yup love it. Maybe edits later but bookshelf is a go!" — the approved
   mock's rules (mockups/part3 48fb1f5, app/site.css item 9), unchanged but for their names. */
/* bookshelf (owner 2026-09-25): spines drawn as the side of a Library cover, standing flush on a real
   shelf with a bronze hockey bookend at each end. Deep ground with the covers' violet haze, gold head and
   tail bands, a gold ornament, the title in gold running up. The gold is the true gold in both themes
   (the light theme's amber cannot carry on a dark ground, as the hero eyebrow does). The plank has a top
   face, a front lip and a depth shadow on the back wall. */
.site-bookshelf { --shelf-gold: var(--gl-lamp); --shelf-wood: color-mix(in srgb, var(--shelf-gold) 27%, #2B0F0A); --shelf-shade: rgba(0, 0, 0, .5); position: relative; display: flex; flex-direction: column; gap: 14px; padding: 18px 18px 20px; border-radius: var(--gl-radius-lg); background: linear-gradient(180deg, var(--gl-panel) 0%, var(--gl-panel-2) 100%); box-shadow: inset 0 0 0 1px var(--gl-rule); min-height: 120px; }
[data-gl-theme="light"] .site-bookshelf { --shelf-gold: #E3B85C; --shelf-shade: rgba(36, 26, 56, .22); }
.site-bookshelf__row { position: relative; display: flex; align-items: flex-end; padding: 34px 12px 17px; z-index: 0;
  background: linear-gradient(0deg, var(--shelf-shade), transparent) left 0 bottom 17px / 100% 22px no-repeat; }
/* the top face of the plank (seen a little from above) and its front lip, lit along the edge */
.site-bookshelf__row::before { content: ""; position: absolute; left: 0; right: 0; bottom: 12px; height: 6px; z-index: -1; background: linear-gradient(180deg, color-mix(in srgb, var(--shelf-wood), #000 30%), color-mix(in srgb, var(--shelf-wood), #fff 14%)); }
.site-bookshelf__row::after { content: ""; position: absolute; left: -4px; right: -4px; bottom: 0; height: 12px; border-radius: 2px;
  background:
    linear-gradient(180deg, rgba(255, 236, 196, .30) 0 1px, transparent 1px),
    repeating-linear-gradient(177deg, rgba(0, 0, 0, .13) 0 1px, transparent 1px 4px, rgba(255, 230, 180, .05) 4px 5px, transparent 5px 9px),
    linear-gradient(180deg, color-mix(in srgb, var(--shelf-wood), #fff 8%) 0%, var(--shelf-wood) 45%, color-mix(in srgb, var(--shelf-wood), #000 45%) 100%);
  box-shadow: 0 6px 8px -4px var(--shelf-shade); }
.site-bookend { flex: none; position: relative; z-index: 1; margin-bottom: -1px; filter: drop-shadow(0 2px 2px rgba(0, 0, 0, .35)); }
.site-bookshelf__group { position: relative; flex: none; display: flex; flex-direction: column; justify-content: flex-end; }
.site-bookshelf__series { position: absolute; left: 1px; bottom: calc(100% + 3px); padding: 0 0 3px 4px; border-left: 1px solid var(--gl-lamp); box-sizing: border-box; font-family: var(--gl-ui); font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--gl-lamp); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2; }
.site-bookshelf__series--t0 { z-index: 2; padding-right: 5px; background: color-mix(in srgb, var(--gl-panel) 55%, var(--gl-panel-2)); }
.site-bookshelf__series--t1 { z-index: 1; padding-bottom: 17px; }
.site-bookshelf__rule { position: absolute; left: 1px; right: 3px; bottom: calc(100% + 2px); height: 1px; background: var(--gl-lamp); }
.site-bookshelf__spines { display: flex; align-items: flex-end; gap: 0; }
.site-spine { --sp: #43266C; position: relative; display: flex; align-items: flex-start; justify-content: center; padding-top: 25px; border-radius: 2px 2px 1px 1px; overflow: hidden; text-decoration: none; color: var(--shelf-gold); box-sizing: border-box;
  background:
    linear-gradient(90deg, rgba(0, 0, 0, .55) 0, rgba(255, 255, 255, .10) 18%, rgba(255, 255, 255, .03) 42%, rgba(0, 0, 0, .12) 72%, rgba(0, 0, 0, .55) 100%),
    radial-gradient(90% 38% at 50% 30%, color-mix(in srgb, var(--sp), #fff 28%), transparent 72%),
    radial-gradient(120% 30% at 50% 88%, rgba(227, 184, 92, .10), transparent 70%),
    linear-gradient(180deg, color-mix(in srgb, var(--sp), #000 30%) 0%, var(--sp) 22%, var(--sp) 78%, color-mix(in srgb, var(--sp), #000 35%) 100%);
  box-shadow: inset 1px 0 0 color-mix(in srgb, var(--shelf-gold) 22%, transparent), inset -1px 0 0 rgba(0, 0, 0, .55); }
.site-spine:hover { transform: translateY(-4px); text-decoration: none; }
.site-spine::before, .site-spine::after { content: ""; position: absolute; left: 2px; right: 2px; height: 3px; border-top: 1.5px solid var(--shelf-gold); border-bottom: 1px solid var(--shelf-gold); opacity: .92; }
.site-spine::before { top: 6px; }
.site-spine::after { bottom: 6px; }
.site-spine__orn { position: absolute; top: 15px; left: 50%; width: 5px; height: 5px; margin-left: -3.5px; border: 1px solid var(--shelf-gold); transform: rotate(45deg); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--shelf-gold) 35%, transparent); }
.site-spine__title { writing-mode: vertical-rl; transform: rotate(180deg); font-family: var(--gl-display); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--shelf-gold); letter-spacing: .02em; text-shadow: 0 1px 1px rgba(0, 0, 0, .7); max-height: calc(100% - 44px); }
.site-spine--thin .site-spine__title { font-size: 10.5px; letter-spacing: 0; }
.site-bookshelf__group--series .site-spine__title, .site-spine:has(.site-spine__n) .site-spine__title { max-height: calc(100% - 58px); }
.site-spine__n { position: absolute; left: 0; right: 0; bottom: 15px; text-align: center; font-size: 10px; font-weight: 800; color: var(--shelf-gold); text-shadow: 0 1px 1px rgba(0, 0, 0, .7); }
.site-spine--violet { --sp: #43266C; } .site-spine--amethyst { --sp: #553189; } .site-spine--aubergine { --sp: #33173F; }
.site-spine--midnight { --sp: #1B2552; } .site-spine--oxblood { --sp: #58161F; } .site-spine--forest { --sp: #1C3A2A; } .site-spine--plum { --sp: #4C1C45; }
.site-shelfview { margin-bottom: 2px; justify-content: space-between; align-items: center; }
.site-shelfview__sort { flex-wrap: nowrap; align-items: center; }
.site-shelfview__sort > :last-child { width: auto; min-width: 190px; }
/* keyboard focus on a spine is plain to see; the lift is left out for anyone who asks for less motion */
.site-spine:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 2px; transform: translateY(-4px); }
@media (prefers-reduced-motion: reduce) { .site-spine:hover, .site-spine:focus-visible { transform: none; } }
/* the writer's spine colour picker (the author's desk form, item 12; the author-submission lane draws it) */
.site-swatches { --shelf-gold: #E3B85C; display: flex; flex-wrap: wrap; gap: 10px; }
.site-swatch { display: inline-flex; flex-direction: column; align-items: center; gap: 6px; padding: 8px 8px 6px; min-width: 76px; background: transparent; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); color: var(--gl-muted); font: inherit; font-size: 12px; cursor: pointer; }
.site-swatch .site-spine { padding-top: 0; }
.site-swatch .site-spine__orn { top: 32px; }
.site-swatch--on { border-color: var(--gl-lamp); box-shadow: 0 0 0 1px var(--gl-lamp); color: var(--gl-text); }
.site-swatch__label { white-space: nowrap; }

/* Guides (Part 3 item 4, owner-approved 2026-09-25). The work page's guide panel sits after Chapters:
   mark, words, button in one row at desk widths; on a phone the button drops below the words
   (owner: "put the guide after chapters not before with the button below on mobile"). Its width is
   the tracker box's own ("cap it like the tracking box"): the work column's width, no wider. */
.site-guidelink { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 12px; align-items: center; }
.site-guidelink__text { min-width: 0; }
@media (max-width: 639.98px) {
  .site-guidelink { grid-template-columns: auto minmax(0, 1fr); align-items: start; }
  .site-guidelink__btn { grid-column: 2; justify-self: start; }
}
.site-guide__text { max-width: 72ch; }
/* ==================================================================== The rich editor (components/RichEditor.tsx)
   Part 3 item 12, owner 2026-09-25: "Eventually we will need a html editor. If possible do that now with a
   WYSIWYG." The surface reads as the site's textarea (the DS .gl-textarea's rule, radius and field colour);
   the toolbar uses the site's small ghost-button look. It wraps on a phone. */
.site-editor { display: flex; flex-direction: column; gap: 0; min-width: 0; }
.site-editor__bar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; padding: 6px; border: 1px solid var(--gl-rule-strong); border-bottom: 0; border-radius: var(--gl-radius-sm) var(--gl-radius-sm) 0 0; background: var(--gl-panel-2); }
.site-editor__group { display: inline-flex; flex-wrap: wrap; gap: 2px; }
.site-editor__btn { min-width: 30px; height: 30px; padding: 0 8px; font: inherit; font-size: 13px; font-weight: 700; color: var(--gl-muted); background: transparent; border: 1px solid transparent; border-radius: 6px; cursor: pointer; }
.site-editor__btn:hover:not(:disabled) { color: var(--gl-text); background: var(--gl-wash); }
.site-editor__btn[aria-pressed="true"] { color: var(--gl-text); border-color: var(--gl-rule-strong); background: var(--gl-wash); }
.site-editor__btn:disabled { opacity: .4; cursor: default; }
.site-editor__btn:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 1px; }
.site-editor__btn--source { margin-left: auto; font-weight: 800; letter-spacing: .04em; }
.site-editor__surface, .site-editor__source { min-height: 220px; max-height: 70vh; overflow-y: auto; margin: 0; padding: 12px 14px; border: 1px solid var(--gl-rule-strong); border-radius: 0 0 var(--gl-radius-sm) var(--gl-radius-sm); background: var(--gl-ink-deep); color: var(--gl-text); font-family: var(--gl-ui); font-size: 14px; line-height: 1.6; box-sizing: border-box; width: 100%; }
.site-editor > div:not(.site-editor__bar) { width: 100%; min-width: 0; }
.site-editor__surface { display: block; white-space: pre-wrap; overflow-wrap: anywhere; }
/* The editing area fills its field like the toolbar above it (it carries .site-prose for the text
   styles, whose 66ch cap made it a narrow box in a wide panel); lines inside keep a reading length. */
.gl-root .site-editor__surface.site-prose { max-width: none; }
.site-editor__surface > * { max-width: 80ch; }
.site-editor__surface:focus, .site-editor__source:focus { outline: none; border-color: var(--gl-link); box-shadow: 0 0 0 3px rgba(169,123,232,.25); }
.site-editor__surface > :first-child { margin-top: 0; }
.site-editor__surface img { max-width: 100%; height: auto; }
.site-editor__surface img.ProseMirror-selectednode { outline: 2px solid var(--gl-lamp); }
.site-editor__source--alone { border-radius: var(--gl-radius-sm); }
.site-editor__source { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; resize: vertical; }
/* A guide's rebuilt nodes (lib/guide-html.ts) sit where the built-in markup sat: the wrapper takes no box. */
.site-guide__nodes { display: contents; }
/* The How To's words were bare text in each step; as stored paragraphs they keep that spacing. */
.site-howto .site-guide__nodes > p { margin: 0; }
/* ==================================================================== The author's desk (routes/Write.tsx)
   Part 3 item 12, as the approved mock draws it; only the site's tokens. */
.site-desk__stat { margin: 4px 0 0; font-family: var(--gl-display); font-size: 28px; color: var(--gl-text); }
.site-desk__note { color: var(--gl-text); }
.site-pagebox--plain .site-pagebox__body { margin-left: 0; }
.site-desk__fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.site-desk__cover { width: 40px; height: 60px; object-fit: cover; border-radius: 3px; box-shadow: var(--gl-shadow-card); }
.site-row--wrap .site-row__full { flex: 1 1 100%; }
.site-row__full .gl-input { min-width: 0; flex: 1 1 auto; }
.site-desk__preview .site-desk__preview-text { font-size: 16px; line-height: 1.7; }
.site-desk__preview-text img { max-width: 100%; height: auto; }
/* The work skin's built-in skins and pictures (owner, 2026-09-25: "let's make those replaceable by the function?"). */
.site-skinslot { display: grid; grid-template-columns: 40px minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 10px 4px; border-bottom: 1px solid var(--gl-rule-soft); }
.site-skinslot__pic { width: 40px; height: 40px; border-radius: 6px; background: var(--gl-panel-2) center / cover no-repeat; border: 1px dashed var(--gl-rule); }
.site-skinslot__pic--round { border-radius: 50%; }
.site-skinslot__text { display: flex; flex-direction: column; min-width: 0; }
.site-skinslot__title { font-family: var(--gl-display); font-weight: 600; font-size: 15px; color: var(--gl-text); overflow-wrap: anywhere; }
.site-skinslot__meta { font-size: 12px; color: var(--gl-faint); overflow-wrap: anywhere; text-wrap: balance; }
/* Phones: the name beside the picture, and the words under it share their row with the button. */
@media (max-width: 479.98px) {
  .site-skinslot { grid-template-columns: 40px minmax(0, 1fr) auto; grid-template-areas: "pic title title" "meta meta btn"; row-gap: 6px; }
  .site-skinslot__pic { grid-area: pic; }
  .site-skinslot__text { display: contents; }
  .site-skinslot__title { grid-area: title; align-self: center; }
  .site-skinslot__meta { grid-area: meta; align-self: center; }
  .site-skinslot > button { grid-area: btn; }
}
/* The Texts generator, Library skin cards and chat blocks on the author's desk (owner, 2026-09-26:
   "the UI on the workskin generator needs... work?"). A skin preview (.site-skinview) carries the
   reader's own #reader-workskin guards, so what the desk draws is what the reader draws. */
.site-skinslot__pic--set { border-style: solid; }
.site-skinslot__meta .gl-badge { white-space: normal; }
.site-skinview { position: relative; isolation: isolate; overflow: clip; container-type: inline-size; max-width: 100%; display: flow-root; white-space: normal; }
:where(.site-skinview) :where(div, table, p, blockquote, pre, figure, ul, ol, dl, details, summary, center, address, h1, h2, h3, h4, h5, h6, [class]) { max-width: 100%; }
.site-skinview :where([data-wsk-paper]) { background-color: #FAF7FC; color: #241A38; }
.site-skinview :where([data-wsk-paper]) :where(a) { color: #6B3FA0; }
.site-skinview :where([data-wsk-ink="dark"]) { color: #241A38; }
.site-skinview :where([data-wsk-ink="light"]) { color: #EDE6F7; }
.site-skincards { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
.site-skincard { display: grid; grid-template-columns: 112px minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 10px; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); background: var(--gl-panel); min-width: 0; }
.site-skincard--on { border-color: var(--gl-lamp); }
.site-skincard__demo { display: block; height: 72px; border-radius: 6px; overflow: clip; background: var(--gl-ink-deep); border: 1px solid var(--gl-rule-soft); pointer-events: none; user-select: none; }
.site-skincard__demo .site-skinview { display: block; zoom: .4; width: 250%; height: 250%; padding: 10px 12px; box-sizing: border-box; font-size: 16px; line-height: 1.5; }
.site-skincard__text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.site-skincard__name { font-family: var(--gl-display); font-weight: 600; font-size: 14.5px; line-height: 1.28; color: var(--gl-text); }
.site-skincard__blurb { font-size: 12.5px; line-height: 1.45; color: var(--gl-muted); overflow-wrap: anywhere; }
.site-skincard > .gl-btn { white-space: nowrap; }
@media (min-width: 1024px) {
  .site-skincards { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .site-skincard { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr auto; align-items: stretch; }
  .site-skincard__demo { height: 92px; }
  .site-skincard__demo .site-skinview { zoom: .5; width: 200%; height: 200%; }
  .site-skincard > .gl-btn { justify-self: start; }
}
@media (max-width: 479.98px) {
  .site-skincard { grid-template-columns: 88px minmax(0, 1fr); grid-template-areas: "demo text" "demo btn"; row-gap: 8px; }
  .site-skincard__demo { grid-area: demo; height: 100%; min-height: 72px; }
  .site-skincard__text { grid-area: text; }
  .site-skincard > .gl-btn { grid-area: btn; justify-self: start; }
}
.site-textsband { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 12px 14px; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); background: var(--gl-panel-2); }
.site-textsband__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.site-textsband__title { font-size: 15.5px; font-weight: 700; color: var(--gl-text); }
.site-textsband__hint { font-size: 12.5px; color: var(--gl-muted); line-height: 1.45; }
.site-textsband > .gl-btn { white-space: nowrap; }
@media (max-width: 479.98px) { .site-textsband { grid-template-columns: minmax(0, 1fr); } .site-textsband > .gl-btn { justify-self: start; } }
.site-advanced { border-top: 1px solid var(--gl-rule-soft); padding-top: 6px; }
.site-advanced__summary { display: flex; align-items: center; gap: 8px; min-height: 36px; cursor: pointer; list-style: none; font-size: 13px; font-weight: 700; color: var(--gl-muted); }
.site-advanced__summary::-webkit-details-marker { display: none; }
.site-advanced__summary::before { content: "\25B8"; font-size: 11px; color: var(--gl-faint); }
.site-advanced[open] > .site-advanced__summary::before { content: "\25BE"; }
.site-advanced__summary:hover { color: var(--gl-text); }
.site-advanced__summary:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 3px; border-radius: 4px; }
.site-advanced__body { padding-top: 6px; }
.site-skinblock { margin: 12px 0; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); background: var(--gl-panel); white-space: normal; overflow: clip; }
.site-editor__surface > .site-skinblock { max-width: 80ch; }
.site-skinblock.ProseMirror-selectednode { outline: 2px solid var(--gl-lamp); outline-offset: 2px; }
.site-skinblock__bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 2px 8px; padding: 4px 6px 4px 12px; border-bottom: 1px solid var(--gl-rule-soft); background: var(--gl-panel-2); font-size: 12.5px; color: var(--gl-muted); }
.site-skinblock__label { flex: 1 1 14ch; min-width: 0; overflow-wrap: break-word; }
.site-skinblock__tools { display: inline-flex; gap: 2px; flex: none; }
/* N-05: Remove is always warning-red, here too. */
.site-editor__btn--danger, .site-editor__btn--danger:hover:not(:disabled) { color: var(--gl-danger); }
.site-skinblock > .site-skinview { padding: 14px; font-size: 16px; line-height: 1.7; }
@media (max-width: 479.98px) { .site-skinblock > .site-skinview { padding: 8px; } .site-editor__surface:has(> .site-skinblock) { padding-left: 8px; padding-right: 8px; } }
@media (max-width: 1023.98px) { .site-editor__btn { padding: 0 6px; } .site-editor__bar { gap: 4px 6px; } }
@media (max-width: 399.98px) { .site-editor__btn { min-width: 26px; padding: 0 5px; } .site-editor__bar { gap: 4px 8px; } }
.gl-modal.site-textsgen { max-width: 1080px; }
.site-textsgen [class~="gl-modal__body"] { color: var(--gl-text); }
.site-textsgen__grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(280px, 360px); gap: 24px; align-items: start; }
.site-textsgen__build { display: flex; flex-direction: column; gap: 22px; min-width: 0; }
.site-textsgen__part { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.site-textsgen__part > span { display: block; }
.site-textsgen__heading { margin: 0; font-family: var(--gl-ui); font-size: 15.5px; font-weight: 700; color: var(--gl-text); }
.site-textsgen__hint { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--gl-muted); }
.site-textsgen__side { position: sticky; top: 0; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.site-textsgen__side > .site-skinview { padding: 14px; min-height: 180px; border: 1px solid var(--gl-rule-soft); border-radius: var(--gl-radius); background: var(--gl-ink-deep); font-size: 16px; line-height: 1.7; }
.site-textsgen__people, .site-textsgen__msgs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.site-textsgen__person { display: grid; grid-template-columns: 40px minmax(0, 1fr) auto; gap: 8px 10px; align-items: center; }
.site-textsgen__pick { grid-column: 2 / -1; }
.site-textsgen__avatar { width: 40px; height: 40px; box-sizing: border-box; border-radius: 50%; background: var(--gl-panel-2) center / cover no-repeat; border: 1px solid var(--gl-rule); display: inline-flex; align-items: center; justify-content: center; font-family: var(--gl-display); font-weight: 600; font-size: 16px; color: var(--gl-muted); flex: none; }
.site-textsgen__avatar--quiet { border-style: dashed; }
.site-textsgen__tools { display: inline-flex; flex-wrap: nowrap; gap: 6px; align-items: center; }
.site-textsgen__tools .gl-btn, .site-textsgen__extras .gl-btn { white-space: nowrap; }
.site-textsgen__group { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: end; }
.site-textsgen__groupic { display: inline-flex; gap: 8px; align-items: center; }
.site-textsgen__msg { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border: 1px solid var(--gl-rule); border-left: 3px solid var(--gl-rule-strong); border-radius: var(--gl-radius); background: var(--gl-panel); min-width: 0; }
.site-textsgen__msg--sent { border-left-color: var(--gl-lamp); }
.site-textsgen__msghead { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px; align-items: center; }
.site-textsgen__n { min-width: 18px; font-size: 11px; font-weight: 700; letter-spacing: .08em; color: var(--gl-faint); }
.site-textsgen__who { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
.site-textsgen__order { display: inline-flex; gap: 2px; }
.site-textsgen__extras { display: flex; flex-wrap: wrap; gap: 6px 8px; align-items: center; }
.site-textsgen__extras .gl-select { width: auto; max-width: 100%; }
.site-textsgen__picrow { display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; gap: 10px; align-items: center; }
.site-textsgen__thumb { width: 48px; height: 48px; box-sizing: border-box; border-radius: 8px; background: var(--gl-panel-2) center / cover no-repeat; border: 1px solid var(--gl-rule); }
.site-textsgen__more { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; }
@media (max-width: 799.98px) {
  .site-textsgen__grid { grid-template-columns: minmax(0, 1fr); }
  .site-textsgen__side { position: static; }
}
@media (max-width: 639.98px) {
  .gl-modal__scrim:has(.site-textsgen) { padding: 8px; }
  .site-textsgen [class~="gl-modal__head"] { padding: 14px 14px 0; }
  .site-textsgen [class~="gl-modal__body"] { padding: 10px 14px 4px; }
  .site-textsgen [class~="gl-modal__foot"] { padding: 14px; }
}
/* The builder column's own width decides (it is narrow beside the preview on a tablet, too). */
.site-textsgen__build { container-type: inline-size; }
@container (max-width: 459.98px) {
  .site-textsgen__person { grid-template-columns: 40px minmax(0, 1fr); }
  .site-textsgen__person > .site-textsgen__tools { grid-column: 2; }
  .site-textsgen__picrow { grid-template-columns: 48px minmax(0, 1fr); }
  .site-textsgen__picrow > .site-textsgen__tools { grid-column: 1 / -1; flex-wrap: wrap; }
  .site-textsgen__group { grid-template-columns: minmax(0, 1fr); }
}
@container (max-width: 299.98px) {
  .site-textsgen__msghead { grid-template-columns: auto minmax(0, 1fr); }
  .site-textsgen__order { grid-column: 1 / -1; justify-content: flex-end; }
}
/* The writer's spine colour picker uses the bookshelf's .site-swatches / .site-spine rules above (one copy). */
/* Comments on a Library-posted work: threads indent under a hairline. */
.site-comments { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.site-comments--reply { margin: 4px 0 0 18px; padding-left: 12px; border-left: 1px solid var(--gl-rule-soft); }
.site-comment .site-friend { padding: 8px 4px; }
/* A work skin paints inside its own box: nothing it does can cover the reader's controls. */
#workskin { contain: paint; }
/* The author's form on a phone: less-used parts fold (Write.tsx PhoneFold; owner, 2026-09-25: "Can you
   make the phone options collapsible for things like,.. workskin and stuff to be shorter?"). Tablets and
   desktops never see the row, so the form there is exactly as it was. */
.site-fold__toggle { display: none; }
@media (max-width: 639.98px) {
  .site-fold__toggle { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 44px; padding: 10px 12px; font: inherit; font-size: 14px; font-weight: 700; color: var(--gl-text); text-align: left; background: var(--gl-panel-2); border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-sm); cursor: pointer; }
  .site-fold__toggle:focus-visible { outline: 2px solid var(--gl-lamp); outline-offset: 1px; }
  .site-fold__label { flex: 1 1 auto; min-width: 0; }
  .site-fold__set { flex: none; font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--gl-lamp); }
  [data-gl-theme="light"] .site-fold__set { color: var(--gl-amethyst); }
  .site-fold__caret { flex: none; color: var(--gl-muted); font-size: 12px; }
  .site-fold[data-open="false"] > .site-fold__body { display: none; }
  .site-fold[data-open="true"] > .site-fold__body { margin-top: 12px; }
  .site-fold .site-fold__heading { display: none; }
}

/* ==========================================================================
   MEEKO ADDITIONS. Everything above this line is Parchment's own web/site.css at the
   vendored pin (app/vendor/parchment/MANIFEST.json sourceCommit), byte for byte. Nothing
   below restyles a Parchment class.
   ========================================================================== */

/* ==========================================================================
   QUEUE TABLE — BUG-066 / E-25, host class on a canonical gl-table.

   The Acquire queue is a real table again ("no longer aligns as a table;
   links move between tasks"): one Action column, one fixed position, the cell
   rendered even when empty so the button can never wander.

   Below 640px the table becomes the same labelled-block layout Parchment gave
   .site-member-table (vendored site.css:938-956) — same breakpoint, same
   tokens, same data-label pseudo-header. Meeko-named because the vendored
   stylesheet is not edited and the queue surface is Meeko's own.
   ========================================================================== */
.site-queue-table td, .site-queue-table th { vertical-align: middle; }
.site-queue-table th:first-child, .site-queue-table td:first-child { padding-left: 8px; }
.site-queue-table td[data-label="Action"] { white-space: nowrap; width: 1%; }
/* History's selection column (1.1.x .task-check): as narrow as its checkbox on a desk; a
   labelled row with the box beside the label in the phone's block layout. */
.site-queue-table .site-queue-table__pick { width: 1%; }
/* Appearance's picture cards: two across on a phone (owner, 2026-09-24: ~3 screens at 390), whatever the group's desk grid. */
@media (max-width: 639.98px) { .meeko-pictures.site-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; } }
/* Owner, 2026-09-24 ("buttons all over the place"): a row's actions sit on ONE line at the right edge of the column, never stacked. */
.site-queue-table td[data-label="Action"] .site-inline-actions { flex-wrap: nowrap; justify-content: flex-end; }
/* Owner, 2026-09-24 ("progress bars stable"): the bar lives in the Task (History) or Progress
   (Acquire) column of an auto-layout table, which re-split its width whenever a row's badge or
   buttons changed (Queued -> Running -> Done, Cancel -> Dismiss): measured on Scriptorium during a
   real download, the track went 731 -> 839 -> 840 px at 1440. On a desk the table's layout is fixed
   and State/Action are sized to their widest real content (History: "Needs review" 95 px,
   Approve + Cancel 145 px; Acquire: Approve 72 px; + 12 px cell padding), so the bar's column never
   moves. Below 640px the card layout above already gives the bar the full row. */
@media (min-width: 640px) {
  .site-queue-table--steady { table-layout: fixed; width: 100%; }
  .site-queue-table--steady th.site-queue-table__pick { width: 44px; }
  .site-queue-table--steady th.site-queue-table__state { width: 112px; }
  .site-queue-table--steady th.site-queue-table__action { width: 92px; }
  .site-queue-table--steady th.site-queue-table__action--pair { width: 164px; }
}
.site-queue-cards td:last-child .site-inline-actions { flex-wrap: nowrap; }
@media (max-width: 639.98px) {
  .site-queue-table td.site-queue-table__pick { width: 100%; display: flex; align-items: center; gap: 8px; }
  .site-queue-table td.site-queue-table__pick::before { margin-bottom: 0; }
  .site-queue-table, .site-queue-table tbody, .site-queue-table tr, .site-queue-table td { display: block; width: 100%; }
  .site-queue-table thead { display: none; }
  .site-queue-table tr {
    border: 1px solid var(--gl-rule);
    border-radius: var(--gl-radius-sm);
    background: var(--gl-panel);
    padding: 12px;
    margin-bottom: 12px;
  }
  .site-queue-table td, .site-queue-table tbody tr ~ tr td { border: 0; padding: 6px 0; min-width: 0; overflow-wrap: anywhere; }
  .site-queue-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 4px;
    font-size: 12px;
    color: var(--gl-muted);
  }
  .site-queue-table td[data-label="Action"] { width: 100%; }
  /* 2026-09-24 (owner: "that much scrolling is unacceptable"): a phone card is the task with its bar, then
     the state and the actions on ONE row - not three labelled blocks. Half the height per job. */
  .site-queue-table tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px; align-items: center; }
  .site-queue-table td[data-label="Task"] { grid-column: 1 / -1; padding-bottom: 8px; }
  .site-queue-table td[data-label]::before { display: none; }
  .site-queue-table td[data-label="Action"] { width: auto; justify-self: end; }
  .site-queue-table td[data-label="Action"] .site-inline-actions { justify-content: flex-end; flex-wrap: wrap; }
  /* Acquire's live queue on a phone: the same card as History's (task, then its bar, each on a full row;
     state and action on one row). In the two-column grid its running row's bar cell (min-width 220px)
     squeezed the work's name to one letter per line and stood the badge on end (Playwright, 390px). */
  .site-queue-table--steady td[data-label="Work"], .site-queue-table--steady td[data-label="Progress"] { grid-column: 1 / -1; min-width: 0 !important; }
  /* The Review queue's table (the vendored DS Table): the same card below 640px - work on top, staged and
     the Approve/Cancel pair on one row - so nothing runs off the right edge (coordinator, 2026-09-24). */
  .site-queue-cards table, .site-queue-cards tbody, .site-queue-cards tr, .site-queue-cards td { display: block; width: 100%; }
  .site-queue-cards thead { display: none; }
  .site-queue-cards tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px; align-items: center; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-sm); background: var(--gl-panel); padding: 12px; margin-bottom: 12px; }
  .site-queue-cards td { border: 0; padding: 4px 0; min-width: 0; overflow-wrap: anywhere; }
  .site-queue-cards td:first-child { grid-column: 1 / -1; padding-bottom: 8px; }
  .site-queue-cards td:last-child { justify-self: end; }
  .site-queue-cards td:last-child .site-inline-actions { justify-content: flex-end; flex-wrap: wrap; }
  .site-queue-cards .gl-tablewrap { overflow: visible; }
}


/* =========================================================================
   MEEKO PROGRESS — the job bar, the raccoon and the steps (app/meeko/files/meeko/progress.tsx).
   Meeko-only classes; nothing of Parchment's is restyled.
   Owner, 2026-09-24: "Purple fills up then gold when done ... raccoons follow books and leave
   check marks as emoji for the statuses."
   ========================================================================= */
.meeko-progress { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.meeko-progress__rail { position: relative; padding: 24px 11px 0; }
.meeko-progress__marks { position: absolute; top: 0; left: 11px; right: 11px; height: 22px; }
.meeko-progress__mark, .meeko-progress__raccoon {
  position: absolute; top: 0; transform: translateX(-50%); width: max-content; line-height: 22px; font-size: 14px;
  transition: opacity .3s ease;
}
.meeko-progress__raccoon { font-size: 18px; transition: left .6s ease; z-index: 1; }
.meeko-progress__track {
  position: relative; height: 10px; border-radius: 999px; overflow: hidden;
  background: var(--gl-wash); border: 1px solid var(--gl-rule);
}
.meeko-progress__fill {
  display: block; height: 100%; border-radius: inherit;
  background: var(--gl-amethyst); transition: width .6s ease, background-color .5s ease;
}
/* The gold itself, not --gl-lamp: canvas.css darkens that token to #8A5A0B in the light theme for
   text contrast, which turned a finished bar brown. site.css uses #E3B85C literally the same way. */
.meeko-progress--done .meeko-progress__fill { background: #E3B85C; }
.meeko-progress--waiting .meeko-progress__fill { background: color-mix(in srgb, var(--gl-amethyst) 55%, var(--gl-wash)); }
.meeko-progress--failed .meeko-progress__fill,
.meeko-progress--stopped .meeko-progress__fill { background: color-mix(in srgb, var(--gl-amethyst) 35%, var(--gl-wash)); }
.meeko-progress__labels { position: relative; height: 18px; margin-top: 4px; }
.meeko-progress__labels span {
  position: absolute; transform: translateX(-50%); font-size: 11px; color: var(--gl-muted); white-space: nowrap;
}
.meeko-progress__labels span.is-done { color: var(--gl-text); }
.meeko-progress__labels span.is-active { color: var(--gl-text-strong); font-weight: 600; }
.meeko-progress__line { display: flex; justify-content: space-between; gap: 12px; margin: 0; font-size: 13px; }
.meeko-progress__what { min-width: 0; overflow-wrap: anywhere; color: var(--gl-text); }
.meeko-progress__label { flex: 0 0 auto; color: var(--gl-muted); font-variant-numeric: tabular-nums; }
.meeko-progress--compact .meeko-progress__rail { padding-top: 26px; }
.meeko-progress--compact .meeko-progress__marks { height: 24px; }
.meeko-progress--compact .meeko-progress__mark { font-size: 11px; line-height: 24px; }
.meeko-progress--compact .meeko-progress__raccoon { font-size: 15px; line-height: 24px; display: flex; align-items: center; height: 24px; }
/* The job's character medallion on the fill's edge (progress.tsx ProgressMedallion): 38px on the
   job page, 24px in a list, 22px on the timeline's working row. Owner, 2026-09-24: medallions for progress. */
.meeko-progress__medallion { object-fit: cover; border-radius: 50%; }
.meeko-progress--compact .meeko-progress__medallion { width: 24px; height: 24px; }
.meeko-steps__glyph .meeko-progress__medallion { width: 22px; height: 22px; vertical-align: middle; box-shadow: 0 0 0 2px #E3B85C, 0 1px 3px rgba(17, 11, 29, .3); }
.meeko-progress--compact .meeko-progress__track { height: 7px; }
.meeko-progress--compact .meeko-progress__line { font-size: 12px; }
/* A phone has no room for eight step names under one bar: they overlapped ("Start Chapters Images EPUB").
   Only the current step's name is drawn there; the step count on the line below still says "Step 2 of 8". */
@media (max-width: 639.98px) {
  .meeko-progress__labels span { font-size: 10px; }
  .meeko-progress__labels span:not(.is-active) { display: none; }
}
/* A job's own page: the house paintings instead of emoji (progress.tsx ART). */
.meeko-progress--art .meeko-progress__rail { padding: 46px 19px 0; }
.meeko-progress--art .meeko-progress__marks { left: 19px; right: 19px; height: 40px; }
.meeko-progress--art .meeko-progress__mark, .meeko-progress--art .meeko-progress__raccoon { line-height: 40px; height: 40px; display: flex; align-items: center; }
.meeko-progress__art { display: block; flex: 0 0 auto; border-radius: 50%; object-fit: cover; box-shadow: 0 1px 3px rgba(17, 11, 29, .28); }
.meeko-progress__art--edge { box-shadow: 0 0 0 2px #E3B85C, 0 2px 6px rgba(17, 11, 29, .35); }
@media (max-width: 639.98px) {
  .meeko-progress--art .meeko-progress__art { width: 22px; height: 22px; }
  .meeko-progress--art .meeko-progress__art--edge { width: 30px; height: 30px; }
}
@media (prefers-reduced-motion: reduce) {
  .meeko-progress__fill, .meeko-progress__raccoon, .meeko-progress__mark { transition: none; }
}
.meeko-steps { list-style: none; margin: 0; padding: 0; }
.meeko-steps__row {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; gap: 10px; align-items: baseline;
  padding: 10px 12px; border-radius: 8px;
}
.meeko-steps__row + .meeko-steps__row { border-top: 1px solid var(--gl-rule-soft); }
.meeko-steps__row.is-done { background: transparent; }
.meeko-steps__row.is-failed { background: color-mix(in srgb, var(--gl-danger, #B3261E) 8%, transparent); }
.meeko-steps__row.is-active { background: color-mix(in srgb, var(--gl-amethyst) 9%, transparent); }
.meeko-steps__glyph { font-size: 16px; text-align: center; }
.meeko-steps__what { font-size: 14px; overflow-wrap: anywhere; }
.meeko-steps__when { font-size: 12px; color: var(--gl-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* =========================================================================
   LOADING MEDALLION — a canon character medallion while a page waits
   (app/meeko/files/meeko/LoadingMedallion.tsx, and the boot page in web/index.html).
   Owner, 2026-09-24: "It was supposed to be the character medallions."
   Meeko-only classes. Motion is a slow breath (opacity and a 3% swell); under
   prefers-reduced-motion the medallion is simply still. The skeleton bars a wait drew before
   the medallions stay, full width, under it (.meeko-loading__skeleton).
   ========================================================================= */
.meeko-loading {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 36px 16px; text-align: center; min-width: 0;
}
.meeko-loading--gate { min-height: 40vh; padding: 56px 16px; }
.meeko-loading--inline { flex-direction: row; justify-content: flex-start; gap: 12px; padding: 8px 0; text-align: left; }
.meeko-loading__medallion {
  display: block; flex: 0 0 auto; border-radius: 50%; object-fit: cover;
  width: 128px; height: 128px; max-width: 100%;
  box-shadow: 0 2px 14px rgba(17, 11, 29, .35);
  animation: meeko-medallion-breathe 2.8s ease-in-out infinite;
}
.meeko-loading--gate .meeko-loading__medallion { width: 160px; height: 160px; }
.meeko-loading--inline .meeko-loading__medallion { width: 48px; height: 48px; box-shadow: 0 1px 4px rgba(17, 11, 29, .3); }
.meeko-loading__label { font-size: 14px; color: var(--gl-muted); }
.meeko-loading__skeleton { align-self: stretch; width: 100%; min-width: 0; text-align: left; }
.meeko-loading--inline .meeko-loading__skeleton { flex: 1 1 auto; width: auto; align-self: center; }
/* The reader's opening keeps its own skeleton column rules (.reader__skeleton) on the bars. */
.meeko-loading.reader__skeleton { gap: 14px; }
.meeko-loading.reader__skeleton .meeko-loading__skeleton { display: flex; flex-direction: column; gap: 10px; }
.meeko-loading--inline .meeko-loading__label { font-size: 13px; }
@media (max-width: 639.98px) {
  .meeko-loading__medallion { width: 112px; height: 112px; }
  .meeko-loading--gate .meeko-loading__medallion { width: 136px; height: 136px; }
  .meeko-loading--inline .meeko-loading__medallion { width: 40px; height: 40px; }
}
@keyframes meeko-medallion-breathe {
  0%, 100% { opacity: .78; transform: scale(.97); }
  50% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .meeko-loading__medallion { animation: none; opacity: 1; transform: none; }
}
/* The boot page (web/index.html): the window's first paint, before the bundle mounts and
   replaces it. Fills the window in the theme's own ground so it never opens on an empty box. */
.meeko-boot {
  min-height: 100vh; min-height: 100dvh; color: var(--gl-text);
  background-color: var(--gl-ink, #110B1D); background-image: var(--gl-page-wash); font-family: var(--gl-ui, Karla, system-ui, sans-serif);
}
.meeko-boot .meeko-loading { min-height: 100vh; min-height: 100dvh; padding: 24px 16px; }
/* Static: the boot page's medallion is still - it is plain HTML, drawn before any script runs. */
.meeko-boot .meeko-loading__medallion { width: 176px; height: 176px; animation: none; opacity: 1; transform: none; }
@media (max-width: 639.98px) { .meeko-boot .meeko-loading__medallion { width: 144px; height: 144px; } }

/* Moved here by the re-vendor (2026-09-24): the lanes had placed these Meeko blocks inside Parchment's own
   sheet; everything above the MEEKO ADDITIONS line is the vendored site.css byte for byte, so Meeko rules
   live below it. Same rules, later in the cascade. */
/* Meeko: Settings > Residents & staff (owner, 2026-09-24: "layout border/alignment fixed"). CSS columns drew a rule down the middle and
   staggered the cards; this is a grid of two equal columns from 960px, one below, rows aligned, no rule, the retired list full width. */
.site-panelcols--residents { columns: auto; column-rule: 0; display: grid; grid-template-columns: 1fr; gap: var(--gl-space-4); align-items: start; }
.site-panelcols--residents > * { margin: 0; min-width: 0; }
.site-panelcols--residents > .site-panelcols__full { grid-column: 1 / -1; }
@media (min-width: 960px) { .site-panelcols--residents { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.site-panelcols--residents .site-resident-editor .site-inline-actions { flex-wrap: wrap; row-gap: 6px; }
/* Meeko: the Settings head and Profile on a desktop with no card show a placeholder, never a resident medallion (owner, 2026-09-24). Same ring as the avatar it stands in for. */
.site-settings-head__placeholder, .site-panel .site-portrait-placeholder { box-shadow: 0 0 0 2px var(--gl-rule); }
/* Meeko (coordinator, 2026-09-24: the first-visit Welcome covered a row's buttons until Skip): the banner is
   in the page's flow at the top of <main> (app-shell.patch welcome-in-flow), never fixed over controls.
   Parchment's own stylesheet keeps it fixed, so the DOM move changes nothing there. */
.site-main > .site-howto-banner { position: static; max-width: none; width: min(1200px, calc(100% - 32px)); margin: 16px auto 0; box-shadow: var(--gl-shadow-card); }

/* =========================================================================
   MEEKO PANEL GRID — About and Privacy (app/meeko/files/meeko/pages.tsx).
   Owner, 2026-09-24: "rearrange the boxes till they're even and remove the border between
   them". Parchment's .site-panelcols is a masonry of CSS columns with a column rule, so its two
   columns end at different heights. This is a grid of matched rows instead: one column on a
   phone or tablet, two from 960px (the breakpoint .site-panelcols--residents uses), each row's
   boxes the same height, no rule. Spacing is Parchment's own: --gl-space-4 between rows (the
   panelcols row margin) and --gl-space-6 between columns (the panelcols column gap).
   Meeko-only class; nothing of Parchment's is restyled.
   ========================================================================= */
.meeko-panelgrid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gl-space-4) var(--gl-space-6); align-items: stretch; }
.meeko-panelgrid > * { margin: 0; min-width: 0; }
.meeko-panelgrid > .meeko-panelgrid__full { grid-column: 1 / -1; }
@media (min-width: 960px) { .meeko-panelgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Owner, 2026-09-24: "that border between looks odd in every page, remove it throughout". Parchment's
   candidate no longer draws .site-panelcols' column rule (repair2/spacing); until the next re-vendor
   carries that line, Meeko's own users of the class — Admin › Meeko (OwnerSettings.tsx) and
   Appearance › Words / Meeko page headings (routes/Appearance.tsx) — drop it here. Same value as
   Parchment's; a no-op once the vendored line above matches. */
.site-panelcols { column-rule: 0; }

/* Meeko (owner, 2026-09-25, the Acquire banner): the INTAKE sign hangs at the top of the picture and the hero's
   height shows the middle band, so the centre crop cut the sign in half. object-position alone moves the focal
   point up; the file, the hero's size and every other banner are untouched (sections.tsx names each hero). */
.site-hero--meeko-acquire .gl-hero__art { object-position: 50% 12%; }

/* ==========================================================================
   NOTHING ALONE ON A LINE — the final visual review's Meeko items (owner, 2026-09-25: "I hate things on
   one line"). Meeko-only classes; nothing of Parchment's is restyled. Placed above MY FICS on purpose: that
   block's phone @media is not closed, so anything after it applies only below 640px.
   ========================================================================== */
/* The desk's stat tiles (Library.tsx LibraryHomeRoute): one even row on a desk (5 on the Archivist, 4 on the
   Librarian) instead of auto-fit's 4 + 1 at 820; on a phone 3 + 2 (the Archivist) or 2 + 2. Each tile spans
   three shared rows (subgrid), so a label that wraps to two lines keeps its number level with its neighbours'. */
.meeko-deskstats.site-grid--150 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.meeko-deskstats--4.site-grid--150 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* The grid's row gap would open inside each tile (its three rows are the grid's), so the gap between two rows
   of tiles is the second row's top margin instead: the same --gl-space-4. */
.meeko-deskstats.site-grid--150 { row-gap: 0; }
.meeko-deskstats > .gl-stat { display: grid; grid-row: span 3; grid-template-rows: subgrid; row-gap: 0; align-content: start; }
@media (max-width: 639.98px) {
  .meeko-deskstats.site-grid--150 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  /* Three across leaves ~100px a tile: the side padding is 12px instead of 24px there, so a label keeps its words together. */
  .meeko-deskstats > .gl-stat { grid-column: span 2; padding-left: 12px; padding-right: 12px; }
  .meeko-deskstats > .gl-stat:nth-child(n + 4) { grid-column: span 3; margin-top: var(--gl-space-4); }
  .meeko-deskstats--4.site-grid--150 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .meeko-deskstats--4 > .gl-stat, .meeko-deskstats--4 > .gl-stat:nth-child(n + 4) { grid-column: auto; padding-left: var(--gl-space-5); padding-right: var(--gl-space-5); }
  .meeko-deskstats--4 > .gl-stat:nth-child(n + 3) { margin-top: var(--gl-space-4); }
}
/* Meeko view tabs (sections.tsx MeekoViews): where a wrapping row breaks so no tab is left alone. A break is
   a zero-width full line; a balanced row draws the 4px line gap with it instead of row-gap, so the spacing
   is what it was. Tight trims each tab's side padding by 3px, only when the even break needs it. */
.meeko-views--balanced { row-gap: 0 !important; }
.meeko-views__break { flex: 0 0 100%; height: 4px; }
.meeko-views--tight > .gl-btn { padding-left: 9px; padding-right: 9px; }
/* History (Library.tsx HistoryRoute): the Show / Order filters are bare on the page, not in a panel, so the
   panel's side padding inset them from the page edge and the job list. */
.meeko-history-filters { padding-left: 0; padding-right: 0; }
/* History's selection row: "Select …" and "With selected …" each wrap as a whole on a desk, so Clear selected
   never sits alone under the row (820). On a phone the row flows as before. */
.meeko-select-row__group { display: inline-flex; flex-wrap: nowrap; align-items: center; gap: 8px; }
@media (max-width: 639.98px) { .meeko-select-row__group { display: contents; } }

/* ==========================================================================
   MY FICS (owner, 2026-09-25): the catalogue's Whole library / My fics rows and, under My fics,
   the 1.1.5A Library tab's controls on each result (app/meeko/files/meeko/myfics.tsx). Meeko-only:
   the counted patch that draws them is applied to the desktop's staged source alone.
   ========================================================================== */
.meeko-myfics__bar { display: flex; flex-direction: column; gap: 10px; padding: 12px 14px; }
.meeko-myfics__bar .site-inline-actions { align-items: center; }
.meeko-myfics__bar .site-label { min-width: 64px; }
.meeko-myfics__scope { display: inline-flex; align-items: center; gap: 6px; }
.meeko-myfics__scope .gl-select, .meeko-myfics__scope select { min-height: 32px; font-size: 13px; }
.meeko-myfic { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.meeko-myfic__bar { display: flex; flex-direction: column; gap: 6px; }
.meeko-myfic__bar--row { align-items: flex-end; }
.meeko-myfic__pick { display: inline-flex; align-items: center; gap: 6px; color: var(--gl-muted); }
.meeko-myfic__actions { gap: 6px; }
.meeko-myfic__actions .gl-btn, .meeko-myfic__actions .gl-btn--sm { white-space: nowrap; }
.site-listitem__action .meeko-myfic__bar--row { align-items: flex-start; }
@media (max-width: 639.98px) {
  .meeko-myfics__bar .site-label { min-width: 0; width: 100%; }
  .meeko-myfic__bar--row { align-items: flex-start; }
}

/* Owner, 2026-09-25: "first and last for all pagination options", "Weird buttons" (the pager's Page field
   cut off at the right edge at phone width) and "Make sure those navigation buttons are fixed across the site".
   Parchment's pager (repair2/navkeep) now wraps instead of scrolling inside its bar, gives First/Last the page
   links' 36px frame, and on a phone puts the numbered window on the first line with First/Last and the jump
   field on the second. Every Meeko list pages through that one vendored SitePager, so until the next re-vendor
   brings the component and these lines, Meeko's pagers (queue, History, Acquire, Subscriptions, Tools, Updates,
   Maintenance, Cover choices, and the vendored pages) get the same layout here. Same values as Parchment's; a
   no-op once the vendored sheet above carries them (the « » buttons then become Parchment's named First/Last). */
.site-pagerbar__controls { flex-wrap: wrap; overflow: visible; min-width: 0; }
.site-pagerbar__controls .gl-pagination { flex-wrap: wrap; row-gap: 6px; }
.site-pagerbar__controls .gl-pagination__link { min-height: 36px; display: inline-flex; align-items: center; }
.site-pagerbar__controls > .gl-btn { min-height: 36px; padding: 6px 12px; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-sm); background: transparent; color: var(--gl-link); font-size: 13px; font-weight: 400; }
.site-pagerbar__controls > .gl-btn:disabled { color: var(--gl-muted); }
@media (max-width: 639.98px) {
  .site-pagerbar__controls .gl-pagination { order: -1; flex: 1 0 100%; }
}

/* Owner, 2026-09-25: "fix the bugs" - the Meeko section pills ran past the right edge at phone width (three
   pills out of sight in a sideways scroller on the queue page). Below 640px the row becomes even columns
   (--meeko-tab-cols from meekoTabColumns in app/meeko/files/meeko/sections.tsx, chosen so no pill is alone on
   the last line); wider, the row is exactly as before. Same pill, same tokens; nothing else moves. */
@media (max-width: 639.98px) {
  .site-tracker-nav.meeko-tabs { display: grid; grid-template-columns: repeat(var(--meeko-tab-cols, 3), minmax(0, 1fr)); overflow-x: visible; }
  .site-tracker-nav.meeko-tabs .site-tagpill { justify-content: center; text-align: center; min-width: 0; white-space: normal; overflow-wrap: normal; }
}

/* ==========================================================================
   THE TOP BAR WITH THE MEEKO TAB, 1024-1279px (card-gating, 2026-09-25).
   Owner rule, verbatim: "never let small things like that happen, multiple lines or off screen".
   Parchment switches from its row nav to the inline nav at 1024px. The desktop's inline nav carries one tab more
   than Parchment's (Meeko, after Friends), and at 1024 it ran 2px past the edge, cutting the account name off.
   Where the Meeko tab is drawn, the band up to 1280px keeps Parchment's own tablet layout (the one it uses from
   640 to 1024) instead. Parchment's own nav, without the Meeko tab, is untouched.
   Re-fit at Parchment 531a092 (nav-0925, owner: "I hate the tablet view?"): there is no second nav row any more;
   from 640 to 1023 Parchment's header is one row with the menu behind Menu, dropping down from the right. So in
   this band the inline nav gives way to Menu and the same drop-down, and the lantern stays in the header.
   ========================================================================== */
@media (min-width: 1024px) and (max-width: 1279.98px) {
  body:has(.site-nav-inline a[href="/meeko"]) .site-nav-inline { display: none; }
  body:has(.site-nav-inline a[href="/meeko"]) .site-menu-btn-wrap { display: flex; }
  body:has(.site-nav-inline a[href="/meeko"]) .site-drawer { position: absolute; z-index: 45; top: 100%; right: 12px; width: min(360px, calc(100vw - 24px)); max-height: calc(100vh - 80px); overflow-y: auto; overscroll-behavior: contain; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius); box-shadow: var(--gl-shadow-frame); }
}

/* ==========================================================================
   THE PARCHMENT ADDRESS ON A LOCKED ARCHIVIST'S SIGN-IN PAGE (meeko/parchmentAddress.tsx; owner, 2026-09-25:
   "in mine yeah"). One row at every width - the address and Change, or the field and its button - so nothing sits
   alone on a line ("never let small things like that happen, multiple lines or off screen").
   ========================================================================== */
.meeko-parchment-address { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.meeko-parchment-address__row { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; }
.meeko-parchment-address__row > span { min-width: 0; overflow-wrap: anywhere; }
.meeko-parchment-address__row .gl-input, .meeko-parchment-address__row input { flex: 1 1 auto; min-width: 0; }
.meeko-parchment-address__row .gl-btn { flex: 0 0 auto; white-space: nowrap; }

/* ==========================================================================
   YOUR OWN PICTURE FOR GENERATED COVERS (Meeko settings > Covers; owner, 2026-09-25: "give them the option to use
   their own?"). Preview, words and buttons share one row; on a phone the buttons stay together beside the words.
   ========================================================================== */
.meeko-own-cover { display: flex; align-items: center; gap: 12px; margin-top: 12px; padding: 12px 14px; border: 1px solid var(--gl-rule); border-radius: var(--gl-radius-sm); min-width: 0; }
.meeko-own-cover__preview { flex: 0 0 auto; width: 64px; height: 94px; object-fit: cover; border-radius: 3px; }
.meeko-own-cover__text { flex: 1 1 auto; min-width: 0; margin: 0; }
.meeko-own-cover__actions { flex: 0 0 auto; display: inline-flex; gap: 6px; flex-wrap: nowrap; }
.meeko-own-cover__actions .gl-btn { white-space: nowrap; }
@media (max-width: 479.98px) {
  .meeko-own-cover { flex-wrap: wrap; }
  .meeko-own-cover__text { flex: 1 1 calc(100% - 76px); }
  .meeko-own-cover__actions { flex: 1 1 100%; justify-content: flex-end; }
}

/* ==========================================================================
   THE TOP BAR ON A PHONE UNDER 420px (card-gating round 2, 2026-09-25).
   Owner rule, verbatim: "never let small things like that happen, multiple lines or off screen".
   Measured live on the Windows canary at 320 with a Library Card: the desktop's wordmark ("Meeko the Archivist",
   164px) is longer than Parchment's ("The Great Library", 147px), and Parchment's phone rule lets the nav shrink
   with it (flex: 0 1 auto, min-width: 0), so the lantern, the portrait and Menu (156px) were squeezed into 135px
   and Menu ran 6px off screen. Parchment's own words for this band are "the wordmark gives way first": here the
   nav keeps its width and the wordmark takes Parchment's ellipsis. No change where the row already fits.
   ========================================================================== */
@media (max-width: 419.98px) {
  .site-topbar .gl-topbar__nav { flex-shrink: 0; }
}

/* Meeko: selected Lane2 90ea04e capability styles; existing tokens and layout retained. */
.site-rw-intro { justify-content: space-between; align-items: center; gap: 12px 24px; margin-bottom: 18px; }
.site-rq-addon { display: flex; flex-direction: column; gap: 10px; padding: 14px 18px 16px; border-radius: var(--gl-radius-md, 10px); background: var(--gl-wash); }
.site-rq-addon__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px 16px; }
@media (max-width: 899.98px) { .site-rq-addon__grid { grid-template-columns: minmax(0, 1fr); } }
.reader__chapter-kudos { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px 14px; margin: 28px 0 8px; }

.site-recruit-modal__to { display: flex; align-items: center; gap: 12px; }
.site-recruit-modal__who { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.site-recruit-modal__to .gl-badge { flex: 0 0 auto; }

/* Visible fallback title correction, paired Member Works capture. */
.site-nocover { container-type: inline-size; }
.site-nocover__title { display: block; width: 100%; font-weight: 600; overflow-wrap: normal; word-break: keep-all; hyphens: none; text-wrap: balance; }
