/* ==========================================================================
   Cards — the card, the list surface, and the anatomy of an item
   ==========================================================================

   THE RULE. Canonical text; docs/admin/design_system.md and
   components/type.css quote it, and tests/core/
   test_the_grammar_is_stated_once.py goes red if the three drift:

       The card goes on the item; the name goes on the ground.

   Two tests decide everything else. If you cannot write the item's title and
   she does not pick it to start writing, it is a row. If a heading is inside
   a box, move it out.

   ⛔ THE 2026-09-03 RULE (the container on whatever HAS A NAME) INVERTED THE
   ONE SCREEN THAT READS. /entries/ has a bare month heading, a bare day rail
   and a white card per ENTRY — the container is on the item, the name is on
   the ground. Memory, People and Open Questions were built to
   the inverse (a boxed group with its heading inside, rows on hairlines) and
   are the screens Gabriel said "feel like another app". Same tokens, same
   radius, same hairline; the recipe was upside down.

   ⭐ FOUR SURFACES, AND NOTHING ELSE:

   Card (.card-surface) — one item she chooses between or opens: an entry, a
     template, a prompt, a Library door, the one offer. White, hairline,
     --radius-card, --space-lg, no resting shadow; --door lifts. Never nested;
     never holds a section heading.
   List surface (.list-surface > .list-row) — items that are a sentence or a
     name with a fact: memories, threads, notifications, settings, mentions,
     people. One white box per group, rows on hairlines, 44px; an open row
     tints inset and grows its sentence.
   Section heading (.section-heading, type.css) — on the ground above every
     run of two or more items; it names the group, the surface beneath is the
     container, so a group gets no box of its own.
   Nothing — her words, an Explore answer, the title and lede.

   ⚠️ ~54 container recipes existed across the app sheets when this was
   written (audit 2026-09-05). They are ported onto these two surfaces one
   screen at a time; the legacy recipes at the foot of this file leave with
   their last consumer. 📐 docs/admin/design_system.md.
   ========================================================================== */

/* ── The card ──────────────────────────────────────────────────────────── */

/* `.entry-card` is `.card-surface` under its old name (v0.79.x), and it is
   always a door — the whole card is one stretched link — so it keeps the
   hover. Everything else opts in with `--door`: a forgotten modifier yields
   a still card, never a lying one. */
.entry-card,
.card-surface {
    position: relative;
    min-width: 0;
    background: var(--surface-raised);
    border: var(--border-width) solid var(--border-color);
    border-radius: var(--radius-card);
    padding: var(--space-lg);
    box-shadow: none;
}

/* The three-band stack is the entry card's; a bare `.card-surface` stays a
   block so the twenty templates already on it keep their own flow. Cards
   built on `.item` get the grid. */
.entry-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.entry-card,
.card-surface--door {
    cursor: pointer;
    transition: border-color var(--duration-normal) var(--ease-out),
                box-shadow var(--duration-normal) var(--ease-out),
                transform var(--duration-normal) var(--ease-out);
}

/* Hover is a pointer thing. On a phone a lifted card that never comes back
   down is the tell that a hover rule leaked; the pressed state below is what
   a finger gets. */
@media (hover: hover) {
    .entry-card:hover,
    .card-surface--door:hover {
        border-color: var(--primary);
        box-shadow: var(--shadow-md);
        transform: translateY(-2px);
    }
}

.entry-card:active,
.card-surface--door:active {
    border-color: var(--primary);
    transform: translateY(0);
}

/* A quieter card for a note inside a page — the settle line on a thread, the
   Pro line on Prompts. Inset ground, no border: it is not an item and must
   not read as one. */
.card-surface--inset {
    background: var(--surface-inset);
    border-color: transparent;
}

/* Cards in a run: a column by default, a grid where the items are doors of
   equal weight (the Library hub, the help index). */
.card-stack {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    margin: 0 0 var(--space-2xl);
    padding: 0;
    list-style: none;
    max-width: var(--measure-page);
}

.card-stack--grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: var(--space-base);
    max-width: none;
}

/* ── The stretched link — the whole card is the target ─────────────────── */

/* ⭐ `::after` rather than sizing the <a> itself, so the link keeps its
   accessible name from the `sr-only` span inside it, and the card keeps its
   own text selectable. Any real control inside the card must be lifted above
   it (`position: relative; z-index: 1`) or the card swallows the press. */
.card-open::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

