/* ============================================================================
 * Pagehawk shared design system — matched to GANDER (src/app), the base app.
 * ONE source of truth: this file styles the iPhone companion today and is the
 * skin the Mac reader inherits when web/reader.js is dropped into Gander.
 * Gander's exact tokens: ivory #FBF7EE / gold #C8A872 / #B8860B, Cambria serif.
 * Two skins (light = "Ivory", dark = "Noir"), the subset of Gander's six schemes
 * a phone needs. No web fonts (matches Gander's system-serif stack; less to load).
 * ========================================================================== */
:root{
  --serif:Cambria,'Big Caslon','Hoefler Text',Georgia,serif;
  --sans:-apple-system,'Inter',system-ui,sans-serif;
  --r:16px; --r-sm:11px; --r-lg:22px;
  --safe-top:env(safe-area-inset-top,0px);
  --safe-bot:env(safe-area-inset-bottom,0px);
  --safe-left:env(safe-area-inset-left,0px);
  --safe-right:env(safe-area-inset-right,0px);
  /* source brand colors (shared) */
  --c-drop:#0061FF; --c-gd:#1FA463; --c-od:#0364B8; --c-ic:#3693F3; --c-local:#8A8F98; --c-sample:#C8A872; --c-files:#5E5CE6;
}
html[data-theme="light"]{
  --bg1:#FBF7EE; --bg2:#EFE7D5; --window:#F4EEE2;
  --bg-grad:radial-gradient(1200px 760px at 82% -10%, #FCFAF3 0%, #F4EEE2 58%);
  --surface:#FFFFFF; --surface-2:#FBFAF7; --surface-3:#F6F1E6;
  /* --ink-3 darkened #8C8678 -> #716C61 (2026-07-20): muted label text measured 3.14:1 on the
     ivory surface, under the WCAG AA 4.5:1 bar for normal text ("or bring your own files").
     Now 4.52:1 on ivory / 5.22:1 on white. Hue and saturation are unchanged — lightness only. */
  /* --ink-3 darkened again #716C61 -> #676359 (2026-09-09, measured on RENDERED PIXELS by
     ios tools/rendered_contrast_audit.py: `.welcome .or` sat at 4.48:1 on the #F3EDE1 gradient
     position behind it; now 5.14:1, worst light ground 4.87:1). RESTORED 2026-09-23: that fix
     landed only in the iOS copy (pagehawk-ios 3b777a4), never here, so the next web-bundle
     re-derive (pagehawk-ios 9137313, Sep 14) put #716C61 back and the audit read 7 of 106 lines
     under AA again. This file is the source; a theme fix made downstream is lost at the next sync. */
  --ink:#23211C; --ink-2:#5A554A; --ink-3:#676359; --ink-4:#B6AE9C;
  /* Error ink, themed. The connectors hardcoded #a85454, which measures 4.49:1 on
     this cream ground and 3.3:1 on the dark one: a fail in both, worse in dark. */
  --danger:#9A2F2F;
  --line:#E7DFCE; --line-2:#EFE9DB;
  --accent:#23211C; --accent-ink:#FBF7EE;     /* primary button: ink-on-ivory */
  /* --gold-deep darkened #B8860B -> #8D6608 (2026-07-20): as TEXT it measured 3.25:1 on white
     and 2.82:1 on ivory (the "PRO" badge and the "by Epiphani Studios" byline), under the AA
     4.5:1 bar. Now 5.20:1 / 4.50:1. The four places it is a BUTTON BACKGROUND all carry white
     text, so those improve too (3.25:1 -> 5.20:1). Same hue, same saturation, lower lightness. */
  --gold:#C8A872; --gold-deep:#8D6608; --gold-soft:#F1E7CE;
  --ring-o:rgba(120,96,48,.22); --ring-m:rgba(120,96,48,.40); --ring-i:#C8A872;
  --shadow:0 2px 6px rgba(58,52,38,.06),0 10px 30px rgba(58,52,38,.08);
  --shadow-h:0 6px 14px rgba(58,52,38,.13),0 18px 44px rgba(58,52,38,.15);
  --scrim:linear-gradient(180deg,rgba(244,238,226,0),rgba(244,238,226,.9) 50%,var(--window));
}
html[data-theme="dark"]{
  --bg1:#1b1b1d; --bg2:#070708; --window:#1b1b1d;
  --bg-grad:radial-gradient(1100px 720px at 82% -12%, #232325 0%, #131315 62%);
  --surface:#202022; --surface-2:#252527; --surface-3:#2A2A2C;
  /* --ink-3 lightened #8A857B -> #9A958D (2026-09-09, pagehawk-ios 2c9f6b8): the muted tier under
     every document card, row subtitle, percent and sheet-row detail failed AA in NOIR on card
     grounds (4.43 / 4.17 / 3.90 on --surface / -2 / -3); now 5.47 / 5.14 / 4.81. Hue and saturation
     unchanged. RESTORED 2026-09-23 for the same reason as the light value above. */
  --ink:#F0EBE1; --ink-2:#C0BBB0; --ink-3:#9A958D; --ink-4:#5E5A52;
  --danger:#F09A9A;
  --line:#33312E; --line-2:#2A2926;
  --accent:#CEB06B; --accent-ink:#1b1b1d;      /* primary button: gold-on-noir */
  --gold:#CEB06B; --gold-deep:#D8BC8C; --gold-soft:#2A2417;
  --ring-o:rgba(246,241,231,.20); --ring-m:rgba(246,241,231,.40); --ring-i:#CEB06B;
  --shadow:0 2px 8px rgba(0,0,0,.34),0 14px 38px rgba(0,0,0,.42);
  --shadow-h:0 8px 18px rgba(0,0,0,.5),0 22px 54px rgba(0,0,0,.56);
  --scrim:linear-gradient(180deg,rgba(27,27,29,0),rgba(27,27,29,.9) 50%,var(--window));
}
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{
  background:var(--bg-grad) fixed; background-color:var(--window); color:var(--ink);
  font-family:var(--sans); -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  -webkit-tap-highlight-color:transparent; overscroll-behavior:none;
  transition:background-color .4s ease,color .4s ease;
}
a{color:var(--gold-deep);text-decoration:none}
button{font-family:inherit}
#phk-gear{display:none!important}            /* settings.js floating gear — companion owns its own */

/* ---- concentric-ring brand mark (Gander's signature loader motif) ---- */
.rings{position:relative;flex:0 0 auto}
.rings span{position:absolute;left:50%;top:50%;border-radius:50%;transform:translate(-50%,-50%)}
.rings .o{border:2.5px solid var(--ring-o)}
.rings .m{border:2.5px solid var(--ring-m)}
.rings .i{border:3px solid var(--ring-i)}
.rings .c{background:var(--ring-i)}
.rings.lg{width:88px;height:88px}
.rings.lg .o{width:88px;height:88px;animation:apf 2.6s ease-in-out infinite}
.rings.lg .m{width:58px;height:58px;animation:apf 2.6s ease-in-out infinite .2s}
.rings.lg .i{width:30px;height:30px;border-width:5px;animation:apf 2.6s ease-in-out infinite .4s}
.rings.lg .c{width:12px;height:12px;animation:apc 2.6s ease-in-out infinite}
.rings.sm{width:30px;height:30px}
.rings.sm .o{width:30px;height:30px}.rings.sm .m{width:20px;height:20px}.rings.sm .i{width:10px;height:10px;border-width:2.5px}.rings.sm .c{width:4px;height:4px}
@keyframes apf{0%,100%{opacity:.42;transform:translate(-50%,-50%) scale(1.12)}50%{opacity:1;transform:translate(-50%,-50%) scale(.9)}}
@keyframes apc{0%,100%{box-shadow:0 0 0 0 rgba(200,168,114,0);opacity:.75}50%{box-shadow:0 0 14px 2px rgba(200,168,114,.55);opacity:1}}
@media (prefers-reduced-motion:reduce){.rings span{animation:none!important}}

/* ---- gold label (Epiphani byline pattern) ---- */
.gold-label{font:600 10px var(--sans);letter-spacing:.22em;text-transform:uppercase;color:var(--gold-deep)}

/* ---- app frame ---- */
.app{min-height:100dvh;display:flex;flex-direction:column;padding-left:var(--safe-left);padding-right:var(--safe-right)}
.top{position:sticky;top:0;z-index:40;display:flex;align-items:center;gap:12px;
  padding:calc(var(--safe-top) + 13px) 18px 13px;
  background:color-mix(in srgb,var(--window) 80%,transparent);backdrop-filter:saturate(1.4) blur(16px);
  border-bottom:1px solid var(--line-2)}
.brand{display:flex;align-items:center;gap:11px;min-width:0;overflow:hidden}
.brand .brand-mark{width:30px;height:30px;border-radius:8px;flex:0 0 auto;display:block}
.brand .word{font-family:var(--serif);font-weight:600;font-size:23px;letter-spacing:.01em;line-height:1.05;color:var(--ink);
  min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.brand .word small{display:block;margin-top:3px}
/* The actions are fixed-size; without this they compress and the wordmark slides under them. */
.top-actions{margin-left:auto;display:flex;align-items:center;gap:8px;flex:0 0 auto}
/* The home search pill: phone and portrait-tablet only. Desktop has Cmd-K (palette.js) and the
   library's own search; a second field there would be a duplicate control. */
.home-search{display:none;align-items:center;gap:9px;margin:10px 16px 0;height:46px;width:calc(100% - 32px);border:1px solid var(--line);border-radius:15px;background:var(--surface);color:var(--ink-3);font:16px var(--sans);padding:0 14px;text-align:left;cursor:pointer}
.home-search svg{width:18px;height:18px;flex:0 0 auto;color:var(--ink-2)}
.home-search:active{background:var(--surface-2)}
/* body.touch is stamped by app.js from (pointer:coarse), or by the ?phktouch=1 harness override the
   reader already honours, so a headless capture exercises the same rule the phone gets. */
@media (max-width:900px){body.touch .home-search{display:flex}}
/* Phone widths: the action cluster (theme + settings + PRO + Open) leaves the brand ~105px,
   which the 23px wordmark overflows. Tighten the lockup so it fits rather than truncates. */
@media (max-width:460px){
  .top{gap:8px;padding-left:14px;padding-right:14px}
  .brand{gap:8px}
  .brand .word{font-size:16px}
  .brand .word small{display:none}
  /* Measured on iPhone 17 Pro (402pt): the brand box gets 123pt, so the mark + gap must
     leave ~83pt for "Pagehawk" at 16px or the ellipsis guard above eats the name. */
  .rings.sm{width:26px;height:26px}
  .rings.sm .o{width:26px;height:26px}
  .rings.sm .m{width:17px;height:17px}
  .rings.sm .i{width:9px;height:9px}
  .brand .brand-mark{width:26px;height:26px;border-radius:7px}
}
.iconbtn{width:40px;height:40px;border-radius:12px;border:1px solid var(--line);background:var(--surface);
  color:var(--ink-2);cursor:pointer;display:grid;place-items:center;transition:.15s;flex:0 0 40px}
.iconbtn svg{width:19px;height:19px}
.btn-open{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:12px;border:0;
  background:var(--accent);color:var(--accent-ink);font:600 13.5px var(--sans);cursor:pointer;transition:.15s}
.btn-open svg{width:16px;height:16px}
@media(hover:hover) and (pointer:fine){.iconbtn:hover{border-color:var(--gold);color:var(--ink)}.btn-open:hover{filter:brightness(1.07)}}

.content{flex:1;width:100%;max-width:920px;margin:0 auto;padding:8px 18px calc(var(--safe-bot) + 40px)}
.greet{font-family:var(--serif);font-weight:600;font-size:31px;line-height:1.12;margin:24px 0 3px;color:var(--ink)}
.greet em{font-style:italic;color:var(--gold-deep)}
.greet-sub{color:var(--ink-2);font-size:14px;margin:0 0 24px;max-width:560px;line-height:1.55}
.sec-h{display:flex;align-items:baseline;gap:10px;margin:30px 0 14px}
.sec-h h2{font-family:var(--serif);font-weight:600;font-size:20px;margin:0;color:var(--ink)}
.sec-h .sub{font-size:12.5px;color:var(--ink-3)}

/* ---- continue rail (the cross-device hook) ---- */
/* PROXIMITY, never MANDATORY (FB-4XCJD, "a small swipe goes kind of catastrophically aggressive").
   The cards are 218px on a 390px phone, so this shelf is never a full-screen pager, and `mandatory`
   forbids the scroller from resting anywhere but a card edge: a 30px nudge is either thrown a whole
   card forward or yanked back to where it started, and a flick lands on a snap point instead of
   gliding to a stop. `proximity` keeps Apple's deceleration (a small swipe moves a small amount and
   stays there) and only tidies the alignment when the glide already ends near an edge. Full-viewport
   pagers are the one place `mandatory` belongs. */
.rail{display:flex;gap:14px;overflow-x:auto;scroll-snap-type:x proximity;padding:4px 2px 10px;margin-inline:-2px;-webkit-overflow-scrolling:touch}
.rail::-webkit-scrollbar{height:0}
.ccard{flex:0 0 218px;scroll-snap-align:start;background:var(--surface);border:1px solid var(--line-2);
  border-radius:16px;overflow:hidden;cursor:pointer;transition:.18s;box-shadow:var(--shadow);text-align:start;padding:0}
.ccard:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
@media(hover:hover) and (pointer:fine){.ccard:hover{transform:translateY(-3px);box-shadow:var(--shadow-h);border-color:var(--gold)}}
.cv{height:130px;position:relative;display:grid;place-items:center;color:#fff;font-family:var(--serif);font-size:14px;text-align:center;padding:14px;line-height:1.25}
.cv .pages{position:absolute;inset:0;opacity:.14;background:repeating-linear-gradient(180deg,transparent 0 9px,rgba(255,255,255,.55) 9px 10px)}
.cv1{background:linear-gradient(150deg,#3E5C78,#27384a)} .cv2{background:linear-gradient(150deg,#7d5a86,#4a3551)}
.cv3{background:linear-gradient(150deg,#2c6e57,#1c4538)} .cv4{background:linear-gradient(150deg,#9a7b3e,#5e4a20)}
.cv5{background:linear-gradient(150deg,#8a4a4a,#5a2f2f)} .cv6{background:linear-gradient(150deg,#3a4a63,#222c3c)}
.src-chip{position:absolute;top:9px;inset-inline-start:9px;display:flex;align-items:center;gap:5px;background:rgba(0,0,0,.46);backdrop-filter:blur(4px);color:#fff;font:600 9.5px var(--sans);letter-spacing:.3px;padding:4px 8px;border-radius:7px}
.src-dot{width:8px;height:8px;border-radius:2.5px;display:inline-block}
.dev-pin{position:absolute;bottom:9px;inset-inline-end:9px;font:600 9.5px var(--sans);color:#fff;background:rgba(0,0,0,.42);backdrop-filter:blur(4px);padding:3px 7px;border-radius:6px;display:flex;align-items:center;gap:4px}
.ccard .meta{padding:11px 13px 13px}
.ccard .t{font:600 13px var(--sans);line-height:1.3;margin:0 0 3px;color:var(--ink);display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}
.ccard .p{font-size:11px;color:var(--ink-3);margin:0 0 8px}
.bar{height:4px;border-radius:3px;background:var(--line);overflow:hidden}
.bar i{display:block;height:100%;background:linear-gradient(90deg,var(--gold),var(--gold-deep))}

/* ---- HOME LAYOUTS (FB-SS2S4) -----------------------------------------------------------
   Jason, 2026-08-09, on a home screen that spent its lower two-thirds empty: "if it's going to
   be in this format with large thumbnails, it should cover the entire page. There's a lot of
   empty space here that's being used inefficiently." He picked four to ship as switchable
   views rather than one: Rails (default), Hero, Wall, List.

   Deliberately CSS over the SAME markup for three of the four. `card()` and `row()` already
   emit data-id and data-thumb, so every view inherits real cover hydration, the click handler
   and the progress bar for free. A second markup path per layout would be four places for a
   cover fix to be forgotten. Only List swaps the builder, because a dense row is genuinely a
   different element rather than a differently-shaped card. */

/* RAILS (default): unchanged from what shipped, and still the one the peek affordance suits. */
.rail.hv-rails{display:flex}

/* WALL: two columns of large covers, filling the page. The literal answer to his sentence. */
.rail.hv-wall{display:grid;grid-template-columns:1fr 1fr;gap:12px;overflow-x:visible;
  scroll-snap-type:none;padding-bottom:4px}
.rail.hv-wall .ccard{flex:1 1 auto;width:auto}
.rail.hv-wall .cv{height:186px}

/* HERO: the document you are actually mid-way through gets the full width and a taller cover,
   everything else falls into the same two-column grid underneath. */
.rail.hv-hero{display:grid;grid-template-columns:1fr 1fr;gap:12px;overflow-x:visible;
  scroll-snap-type:none;padding-bottom:4px}
.rail.hv-hero .ccard{flex:1 1 auto;width:auto}
.rail.hv-hero .cv{height:150px}
.rail.hv-hero .ccard:first-child{grid-column:1 / -1}
.rail.hv-hero .ccard:first-child .cv{height:230px}
.rail.hv-hero .ccard:first-child .t{-webkit-line-clamp:2;font-size:15px}

/* LIST: nine documents visible at once, for a library this size. Uses row(), so it inherits the
   library's own row styling rather than inventing a second one. */
.rail.hv-list{display:block;overflow-x:visible;scroll-snap-type:none;padding:0}
.rail.hv-list .row{width:100%}

/* ---- library tools: search + view/sort/filter chips ---- */
.lib-tools{display:flex;flex-direction:column;gap:10px;margin:0 0 14px}
.lib-search{display:flex;align-items:center;gap:9px;background:var(--surface);border:1px solid var(--line);border-radius:13px;padding:0 13px;height:44px}
.lib-search svg{width:17px;height:17px;color:var(--ink-3);flex:0 0 auto}
.lib-search input{flex:1;min-width:0;border:0;background:transparent;color:var(--ink);font:500 14.5px var(--sans);outline:none}
.lib-search input::placeholder{color:var(--ink-3)}
/* 16px is the size below which iOS zooms the page to read a focused field (FB-B774Y). Touch only, so the desktop keeps its 14.5px. */
@media (hover:none) and (pointer:coarse){.lib-search input,.tag-new input{font-size:16px}}
.lib-search input::-webkit-search-cancel-button{-webkit-appearance:none}
.lib-chips{display:flex;align-items:center;gap:8px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:2px;scrollbar-width:none}
.lib-chips::-webkit-scrollbar{height:0}
.chip{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 13px;border-radius:999px;border:1px solid var(--line);
  background:var(--surface);color:var(--ink-2);font:600 12.5px var(--sans);cursor:pointer;white-space:nowrap;flex:0 0 auto;transition:.14s}
.chip.on{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}
.chip .src-dot{width:9px;height:9px;border-radius:3px}
.chip svg{width:15px;height:15px}
.viewseg{display:inline-flex;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:3px;flex:0 0 auto}
.viewseg button{border:0;background:transparent;color:var(--ink-3);border-radius:999px;width:36px;height:28px;cursor:pointer;display:grid;place-items:center}
.viewseg button.on{background:var(--accent);color:var(--accent-ink)}
.viewseg button svg{width:15px;height:15px}
/* cards view: the ccard, in a responsive grid instead of the rail */
.lib-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:13px}
.lib-cards .ccard{flex:none;width:auto}
/* grid (thumbnail) view: pure covers */
.lib-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(108px,1fr));gap:12px}
.gtile{border:1px solid var(--line-2);border-radius:12px;overflow:hidden;background:var(--surface);cursor:pointer;padding:0;text-align:start;box-shadow:var(--shadow);transition:.15s}
@media(hover:hover) and (pointer:fine){.gtile:hover{transform:translateY(-2px);border-color:var(--gold);box-shadow:var(--shadow-h)}}
.gtile .gcv{aspect-ratio:3/4;position:relative;display:grid;place-items:center;color:#fff}
.gtile .gcv svg{width:22px;height:22px;opacity:.95}
.gtile .gbar{position:absolute;left:0;right:0;bottom:0;height:3.5px;background:rgba(0,0,0,.25)}
.gtile .gbar i{display:block;height:100%;background:linear-gradient(90deg,var(--gold),var(--gold-deep))}
/* Margin, not padding: overflow:hidden clips at the PADDING edge, so with padding the clamped third
   line bled into the bottom padding ("Quarterly Business... Revi"). min-height 21px keeps the old
   box heights (36px with one line, 45px with two). */
.gtile .gt{font:600 11.5px var(--sans);color:var(--ink);margin:7px 9px 8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;line-height:1.3;min-height:21px;overflow-wrap:anywhere}
.lib-empty{padding:26px 8px;text-align:center;font:500 13.5px var(--sans);color:var(--ink-3);line-height:1.5}
/* ---- tags + smart collections ---- */
.chip.sm{border-style:dashed}
.chip.tg::before{content:"#";opacity:.5;font-weight:700}
.row .rtags{display:flex;gap:5px;margin-top:5px;flex-wrap:wrap}
.row .rtags span{font:600 10.5px var(--sans);color:var(--ink-2);background:var(--surface-3);border:1px solid var(--line);border-radius:999px;padding:2px 8px;white-space:nowrap}
.tag-doc{font:600 13px var(--sans);color:var(--ink-3);padding:0 20px 10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tag-new{display:flex;gap:8px;padding:12px 16px 4px}
.tag-new input{flex:1;min-width:0;border:1.5px solid var(--line);border-radius:11px;background:var(--surface);color:var(--ink);font:500 14.5px var(--sans);padding:11px 13px;outline:none}
.tag-new input::placeholder{color:var(--ink-3)}
.tag-new button{border:0;background:var(--accent);color:var(--accent-ink);font:700 13px var(--sans);border-radius:11px;padding:0 16px;cursor:pointer;flex:0 0 auto}
.tag-hint{font:400 12px var(--sans);color:var(--ink-3);padding:8px 20px 2px;line-height:1.45}

/* ---- search scope (Titles / Full text) + full-text hit rows ---- */
.lib-scope.hide{display:none}
.scopeseg{display:flex;gap:0;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:3px;width:100%}
.scopeseg button{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;border:0;background:transparent;
  color:var(--ink-2);font:600 12.5px var(--sans);height:30px;border-radius:999px;cursor:pointer;transition:.14s}
.scopeseg button.on{background:var(--accent);color:var(--accent-ink)}
.scopeseg button .sn{font:700 11px var(--sans);opacity:.62;font-variant-numeric:tabular-nums}
.fts-row{display:flex;align-items:flex-start;gap:13px;padding:12px;border-radius:14px;background:var(--surface);border:1px solid var(--line-2);cursor:pointer;transition:.15s;width:100%;text-align:start}
@media(hover:hover) and (pointer:fine){.fts-row:hover{border-color:var(--gold);box-shadow:var(--shadow-h)}}
.fts-row .thumb{margin-top:1px}
.fts-row .fmeta{flex:1;min-width:0}
.fts-row .ft{display:flex;align-items:center;gap:7px;font:600 14px var(--sans);color:var(--ink);line-height:1.25}
.fts-row .ft>:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* --ink-2, not --ink-3: measured 4.43:1 on the dark surface at 10.5px, under the 4.5 AA floor. */
.fts-row .fpg{flex:0 0 auto;font:700 10.5px var(--sans);letter-spacing:.3px;color:var(--ink-2);border:1px solid var(--line);border-radius:999px;padding:2px 7px;white-space:nowrap}
.fts-row .fpg.fsc{text-transform:uppercase}
.fts-row .fs{font:400 12.5px var(--sans);color:var(--ink-2);line-height:1.5;margin-top:5px;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.fts-row .fs b{font-weight:700;color:var(--ink);background:var(--gold-soft,rgba(200,168,114,.28));border-radius:3px;padding:0 2px}
.fts-note{padding:12px 8px 2px;text-align:center;font:500 11.5px var(--sans);color:var(--ink-3)}
.fts-hint{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;margin-top:10px;padding:13px 12px;border-radius:14px;
  border:1px dashed var(--gold,#C8A872);background:transparent;color:var(--ink-2);font:600 13px var(--sans);cursor:pointer}
.fts-hint span{color:var(--ink-3);font-size:15px}

/* ---- in-progress list rows ---- */
.rows{display:flex;flex-direction:column;gap:8px}
.row{display:flex;align-items:center;gap:13px;padding:12px;border-radius:14px;background:var(--surface);border:1px solid var(--line-2);cursor:pointer;transition:.15s;width:100%;text-align:start}
@media(hover:hover) and (pointer:fine){.row:hover{border-color:var(--gold);background:var(--surface-2)}}
.row .thumb,.fts-row .thumb{width:42px;height:54px;border-radius:7px;flex:0 0 42px;display:grid;place-items:center;color:#fff}
.row .thumb svg,.fts-row .thumb svg{width:20px;height:20px}
.row .rmeta{min-width:0;flex:1}
.row .rt{font:600 14px var(--sans);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0 0 2px}
.row .rs{font-size:11.5px;color:var(--ink-3);display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.row .rbar{width:62px;height:4px;border-radius:3px;background:var(--line);overflow:hidden;flex:0 0 62px}
.row .rbar i{display:block;height:100%;background:linear-gradient(90deg,var(--gold),var(--gold-deep))}
.row .pct{font:600 11px var(--sans);color:var(--ink-3);min-width:30px;text-align:end;flex:0 0 auto}

/* ---- welcome / first-run (Gander aesthetic) ---- */
.welcome{text-align:center;padding:40px 22px 30px;max-width:500px;margin:14px auto 0}
.welcome .ringwrap{margin:6px auto 26px;width:88px;height:88px}
.welcome h2{font-family:var(--serif);font-weight:600;font-size:28px;margin:0 0 9px;color:var(--ink)}
.welcome p{font-size:14.5px;color:var(--ink-2);line-height:1.55;margin:0 0 26px}
.welcome .cta{display:inline-flex;align-items:center;gap:9px;height:50px;padding:0 26px;border-radius:15px;border:0;background:var(--accent);color:var(--accent-ink);font:600 15px var(--sans);cursor:pointer;box-shadow:var(--shadow);transition:.15s}
.welcome .cta svg{width:18px;height:18px}
@media(hover:hover) and (pointer:fine){.welcome .cta:hover{filter:brightness(1.07);transform:translateY(-1px)}}
.welcome .or{margin:26px 0 14px;font:600 11px var(--sans);letter-spacing:.18em;text-transform:uppercase;color:var(--ink-3);display:flex;align-items:center;gap:12px}
.welcome .or:before,.welcome .or:after{content:"";flex:1;height:1px;background:var(--line)}
.cloud-row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.cloud-btn{display:inline-flex;align-items:center;gap:8px;height:44px;padding:0 16px;border-radius:13px;border:1px solid var(--line);background:var(--surface);color:var(--ink);font:600 13px var(--sans);cursor:pointer;transition:.15s}
.cloud-btn .src-dot{width:11px;height:11px;border-radius:3px}
@media(hover:hover) and (pointer:fine){.cloud-btn:hover{border-color:var(--gold)}}

/* ---- bottom sheet (open / sources / settings) ---- */
.sheet-scrim{position:fixed;inset:0;z-index:60;background:rgba(20,18,12,.42);backdrop-filter:blur(2px);display:flex;align-items:flex-end;justify-content:center;opacity:0;transition:opacity .2s}
.sheet-scrim.on{opacity:1}
.sheet{background:var(--surface);border:1px solid var(--line);border-bottom:0;border-radius:22px 22px 0 0;width:min(560px,100%);
  padding:10px 16px calc(var(--safe-bot) + 18px);box-shadow:0 -10px 50px rgba(0,0,0,.3);transform:translateY(14px);transition:transform .22s}
.sheet-scrim.on .sheet{transform:none}
.sheet .grip{width:38px;height:5px;border-radius:3px;background:var(--line);margin:6px auto 12px}
/* FB-5EPYB: the Open tray drew this grabber and wired nothing to it, so the sheet advertised a
   drag it did not implement and the only way out was a tap on whatever scrim was left showing.
   app.js installGrabDismissal now replaces the bare .grip with this zone (same shape as the
   settings family's .set-grab), so the grabber really drags, there is a visible close button, and
   Escape works on iPad and Mac. touch-action:none stops iOS claiming the drag for scrolling. */
.sheet-grab{position:relative;padding:2px 0 6px;touch-action:none;cursor:grab}
.sheet-grab:active{cursor:grabbing}
.sheet-grab .grip{margin:6px auto 10px;transition:background .15s}
.sheet-grab:active .grip{background:var(--ink-3)}
/* 34px hit target (over the 24px floor) and ink-on-tint rather than a grey-on-grey glyph, so it
   stays legible in both themes. --surface-3 and --ink both track the theme; neither is pinned. */
.sheet-x{position:absolute;top:0;right:2px;width:34px;height:34px;display:flex;align-items:center;justify-content:center;
  border:0;border-radius:50%;background:var(--surface-3);color:var(--ink);cursor:pointer;padding:0}
.sheet-x:active{background:var(--gold)}
.sheet h3{font-family:var(--serif);font-weight:600;font-size:19px;margin:2px 6px 10px;color:var(--ink)}
.sheet-row{display:flex;align-items:center;gap:13px;padding:13px 12px;border-radius:13px;cursor:pointer;width:100%;text-align:start;background:none;border:0;transition:.12s}
@media(hover:hover) and (pointer:fine){.sheet-row:hover{background:var(--surface-3)}}
.sheet-row .si{width:38px;height:38px;border-radius:11px;background:var(--surface-3);display:grid;place-items:center;color:var(--gold-deep);flex:0 0 38px}
.sheet-row .si svg{width:19px;height:19px}.sheet-row .si .src-dot{width:13px;height:13px;border-radius:4px}
.sheet-row .st{flex:1;min-width:0}
.sheet-row .stt{font:600 14.5px var(--sans);color:var(--ink)}
.sheet-row .sd{font-size:12px;color:var(--ink-3);margin-top:1px}
.sheet-row .sv{font-size:12.5px;color:var(--ink-3);flex:0 0 auto}
.sheet-row[disabled]{opacity:.5}
.sheet-div{height:1px;background:var(--line-2);margin:6px 12px}

/* ---- sync status pill ---- */
.sync-pill{display:inline-flex;align-items:center;gap:8px;margin-top:10px;padding:7px 13px;border-radius:999px;background:var(--surface);border:1px solid var(--line);font:600 12px var(--sans);color:var(--ink-2)}
.sync-pill .dot{width:8px;height:8px;border-radius:50%;background:var(--gold)}
.sync-pill.live .dot{background:#5C8A5C;animation:pulse 1.8s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}

.hide{display:none!important}

/* Perry, the PageHawk mascot (character, not the logo — the header rings stay the mark).
   Inline SVG inherits `color`, so he themes light/dark for free. */
.welcome .perry-wrap{width:172px;height:172px;margin:0 auto 12px;color:var(--gold);
  display:flex;align-items:center;justify-content:center;animation:perry-in .5s ease-out both}
.welcome .perry-wrap svg{width:100%;height:100%;display:block;animation:perry-float 6s ease-in-out 0.5s infinite}
@keyframes perry-in{from{opacity:0;transform:translateY(6px) scale(.98)}to{opacity:1;transform:none}}
@keyframes perry-float{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}
@media (prefers-reduced-motion:reduce){.welcome .perry-wrap,.welcome .perry-wrap svg{animation:none!important}}

/* ---- destination bar (FB-9FGRR direction F, decided by Jason 2026-08-08) ----------------
   Library, Collections and Reading are three real screens, not three bands of one scroll.
   That is what un-buries the view/sort control: it is no longer below an unbounded
   Collections list, because Collections is now somewhere you GO.
   Label colours are the measured pair, not a guess: a 10px bold label is normal text under
   WCAG, and --ink-3 measures only 4.27:1 on --surface in dark, so inactive uses --ink-2
   (7.29 light / 8.20 dark) and active uses --gold-deep (5.12 light / 8.58 dark). */
/* FB-SS2S4 (Jason, 2026-08-09): "there's a big white space on the bottom ... at the bottom of the
   PageHawk ribbon on iOS." Two things were wrong and only one of them is arguable.
   NOT arguable: env(safe-area-inset-bottom) was written with NO fallback, alone among every
   safe-area rule in this codebase (annots.js, reader.js and ink.js all pass ,0px). Anywhere the
   inset is unsupported the whole calc() is invalid and the padding collapses.
   The white band itself: the bar stacked a further 6px UNDER the labels on top of the full ~34px
   home-indicator inset while the top edge got 7px, so the bar read bottom-heavy, and it is white
   --surface against a cream --surface-3 page, which makes the asymmetry obvious. The inset alone
   now provides the bottom space, which is what the tab bars we compared against do.
   Confirm on device before calling this closed; the exact figure travels with whichever home
   direction is picked from lab.belstone.com/staging/pagehawk-home/. */
.destbar{position:fixed;inset-inline:0;bottom:0;z-index:30;display:flex;
  background:var(--surface);border-top:1px solid var(--line);
  padding:7px 4px env(safe-area-inset-bottom,0px);
  box-shadow:0 -6px 22px rgba(35,33,28,.07)}
[data-theme="dark"] .destbar,html[data-skin="noir"] .destbar{box-shadow:0 -6px 22px rgba(0,0,0,.45)}
.destbar .dbtn{flex:1;display:grid;place-items:center;gap:3px;padding:5px 0;
  border:0;background:transparent;color:var(--ink-2);cursor:pointer;border-radius:12px}
.destbar .dbtn svg{width:19px;height:19px;display:block}
.destbar .dbtn span{font:600 10px var(--sans)}
.destbar .dbtn.on{color:var(--gold-deep)}
/* CENTRE ACTION DISC (Jason's pick A, 2026-08-10). Bilt's treatment, measured rather than eyeballed:
   the glyph on the disc is 13.07:1 light / 12.97:1 dark, against Bilt's own 14.56:1.
   The disc's FILL barely separates from the bar (1.23:1), exactly as Bilt's does (~1.05:1), so it
   reads by its RING and SHADOW, not by contrast. That makes both load-bearing rather than
   decorative: where a shadow is suppressed the ring is what still says "raised". */
.destbar .dbtn-disc{flex:0 0 auto;width:66px;display:grid;place-items:center;border:0;
  background:transparent;padding:0;cursor:pointer}
.destbar .dbtn-disc .disc{width:50px;height:50px;border-radius:50%;display:grid;place-items:center;
  background:var(--gold-soft);border:1px solid var(--line);color:var(--ink);
  box-shadow:0 2px 8px rgba(35,33,28,.14);margin-top:-14px;transition:transform .12s,filter .12s}
[data-theme="dark"] .destbar .dbtn-disc .disc{box-shadow:0 2px 10px rgba(0,0,0,.5)}
.destbar .dbtn-disc .disc svg{width:24px;height:24px;display:block}
.destbar .dbtn-disc:active .disc{transform:scale(.94)}
.destbar .dbtn-disc:focus-visible .disc{outline:2px solid var(--gold);outline-offset:3px}
@media(hover:hover) and (pointer:fine){.destbar .dbtn-disc:hover .disc{filter:brightness(1.04)}}
/* The disc overhangs the bar, so the page needs a little more clearance than the tabs alone want. */
body.has-destbar .content{padding-bottom:calc(94px + env(safe-area-inset-bottom,0px))}
.destbar .dbtn:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
/* The bar is fixed, so the page owes it clearance or the last row hides underneath. */
body.has-destbar .content{padding-bottom:calc(86px + env(safe-area-inset-bottom))}
.dest-empty{color:var(--ink-2);font-size:13.5px;line-height:1.6;margin:18px 2px;max-width:52ch}
/* Each destination owns its controls at the top and keeps them there while rows scroll. */
#destLibrary .lib-tools{position:sticky;top:0;z-index:6;background:var(--window);
  padding-top:6px;padding-bottom:6px;margin-bottom:4px}

/* ============================================================================================
 * LIBRARY IDEAS 7, 8 AND 9 (Jason 2026-09-24: "do everything here of the ideas page except 6").
 * 7: one layout menu behind a slider button. 8: a Shelves layout. 9: a floating glass tab bar.
 * All three are phone/tablet work; the desktop (html.dsk) keeps its chip row, search field and
 * full-width bar exactly as they were.
 * ========================================================================================== */

/* ---- 7. one layout menu ---- */
.lib-h .lib-menu-btn{margin-left:auto;align-self:center}
.lib-menu-btn{position:relative;width:40px;height:40px;flex:0 0 auto;padding:0;border-radius:12px;
  border:1px solid var(--line);background:var(--surface);color:var(--ink);display:grid;place-items:center;cursor:pointer}
.lib-menu-btn svg{width:20px;height:20px;display:block}
.lib-menu-btn[aria-expanded="true"]{border-color:var(--gold)}
.lib-menu-btn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* A filter is on: said by a dot on the button as well as the "Showing" line, since the lit chips
   that used to say it now live inside the menu. */
.lib-menu-btn .lm-dot{position:absolute;top:6px;right:6px;width:8px;height:8px;border-radius:50%;
  background:var(--gold-deep);box-shadow:0 0 0 2px var(--surface);display:none}
.lib-menu-btn.filtered .lm-dot{display:block}
html.dsk .lib-menu-btn{display:none}
html:not(.dsk) #libChips{display:none}
/* The Library's own search field is a duplicate exactly where the app-level search pill shows
   (theme.css .home-search: touch screens up to 900px), so it goes exactly there and nowhere else.
   On a wide or pointer screen the field is still the only title search, so it stays. */
@media (max-width:900px){
  body.touch #destLibrary .lib-search{display:none}
  body.touch #destLibrary .lib-tools{padding-top:0;padding-bottom:0;margin-bottom:0}
  body.touch #destLibrary .lib-tools > :not(.hide){margin-bottom:10px}
}
.lib-menu{position:fixed;z-index:60;overflow-y:auto;overscroll-behavior:contain;padding:2px 0 6px;
  background:var(--surface);color:var(--ink);border:1px solid var(--line);border-radius:16px;
  box-shadow:0 18px 50px rgba(35,33,28,.22),0 2px 8px rgba(35,33,28,.08);font:500 15px var(--sans);
  animation:lmIn .16s ease-out;transform-origin:top right}
[data-theme="dark"] .lib-menu{box-shadow:0 18px 50px rgba(0,0,0,.62),0 2px 8px rgba(0,0,0,.4)}
@keyframes lmIn{from{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.lib-menu{animation:none}}
.lm-sec + .lm-sec{border-top:6px solid var(--surface-3)}
.lm-h{font:700 11.5px var(--sans);letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);padding:11px 16px 3px}
.lm-sub{font:600 12px var(--sans);color:var(--ink-3);padding:9px 16px 2px 42px}
.lm-it{display:flex;align-items:center;gap:10px;width:100%;min-height:44px;padding:0 16px;margin:0;border:0;
  background:transparent;color:var(--ink);font:inherit;text-align:start;cursor:pointer}
.lm-it:active{background:var(--surface-3)}
@media(hover:hover) and (pointer:fine){.lm-it:hover{background:var(--surface-2)}}
.lm-it:focus-visible{outline:2px solid var(--gold);outline-offset:-3px;border-radius:10px}
.lm-ck{width:16px;flex:0 0 16px;color:var(--gold-deep)}
.lm-ck svg{width:16px;height:16px;display:block}
.lm-l{flex:1;min-width:0;display:flex;align-items:center;gap:7px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lm-l .src-dot{width:9px;height:9px;border-radius:3px;flex:0 0 auto}
.lm-it[aria-checked="true"] .lm-l{font-weight:650}
.lm-ic{color:var(--ink-3);display:grid;place-items:center}
.lm-ic svg{width:18px;height:18px;display:block}
.lib-filtered{display:flex;align-items:center;gap:10px;padding:6px 8px 6px 12px;border-radius:12px;
  background:var(--gold-soft);color:var(--ink);font:600 13px var(--sans)}
.lib-filtered .lf-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lib-filtered .lf-x{flex:0 0 auto;min-height:32px;padding:0 8px;border:0;background:transparent;color:var(--ink);
  font:700 13px var(--sans);text-decoration:underline;text-underline-offset:2px;cursor:pointer}

/* Cover size: one choice, applied to whichever layout is showing. */
.lib-grid.cov-compact{grid-template-columns:repeat(auto-fill,minmax(74px,1fr));gap:10px}   /* four across on a 390pt phone */
.lib-grid.cov-large{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px}
.rows.cov-compact .row .thumb{width:34px;height:44px;flex-basis:34px}
.rows.cov-large .row .thumb{width:54px;height:70px;flex-basis:54px}

/* ---- 8. shelves ---- */
/* Rows run edge to edge (the negative margin undoes .content's 18px gutter) so a shelf reads as a
   shelf that continues past the screen, not a boxed carousel. */
.lib-shelves{display:block;margin:0 -18px}
.shelf{padding:2px 0 16px}
.shelf-h{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:0 18px;margin:0 0 8px}
.shelf-h h3,.shelf-top h3{margin:0;min-width:0;font:600 19px var(--serif);color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.shelf-all{flex:0 0 auto;display:inline-flex;align-items:center;gap:1px;min-height:32px;padding:0 0 0 8px;border:0;
  background:transparent;color:var(--ink-2);font:600 13px var(--sans);cursor:pointer}
.shelf-all svg{width:15px;height:15px;color:var(--gold-deep)}
.shelf-all:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:8px}
.shelf-row{display:flex;gap:12px;overflow-x:auto;padding:2px 18px 6px;scroll-snap-type:x proximity;
  scroll-padding-inline:18px;-webkit-overflow-scrolling:touch;scrollbar-width:none}
.shelf-row::-webkit-scrollbar{display:none}
.scover{--cw:104px;flex:0 0 var(--cw);width:var(--cw);padding:0;margin:0;border:0;background:transparent;color:inherit;
  text-align:start;cursor:pointer;scroll-snap-align:start}
.cov-compact .scover{--cw:84px}
.cov-large .scover{--cw:132px}
/* No border: a border would show the tile's source-color background as a colored hairline around
   a cover image (the background paints under the border). The edge is a shadow ring instead. */
.scv{position:relative;width:100%;aspect-ratio:3/4;border-radius:8px;overflow:hidden;display:grid;place-items:center;
  box-shadow:0 0 0 .5px rgba(0,0,0,.1),0 1px 2px rgba(35,33,28,.12),0 5px 14px rgba(35,33,28,.13)}
[data-theme="dark"] .scv{box-shadow:0 0 0 .5px rgba(255,255,255,.08),0 1px 2px rgba(0,0,0,.4),0 5px 14px rgba(0,0,0,.45)}
.scv svg{width:22px;height:22px;opacity:.95}
.scv.has-thumb svg{display:none}
.scv.has-thumb{background-size:cover!important;background-position:top center!important;background-repeat:no-repeat!important}
/* The gold progress edge: a 3px track across the cover's foot, filled to the percent read. The
   same "p% read" is printed under the title, so the edge is a glance, never the only signal. */
.scv::after{content:"";position:absolute;left:0;right:0;bottom:0;height:3px;background:rgba(0,0,0,.22)}
.scv .sedge{position:absolute;left:0;bottom:0;z-index:1;height:3px;border-radius:0 2px 2px 0;
  background:linear-gradient(90deg,var(--gold),var(--gold-deep))}
.scover .st{font:600 12px var(--sans);color:var(--ink);margin-top:6px;line-height:1.3;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.scover .ss{font:500 11px var(--sans);color:var(--ink-3);margin-top:1px}
.scover:focus-visible{outline:none}
.scover:focus-visible .scv{outline:2px solid var(--gold);outline-offset:2px}
@media(hover:hover) and (pointer:fine){.scover:hover .scv{box-shadow:var(--shadow-h)}}
/* "See all": the shelf's set as a grid (or the list), with a way back to the shelves. */
.shelf-top{grid-column:1 / -1;display:flex;align-items:center;gap:10px;margin:0 0 4px;min-width:0}
.shelf-back{flex:0 0 auto;display:inline-flex;align-items:center;gap:2px;height:34px;padding:0 12px 0 6px;
  border:1px solid var(--line);border-radius:999px;background:var(--surface);color:var(--ink);font:600 12.5px var(--sans);cursor:pointer}
.shelf-back svg{width:16px;height:16px}

/* ---- 9. floating glass tab bar (phone only) ---- */
/* The same four items, in a frosted capsule lifted off the bottom by the safe area plus 12px.
   Contrast was computed against the worst thing that can sit behind the glass (solid black under a
   light bar, solid white under a dark one), not against the page color: labels (--ink-2) read
   5.04:1 light / 5.84:1 dark at that worst case; the active item's --ink on its gold pill reads
   9.98:1 / 6.23:1. The active label is ink, not --gold-deep, because gold-deep on the pill
   measured 3.23:1, under the 4.5:1 floor. */
@media (max-width:767px){
  html:not(.dsk) .destbar{inset-inline:auto;left:50%;right:auto;transform:translateX(-50%);
    bottom:calc(12px + env(safe-area-inset-bottom,0px));width:auto;min-width:min(340px,calc(100vw - 32px));
    max-width:calc(100vw - 24px);align-items:center;gap:2px;padding:6px;border-radius:999px;
    border:1px solid rgba(255,255,255,.6);background:rgba(255,253,248,.84);
    -webkit-backdrop-filter:blur(22px) saturate(1.6);backdrop-filter:blur(22px) saturate(1.6);
    box-shadow:0 10px 34px rgba(40,30,10,.18),0 0 0 .5px rgba(0,0,0,.08)}
  html[data-theme="dark"]:not(.dsk) .destbar{background:rgba(32,32,34,.88);border-color:rgba(255,255,255,.08);
    box-shadow:0 10px 34px rgba(0,0,0,.5),0 0 0 .5px rgba(255,255,255,.1)}
  html:not(.dsk) .destbar .dbtn{flex:1 1 0;min-width:64px;padding:6px 10px;border-radius:999px;transition:background .15s}
  html:not(.dsk) .destbar .dbtn.on{background:rgba(200,168,114,.24);color:var(--ink)}
  html[data-theme="dark"]:not(.dsk) .destbar .dbtn.on{background:rgba(206,176,107,.22);color:var(--ink)}
  html:not(.dsk) .destbar .dbtn:focus-visible{outline-offset:-3px}
  html:not(.dsk) .destbar .dbtn-disc{width:56px}
  html:not(.dsk) .destbar .dbtn-disc .disc{width:44px;height:44px;margin-top:0}
  /* Clearance so the last card scrolls fully clear of the bar: bar (~58px) + lift (12px) +
     breathing room, on top of the home-indicator inset. */
  html:not(.dsk) body.has-destbar .content{padding-bottom:calc(112px + env(safe-area-inset-bottom,0px))}
}
