/* ==== Fonts ==== */
/* Self-hosted (static/fonts/, built by scripts/build_fonts.py), so a page load
   makes no third-party request. SIL Open Font License 1.1; the licences sit
   beside the files. */
@font-face {
    font-family: "Newsreader";
    src: url("/static/fonts/newsreader-roman.woff2") format("woff2");
    font-weight: 400 600;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Newsreader";
    src: url("/static/fonts/newsreader-italic.woff2") format("woff2");
    font-weight: 400 500;
    font-style: italic;
    font-display: swap;
}
@font-face {
    font-family: "Instrument Sans";
    src: url("/static/fonts/instrument-sans.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
}

/* ==== Tokens ==== */
/* Every colour, font and size the components use. No rule below this section
   may use a raw hex value (tests/test_design_tokens.py), because a hard-coded
   colour is what breaks dark mode. Text/background pairs are WCAG AA in both
   modes; the test computes every pair it lists. */
:root {
    color-scheme: light dark;

    --paper: #F2EFE8;
    --paper-raised: #FAF8F3;
    --paper-sunk: #E9E4D8;
    --ink: #1C1A17;
    --ink-body: #3B362F;
    --muted: #5E584F;
    --rule: #D3CCBE;
    --brass: #7A5C24;
    --accent: #1F4A3D;
    --accent-hover: #163629;
    --on-accent: #F7F4EC;
    --warn: #8A4F00;
    --warn-bg: #EAE2D1;
    --danger: #9B2C22;
    --danger-bg: #F3DEDA;
    --done-bg: #DDE7E0;
    --info: #23395B;
    --info-bg: #DCE3EA;
    /* The select-mode action bar (James, 2026-10-05): dark in both modes, so
       it does not follow --ink to cream at night. --on-bar is an enabled
       action (AA); --on-bar-quiet a disabled one, held to 3.0-3.6:1 on --bar
       so it reads as plainly off and is still legible. */
    --bar: #1C1A17;
    --on-bar: #F2EFE8;
    --on-bar-quiet: #736D63;
    /* The dropdown arrow; stroke = the --muted colour. */
    --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235E584F' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");

    /* The same in both modes: a box label is a print preview, and the photo
       lightbox is always a dark room. */
    --label-paper: #FFFFFF;
    --label-ink: #000000;
    --label-ink-soft: #3A3A3C;
    --photo-ink: #FFFFFF;
    --photo-scrim: rgba(0, 0, 0, 0.92);
    --photo-control: rgba(0, 0, 0, 0.6);
    --photo-caption: rgba(0, 0, 0, 0.55);

    --scrim: rgba(28, 26, 23, 0.5);
    --shadow: 0 12px 36px rgba(28, 26, 23, 0.18);

    --font-serif: "Newsreader", Georgia, "Times New Roman", serif;
    --font-sans: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    /* The stack the box labels were measured in. Do not change it. */
    --font-system: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    --font-mono: ui-monospace, "SF Mono", Menlo, monospace;

    --radius: 4px;
    --tabbar-h: 64px;
    /* The piece page's pinned bar at its tallest: two-line labels make it
       81px at 375-390px wide and up to 99px at 320px. */
    --piece-bar-h: 100px;
}

@media (prefers-color-scheme: dark) {
    :root {
        --paper: #161512;
        --paper-raised: #1F1D19;
        --paper-sunk: #2A2721;
        --ink: #EFEAE0;
        --ink-body: #D6D0C4;
        --muted: #A9A194;
        --rule: #36332C;
        --brass: #D2AE68;
        --accent: #8FC2A8;
        --accent-hover: #A9D3BD;
        --on-accent: #161512;
        --warn: #E2B866;
        --warn-bg: #2A2418;
        --danger: #E58A80;
        --danger-bg: #2E1A17;
        --done-bg: #1C2A24;
        --info: #9DB4D6;
        --info-bg: #1B2230;
        /* Raised above the page and lighter than a ticked row (--paper-sunk),
           like iOS's own select-mode toolbar. */
        --bar: #33302A;
        --on-bar: #EFEAE0;
        --on-bar-quiet: #868074;
        /* The dropdown arrow; stroke = the --muted colour. */
        --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23A9A194' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");

        --scrim: rgba(0, 0, 0, 0.6);
        --shadow: 0 12px 36px rgba(0, 0, 0, 0.5);
    }
}

/* ==== Base and components ==== */

/* Every colour here is a token from the blocks above. */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: 16px;
    line-height: 1.45;
    -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); }
a:hover { color: var(--accent-hover); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A disabled button is drawn in a quieter token, never faded: opacity took its
   words below 4.5:1. This is the fallback for a button with no class of its
   own; each button class sets its own disabled look (Buttons, below), and a
   class rule's :disabled outranks this one. */
button:disabled { color: var(--muted); cursor: default; }
.hidden { display: none !important; }
/* The hidden attribute always wins. A component's own display (flex, grid)
   otherwise outranks the browser's [hidden] rule, and the element shows
   anyway — .auth-form and .lightbox each needed their own fix for this. */
[hidden] { display: none !important; }

/* One 600px reading column. The bottom padding clears the fixed tab bar and
   the iPhone's home indicator, so a list's last row is never hidden behind
   them. */
main {
    max-width: 640px;
    margin: 0 auto;
    padding: 1.25rem 1.25rem calc(var(--tabbar-h) + 2rem + env(safe-area-inset-bottom));
}
/* While a save or an upload runs (app.js holdPage), every link and button ignores
   taps, so none shows hover or press feedback for a tap that would do nothing.
   What keeps the work from being cut off is app.js, which drops every click
   (tap or keyboard) and submit meanwhile. The toast region and the photo viewer
   sit outside <main> and stay usable. Nothing is faded. */
body.page-busy > header,
body.page-busy > main { pointer-events: none; }

/* --- Masthead, tab bar, account menu ----------------------------------- */

header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    width: min(calc(100% - 2.5rem), 37.5rem);
    margin: 0 auto;
    padding: max(0.9rem, env(safe-area-inset-top)) 0 0.55rem;
    border-bottom: 2px solid var(--ink);
}
header h1, .wordmark {
    font-family: var(--font-serif);
    font-size: 1.55rem;
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.01em;
    color: var(--ink);
    text-decoration: none;
}
header h1 a, .wordmark:hover { color: var(--ink); text-decoration: none; }

/* One <nav>: a fixed bottom tab bar on a phone, inline in the masthead from
   960px. It lives inside <header>, so the print rule that hides the header
   hides it too. */
.tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    align-items: start;
    /* max() with the side insets: a notched iPhone in landscape puts its
       sensor housing over the outer tabs otherwise. */
    padding: 0.4rem max(0.4rem, env(safe-area-inset-right)) env(safe-area-inset-bottom) max(0.4rem, env(safe-area-inset-left));
    background: var(--paper);
    border-top: 1px solid var(--ink);
}
.tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    min-height: 52px;
    padding-top: 0.25rem;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
    text-decoration: none;
}
.tab:hover { color: var(--ink); }
.tab svg {
    width: 23px;
    height: 23px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.tab[aria-current="page"] { color: var(--accent); }
.tab-add {
    justify-self: center;
    justify-content: center;
    width: 50px;
    height: 50px;
    min-height: 0;
    padding: 0;
    border-radius: 14px;
    background: var(--accent);
    color: var(--on-accent);
}
.tab-add:hover, .tab-add[aria-current="page"] { background: var(--accent-hover); color: var(--on-accent); }
.tab-add svg { stroke-width: 2; }
/* At 320px each tab column is ~61px, and "GALLERIES" at the full 0.12em
   tracking does not fit it. */
@media (max-width: 339px) { .tab { letter-spacing: 0.04em; } }

.account-menu { position: relative; flex-shrink: 0; }
.account-menu > summary {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    list-style: none;
    cursor: pointer;
}
.account-menu > summary::-webkit-details-marker { display: none; }
.avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border: 1px solid var(--ink);
    border-radius: 50%;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--ink);
}
.account-panel {
    position: absolute;
    top: calc(100% + 0.35rem);
    right: 0;
    z-index: 60;
    min-width: 14rem;
    max-width: calc(100vw - 2.5rem);
    padding: 0.9rem 1rem;
    background: var(--paper-raised);
    border: 1px solid var(--ink);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
.account-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-link {
    display: flex;
    align-items: center;
    min-height: 44px;
    margin-top: 0.35rem;
    font-weight: 600;
    text-decoration: none;
}
.account-signout { margin-top: 0.75rem; }
.account-signout button { width: 100%; }

@media (min-width: 960px) {
    header { width: min(calc(100% - 2.5rem), 60rem); }
    .tabbar {
        position: static;
        display: flex;
        align-items: center;
        gap: 1.4rem;
        margin-left: auto;
        padding: 0;
        background: none;
        border: none;
    }
    .tab { flex-direction: row; gap: 0.4rem; min-height: 44px; padding: 0; font-size: 0.72rem; }
    .tab svg { width: 18px; height: 18px; }
    .tab-add { width: 40px; height: 40px; border-radius: 10px; }
    main { padding-bottom: 3rem; }
}

/* --- Toasts (static/js/toast.mjs) --------------------------------------- */

.toast-region {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 0.75rem);
    z-index: 90;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 0 1rem;
    max-height: 45vh;
    overflow-y: auto;
    pointer-events: none;
}
.toast {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    width: min(100%, 36rem);
    padding: 0.8rem 0.5rem 0.8rem 1rem;
    border-radius: var(--radius);
    background: var(--ink);
    color: var(--paper);
    box-shadow: var(--shadow);
    font-size: 0.92rem;
    line-height: 1.45;
    pointer-events: auto;
}
.toast-error { background: var(--danger); color: var(--paper); }
.toast-text { flex: 1; min-width: 0; white-space: pre-line; overflow-wrap: anywhere; }
.toast-close {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    margin: -0.7rem 0;
    border: none;
    background: none;
    color: inherit;
    font-size: 1.3rem;
    line-height: 1;
    cursor: pointer;
}
@media (min-width: 960px) { .toast-region { bottom: 1.5rem; } }
@media (prefers-reduced-motion: no-preference) {
    .toast { animation: toast-in 160ms ease-out; }
}
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } }
/* The global accent ring is under 2:1 against a toast's dark or danger
   background; the toast's own text colour is what is readable there. */