/* ⛔ A door a template marked `hidden` must not still be a door. The scan
   batch page renders one on EVERY page row and hides it on the rows that are
   not read yet (docs/redesign/scan-flow-review.md, finding 2 — the successor
   to the "Check it" button that finding was written about). Today the UA's
   own `[hidden]` does that, because the <a> is inline and nothing here sets a
   `display` — but the whole point of the `::after` above is that this element
   is one `display: block` away from covering its card, and that one line
   would silently make every waiting row a link into the deck.
   ⚠️ Specificity, not luck: `.card-open[hidden]` outranks a later
   `.card-open { display: … }`. Same trap, same fix as `.btn[hidden]`
   (components/buttons.css) and `.scan-card[hidden]` (scan-review.css).
   🧪 tests/browser/test_the_scan_flow_has_one_next_step.py
      ::test_a_row_nobody_asked_to_read_is_not_a_door */
.card-open[hidden] {
    display: none;
}

/* Keyboard focus has to be visible on the CARD, because the link that
   receives it has no box of its own. */
.card-surface:has(.card-open:focus-visible),
.entry-card:has(.card-open:focus-visible) {
    outline: var(--border-width-md) solid var(--primary);
    outline-offset: 2px;
}

/* ── The list surface ──────────────────────────────────────────────────── */

/* ONE white hairline box, zero padding, holding items that are a sentence
   or a name-with-a-fact as 44px hairline rows. The group's name is a
   `.section-heading` ABOVE the box, on the ground. Notifications was the
   reference instance (.notification-list); this is it under one name. */
.list-surface {
    list-style: none;
    margin: 0 0 var(--space-2xl);
    padding: 0;
    max-width: var(--measure-page);
    background: var(--surface-raised);
    border: var(--border-width) solid var(--border-color);
    border-radius: var(--radius-card);
    overflow: hidden;
}

.list-surface > * + * {
    border-top: var(--border-width) solid var(--border-color);
}

.list-row {
    display: block;
    margin: 0;
    color: inherit;
    text-decoration: none;
}

/* A row that opens in place is a <details>; its <summary> is the row. */
.list-row__summary {
    list-style: none;
    cursor: pointer;
}

.list-row__summary::-webkit-details-marker,
.list-row__summary::marker {
    content: "";
    display: none;
}

.list-row__summary,
a.list-row,
label.list-row,
.list-row--static {
    display: block;
    min-height: 44px;
    padding: var(--space-md) var(--space-lg);
}

@media (hover: hover) {
    a.list-row:hover,
    .list-row__summary:hover {
        background: var(--surface-hover);
    }
}

a.list-row:active,
.list-row__summary:active {
    background: var(--surface-inset);
}

a.list-row:focus-visible,
.list-row__summary:focus-visible {
    outline: var(--border-width-md) solid var(--primary);
    outline-offset: -2px;
}

/* Open state: the row is already on white, so it tints INSET and grows its
   sentence. It does not become a slab with its own radius and negative
   margins — that was Memory's and Open Questions' "open row becomes a card
   inside the container" rule, which is a card in a box. */
.list-row[open] {
    background: var(--surface-inset);
}

.list-row[open] > .list-row__summary {
    padding-bottom: var(--space-sm);
}

.list-row__body {
    padding: 0 var(--space-lg) var(--space-md);
}

/* ── The anatomy of an item ─────────────────────────────────────────────── */

/* One vocabulary for the inside of a thing, whether it sits on a card or in
   a row: title OR sentence, description, meta, the right-hand slot (chevron,
   toggle, value, Ask), the doors strip. This is what makes /entries/ and
   Memory the same app, and it stops the .card__title / .row__title
   duplication. Grid areas, not negative line numbers, so the end slot spans
   the stacked lines on any combination of them. */
/* ⚠️ NO ROW-GAP. Five areas are declared and most items fill one or two;
   Chromium still lays out the empty tracks, and a row-gap between them made
   a one-line row 73px instead of 48px with the lead slot pinned to the top
   (found by the reflection port, 2026-09-05). The rhythm between filled
   lines is a margin on each optional line instead, so an absent line costs
   nothing. */
.item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
        "eyebrow end"
        "main    end"
        "desc    end"
        "meta    end"
        "doors   doors";
    column-gap: var(--space-md);
    row-gap: 0;
    align-items: center;
    min-width: 0;
}

/* With a mark on the left: an avatar, a feature icon, a notification mark. */
.item--lead {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
        "lead eyebrow end"
        "lead main    end"
        "lead desc    end"
        "lead meta    end"
        "lead doors   doors";
}

/* The /entries/ band rhythm, when the item is a card: a wider step between
   the lines than a row gets. */
.card-surface.item > .item__desc,
.card-surface.item > .item__meta {
    margin-top: var(--space-sm);
}

