/* ============================================================== TOKENS */
:root {
    /* Surface */
    --bg:         #f5f1ea;
    --paper:      #fbf8f3;
    --paper-alt:  #efeae1;

    /* Ink */
    --ink:        #1f1a16;
    --ink-soft:   #3a312a;
    --ink-muted:  #6b5f55;
    --ink-faint:  #9a8d82;

    /* Lines & accents */
    --rule:       #e3dcd0;
    --rule-soft:  #ece5d8;
    --rule-strong:#c9bfae;   /* the shelf line; --rule is too pale to read */
    --accent:     #8a4a2c;
    --accent-soft:#c97a4b;

    /* Chrome / chips */
    --nav:        #221b16;
    --nav-ink:    #f4ead8;
    --chip:       #ede5d6;
    --chip-ink:   #5a4c3e;

    /* Status */
    --success:    #5a7a3a;

    /* Type */
    --font-display: "Instrument Serif", "Cormorant Garamond", Georgia, serif;
    --font-body:    "Newsreader", Georgia, "Times New Roman", serif;
    --font-chrome:  "Geist", system-ui, -apple-system, sans-serif;
    --font-mono:    "Geist Mono", ui-monospace, "SF Mono", monospace;

    /* Geometry */
    --radius-sm: 4px;
    --radius-md: 6px;
    --radius-lg: 10px;
    --radius-xl: 14px;

    --shadow-card:       0 1px 0 rgba(0,0,0,.02), 0 2px 8px rgba(58,46,40,.04);
    --shadow-card-hover: 0 2px 0 rgba(0,0,0,.03), 0 12px 28px rgba(58,46,40,.10);
    --shadow-modal:      0 24px 60px rgba(20,16,14,.18);

    /* Bootstrap variable overrides */
    --bs-body-bg: var(--bg);
    --bs-body-color: var(--ink);
    --bs-body-font-size: 1.06rem;
    --bs-primary: var(--accent);
    --bs-primary-rgb: 138, 74, 44;
    --bs-link-color: var(--accent);
    --bs-link-hover-color: #6f3a23;
    --bs-border-color: var(--rule);
}

/* =============================================================== GLOBAL */
html, body {
    background: var(--bg);
}

/* Text size (chosen on the everything page, remembered per browser, applied
   in base.html before first paint). Everything downstream is rem-based, so
   scaling the root scales the whole page — rows, lightbox, nav — in step. */
html.text-lg { font-size: 115%; }
html.text-xl { font-size: 130%; }

/* The "Text size: A A A" picker, living in the (dark) navbar on every page.
   Fixed-height buttons so the growing A's align on the baseline; generous
   hit areas for less steady hands. */
.text-size-picker {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    font-family: var(--font-chrome);
}
.text-size-picker button {
    width: 2.4rem;
    height: 2.4rem;
    display: inline-flex;
    align-items: flex-end;
    justify-content: center;
    padding: 0 0 .3rem;
    border: 1px solid rgba(244, 234, 216, .45);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--nav-ink);
    line-height: 1;
    font-family: var(--font-body);
}
.text-size-picker button:nth-of-type(1) { font-size: .85rem; }
.text-size-picker button:nth-of-type(2) { font-size: 1.1rem; }
.text-size-picker button:nth-of-type(3) { font-size: 1.4rem; }
.text-size-picker button[aria-pressed="true"] {
    background: var(--nav-ink);
    color: var(--nav);
}
.text-size-picker button:focus-visible {
    outline: 2px solid var(--accent-soft);
    outline-offset: 2px;
}

/* Everything-page items: title, one full-width still, actions — built for
   phones. Two type voices only: display for the title, chrome for meta. */
.ev-item {
    padding: 1.1rem 0 .6rem;
}
.ev-item-link {
    cursor: pointer;
}
.ev-item-title {
    font-family: var(--font-display);
    font-size: 1.35rem;
    line-height: 1.15;
    margin: 0 0 .55rem;
    color: var(--ink);
}
.ev-item-badge {
    font-family: var(--font-chrome);
    font-size: .68rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    background: var(--ink-soft);
    color: var(--paper);
    border-radius: var(--radius-sm);
    padding: .2em .55em;
    vertical-align: .25em;
    margin-left: .35rem;
    white-space: nowrap;
}
.ev-item-photo {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--radius-lg);
    background: var(--paper-alt);
}

/* The ⓘ on every star row — a permanent way to recall what the stars
   mean, without banners repeating down the list. */
.interest-scorer .star-info {
    width: 1.35rem;
    height: 1.35rem;
    margin-left: .45rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ink-faint);
    border-radius: 50%;
    background: transparent;
    color: var(--ink-muted);
    font-family: var(--font-chrome);
    font-size: .72rem;
    font-style: italic;
    line-height: 1;
    flex-shrink: 0;
}
.interest-scorer .star-info[aria-expanded="true"] {
    background: var(--ink-muted);
    color: var(--paper);
}
.interest-scorer .star-info:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.interest-scorer .star-scale {
    font-size: .85rem;
    color: var(--ink-muted);
    line-height: 1.75;
    padding: .3rem 0 .1rem;
}

/* Top bar on phones: keep brand + text size + menu button on ONE tidy row —
   the brand truncates rather than shoving the controls onto a second line.
   flex-basis must be 0: with basis auto the brand's hypothetical size is its
   full text width, and flex-wrap moves the controls to a second row before
   shrinking ever applies. */
@media (max-width: 991.98px) {
    .navbar .navbar-brand {
        min-width: 0;
        flex: 1 1 0;
    }
    /* The span is a flex child of the inline-flex brand — it needs its own
       min-width:0 before text-overflow can bite. */
    .navbar .navbar-brand span {
        min-width: 0;
        flex: 0 1 auto;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .text-size-picker { gap: .3rem; }
    .text-size-picker button { width: 2.1rem; height: 2.1rem; }
}

body {
    font-family: var(--font-body);
    color: var(--ink);
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-display);
    color: var(--ink-soft);
    font-weight: 400;
    letter-spacing: -0.005em;
}

h1 { font-size: clamp(2.2rem, 4vw, 3.4rem); line-height: 1.05; }
h2 { font-size: 1.9rem; line-height: 1.15; }
h3 { font-size: 1.45rem; line-height: 1.2; }

.text-muted { color: var(--ink-muted) !important; }
.text-meta  { font-family: var(--font-chrome); font-size: .78rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-muted); }
.text-soft  { color: var(--ink-soft); }
.text-faint { color: var(--ink-faint); }

a { color: var(--accent); }
a:hover { color: #6f3a23; }

/* ===================================================== ICONS (inline SVG) */
.icon {
    display: inline-block;
    vertical-align: -0.18em;
    flex-shrink: 0;
}

/* ============================================================== NAVBAR */
.navbar {
    background-color: var(--nav) !important;
    font-family: var(--font-chrome);
    position: sticky;
    top: 0;
    /* Above Bootstrap modal (1055) and modal-backdrop (1050) so the menu
       stays visible while the lightbox is open. */
    z-index: 1060;
    padding: .85rem 0;
    --bs-navbar-color: rgba(244, 234, 216, .78);
    --bs-navbar-hover-color: var(--nav-ink);
    --bs-navbar-active-color: var(--nav-ink);
    --bs-navbar-toggler-border-color: rgba(244, 234, 216, .35);
    --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28244,234,216,0.9%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}

.navbar .navbar-toggler:focus {
    box-shadow: 0 0 0 .2rem rgba(244, 234, 216, .15);
}

.navbar .navbar-brand {
    font-family: var(--font-display);
    font-size: 1.5rem;
    letter-spacing: 0.01em;
    color: var(--nav-ink);
    display: inline-flex;
    align-items: center;
    gap: .55rem;
}

.navbar .navbar-brand .icon {
    color: var(--accent-soft);
}

.navbar .nav-link {
    color: rgba(244, 234, 216, .78) !important;
    font-size: .92rem;
    /* Never word-wrap a menu item — a squeezed row must wrap whole links
       to the next line, not stack "Sign in or sign up" four words tall. */
    white-space: nowrap;
}
.navbar .nav-link:hover,
.navbar .nav-link.active {
    color: var(--nav-ink) !important;
}

.navbar .dropdown-menu {
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card-hover);
}