.toast :focus-visible { outline-color: currentColor; }

/* --- Type and spacing helpers ------------------------------------------- */

/* Read by a screen reader, not shown. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.cap, .card-title, .detail-card h3, .section-heading, .owners-card h2,
.form-group label, .edit-details label, .auth-form label {
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}
.serif { font-family: var(--font-serif); }
.muted { color: var(--muted); }
.small { font-size: 0.875rem; }
.body-copy { color: var(--ink-body); line-height: 1.55; }
.center { text-align: center; }
.w-full { width: 100%; }
.mt-xs { margin-top: 0.35rem; }
.mt-s { margin-top: 0.5rem; }
.mt-m { margin-top: 0.75rem; }
.mt-l { margin-top: 1rem; }
.mb-xs { margin-bottom: 0.35rem; }
.mb-s { margin-bottom: 0.5rem; }
.mb-m { margin-bottom: 0.75rem; }
.align-start { align-items: flex-start; }
.tall { min-height: 7rem; }
.status-line { margin-top: 1rem; font-size: 0.9rem; color: var(--muted); }
.page-title {
    margin-bottom: 1rem;
    font-family: var(--font-serif);
    font-size: 2rem;
    font-weight: 400;
    line-height: 1.1;
    letter-spacing: -0.015em;
}
.toolbar .page-title { margin-bottom: 0; }
.empty-state {
    padding: 2.5rem 0;
    text-align: center;
    font-family: var(--font-serif);
    font-size: 1.05rem;
    font-style: italic;
    color: var(--muted);
}
.empty-state a { color: var(--accent); }

/* --- Buttons ------------------------------------------------------------- */

.btn-primary, .btn-secondary, .btn-danger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: 44px;
    padding: 0.55rem 1.1rem;
    border-radius: var(--radius);
    font: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
}
.btn-primary { background: var(--accent); border: 1px solid var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--on-accent); }
.btn-secondary { background: transparent; border: 1px solid var(--ink); color: var(--ink); }
.btn-secondary:hover { background: var(--paper-sunk); color: var(--ink); }
.btn-danger { background: transparent; border: 1px solid var(--danger); color: var(--danger); }
.btn-danger:hover { background: var(--danger-bg); color: var(--danger); }
.link-button {
    padding: 0;
    background: none;
    border: none;
    font: inherit;
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}
/* Disabled (a request in flight, or nothing chosen yet): quieter tokens, never
   faded text. :disabled:hover is named too: each :hover above has the same
   specificity as its :disabled and must not light a disabled button up. */
.btn-primary:disabled, .btn-primary:disabled:hover {
    background: var(--paper-sunk);
    border-color: var(--rule);
    color: var(--muted);
}
.btn-secondary:disabled, .btn-secondary:disabled:hover,
.btn-danger:disabled, .btn-danger:disabled:hover {
    background: transparent;
    border-color: var(--rule);
    color: var(--muted);
}
.link-button:disabled { color: var(--muted); text-decoration: none; }

/* --- Form fields --------------------------------------------------------- */
/* :where() keeps these at zero specificity, so a component class
   (.sort-select, .box-find input, .share-url) overrides them without a fight.
   1rem text stops iPhone Safari zooming the page when a field takes focus. */

:where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]), select) {
    width: 100%;
    min-height: 44px;
    padding: 0.55rem 0.1rem;
    border: none;
    border-bottom: 1px solid var(--ink);
    border-radius: 0;
    background-color: transparent;
    color: var(--ink);
    font: inherit;
    font-size: 1rem;
}
:where(select) {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 1.75rem;
    background-image: var(--select-chevron);
    background-position: right 0.2rem center;
    background-repeat: no-repeat;
}
:where(textarea) {
    width: 100%;
    min-height: 6rem;
    padding: 0.6rem 0.7rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--paper-raised);
    color: var(--ink);
    font: inherit;
    font-size: 1rem;
    line-height: 1.5;
    resize: vertical;
}
/* Full --muted, which is what clears 4.5:1. opacity: 1 rather than no opacity
   at all: Firefox's own default dims a placeholder to 0.54. */
::placeholder { color: var(--muted); opacity: 1; }
:where(input[type="checkbox"], input[type="radio"]) { width: 20px; height: 20px; accent-color: var(--accent); }

.form-group { margin-bottom: 1.4rem; }
.form-group label { display: block; margin-bottom: 0.2rem; color: var(--ink); }
.form-group .check-row {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    font-size: 0.95rem;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
}
/* A two-line label would otherwise shrink the box in the flex row. */
.form-group .check-row input[type="checkbox"] { flex-shrink: 0; }

/* --- Add a piece (templates/add.html) ------------------------------------- */
/* The form ends in the pinned bar: body.piece-page hides the tab bar and
   reserves the bar's height, as on the piece page. */

.add-head {
    display: grid;
    grid-template-columns: 5rem minmax(0, 1fr) 5rem;
    align-items: center;
    min-height: 44px;
    padding-bottom: 0.5rem;
    border-bottom: 2px solid var(--ink);
}
.add-title {
    grid-column: 2;
    font-family: var(--font-serif);
    font-size: 1.35rem;
    font-weight: 500;
    text-align: center;
}
.add-section { margin-top: 1.25rem; color: var(--ink); }

/* The queued photos: three across, the cover outlined and tagged. */
.add-thumbs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.5rem; margin-top: 0.6rem; }
.add-thumbs:empty { display: none; }
.add-thumb { position: relative; aspect-ratio: 1; background: var(--paper-sunk); }
.add-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.add-thumb-cover { outline: 1.5px solid var(--ink); outline-offset: 2px; }
.add-thumb-tag {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    padding: 0.2rem 0;
    background: var(--ink);
    color: var(--paper);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-align: center;
    text-transform: uppercase;
}
/* A 44px target, with a smaller mark drawn in its corner. */
.add-thumb-remove {
    position: absolute;
    top: 0;
    right: 0;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    width: 44px;
    height: 44px;
    padding: 4px;
    border: none;
    background: none;
    cursor: pointer;
}
.add-thumb-x {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--ink);
    color: var(--paper);
    font-size: 1.05rem;
    line-height: 1;
}

/* Take photo and From library: a button-styled label around a clipped file
   input, so the picker opens from the label and the input keeps the focus.
   Shared with the piece page's Add photos. */
.photo-picks { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; margin-top: 0.75rem; }
.photo-picks-single { grid-template-columns: minmax(0, 1fr); }
.photo-pick { position: relative; min-height: 46px; }
.photo-pick svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.photo-pick:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Disabled while a save is in flight: quieter tokens, never faded text. The
   doubled class beats .photo-pick-quiet's colour, which comes later. */
.photo-pick.photo-pick:has(input:disabled) { color: var(--muted); border-color: var(--rule); cursor: default; }
.add-thumb-remove:disabled .add-thumb-x { background: var(--paper-sunk); color: var(--muted); }

/* Two classes: .field comes later in this file and won. */
.field.add-field { margin-top: 1.4rem; }
.add-title-input { font-family: var(--font-serif); font-size: 1.15rem; }

/* Condition: five radios drawn as one segmented control. */
.segmented-group { margin: 1.4rem 0 0; padding: 0; border: none; }
.segmented-group legend { padding: 0; color: var(--muted); }
.segmented {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    margin-top: 0.5rem;
    border: 1px solid var(--ink);
    border-radius: var(--radius);
    overflow: hidden;
}
.segment {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0.2rem;
    border-left: 1px solid var(--ink);
    font-size: 0.8rem;
    line-height: 1.1;
    text-align: center;
    cursor: pointer;
}
.segment:first-child { border-left: none; }
/* The radio is clipped, as .visually-hidden does; the segment is the
   control and carries the ring, inset because the group clips its edges. */
.segment input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.segment:has(input:checked) { background: var(--ink); color: var(--paper); font-weight: 700; }
.segment:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: -4px; }

/* Folder, Owner and Box: native selects drawn as rows (Decision 4). */
.row-fields { margin-top: 1.4rem; border-top: 1px solid var(--ink); }
.row-field {
    display: grid;
    /* The label takes only its own width, leaving the rest to the value: a
       box's name ("BOX 002-1 — Mom Dress Clothes") was cut off at 390px
       with a fixed 5.5rem label column. */
    grid-template-columns: max-content minmax(0, 1fr);
    align-items: center;
    gap: 0.75rem;
    border-bottom: 1px solid var(--rule);
}
.row-field > label { color: var(--muted); }
.row-field select { border-bottom: none; font-weight: 600; text-align: right; text-align-last: right; }
.row-new-folder { margin-top: 0.25rem; }
/* Two classes: .form-note's own colour comes later in this file and won. */
.form-note.owner-hint { margin-top: 0.4rem; color: var(--warn); }

.add-isbn { margin-top: 1rem; }
.add-isbn > summary {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-weight: 600;
    color: var(--accent);
    cursor: pointer;
}