.item__lead {
    grid-area: lead;
    align-self: start;
}

/* The template adds `.eyebrow` to it — one declaration of the rung. */
.item__eyebrow {
    grid-area: eyebrow;
    margin-bottom: var(--space-2xs);
}

.item__title {
    grid-area: main;
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: 0;
    color: var(--text-color);
    overflow-wrap: anywhere;
}

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

/* A row's sentence: a memory, a thread, a prompt. Body face at 400, 16px,
   and it is the whole row when there is no title. */
.item__text {
    grid-area: main;
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 400;
    line-height: 1.5;
    color: var(--text-color);
    overflow-wrap: anywhere;
}

/* Closed rows clamp the sentence to three lines; the open state shows all
   of it. Part of "so much text" on Memory was wrapping, not hierarchy.
   📐 design_system.md — ROW_CLAMP_LINES = 3, a product number. */
.list-row:not([open]) .item__text {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.item__desc {
    grid-area: desc;
    margin: var(--space-2xs) 0 0;
    font-size: var(--text-base-sm);
    line-height: 1.6;
    color: var(--text-secondary);
}

/* Two lines of her own writing on a card, and nothing else. */
.card-surface .item__desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The template adds `.meta` to it — one declaration of the rung. `.meta`
   declares `margin: 0`; this margin wins on specificity order because it is
   declared later in the same sheet load order (cards.css after type.css). */
.item__meta {
    grid-area: meta;
    margin-top: var(--space-2xs);
}

/* The right-hand slot: chevron, toggle, value, or the Ask pill. Lifted
   above the stretched link so a real control inside a door still works. */
.item__end {
    grid-area: end;
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    flex-shrink: 0;
    position: relative;
    z-index: 1;
    color: var(--text-muted);
}

.item__chevron {
    color: var(--text-muted);
}

/* The doors strip: buttons under an item, wrapping on a phone. Per the
   control-hierarchy table in design_system.md — one primary at most. */
.item__doors {
    grid-area: doors;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    margin-top: var(--space-xs);
    position: relative;
    z-index: 1;
}

/* ── On a phone, a wide end slot becomes a doors strip ──────────────────── */

/*
 * ⭐ **A BUTTON BESIDE A SENTENCE IS A COLUMN AS WIDE AS THE BUTTON.** The end
 * slot is `auto`, so on Settings and everything under Library — where nearly
 * every row is a sentence with one control — the control took more width than
 * the words explaining it, and the sentence wrapped to three lines beside a
 * two-word button. Gabriel, 2026-09-08: *"on the side it creates an unbalance
 * view, like the button column takes more space than the text in many cases,
 * and it would be worse with a smaller screen."*
 *
 * ⭐ **AND THE GRAMMAR ALREADY HAD THE ANSWER: `.item__doors`** — buttons under
 * an item, left-aligned with the words. This does not invent an arrangement; it
 * lends the end slot the doors strip's one, below the phone breakpoint. Above
 * it, side by side is denser and reads fine, so nothing changes.
 *
 * ⚠️ **ONLY WIDE CONTROLS MOVE.** A toggle, a chevron, an emotion name or a
 * date is narrower than the gap it would leave behind, and dropping those below
 * the sentence would cost a line and gain nothing — so the rule asks what the
 * slot HOLDS. `:has()` is how it asks; the alternative was a modifier class on
 * every row, which is a class someone adds a new Settings row without.
 */
@media (max-width: 600px) {
    .item:has(> .item__end .btn),
    .item:has(> .item__end .form-select) {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            "eyebrow"
            "main"
            "desc"
            "meta"
            "end"
            "doors";
    }

    /* The mark keeps its column and the control lines up with the words, not
       with the mark — same as the doors strip above. */
    .item--lead:has(> .item__end .btn),
    .item--lead:has(> .item__end .form-select) {
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-areas:
            "lead eyebrow"
            "lead main"
            "lead desc"
            "lead meta"
            "lead end"
            "lead doors";
    }

    .item:has(> .item__end .btn) > .item__end,
    .item:has(> .item__end .form-select) > .item__end {
        justify-content: flex-start;
        flex-wrap: wrap;
        gap: var(--space-sm);
        margin-top: var(--space-sm);
    }
}

/* ==========================================================================
   Legacy recipes — each leaves with its last consumer (docs/admin/
   design_system.md, "Ports"). Nothing below is a fourth surface; it is a
   copy of one of the two above under an older name.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Stats Grid — dashboard / vault pages. Migrates to .card-surface.
   -------------------------------------------------------------------------- */
.stats-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.75rem;
    margin-bottom: 1.5rem;
}