/* ================================================================ CARDS */
.memory-card {
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    transition: box-shadow .18s ease, transform .18s ease, border-color .18s ease;
    box-shadow: var(--shadow-card);
    height: 100%;
    overflow: hidden;
}

/* Grid columns are flex columns (memory_grid_page.html): the card grows to
   keep rows equal-height while the interest scorer below it takes real space
   instead of overflowing behind the next row. height:100% must yield here —
   in a flex column it resolves against the column and re-creates the
   overflow. */
.memory-grid-col .memory-card {
    height: auto;
    flex: 1 1 auto;
}

.memory-card:hover {
    box-shadow: var(--shadow-card-hover);
    border-color: var(--rule-soft);
    transform: translateY(-1px);
}

.memory-card .card-body {
    padding: 1.25rem 1.35rem 1.35rem;
    display: flex;
    flex-direction: column;
    gap: .35rem;
}

.memory-card .card-title {
    font-family: var(--font-display);
    font-size: 1.35rem;
    line-height: 1.15;
    color: var(--ink-soft);
    margin: 0;
}

.memory-card .card-title a {
    color: inherit;
    text-decoration: none;
}

.memory-card:hover .card-title {
    color: var(--accent);
}

/* --- Image-led + stacked variants (memory_card.html) --- */
.memory-card--image,
.memory-card--stacked {
    display: flex;
    flex-direction: column;
}

.memory-card__hero {
    position: relative;
    overflow: hidden;
    background: var(--paper-alt);
    aspect-ratio: 4 / 3;
}

.memory-card__hero img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .25s ease;
}

.memory-card:hover .memory-card__hero img { transform: scale(1.02); }

.memory-card__body {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: 1.1rem 1.25rem 1.3rem;
}

.memory-card__title {
    font-family: var(--font-display);
    font-size: 1.35rem;
    line-height: 1.18;
    color: var(--ink-soft);
    margin: 0;
}

.memory-card__title a {
    color: inherit;
    text-decoration: none;
}

.memory-card:hover .memory-card__title,
.memory-card:hover .memory-card__title a { color: var(--accent); }

.memory-card__excerpt {
    margin: 0;
    color: var(--ink-muted);
    font-size: .96rem;
    line-height: 1.45;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

.memory-card__meta {
    display: flex;
    flex-wrap: wrap;
    gap: .85rem;
    margin-top: .1rem;
    color: var(--ink-muted);
    font-family: var(--font-chrome);
    font-size: .8rem;
}

.memory-card__tags {
    display: flex;
    flex-wrap: wrap;
    gap: .3rem;
    margin-top: .15rem;
}

/* ========================================================== BOOK CELLS */
/* The latent card (design review 2a). Books are not photographs of objects:
   a cover is already a designed rectangle and a frame around it fights it.
   So there is no frame at rest — the cover stands on a hairline and the card
   appears only when someone engages with it. */

.book-cell { position: relative; }

/* Team-only selection tick for the bulk bar, kept out of the link's hit
   area entirely (a sibling overlay on the grid column). */
.grid-select {
    position: absolute;
    top: .1rem;
    right: .85rem;   /* inside the g-4 gutter */
    z-index: 3;
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    cursor: pointer;
}
.grid-select input {
    width: 1.1rem;
    height: 1.1rem;
    cursor: pointer;
    accent-color: var(--accent, #7a5c3e);
}
.memory-grid-col { position: relative; }

/* THE CELL. Whole thing is one link: cover + title + author. The interest
   scorer is a sibling outside it (memory_grid_page.html), so rating a book
   can never open it.

   The side bleed MUST stay under half the column gutter. At `row g-4` that
   is .75rem against a .5rem bleed — safe. Tighten the gutter and this must
   shrink with it, or neighbouring hit areas overlap and a tap in the seam
   opens the wrong book. */
.book-cell__hit {
    display: block;
    text-decoration: none;
    color: inherit;
    padding: .5rem;
    margin: -.5rem -.5rem 0;
    border-radius: var(--radius-md);
    background: transparent;
    box-shadow: none;
    transition: background .15s ease, box-shadow .18s ease;
}

/* Covers stand on a shared line. The height is a ceiling for the IMAGE, not
   the cell, and it is in rem — the A A A control grows the covers and drops
   the hairline with them rather than clipping anything. */
.book-cell__cover {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    height: 11rem;
    border-bottom: 1px solid var(--rule-strong);
    transition: border-color .18s ease;
}
@media (min-width: 768px)  { .book-cell__cover { height: 14rem; } }
@media (min-width: 1200px) { .book-cell__cover { height: 17rem; } }

/* Reaction pills overlaid on the cover art, anchored to its bottom edge.
   The `top` values MUST mirror the cover heights above — change one,
   change the other. translateY(-100%) hangs the block's bottom on the
   hairline, so the unfolded scorer panel grows UP over the art rather
   than down over the title. The container passes clicks through to the
   cover; only the pills themselves catch them. */
.book-cell__reactions {
    position: absolute;
    left: 0;
    right: 0;
    top: 11rem;
    transform: translateY(-100%);
    z-index: 2;
    padding: 0 .1rem .25rem;
    pointer-events: none;
}
@media (min-width: 768px)  { .book-cell__reactions { top: 14rem; } }
@media (min-width: 1200px) { .book-cell__reactions { top: 17rem; } }
.book-cell__reactions .reaction-pill,
.book-cell__reactions .like-btn,
.book-cell__reactions .reaction-panel {
    pointer-events: auto;
}
/* Over art the pills need their own ground: the old hero-pill treatment. */
.book-cell__reactions .reaction-pill,
.book-cell__reactions .like-btn {
    background: rgba(34, 27, 22, .72);
    border-color: transparent;
    color: #fff;
    backdrop-filter: blur(4px);
}
.book-cell__reactions .reaction-pill:hover,
.book-cell__reactions .like-btn:hover {
    background: rgba(34, 27, 22, .88);
    color: #fff;
    border-color: var(--accent);
}
.book-cell__reactions .reaction-panel {
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: .5rem;
    padding: .3rem .5rem;
    box-shadow: 0 4px 16px rgba(0, 0, 0, .15);
}
/* Own-state tints need contrast over the dark pill ground. */
.book-cell__reactions .star-pill.mine {
    color: #f0c869;
    border-color: transparent;
}

.book-cell__cover img {
    display: block;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 100%;
    box-shadow: 0 2px 5px rgba(58, 46, 40, .20);
    transition: box-shadow .18s ease, transform .18s ease;
}

.book-cell__nocover {
    align-self: stretch;
    width: 100%;
    background: var(--paper-alt);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-chrome);
    font-size: .78rem;
    color: var(--ink-muted);
}

.book-cell__caption {
    display: flex;
    flex-direction: column;
    gap: .2rem;
    padding-top: .5rem;
}
.book-cell__title {
    font-family: var(--font-display);
    font-size: 1.15rem;
    line-height: 1.15;
    color: var(--ink-soft);
    transition: color .15s ease;
    text-wrap: pretty;
}
.book-cell__meta {
    font-family: var(--font-chrome);
    font-size: .8rem;
    color: var(--ink-muted);
}

/* Latent card appears on engage. :active as well as :hover — a phone has no
   hover, and the wash arriving under a thumb IS the affordance. */
.book-cell__hit:hover,
.book-cell__hit:active {
    background: var(--paper);
    box-shadow: var(--shadow-card);
}
.book-cell__hit:hover .book-cell__cover,
.book-cell__hit:active .book-cell__cover {
    border-bottom-color: var(--accent);
}
.book-cell__hit:hover .book-cell__cover img {
    box-shadow: 0 4px 10px rgba(58, 46, 40, .28);
    transform: translateY(-2px);
}
.book-cell__hit:hover .book-cell__title { color: var(--accent); }

/* Keyboard is never latent: the site's halo, full strength. */
.book-cell__hit:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    box-shadow: 0 0 0 3px var(--bg), var(--shadow-card);
    background: var(--paper);
}