.toggle-rows { margin-top: 1.4rem; border-top: 1px solid var(--rule); }
.toggle-row {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--rule);
    cursor: pointer;
}
/* A two-line label would otherwise shrink the box in the flex row. */
.toggle-row input[type="checkbox"] { flex-shrink: 0; margin-top: 0.1rem; }
.toggle-title { display: block; font-weight: 600; }
.toggle-note { display: block; margin-top: 0.1rem; font-size: 0.82rem; color: var(--muted); }

/* The pinned bar holds the progress line over one full-width Save. Two
   classes, because the piece page's .piece-bar rules come later in this file
   and would otherwise win (align-items: center, flex: 1.3). */
.piece-bar.add-bar { flex-direction: column; align-items: stretch; gap: 0.4rem; }
.piece-bar.add-bar > .btn-primary { flex: none; min-height: 50px; }
.add-status { font-size: 0.85rem; text-align: center; color: var(--muted); }
.add-status:empty { display: none; }

/* --- Toolbars and headers ------------------------------------------------ */

/* Buttons sit together, start-aligned; a page title pushes them to the
   right. space-between used to spread them across the row, which on the
   Boxes page at laptop width left Print labels floating mid-row, and spread
   a box's four buttons to the card's corners. */
.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
}
.toolbar > .page-title { margin-right: auto; }
/* The pick lists' action bar stays reachable above ~70 full-height cards. */
.toolbar.pick-toolbar {
    position: sticky;
    top: 0;
    z-index: 5;
    padding: 0.5rem 0;
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
}
.section-heading { margin: 1.5rem 0 0.5rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--ink); }

/* --- Item cards (the list, folders, a box, the owner-side gallery) ------ */

.item-grid { display: flex; flex-direction: column; }
.item-card {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    padding: 0.9rem 0;
    border-bottom: 1px solid var(--rule);
}
.item-checkbox { flex-shrink: 0; margin-top: 1.75rem; cursor: pointer; }
.item-thumb {
    flex-shrink: 0;
    width: 76px;
    height: 76px;
    object-fit: cover;
    border-radius: 3px;
    background: var(--paper-sunk);
}
.item-thumb-placeholder { display: block; }
.item-info { flex: 1; min-width: 0; color: inherit; text-decoration: none; }
.item-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-bottom: 0.35rem;
    font-family: var(--font-serif);
    font-size: 1.12rem;
    line-height: 1.22;
}
/* What the card knows about the item (services/item_card.card_details): a
   small label-over-value grid, two columns, one on a very narrow phone.
   minmax(0, 1fr) and min-width: 0 let a long folder or category name
   truncate instead of widening the card past the screen. */
.item-details {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.2rem 0.9rem;
    margin: 0 0 0.45rem;
}
.item-details > div { min-width: 0; }
.item-details dt {
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    line-height: 1.4;
    text-transform: uppercase;
    color: var(--muted);
}
.item-details dd {
    overflow: hidden;
    font-size: 0.82rem;
    line-height: 1.35;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ink-body);
}
@media (max-width: 339px) {
    .item-details { grid-template-columns: minmax(0, 1fr); }
}
.item-delete-btn {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border: none;
    border-radius: var(--radius);
    background: none;
    color: var(--muted);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
}
.item-delete-btn:hover { background: var(--danger-bg); color: var(--danger); }

/* Category · folder, above the name (services/item_card.card_kicker). */
.item-kicker {
    display: block;
    margin-bottom: 0.15rem;
    overflow: hidden;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    line-height: 1.4;
    text-overflow: ellipsis;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--brass);
}

/* One status per piece (services/item_card.status_label): a dot and a word,
   coloured by tone. Every tone in STATUS_TONES needs a rule here
   (tests/test_badge_styles.py). */
/* A block-level flex box sized to its text: inline-flex sat in a line box,
   whose strut added ~6px between the grid and the label. */
.status-label {
    display: flex;
    width: fit-content;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.3;
}
.status-label::before {
    content: "";
    flex-shrink: 0;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}
.status-label-running { color: var(--brass); }
.status-label-failed { color: var(--danger); }
.status-label-idle { color: var(--muted); }
.status-label-done { color: var(--accent); }
.status-label-listed { color: var(--info); }
.status-label-appraise { color: var(--warn); }
.status-label-ready { color: var(--ink-body); }

/* A small badge: gallery.html's "Revoked". The piece page's research badge,
   which printed .status-<research state>, gave way to its progress line in
   Stage 3 and those rules went with it (tests/test_badge_styles.py). Item
   lists show a .status-label instead (above). */
.status-badge {
    display: inline-block;
    margin-right: 0.3rem;
    padding: 0.12rem 0.45rem;
    border-radius: 3px;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: var(--paper-sunk);
    color: var(--muted);
}
.status-failed { background: var(--danger-bg); color: var(--danger); }

/* --- Folders ------------------------------------------------------------- */

.owners-card { margin-top: 1.75rem; }
.owners-card h2 { padding-bottom: 0.4rem; border-bottom: 1px solid var(--ink); }
.owners-hint { margin: 0.5rem 0 0.25rem; font-size: 0.85rem; color: var(--muted); }
.owner-list { list-style: none; margin-bottom: 0.75rem; }
.owner-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--rule);
}
.owner-row.empty-state { padding: 0.6rem 0; text-align: left; }
.owner-label { flex: 1; min-width: 6rem; font-family: var(--font-serif); font-size: 1.15rem; }
/* Beside the name, not among the buttons: there it pushed Delete onto a line
   of its own at phone width. */
.owner-count { margin-left: 0.4rem; font-family: var(--font-sans); font-size: 0.82rem; color: var(--muted); }
.account-who { margin: -0.5rem 0 0.5rem; color: var(--muted); }
.account-signout-section { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--rule); }
/* A name and three buttons do not fit one phone row: Delete used to wrap
   alone onto a second line. The name takes its own line, the buttons the
   next. */
@media (max-width: 30rem) {
    .owner-label { flex-basis: 100%; }
}

/* --- Home (templates/home.html) ------------------------------------------- */