.stat-card {
    background: var(--card-background);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    padding: 1.25rem 1rem;
    text-align: center;
    box-shadow: var(--shadow-sm);
    transition: box-shadow var(--transition-base, 0.2s ease), transform var(--transition-base, 0.2s ease);
}

.stat-card:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}

.stat-value {
    font-size: 2rem;
    font-weight: 600;
    color: var(--primary);
    line-height: 1;
    margin-bottom: 0.25rem;
}

.stat-label {
    font-size: 0.85rem;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.09em;
}

/* --------------------------------------------------------------------------
   Generic Card — token / vault / scan pages (7 consumers). Migrates to
   .card-surface; .card-title / .card-meta / .card-preview to .item__*.
   -------------------------------------------------------------------------- */
.card {
    background: var(--card-background);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    padding: 1rem;
    margin-bottom: 1rem;
    box-shadow: var(--shadow-sm);
    transition: border-color var(--transition-base, 0.2s ease), box-shadow var(--transition-base, 0.2s ease);
}

.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--border-color);
}

.card-header h2 {
    font-size: 1.1rem;
    margin: 0;
}

.card-title {
    font-size: 1rem;
    font-weight: 600;
    margin: 0;
}

.card-title a {
    color: inherit;
}

.card-title a:hover {
    color: var(--primary);
}

.card-meta {
    font-size: 0.8rem;
    color: var(--text-secondary);
}

.card-body {
    color: var(--text-color);
}

.card-preview {
    font-size: 0.9rem;
    color: var(--text-secondary);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.entry-card-header {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-bottom: 0.5rem;
}

.entry-type-badge {
    display: inline-block;
    padding: 0.2em 0.5em;
    font-size: 0.7rem;
    font-weight: 500;
    background: var(--primary-light);
    color: var(--primary-dark);
    border-radius: 3px;
}

.entry-preview {
    font-size: 0.9rem;
    color: var(--text-secondary);
    line-height: 1.5;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* --------------------------------------------------------------------------
   People Grid — the pre-2.0 people list. No template renders it; kept
   until tests/core/test_hover_states_and_transitions.py is retargeted.
   -------------------------------------------------------------------------- */
.people-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.75rem;
}

.person-card {
    background: var(--card-background);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    padding: 1rem;
    display: flex;
    align-items: center;
    gap: 1rem;
    transition: border-color var(--transition-base, 0.2s ease),
        box-shadow var(--transition-base, 0.2s ease),
        transform var(--transition-base, 0.2s ease);
}

.person-card:hover {
    border-color: var(--primary);
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}

.person-info {
    flex: 1;
    min-width: 0;
}

.person-mention-count {
    font-size: 0.85rem;
    color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   Section Headers — the dashboard's. Migrates to .section-heading.
   -------------------------------------------------------------------------- */
.section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
}

.section-title {
    font-size: 1.125rem;
    font-weight: 600;
    margin: 0;
}

.section-link {
    font-size: 0.85rem;
    color: var(--primary);
    transition: color var(--transition-base, 0.2s ease);
}

.section-link:hover {
    color: var(--primary-dark);
    text-decoration: underline;
}

/* --------------------------------------------------------------------------
   Card Section — the alias group (Phase E.7, v0.21.7): a boxed section WITH
   ITS HEADING INSIDE, i.e. the inverse of the rule. Each alias leaves as its
   page is ported (entry reader, person). `.section` left on 2026-09-05 with
   the reflection and Open Questions ports — its last two consumers.
   -------------------------------------------------------------------------- */
.themes-section,
.prompts-section,
.connected-entries-section,
.emotion-chart,
.relationship-insights {
    background: var(--card-background);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius-lg);
    padding: var(--space-lg);
    margin-bottom: var(--space-lg);
}

/* --------------------------------------------------------------------------
   Responsive — legacy grids
   -------------------------------------------------------------------------- */
@media (min-width: 768px) {
    .stats-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 1rem;
    }

    .people-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 1rem;
    }
}

@media (min-width: 1024px) {
    .stats-grid {
        grid-template-columns: repeat(3, 1fr);
    }

    .stats-grid.cols-4 {
        grid-template-columns: repeat(4, 1fr);
    }

    .people-grid {
        grid-template-columns: repeat(3, 1fr);
    }

    .entry-card-header {
        flex-direction: row;
        justify-content: space-between;
        align-items: center;
    }
}

@media (min-width: 1280px) {
    .stats-grid {
        grid-template-columns: repeat(4, 1fr);
    }

    .people-grid {
        grid-template-columns: repeat(4, 1fr);
    }
}