@media (prefers-reduced-motion: reduce) {
    .book-cell__hit,
    .book-cell__cover,
    .book-cell__cover img { transition: none; }
    .book-cell__hit:hover .book-cell__cover img { transform: none; }
}

/* ====================================================== BADGES & CHIPS */
.badge {
    font-family: var(--font-chrome);
    font-weight: 500;
    font-size: .72rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    padding: .3rem .55rem;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    line-height: 1;
}

.badge-soft {
    background: var(--tint, var(--chip));
    color: var(--chip-ink);
    /* Slight ink darkening for legibility on lighter tints. */
    color: color-mix(in srgb, var(--tint, var(--chip)) 60%, #2a221c);
}

.badge-ghost {
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink-muted);
}

.tag-chip {
    display: inline-flex;
    align-items: center;
    padding: .2rem .55rem;
    background: var(--chip);
    color: var(--chip-ink);
    border-radius: 999px;
    font-family: var(--font-chrome);
    font-size: .75rem;
    letter-spacing: .02em;
}

a.tag-chip { text-decoration: none; }
a.tag-chip:hover { background: var(--ink-soft); color: var(--paper); }

/* A subject read off the book rather than found in a catalogue — same
   affordance, quieter, so the distinction is visible without being a
   warning. */
.tag-chip--inferred {
    background: transparent;
    box-shadow: inset 0 0 0 1px var(--rule);
    color: var(--ink-muted);
}

/* Tag row: coloured selected chips first, then a "+ Add" pill that expands the
   white unadded chips inline (toggled by the .is-adding class via JS). */
.tag-row { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.tag-add-list { display: none; }
#tags-section.is-adding .tag-add-list { display: contents; }   /* pills flow into .tag-row */
#tags-section.is-adding .tag-add-toggle { display: none; }
.tag-add-new { display: inline-flex; align-items: center; gap: .3rem; }
.tag-add-done {
    background: var(--chip);
    color: var(--chip-ink);
    border-color: var(--chip);
}
.tag-add-pill {
    display: inline-flex;
    align-items: center;
    padding: .2rem .55rem;
    margin: 0;
    background: transparent;
    color: var(--ink-soft);
    border: 1px solid var(--rule);
    border-radius: 999px;
    font-family: var(--font-chrome);
    font-size: .75rem;
    letter-spacing: .02em;
    line-height: 1.3;
    cursor: pointer;
    transition: background .12s, color .12s, border-color .12s;
}
.tag-add-pill:hover,
.tag-add-pill:focus-visible {
    background: var(--chip);
    color: var(--chip-ink);
    border-color: var(--chip);
    outline: none;
}
/* The "+ New tag" pill reads as an action via a dashed outline. */
.tag-add-pill--new { border-style: dashed; }
/* Inline editor the "+ New tag" pill expands into. */
.tag-add-input {
    padding: .2rem .55rem;
    margin: 0 .3rem .3rem 0;
    background: var(--paper);
    color: var(--ink);
    border: 1px solid var(--chip);
    border-radius: 999px;
    font-family: var(--font-chrome);
    font-size: .75rem;
    letter-spacing: .02em;
    line-height: 1.3;
    max-width: 10rem;
}
.tag-add-input:focus { outline: none; box-shadow: 0 0 0 2px var(--chip); }

/* ============================================================ BUTTONS */
.btn {
    font-family: var(--font-chrome);
    border-radius: var(--radius-md);
}

.btn-dark {
    background-color: var(--ink-soft);
    border-color: var(--ink-soft);
}
.btn-dark:hover { background-color: var(--ink); border-color: var(--ink); }

.btn-outline-dark {
    color: var(--ink-soft);
    border-color: var(--rule);
}
.btn-outline-dark:hover {
    background-color: var(--paper-alt);
    color: var(--ink-soft);
    border-color: var(--rule);
}

.btn-primary {
    background-color: var(--accent);
    border-color: var(--accent);
}
.btn-primary:hover {
    background-color: #6f3a23;
    border-color: #6f3a23;
}

/* ========================================================= FOCUS HALO */
:focus { outline: none; }

a:focus-visible,
button:focus-visible,
.btn:focus-visible,
.memory-card:focus-visible,
.like-btn:focus-visible,
.interest-star:focus-visible,
.dropdown-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    box-shadow: 0 0 0 3px var(--bg);
}

/* ================================================== REACTION PILLS
   The compact ☆/♡ pills under grid cells and everything rows: empty icon
   at zero (an invitation), filled once anyone has reacted, own state as
   a tint. Small enough that two of them fit a col-6 phone cell with room
   to spare — the fix for the old wrapping scorer row. */
.reaction-wrap { min-width: 0; }
.reaction-row { flex-wrap: wrap; min-width: 0; }

.reaction-pill,
.like-btn {
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink-soft);
    border-radius: 999px;
    padding: .15rem .55rem;
    font-family: var(--font-chrome);
    font-size: .8rem;
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    cursor: pointer;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.star-pill--static { cursor: default; }

.reaction-pill:hover,
.like-btn:hover {
    border-color: var(--accent);
    background: var(--paper);
    color: var(--accent);
}
.star-pill--static:hover {
    border-color: var(--rule);
    background: transparent;
    color: var(--ink-soft);
}

.like-btn.liked {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}
.star-pill.mine {
    border-color: var(--accent);
    color: var(--accent);
}

/* The fold-out 5-star panel: in the flow (never clipped; the flex column
   re-equalises row heights while it's open). */
.reaction-panel {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .1rem;
    padding: .25rem 0;
}
.reaction-panel .star-scale {
    flex-basis: 100%;
    font-size: .78rem;
    color: var(--ink-muted);
    padding-top: .25rem;
}
.reaction-panel__close {
    margin-left: auto;
    border: none;
    background: none;
    color: var(--ink-muted);
    font-size: .9rem;
    line-height: 1;
    padding: .15rem .35rem;
    cursor: pointer;
}
.reaction-panel__close:hover { color: var(--accent); }

/* ============================================================ AUTH TOAST */
.auth-toast {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 1080;
    max-width: 22rem;
    background: var(--paper);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card-hover);
    padding: 1rem 1.1rem;
    display: flex;
    gap: .75rem;
    align-items: flex-start;
    font-family: var(--font-chrome);
    animation: authToastIn .25s ease-out;
}

@keyframes authToastIn {
    from { transform: translateY(6px); opacity: 0; }
    to   { transform: translateY(0); opacity: 1; }
}

.auth-toast__body { flex: 1; }
.auth-toast__title {
    font-family: var(--font-display);
    font-size: 1.1rem;
    margin: 0 0 .2rem;
    color: var(--ink-soft);
}
.auth-toast__lede {
    margin: 0 0 .7rem;
    font-size: .88rem;
    color: var(--ink-muted);
}
.auth-toast__actions {
    display: flex;
    gap: .5rem;
}
.auth-toast__close {
    background: none;
    border: none;
    color: var(--ink-faint);
    padding: .15rem;
    line-height: 1;
    cursor: pointer;
}
.auth-toast__close:hover { color: var(--ink); }

/* ====================================================== INTEREST STARS */
.interest-scorer { font-family: var(--font-chrome); }

