/* Self-hosted so that no request — and no visitor IP — reaches Google. One variable file per
   family and subset covers every weight the app uses. */
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:100 1000; font-display:swap; src:url('fonts/dm-sans-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:100 1000; font-display:swap; src:url('fonts/dm-sans-latin.woff2') format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:300 700; font-display:swap; src:url('fonts/space-grotesk-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:300 700; font-display:swap; src:url('fonts/space-grotesk-latin.woff2') format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
/* Plum and gold on warm paper — a bound book rather than Tracktivity's cool green. --plum is the
   colour anything interactive is drawn in; --accent is reserved for the stars and for the marker on
   the book currently being read, so that a gold thing on this page always means a judgement. */
:root { --ink:#241b28; --muted:#7c7080; --paper:#faf5ef; --card:#fffdfa; --line:#e8ddd0; --accent:#c08a3e; --plum:#6d4370; --plum-soft:#f2eaf2; }
* { box-sizing:border-box; }
/* Defensive: user-select inherits, so any future none on an ancestor would silently take away the
   long-press "paste" menu inside text fields. */
input,textarea { user-select:text; -webkit-user-select:text; }
/* Beats any display rule below: .login-shell sets display:flex, which otherwise outranks the
   browser's own [hidden] { display:none } and leaves the signed-out panel showing under every tab. */
[hidden] { display:none !important; }
/* Android browsers "boost" text they take for body copy, which silently overrides small sizes. */
html { -webkit-text-size-adjust:100%; text-size-adjust:100%; }
/* A sideways drag would otherwise engage the browser's own horizontal overscroll — the swipe-to-go-back
   gesture — which nudges the whole page and drops fixed elements like the tab bar for a few frames. */
html,body { overscroll-behavior-x:none; }
/* The document does not scroll; the body inside it does.
   Browsers hang their own furniture off whatever the *root* scroller is, and one piece of it is a
   floating "scroll to top" button — Samsung Internet draws one, and keeps drawing it in an installed
   app, where it lands on top of the tab bar. A page cannot move that button, restyle it or turn it
   off. It can stop being the root scroller, and then there is nothing for the button to attach to.
   The tab bar is unaffected either way: position:fixed is relative to the viewport, not to whatever
   happens to be scrolling. The cost is that a scrolling body does sometimes repaint fixed children
   late on mobile, which is what translate3d and will-change on .tabbar are there to prevent. */
html { height:100%; overflow:hidden; }
body { height:100%; overflow-x:hidden; overflow-y:auto; }
/* The card slides in from off-panel and must be clipped, and `clip` crops without making yet another
   scroll container out of the column itself. */
.shell { overflow-x:clip; }
/* Only transform and opacity are animated: those two the compositor handles on its own, without
   re-rasterizing the text every frame. */
@keyframes card-in-right { from { opacity:0; transform:translate3d(22%,0,0); } to { opacity:1; transform:none; } }
@keyframes card-in-left { from { opacity:0; transform:translate3d(-22%,0,0); } to { opacity:1; transform:none; } }
.view.card-right,.view.card-left { backface-visibility:hidden; will-change:transform, opacity; }
.view.card-right { animation:card-in-right .24s cubic-bezier(.16,.84,.36,1); }
.view.card-left { animation:card-in-left .24s cubic-bezier(.16,.84,.36,1); }
@media (prefers-reduced-motion: reduce) { .view.card-right,.view.card-left { animation:none; } }
body { margin:0; background:radial-gradient(circle at 100% 0%, #f1e7f0 0, transparent 38%), var(--paper); color:var(--ink); font-family:'DM Sans', sans-serif; }
button,input,select,textarea { font:inherit; }
button { cursor:pointer; }
.shell,.login-shell { width:min(100% - 32px, 680px); margin:0 auto; }
.topbar { display:flex; align-items:center; justify-content:space-between; padding:28px 0 18px; gap:10px; }
h1,h2 { font-family:'Space Grotesk', sans-serif; margin:0; letter-spacing:0; }
h1 { font-size:clamp(1.7rem, 6vw, 2.3rem); }
h2 { font-size:1.55rem; }
/* The back link is styled as an eyebrow, not merely like one: sharing the rule guarantees the gap
   between the small line and the heading below it is identical on every page. */
.kicker,.eyebrow { color:var(--plum); font-size:.68rem; font-weight:700; letter-spacing:.14em; margin:0 0 7px; text-transform:uppercase; }
.muted { color:var(--muted); }
.icon-button,.small-button { border:0; background:transparent; color:var(--muted); }
.icon-button { font-size:1.35rem; padding:8px; }
.topbar-actions { align-items:center; display:flex; gap:2px; position:relative; }
.menu { background:var(--card); border:1px solid var(--line); border-radius:6px; box-shadow:0 12px 28px rgba(36,27,40,.16); display:flex; flex-direction:column; overflow:hidden; padding:5px; position:absolute; right:0; top:calc(100% + 6px); width:max-content; z-index:40; }
/* nowrap plus a max-content menu keeps every label on one line, whatever the phone's width.
   Absolute px, not rem: immune to a scaled root font size on the phone. */
.menu-item { background:transparent; border:0; border-radius:4px; color:var(--ink); font-size:11px; letter-spacing:.02em; padding:8px 11px; text-align:left; white-space:nowrap; width:100%; }
.menu-item:hover { background:var(--paper); }
.menu-item.danger { color:#b3382f; }
.menu-caption { color:var(--muted); display:block; font-size:.85em; letter-spacing:.08em; text-transform:uppercase; }
.menu-value { display:block; font-weight:600; margin-top:2px; }
.menu-value:after { content:' ▸'; color:var(--muted); }
.menu-divider { background:var(--line); height:1px; margin:4px 6px; }
.section-heading,.view-title { align-items:end; display:flex; gap:12px; justify-content:space-between; }
.section-heading { margin:26px 0 12px; }
.section-heading span { color:var(--muted); font-size:.78rem; }
.heading-tools { align-items:center; display:flex; flex:none; gap:8px; }
.view { padding-bottom:100px; }
.view-title { margin:2px 0 18px; }
/* The legal page has no tab bar under it, so it needs far less room at the bottom. */
#view-legal { padding-bottom:40px; }
.intro { margin:-8px 0 18px; }

/* ---- controls shared by the library and search bars ---- */
.searchbar { display:flex; gap:8px; }
.searchbar input { background:var(--card); border:1px solid var(--line); border-radius:6px; color:var(--ink); flex:1; min-width:0; padding:12px 13px; }
.searchbar input:focus-visible,.field input:focus-visible,.field textarea:focus-visible,.field select:focus-visible,.ghost-button:focus-visible,.chip:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }
/* The glyph is a run of block characters standing in for bars — a barcode drawn in text, so no
   icon has to be shipped for it. */
.scan-button { align-items:center; display:flex; gap:9px; justify-content:center; margin-top:8px; width:100%; }
.scan-button span { color:var(--plum); font-size:1.05rem; letter-spacing:-1px; line-height:1; }
.field-with-button { display:flex; gap:7px; }
.field-with-button input { flex:1; min-width:0; }
.field-with-button .tiny-button { flex:none; letter-spacing:-1px; padding:0 10px; }
/* 4:3 rather than the video's own shape: the preview must not resize when the camera reports its
   dimensions, which happens a beat after the stream opens and would otherwise jump the dialog. */
.scan-stage { aspect-ratio:4/3; background:#1a131f; border-radius:8px; overflow:hidden; position:relative; width:100%; }
.scan-stage video { display:block; height:100%; object-fit:cover; width:100%; }
/* Marks the band the decoder is actually given, so aiming inside it is aiming at something real. */
.scan-reticle { border:2px solid rgba(250,245,239,.85); border-radius:6px; box-shadow:0 0 0 100vmax rgba(26,19,31,.42); height:45%; left:8%; position:absolute; top:27.5%; width:84%; }
.scan-status { color:var(--muted); font-size:.82rem; margin:10px 0 0; min-height:1.2em; text-align:center; }
#scan-torch.lit { background:var(--accent); border-color:var(--accent); color:var(--ink); font-weight:700; }
.find-second { background:var(--card); border:1px solid var(--line); border-radius:6px; color:var(--ink); margin-top:8px; padding:11px 13px; width:100%; }
.find-second:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }
.segmented { background:var(--card); border:1px solid var(--line); border-radius:6px; display:flex; margin-top:9px; overflow:hidden; }
.segmented button { background:transparent; border:0; color:var(--muted); flex:1; font-size:.82rem; padding:9px 6px; }
.segmented button.active { background:var(--plum); color:#fff; font-weight:700; }
/* Scrolls sideways rather than wrapping to a second line: five filters and a phone's width do not
   fit, and a row that grows taller pushes the shelf down every time a filter is added. */
.chips { display:flex; gap:7px; margin:14px 0 4px; overflow-x:auto; padding-bottom:4px; scrollbar-width:none; }
.chips::-webkit-scrollbar { display:none; }
.chip { background:var(--card); border:1px solid var(--line); border-radius:999px; color:var(--muted); flex:none; font-size:.8rem; padding:7px 13px; }
.chip.active { background:var(--plum); border-color:var(--plum); color:#fff; font-weight:700; }
.chip .chip-count { opacity:.7; margin-left:5px; }
.layout-toggle { background:var(--card); border:1px solid var(--line); border-radius:5px; display:flex; flex:none; overflow:hidden; }
.layout-button { background:transparent; border:0; color:var(--muted); font-size:.95rem; line-height:1; padding:8px 10px; }
.layout-button.active { background:var(--plum); color:#fff; }
.sort-select { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); font-size:.8rem; padding:8px 9px; }

/* ---- covers ----
   Every cover sits in a fixed 2:3 frame so a shelf of mismatched scans still lines up. A book with
   no cover gets its initials on plum rather than a broken-image glyph: the grid is read by shape,
   and a hole in it reads as a fault. */
.cover { align-items:center; aspect-ratio:2/3; background:var(--plum-soft); border-radius:4px; box-shadow:0 1px 3px rgba(36,27,40,.16); color:var(--plum); display:flex; flex:none; font-family:'Space Grotesk', sans-serif; font-weight:700; justify-content:center; overflow:hidden; position:relative; }
.cover img { height:100%; object-fit:cover; width:100%; }
.cover .cover-initials { font-size:.9rem; letter-spacing:.04em; opacity:.75; padding:4px; text-align:center; }

/* ---- the shelf, as a grid of covers ---- */
.shelf-grid { display:grid; gap:14px 12px; grid-template-columns:repeat(auto-fill, minmax(104px, 1fr)); }
.shelf-card { background:none; border:0; color:var(--ink); display:flex; flex-direction:column; gap:7px; padding:0; text-align:left; }
.shelf-card .cover { width:100%; }
.shelf-card strong { font-size:.82rem; font-weight:600; line-height:1.25; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.shelf-card small { color:var(--muted); font-size:.72rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* A ribbon rather than a border, so the status reads at a glance without changing the frame the
   covers line up in. */
.cover-badge { background:var(--plum); border-radius:3px; color:#fff; font-size:.62rem; font-weight:700; left:5px; letter-spacing:.06em; padding:3px 6px; position:absolute; text-transform:uppercase; top:5px; }
.cover-badge.reading { background:var(--accent); color:#241b28; }
.cover-badge.aborted { background:#8d6b74; }
.cover-badge.unread { background:rgba(36,27,40,.62); }

/* ---- the shelf, as a list ---- */
.shelf-list { display:flex; flex-direction:column; gap:8px; }
.shelf-row { align-items:center; background:var(--card); border:1px solid var(--line); border-radius:7px; color:var(--ink); display:flex; gap:12px; padding:10px 12px; text-align:left; width:100%; }
.shelf-row .cover { width:42px; }
.shelf-row-copy { display:flex; flex:1; flex-direction:column; gap:3px; min-width:0; }
.shelf-row-copy strong { font-size:.92rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.shelf-row-copy small { color:var(--muted); font-size:.76rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.shelf-row .arrow { color:var(--muted); font-size:1.2rem; margin-left:auto; }

/* ---- search results ---- */
.result-list { display:flex; flex-direction:column; gap:8px; margin-top:16px; }
.result-row { align-items:flex-start; background:var(--card); border:1px solid var(--line); border-radius:7px; color:var(--ink); display:flex; gap:12px; padding:11px 12px; text-align:left; width:100%; }
.result-row .cover { width:48px; }
.result-copy { display:flex; flex:1; flex-direction:column; gap:4px; min-width:0; }
.result-copy strong { font-size:.94rem; line-height:1.3; }
.result-copy small { color:var(--muted); font-size:.77rem; }
.result-copy .result-meta { display:flex; flex-wrap:wrap; gap:4px 10px; }
.result-owned { color:var(--plum); font-size:.72rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; }
.searching { color:var(--muted); padding:26px 10px; text-align:center; }

/* ---- stars ----
   Drawn as two stacked rows of glyphs with the filled one clipped to a width, which is what makes a
   half star possible without half a glyph existing. */
.stars { display:inline-flex; line-height:1; position:relative; white-space:nowrap; }
.stars-back { color:var(--line); }
.stars-front { color:var(--accent); left:0; overflow:hidden; position:absolute; top:0; }
.stars.large { font-size:1.9rem; }
.rating-input { align-items:center; display:flex; gap:12px; }
/* The strip is one control: which half of which star the pointer is over decides the value, so the
   glyphs themselves take no clicks and there is nothing to miss between them. */
.rating-strip { cursor:pointer; padding:2px 0; touch-action:manipulation; }
.rating-readout { color:var(--muted); font-size:.85rem; font-variant-numeric:tabular-nums; }

/* ---- one book, opened ---- */
.book-head { display:flex; gap:15px; margin-bottom:16px; }
.book-head .cover { width:104px; }
.book-head-copy { display:flex; flex:1; flex-direction:column; gap:6px; min-width:0; }
.book-head-copy h3 { font-family:'Space Grotesk', sans-serif; font-size:1.1rem; line-height:1.3; margin:0; }
.book-head-copy .byline { color:var(--muted); font-size:.88rem; }
.book-facts { display:grid; gap:2px 16px; grid-template-columns:auto 1fr; margin:0 0 16px; }
.book-facts dt { color:var(--muted); font-size:.72rem; letter-spacing:.06em; padding-top:3px; text-transform:uppercase; }
.book-facts dd { margin:0 0 9px; font-size:.92rem; }
.book-review { background:var(--paper); border-left:3px solid var(--plum); border-radius:0 5px 5px 0; font-size:.88rem; line-height:1.55; margin:0 0 16px; overflow-wrap:anywhere; padding:11px 13px; white-space:pre-wrap; }
.status-line { align-items:center; display:flex; flex-wrap:wrap; gap:8px; margin-bottom:14px; }
.pill { background:var(--plum-soft); border-radius:999px; color:var(--plum); font-size:.74rem; font-weight:700; letter-spacing:.05em; padding:5px 11px; text-transform:uppercase; }
.pill.reading { background:#f7ecd9; color:#8a6220; }
.pill.aborted { background:#f1e6e8; color:#8d5560; }
.pill.unread { background:#eeeae6; color:var(--muted); }

/* ---- the cover editor inside the book dialog ---- */
.cover-field { align-items:center; display:flex; gap:13px; }
.cover-field .cover { width:76px; }
.cover-actions { display:flex; flex-direction:column; flex:1; gap:7px; min-width:0; }
.cover-actions button { width:100%; }
.tiny-button { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); font-size:.82rem; padding:8px 10px; }
.tiny-button.danger { color:#b3382f; }

/* ---- statistics ---- */
.stat-grid { display:grid; gap:9px; grid-template-columns:repeat(auto-fit, minmax(140px, 1fr)); }
.stat-tile { background:var(--card); border:1px solid var(--line); border-radius:8px; padding:14px 15px; }
.stat-tile span { color:var(--muted); font-size:.7rem; font-weight:700; letter-spacing:.09em; text-transform:uppercase; }
.stat-tile strong { display:block; font-family:'Space Grotesk', sans-serif; font-size:1.5rem; font-variant-numeric:tabular-nums; margin-top:5px; }
.stat-tile small { color:var(--muted); display:block; font-size:.75rem; margin-top:2px; }
.stats-panel { background:var(--card); border:1px solid var(--line); border-radius:8px; margin-top:14px; padding:15px 16px; }
.stats-panel h3 { font-family:'Space Grotesk', sans-serif; font-size:.98rem; margin:0 0 12px; }
.stats-body { display:flex; flex-direction:column; gap:12px; }
.stats-copy { display:flex; flex-direction:column; gap:5px; min-width:0; }
.stats-line { align-items:baseline; display:flex; gap:10px; justify-content:space-between; }
.stats-line strong { font-size:.9rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.stats-length { color:var(--muted); font-size:.82rem; font-variant-numeric:tabular-nums; white-space:nowrap; }
/* Measured against the largest row rather than against the total, so the bars compare the years to
   each other instead of all being short in a long-running shelf. */
.stats-bar { background:var(--line); border-radius:3px; height:6px; overflow:hidden; }
.stats-bar i { background:var(--plum); border-radius:3px; display:block; height:100%; }
.stats-bar.gold i { background:var(--accent); }

/* ---- imprint and privacy ---- */
.back-button { background:transparent; border:0; color:inherit; font:inherit; letter-spacing:inherit; margin:0; padding:0; text-transform:inherit; }
/* Sits in the top bar next to the settings wheel, so the imprint is one tap away from every tab. */
.imprint-button { background:transparent; border:0; color:var(--muted); font-size:.78rem; padding:8px 4px; white-space:nowrap; }
.legal-block { background:var(--card); border:1px solid var(--line); border-radius:8px; padding:16px 18px; }
.legal-facts { display:grid; gap:2px 16px; grid-template-columns:auto 1fr; margin:0 0 14px; }
.legal-facts dt { color:var(--muted); font-size:.76rem; letter-spacing:.06em; text-transform:uppercase; }
.legal-facts dd { margin:0 0 8px; }
.legal-block a,.legal-body a { color:var(--plum); }
.legal-details { background:var(--card); border:1px solid var(--line); border-radius:8px; margin-top:12px; }
.legal-details summary { cursor:pointer; font-family:'Space Grotesk', sans-serif; font-size:1.05rem; font-weight:600; list-style:none; padding:15px 18px; }
.legal-details summary::-webkit-details-marker { display:none; }
.legal-details summary:after { color:var(--muted); content:'▾'; float:right; transition:transform .2s; }
.legal-details[open] summary:after { transform:rotate(180deg); }
.legal-body { border-top:1px solid var(--line); padding:4px 18px 18px; }
.legal-body h3 { font-family:'Space Grotesk', sans-serif; font-size:.95rem; margin:18px 0 6px; }
.legal-body p,.legal-body li { color:#463c4a; font-size:.88rem; line-height:1.55; margin:6px 0; }
.legal-body ul { margin:6px 0; padding-left:20px; }

/* ---- tab bar ----
   Opaque, not translucent: translate3d and will-change keep it on its own compositor layer, so it is
   not repainted — and briefly dropped — while a card animates past. */
.tabbar { background:var(--paper); border-top:1px solid var(--line); bottom:0; display:flex; justify-content:space-around; left:50%; max-width:680px; padding:11px 0 max(11px, env(safe-area-inset-bottom)); position:fixed; transform:translate3d(-50%,0,0); width:100%; will-change:transform; z-index:3; }
.tabbar button { background:none; border:0; color:var(--muted); display:flex; flex-direction:column; font-size:.72rem; gap:4px; padding:3px 22px; }
.tabbar button span { font-size:1.25rem; line-height:1; }
.tabbar button.active { color:var(--plum); font-weight:700; }

/* ---- buttons, dialogs, the rest of the furniture ---- */
.add-button,.primary-button { background:var(--plum); border:0; border-radius:5px; color:#fff; font-weight:700; padding:10px 14px; }
.primary-button { padding:0 18px; white-space:nowrap; }
.primary-button:disabled,.update-button:disabled,.tiny-button:disabled { opacity:.55; cursor:default; }
.ghost-button { background:transparent; border:1px solid var(--line); border-radius:5px; color:var(--ink); font-weight:500; padding:10px 14px; }
.danger-button { background:#b3564d; }
.link-button { background:none; border:0; color:var(--plum); font-weight:700; padding:0; text-decoration:underline; }
.small-button { font-size:1.1rem; padding:8px 5px; }
.danger { color:#b3564d; }
.empty-state { color:var(--muted); padding:30px 10px; text-align:center; }
.empty-state .link-button { margin-top:8px; }
.modal-overlay { align-items:center; background:rgba(36,27,40,.46); bottom:0; display:flex; justify-content:center; left:0; opacity:0; padding:20px; position:fixed; right:0; top:0; transition:opacity .16s; z-index:10; }
.modal-overlay.open { opacity:1; }
.modal { background:var(--paper); border-radius:10px; box-shadow:0 18px 44px rgba(36,27,40,.26); max-height:calc(100vh - 40px); max-width:440px; overflow-y:auto; padding:22px; transform:translateY(8px); transition:transform .16s; width:100%; }
/* vh on a phone is measured against the viewport with the browser's toolbars hidden, so a dialog
   sized in it is taller than what can actually be seen and its last row — which is where the buttons
   are — sits under the address bar until the page is scrolled. dvh is the height there really is. */
@supports (max-height: 100dvh) { .modal { max-height:calc(100dvh - 40px); } }
.modal-overlay.open .modal { transform:none; }
.modal h3 { font-family:'Space Grotesk', sans-serif; font-size:1.2rem; margin:0 0 10px; }
.modal-copy { color:var(--muted); font-size:.9rem; line-height:1.45; margin:0 0 18px; }
/* Wrapping is not cosmetic here. A flex row that justifies to the end overflows towards the *start*,
   and overflow in that direction cannot be scrolled to by anything — so on a narrow phone the
   leftmost button, which is Delete, was simply gone. Three buttons and a German label overrun even
   this dialog's own 440px, so it must wrap on a desktop too. */
.modal-actions { display:flex; flex-wrap:wrap; gap:9px; justify-content:flex-end; margin-top:20px; }
.modal-actions .spacer { flex:1 1 0; min-width:0; }
/* On a phone they stack full width instead, which is both readable and a bigger target than a row of
   three squeezed buttons. Reversed, so the confirming action is nearest the thumb and the
   destructive one is furthest from where the last tap landed. */
@media (max-width: 460px) {
  .modal-actions { flex-direction:column-reverse; }
  .modal-actions .spacer { display:none; }
  .modal-actions > button { padding:12px 14px; width:100%; }
}
/* The picker that folds a catalogue result into a book already on the shelf. The list scrolls inside
   the dialog rather than the dialog itself, so the search box and what is about to be written stay
   in view while a shelf of a hundred books is scrolled past them. */
.attach-takes { background:var(--plum-soft); border-radius:6px; color:var(--ink); font-size:.82rem; margin:0 0 12px; padding:9px 11px; }
#attach-search { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); margin-bottom:10px; padding:11px; width:100%; }
.attach-list { display:flex; flex-direction:column; gap:7px; max-height:46vh; overflow-y:auto; }
.attach-list .shelf-row { cursor:pointer; }
.attach-list .shelf-row:hover { border-color:var(--accent); }
.attach-list .cover { width:34px; }
.attach-list p { margin:6px 0; }
.field { display:flex; flex-direction:column; gap:8px; margin-bottom:16px; }
.field > span { color:var(--muted); font-size:.72rem; font-weight:700; letter-spacing:.09em; text-transform:uppercase; }
.field input,.field textarea,.field select { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); padding:11px; width:100%; }
.field textarea { font-family:inherit; font-size:.95rem; resize:vertical; }
.field select { appearance:none; background-image:linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%); background-position:calc(100% - 17px) 50%, calc(100% - 12px) 50%; background-repeat:no-repeat; background-size:5px 5px, 5px 5px; padding-right:34px; }
.field-row { display:flex; gap:9px; }
.field-row > .field { flex:1; min-width:0; }
/* Two fields side by side stop being two fields at this width: a date input has a width below which
   it clips its own digits, and two of them plus the dialog's padding is past it. */
@media (max-width: 360px) {
  .field-row { flex-direction:column; gap:0; }
}
/* The bibliographic block of a book added from the catalogue: shown, not editable. What the
   catalogue says the book is, is not a preference — it is corrected afterwards, from the book's own
   page, where a correction is what you came to make. */
.fixed-facts { background:var(--card); border:1px solid var(--line); border-radius:7px; margin-bottom:16px; padding:12px 14px; }
.fixed-facts dl { display:grid; gap:1px 14px; grid-template-columns:auto 1fr; margin:0; }
.fixed-facts dt { color:var(--muted); font-size:.7rem; letter-spacing:.06em; padding-top:3px; text-transform:uppercase; }
.fixed-facts dd { font-size:.88rem; margin:0 0 6px; }
.import-summary { background:var(--card); border:1px solid var(--line); border-radius:7px; font-size:.86rem; margin-bottom:14px; padding:12px 14px; }
.import-summary strong { font-variant-numeric:tabular-nums; }
.import-list { color:var(--muted); font-size:.82rem; margin:10px 0 0; max-height:34vh; overflow-y:auto; padding-left:18px; }
.import-list li { margin:3px 0; }
.progress { background:var(--line); border-radius:3px; height:6px; margin-top:10px; overflow:hidden; }
.progress i { background:var(--plum); display:block; height:100%; transition:width .2s; }
.file-drop { background:var(--card); border:1.5px dashed var(--line); border-radius:8px; color:var(--muted); font-size:.88rem; padding:22px 16px; text-align:center; width:100%; }
.file-drop strong { color:var(--plum); display:block; font-size:.95rem; margin-bottom:4px; }

/* ---- signed out, and the two banners ---- */
.login-shell { align-items:flex-start; display:flex; flex-direction:column; justify-content:center; min-height:100vh; padding:30px 0; }
.login-mark { align-items:center; background:var(--ink); border-radius:50%; color:var(--accent); display:flex; font-size:2rem; height:64px; justify-content:center; margin-bottom:26px; width:64px; }
.login-shell h1 { font-size:clamp(2.2rem, 11vw, 4.5rem); max-width:500px; }
.login-shell > .muted { margin:10px 0 30px; }
.login-actions { display:flex; gap:9px; margin-top:4px; }
.login-actions .primary-button { flex:1; min-height:48px; }
.login-switch { color:var(--muted); font-size:.85rem; margin:14px 0 0; }
/* Fixed rather than in the document flow, which would put it behind the tab bar or the header. */
.build-badge { color:var(--muted); font-size:.6rem; letter-spacing:.04em; margin:0; opacity:.7; pointer-events:none; position:fixed; right:12px; top:max(calc(env(safe-area-inset-top) + 6px), 6px); z-index:2; }
.update-banner { align-items:center; background:var(--ink); border-radius:6px; color:#faf5ef; display:flex; font-size:.85rem; gap:12px; left:50%; max-width:calc(100% - 32px); padding:10px 12px; position:fixed; top:12px; transform:translateX(-50%); width:680px; z-index:6; }
.update-banner span { flex:1; }
.update-button { background:var(--accent); border:0; border-radius:5px; color:var(--ink); font-weight:700; padding:8px 14px; }
#toast { background:var(--ink); border-radius:5px; bottom:85px; color:#fff; left:50%; max-width:calc(100% - 32px); opacity:0; padding:12px 16px; pointer-events:none; position:fixed; transform:translate(-50%, 15px); transition:.2s; z-index:5; }
#toast.show { opacity:1; transform:translate(-50%,0); }

@media (max-width:520px) {
  .shell,.login-shell { width:min(100% - 24px, 680px); }
  .section-heading .count { display:none; }
  .shelf-grid { gap:12px 10px; grid-template-columns:repeat(auto-fill, minmax(92px, 1fr)); }
  .primary-button { min-height:46px; }
  .book-head .cover { width:88px; }
}
/* The tab labels sit in <em> so the glyph above them can be sized on its own; nothing about them is
   emphasis, so the browser's italics are taken back off. */
.tabbar button em { font-style:normal; }