.kicker-rule { padding: 0.45rem 0; border-bottom: 1px solid var(--rule); color: var(--muted); }
.headline {
    margin-top: 1.4rem;
    font-family: var(--font-serif);
    font-size: 2.2rem;
    font-weight: 400;
    line-height: 1.08;
    letter-spacing: -0.015em;
}
.headline em { color: var(--accent); }
.headline-summary { margin-top: 0.75rem; line-height: 1.55; color: var(--ink-body); }
.stat-strip {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    margin-top: 1.25rem;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
}
.stat {
    display: block;
    padding: 0.75rem 0 0.75rem 0.9rem;
    border-left: 1px solid var(--rule);
    color: var(--ink);
    text-decoration: none;
}
.stat:first-child { padding-left: 0; border-left: none; }
.stat:hover { color: var(--ink); background: var(--paper-sunk); }
.stat-count {
    display: block;
    font-family: var(--font-serif);
    font-size: 1.9rem;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
.stat-count-done { color: var(--accent); }
.stat-label { display: block; margin-top: 0.4rem; color: var(--muted); }
.section-cap { margin-top: 1.6rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--ink); }
.section-note { margin-top: 0.4rem; font-size: 0.85rem; line-height: 1.5; color: var(--muted); }
.attention-row {
    display: grid;
    grid-template-columns: 3.25rem minmax(0, 1fr) 16px;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--rule);
    color: var(--ink);
    text-decoration: none;
}
.attention-row:hover { color: var(--ink); background: var(--paper-sunk); }
.attention-count {
    font-family: var(--font-serif);
    font-size: 1.75rem;
    line-height: 1;
    color: var(--brass);
    font-variant-numeric: tabular-nums;
}
.attention-title { display: block; font-weight: 600; }
.attention-note { display: block; margin-top: 0.05rem; font-size: 0.82rem; color: var(--muted); }
.chevron {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: var(--muted);
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.attention-clear { padding: 0.75rem 0; font-family: var(--font-serif); font-style: italic; color: var(--muted); }
.folder-row {
    display: grid;
    grid-template-columns: 56px minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--rule);
    color: var(--ink);
    text-decoration: none;
}
.folder-row:hover { color: var(--ink); }
.folder-row:hover .folder-title { color: var(--accent); }
.folder-thumb {
    display: block;
    width: 56px;
    height: 56px;
    object-fit: cover;
    border-radius: 3px;
    background: var(--paper-sunk);
}
.folder-title {
    display: block;
    font-family: var(--font-serif);
    font-size: 1.2rem;
    line-height: 1.2;
    overflow-wrap: anywhere;
}
.folder-row-meta { display: block; margin-top: 0.1rem; font-size: 0.8rem; color: var(--muted); }
.folder-value { font-size: 0.9rem; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.folder-value-realized { color: var(--accent); }
.ledger-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--rule);
}
.ledger-owner { font-family: var(--font-serif); font-size: 1.2rem; }
.ledger-count { margin-left: 0.5rem; font-size: 0.8rem; color: var(--muted); }
.ledger-download {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 44px;
    font-size: 0.88rem;
    font-weight: 600;
    text-decoration: none;
}
.ledger-download svg {
    width: 17px;
    height: 17px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.ledger-alt {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-size: 0.85rem;
    color: var(--accent);
}
.ledger-unassigned {
    display: block;
    margin-top: 0.75rem;
    font-size: 0.88rem;
    font-weight: 600;
    line-height: 1.45;
    color: var(--warn);
    text-decoration: none;
}
.ledger-unassigned:hover { color: var(--warn); text-decoration: underline; }

/* --- Facet bar ----------------------------------------------------------- */

/* The bar's heavy bottom rule is the top of the list beneath it. It used to
   be the first card's top border, which doubled up with this bar's own rule
   (and, on a box or gallery page, with the section heading's) — two lines a
   finger apart — and vanished when a filter hid that first card. */
.facet-bar {
    padding: 0.25rem 0;
    border-top: 1px solid var(--rule);
    border-bottom: 2px solid var(--ink);
}
.section-heading + .facet-bar { border-top: none; }
.facet-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.facet-toggle {
    min-height: 44px;
    padding: 0;
    border: none;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
}
.sort-select { width: auto; max-width: 55%; font-size: 0.9rem; }
.facet-body { margin-top: 0.4rem; }
.facet-row {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.5rem 0;
    border-top: 1px solid var(--rule);
}
.facet-label {
    flex-shrink: 0;
    width: 5.5rem;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
}
.chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.chip {
    min-height: 32px;
    padding: 0.25rem 0.7rem;
    border: 1px solid var(--rule);
    border-radius: 999px;
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: 0.82rem;
    cursor: pointer;
}
.chip-on { border-color: var(--ink); background: var(--ink); color: var(--paper); }
/* An option with no matches under the other filters. Marked by a dashed
   outline and muted text (6:1 or better), not faded: opacity took the text
   below 3:1, and a selected-but-empty chip still has to be read to be cleared. */
.chip-dim { color: var(--muted); border-style: dashed; border-color: var(--muted); }
.chip-on.chip-dim { color: var(--paper); border-color: var(--paper); }
.chip-count { font-size: 0.72rem; font-variant-numeric: tabular-nums; }
.facet-clear {
    min-height: 44px;
    padding: 0;
    border: none;
    background: none;
    color: var(--accent);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}

/* --- Pieces and folder pages (templates/list.html) ------------------------- */

.back-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    margin-bottom: 0.25rem;
    font-weight: 600;
    color: var(--ink);
    text-decoration: none;
}
.back-link:hover { color: var(--accent); }
.list-head { margin-bottom: 0.9rem; }
.list-title-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 0.75rem; }
.list-kicker { color: var(--muted); }
.list-title { margin: 0.2rem 0 0; overflow-wrap: anywhere; }
.list-count { font-size: 1rem; color: var(--brass); vertical-align: super; font-variant-numeric: tabular-nums; }
.folder-summary { margin-top: 0.35rem; font-size: 0.9rem; color: var(--muted); }
.folder-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.75rem; }
.search-field {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-height: 44px;
    border-bottom: 1px solid var(--ink);
    color: var(--muted);
}
.search-field svg {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.search-field input[type="search"] {
    flex: 1;
    min-width: 0;
    padding: 0.4rem 0;
    border: none;
    border-radius: 0;
    background: transparent;
    -webkit-appearance: none;
    appearance: none;
    font-family: var(--font-serif);
    font-size: 1.05rem;
    font-style: italic;
    color: var(--ink);
}
.filter-tabs {
    display: flex;
    gap: 1.1rem;
    margin-top: 0.6rem;
    overflow-x: auto;
    scrollbar-width: none;
}
.filter-tabs::-webkit-scrollbar { display: none; }
.filter-tab {
    flex-shrink: 0;
    min-height: 44px;
    padding: 0;
    border: none;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--muted);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
}
.filter-tab[aria-pressed="true"] { border-bottom-color: var(--accent); color: var(--ink); font-weight: 700; }
.filter-tab-count { font-variant-numeric: tabular-nums; }
.sort-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 0.35rem;
    padding-bottom: 0.25rem;
    border-bottom: 2px solid var(--ink);
}
.sort-row .sort-select {
    max-width: 65%;
    padding-left: 0;
    border: none;
    background-color: transparent;
    font-family: var(--font-serif);
    font-size: 0.95rem;
    font-style: italic;
    color: var(--muted);
}

/* The filter sheet: the full facet bar in a <dialog>, rising from the bottom
   on a phone and centred on a laptop. */
.sheet {
    width: 100%;
    max-width: 40rem;
    max-height: 85vh;
    margin: auto auto 0;
    padding: 0;
    overflow-y: auto;
    border: 1px solid var(--ink);
    border-bottom: none;
    border-radius: var(--radius) var(--radius) 0 0;
    background: var(--paper);
    color: var(--ink);
    box-shadow: var(--shadow);
}
.sheet::backdrop { background: var(--scrim); }
.sheet-body { padding: 0.9rem 1.25rem calc(1rem + env(safe-area-inset-bottom)); }
.sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 44px;
    border-bottom: 2px solid var(--ink);
}
.sheet .facet-bar { padding: 0; border: none; }
@media (min-width: 960px) {
    .sheet { margin: auto; border-bottom: 1px solid var(--ink); border-radius: var(--radius); }
}

/* --- Select mode (templates/list.html; initSelectMode in app.js) ------------ */

/* Checkboxes show only while selecting. The box and gallery pick lists, which
   are always pickers, are not .piece-list and keep theirs. */
.piece-list:not(.selecting) .item-checkbox { display: none; }
.piece-list.selecting .item-card { cursor: pointer; }
.piece-list.selecting .item-card:has(.item-checkbox:checked) {
    margin: 0 -0.6rem;
    padding-left: 0.6rem;
    padding-right: 0.6rem;
    background: var(--paper-sunk);
}
/* The photo-less placeholder is --paper-sunk too; on a ticked row it would
   vanish into the row. */
.piece-list.selecting .item-card:has(.item-checkbox:checked) .item-thumb-placeholder { background: var(--rule); }
.select-all {
    min-height: 44px;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-decoration: none;
    text-transform: uppercase;
}
.select-toggle, .select-done { min-height: 44px; font-weight: 700; }
.select-done { text-decoration: none; }
.selection-line { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; margin-top: 0.35rem; }
.selection-summary { font-family: var(--font-serif); font-style: italic; color: var(--muted); }

/* In place of the tab bar while selecting: dark, five actions. */
.action-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 55;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    padding: 0.25rem max(0.4rem, env(safe-area-inset-right)) env(safe-area-inset-bottom) max(0.4rem, env(safe-area-inset-left));
    background: var(--bar);
    color: var(--on-bar);
    /* The tab bar's own top rule, kept: the same colour as the bar in light
       mode, and the bar's edge against a dark page at night. */
    border-top: 1px solid var(--ink);
}
.action {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    min-height: 56px;
    border: none;
    background: none;
    color: var(--on-bar);
    font: inherit;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    cursor: pointer;
}
.action svg {
    width: 22px;
    height: 22px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* Disabled until something is ticked: the bar's own quieter token (the page's
   --muted is not drawn for the dark bar), never faded. */
.action:disabled { color: var(--on-bar-quiet); cursor: default; }
/* The accent focus ring is invisible on the bar. */
.action:focus-visible { outline-color: var(--on-bar); outline-offset: -4px; }
/* The action bar replaces the nav at every width (inline in the masthead from 960px). */
body.selecting .tabbar { display: none; }
@media (min-width: 960px) {
    .action-bar {
        left: 50%;
        right: auto;
        width: min(40rem, calc(100% - 2.5rem));
        transform: translateX(-50%);
        border: 1px solid var(--ink);
        border-bottom: none;
        border-radius: var(--radius) var(--radius) 0 0;
    }
    /* Toasts sit at the bottom on a laptop; lift them clear of the bar. */
    body.selecting .toast-region { bottom: calc(var(--tabbar-h) + 0.75rem); }
    /* main's laptop padding (3rem) is shorter than the bar, which would
       cover the last row's foot. */
    body.selecting main { padding-bottom: calc(var(--tabbar-h) + 2rem); }
}

/* --- The piece page (templates/detail.html) ---------------------------- */

.piece-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 44px;
}
.piece-back {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-weight: 600;
    color: var(--ink);
    text-decoration: none;
}
.piece-back:hover { color: var(--accent); }

/* The ⋯ menu: a <details>, so it opens without JavaScript. */
.piece-menu { position: relative; }
.piece-menu > summary {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    list-style: none;
    color: var(--ink);
    cursor: pointer;
}
.piece-menu > summary::-webkit-details-marker { display: none; }
.piece-menu > summary svg { width: 21px; height: 21px; fill: currentColor; }
.piece-menu-panel {
    position: absolute;
    top: 100%;
    right: 0;
    z-index: 60;
    min-width: 13rem;
    padding: 0.35rem 0;
    background: var(--paper-raised);
    border: 1px solid var(--ink);
    box-shadow: var(--shadow);
}
.piece-menu-item {
    display: block;
    width: 100%;
    min-height: 44px;
    padding: 0.65rem 1rem;
    border: none;
    background: none;
    font: inherit;
    text-align: left;
    color: var(--ink);
    text-decoration: none;
    cursor: pointer;
}
.piece-menu-item:hover { background: var(--paper-sunk); color: var(--ink); }
.piece-menu-danger, .piece-menu-danger:hover { color: var(--danger); }

/* The photo plate: the cover framed like a catalog plate, then every photo
   as a thumbnail. Each opens the lightbox. */
.plate { margin: 0.4rem 0 0; }
.plate-frame { padding: 0.6rem; background: var(--paper-raised); border: 1px solid var(--rule); }
.plate-photo { display: block; width: 100%; max-height: 320px; object-fit: contain; cursor: zoom-in; }
.plate-caption {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 0.5rem;
}
.plate-count { font-family: var(--font-serif); font-size: 0.9rem; font-style: italic; color: var(--muted); }
.plate-thumbs { display: flex; gap: 0.4rem; overflow-x: auto; }
.plate-thumb { flex-shrink: 0; width: 40px; height: 40px; object-fit: cover; cursor: zoom-in; }
.plate-thumb:first-child { outline: 1.5px solid var(--ink); outline-offset: 1px; }