.interest-star {
    background: none;
    border: none;
    font-size: 1.35rem;
    color: var(--rule);
    cursor: pointer;
    padding: 0 .15rem;
    transition: color .15s, transform .15s;
}
.interest-star:hover { color: var(--accent-soft); transform: scale(1.15); }
.interest-star.active { color: var(--accent); }

.interest-scorer__row { display: flex; align-items: center; flex-wrap: wrap; }
.interest-scorer__attribution {
    list-style: none;
    padding: 0;
    margin: .25rem 0 0;
    font-size: .85rem;
}
.interest-scorer__attribution li {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .15rem 0;
    color: var(--ink-muted);
}
.interest-scorer__attribution .interest-scorer__score {
    color: var(--accent);
    display: inline-flex;
    gap: .05rem;
}
.interest-scorer__attribution .interest-scorer__who { color: var(--ink); }

/* ============================================================ COMMENTS */
.comments-section { font-family: var(--font-body); }
.comment {
    font-size: .96rem;
    padding: .85rem 1rem;
    background: var(--paper);
    border: 1px solid var(--rule-soft);
    border-radius: var(--radius-md);
}

/* ============================================================== HERO */
.hero {
    padding: 4.5rem 0 3rem;
    color: var(--ink-soft);
    border-bottom: 1px solid var(--rule);
}

.hero h1 {
    font-family: var(--font-display);
    color: var(--ink-soft);
    margin: 0;
}

/* Home mosaic (Option B) */
.home-hero {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
    align-items: center;
    padding: 4rem 1rem 3rem;
}

@media (max-width: 992px) {
    .home-hero {
        grid-template-columns: 1fr;
        gap: 2rem;
    }
}

.home-hero__title {
    font-family: var(--font-display);
    font-size: clamp(2.8rem, 7vw, 5rem);
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--ink-soft);
    margin: 0 0 1rem;
}

.home-hero__lede {
    color: var(--ink-muted);
    font-size: 1.1rem;
    line-height: 1.6;
    max-width: 36ch;
}

.home-hero__lead-link {
    display: block;
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-card-hover);
    aspect-ratio: 4 / 3;
}

.home-hero__lead-link img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .35s ease;
}

.home-hero__lead-link:hover img { transform: scale(1.03); }

.home-hero__lead-caption {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 1.5rem 1.6rem;
    background: linear-gradient(0deg, rgba(20,16,14,.78) 0%, rgba(20,16,14,0) 100%);
    color: #f6efe2;
    display: flex;
    flex-direction: column;
    gap: .35rem;
}
.home-hero__lead-caption h2 {
    font-family: var(--font-display);
    color: #f6efe2;
    margin: 0;
    font-size: clamp(1.6rem, 3vw, 2.2rem);
    line-height: 1.1;
}
.home-hero__lead-caption .text-meta { color: rgba(246,239,226,.78); }

/* Pulled quote */
.pulled-quote {
    margin: 0 auto;
    max-width: 48rem;
    text-align: center;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    padding: 2.25rem 1rem;
}
.pulled-quote__text {
    font-family: var(--font-display);
    font-style: italic;
    color: var(--ink-soft);
    font-size: clamp(1.4rem, 3vw, 2rem);
    line-height: 1.4;
    margin: 0;
    border: none;
    padding: 0;
}
.pulled-quote__by {
    margin-top: 1rem;
    font-family: var(--font-chrome);
    font-size: .8rem;
    color: var(--ink-muted);
    letter-spacing: .08em;
    text-transform: uppercase;
}
.pulled-quote__by a {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid var(--rule);
}

/* =================================================== COLLECTION MASTHEAD */
.collection-masthead {
    padding-top: 3rem;
    padding-bottom: 1.5rem;
    border-bottom: 1px solid var(--rule);
    margin-bottom: 0;
}

.collection-masthead__row {
    display: grid;
    grid-template-columns: 1.5fr 1fr;
    gap: 2.5rem;
    align-items: end;
}

@media (max-width: 768px) {
    .collection-masthead { padding-top: 2rem; }
    .collection-masthead__row {
        grid-template-columns: 1fr;
        gap: 1.5rem;
        align-items: stretch;
    }
}

.collection-masthead__title {
    font-family: var(--font-display);
    font-size: clamp(2.5rem, 6vw, 4.5rem);
    line-height: 1;
    color: var(--ink-soft);
    margin: 0;
    letter-spacing: -0.015em;
}

.collection-masthead__intro {
    margin: 1rem 0 0;
    max-width: 36rem;
    color: var(--ink-muted);
    font-size: 1.05rem;
    line-height: 1.55;
}

.collection-stats {
    display: flex;
    gap: 1.75rem;
    justify-content: flex-end;
    flex-wrap: wrap;
}

@media (max-width: 768px) {
    .collection-stats { justify-content: flex-start; }
}

.collection-stats > div { text-align: right; }
@media (max-width: 768px) { .collection-stats > div { text-align: left; } }

.collection-stats__value {
    font-family: var(--font-display);
    font-size: 2rem;
    line-height: 1;
    color: var(--ink-soft);
}

/* The masthead "Add/View yours" call-to-action: sized to sit comfortably
   beside the big stat figures rather than as a small chip. */
.contribute-cta {
    font-size: 1.05rem;
    padding: .6rem 1.4rem;
}

/* ========================================================= PAGINATION */
.pagination-row .rule-divider {
    width: 1px;
    height: 14px;
    background: var(--rule);
}

/* ========================================================= FILTER BAR */
.filter-bar {
    background: rgba(245, 241, 234, .92);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: none;
    border-bottom: 1px solid var(--rule-soft);
    border-radius: 0;
    padding: .9rem 0;
    margin-bottom: 1.25rem;
    font-family: var(--font-chrome);
    position: sticky;
    /* Below the sticky navbar, whose REAL height memoriarch.js measures
       into --navbar-h (a hardcoded 60px sat a few pixels short, so
       sticky bars slid partially underneath). */
    top: var(--navbar-h, 60px);
    z-index: 5;
}

.filter-bar .btn-sm {
    font-size: .82rem;
    padding: .35rem .75rem;
    border-radius: 999px;
}

/* A book's synopsis — the first thing worth reading on the page, so it gets
   body serif above the size of the surrounding meta rather than chrome type.
   Placeholder styling for the design review; the real treatment comes back
   with the card work. */
.book-synopsis {
    font-family: var(--font-body);
    font-size: 1.08rem;
    line-height: 1.6;
    color: var(--ink);
    max-width: 46ch;
    margin: 0 0 1.1rem;
}

/* Subject cloud (book catalogue) — design review 1h. No computed type ramp:
   two steps, and the count carries the weight. The cap is a fold, not a
   ceiling: the tail is in the DOM behind <details>. */
.subject-cloud-wrap { padding-top: .25rem; }

.subject-cloud {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .35rem 1.1rem;
    margin-bottom: 1.1rem;
    font-family: var(--font-chrome);
    /* a subject and its count must never break apart */
    white-space: nowrap;
}

.subject-cloud__chip {
    color: var(--ink-soft);
    text-decoration: none;
    text-transform: capitalize;
    line-height: 1.3;
    font-size: 1.05rem;            /* step 1 of 2 — the twelve in view */
    border-bottom: 1px solid transparent;
    display: inline-flex;
    align-items: baseline;
    gap: .15rem;
    min-height: 2.5rem;            /* real tap target; min, not fixed */
    padding: .35rem 0;
    white-space: nowrap;
}
.subject-cloud__chip:hover,
.subject-cloud__chip:focus-visible {
    color: var(--ink);
    border-bottom-color: var(--ink-soft);
}

.subject-cloud__chip--active {
    color: var(--paper);
    background: var(--ink-soft);
    border-radius: 999px;
    padding: .35rem .8rem;
}

/* --ink-faint fails contrast at 2.7:1 — counts are content, not decoration. */
.subject-cloud__count {
    font-size: .72em;
    color: var(--ink-muted);
    vertical-align: super;
    margin-left: .1rem;
}

/* The shelf row (design review 1i) — the browse level above the cloud.
   Same chip anatomy as the cloud, one step quieter in size, with a hairline
   under the band so the two levels read apart. */
.shelf-row-wrap { padding-top: .5rem; }
/* The places row sits under the shelves: the rule line moves to whichever
   row is last, so the pair reads as one block above the subject cloud. */
.shelf-row-wrap:has(+ .place-row-wrap) .shelf-row { border-bottom: 0; padding-bottom: 0; }
.place-row-wrap { padding-top: 0; }
.place-description { padding-top: .75rem; max-width: 46rem; }
.shelf-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .2rem .9rem;
    padding-bottom: .35rem;
    border-bottom: 1px solid var(--rule);
    font-family: var(--font-chrome);
    white-space: nowrap;
}
.shelf-row__chip { font-size: .95rem; }

/* THE FOLD. A different shape from the cloud on purpose: an alphabetical,
   searchable index in columns. Expanding a cloud into a bigger cloud is what
   produces a wall of small text at 2,000 books. */
.subject-fold { display: inline-block; }
.subject-fold__toggle {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    min-height: 2.5rem;
    padding: .35rem .9rem;
    border: 1px solid var(--rule-strong);
    border-radius: 999px;
    font-family: var(--font-chrome);
    font-size: .92rem;
    color: var(--ink-soft);
    white-space: nowrap;
}
.subject-fold__toggle::-webkit-details-marker { display: none; }
.subject-fold__toggle:hover { background: var(--paper); border-color: var(--accent); color: var(--accent); }
.subject-fold[open] .subject-fold__toggle { background: var(--paper); }
/* must clear the sticky filter bar below it */
.subject-fold[open] { position: relative; z-index: 6; }

/* Open, the fold takes a line of its own so the panel spans the cloud's full
   width. Inline-block alone leaves the panel as narrow as its toggle, which
   squeezes the columns to two and defeats the point of an index. */
.subject-fold[open] { width: 100%; }

.subject-fold__panel {
    /* breaks out of the inline-block toggle to span the cloud's width */
    position: relative;
    margin-top: .8rem;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    padding: 1rem 1.1rem 1.2rem;
}

.subject-fold__search {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6rem 1rem;
    margin-bottom: .9rem;
}
.subject-fold__input {
    flex: 1 1 14rem;
    min-width: 0;
    font-family: var(--font-chrome);
    font-size: 1rem;
    padding: .6rem 1rem;
    min-height: 2.75rem;
    background: var(--bg);
    color: var(--ink);
    border: 1px solid var(--rule-strong);
    border-radius: 999px;
}
.subject-fold__input:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.subject-fold__result {
    font-family: var(--font-mono);
    font-size: .78rem;
    color: var(--ink-muted);
}

.subject-fold__list {
    list-style: none;
    margin: 0;
    padding: 0;
    columns: 13rem auto;           /* as many columns as fit; 1 on a phone */
    column-gap: 1.6rem;
    font-family: var(--font-chrome);
}
.subject-fold__list > li {
    break-inside: avoid;
    white-space: nowrap;
}
.subject-fold__list a {
    display: block;
    min-height: 2.5rem;
    padding: .45rem 0;
    color: var(--ink-soft);
    text-decoration: none;
    text-transform: capitalize;
    font-size: .95rem;
    overflow: hidden;
    text-overflow: ellipsis;
}
.subject-fold__list a:hover,
.subject-fold__list a:focus-visible { color: var(--accent); }
.subject-fold__list a.is-active { color: var(--accent); font-weight: 500; }
.subject-fold__count {
    color: var(--ink-muted);
    font-family: var(--font-mono);
    font-size: .78rem;
}

/* Does the cloud earn the top of the page? At 26 subjects yes; at 300 no. Once
   it outgrows about four lines on a phone the head collapses to a scroller and
   the shelf itself is the first thing on screen. */
@media (max-width: 767.98px) {
    .subject-cloud {
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scrollbar-width: none;
        padding-bottom: .3rem;
        margin-bottom: .8rem;
    }
    .subject-cloud::-webkit-scrollbar { display: none; }
    /* The subject cloud is a horizontal scroller — but an open index must
       not live inside one: while the fold is open it goes back to wrapping. */
    .subject-cloud:has(.subject-fold[open]) {
        flex-wrap: wrap;
        overflow-x: visible;
    }
    /* The shelf and place rows used to become hidden-scrollbar horizontal
       scrollers here — which just LOOKS like chips cut off at the screen
       edge, with nothing saying "more this way". Instead: wrap normally
       but clamp to roughly two chip lines, with a "…" expander (injected
       by memoriarch.js only when the row actually overflows). */
    .shelf-row-wrap .shelf-row {
        max-height: 4.6rem;
        overflow: hidden;
    }
    .shelf-row-wrap .shelf-row.is-open { max-height: none; overflow: visible; }
    /* An open shelf holds the subject chips and their fold — never clamp
       those away. */
    .shelf-row-wrap .shelf-row:has(.subject-fold[open]) {
        max-height: none;
        overflow: visible;
    }
    .subject-fold__panel {
        /* full-bleed panel on a phone: the container padding is 1rem */
        margin-left: -1rem;
        margin-right: -1rem;
        border-radius: 0;
        border-left: none;
        border-right: none;
    }
}

/* Active filter, said at the size of the masthead figure rather than as a
   small pill halfway down the page. */
.stat-clear {
    text-decoration: none;
    color: var(--accent);
    margin-left: .3rem;
    padding: .2rem .35rem;
}
.stat-clear:hover { color: #6f3a23; }

/* ====================================================== STREAM CONTENT */
.rich-text {
    max-width: 72ch;
    font-family: var(--font-body);
    color: var(--ink);
}

.rich-text img {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-md);
}

blockquote {
    border-left: 3px solid var(--accent);
    padding-left: 1.4rem;
    margin: 1.6rem 0;
    font-style: italic;
    color: var(--ink-soft);
    font-family: var(--font-display);
    font-size: 1.25rem;
    line-height: 1.45;
}

/* --- Streamfield block styles --- */
.block-image {
    margin: 2rem -1rem;
}
.block-image__media {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0 auto;
    border-radius: var(--radius-md);
}
.block-image__caption {
    margin-top: .65rem;
    text-align: center;
    font-family: var(--font-body);
    font-style: italic;
    color: var(--ink-muted);
    font-size: .95rem;
}

.block-quote {
    border: none;
    margin: 2.5rem auto;
    padding: 1.5rem 1rem;
    max-width: 44rem;
    text-align: center;
}
.block-quote__mark {
    display: inline-block;
    color: var(--accent-soft);
    margin-bottom: .5rem;
}
.block-quote__text {
    font-family: var(--font-display);
    font-size: clamp(1.3rem, 2.4vw, 1.8rem);
    line-height: 1.4;
    color: var(--ink-soft);
    margin: 0;
    border: none;
    padding: 0;
    font-style: normal;
}
.block-quote__by {
    margin-top: .85rem;
    font-family: var(--font-chrome);
    font-size: .8rem;
    color: var(--ink-muted);
    letter-spacing: .08em;
    text-transform: uppercase;
}

.block-poem {
    margin: 2rem 0;
}
.block-poem--centred { text-align: center; }
.block-poem__text {
    font-family: var(--font-display);
    font-size: 1.18rem;
    line-height: 1.7;
    white-space: pre-line;
    color: var(--ink-soft);
}
.block-poem__by {
    margin-top: .65rem;
    font-style: italic;
    color: var(--ink-muted);
}