/* No photo yet: the plate's place, holding the sentence that says why it
   matters. */
.plate-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 7rem;
    margin-top: 0.4rem;
    padding: 1rem;
    background: var(--paper-raised);
    border: 1px dashed var(--rule);
}
.plate-empty-text { font-family: var(--font-serif); font-style: italic; text-align: center; color: var(--muted); }
/* Add photos: one quiet line under the plate, so it does not crowd the name. */
.add-photos { display: flex; flex-wrap: wrap; align-items: center; gap: 0 1.1rem; margin-top: 0.15rem; }
.add-photos-label { color: var(--muted); }
.photo-pick-quiet {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 44px;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--accent);
    cursor: pointer;
}
.photo-pick-quiet:hover { color: var(--accent-hover); }
/* Two classes: .form-note comes later in this file and won. */
.form-note.add-photos-status { flex-basis: 100%; margin-top: 0; }
.form-note.add-photos-status:empty { display: none; }

.piece-kicker {
    margin-top: 1.25rem;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--brass);
}
.piece-name {
    margin-top: 0.5rem;
    font-family: var(--font-serif);
    font-size: 1.75rem;
    font-weight: 400;
    line-height: 1.16;
    letter-spacing: -0.01em;
}

.estimate-block {
    display: grid;
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    margin-top: 1.1rem;
    border-top: 2px solid var(--ink);
    border-bottom: 1px solid var(--rule);
}
.estimate-figure-cell { padding: 0.75rem 0; }
.estimate-block .cap { color: var(--muted); }
.estimate-figure {
    margin-top: 0.25rem;
    font-family: var(--font-serif);
    font-size: 2.1rem;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
.estimate-side { padding: 0.75rem 0 0.75rem 0.9rem; border-left: 1px solid var(--rule); }
.estimate-confidence { margin-top: 0.35rem; font-weight: 600; }
.estimate-note { margin-top: 0.1rem; font-size: 0.8rem; color: var(--muted); }

/* Added → Researched → Listed → Done. The line before a step is accent once
   that step is done. */
.progress {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.9rem 0 0;
    padding: 0;
    list-style: none;
    font-size: 0.8rem;
    font-weight: 600;
}
.progress-step { display: flex; align-items: center; gap: 0.3rem; color: var(--ink); }
.progress-dot { width: 9px; height: 9px; border-radius: 50%; box-sizing: border-box; }
.progress-done { color: var(--ink); }
.progress-done .progress-dot { background: var(--accent); }
.progress-current { color: var(--accent); }
.progress-current .progress-dot { border: 1.5px solid var(--accent); }
.progress-todo, .progress-skipped { color: var(--muted); }
.progress-todo .progress-dot { border: 1.5px solid var(--muted); }
.progress-skipped .progress-dot { border: 1.5px dashed var(--muted); }
.progress-line { flex: 1; height: 1px; background: var(--rule); }
.progress-line-done { background: var(--accent); }

/* The four section tabs. Without JavaScript they are in-page links and every
   panel shows, each under its own heading; initPieceTabs adds .tabs-ready to
   <body> and shows one panel at a time. */
.piece-tabs {
    display: flex;
    gap: 1.4rem;
    margin-top: 1.4rem;
    overflow-x: auto;
    overflow-y: hidden;
    box-shadow: inset 0 -1px 0 var(--ink);
}
.piece-tab {
    padding: 0.7rem 0 0.6rem;
    border-bottom: 2px solid transparent;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--muted);
    text-decoration: none;
}
.piece-tab:hover { color: var(--ink); }
.piece-tab[aria-selected="true"] { border-bottom-color: var(--accent); color: var(--ink); }
.piece-panel { padding-top: 1.25rem; }
.panel-heading {
    margin-bottom: 0.25rem;
    font-family: var(--font-serif);
    font-size: 1.35rem;
    font-weight: 400;
}
.tabs-ready .panel-heading {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
.piece-panel .section-heading {
    margin: 1.6rem 0 0;
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--ink);
}
.piece-panel .panel-heading + .section-heading { margin-top: 0.4rem; }

/* The Overview. */
.outcome-note { margin-top: 0.5rem; padding: 1rem 1.1rem; background: var(--done-bg); }
.outcome-note .cap { color: var(--muted); }
.outcome-line { margin-top: 0.4rem; color: var(--ink); }
.outcome-notes { margin-top: 0.3rem; font-size: 0.875rem; color: var(--muted); }
.outcome-note .btn-secondary { margin-top: 0.75rem; }
.appraisal-note { margin-top: 1rem; padding: 1rem 1.1rem; background: var(--warn-bg); }
.appraisal-note .cap { color: var(--warn); }
.appraisal-text {
    margin-top: 0.5rem;
    font-family: var(--font-serif);
    font-size: 1.1rem;
    font-style: italic;
    line-height: 1.4;
    color: var(--ink-body);
}
.piece-description, .piece-notes { margin-top: 0.6rem; line-height: 1.6; color: var(--ink-body); }
/* Clamped only once JavaScript has measured it and can offer "Continue
   reading" (initDescriptionClamp): without it the whole description shows. */
.piece-description.clamped {
    display: -webkit-box;
    overflow: hidden;
    -webkit-line-clamp: 5;
    -webkit-box-orient: vertical;
}
#description-more { margin-top: 0.35rem; font-weight: 600; }
.particulars { margin: 0; font-size: 0.92rem; }
.particular {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--rule);
}
.particular dt { color: var(--muted); }
.particular dd { margin: 0; font-weight: 600; text-align: right; }
.particular-attention {
    color: var(--warn);
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 3px;
}
.best-venue {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 1.4rem;
    padding: 0.9rem 1rem;
    border: 1px solid var(--ink);
    color: var(--ink);
    text-decoration: none;
}
.best-venue::after {
    content: "";
    flex-shrink: 0;
    width: 0.5rem;
    height: 0.5rem;
    border-top: 1.5px solid currentColor;
    border-right: 1.5px solid currentColor;
    transform: rotate(45deg);
}
.best-venue:hover { color: var(--ink); background: var(--paper-sunk); }
.best-venue .cap { display: block; color: var(--muted); }
.best-venue-name { display: block; margin-top: 0.25rem; font-family: var(--font-serif); font-size: 1.2rem; }
.best-venue-draft { font-family: var(--font-sans); font-size: 0.82rem; font-weight: 600; color: var(--accent); }

/* The Sell panel. */
.venue-ranks { margin: 0; padding: 0; list-style: none; }
.venue-rank {
    display: grid;
    grid-template-columns: 1.75rem minmax(0, 1fr);
    gap: 0.6rem;
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--rule);
}
.venue-rank-number { font-family: var(--font-serif); font-size: 1.5rem; line-height: 1; color: var(--brass); }
.venue-rank-name { font-family: var(--font-serif); font-size: 1.12rem; }
.venue-rank-reason { margin-top: 0.15rem; font-size: 0.85rem; line-height: 1.5; color: var(--muted); }
.sell-note { margin-top: 0.75rem; font-size: 0.875rem; color: var(--ink-body); }
.venue-chips {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.75rem;
    padding-bottom: 0.25rem;
    overflow-x: auto;
}
.venue-chip { position: relative; display: inline-flex; flex-shrink: 0; align-items: center; gap: 0.4rem; min-height: 36px; }
/* The radio itself is hidden (clipped, as .visually-hidden does, so it stays
   in the tab order); the chip is the control, and its focus ring is drawn on
   the chip. */
.venue-chip input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
}
.venue-chip:has(input:checked) { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.venue-chip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.draft-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.venue-chip:has(input:checked) .draft-dot { background: var(--paper); }
.draft-card {
    margin-top: 0.9rem;
    padding: 1rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
}
.draft-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.draft-card-head .cap { color: var(--muted); }
.draft-price { font-size: 0.875rem; color: var(--muted); }
.draft-price strong { color: var(--ink); }
.draft-card label { display: block; margin-top: 0.9rem; color: var(--muted); }
.draft-title { font-family: var(--font-serif); font-size: 1.05rem; }
.draft-body { min-height: 9rem; }
.draft-actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: 0.5rem; margin-top: 0.75rem; }
.draft-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 0.75rem;
    font-size: 0.8rem;
    color: var(--muted);
}
.draft-foot .link-button { font-size: 0.875rem; font-weight: 700; }
#generate-draft { margin-top: 0.9rem; }

.listing-row { padding: 0.75rem 0; border-bottom: 1px solid var(--rule); }
.listing-row-head { display: flex; justify-content: space-between; gap: 1rem; padding: 0.35rem 0; }
.listing-venue { font-weight: 600; }
.listing-meta { font-size: 0.875rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.listing-link { font-size: 0.875rem; }
.listing-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
.empty-line {
    margin-top: 0.75rem;
    font-family: var(--font-serif);
    font-size: 1rem;
    font-style: italic;
    color: var(--muted);
}
.past-listings { margin-top: 0.75rem; }
.past-listings > summary { min-height: 44px; padding: 0.6rem 0; color: var(--accent); font-weight: 600; cursor: pointer; }

/* A labelled field: the label over its control. Used by the listing form
   here and by the Details panel and the outcome sheet. */
.field { margin-top: 0.9rem; }
.field > label, .field > .cap { display: block; margin-bottom: 0.15rem; color: var(--muted); }
.field-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 0.9rem; }
.optional { font-weight: 500; letter-spacing: 0.04em; text-transform: none; }
.listing-form > .btn-primary { margin-top: 1.1rem; }
.form-note { margin-top: 0.6rem; font-size: 0.8rem; line-height: 1.5; color: var(--muted); }
.listing-reminder { margin-top: 0.15rem; font-size: 0.8rem; font-weight: 600; color: var(--warn); }