/* --- Next memory CTA on standalone memory_page --- */
.next-memory-cta {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    border-top: 1px solid var(--rule);
    margin-top: 3rem;
    padding-top: 2.5rem;
}
.next-memory-cta__label {
    font-family: var(--font-chrome);
    font-size: .75rem;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: .35rem;
}
.next-memory-cta__title {
    font-family: var(--font-display);
    font-size: clamp(1.6rem, 3vw, 2.4rem);
    color: var(--ink-soft);
    margin: 0;
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: color .15s, border-color .15s;
}
.next-memory-cta__title:hover {
    color: var(--accent);
    border-color: var(--accent);
}

/* ============================================================= FOOTER */
body > footer {
    background-color: var(--nav) !important;
    color: rgba(244, 234, 216, .65);
    font-family: var(--font-chrome);
    font-size: .82rem;
    border-top: 1px solid rgba(255,255,255,.04);
}

/* ================================================================ AUTH */
.auth-shell {
    display: grid;
    grid-template-columns: 1.05fr 1fr;
    min-height: calc(100vh - var(--navbar-h, 60px));
}

@media (max-width: 992px) {
    .auth-shell { grid-template-columns: 1fr; min-height: 0; }
}

.auth-aside {
    background: var(--ink-soft);
    color: var(--nav-ink);
    padding: 3rem 3rem 2rem;
    display: flex;
    flex-direction: column;
    gap: 2.5rem;
    position: relative;
    overflow: hidden;
}

.auth-aside::before {
    /* warm texture wash */
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(60% 50% at 80% 0%, rgba(201,122,75,.22), transparent 60%),
        radial-gradient(50% 50% at 10% 100%, rgba(138,74,44,.18), transparent 70%);
    pointer-events: none;
}

.auth-aside > * { position: relative; }

.auth-aside__brand {
    display: inline-flex;
    align-items: center;
    gap: .55rem;
    font-family: var(--font-display);
    font-size: 1.5rem;
    color: var(--nav-ink);
    text-decoration: none;
}

.auth-aside__body { max-width: 30rem; }

.auth-aside__heading {
    font-family: var(--font-display);
    font-size: clamp(2rem, 4vw, 3rem);
    color: var(--nav-ink);
    line-height: 1.1;
    letter-spacing: -0.01em;
    margin: .35rem 0 1.5rem;
}

.auth-aside__quote {
    margin: 0;
    color: rgba(244,234,216,.85);
}
.auth-aside__quote blockquote {
    border: none;
    padding: 0;
    margin: 0;
    font-family: var(--font-display);
    font-size: 1.25rem;
    line-height: 1.45;
    font-style: italic;
    color: rgba(244,234,216,.92);
}
.auth-aside__quote figcaption {
    margin-top: .75rem;
    font-family: var(--font-chrome);
    font-size: .75rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: rgba(244,234,216,.55);
}

.auth-pane {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 3rem 1.5rem;
}

.auth-form {
    width: 100%;
    max-width: 26rem;
}

.auth-form__title {
    font-family: var(--font-display);
    font-size: clamp(1.8rem, 3.2vw, 2.4rem);
    color: var(--ink-soft);
    margin: 0 0 .35rem;
}

.auth-form__lede {
    color: var(--ink-muted);
    margin-bottom: 1.5rem;
}

.auth-form__details {
    margin-top: 1.25rem;
    border-top: 1px solid var(--rule);
    padding-top: 1rem;
}

.auth-form__details > summary {
    cursor: pointer;
    color: var(--ink-muted);
    font-family: var(--font-chrome);
    font-size: .9rem;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: .3rem;
}
.auth-form__details > summary::-webkit-details-marker { display: none; }
.auth-form__details > summary::after {
    content: "›";
    transition: transform .15s;
    color: var(--ink-faint);
}
.auth-form__details[open] > summary::after { transform: rotate(90deg); }

.auth-form__foot {
    margin-top: 1.25rem;
    color: var(--ink-muted);
}

/* Code-input cells (login + email-confirm) */
.code-input {
    display: flex;
    gap: .55rem;
    justify-content: space-between;
}
.code-input input {
    width: 100%;
    min-width: 0;
    aspect-ratio: 1 / 1;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 1.5rem;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    color: var(--ink-soft);
}
.code-input input:focus {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-color: var(--accent);
}

/* ============================================================ LIGHTBOX */
#memoryLightbox .modal-dialog.modal-fullscreen {
    height: calc(100vh - var(--navbar-h, 60px));
    margin-top: var(--navbar-h, 60px);
}

#memoryLightbox .modal-content {
    background: var(--bg);
    height: 100%;
}

#memoryLightbox .modal-body {
    overflow-y: auto;
    padding: 0 4rem 2rem;
    /* Suppress Android Chrome's swipe-from-edge history gesture while the
       lightbox owns the screen (its own swipes page memories). */
    overscroll-behavior: contain;
}

.lightbox-toolbar {
    position: sticky;
    top: 0;
    background: rgba(245, 241, 234, .94);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--rule-soft);
    padding: .75rem 0;
    margin: 0 -4rem 1.25rem;
    padding-left: 4rem;
    padding-right: 4rem;
    z-index: 5;
    font-family: var(--font-chrome);
}

/* The tools tray (admin controls, Edit, Permalink, contribute). Desktop:
   always visible, inline, with the close button re-ordered to stay the
   rightmost item (the tray sits last in the DOM for the phone layout).
   Phones: a Bootstrap collapse behind the Tools/More toggle; expanded, it
   takes a full-width row of its own under the toolbar. */
@media (min-width: 768px) {
    .lightbox-tools { display: flex !important; }
    .lightbox-toolbar > .btn-close { order: 99; }
}
@media (max-width: 767.98px) {
    .lightbox-tools { flex-basis: 100%; }
    .lightbox-tools.show,
    .lightbox-tools.collapsing { display: flex; }
}

#memoryLightbox .rich-text { max-width: 100%; }

/* the plate and the thumb strip size themselves */
#memoryLightbox .book-page__cover img,
#memoryLightbox .book-page__thumb img { margin: 0; }

#memoryLightbox img,
#memoryLightbox video {
    max-height: 70vh;
    width: auto;
    max-width: 100%;
    object-fit: contain;
    display: block;
    margin: 0 auto;
    border-radius: var(--radius-md);
}

#memoryLightbox audio { width: 100%; }

.lightbox-title {
    font-family: var(--font-display);
    font-size: clamp(1.6rem, 3vw, 2.4rem);
    line-height: 1.1;
    color: var(--ink-soft);
    margin: 0 0 .85rem;
}

.lightbox-meta { font-family: var(--font-chrome); font-size: .88rem; }

/* 1px progress bar that pulses while an HTMX swap is in flight */
.lightbox-progress {
    position: sticky;
    top: 0;
    height: 2px;
    background: transparent;
    overflow: hidden;
    z-index: 6;
}
.lightbox-progress::before {
    content: "";
    position: absolute;
    left: -40%;
    top: 0;
    width: 40%;
    height: 100%;
    background: var(--accent);
    opacity: 0;
    transition: opacity .15s linear;
}
.lightbox-progress.is-loading::before {
    opacity: 1;
    animation: lightboxProgress 1s linear infinite;
}
@keyframes lightboxProgress {
    0%   { left: -40%; }
    100% { left: 100%; }
}

.lightbox-foot {
    border-top: 1px solid var(--rule);
    padding-top: 1rem;
    margin-top: 1rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
}