/* The Research and Details panels. */
.research-status { margin-top: 0.75rem; color: var(--ink-body); }
.research-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.9rem; margin-top: 0.75rem; }
.source-list { margin: 0; padding: 0; list-style: none; }
.piece-panel .edit-details { margin-top: 0.4rem; }
.danger-zone { margin-top: 2rem; }
.danger-zone .section-heading { border-bottom-color: var(--danger); color: var(--danger); }
.danger-zone .btn-danger { margin-top: 0.75rem; }

/* The pinned action bar, in place of the tab bar: body.piece-page hides the
   tab bar at every width, as on any page with its own pinned bar. */
body.piece-page .tabbar { display: none; }
body.piece-page main { padding-bottom: calc(var(--piece-bar-h) + 1.5rem + env(safe-area-inset-bottom)); }
body.piece-page .toast-region { bottom: calc(var(--piece-bar-h) + env(safe-area-inset-bottom) + 0.75rem); }
/* A control tabbed to near the bottom scrolls clear of the fixed bar. */
html:has(body.piece-page) { scroll-padding-bottom: calc(var(--piece-bar-h) + env(safe-area-inset-bottom)); }
.piece-bar {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 55;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.75rem max(1.25rem, env(safe-area-inset-right)) calc(0.75rem + env(safe-area-inset-bottom)) max(1.25rem, env(safe-area-inset-left));
    background: var(--paper);
    border-top: 1px solid var(--ink);
}
.piece-bar > .btn-secondary { flex: 1; min-height: 48px; }
.piece-bar > .btn-primary { flex: 1.3; min-height: 48px; }
.piece-bar-outcome { flex: 1; font-family: var(--font-serif); font-size: 1.1rem; }
@media (min-width: 960px) {
    .piece-bar {
        right: auto;
        left: 50%;
        width: min(40rem, calc(100% - 2.5rem));
        border: 1px solid var(--ink);
        border-bottom: none;
        border-radius: var(--radius) var(--radius) 0 0;
        transform: translateX(-50%);
    }
    body.piece-page main { padding-bottom: calc(var(--piece-bar-h) + 2rem); }
    /* A mouse wheel cannot scroll the strip sideways, and the strip clips a
       chip's focus ring, so at this width the chips wrap. */
    .venue-chips { flex-wrap: wrap; overflow-x: visible; }
}

/* The Record outcome sheet: a <dialog> rising from the bottom, built on the
   filter sheet's .sheet. */
.outcome-sheet .sheet-head { align-items: flex-start; padding-bottom: 0.6rem; }
.outcome-sheet-title {
    margin-top: 0.25rem;
    font-family: var(--font-serif);
    font-size: 1.35rem;
    font-weight: 400;
    line-height: 1.2;
}
.sheet-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin: -0.4rem -0.6rem 0 0;
    border: none;
    background: none;
    color: var(--ink);
    cursor: pointer;
}
.sheet-close svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.choice-group { margin: 1rem 0 0; padding: 0; border: none; }
.choice-group legend { padding: 0; color: var(--muted); }
.choice-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
.choice-chip {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-height: 38px;
    padding: 0 1rem;
    border: 1px solid var(--rule);
    border-radius: 999px;
    font-size: 0.9rem;
    cursor: pointer;
}
/* The radio itself is clipped, as .visually-hidden does; the chip is the
   control and carries the focus ring. The chip is positioned so the clipped
   radio stays inside it, as the venue chips' does. */
.choice-chip input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
}
.choice-chip:has(input:checked) { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.choice-chip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.choice-chip-small { min-height: 34px; padding: 0 0.75rem; font-size: 0.82rem; }
.net-line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-top: 1.1rem;
    padding: 0.75rem 0;
    border-top: 2px solid var(--ink);
    border-bottom: 1px solid var(--rule);
}
.net-line output { font-family: var(--font-serif); font-size: 1.85rem; line-height: 1; font-variant-numeric: tabular-nums; }
.sheet-note { margin-top: 0.9rem; font-size: 0.8rem; line-height: 1.5; color: var(--muted); }
.sheet-error { margin-top: 0.75rem; font-size: 0.875rem; color: var(--danger); }
.sheet-error:empty { display: none; }
.outcome-sheet .btn-primary.w-full { margin-top: 1rem; min-height: 52px; }

.detail-card {
    margin-bottom: 0.9rem;
    padding: 1rem 1.1rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}
.detail-card h2 {
    margin-bottom: 0.75rem;
    font-family: var(--font-serif);
    font-size: 1.65rem;
    font-weight: 400;
    line-height: 1.15;
    letter-spacing: -0.01em;
}
.detail-card h3, .card-title {
    margin-bottom: 0.6rem;
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--ink);
    color: var(--ink);
}
.source-item { padding: 0.6rem 0; border-bottom: 1px solid var(--rule); font-size: 0.9rem; }
.source-item:last-child { border-bottom: none; }
.source-item a { display: block; margin-bottom: 0.2rem; color: var(--accent); text-decoration: none; }
.source-type {
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--brass);
}
.enrichment-error { margin-top: 0.5rem; font-size: 0.875rem; color: var(--danger); overflow-wrap: anywhere; }

.raw-data-section { margin-bottom: 0.9rem; }
.raw-data-section > summary {
    display: block;
    padding: 0.6rem 0;
    list-style: none;
    color: var(--accent);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
}
.raw-data-section > summary::-webkit-details-marker { display: none; }
.enrichment-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.enrichment-table th {
    width: 35%;
    padding: 0.45rem 0.6rem 0.45rem 0;
    text-align: left;
    vertical-align: top;
    font-weight: 600;
    color: var(--muted);
}
.enrichment-table td { padding: 0.45rem 0; vertical-align: top; color: var(--ink); }
.enrichment-table tr { border-bottom: 1px solid var(--rule); }
.enrichment-table tr:last-child { border-bottom: none; }
.raw-json-details {
    margin-bottom: 0.5rem;
    padding: 0.75rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}
.raw-json-details > summary { list-style: none; color: var(--muted); font-size: 0.875rem; font-weight: 600; cursor: pointer; }
.raw-json-details > summary::-webkit-details-marker { display: none; }
.raw-json-pre {
    max-height: 300px;
    margin-top: 0.5rem;
    padding: 0.5rem;
    overflow: auto;
    border-radius: 3px;
    background: var(--paper-sunk);
    color: var(--ink);
    font-family: var(--font-mono);
    font-size: 0.7rem;
    white-space: pre;
}

/* The card follows the full-width Delete Item button, which has no bottom
   margin of its own. */
.edit-details { margin-top: 0.9rem; }
.edit-details > summary {
    display: flex;
    align-items: center;
    min-height: 44px;
    list-style: none;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
}
.edit-details > summary::-webkit-details-marker { display: none; }
/* The browser's own disclosure triangle is hidden above, so this chevron is
   what says the card opens: without it the closed card read as an empty
   heading. */
.edit-details > summary::after {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    margin: -0.25rem 0.3rem 0 auto;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg);
}
.edit-details[open] > summary::after { margin-top: 0.25rem; transform: rotate(-135deg); }
.edit-details label { display: block; margin: 0.8rem 0 0.1rem; color: var(--muted); }
.edit-details textarea { min-height: 5rem; }
.edit-details .edit-details-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.95rem;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
}

/* How many research runs an account has left this month: a quiet caption
   while runs remain, danger once none do — the one state worth drawing the
   eye to, since Research still answers 429 if you try anyway. */
.quota-note { font-size: 0.82rem; color: var(--muted); }
.quota-note-out { color: var(--danger); }

/* --- Boxes and labels ------------------------------------------------------ */

/* Find a box: the field reads like the printed label, "BOX" then the digits. */
.box-find { margin-bottom: 1.25rem; }
.box-find > .cap { display: block; color: var(--muted); }
.box-find-row { display: flex; gap: 0.5rem; margin-top: 0.4rem; }
.box-find-field {
    display: flex;
    flex: 1;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    min-height: 48px;
    padding: 0 0.75rem;
    border: 1.5px solid var(--ink);
    border-radius: var(--radius);
    background: var(--paper-raised);
}
.box-find-field:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.box-find-prefix { font-family: var(--font-serif); font-size: 1.25rem; font-weight: 600; letter-spacing: 0.04em; }
.box-find-field input {
    flex: 1;
    min-width: 0;
    border-bottom: none;
    font-family: var(--font-serif);
    font-size: 1.25rem;
    letter-spacing: 0.06em;
}
/* The field's own ring is drawn around the whole label-style box. */
.box-find-field input:focus-visible { outline: none; }
.box-find-row > .btn-secondary { min-width: 4.5rem; min-height: 48px; }