.lightbox-next-preview {
    background: transparent;
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: .55rem 1rem;
    color: var(--ink-soft);
    cursor: pointer;
    transition: background .15s, border-color .15s, color .15s;
}
.lightbox-next-preview:hover {
    background: var(--paper);
    border-color: var(--accent);
    color: var(--accent);
}
.lightbox-next-preview__title {
    font-family: var(--font-display);
    font-size: 1rem;
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Side chevron navigation */
.lightbox-nav-side {
    position: fixed;
    top: 0;
    bottom: 0;
    z-index: 1060;
    background: #3a332d;   /* opaque so the L/R bars can't differ over the scrollbar */
    color: var(--nav-ink);
    border: none;
    width: 3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background .2s;
}
.lightbox-nav-side:hover { background: #4d453d; }
.lightbox-nav-side:disabled { opacity: .2; cursor: default; }
.lightbox-nav-prev { left: 0; }
.lightbox-nav-next { right: 0; }

@media (max-width: 576px) {
    #memoryLightbox .modal-body {
        padding: 0 1rem 1rem;
    }
    .lightbox-toolbar {
        margin: 0 -1rem .75rem;
        padding-left: 1rem;
        padding-right: 1rem;
    }
    .lightbox-nav-side { display: none; }

    /* Heart + star row docks as a sticky bar at the bottom of the lightbox,
       pinned from open (sticky bottom) until its natural position scrolls in. */
    #memoryLightbox .lightbox-engage {
        position: sticky;
        bottom: 0;
        z-index: 6;
        background: var(--paper);
        margin: 0 -1rem;
        padding: .5rem 1rem calc(.5rem + env(safe-area-inset-bottom));
        border-top: 1px solid var(--rule);
        box-shadow: 0 -6px 16px rgba(0, 0, 0, .07);
    }
    /* Keep the bar slim: the per-person star list stays desktop-only. */
    #memoryLightbox .lightbox-engage .interest-scorer__attribution { display: none; }
}

/* Dismissable star-scale explainer inside the engage bar (phones only —
   d-sm-none in the markup). */
.star-help {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: .75rem;
    padding-bottom: .4rem;
    margin-bottom: .4rem;
    border-bottom: 1px dotted var(--rule);
}
.star-help[hidden] { display: none; }

/* ========================================================== BOOK PAGE */
/* Design review 1d. DOM order is the phone order; the grid only moves the
   cover into a column of its own on wide screens. No `order:` anywhere, so
   there is one hierarchy rather than two. */

.book-page {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
    align-items: start;
}
@media (min-width: 992px) {
    .book-page {
        grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
        column-gap: 2.5rem;
    }
    .book-page__photographs { grid-column: 1 / -1; }
}

/* The cover plate: same paper mount as the card, so the object reads the same
   on both surfaces. Any ratio, nothing cropped. */
.book-page__plate {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.book-page__cover {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--paper-alt);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    padding: 1rem;
}
.book-page__cover img {
    max-width: 100%;
    max-height: 26rem;              /* rem: grows with the A A A control */
    width: auto;
    height: auto;
    border-radius: 0;
    box-shadow: 0 2px 8px rgba(58, 46, 40, .24);
}

.book-page__strip { display: flex; flex-direction: column; gap: .5rem; }
.book-page__thumbs {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
    gap: .4rem;
}
.book-page__thumb {
    display: block;
    aspect-ratio: 1;
    overflow: hidden;
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    background: var(--paper-alt);
}
.book-page__thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 0;               /* beats the #memoryLightbox img rule */
    max-height: none;
    margin: 0;
}

.book-page__text {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
}
.book-page__byline {
    font-family: var(--font-chrome);
    font-size: .9rem;
    color: var(--ink-muted);
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    margin-top: -.6rem;             /* tucks under the display title */
}
/* The author is a filter link, but it must read as the byline it always
   was — colour inherited, the affordance only on hover. */
.book-page__author-link {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px dotted var(--rule-strong, currentColor);
}
.book-page__author-link:hover {
    color: var(--ink);
    border-bottom-style: solid;
}
/* Search-within box in the sticky filter bar (desktop). 16px+ so iOS
   doesn't zoom the page when it gains focus. */
.collection-search .form-control {
    font-size: 1rem;
    min-width: 11rem;
}
.collection-search__input {
    font-size: 1rem;
    min-height: 2.75rem;
}
.book-page__unidentified {
    margin: 0;
    color: var(--ink-muted);
    font-style: italic;
}

/* THE SYNOPSIS WELL. The tint is the boundary between the machine's voice and
   the household's — everything outside this box was written by a person. */
.book-synopsis {
    background: var(--paper-alt);
    border-radius: var(--radius-lg);
    padding: 1.1rem 1.2rem 1rem;
}
.book-synopsis--unsourced {
    box-shadow: inset 0 0 0 1px var(--accent-soft);
}
.book-synopsis__label {
    font-family: var(--font-chrome);
    font-size: .7rem;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin: 0 0 .5rem;
}
.book-synopsis__text {
    font-family: var(--font-body);
    font-size: 1.06rem;
    line-height: 1.6;
    color: var(--ink);
    text-wrap: pretty;
}
.book-synopsis__text p:last-child { margin-bottom: 0; }
.book-synopsis__note {
    margin: .75rem 0 0;
    padding-top: .6rem;
    border-top: 1px solid #e0d8c8;
    font-family: var(--font-chrome);
    font-size: .82rem;
    line-height: 1.5;
    color: var(--ink-muted);
}
.book-synopsis--unsourced .book-synopsis__note { color: var(--accent); }
.book-synopsis__note a { white-space: nowrap; }

/* Sits under the synopsis note, quieter than the body text but a real
   offer — the one route off this page to somewhere that might know more. */
.book-search {
    font-size: .82rem;
    color: var(--muted, #6b6b6b);
    margin: .35rem 0 0;
}
.book-search a { white-space: nowrap; }
.book-search__sep { margin: 0 .15rem; opacity: .5; }

/* Stepping this copy's photographs in place. The chevrons sit ON the plate —
   the alternative, controls below it, pushes the synopsis down the page on
   phones for a feature most books (single cover) never show at all. They stay
   faint until hover/focus so the photograph is what you look at, and are
   always fully opaque on touch, where there is no hover to discover them. */
/* Wrapper only positions the controls — it must not restyle the plate, which
   is a centred flex mount with its own padding and border above. */
.book-page__coverwrap { position: relative; }
.book-page__cover { cursor: zoom-in; }
.book-plate__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    border: 0;
    width: 2.1rem;
    height: 3.2rem;
    font-size: 1.6rem;
    line-height: 1;
    color: #fff;
    background: rgba(20, 16, 14, .42);
    cursor: pointer;
    opacity: .35;
    transition: opacity .15s ease, background .15s ease;
}
.book-plate__nav--prev { left: 0; border-radius: 0 4px 4px 0; }
.book-plate__nav--next { right: 0; border-radius: 4px 0 0 4px; }
.book-page__coverwrap:hover .book-plate__nav,
.book-plate__nav:focus-visible { opacity: 1; background: rgba(20, 16, 14, .68); }
.book-plate__count {
    position: absolute;
    right: .4rem;
    bottom: .4rem;
    font-size: .7rem;
    line-height: 1;
    padding: .2rem .4rem;
    border-radius: 3px;
    color: #fff;
    background: rgba(20, 16, 14, .55);
    font-variant-numeric: tabular-nums;
}
@media (hover: none) {
    .book-plate__nav { opacity: .85; }
}
/* Which photograph the plate is showing. Additive: the rule above sets
   display, aspect-ratio and overflow for the grid, and must keep them. */