.box-section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 1.5rem;
    padding-bottom: 0.3rem;
    border-bottom: 2px solid var(--ink);
}
.box-print-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 44px;
    font-size: 0.88rem;
    font-weight: 600;
    text-decoration: none;
}
.box-print-link svg {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* A box card is drawn like its printed label: the code large, the name, and
   the QR. */
.box-cards { display: grid; gap: 0.75rem; margin-top: 0.9rem; }
@media (min-width: 600px) {
    .box-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.box-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 64px;
    align-items: center;
    gap: 0.9rem;
    padding: 0.9rem 1rem;
    background: var(--paper-raised);
    border: 1px solid var(--ink);
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
}
.box-card:hover { background: var(--paper-sunk); color: var(--ink); }
.box-card-code {
    display: block;
    font-family: var(--font-serif);
    font-size: 1.75rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.02em;
    white-space: nowrap;
}
.box-card-name { display: block; margin-top: 0.4rem; font-weight: 600; overflow-wrap: anywhere; }
.box-card-meta { display: block; margin-top: 0.1rem; font-size: 0.82rem; color: var(--muted); }
/* Black on white in either mode, as the printed label is: inverted, a QR is
   a pattern many cameras will not read. */
.box-card-qr { display: block; width: 64px; height: 64px; padding: 4px; background: var(--label-paper); border: 1px solid var(--rule); }
.box-card-qr svg { display: block; width: 100%; height: 100%; }
.box-unboxed {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    margin-top: 1.25rem;
    padding: 0.75rem 0;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    color: var(--ink-body);
}
.box-unboxed-count {
    font-family: var(--font-serif);
    font-size: 1.75rem;
    line-height: 1;
    color: var(--brass);
    font-variant-numeric: tabular-nums;
}

/* One box: its code as the title, its form as a panel. */
.box-head { margin-bottom: 1rem; }
.box-head-code {
    margin-top: 0.2rem;
    font-family: var(--font-serif);
    font-size: 2.4rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.02em;
    white-space: nowrap;
}
.box-head-name { margin-top: 0.45rem; font-family: var(--font-serif); font-size: 1.25rem; overflow-wrap: anywhere; }
.box-head-meta { margin-top: 0.2rem; font-size: 0.88rem; color: var(--muted); }
.box-panel {
    margin-bottom: 1.25rem;
    padding: 0.25rem 1.1rem 1.1rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}
.box-sealed { display: flex; align-items: center; gap: 0.5rem; margin: 0.9rem 0; font-size: 0.95rem; }
.box-delete { margin-top: 1.25rem; }
.box-find-error {
    margin-bottom: 0.75rem;
    padding: 0.6rem 0.75rem;
    border-radius: var(--radius);
    background: var(--danger-bg);
    color: var(--danger);
    font-size: 0.875rem;
}
.box-tag-url { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.75rem; }
/* A read-only URL beside a Copy button, not a form field. */
.box-tag-url input[type="text"] {
    flex: 1;
    width: auto;
    min-width: 0;
    border: none;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    color: var(--muted);
}

.label-sheet { display: flex; flex-wrap: wrap; gap: 0.75rem; }
/* A label is a print preview: paper-white with black ink in light or dark
   mode, and in the font and line height its sizes were measured in. The
   .label-code line (1.32in of text in a 1.50in column on the 3x2 sticker)
   was measured in the system font at normal line height; inheriting the
   app's font would change that width with no error. */
.label-card {
    width: 100%;
    max-width: 20rem;
    padding: 0.9rem;
    border: 1px solid var(--rule);
    border-radius: 12px;
    background: var(--label-paper);
    color: var(--label-ink);
    font-family: var(--font-system);
    line-height: normal;
    text-align: center;
    page-break-inside: avoid;
    break-inside: avoid;
}
.label-code { font-size: 2rem; font-weight: 700; letter-spacing: 0.03em; }
.label-name { margin-top: 0.15rem; font-size: 0.95rem; color: var(--label-ink-soft); }
/* The QR carries explicit width/height attributes from the library; CSS wins
   over presentation attributes, so this is what makes it scale. */
.label-qr svg { display: block; width: 100%; height: auto; max-width: 12rem; margin: 0.5rem auto 0; }
.label-help { margin: 0.75rem 0; font-size: 0.875rem; color: var(--ink-body); }
/* Which size the label pages will print at. The active one is plain text,
   not a link: with two layouts the failure is printing one while meaning the
   other, so the control has to say which is current. */
.label-size-toggle { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0.75rem 0; font-size: 0.875rem; }
.label-size-caption { color: var(--muted); }
.label-size-active { padding: 0.2rem 0.65rem; border-radius: 999px; background: var(--ink); color: var(--paper); font-weight: 600; }
.label-size-toggle a { padding: 0.2rem 0.65rem; border-radius: 999px; color: var(--accent); }

/* --- Galleries ------------------------------------------------------------- */

/* The list: one row per gallery, as Home lists folders. */
.gallery-list { border-top: 2px solid var(--ink); }
.gallery-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 16px;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--rule);
    color: var(--ink);
    text-decoration: none;
}
.gallery-row:hover { background: var(--paper-sunk); color: var(--ink); }
.gallery-row-name { display: block; font-family: var(--font-serif); font-size: 1.25rem; line-height: 1.2; overflow-wrap: anywhere; }
.gallery-row-meta { display: block; margin-top: 0.1rem; font-size: 0.82rem; color: var(--muted); overflow-wrap: anywhere; }
.gallery-row-revoked { font-weight: 600; color: var(--danger); }
.gallery-row-url { display: block; margin-top: 0.2rem; font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); overflow-wrap: anywhere; }
/* The owner's gallery page. */
.share-url { margin-top: 0.6rem; font-family: var(--font-mono); font-size: 0.8rem; }
.share-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
.gallery-delete { margin-top: 2rem; }

/* The public page (gallery_public.html): a catalog of framed photographs.
   No tab bar here, so none of the bottom padding main keeps for one. */
body.public-page main { padding-bottom: calc(2.5rem + env(safe-area-inset-bottom)); }
.public-kicker { padding-bottom: 0.45rem; border-bottom: 2px solid var(--ink); color: var(--muted); }
.public-title {
    margin-top: 1.1rem;
    font-family: var(--font-serif);
    font-size: 2.4rem;
    font-weight: 400;
    line-height: 1.05;
    letter-spacing: -0.015em;
    overflow-wrap: anywhere;
}
.public-intro { margin-top: 0.6rem; line-height: 1.55; color: var(--ink-body); }
.gallery-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.4rem 0.9rem; margin-top: 1.4rem; }
@media (min-width: 600px) {
    .gallery-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.gallery-tile {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 0;
    padding: 0;
    border: none;
    background: none;
    color: var(--ink);
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.gallery-tile-plate { display: block; padding: 6px; background: var(--paper-raised); border: 1px solid var(--rule); }
.gallery-tile img, .gallery-tile-nophoto { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; }
.gallery-tile-nophoto {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--paper-sunk);
    color: var(--muted);
    font-size: 0.8rem;
}
.gallery-tile-kicker {
    margin-top: 0.35rem;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--brass);
}
/* Two lines at most; the popup shows the whole name. */
.gallery-tile-name {
    display: -webkit-box;
    overflow: hidden;
    font-family: var(--font-serif);
    font-size: 1.05rem;
    line-height: 1.22;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}
/* A quiet caption, not a warning: the piece is no longer up for grabs, which
   is nothing for the recipient to be alarmed about. */
.gallery-tile-sold { font-size: 0.8rem; color: var(--muted); }
.gallery-tile-value { font-size: 0.85rem; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.gallery-empty { padding: 1rem 0; color: var(--muted); }
.public-footer {
    margin-top: 2rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--rule);
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--muted);
}

/* --- Sign in, sign up, forgot, reset (auth_base.html) --------------------- */

.auth-card {
    max-width: 23rem;
    margin: 0 auto;
    padding: max(4.5rem, env(safe-area-inset-top)) max(1.5rem, env(safe-area-inset-left)) 3rem;
}
.auth-kicker { color: var(--brass); }
.auth-card h1 {
    margin-top: 0.6rem;
    font-family: var(--font-serif);
    font-size: 2.75rem;
    font-weight: 500;
    line-height: 1;
    letter-spacing: -0.02em;
}
.auth-tagline {
    margin: 0.75rem 0 1.5rem;
    padding-bottom: 1.4rem;
    border-bottom: 2px solid var(--ink);
    font-family: var(--font-serif);
    font-size: 1.2rem;
    font-style: italic;
    line-height: 1.35;
    color: var(--ink-body);
}
/* "or", between two hairlines, before Continue with Google. */
.auth-or { display: flex; align-items: center; gap: 0.75rem; margin-top: 1.1rem; font-size: 0.85rem; color: var(--muted); }
.auth-or::before, .auth-or::after { content: ""; flex: 1; height: 1px; background: var(--rule); }
.auth-form { display: flex; flex-direction: column; gap: 0.25rem; }
/* display:flex above outranks the browser's own [hidden] rule, so without this
   reset.html's form.hidden = true left the form on screen on an expired link. */
.auth-form[hidden] { display: none; }
.auth-form label { margin-top: 0.9rem; color: var(--muted); }
.auth-form button {
    min-height: 52px;
    margin-top: 1.5rem;
    border: none;
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--on-accent);
    font: inherit;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
}
.auth-form button:hover { background: var(--accent-hover); }
.auth-alt {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 52px;
    margin-top: 1.1rem;
    border: 1px solid var(--ink);
    border-radius: var(--radius);
    color: var(--ink);
    font-weight: 600;
    text-decoration: none;
}
.auth-note { margin-top: 1.25rem; font-size: 0.85rem; line-height: 1.5; text-align: center; color: var(--muted); }
.auth-links { margin-top: 1.25rem; font-size: 0.9rem; text-align: center; }
.auth-error, .auth-notice { margin-bottom: 1rem; padding: 0.7rem 0.8rem; border-radius: var(--radius); font-size: 0.9rem; }
.auth-error { background: var(--danger-bg); color: var(--danger); }
.auth-notice { background: var(--done-bg); color: var(--accent); }