.book-page__thumb {
    cursor: zoom-in;
    outline: 2px solid transparent;
    outline-offset: 2px;
    transition: outline-color .15s ease;
}
.book-page__thumb.is-active { outline-color: var(--accent, #8a6a4f); }

.book-page__engage { border-top: 1px solid var(--rule); padding-top: .9rem; }

/* Subjects, provenance said in words (1g). Chips are now real tap targets:
   min-height, not height, so 130% text grows them. */
.book-subjects { display: flex; flex-direction: column; gap: .8rem; }
.book-subjects__group { display: flex; flex-direction: column; gap: .4rem; }
.book-subjects__chips { display: flex; flex-wrap: wrap; gap: .4rem; }

.subject-chip {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: .45rem .9rem;
    background: var(--chip);
    color: var(--chip-ink);
    border: 1px solid transparent;
    border-radius: 999px;
    font-family: var(--font-chrome);
    font-size: .92rem;
    text-decoration: none;
}
.subject-chip:hover,
.subject-chip:focus-visible { background: var(--ink-soft); color: var(--paper); }

/* Read off the book, not from a record. The dashed edge is a reminder for
   people who have learned it; the group label carries the meaning. */
.subject-chip--inferred {
    background: var(--paper);
    border: 1px dashed var(--rule-strong);
    color: var(--chip-ink);
}

.book-page__shelved {
    margin: 0;
    font-family: var(--font-chrome);
    font-size: .88rem;
    color: var(--ink-muted);
}

.book-page__catalogue {
    border-top: 1px solid var(--rule);
    padding-top: .7rem;
    font-family: var(--font-chrome);
    font-size: .85rem;
    color: var(--ink-muted);
}
.book-page__catalogue > summary {
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    min-height: 2.5rem;
}
.book-page__catalogue > summary::-webkit-details-marker { display: none; }
.book-page__catalogue > summary::after { content: "\203A"; color: var(--ink-faint); }
.book-page__catalogue[open] > summary::after { transform: rotate(90deg); }
.book-page__catalogue-body {
    font-family: var(--font-mono);
    font-size: .82rem;
    line-height: 1.8;
    padding-top: .3rem;
}

/* ==================================================== RESPONSIVE NOTES */
@media (max-width: 768px) {
    .hero { padding: 3rem 0 2rem; }
    .memory-card .card-body { padding: 1rem; }
    .filter-bar { top: 56px; }
}

/* ============================================== LIGHTBOX SHELVE POPUP */
/* Admin "Shelve…" in the lightbox toolbar: a details-dropdown of shelf
   checkboxes, POSTing like its Move-to neighbour. (The grid's Shelve
   lives on the bulk bar now.) */
.shelve-pop { position: relative; display: inline-block; }
.shelve-pop > summary { list-style: none; }
.shelve-pop > summary::-webkit-details-marker { display: none; }
.shelve-pop__panel {
    position: absolute;
    right: 0;
    top: calc(100% + .3rem);
    z-index: 1060;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: .5rem;
    padding: .6rem .9rem;
    min-width: 15rem;
    box-shadow: 0 6px 24px rgba(0, 0, 0, .12);
    /* Hangs from a STICKY toolbar, so page/modal scroll can never reach a
       tall panel — it must scroll itself. The cap keeps the top edge (and
       so the scroll start) always on screen; dvh dodges the iOS URL bar.
       It cannot go position:fixed: the toolbar's backdrop-filter makes the
       toolbar the containing block for fixed descendants. */
    max-height: min(60vh, calc(100dvh - var(--navbar-h, 60px) - 10rem));
    overflow-y: auto;
    white-space: normal;
    max-width: min(20rem, 90vw);
}
@media (max-width: 576px) {
    /* The phone toolbar is taller (tools tray wraps full-width). */
    .shelve-pop__panel {
        max-height: calc(100dvh - var(--navbar-h, 60px) - 16rem);
    }
}

/* ==================================================== GRID BULK BAR */
/* The team's selection bar above collection grids: sticky so the actions
   stay in reach while ticking down a long grid. top must clear the sticky
   NAVBAR (top:0, z 1060) — at top:0 the bar sticks UNDERNEATH it and
   reads as not sticky at all. The navbar's real height is measured into
   --navbar-h by memoriarch.js (hardcoded offsets ran a few px short). */
.bulk-bar {
    position: sticky;
    top: var(--navbar-h, 60px);
    z-index: 1020;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: .5rem;
    padding: .5rem .75rem;
    font-family: var(--font-chrome);
}
.bulk-bar .bulk-bar__actions button:not([data-bulk]),
.bulk-bar .bulk-bar__actions select {
    opacity: .45;
    pointer-events: none;
}
.bulk-bar.has-selection .bulk-bar__actions button:not([data-bulk]),
.bulk-bar.has-selection .bulk-bar__actions select {
    opacity: 1;
    pointer-events: auto;
}
.bulk-bar__shelve { position: relative; }
.bulk-bar__shelve > summary { list-style: none; }
.bulk-bar__shelve > summary::-webkit-details-marker { display: none; }
.bulk-bar__flyout {
    position: absolute;
    right: 0;
    top: calc(100% + .3rem);
    z-index: 1060;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: .5rem;
    padding: .6rem .9rem;
    min-width: 15rem;
    box-shadow: 0 6px 24px rgba(0, 0, 0, .12);
    text-align: left;
    /* Anchored inside a viewport-pinned sticky bar, so page scroll can
       never bring a tall panel's Apply into reach — it scrolls itself.
       dvh dodges the iOS URL bar; normal wrapping stops long shelf names
       pushing the box off the left of a phone screen. */
    max-height: min(60vh, calc(100dvh - var(--navbar-h, 60px) - 10rem));
    overflow-y: auto;
    white-space: normal;
    max-width: min(20rem, 90vw);
}
@media (max-width: 576px) {
    /* The wrapped phone bar is many rows tall, so any top-anchored panel
       starts too low. Become a fixed bottom sheet instead (the same idiom
       as the filter offcanvas); the bar's ancestors carry no
       filter/transform, so fixed is genuinely viewport-relative. */
    .bulk-bar__flyout {
        position: fixed;
        inset: auto .75rem calc(.75rem + env(safe-area-inset-bottom)) .75rem;
        top: auto;
        max-width: none;
        max-height: 60vh;
        z-index: 1080;
        box-shadow: 0 -6px 24px rgba(0, 0, 0, .25);
    }
}

/* The "…" expander for clamped chip rows (mobile shelf/place rows) —
   injected by JS only when the row actually overflows its clamp. */
.chips-more {
    border: 1px solid var(--rule);
    background: var(--paper);
    color: var(--ink-muted);
    border-radius: 999px;
    font-size: .8rem;
    line-height: 1;
    padding: .2rem .7rem;
    margin: .15rem 0 .35rem;
    cursor: pointer;
}
.chips-more:hover { color: var(--accent); border-color: var(--accent); }

/* The sort dropdown lives in the sticky filter bar (z 5) while the admin
   bulk bar below carries z 1020 — an open sort menu rendered UNDERNEATH
   it. Lift the filter bar above the bulk bar only while one of its
   dropdowns is open. */
.filter-bar:has(.dropdown-menu.show) { z-index: 1021; }

/* The bulk bar's action cluster is one long inline-flex unit — let it
   wrap instead of running off a phone screen. */
.bulk-bar__actions { flex-wrap: wrap; }
@media (max-width: 576px) {
    .bulk-bar__actions {
        margin-left: 0 !important;   /* drop ms-auto's push on phones */
        width: 100%;
    }
}

/* ===================================================== RESUME CHIP */
/* The "continue where you left off" offer — floats until tapped or
   dismissed; the page itself still lands at the top. */
.resume-chip {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(1rem + env(safe-area-inset-bottom));
    z-index: 1030;
    display: inline-flex;
    align-items: center;
    gap: .15rem;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: 999px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, .18);
    font-family: var(--font-chrome);
}
.resume-chip__go {
    border: none;
    background: none;
    color: var(--ink);
    font-size: .9rem;
    padding: .55rem .35rem .55rem 1.1rem;
    cursor: pointer;
}
.resume-chip__go:hover { color: var(--accent); }
.resume-chip__x {
    border: none;
    background: none;
    color: var(--ink-muted);
    font-size: 1.1rem;
    line-height: 1;
    padding: .55rem .9rem .55rem .35rem;
    cursor: pointer;
}
.resume-chip__x:hover { color: var(--accent); }