/* --- The bulk Move / Set owner dialog (list page) -------------------------- */

.choice-dialog {
    width: min(92vw, 24rem);
    padding: 1.25rem;
    border: 1px solid var(--ink);
    border-radius: var(--radius);
    background: var(--paper);
    color: var(--ink);
    box-shadow: var(--shadow);
}
.choice-dialog::backdrop { background: var(--scrim); }
.choice-dialog h3 { margin: 0 0 0.9rem; font-family: var(--font-serif); font-size: 1.3rem; font-weight: 500; }
.choice-dialog select, .choice-dialog input[type="text"] { display: block; margin-bottom: 0.9rem; }
.choice-dialog input[hidden] { display: none; }
.choice-actions { display: flex; justify-content: flex-end; gap: 0.75rem; }
.choice-actions button { min-width: 5.5rem; }

/* --- Photo lightbox -------------------------------------------------------- */
/* The app's first overlay, hence the first z-index above 90 in this file. */

.lightbox {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: var(--photo-scrim);
    display: flex;
    align-items: center;
    justify-content: center;
    /* pan-y is what stops a vertical drag scrolling the (locked) page behind
       the overlay instead of being read as a swipe; pinch-zoom is added back
       explicitly so it isn't lost as a side effect. This is the one screen
       built for close inspection of an antique's condition, and the rest of
       the app is already zoomable (base.html's viewport meta sets neither
       user-scalable=no nor maximum-scale) — pan-x stays blocked, which is
       what the swipe handling depends on. */
    touch-action: pan-y pinch-zoom;
}

/* Load-bearing. The `hidden` attribute's default `display: none` comes from
   the UA stylesheet and LOSES to the `.lightbox` class rule above, so without
   this the overlay covers the entire app on every page load. */
.lightbox[hidden] { display: none; }

.lightbox-img { max-width: 100%; max-height: 100%; object-fit: contain; }

.lightbox-counter {
    position: absolute;
    /* max() with env(safe-area-inset-*): a notched iPhone in landscape has a
       ~59px inset on one side, and full-screen photo viewing is the natural
       reason to rotate. */
    top: max(1rem, env(safe-area-inset-top));
    left: max(1rem, env(safe-area-inset-left));
    color: var(--photo-ink);
    font-size: 0.9rem;
    opacity: 0.85;
}

.lightbox-btn {
    position: absolute;
    width: 44px;              /* 44px is Apple's minimum touch target */
    height: 44px;
    border: none;
    border-radius: 50%;
    background: var(--photo-control);
    color: var(--photo-ink);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}

/* Same safe-area reasoning as .lightbox-counter: without it, a 44px button
   8-52px from the edge lands inside a notch's sensor housing in landscape. */
.lightbox-close { top: max(1rem, env(safe-area-inset-top)); right: max(1rem, env(safe-area-inset-right)); }
.lightbox-prev { left: max(0.5rem, env(safe-area-inset-left)); top: 50%; transform: translateY(-50%); }
.lightbox-next { right: max(0.5rem, env(safe-area-inset-right)); top: 50%; transform: translateY(-50%); }

/* Set on <body> while the overlay is open so the page behind cannot scroll. */
body.lightbox-open { overflow: hidden; }

.lightbox-caption {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    max-height: 40%;
    padding: 0.75rem max(1rem, env(safe-area-inset-right)) max(1rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
    overflow-y: auto;
    background: var(--photo-caption);
    color: var(--photo-ink);
    font-size: 0.85rem;
}
.lightbox-caption[hidden] { display: none; }
.lightbox-caption-row { margin-bottom: 0.2rem; }
/* Same reason as the toast's ring: the accent all but vanishes on the scrim. */
.lightbox :focus-visible { outline-color: var(--photo-ink); }

/* --- Print ------------------------------------------------------------------ */

@media print {
    /* Paper is light whatever the phone is set to. */
    :root {
        --paper: #FFFFFF;
        --paper-raised: #FFFFFF;
        --ink: #000000;
        --ink-body: #000000;
        --muted: #3A3A3C;
        --rule: #D1D1D6;
    }
    .no-print, header { display: none !important; }
    .toast-region { display: none !important; }
    body { background: var(--label-paper); }
    main { padding: 1rem; }
    .label-card { border: 1px solid var(--label-ink); box-shadow: none; }
    /* The piece page: the fixed bar would repeat on every sheet and the tabs
       do nothing on paper. The bar's bottom padding is reset by name, since
       body.piece-page main outranks the plain main rule above. */
    .piece-bar, .piece-tabs { display: none !important; }
    body.piece-page main { padding: 1rem; }
}

/* The photo inbox (/inbox): one grid of unsorted photos, headed by suggested
   groups; ticking photos raises a dark bar that makes them a piece. The bar is
   taller than the piece page's, so this page reserves more room for it. */
body.inbox-page { --inbox-bar-h: 13rem; }
body.inbox-page main { padding-bottom: calc(var(--inbox-bar-h) + 1.5rem + env(safe-area-inset-bottom)); }
html:has(body.inbox-page) { scroll-padding-bottom: calc(var(--inbox-bar-h) + env(safe-area-inset-bottom)); }
body.inbox-page .toast-region { bottom: calc(var(--inbox-bar-h) + env(safe-area-inset-bottom) + 0.75rem); }
.inbox-sub { margin: 0.2rem 0 0; color: var(--muted); }
.inbox-waiting { margin-bottom: 1.4rem; }
.inbox-made {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-top: 1.4rem;
    padding: 0.6rem 0;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
}
.inbox-made-count { font-family: var(--font-serif); font-size: 1.1rem; }
.inbox-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.35rem; margin-top: 1rem; }
@media (min-width: 640px) { .inbox-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
.inbox-group-head {
    grid-column: 1 / -1;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-top: 0.9rem;
    font-size: 0.8rem;
    color: var(--muted);
}
.inbox-group-toggle {
    min-height: 44px;
    padding: 0 0.25rem;
    border: none;
    background: none;
    color: var(--accent);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}
.inbox-tile { position: relative; aspect-ratio: 1; background: var(--paper-sunk); }
.inbox-pick { display: block; width: 100%; height: 100%; padding: 0; border: none; background: none; cursor: pointer; }
.inbox-pick img { display: block; width: 100%; height: 100%; object-fit: cover; }
.inbox-pick[aria-pressed="true"] { outline: 3px solid var(--accent); outline-offset: -3px; }
.inbox-tick {
    position: absolute;
    top: 0.25rem;
    right: 0.25rem;
    display: none;
    align-items: center;
    justify-content: center;
    width: 1.4rem;
    height: 1.4rem;
    border-radius: 50%;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 0.8rem;
    font-weight: 700;
}
.inbox-pick[aria-pressed="true"] .inbox-tick { display: flex; }
.inbox-zoom {
    position: absolute;
    left: 0.2rem;
    bottom: 0.2rem;
    width: 2rem;
    height: 2rem;
    border: none;
    border-radius: 50%;
    background: var(--photo-control);
    color: var(--photo-ink);
    font-size: 1rem;
    cursor: pointer;
}
.inbox-finish { margin-top: 1.4rem; }
.inbox-finish-list { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.35rem; margin-top: 1rem; }
.inbox-finish-piece { display: block; aspect-ratio: 1; background: var(--paper-sunk); }
.inbox-finish-piece img { display: block; width: 100%; height: 100%; object-fit: cover; }
.inbox-finish-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin-top: 1.2rem; }
/* The bar: the select-mode bar's colours (--bar / --on-bar), so it stays dark at night. */
.inbox-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 55;
    padding: 0.6rem max(1rem, env(safe-area-inset-right)) max(0.8rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
    background: var(--bar);
    color: var(--on-bar);
    border-top: 1px solid var(--ink);
}
@media (min-width: 960px) {
    .inbox-bar {
        left: 50%;
        right: auto;
        width: min(40rem, calc(100% - 2.5rem));
        transform: translateX(-50%);
        border: 1px solid var(--ink);
        border-bottom: none;
        border-radius: var(--radius) var(--radius) 0 0;
    }
}
.inbox-bar-top { display: flex; align-items: center; gap: 1rem; }
.inbox-bar-heading { flex: 1; font-weight: 700; }
.inbox-bar-link {
    min-height: 44px;
    border: none;
    background: none;
    color: var(--on-bar);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}
.inbox-bar-link:disabled { color: var(--on-bar-quiet); cursor: default; }
.inbox-bar-link:focus-visible, .inbox-make:focus-visible { outline: 2px solid var(--on-bar); outline-offset: 2px; }
.inbox-bar .segmented-group { margin-top: 0.4rem; }
.inbox-bar .segmented { border-color: var(--on-bar); }
.inbox-bar .segment { border-left-color: var(--on-bar); }
.inbox-bar .segment:has(input:checked) { background: var(--on-bar); color: var(--bar); }
.inbox-bar .segment:has(input:focus-visible) { outline-color: var(--on-bar); }
.inbox-bar-hint { margin: 0.4rem 0 0; font-size: 0.85rem; }
.inbox-bar-hint:empty { display: none; }
.inbox-make {
    display: block;
    width: 100%;
    min-height: 48px;
    margin-top: 0.5rem;
    border: 1px solid var(--on-bar);
    border-radius: var(--radius);
    background: var(--on-bar);
    color: var(--bar);
    font: inherit;
    font-weight: 700;
    cursor: pointer;
}
.inbox-make:disabled { background: none; color: var(--on-bar-quiet); border-color: var(--on-bar-quiet); cursor: default; }
