/*
 * athena.css — the app layer, on top of organic.css.
 *
 * Pediment is retired. This file no longer defines a :root of its own —
 * every rule below reads the Organic custom properties (--color-*,
 * --font-*, --space-*, --radius-*, --shadow-*) directly from
 * public/css/organic.css, which is linked before this file. None of
 * Pediment's old custom properties are ported forward under new names;
 * that would just be Pediment with an alias.
 *
 * Two kinds of rule live here, and they are labelled as such:
 *
 *   1. Genuinely app-specific — the board's masonry columns, the card
 *      image treatment, the list view and the tag chips. The design
 *      system has no opinion on any of these; they stay here for good.
 *
 *   2. Transitional — .btn and label.field. The remaining Blade
 *      views still emit this markup rather than the system's own
 *      .btn-primary/.input/.table classes. Rather than ship those screens
 *      unstyled, these rules give the existing markup the Organic palette
 *      under its current class names. Each later task that touches a given
 *      screen should delete the corresponding block here once that screen's
 *      markup adopts the system's classes directly — a duplicated rule is
 *      easy to remove, a screen left unstyled for several tasks is not.
 *
 *      Task 7b (settings, admin, auth, trash) removed .notice, whose last
 *      five consumers were all in its scope, and narrowed .field to
 *      label.field. Task 7c removed the bare table/th/td rules the same way,
 *      its own screen having been their last consumer. What is left is named
 *      with its remaining consumers at each block, because "delete it once
 *      the markup adopts the system" is only safe advice if you can see who
 *      has not adopted it yet.
 */

/* — layout shell —
   236px sidebar + flexed main, per the handoff (§2). Two collapses below
   it, both CSS-only (no JS, no build step): an icon rail under 900px, and
   a bottom bar carrying only the primary group under 640px. The DOM never
   changes shape — .sidebar-nav-primary is the same markup in all three
   states, just repositioned and relabelled by media query — so there is
   nothing here for setTab()-style hidden-canvas problems to hide in. */
.shell { display: flex; align-items: stretch; min-height: 100vh; }

.sidebar {
    width: 236px;
    flex: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: 26px 18px;
    background: var(--color-bg);
    border-right: 1px solid var(--color-divider);
    position: sticky;
    top: 0;
    align-self: flex-start;
    height: 100vh;
    overflow-y: auto;
}
.sidebar .brand {
    display: flex;
    align-items: center;
    color: inherit;
    text-decoration: none;
    margin-bottom: var(--space-4);
}

.main {
    flex: 1;
    min-width: 0;
    padding: 30px 40px 80px;
}

/* Utility: make the `hidden` ATTRIBUTE win.
   The UA stylesheet's [hidden] { display: none } is an author-level default
   with the lowest possible weight, so any class that sets display beats it —
   organic.css's own .btn is display: inline-flex, which means a
   <button class="btn" hidden> renders in full. That is the same
   shared-class-name trap as .btn inheriting Caprasimo and .card inheriting
   padding, and it is silent in exactly the same way: the markup says hidden,
   nothing in a test reads geometry, and the button is simply there. !important
   is the conventional fix and is deliberate — an element carrying `hidden` is
   asking for one thing and there is no case in this app where a class should
   overrule it. */
[hidden] { display: none !important; }

/* Utility: hides text visually without pulling it from the accessibility
   tree — the icon rail and bottom bar drop labels from view, not from
   screen readers. */
.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* — sidebar: save button —
   Deliberately self-contained rather than organic.css's .btn/.btn-primary:
   those collide with the transitional .btn block below, which restyles
   the bare class="btn" markup the rest of the app still emits (see the
   file header). Composing both here would mean whichever rule happens to
   sit later in the cascade wins font and background, not the one that's
   actually meant to. */
.sidebar-save {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    margin-bottom: var(--space-3);
    padding: 10px 16px;
    border: none;
    border-radius: 999px;
    background: var(--color-accent);
    /* Task 8: was --color-bg, 3.03:1. --color-text on --color-accent is
       4.60:1 — the pair Tasks 6, 7a and 7c already chose for every accent
       fill they introduced. This is the sweep that makes the app agree with
       itself. */
    color: var(--color-text);
    text-decoration: none;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
}
/* Hover goes LIGHTER, not darker — see the note on .btn below. The system's
   own darker hover takes this pill from 4.60:1 to 3.70:1, i.e. hovering a
   compliant control drops it under AA. accent-400 is 8.03:1, accent-500 is
   5.53:1. */
.sidebar-save:hover { background: var(--color-accent-400); }
.sidebar-save:active { background: var(--color-accent-500); }
.sidebar-save .nav-icon { display: none; }

/* — sidebar: nav groups —
   No pill or background on the active item in the design — just weight and
   colour — so hover is the only state that needs a background tint. */
.sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.sidebar-nav-primary { margin-bottom: auto; }
.sidebar-nav a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    color: var(--color-neutral-700);
    text-decoration: none;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: 500;
}
.sidebar-nav a:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); color: var(--color-text); }
.sidebar-nav a.is-active { color: var(--color-text); font-weight: 700; }
.sidebar-nav .nav-icon { display: none; flex: none; width: 20px; height: 20px; }

.sidebar-settings { margin-top: var(--space-4); }
.sidebar-heading {
    font-family: var(--font-body);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    /* Task 8: was --color-neutral-600, 3.61:1 on the ground. An 11px
       uppercase group label is body copy. -700 is 5.53:1. */
    color: var(--color-neutral-700);
    margin: 0 0 var(--space-2) 10px;
}
.sidebar-settings .sidebar-nav a { font-size: 13px; }

/* — sidebar: user chip — */
.user-chip {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: var(--space-3);
    padding: 8px;
    border-radius: 999px;
    background: var(--color-surface);
    text-decoration: none;
    color: inherit;
}
.user-chip:hover { background: var(--color-neutral-200); }
.user-chip .avatar {
    flex: none;
    width: 32px; height: 32px;
    display: grid; place-items: center;
    border-radius: 50%;
    /* Task 8: the base sage carried --color-bg at 3.14:1. An initial in an
       avatar is a 13px glyph and is the only thing in it, so it is text.
       --color-accent-2-700 takes the same cream to 5.43:1 — the same
       deepening .card--note needed, and for the same reason: cream on the
       base sage does not pass anywhere. */
    background: var(--color-accent-2-700);
    color: var(--color-bg);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 13px;
}
.user-chip .user-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.user-chip .user-name { font-size: 13px; font-weight: 600; color: var(--color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Task 8: was -600, 3.21:1 on the chip's --color-surface. -700 is 4.92:1. */
.user-chip .user-count { font-size: 12px; color: var(--color-neutral-700); }

/* — sidebar: wordmark collapse — */
.wordmark-short { display: none; }

/* Mobile-only Account route (Finding 2, 20 Aug 2026 usability review):
   hidden at every width except the <=640px bottom bar, which un-hides it
   below. Declared here rather than only inside that media query so the
   rule holding it hidden survives independently of the 640px block's own
   edits — the same pattern `.wordmark-short` just above uses. */
.sidebar-account-mobile { display: none; }

/* — sidebar: icon rail (900px) —
   Labels drop from view (not from the tree — see .visually-hidden), icons
   take over, and the primary group is the only thing that keeps its own
   identity; settings stays reachable but recessed, same as full width. */
@media (max-width: 900px) {
    .sidebar { width: 56px; padding: 16px 8px; align-items: center; }
    .sidebar .brand { margin-bottom: var(--space-3); }
    .wordmark-full { display: none; }
    .wordmark-short { display: inline; }

    .sidebar-save { width: 40px; height: 40px; padding: 0; border-radius: 50%; }
    .sidebar-save .nav-icon { display: block; }
    .sidebar-save .nav-label { }

    .sidebar-nav { width: 100%; align-items: center; }
    .sidebar-nav a { justify-content: center; padding: 10px; width: 40px; }
    .sidebar-nav .nav-icon { display: block; }

    .user-chip { width: 40px; height: 40px; padding: 0; justify-content: center; }

    .sidebar-heading,
    .sidebar-save .nav-label,
    .sidebar-nav a .nav-label,
    .user-chip .user-meta {
        position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
        overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
    }

    .main { padding: 24px 20px 60px; }
}

/* — sidebar: bottom bar (640px) —
   The primary group survives at this width, flanked by two compact round
   controls: Save on the left, Account on the right. `.sidebar-settings` and
   `.user-chip` stay hidden — the REST of Settings (MyMind sync, API
   tokens, Extensions, What's public…) is still reached from the Account
   screen, which lists them directly for exactly this reason — but Account
   itself and Save were genuinely unreachable before Finding 2 of the 20 Aug
   2026 usability review, not just recessed: nothing on the bar linked to
   /settings/account (no sign-out, no token revoke, no path to the iPhone
   setup page), and `.sidebar-save` was flatly `display: none`, so a mobile
   *browser* visitor — not the share-sheet or app-shortcut paths — had no
   way to save at all.

   Both stay OUT of `.sidebar-nav-primary`'s own five-way split rather than
   becoming a 6th/7th equal slot: seven abstract glyphs in one row reads as
   worse than five, and Save already has a settled compact form — the round
   icon-only button the 900px icon rail draws above. Re-showing it here and
   adding a matching `.sidebar-account-mobile` control either side of the
   primary group keeps every existing tap target the width it already had.
   Verify with a 375px screenshot: nothing overlapping, every control at
   least 24x24 (WCAG 2.5.8) — both compact controls are 40x40. */
@media (max-width: 640px) {
    .sidebar {
        position: fixed;
        inset: auto 0 0 0;
        width: 100%;
        height: auto;
        flex-direction: row;
        align-items: center;
        gap: var(--space-2);
        padding: 6px 4px;
        border-right: none;
        border-top: 1px solid var(--color-divider);
        z-index: 20;
    }
    .sidebar .brand,
    .sidebar-settings,
    .user-chip { display: none; }

    /* Un-hides the 900px icon-rail's own round-button rule (width/height
       40px, border-radius:50%, icon-only) rather than restating it — this
       block only needs to bring it back and drop the margin the full-width
       desktop version carries. */
    .sidebar-save {
        display: flex;
        flex: none;
        margin-bottom: 0;
    }

    .sidebar-nav-primary {
        flex-direction: row;
        flex: 1;
        min-width: 0;
        margin-bottom: 0;
        justify-content: space-around;
    }
    .sidebar-nav-primary a { width: auto; flex: 1; }

    /* New control, not a collapsed desktop one — see the file-header
       comment on the <a> in app.blade.php for why it is a second element
       rather than the existing Account link. display:none outside this
       block is set once, above (with .wordmark-short and the other
       collapse-only elements), so nothing here re-hides it at wider
       widths. */
    .sidebar-account-mobile {
        display: flex;
        flex: none;
        align-items: center;
        justify-content: center;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        color: var(--color-neutral-700);
    }
    .sidebar-account-mobile .nav-icon { display: block; width: 20px; height: 20px; }
    .sidebar-account-mobile:hover,
    .sidebar-account-mobile.is-active {
        background: color-mix(in srgb, var(--color-text) 6%, transparent);
        color: var(--color-text);
    }

    .main { padding: 20px 16px 84px; }
}

/* Organic's own h1 is 42px, tuned for a marketing-style hero. Every screen
   in this app uses <h1> as a compact page title (Account, Tags, Trash …),
   so it is set back down here — a page-density decision, not a system
   override. */
h1 { font-size: 26px; letter-spacing: -0.02em; }

/* — the prose link colour (Task 8's contrast sweep) —
   MEASURED FAILURE IN THE PACKAGE: organic.css gives every <a> the base
   --color-accent, which is 3.03:1 on --color-bg and 3.30:1 on
   --color-neutral-100. Both fail AA outright, and this is the colour of
   every ordinary link the app renders — "Show all 8 tags", "Back to the
   board", the links inside a panel of prose. --color-accent-700 is 5.72:1
   on the ground and 6.22:1 on the panel, and still reads as the accent:
   it is the ramp step .error and the duplicate-suggestion link already use.

   Same specificity as organic's own `a` (0,0,1), and this file loads after
   it, so this wins. It reaches ONLY links that state no colour of their
   own — every navigational or filled control in the app (.btn, .card,
   .itemrow, .tagrow, .jumpto-chip, .sidebar-nav a, .itemview-*) sets its
   own at class specificity and is untouched.

   organic.css is read-only and unchanged; this is the app layer retuning
   it, which is the rule this whole redesign runs on. */
a { color: var(--color-accent-700); }

/* The wordmark's dot is EXEMPT and stays --color-accent (3.03:1 on the
   ground). WCAG 1.4.3 exempts text that is part of a logotype, the handoff
   requires the dot "always --color-accent", and it carries no information —
   the word beside it is --color-text at 13.95:1. Measured and left alone
   deliberately, so nobody re-finds it and "fixes" the brand mark. */

/* — wordmark — */
/* Lowercase, live text, per the handoff: "athenana", weight 600, the dot
   always present and always --color-accent. Never Caprasimo, never Title
   Case, never a recoloured or omitted dot. */
.athenana-logo {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 22px;
    letter-spacing: -0.045em;
    line-height: 1;
    color: var(--color-text);
}
.athenana-logo .dot { color: var(--color-accent); }

/* — transitional: buttons —
   STILL NEEDED BY, and therefore not removed by Task 7b or 7c:
   board.blade.php (the Load more button), tags/index.blade.php,
   settings/tags.blade.php and settings/tags-merge.blade.php (Task 7c),
   items/show.blade.php and items/_saveform.blade.php (Task 7a), plus every
   one of Task 7b's nine views. Task 7b's own nine views keep
   class="btn"/"btn secondary" for the same reason: while this block exists,
   adopting organic's modifier names is actively WRONG rather than merely
   redundant. `class="btn btn-secondary"` would take border-color from
   .btn-secondary and then have it overwritten by this block's
   `border: 1px solid transparent` shorthand — same specificity, later in the
   cascade — leaving an outlined button rendered as a filled primary one. The
   block goes when the last consumer does, and every view converts in the
   same commit.

   organic.css's own .btn/.btn-primary/.btn-secondary exist; this app's
   views still emit class="btn" / class="btn secondary" rather than the
   modifier-class pair, so it is mirrored here under the classes actually
   in use.

   font-family is a deliberate departure from organic.css's own .btn,
   which sets --font-heading (Caprasimo). The handoff is unambiguous —
   "Figtree 600 on every button, label, tab and small caption ... Caprasimo
   at interface sizes reads as noise" — and organic.css cannot be edited,
   so the override lives here, once, for every element still using the
   bare class="btn"/"btn secondary" markup this block targets. Fixing it
   here fixes every one of them at once; do not re-add a per-button
   override instead — that is what let this slip through once already
   (.sidebar-save had its own self-contained font-family before this rule
   was corrected, because composing it with .btn/.btn-primary inherited
   Caprasimo from exactly this collision). */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    cursor: pointer;
    text-decoration: none;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
    line-height: 1.2;
    /* TASK 8, MEASURED. Was --color-bg on --color-accent: 3.03:1, which is
       every filled button in the app — Save, Save key, Create, Download
       OPML, Delete my account. --color-text on the same fill is 4.60:1.
       Tasks 6, 7a and 7c each measured this pair and chose --color-text for
       the fills they introduced rather than copy the failing one, leaving
       the app inconsistent on purpose so the sweep could land here in one
       commit. This is that commit. */
    color: var(--color-text);
    background: var(--color-accent);
    border: 1px solid transparent;
    padding: var(--space-2) calc(var(--space-3) * 1.2);
    border-radius: 999px;
}
/* HOVER GOES LIGHTER, WHICH LOOKS WRONG AND IS NOT. The system's own
   .btn-primary:hover is --color-accent-600, and --color-text on that is
   3.70:1 — so the conventional darker hover takes a compliant control BELOW
   AA the moment the pointer touches it, which is the worst possible time.
   accent-400 is 8.03:1 and accent-500 is 5.53:1. Task 7a reached the same
   conclusion independently for .itemview-open; these now match it. */
.btn:hover { background: var(--color-accent-400); }
.btn:active { background: var(--color-accent-500); }
.btn.secondary {
    background: transparent;
    color: var(--color-text);
    border-color: var(--color-divider);
}
.btn.secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn.secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }

/* — errors —
   Permanent, not transitional: the layout itself renders <p class="error">
   above @yield('content') on every screen, and the settings forms below use
   the same class for a per-field message. (.notice, which used to sit here,
   is gone — Task 7b converted its last five consumers, all of them its own,
   to the .panel/.authcard objects at the foot of this file.)

   No dedicated danger colour in the Organic palette (terracotta and sage
   are both accents, not a warning colour). The readme's own guidance for
   paragraph-size accent text is a deep ramp step, so error copy uses
   --color-accent-700 rather than the base accent. 5.72:1 on --color-bg,
   6.22:1 on --color-neutral-100. */
.error { color: var(--color-accent-700); margin-bottom: var(--space-3); }

/* The transitional bare `table` / `th, td` rules stood here until Task 7c.
   Their last consumer was settings/tags.blade.php, which no longer renders a
   table at all — the mockup's screen is a grid — so the block is gone rather
   than left with a note. Every table in the app now carries organic.css's
   .table inside the app's .panel, and the pair is dressed at the foot of this
   file. Nothing in the app emits a bare <th> or <td>; checked by grep. */

/* — form fields, permanent —
   organic.css styles .field > label and nothing else; the wrapper itself has
   no spacing of its own, so the stack rhythm lives here. This applies to both
   markups — the system's <div class="field"><label>…</label><input class="input">
   and the old <label class="field"><span> below — which is deliberate: they
   have to sit at the same rhythm while both exist. .saveform .field (0,2,0)
   still overrides it to 0. */
.field { margin-bottom: var(--space-3); }

/* — transitional: the old field markup —
   STILL NEEDED BY items/_saveform.blade.php and items/show.blade.php (Task
   7a). settings/tags.blade.php dropped off this list in Task 7c — it now uses
   the system's .input directly — so the two views left are both 7a's. Both
   emit
   <label class="field"><span>…</span><input></label> rather than the system's
   wrapper-plus-label. Qualified with `label` by Task 7b so it cannot reach the
   <div class="field"> its own screens now use — without that, `.field input`
   (0,1,1) outranks organic's `.input` (0,1,0) and the system's control would
   be silently overpainted by this one. Verified by grep that every remaining
   consumer is a <label class="field">; a <div class="field"> in one of them
   would lose its input styling with no error. */
label.field { display: block; }
/* Task 8: was -600, 3.61:1 on the ground. -700 is 5.53:1. The <small>
   inside inherits it, and at 10.8px it needed the fix more than the label
   did. */
label.field > span { display: block; font-size: 13px; color: var(--color-neutral-700); margin-bottom: 4px; }
label.field small { font-weight: 400; }
label.field input, label.field select, label.field textarea {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-sm);
    font: inherit;
    color: var(--color-text);
    background: var(--color-surface);
}

/* — board toolbar —
   The search box and the board/list switch have no equivalent in Organic
   (.seg/.seg-opt is radio-driven; this toggle is two plain buttons), so
   these stay app-specific rather than transitional. */
.toolbar { display: flex; gap: 10px; align-items: center; margin-bottom: var(--space-4); }
.search {
    flex: 1;
    padding: 10px 14px;
    border: 1px solid var(--color-divider);
    border-radius: 999px;
    font: inherit;
    color: var(--color-text);
    background: var(--color-surface);
}
.switch {
    display: flex;
    border: 1px solid var(--color-divider);
    border-radius: 999px;
    overflow: hidden;
    background: var(--color-surface);
}
.switch button {
    border: 0;
    background: transparent;
    /* Task 8: was -600, 3.21:1 on the switch's --color-surface track.
       -700 is 4.92:1. */
    color: var(--color-neutral-700);
    padding: 9px 12px;
    font: inherit;
    font-family: var(--font-body);
    font-size: 13px;
    cursor: pointer;
}
/* Task 8: the pressed state was --color-bg on --color-accent, 3.03:1 — one
   of the two pre-existing instances of that pair the earlier tasks named and
   left. --color-text is 4.60:1, and it now matches .jumpto-chip.is-current,
   which is the same idea (accent means "you are here") drawn twice. */
.switch button[aria-pressed="true"] { background: var(--color-accent); color: var(--color-text); }

/* — board — */
/* Masonry columns: the design system has no board/grid component, so this
   is app-specific and stays here for good. */
.board { column-count: 4; column-gap: var(--space-4); }
@media (max-width: 1000px) { .board { column-count: 3; } }
@media (max-width: 700px)  { .board { column-count: 2; } }
@media (max-width: 460px)  { .board { column-count: 1; } }

/* Card container: app-specific — this is the board's own item card (image
   bled to the edges + a padded kicker/title/body/tags stack), a masonry
   tile. organic.css's own .card is a flex column with padding on every
   side, which would inset the image along with the text; this container
   instead carries no padding of its own — .pad (below) scopes it to the
   text — so the thumbnail can bleed to the card's rounded corners exactly
   as the mockup shows. The *content* inside now comes from organic.css's
   card-kicker/card-title/card-body/.tag components, per Task 3; only the
   outer shape and the tag-row wrapper stay app-specific. */
.card {
    background: var(--color-surface);
    /* organic.css has its own .card component at the same specificity, and
       it sets padding: var(--space-3). Overriding display/border/radius here
       does not touch that, so without this reset every thumbnail sat 13.2px
       inside the card instead of bleeding to the rounded corner, and .pad
       stacked on top of it for double the intended text inset. Measured:
       image left edge 14.2px in from the card's. */
    padding: 0;
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    overflow: hidden;
    break-inside: avoid;
    margin-bottom: var(--space-4);
    display: inline-block;
    width: 100%;
    text-decoration: none;
    color: var(--color-text);
}
.card:hover { border-color: var(--color-neutral-500); box-shadow: var(--shadow-sm); }
.card .pad { padding: var(--space-3) var(--space-4); }

/* The item link, which now wraps the card's CONTENT rather than the card —
   the chips below it are links and an <a> cannot contain an <a>. It carries
   the text colour and the decoration reset that .card used to, because .card
   is an <article> now and those applied to the anchor, not the box. */
.card-open { display: block; text-decoration: none; color: inherit; }

/* The focus ring is drawn INSIDE the card, not outside it.
   The system's global rule is `:focus-visible { outline: 2px solid; outline-offset: 2px }`.
   .card-open sits 1px inside .card (the border), so a +2px offset puts the ring
   1px OUTSIDE the card's border box — and .card is `overflow: hidden`, which
   clips it. A clipped ring is not merely invisible: `overflow:hidden` makes the
   card a scroll container, so a browser trying to reveal the ring can scroll
   something to bring it into view, and the card appears to shift by a couple of
   pixels when it takes focus.
   organic.css uses exactly this remedy on `.seg-opt:has(input:focus-visible)`,
   so a negative offset is the system's own answer rather than an invention.
   The same reasoning does NOT apply to .itemrow-open: .itemrow does not clip. */
.card-open:focus-visible { outline-offset: -3px; }

/* The strip the chips sit on. Its top padding is zero because the content
   above already ends on .pad's bottom padding; before the split this was an
   inline style doing the same job. Qualified with .card so it outranks
   `.card .pad` above on specificity rather than on !important — the rule it
   needs to beat is one line up, not somewhere unknown. */
.card .card-foot { padding-top: 0; }

/* Thumbnails have three provenances (extension screenshot, server
   screenshot, scraped/supplied og:image) that differ wildly in aspect —
   a full-page capture can run many times taller than a 1200×630 social
   image. Capping height and cropping from the top keeps every provenance
   readable as a card thumbnail: the top of a page is its most identifying
   part, and a landscape og:image loses little from a top-anchored crop. */
.card img.washed { width: 100%; height: 220px; object-fit: cover; object-position: top; }

/* card-kicker/card-title/card-body come from organic.css; they were tuned
   for its own flex-gapped .card, so this stack needs its own spacing. */
/* Task 8: was -600, 3.21:1 on the card's --color-surface. -700 is 4.92:1.
   A 10px uppercase source name is small body copy, not decoration. */
.card .card-kicker { color: var(--color-neutral-700); margin-bottom: 4px; }

/* MEASURED, AND A HEX-ONLY AUDIT CANNOT SEE IT — organic's .card-meta is
   color-mix(in srgb, var(--color-text) 50%, transparent), which composites
   to #908884 on a highlight card and measures 3.17:1 (2.99:1 on a plain
   one). Same family as the .table th mix Task 7b caught at 4.25:1. It
   renders as a highlight's source line, which the development database has
   no example of — so it never appeared in the live walk and only the
   composited arithmetic found it. -700 is 6.19:1 on the highlight wash. */
.card .card-meta { color: var(--color-neutral-700); }
/* overflow-wrap: a title falling back to a bare canonical_url (no title,
   no spaces to break on) otherwise runs straight past the card's right
   edge instead of wrapping — seen live on a link saved with no metadata
   yet. */
.card .card-title { margin-bottom: 4px; overflow-wrap: anywhere; }
.card .card-body { margin: 0; }
.card .quote { font-style: italic; line-height: 1.45; }

/* Colour carries item type, and nothing else — the load-bearing rule of
   the redesign. A note is a solid sage card with light text; a highlight
   is a light terracotta wash with a terracotta kicker. Every other item
   type gets no tint at all: the plain neutral .card above. */
/* TASK 8, AND THIS ONE NOBODY HAD MEASURED. The note card is the only
   place in the app where light text sits on a solid tint, and on the BASE
   sage nothing on it passed: title and body --color-bg at 3.14:1, the
   kicker's sage-200 at 3.08:1, and the body worse still at 2.58:1 because
   organic's own .card-body carries opacity: 0.8, which a declared-value
   audit never sees. The whole card was under AA.

   The design intent is kept exactly — a SOLID sage card with light text is
   what the mockup draws and what "colour carries item type" means here —
   and the fix is one step down the same ramp. --color-accent-2-700 takes
   the cream to 5.43:1 and the kicker to 5.32:1. A deeper step of one hue is
   already how this palette works: the highlight card is accent-100 while
   the highlight MARKER is the base accent, so a type colour has never been
   one literal value. */
.card--note {
    background: var(--color-accent-2-700);
}
.card--note .card-kicker { color: var(--color-accent-2-200); }
.card--note .card-title,
.card--note .card-body { color: var(--color-bg); }
/* organic's .card-body is opacity: 0.8. On a light ground that is harmless
   (7.19:1 on a plain card); on a solid tint it is what took the note's body
   to 2.58:1. Full strength here, so the declared 5.43:1 is what renders. */
.card--note .card-body { opacity: 1; }
/* The chip was a wash of the card's own LIGHT text — which lightens the
   ground under light text, 2.26:1, the worst pair in the app. A wash of the
   card's INK instead keeps the ghost-chip shape and darkens it: 6.64:1. */
.card--note .tag {
    background: color-mix(in srgb, var(--color-text) 20%, transparent);
    color: var(--color-bg);
}

.card--highlight {
    background: var(--color-accent-100);
}
/* Task 8: was the base --color-accent on the accent-100 wash, 3.29:1.
   accent-700 is 6.21:1 and is the ramp step this app already uses whenever
   the accent has to carry small text. */
.card--highlight .card-kicker { color: var(--color-accent-700); }

/* Task 8: all three were -600 — 3.21:1 on a card, 3.61:1 on the ground.
   -700 is 4.92:1 and 5.53:1. */
.card .working { color: var(--color-neutral-700); font-size: 12px; margin-top: 8px; font-style: italic; }
p.working { color: var(--color-neutral-700); font-size: 13px; font-style: italic; }
.card .noteflag { color: var(--color-neutral-700); font-size: 12px; margin-top: 8px; }

/* — tag chips —
   organic.css's own .tag / .tag-accent / .tag-neutral supply the pill
   styling directly now (Task 3); this wrapper is just the flex row that
   lays them out and stays app-specific, since organic.css has no
   equivalent list component. Only the first tag carries an accent tint —
   a row of identically-coloured chips reads as noise, and this is a
   tag-list convenience rather than a type signal, so it deliberately
   never reaches for --color-accent-2 (sage), which .card--note owns. */
.card .tags { margin-top: 8px; display: flex; gap: 5px; flex-wrap: wrap; }

/* — tag chips as links —
   organic's .tag was written for a <span>, so an <a> wearing it arrives with
   the global link colour and underline. Reset both, then give it the states a
   link needs: the chip itself is the hit area, so hover has to read on the
   chip rather than on the text.

   Deliberately no colour change on hover, only ink: the tint is carrying
   first-tag-vs-rest on a card, and darkening it on hover would make a hovered
   neutral chip look like the accented one. */
a.tag { text-decoration: none; cursor: pointer; }
a.tag:hover { filter: brightness(0.94); }
/* No :focus-visible rule here on purpose. A chip is an <a>, so the system's
   own accent focus ring already applies — and an override gave it a different
   colour from the "Jump to" chips two lines above it, which is two focus rings
   in one screen for the same kind of control. Measured: rgb(32,30,29) against
   the system's rgb(198,113,57). */

/* The chip for the tag this board is already filtered to is a <span>, not a
   link — same treatment, no pointer, because there is nowhere for it to go. */
.tag[aria-current] { cursor: default; }

/* — list view —
   App-specific: organic.css has no list or row component of any kind, and
   nothing under .itemlist/.itemrow collides with a name it does define —
   checked by grep and enforced by a test (Feature/Board/ListViewTest), for
   the reason the .btn and .card notes above give.

   This replaces the old `.board.as-list` block, which reflowed the CARD
   partial into one column: a narrow board rather than a list. The row is now
   its own partial (items/_row.blade.php) with its own markup, so there is no
   image or description here to hide.

   The geometry is the point, so it is stated in absolutes rather than left
   to content: 36px lines, a 1px hairline between them, one line of text that
   elides. Measured live in Chrome at 1440x900 against the dev database: all
   14 rows 36.00px, divider 1px, row-to-row gap 0.00px, and a 239-character
   title still one line — the same row measures 44.39px with the three
   ellipsis declarations below removed, which is the two-line list this task
   exists to stop. */
/* The panel the rows sit in, from the list mockup in the design package
   (docs/design — search "Athenana App.dc.html" for `isList`; it is an
   interactive branch rather than an image, which is why it was missed once
   already). It is what makes the list read as one object instead of loose
   text on the page, and it is the reason the rows below can stay square:
   overflow:hidden clips them to this radius, so no hairline curves away
   from its own ends. Compatible with the compact row — the mockup's own
   84px thumbnail row is not, and spec section 6 overrides it. */
/* Two consumers, one declaration — generalised deliberately rather than
   copied. The tag list (Task 6) wants exactly this panel, and the design
   package draws the two screens with identical markup: neutral-100 on a
   neutral-300 border, 28px, overflow hidden. Duplicating four properties
   under a second name is how two panels drift a pixel apart; grouping the
   selector is how they cannot. Deliberately NOT renamed to something generic
   like .panel — .itemlist is referenced by name in scroll.js's contract
   comments, the list-view test and Task 5's report, and a rename across those
   buys nothing this does not.
   Task 7b added .panel as a THIRD consumer rather than as a rename: the
   settings, admin and trash screens each carry a table or a block of text in
   exactly this object, and the same argument applies a third time. Nothing is
   renamed, so scroll.js's contract and the list-view test are untouched. */
.itemlist,
.taglist,
.panel {
    background: var(--color-neutral-100);
    border: 1px solid var(--color-neutral-300);
    border-radius: 28px;
    overflow: hidden;
}

.itemrow {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 36px;
    padding: 0 var(--space-4);
    /* A gap between rows would make each one an object; a hairline makes
       them a list. 8% ink is what organic.css's own .table td uses between
       dense rows — --color-divider (16%) is a component edge and reads as a
       rule at this repetition. */
    border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
    /* Deliberately square: the roundness belongs to .itemlist above, which
       clips these to it. A radius per row would be drawn along the divider
       too, so every hairline would curve away from its own ends. */
    color: var(--color-text);
    text-decoration: none;
}
.itemrow:last-child { border-bottom: 1px solid transparent; }
/* Same 4% tint organic.css gives .table tbody tr:hover, for the same job. */
.itemrow:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }

/* The item link, covering everything left of the tag column. It carries the
   row's own flex layout now, because the row became a container when the
   chips became links — an <a> cannot hold an <a>. flex:1 makes it claim the
   whole line, so the click target is the row rather than just its text, and
   min-width:0 is what still lets the title elide inside it. */
.itemrow-open {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    color: inherit;
}

/* Type colour, and nothing else — a marker rather than a fill. Reserved on
   every row whatever its type, so all fifty titles start at the same x and
   the coloured ones read as a column rather than as ragged indentation. */
.itemrow-mark {
    flex: none;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: transparent;
}
.itemrow--note .itemrow-mark { background: var(--color-accent-2); }
.itemrow--highlight .itemrow-mark { background: var(--color-accent); }

/* Figtree 600, not Caprasimo: this is a card title by role but an interface
   size in fact, and the handoff is explicit that Caprasimo at interface
   sizes reads as noise.

   Still flex-grow:0 — a title with room to spare stays at its own content
   width rather than stretching into space the meta column would otherwise
   use, exactly as before. What changed is flex-shrink stays 1 (unchanged)
   but min-width is now a 10ch floor instead of 0: the title is the one
   thing a reader scans, so under real pressure it must be the LAST thing
   to give up room, not the first. (An earlier version of this fix also
   set flex-grow:1, which fixed the squeeze but broke the common case —
   title greedily ate space `.itemrow-meta` needed even on untagged rows,
   truncating dates that used to show in full. flex-grow:0 is what keeps
   this fix scoped to the actual defect.) Mobile list view at 375px: this
   is Finding 1 of the 20 Aug 2026 usability review — the title used to
   compute to 0px width while `.itemrow-meta` (flex-shrink:0, could not
   shrink at all) and `.itemrow-tags` (200px min-width, dropped below
   1000px but still flex:none) took everything. */
.itemrow-title {
    flex: 0 1 auto;
    min-width: 10ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
}

/* --color-neutral-700 rather than the -600 used for muted text elsewhere:
   measured 5.53:1 on --color-bg against -600's 3.61:1, and this is 12px
   body copy, so 4.5:1 is the floor.

   .itemrow-note and .itemrow-working stay flex:none — a fixed-width icon
   glyph and an italic status word, neither of which should ever give up
   room to the title. .itemrow-meta gets its own rule below: it is the part
   that must shrink before the title does. */
.itemrow-meta,
.itemrow-note,
.itemrow-working {
    font-size: 12px;
    color: var(--color-neutral-700);
}
.itemrow-note,
.itemrow-working {
    flex: none;
}
/* 38ch is still the CEILING, not a reservation — it bounds how wide the meta
   slot is allowed to get so "southernliving.com · saved 3 days ago" (37)
   doesn't push the title out on a wide row, same as always. What is new is
   flex: 0 20 auto plus a 3ch min-width: meta now actually gives up width
   before the title does, instead of holding its full size (flex:none) and
   overflowing into `.itemrow-tags` the way Finding 1 measured (204px meta
   on a 158px openRect, painted over the tag column on a 375px row).

   20, not 1:1 with the title's shrink factor, because a plain 4:1 still let
   a THIN deficit (e.g. an untagged row where the row only overflows by
   ~30px) clip a couple of characters off an otherwise-fitting title before
   meta gave up anything — weighted shrink is proportional to
   factor×basis, and meta's basis (the whole "site · saved when" string) is
   usually not that much bigger than the title's, so a small factor left
   title paying a visible share of even a small deficit. 20 pushes
   virtually all of a small deficit onto meta, so an untagged or lightly-
   squeezed row keeps its title intact; the title still has an unconditional
   floor below, so on a genuinely tag-crowded row meta still yields
   everything down to its own 3ch floor before the title has to give up
   anything past that floor.

   3ch, not 0: meta shrinking to nothing on the worst rows still reads as
   "the row lost its metadata line", not as an overlap — the title/tags
   collision is gone either way — but a bare 0 renders as if the slot were
   never there at all. 3ch keeps one truncated character plus the ellipsis
   visible so the row still signals "there was more here", consistent with
   every other truncation in this block. The date is still the part that
   survives whenever there IS room (untouched: the ceiling, the ellipsis
   direction and the string order — site name then date — are all
   unchanged), and continues to be what an aggressively squeezed row loses
   first, same as it always was past the 38ch ceiling. */
.itemrow-meta {
    flex: 0 20 auto;
    min-width: 3ch;
    max-width: 38ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.itemrow-working { font-style: italic; }

/* The tag column. min-width holds it open on rows with no tags at all —
   without it the chips on the rows above and below stop lining up, which is
   the whole value of a right-hand column. */
.itemrow-tags {
    flex: none;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 4px;
    margin-left: auto;
    min-width: 200px;
    max-width: 45%;
    overflow: hidden;
}
/* Below the board's own column breakpoint the reservation stops paying for
   itself: measured at a 600px viewport it held 200px of a 568px row — a
   third of the line — open on rows with no tags, while their titles elided
   with that space sitting empty beside them. A column you cannot follow
   because only two chips fit is not worth a third of every title. */
@media (max-width: 1000px) {
    .itemrow-tags { min-width: 0; }
}

/* Finding 1's third symptom, found while verifying the title/meta fix
   above on the seeded nytimes row (3 tags): even with meta no longer
   overlapping this column, three chips at 11px still do not fit inside
   45% of a 375px row (they measure ~172px combined; the cap is ~138px).
   `.itemrow-tags` is flex:none/overflow:hidden, so it does not shrink its
   CHILDREN to fit — it clips the rendered box at a pixel boundary instead,
   and because `justify-content:flex-end` anchors the chips to the right,
   the clip eats into the FIRST chip from the left, mid-glyph
   ("wellness" rendered as a stray fragment) rather than eliding a whole
   chip cleanly. Each `.tag` already ellipses its OWN text past 16ch; nothing
   made the column itself drop a whole chip when its total no longer fit.
   Guidance for Finding 1 explicitly allows "tags reduced to the first
   chip" below ~640px, so below that width every chip but the first is
   dropped outright — a single chip (capped at 16ch by the rule below) is
   comfortably inside the 45% ceiling, so this is not a narrower patch of
   the same clip, it removes the condition that causes it. The "+N" spare
   span for a 4th-and-beyond tag is a sibling of the same `.tag` class and
   is dropped by the same rule, which is correct: it is exactly as much
   "another chip" as chip 2 and 3 are. */
@media (max-width: 640px) {
    /* .itemrow-tags > .tag, not the bare :nth-child(n+2) this started as:
       specificity (0,2,0) tied with `.itemrow-tags .tag { display: block }`
       below and lost to it on source order, so every chip past the first
       kept rendering. The extra .tag class breaks the tie. */
    .itemrow-tags > .tag:nth-child(n+2) { display: none; }
}

/* organic.css's .tag is inline-flex, on which text-overflow does nothing;
   block restores the ellipsis and keeps its padding, radius and 11px size.
   A 60-character tag exists in this database and would otherwise push the
   title out of the row. */
.itemrow-tags .tag {
    display: block;
    flex: none;
    max-width: 16ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* — infinite scroll foot —
   App-specific; organic.css defines nothing under .scrollfoot (checked by
   grep and enforced by a test, same as the row's classes). Sits outside the
   item container in both views, so this block is the same shape whether the
   thing above it is a masonry board or a list panel. */
.scrollfoot { text-align: center; }

/* The IntersectionObserver's target, and nothing else — no height of its own
   to add a gap the reader would see, and out of the accessibility tree. */
.scrollfoot-sentinel { height: 1px; }

/* Quiet, and deliberately so: the terminus is meant to answer "is it still
   loading?" and then be forgotten, not to be a piece of furniture at the
   bottom of every session. --color-neutral-700 rather than the -600 used for
   muted text elsewhere: measured 5.53:1 on --color-bg against -600's 3.61:1,
   and this is 13px body copy, so 4.5:1 is the floor. (Same finding the list
   row carries; -600 is Task 8's to fix globally.) */
.scrollfoot-status,
.scrollfoot-end {
    margin: 0;
    padding: 28px 0;
    font-size: 13px;
    color: var(--color-neutral-700);
}

.scrollfoot-more { margin: 24px 0; }

/* The numbered paginator, which is what everyone without JavaScript gets and
   is NOT hidden here — scroll.js sets .hidden on it once it has taken over.
   This block only spaces it. */
.scrollfoot-pages { padding: 24px 0; }

/* — the numbered paginator —
   Styling for resources/views/vendor/pagination/athenana.blade.php, which
   replaces Laravel's Tailwind default (see that file for why). App-specific:
   organic.css has no pagination component and defines none of these names.
   Shared by the board and the trash screen. */
.pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 6px;
}
.pager-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    padding: 7px 13px;
    border: 1px solid var(--color-divider);
    border-radius: 999px;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 13px;
    /* -700, not the -600 used for muted text elsewhere: 13px is body copy and
       -600 measures 3.61:1 on --color-bg, which fails AA. */
    color: var(--color-neutral-700);
    text-decoration: none;
}
.pager-link:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.pager-link.is-current {
    background: var(--color-accent);
    border-color: var(--color-accent);
    /* Task 8: was --color-bg, 3.03:1 — the older of the two pre-existing
       instances of that pair, named by Task 6 and left for this sweep.
       --color-text is 4.60:1. The comment that stood here reasoned that the
       cream ground was right because the switch already used it; the switch
       was wrong too, and both are fixed together. */
    color: var(--color-text);
}
/* A dead control still occupies the row, so it reads as a boundary rather than
   as something that vanished. Not `display:none`: "Previous" disappearing on
   page 1 moves every other control sideways between pages. */
.pager-link.is-disabled { opacity: 0.4; }
.pager-gap { color: var(--color-neutral-700); padding: 0 2px; }

/* Task 8: was -600 — 3.61:1 on the ground and 3.92:1 inside a panel. This
   is the one rule the earlier tasks kept naming and could not fix, because
   it is shared by the board's no-results line, the tag browser, the
   duplicate suggestions and both empty panels. -700 is 5.53:1 / 6.02:1. */
.empty { color: var(--color-neutral-700); padding: 40px 0; }

/* — tag navigation —
   App-specific. organic.css defines nothing under .jumpto/.taglist/.tagrow/
   .boardhead/.tagbrowse — checked by grep and pinned by a test in
   Feature/Tags/TagNavigationTest, for the reason the .btn, .card and [hidden]
   notes above give: a shared name at the same specificity inherits whatever
   this file does not restate, and every instance of that has shipped green. */

/* The flash line above the board. Was style="color:var(--mute)" — a Pediment
   token that has not existed since Task 1, so it resolved to initial and the
   message rendered in body colour. -700 rather than -600: 14px is body copy
   and -600 measures 3.61:1 on --color-bg. 5.53:1.
   .flash is the same line on the settings, admin and trash screens, which
   carried the same dead token (Task 7b). One declaration rather than two: it
   is one sentence in one place on the page, and two rules for it is how the
   board's flash and everyone else's drift apart. .boardstatus keeps its own
   name for the same reason .itemlist did — it is referenced by name elsewhere. */
.boardstatus,
.flash { color: var(--color-neutral-700); font-size: 14px; margin-bottom: var(--space-3); }

/* — the filtered board's heading —
   Only rendered at /t/{slug}. The unfiltered board deliberately has no <h1>:
   it is the app's home and a heading reading "Board" over the board is a
   label for something nobody mistook. */
.boardhead {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: var(--space-4);
}
/* Caprasimo, from the global h1 rule — this IS a page heading, which is the
   one place the handoff wants it. */
.boardhead-title { margin: 0; overflow-wrap: anywhere; }
.boardhead-count,
.boardhead-back { font-size: 13px; color: var(--color-neutral-700); }
.boardhead-back { margin-left: auto; }

/* — the "Jump to" row —
   From the design package's isBoard branch. Sits above the items and below
   the toolbar, in both board and list view: it selects WHICH items are shown,
   which is orthogonal to how they are laid out. */
.jumpto {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-bottom: var(--space-6);
}
.jumpto-label {
    font-size: 13px;
    /* -700, not the -600 the mockup uses: 13px is body copy and -600 measures
       3.61:1 on --color-bg. -700 is 5.53:1. */
    color: var(--color-neutral-700);
    margin-right: 4px;
}

/* Every chip neutral. The mockup tints them terracotta / sage / neutral with
   nothing in the data to key off, and the Global Constraint is that colour
   carries item TYPE and nothing else — a tag is not a type, and a sage chip
   in particular would read as the "this is a note" signal .card--note owns.
   Consistent with the list row, which is all-neutral for the same reason.
   Measured 8.12:1 (neutral-800 on neutral-200). */
.jumpto-chip {
    font-size: 13px;
    padding: 6px 14px;
    border-radius: 999px;
    background: var(--color-neutral-200);
    color: var(--color-neutral-800);
    text-decoration: none;
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.jumpto-chip:hover { background: var(--color-neutral-300); }

/* The one chip that carries the accent is the tag you are looking at. That is
   a STATE, which is the one job colour has here besides item type, and it is
   the same accent-on-current the paginator and the layout switch already use.
   --color-text on --color-accent, not --color-bg: measured 4.60:1 against
   3.03:1, and 13px is body copy. (Task 8 has since brought the two older
   instances — .pager-link.is-current and the board/list switch — onto this
   same pair, so the app now draws "you are here" one way.) */
.jumpto-chip.is-current {
    background: var(--color-accent);
    color: var(--color-text);
    font-weight: 600;
}
/* Task 8: the hover was accent-600, which drops this chip from 4.60:1 to
   3.70:1 — a compliant control failing the moment you point at it. Lighter,
   like every other accent hover in the app now: accent-400 is 8.03:1. */
.jumpto-chip.is-current:hover { background: var(--color-accent-400); }

/* Random is the one chip that does not filter this board — it leaves for a
   single item — so it is drawn as an outline rather than a fill. Outline and
   not a colour: the Global Constraint gives colour to item TYPE, and the
   accent is spoken for by .is-current above, which Random can never be.
   inset box-shadow rather than a border, so the 1px does not make this chip a
   pixel taller than the filled ones sitting beside it. */
.jumpto-random {
    background: transparent;
    box-shadow: inset 0 0 0 1px var(--color-neutral-300);
}
.jumpto-random:hover { background: var(--color-neutral-200); }

.jumpto-all {
    font-size: 13px;
    color: var(--color-neutral-700);
    padding: 6px 4px;
}

/* Fewer chips on a narrow window. Measured at 375px with import-scale tag
   names: twelve chips wrapped to FIVE rows and stood 233px tall, so a quarter
   of a phone screen was navigation before a single saved item appeared.
   nth-of-type counts by ELEMENT, not by class, and the row opens with two
   chips that are not tags — "Recent" at index 1 and "Random" at index 2 — so
   every tag chip sits two places along. The "all N tags" link is counted too
   but carries a different class, so the selector never matches it and it
   always survives — deliberately, since it is the way to the ones that just
   went.

   The thresholds did NOT move when Random arrived on 19 August 2026, and that
   is the decision rather than an oversight. They cap the row at seven chips
   and five; what changed is that one of those slots is now Random instead of a
   tag. Holding the CHIP count is what holds the measured height — the
   thresholds were derived from rows of chips, not from a count of tags — so
   raising them to n+9/n+7 to keep six tags and four would have given back the
   row this query exists to prevent.

   Done in CSS rather than by sending fewer chips because the same document is
   what the scroll script fetches, and a viewport is not a server-side fact. */
@media (max-width: 700px) {
    .jumpto-chip:nth-of-type(n+8) { display: none; }
}
@media (max-width: 460px) {
    .jumpto-chip:nth-of-type(n+6) { display: none; }
}

/* — the tag browse screen — */
.tagbrowse { max-width: 900px; }
.tagbrowse-lede {
    max-width: 560px;
    font-size: 16px;
    line-height: 1.55;
    color: var(--color-neutral-700);
    margin: 10px 0 var(--space-6);
}
.tagbrowse-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
    margin-bottom: var(--space-4);
}
/* Holds the filter input at the mockup's width instead of letting .search's
   flex:1 stretch it across a 900px panel. */
.tagbrowse-filter { flex: 1; max-width: 340px; display: flex; }
.tagbrowse-summary { font-size: 13px; color: var(--color-neutral-700); }

/* The rows. Same grid as the design package's tag screen with its third
   column — Rename / Delete / Merge — removed: those are /settings/tags, a
   different screen with a different job. What is left is a name and a number,
   and the whole row is the link. */
.taglist-head,
.tagrow {
    display: grid;
    grid-template-columns: 1fr 90px;
    gap: 16px;
    align-items: center;
    padding: 14px 24px;
}
.taglist-head {
    font-size: 12px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    /* -700 rather than the mockup's -600: 12px uppercase is body copy and
       -600 is 3.61:1. */
    color: var(--color-neutral-700);
}
.tagrow {
    border-top: 1px solid var(--color-neutral-200);
    color: var(--color-text);
    text-decoration: none;
}
/* Same 4% tint the list row and organic's own table row use. */
.tagrow:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }

/* Figtree 600 — a tag name is interface text at an interface size, and
   Caprasimo here would be the noise the handoff warns about. A 60-character
   tag exists in this database, hence the elision. */
.tagrow-name {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 15px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tagrow-count { color: var(--color-neutral-700); font-variant-numeric: tabular-nums; }

@media (max-width: 640px) {
    .taglist-head,
    .tagrow { padding: 14px 18px; }
}

/* ————————————————————————————————————————————————————————————————
   Task 7a — the item detail view, the save modal and the empty state.

   Every class below is a new name: none of .itemview-*, .emptystate-*,
   .saveform-*, .savedialog-* or .savepage-* is defined in organic.css
   (checked by grep, pinned by a test in Feature/Items/ItemDetailTest). What
   IS reused from the system is reused whole and named where it is overridden
   — .dialog, .dialog-title, .seg/.seg-opt, .tag/.tag-neutral, .washed.

   Contrast is measured against --color-bg #f5ead8, --color-neutral-100
   #f9f4ed or the stated ground, never assumed. The mockup's own muted text is
   --color-neutral-500, which measures 2.42:1 — so it is not used anywhere
   here. --color-neutral-600, which WAS the app's standard muted colour, is
   3.61:1 and also fails; -700 is 5.53:1 on the ground and 6.02:1 on
   neutral-100, and is what every muted string below uses. (Task 8 swept -600
   out of the whole file; a test now keeps it out.)
   ———————————————————————————————————————————————————————————————— */

/* — item detail — */
.itemview {
    max-width: 760px;
    display: flex;
    flex-direction: column;
    gap: 24px;
}

/* The outlined pill back to the board. --color-neutral-700 on the ground,
   5.53:1; the mockup's own choice, and the one muted value it gets right. */
.itemview-back {
    align-self: flex-start;
    padding: 6px 16px;
    border: 1px solid var(--color-neutral-300);
    border-radius: 999px;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 13px;
    color: var(--color-neutral-700);
    text-decoration: none;
}
.itemview-back:hover { background: var(--color-surface); }

.itemview-flash {
    margin: 0;
    font-size: 14px;
    color: var(--color-neutral-700);
}

.itemview-head {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

/* "nytimes.com · saved 2 days ago" — the same sentence the list row builds,
   from the same two fields, so the two screens cannot word one fact twice.
   -700, not the mockup's -500 (2.42:1) or the app's -600 (3.61:1): this is
   12px, which is body copy however short it is. */
.itemview-kicker {
    margin: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    font-family: var(--font-body);
    font-weight: 600;
    color: var(--color-neutral-700);
}
/* The same 6px type marker the list row uses, and the same rule: sage for a
   note, terracotta for a highlight, nothing at all for any other type. Space
   is NOT reserved on the types that have no marker — unlike the row, where
   fifty titles have to start at one x, this is a single line on its own
   page. */
.itemview-mark {
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    display: none;
}
.itemview-kicker--note .itemview-mark {
    display: block;
    background: var(--color-accent-2);
}
.itemview-kicker--highlight .itemview-mark {
    display: block;
    background: var(--color-accent);
}

/* Caprasimo at 42px — a page heading, which is the one place the handoff
   wants it. Overrides the app's own 26px h1, which is tuned for the compact
   titles on Account/Tags/Trash rather than for a reader view. */
.itemview-title {
    margin: 0;
    font-size: 42px;
    line-height: 1.12;
    letter-spacing: -0.015em;
    text-wrap: pretty;
    /* A title falling back to a bare canonical_url has no spaces to break on
       and would otherwise run past 760px — the same guard the card carries. */
    overflow-wrap: anywhere;
}

.itemview-standfirst {
    margin: 0;
    font-size: 18px;
    line-height: 1.5;
    color: var(--color-neutral-700);
}

.itemview-actions {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    align-items: center;
}
/* The trash action is a POST form, so it has to not introduce a line of its
   own between the buttons beside it. */
.itemview-actions form { margin: 0; display: contents; }

/* The accent fill.
   --color-text on --color-accent measures 4.60:1; the mockup's
   --color-accent-100 on it is 3.29:1 and --color-bg — which .btn and
   .sidebar-save already use — is 3.03:1. Both fail AA at 14px. This is the
   same choice Task 6 made for the current tag chip, and for the same reason:
   a NEW accent-on-something pair is measured and made to pass, while the two
   existing 3.03:1 pairs are Task 8's audit to fix everywhere at once rather
   than mine to fix in one place. Deliberately not class="btn": that would
   inherit --color-bg from the transitional block above. */
.itemview-open {
    display: inline-flex;
    align-items: center;
    padding: 10px 20px;
    border: 1px solid transparent;
    border-radius: 999px;
    background: var(--color-accent);
    color: var(--color-text);
    text-decoration: none;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
    /* Must match .itemview-action's below. Without it this <a> inherits the
       body's 1.55 while the <button> beside it resolves its own normal, and
       the two pills sat at 43.70px and 38.80px in the same row — measured, not
       noticed. */
    line-height: 1.2;
}
.itemview-open:hover { background: var(--color-accent-400); }
.itemview-open:active { background: var(--color-accent-500); }

/* The outlined pair. One rule for the <a> and the <button> so the two cannot
   drift a pixel apart — they sit side by side. */
.itemview-action {
    display: inline-flex;
    align-items: center;
    padding: 10px 20px;
    border: 1px solid var(--color-neutral-300);
    border-radius: 999px;
    background: transparent;
    color: var(--color-text);
    text-decoration: none;
    cursor: pointer;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
    line-height: 1.2;
}
.itemview-action:hover { background: var(--color-surface); }
.itemview-action:active { background: var(--color-neutral-200); }

/* 320px and 28px from the mockup. Cropped from the top because a full-page
   screenshot runs many times taller than a 1200x630 social image, and the top
   of a page is its most identifying part. That reasoning is about
   screenshots specifically — it does not hold for a photo the user
   deliberately saved, which .itemview-image--photo below overrides for. */
.itemview-image {
    width: 100%;
    height: 320px;
    object-fit: cover;
    object-position: top;
    border-radius: 28px;
}

/* An image item (a saved photo, not a screenshot of a page) keeps the 28px
   radius but drops the crop: sized to its own aspect ratio rather than
   stretched to fill a fixed box, capped at 70vh so a tall portrait still
   leaves the title, tags and action row reachable without scrolling past a
   screen-filling image. Same specificity as .itemview-image above (one
   class), so it must come after in source order to win. */
.itemview-image--photo {
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 70vh;
    object-fit: contain;
}

.itemview-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}
/* .tag/.tag-neutral come from organic.css whole — 11px, neutral-100 on
   neutral-800, 9.12:1. Only the size is lifted to the mockup's 12px and the
   chip made to read as the link it is. */
.itemview-tag {
    font-size: 12px;
    padding: 4px 13px;
    text-decoration: none;
}
.itemview-tag:hover { background: var(--color-neutral-200); }

.itemview-working {
    margin: 0;
    font-size: 13px;
    font-style: italic;
    color: var(--color-neutral-700);
}

/* Type colour, on the item's own content, on an item of that type — the same
   signal .card--highlight and .card--note carry on the board. This is the one
   thing on the page that is coloured, and it is coloured for the only reason
   colour is allowed to be. --color-text on --color-accent-100 is 15.14:1;
   --color-accent-2-900 on --color-accent-2-100 is 13.04:1. */
.itemview-quote {
    margin: 0;
    padding: 24px;
    border-radius: 28px;
    background: var(--color-accent-100);
    color: var(--color-text);
    font-size: 20px;
    line-height: 1.45;
    font-style: italic;
}
.itemview-notebody {
    padding: 24px;
    border-radius: 28px;
    background: var(--color-accent-2-100);
    color: var(--color-accent-2-900);
    font-size: 17px;
    line-height: 1.6;
}

/* The neutral panel — "Your note", and the enrichment-failure notice.
   The mockup draws the note panel in SAGE. That is refused: sage means "this
   item is a note", and a sage panel on a saved article would teach the reader
   that the type signal is not a type signal. Neutral-100 on neutral-300 at
   28px is the app's established panel — .itemlist, .taglist and the empty
   state's cards are all exactly this. -700 kicker on neutral-100 is 6.02:1;
   body --color-text is 15.17:1. */
.itemview-panel {
    background: var(--color-neutral-100);
    border: 1px solid var(--color-neutral-300);
    border-radius: 28px;
    padding: 24px;
}
.itemview-panel-kicker {
    margin: 0 0 8px;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 12px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-neutral-700);
}
.itemview-panel-body {
    margin: 0;
    font-size: 16px;
    line-height: 1.5;
    color: var(--color-text);
}
.itemview-errors {
    margin: 10px 0 16px;
    padding-left: 20px;
    font-size: 13px;
    color: var(--color-neutral-700);
}

/* The image metadata panel's field list. No bare `dl`/`dt`/`dd` rule exists
   anywhere else in this app (organic.css only styles them inside .prose),
   so this resets the browser default margins rather than fighting one. */
.itemview-metalist {
    margin: 0;
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-2) var(--space-4);
}
.itemview-metalist dt {
    margin: 0;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
    color: var(--color-neutral-700);
}
.itemview-metalist dd {
    margin: 0;
    font-size: 14px;
    color: var(--color-text);
}

/* The demoted error line on an image item: one short row, not a panel and
   not a heading — see show.blade.php. `form` reset to margin:0 so it sits
   beside the line rather than adding the .itemview flex gap a second time. */
.itemview-errornote {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}
.itemview-errornote p {
    margin: 0;
    font-size: 14px;
    color: var(--color-neutral-700);
}
.itemview-errornote form {
    margin: 0;
}

.itemview-article {
    border-top: 1px solid var(--color-divider);
    padding-top: 24px;
}
/* Caprasimo 20px — a section heading by role even though it is a <summary>. */
.itemview-article-summary {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    font-size: 20px;
    cursor: pointer;
}
.itemview-article-body {
    margin-top: 14px;
    font-size: 17px;
    line-height: 1.65;
    /* 11.85:1. The one place on the page with a page's worth of prose. */
    color: var(--color-neutral-900);
}

.itemview-edit {
    border-top: 1px solid var(--color-divider);
    padding-top: 24px;
}
.itemview-edit-title {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    font-size: 20px;
    margin: 0 0 var(--space-3);
}

/* The SECOND heading in this section — "Tags", which follows the note form's
   Save button. margin-top is 0 above, which is right for the first heading
   and put "Tags" flush against the button when the tag editor landed beneath
   it. A rule as well as the space, because the two are separate things to do
   rather than one block that happens to continue.

   --space-6, NOT --space-5: THERE IS NO --space-5. organic's scale is
   1,2,3,4,6,8, and an undefined custom property makes the whole declaration
   invalid at computed-value time — margin-top falls back to 0 and the rule
   looks like it applied because its OTHER declarations did. That is exactly
   how this bug shipped: the border rendered, the space did not. */
.itemview-edit form + .itemview-edit-title {
    margin-top: var(--space-6);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-divider);
}

@media (max-width: 640px) {
    .itemview-title { font-size: 32px; }
    .itemview-image { height: 220px; }
    /* A saved photo is not re-cropped on a phone — the device most of them
       come from. Same specificity as the rule above (one class), so it has
       to come after in source order to win back the auto height. */
    .itemview-image--photo { height: auto; }
}

/* — the save form —
   Shared by /items/create and the modal, so it is styled once. Nothing here
   reaches outside .saveform: the transitional .field block is still what the
   settings screens use, and Tasks 7b/7c own those. */
.saveform {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.saveform .field { margin-bottom: 0; }
/* -700 on both grounds it renders against: 5.53:1 on --color-bg, 6.02:1 on
   the dialog's --color-neutral-100. The transitional block's -600 is 3.61:1
   and 3.92:1. */
.saveform .field > span { color: var(--color-neutral-700); }
/* Pill inputs, per the mockup — but not textareas: a 999px radius on a
   six-row box bows its own corners into the text. */
.saveform .field input { border-radius: 999px; padding: 11px 18px; }
.saveform .field textarea { border-radius: var(--radius-md); padding: 11px 16px; }

/* organic.css's .seg is a bordered track with hairline-divided options; the
   mockup's is a filled track with an inset pill, so the border and the
   dividers are removed rather than fought with. Figtree 600 throughout, per
   the handoff's "every button, label, tab". */
.saveform-type {
    align-self: flex-start;
    border: 0;
    padding: 3px;
    background: var(--color-surface);
}
.saveform-type .seg-opt {
    padding: 7px 18px;
    border-radius: 999px;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 14px;
    /* 4.92:1 on --color-surface. */
    color: var(--color-neutral-700);
}
/* Task 8, and only visible once composited: organic's own
   .seg-opt:not(:has(input:checked)):hover lays
   color-mix(in srgb, var(--color-text) 7%, transparent) over this track,
   which darkens the ground under a -700 label to 4.31:1 — an unselected
   option failing AA the moment you point at it. -800 on that same tinted
   ground is 5.90:1. Neither value is in either rule; the pair only exists
   at render time. */
.saveform-type .seg-opt:not(:has(input:checked)):hover { color: var(--color-neutral-800); }
.saveform-type .seg-opt + .seg-opt { border-left: 0; }
/* The selected option, overriding organic's accent fill. Not a preference:
   its pair is --color-bg on --color-accent, 3.03:1, and this is 14px. The
   mockup's inset neutral-100 pill is 15.17:1 and is what a segmented control
   looks like anyway. */
.saveform-type .seg-opt:has(input:checked) {
    background: var(--color-neutral-100);
    color: var(--color-text);
}

/* Show only the fields the chosen type uses — the job the create page needed
   JavaScript for. Written as "hide what does not apply" rather than "hide
   everything, then show what does": if :has() is unsupported the whole rule
   is dropped and every field shows, which is the old pre-JS behaviour. The
   other way round, an unsupported :has() would hide every field and leave a
   form with nothing in it. */
.saveform:has(.saveform-type input[value="link"]:checked) [data-for]:not([data-for~="link"]),
.saveform:has(.saveform-type input[value="note"]:checked) [data-for]:not([data-for~="note"]),
.saveform:has(.saveform-type input[value="highlight"]:checked) [data-for]:not([data-for~="highlight"]) {
    display: none;
}

.saveform-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: var(--space-2);
}

/* — the save page (/items/create) — */
.savepage { max-width: 560px; }
.savepage-title { margin: 0 0 var(--space-4); }

/* — the save modal —
   The <dialog> element carries positioning only. Deliberately NO display
   property: the UA's dialog:not([open]) { display: none } is what keeps it
   closed, and any author rule that sets display beats it — which is exactly
   how organic.css's .dialog (display: flex) would have pinned this modal open
   on every page. The system's .dialog dresses the panel INSIDE instead. */
.savedialog {
    max-width: 100%;
    max-height: 100%;
    margin: auto;
    padding: var(--space-4);
    border: 0;
    background: transparent;
    overflow: auto;
}
/* rgba(32,30,29,0.45) — the spec's value exactly, written through the token
   it is (--color-text is #201e1d = rgb(32,30,29)) rather than as a literal.
   organic's own .dialog-backdrop is neutral-900 at 50%, but that class styles
   a <div> overlay and a <dialog> has ::backdrop instead. */
.savedialog::backdrop {
    background: color-mix(in srgb, var(--color-text) 45%, transparent);
}
/* The page behind a modal must not scroll under it. CSS, not a JS class
   toggle, so it cannot be left behind if a close path is ever missed. */
html:has(.savedialog[open]) { overflow: hidden; }

/* What .dialog already gives this panel and is KEPT: display:flex,
   flex-direction:column, gap var(--space-3), box-shadow var(--shadow-lg)
   (which is 0 12px 32px #2e2b25 at 22% — the mockup's shadow exactly).
   What is overridden, and why: width (its min(440px,100%) against the spec's
   560px), padding (space-4 against the mockup's 32px), border-radius (the
   system's rounded-frame block makes .dialog 32.2px; spec section 8 says
   modals are 28px), and background (--color-surface against the mockup's
   --color-neutral-100, which is also what every other panel in this app
   uses). --color-text on it is 15.17:1. */
.savedialog-panel {
    width: 560px;
    max-width: 100%;
    padding: 32px;
    border-radius: 28px;
    background: var(--color-neutral-100);
}

.savedialog-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
}
/* .dialog-title is Caprasimo at 20px; the mockup's modal heading is 30px. */
.savedialog-title {
    margin: 0;
    font-size: 30px;
    line-height: 1.12;
    letter-spacing: -0.015em;
}
.savedialog-close {
    flex: none;
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 999px;
    background: var(--color-neutral-200);
    /* 5.36:1 on neutral-200. */
    color: var(--color-neutral-700);
    font: inherit;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
}
/* Task 8: the glyph is -700 and 5.36:1 at rest, but this hover darkens the
   ground to neutral-300 and takes it to 4.44:1 — a state pair that exists in
   neither rule on its own. -800 on neutral-300 is 6.73:1. */
.savedialog-close:hover { background: var(--color-neutral-300); color: var(--color-neutral-800); }

/* The validation message, inside the dialog rather than only in the layout's
   banner behind it — a reopened modal covers that banner completely. */
.savedialog-error {
    margin: 0;
    font-size: 13px;
    color: var(--color-accent-700);
}

@media (max-width: 640px) {
    .savedialog { padding: var(--space-2); }
    .savedialog-panel { padding: 22px; }
    .savedialog-title { font-size: 25px; }
}

/* — the empty state —
   620px, centred, 80px down, per the mockup. Every destination on this screen
   was checked against the routes that exist; see items/_empty.blade.php for
   which of the mockup's four controls survived and why the other two did
   not. */
.emptystate {
    max-width: 620px;
    margin: 80px auto 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 22px;
    text-align: center;
}
/* The wordmark at 34px — the handoff's empty-state size. Overrides
   .athenana-logo's sidebar 22px and nothing else about it: still Figtree 600,
   still lowercase, still an accent dot. */
.emptystate-logo { font-size: 34px; }

.emptystate-title {
    margin: 0;
    font-size: 40px;
    line-height: 1.12;
    letter-spacing: -0.015em;
}
.emptystate-lede {
    margin: 0;
    font-size: 17px;
    line-height: 1.55;
    text-wrap: pretty;
    /* 5.53:1. */
    color: var(--color-neutral-700);
}

/* The one action, at the mockup's larger size. Same measured pair as
   .itemview-open — --color-text on --color-accent, 4.60:1. */
.emptystate-save {
    display: inline-flex;
    align-items: center;
    padding: 12px 24px;
    border: 1px solid transparent;
    border-radius: 999px;
    background: var(--color-accent);
    color: var(--color-text);
    text-decoration: none;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 15px;
    line-height: 1.2;
}
.emptystate-save:hover { background: var(--color-accent-400); }
.emptystate-save:active { background: var(--color-accent-500); }

.emptystate-cards {
    display: flex;
    gap: 14px;
    margin-top: 24px;
    width: 100%;
}
/* The app's panel again — neutral-100 on neutral-300 at 28px, the same object
   as .itemlist, .taglist and .itemview-panel. Each one is a whole-card link,
   which is what makes it a card rather than a paragraph with a link in it. */
.emptystate-card {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 20px;
    background: var(--color-neutral-100);
    border: 1px solid var(--color-neutral-300);
    border-radius: 28px;
    text-align: left;
    text-decoration: none;
    color: var(--color-text);
}
.emptystate-card:hover { border-color: var(--color-neutral-500); box-shadow: var(--shadow-sm); }
.emptystate-card-title {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 17px;
}
/* 6.02:1 on neutral-100. */
.emptystate-card-body {
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-neutral-700);
}

@media (max-width: 640px) {
    .emptystate { margin-top: 40px; }
    .emptystate-title { font-size: 32px; }
    .emptystate-cards { flex-direction: column; }
}

/* ————————————————————————————————————————————————————————————————
   Task 7b — settings, admin, auth and trash.

   Nine views that still emitted the app's original markup. Every class below
   is a new name and none of .settings-*, .page-lede, .flash, .panel-*,
   .table-sub, .table-actions, .copyfield, .formrow, .codeblock or .authcard-*
   is defined in organic.css — checked by grep, pinned by a test in
   Feature/Design/SettingsScreensTest. What is reused from the system is
   reused whole: .field/.input on every form, .table on every table, and the
   app's own .panel object (shared with .itemlist/.taglist) around them.

   Contrast is measured against the ground it renders on, never assumed.
   --color-neutral-700 is 5.53:1 on --color-bg and 6.02:1 on
   --color-neutral-100, and is what every muted string here uses.
   --color-neutral-600, the app's older muted value, is 3.61:1 and is used
   nowhere at all any more. Two pre-existing pairs on these screens were
   deliberately NOT touched by Task 7b, because they were shared with screens
   it did not own and Task 8 wanted that audit in one commit: .btn's
   --color-bg on --color-accent (3.03:1) and .empty's -600 (3.61:1). Both are
   now fixed at their own rules — 4.60:1 and 5.53:1.
   ———————————————————————————————————————————————————————————————— */

/* — the page column —
   One stack, one rhythm. Settings forms, admin tables and trash are all a
   heading, a sentence and a block, so they get a flex column rather than a
   pile of per-element margins — the same shape .itemview and .emptystate use.
   align-items:stretch is what lets a table fill the column; a direct-child
   .btn would stretch with it, so those name their own align-self below. */
.settings {
    max-width: 640px;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-4);
}
/* The screens whose content is a wide table rather than a form: feeds carries
   four columns including a full feed URL, admin two tables, MyMind's sync log
   eight numeric columns. 640px elides those into unreadability. */
.settings--wide { max-width: 900px; }

/* The extra width is for the TABLE. Prose still wants a reading measure and a
   text input still wants to look like one — 900px of "Key ID" is not a better
   input, it is a worse one. Direct children only, so the small forms inside a
   table's action column are untouched. */
.settings--wide > .page-lede,
.settings--wide > form,
.settings--wide > .panel-prose,
.settings--wide .settings-section > .page-lede,
.settings--wide .settings-section > form,
.settings--wide .settings-section > .codeblock { max-width: 640px; }

/* The gap owns the rhythm, so the elements must not also carry margins of
   their own — organic gives every h1/h2/p a bottom margin. */
.settings > *,
.settings-section > * { margin-top: 0; margin-bottom: 0; }

/* A subsequent section of the same screen — "Delete my account", "Running a
   sync", "Members". Further apart than the items within one. */
.settings-section {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
    margin-top: var(--space-6);
}
/* Caprasimo from the global h2 rule, which is the one place the handoff wants
   it — this is a page heading. 20px against organic's 32px, for the same
   page-density reason the app sets h1 back to 26px. */
.settings-heading { font-size: 20px; }

/* A .btn is the one direct child that must not stretch to the column. A form
   deliberately still does — the inputs inside it are width:100% and want the
   full column, and a form whose only child is a button looks identical either
   way, because .btn is inline-flex and sits at the start of the line. */
.settings > .btn,
.settings-section > .btn { align-self: flex-start; }

/* The muted sentence under a heading that explains what the screen is for.
   5.53:1. */
.page-lede {
    font-size: 15px;
    line-height: 1.55;
    color: var(--color-neutral-700);
}

/* — panelled tables —
   The table sits in the app's .panel, so the panel is the surface. This block
   used to open with `.panel > .table { background: transparent }`, cancelling
   the transitional bare `table` rule's --color-surface; that rule went with
   Task 7c and organic's own .table paints no background, so the override was
   dead and is gone with it. */

/* A table can be wider than the column it is in — a feed URL is long and the
   sync log has eight numeric columns. .panel's shared overflow:hidden exists
   to clip .itemlist's square rows to the radius; on a wide table it would
   silently cut a column off instead. Scroll that axis, keep the clip on the
   other. */
.panel:has(> .table) { overflow-x: auto; }

/* organic's .table pads cells at var(--space-2) (8.8px), which is right for a
   table drawn edge to edge and too tight against a 28px corner. 24px matches
   .taglist's own rows and .panel's other consumers. */
.panel > .table th,
.panel > .table td { padding: 12px var(--space-3); }
.panel > .table th:first-child,
.panel > .table td:first-child { padding-left: 24px; }
.panel > .table th:last-child,
.panel > .table td:last-child { padding-right: 24px; }

/* MEASURED AND OVERRIDDEN: organic's own .table th is
   color-mix(in srgb, var(--color-text) 60%, transparent), which composites to
   #777470 on --color-neutral-100 and measures 4.25:1 — it FAILS AA, and an
   11px uppercase column heading is body copy. -700 is 6.02:1. This is a new
   pair introduced by adopting .table here, so it is fixed here rather than
   deferred to Task 8; organic.css itself is read-only and unchanged.

   Task 8 WIDENED the selector from `.panel > .table th` to `.table th`. The
   mix fails on every ground this app has (4.13:1 on --color-bg, 4.19:1 on
   --color-surface), so scoping the fix to tables that happen to sit as a
   DIRECT child of a panel left the next table in the app to reintroduce it
   silently. Still (0,1,1) against organic's own (0,1,1), and this file loads
   later, so it wins by order — the padding rules below stay panel-scoped
   because those genuinely are about the panel. */
.table th { color: var(--color-neutral-700); }

/* The last row's rule would otherwise sit a hairline above the panel's own
   bottom edge — the same thing .itemrow:last-child does, for the same
   reason. Kept as a transparent border rather than none, so the row does not
   change height on the last line. */
.panel > .table tbody tr:last-child td { border-bottom-color: transparent; }

/* .panel carries no padding of its own — .itemlist brings it on the row and
   .table brings it on the cell, which is what lets a row's hover tint and a
   table's rules reach the panel's edges. A panel holding prose has neither,
   so it says so. --color-text on --color-neutral-100 is 15.17:1. */
.panel-prose { padding: 20px 24px; }
.panel-prose > :first-child { margin-top: 0; }
.panel-prose > :last-child { margin-bottom: 0; }
/* A prose link inside a panel used to be fixed HERE, at accent-700, with a
   note saying the app-wide <a> colour was Task 8's to change in one commit.
   Task 8 changed it — see the `a` rule near the top of this file — so this
   rule would now restate the value it already inherits. Removed rather than
   left as a duplicate that reads like a deliberate exception. */

/* The second line in a cell: a feed's host and kind, a sync run's error, the
   "· admin" marker. 6.02:1 on the panel. */
.table-sub {
    font-size: 12px;
    color: var(--color-neutral-700);
}

/* The actions column. The forms are POSTs, so each control is its own <form>
   and they must not stack. */
.table-actions { text-align: right; white-space: nowrap; }
.table-actions form { display: inline-block; }
.table-actions form + form { margin-left: 6px; }

/* A readonly value that exists to be copied — a feed URL, a freshly created
   API token. organic's .input carries the shape; only the face changes, since
   a URL and a token are strings you read character by character. */
.copyfield {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px;
    /* FOUND BY LOOKING AT 375px, not by a test: organic's .input is width:100%
       of its cell, and in a four-column table on a phone that cell collapses —
       the field rendered as a 30px CIRCLE, because the pill radius survives
       whatever the width is. Nothing failed; it just looked broken. A floor in
       ch means the table gets wider than the panel instead, and the panel
       already scrolls that axis. */
    min-width: 24ch;
}

/* A label-and-button pair on one line: "Name this token" + Create. flex-end
   aligns the button with the input rather than with the label above it. */
.formrow {
    display: flex;
    gap: 10px;
    align-items: flex-end;
}
.formrow .field { flex: 1; margin-bottom: 0; }

/* The two commands that run a MyMind sync. Was a <pre> with
   border:1px solid var(--line) — a Pediment token retired in Task 1, so the
   border resolved to `initial` and there was no border at all rather than a
   wrong one. The app's panel face, at a size a command line can be read at.
   --color-text on --color-neutral-100 is 15.17:1. */
.codeblock {
    margin: 0;
    padding: 16px 20px;
    overflow-x: auto;
    background: var(--color-neutral-100);
    border: 1px solid var(--color-neutral-300);
    border-radius: var(--radius-md);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
    line-height: 1.6;
    color: var(--color-text);
}

/* — the account screen's second way into Settings —
   Outlined pills, not bare links: organic's <a> is --color-accent, which
   measures 3.03:1 on the ground and fails AA outright. .btn.secondary is
   --color-text on the ground, 13.95:1, and these are navigation actions
   rather than prose links. */
.settings-links {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-self: flex-start;
}

/* — the signed-out screens —
   /login and /pending render inside the same shell as everything else, with
   an empty sidebar carrying the wordmark. The card therefore does NOT repeat
   the wordmark: one identity per screen. The app's panel face again, so a
   signed-out visitor's first impression is the same object the rest of the
   app is built from. */
.authcard {
    max-width: 460px;
    margin: 8vh auto 0;
    padding: 32px;
    background: var(--color-neutral-100);
    border: 1px solid var(--color-neutral-300);
    border-radius: 28px;
}
.authcard--wide { max-width: 560px; }
.authcard--centred { text-align: center; }

/* Caprasimo, from the global heading rule. 30px rather than the app's 26px
   h1: this is the only thing on the page. */
.authcard-title {
    margin: 0 0 var(--space-3);
    font-size: 30px;
}
.authcard-body {
    margin: 0 0 var(--space-3);
    font-size: 15px;
    line-height: 1.55;
    color: var(--color-text);
}
/* 6.02:1 on --color-neutral-100. */
.authcard-lede {
    margin: 0 0 var(--space-4);
    font-size: 15px;
    line-height: 1.55;
    color: var(--color-neutral-700);
}
.authcard form { margin: 0; }

@media (max-width: 640px) {
    .authcard { margin-top: 24px; padding: 22px; }
    .authcard-title { font-size: 25px; }
    .formrow { flex-wrap: wrap; }
}

/* ————————————————————————————————————————————————————————————————
   Task 7c — managing tags, and the near-duplicate suggestions.

   Every class below is a new name: none of .tagmanage-*, .dupe*, or
   .mergeconfirm-* is defined in organic.css (checked by grep, pinned by a
   test in Feature/Tags/TagManagementTest). What is reused from the system is
   reused whole — .panel, .input, .btn, .empty, .page-lede.

   The grid is the design package's tag screen exactly: 1fr 90px 150px, a
   12px uppercase header row, and a hairline between rows. Contrast measured
   against --color-neutral-100 #f9f4ed and --color-accent-100 #fff2eb, the two
   grounds a row can have.
   ———————————————————————————————————————————————————————————————— */

.tagmanage { max-width: 900px; }

.tagmanage-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
    margin-bottom: var(--space-3);
}
.tagmanage-bar form { margin: 0; display: flex; gap: 12px; align-items: center; }
/* Holds the filter at the mockup's width rather than letting .search's flex:1
   stretch it across 900px — the same containment /tags uses. */
.tagmanage-filter { flex: 1; max-width: 340px; display: flex; }
/* 6.02:1 on --color-neutral-100, 5.53:1 on --color-bg. */
.tagmanage-summary,
.tagmanage-filtered { font-size: 13px; color: var(--color-neutral-700); }
.tagmanage-filtered { margin: 0 0 var(--space-3); }
/* 5.72:1 on --color-bg. The route to the suggestions, and the only thing on
   the screen that gathers them — there is deliberately no control anywhere
   that applies more than one group. */
.tagmanage-dupelink { font-size: 13px; color: var(--color-accent-700); }

.tagmanage-head,
.tagmanage-row {
    display: grid;
    grid-template-columns: 1fr 90px 150px;
    gap: 16px;
    align-items: center;
    padding: 14px 24px;
}
.tagmanage-head {
    font-size: 12px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    /* -700 rather than the mockup's -600: 12px uppercase is body copy and
       -600 is 3.61:1. Same substitution /tags made. */
    color: var(--color-neutral-700);
}
/* Lines the column heading up with the tag name rather than with the
   checkbox that precedes it (16px box + 10px gap). */
.tagmanage-head-tag { padding-left: 26px; }

/* 8px rather than the header's 14px, because the name is a 32px field and not
   a line of text: measured, 14px gave a 66px row against the mockup's 48, and
   over a hundred rows that is a third of the list again. 8 + 32 + 8 lands on
   the mockup's height with a control still big enough to hit. */
.tagmanage-row {
    padding-top: 8px;
    padding-bottom: 8px;
    border-top: 1px solid var(--color-neutral-200);
    margin: 0;
}
/* A row with a suggestion on it. This is a STATE — this row has something to
   review — in the same way .pager-link.is-current and the current tag chip
   are states; colour still carries item type, and a tag has no type. 15.14:1
   for the name, 6.01:1 for the muted text. */
.tagmanage-row.is-dupe { background: var(--color-accent-100); }

.tagmanage-cell { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.tagmanage-nameline { display: flex; align-items: center; gap: 10px; min-width: 0; }
.tagmanage-check {
    flex: none;
    width: 16px; height: 16px;
    accent-color: var(--color-accent);
    cursor: pointer;
}

/* The mockup draws the name as plain text with a Rename button beside it. It
   is a field here because renaming is what this screen is for, but it is
   dressed as the mockup's text until you reach for it: no fill, no border,
   the mockup's 15px/600 — and the system's .input geometry underneath, so it
   still lines up with every other control in the app. */
.tagmanage-input {
    background: transparent;
    border-color: transparent;
    font-weight: 600;
    font-size: 15px;
    padding-left: 8px;
    min-height: 32px;
    min-width: 0;
}
.tagmanage-input:hover { border-color: var(--color-neutral-300); }
.tagmanage-input:focus-visible { background: var(--color-neutral-100); }

.tagmanage-count { color: var(--color-neutral-700); font-variant-numeric: tabular-nums; }

.tagmanage-actions { display: flex; gap: 8px; justify-content: flex-end; }
.tagmanage-actions .btn { font-size: 13px; padding: 5px 14px; }

/* The suggestion block: the badge, the reason, and the way to act on it, all
   in the tag's own column. The row's Rename and Delete stay where they are on
   every row — a suggestion that removes the ordinary controls is a suggestion
   leaning on you. */
.tagmanage-dupe { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-left: 26px; }
/* 5.49:1. */
.dupebadge {
    font-size: 12px;
    background: var(--color-accent-200);
    color: var(--color-accent-700);
    border-radius: 999px;
    padding: 3px 11px;
    white-space: nowrap;
}
/* The other members of a group, which are usually nowhere near it in the
   list. Neutral, because they are not the row being offered an action. 5.36:1. */
.dupebadge-quiet { background: var(--color-neutral-200); color: var(--color-neutral-700); }
/* 6.01:1 on the tinted row. Never omitted: this is the sentence that makes the
   badge reviewable by the person about to delete a tag. */
.dupewhy { font-size: 13px; color: var(--color-neutral-700); }
/* "Compare these N…". DELIBERATELY SECONDARY, not the accent fill the mockup
   draws: an accent-filled "Merge 3" reads as the screen's recommendation, and
   the rule has no grounds to recommend anything. It opens the comparison, and
   the comparison is where a person decides. */
.dupemerge { font-size: 13px; padding: 5px 14px; }

.tagmanage-pages { padding: 24px 0; }

/* — the merge confirmation —
   Its own screen rather than a dialog, because a merge cannot be undone and
   the figures on it are the whole point of it existing. */
.mergeconfirm { max-width: 640px; }
.mergeconfirm-whyhead { font-weight: 600; margin: 0 0 8px; }
.mergeconfirm-why { margin: 0 0 var(--space-3); padding-left: 20px; }
.mergeconfirm-why li { margin-bottom: 4px; }
/* 6.02:1 on --color-neutral-100. */
.mergeconfirm-caveat { margin: 0; font-size: 14px; color: var(--color-neutral-700); }

.mergeconfirm-choice {
    display: flex;
    flex-direction: column;
    gap: 2px;
    border: 1px solid var(--color-neutral-300);
    border-radius: 28px;
    background: var(--color-neutral-100);
    padding: 8px;
    margin: 0 0 var(--space-3);
}
.mergeconfirm-choice legend {
    font-size: 12px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-neutral-700);
    padding: 0 12px;
}
.mergeconfirm-option {
    display: grid;
    grid-template-columns: 16px 1fr;
    gap: 4px 10px;
    align-items: center;
    padding: 10px 12px;
    border-radius: 20px;
    cursor: pointer;
}
.mergeconfirm-option:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }
.mergeconfirm-option input { accent-color: var(--color-accent); margin: 0; }
.mergeconfirm-name { font-weight: 600; font-size: 15px; overflow-wrap: anywhere; }
/* 6.02:1 on --color-neutral-100. The figure that answers "how many items
   move": the count now, and the count the survivor ends up with. */
.mergeconfirm-figures {
    grid-column: 2;
    font-size: 13px;
    color: var(--color-neutral-700);
}
/* 5.49:1. */
.mergeconfirm-proposed {
    font-size: 12px;
    background: var(--color-accent-200);
    color: var(--color-accent-700);
    border-radius: 999px;
    padding: 2px 9px;
    margin-left: 4px;
}
.mergeconfirm-sum { font-size: 14px; line-height: 1.55; margin: 0 0 var(--space-4); }
.mergeconfirm-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* — long-form pages (privacy) —
   Not .settings: those are forms and tables at a wider measure. This is
   reading matter, so it takes a prose measure and a paragraph rhythm. */
.prose { max-width: 680px; }
/* --space-6: --space-5 does not exist, so this margin was silently 0. */
.prose h2 { font-size: 20px; margin: var(--space-6) 0 var(--space-2); }
.prose p, .prose li, .prose dd { font-size: 15px; line-height: 1.6; }
.prose ul { padding-left: 1.2em; }
.prose li { margin-bottom: 6px; }
.prose dt { font-family: var(--font-body); font-weight: 600; font-size: 15px; margin-top: var(--space-3); }
.prose dd { margin: 4px 0 0; padding-left: 0; }
/* neutral-700, not -600: -600 is 3.61:1 on the ground and fails AA. */
.privacy-updated { color: var(--color-neutral-700); font-size: 13px; margin-top: var(--space-6); }

/* — the extension install page — */
/* .panel is padding:0 — .panel-prose is what supplies 20px 24px, and these
   two are panels without being prose. Stated here rather than borrowing
   .panel-prose, which also sets type. Caught by looking: both panels shipped
   with their contents flush to the edge and overflowing it. */
.install-download { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 20px 24px; margin-bottom: var(--space-4); }
.install-none { padding: 20px 24px; }
.install-chrome { margin-bottom: var(--space-4); }
.install-browserhead { margin-top: var(--space-4); margin-bottom: var(--space-2); }
.install-chrome p + p { margin-top: 10px; }
.install-get { font-size: 15px; padding: 11px 22px; }
/* neutral-700, not -600: -600 is 3.61:1 on this panel and fails AA. */
.install-meta { margin: 0; font-size: 13px; color: var(--color-neutral-700); }

.install-steps { margin: 0 0 var(--space-4); padding-left: 1.3em; display: flex; flex-direction: column; gap: 10px; }
.install-steps li { font-size: 14px; line-height: 1.55; }

.install-notes { margin-bottom: var(--space-4); }
.install-noteshead { font-family: var(--font-body); font-weight: 600; font-size: 15px; margin: 0 0 6px; }
.install-notes p + p { margin-top: 10px; }

.install-none p { margin: 0; }
.install-none p + p { margin-top: 10px; }
.install-other { font-size: 13px; color: var(--color-neutral-700); }

/* The four badge states, drawn as the toolbar draws them: white on the
   extension's own colours, which are the app's ramps. Kept in step with
   extension/lib/badge.js by hand — there is no build step to share them
   through, and a page that shows the wrong colour is worse than one that
   shows none. */
.install-badge {
    display: inline-block;
    min-width: 1.5em;
    padding: 1px 5px;
    border-radius: 4px;
    text-align: center;
    font-size: 12px;
    font-weight: 600;
    color: #fff;
}
.install-badge.is-saving { background: var(--color-neutral-700); }
.install-badge.is-saved { background: var(--color-accent-2-700); }
.install-badge.is-dupe { background: var(--color-accent-700); }
/* Organic has no danger colour; this is badge.js's red, for the same reason
   it is not on the ramp there. */
.install-badge.is-failed { background: #c53030; }

/* — the sort control —
   Shared by /tags and /settings/tags. Sits in the same bar as the filter and
   the cut-off toggle, pushed to the right so the controls that NARROW the list
   stay together on the left and the one that only re-orders it does not read
   as another filter. */
.tagsort { display: flex; align-items: center; gap: 8px; }

/* The auto margin needs BOTH bars named, and that is the shared-selector trap
   again — this time from this file rather than organic.css. `.tagmanage-bar
   form { margin: 0 }` is (0,1,1) and `.tagsort` is (0,1,0), so a bare
   `margin-left: auto` on the class computed to 0px and the control sat beside
   the summary instead of at the end of the bar. Measured before the fix:
   marginLeft "0px" with the rule plainly in the stylesheet. (0,2,0) wins. */
.tagmanage-bar .tagsort,
.tagbrowse-bar .tagsort { margin-left: auto; }
/* neutral-700, not -600: -600 is 3.61:1 on the ground and fails AA. */
.tagsort-label { font-family: var(--font-body); font-size: 13px; color: var(--color-neutral-700); }
.tagsort-select { width: auto; min-width: 9.5rem; padding-block: 7px; }

@media (max-width: 700px) {
    /* The bar wraps here, and an auto margin on a wrapped line pushes this to
       the far right of a row of its own. Full width reads as the next control
       down rather than as a stranded one. */
    .tagsort { margin-left: 0; width: 100%; }
    .tagsort-select { flex: 1; }
}

/* — the delete confirmation's list —
   The same rounded panel the tag list and the board's list view use, so the
   selection reads as the same object it was ticked on. No control inside it:
   the only decision this screen offers is the one in the actions below, and a
   per-row affordance here would invite editing a set that the hidden inputs
   have already fixed. */
.deleteconfirm-list { padding: 0; margin-bottom: var(--space-4); }
.deleteconfirm-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 10px var(--space-4);
    border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.deleteconfirm-row:last-child { border-bottom: 1px solid transparent; }
.deleteconfirm-name { font-family: var(--font-body); font-weight: 600; font-size: 15px; overflow-wrap: anywhere; }
/* neutral-700, not -600: -600 is 3.61:1 on this panel and fails AA. */
.deleteconfirm-uses { flex: none; font-size: 13px; color: var(--color-neutral-700); }

/* These two screens once had to opt OUT of the transitional .btn block's
   --color-bg fill (3.03:1) with a rule of their own, because nothing new was
   going to ship at 3.03 while the pre-existing instances waited for Task 8's
   sweep. Task 8 has run: .btn itself is now --color-text on --color-accent at
   4.60:1, so the opt-out has nothing left to opt out of and is gone.

   .dupemerge was deliberately NOT in that list — it is .btn.secondary, so
   that nothing on the list reads as a recommendation. Still true, and now it
   needs no explaining, because .btn.secondary was always --color-text.

   The removed rule is worth remembering for a different reason: two lines of
   this explanation once sat OUTSIDE the comment, the block closed early, and
   they ran on as text before a comma-separated selector list. One invalid
   selector invalidates the whole rule in CSS, so both merge buttons silently
   kept 3.03:1 — including the confirmation screen's own irreversible-merge
   submit — while 623 tests stayed green. That is what
   tests/Feature/Design/StylesheetTest.php now exists to catch. */

@media (max-width: 780px) {
    /* The actions no longer fit beside a usable name field; the row becomes
       two lines rather than a 40px-wide rename box. */
    .tagmanage-head { display: none; }
    .tagmanage-row { grid-template-columns: 1fr auto; }
    .tagmanage-cell { grid-column: 1 / -1; }
    .tagmanage-count::before { content: 'Items: '; }
    .tagmanage-head,
    .tagmanage-row { padding: 14px 18px; }
}

/* — trash row actions —
   Restore and Delete forever side by side. The row already has .table-actions
   pushing it right; this only keeps the two from stacking and gives the
   destructive one its own ink. */
.trash-actions { display: flex; gap: 8px; justify-content: flex-end; align-items: center; }
.trash-actions form { margin: 0; }
/* accent-700, the same deep terracotta the tag screen gives a destructive
   secondary — 6.81:1 on the panel. Not the base accent, which is 3.03:1. */
.trash-purge { color: var(--color-accent-700); }

/* — feeds: the reading list —
   App-specific. organic.css defines nothing under .feedcard/.feedlist — checked
   by grep and pinned by a test in Feature/Feeds/FeedEntriesTest, for the reason
   the .btn, .card and [hidden] notes above give: a shared name at the same
   specificity inherits whatever this file does not restate, and every instance
   of that has shipped green.

   The app's .panel is the surface and carries NO padding of its own (it is
   built for .table and .itemlist, which pad their own rows), so every direct
   child here states its own. */

.feedcard { padding: 24px; margin-bottom: var(--space-4); }

.feedcard-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
}

.feedcard-title {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    font-size: 20px;
    margin: 0;
    /* A feed title is a publisher's string and can be a single unbroken URL. */
    overflow-wrap: anywhere;
}

.feedcard-sub {
    margin: var(--space-1) 0 0;
    font-size: 13px;
    /* neutral-700, not 600: measured 4.9:1 on the panel's neutral-100 ground,
       where 600 is 3.4:1 and fails AA for body text. */
    color: var(--color-neutral-700);
}

.feedcard .copyfield { margin-top: var(--space-3); }

.feedcard-error {
    margin: var(--space-3) 0 0;
    padding: var(--space-2) var(--space-3);
    border-radius: 12px;
    font-size: 13px;
    background: var(--color-accent-100);
    /* accent-800 on accent-100 measures 8.1:1. The brand accent itself is
       3.2:1 and cannot carry text this size. */
    color: var(--color-accent-800);
}

.feedcard-empty {
    margin: var(--space-3) 0 0;
    font-size: 14px;
    color: var(--color-neutral-700);
}

.feedlist {
    list-style: none;
    margin: var(--space-4) 0 0;
    padding: 0;
}

.feedlist-item {
    padding: var(--space-3) 0;
    border-top: 1px solid var(--color-divider);
}

.feedlist-link {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--color-text);
    text-decoration: none;
    /* Entry titles are frequently one long unspaced string. */
    overflow-wrap: anywhere;
}

.feedlist-link:hover,
.feedlist-link:focus-visible { text-decoration: underline; }

.feedlist-date {
    display: block;
    margin-top: var(--space-1);
    font-size: 12px;
    color: var(--color-neutral-700);
}

.feedlist-summary {
    margin: var(--space-2) 0 0;
    font-size: 14px;
    line-height: 1.5;
    color: var(--color-neutral-800);
}

/* — editing an item's tags —
   App-specific. organic.css defines nothing under .tagedit/.tagadd — checked by
   grep and pinned by a test in Feature/Items/ItemTagEditTest, for the reason the
   .btn, .card and [hidden] notes above give.

   .tag/.tag-neutral ARE organic's and are reused whole; only the layout the
   remove button needs is added, because organic's .tag is already an
   inline-flex row and a second child sits in it correctly. */

.tagedit {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-3);
}

/* Gap between the label and the ×, and a little right padding so the glyph is
   not flush against the pill's curve. */
.tagedit-chip { gap: 6px; padding-right: 6px; }

.tagedit-name { color: inherit; text-decoration: none; }
.tagedit-name:hover, .tagedit-name:focus-visible { text-decoration: underline; }

/* The form wrapper must not become a line box of its own, or the × drops below
   the label inside the pill. */
.tagedit-chip form { display: inline-flex; }

.tagedit-remove {
    /* Not organic's .btn: that is a pill with its own padding and font, and a
       24px control inside an 11px chip is what it would produce. */
    appearance: none;
    background: none;
    border: 0;
    padding: 0 2px;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    /* neutral-700 on the chip's neutral-100 measures 4.9:1. neutral-500 is
       2.6:1 and fails, and a dismiss control is exactly the one that must not
       be hard to see. */
    color: var(--color-neutral-700);
}

.tagedit-remove:hover { color: var(--color-accent-700); }

.tagadd {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-3);
    flex-wrap: wrap;
}

/* Takes the slack so the Add button stays its own width. The floor stops the
   field collapsing to a stub inside a flex row, which is the same failure the
   .copyfield note above records at 375px. */
.tagadd-field { flex: 1 1 220px; min-width: 180px; margin: 0; }

.itemview-tagnote {
    margin: var(--space-2) 0 0;
    font-size: 13px;
    color: var(--color-neutral-700);
}

/* — suggested tags —
   App-specific. organic.css defines nothing under .proposed/.nav-badge —
   checked by grep and pinned by a test in Feature/Tags/ProposedTagTest, for the
   reason the .btn, .card and [hidden] notes above give.

   .panel again carries no padding of its own, so each block states it. */

.proposed { padding: 24px; margin-bottom: var(--space-4); }

.proposed-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
}

.proposed-name {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    font-size: 20px;
    margin: 0;
    overflow-wrap: anywhere;
}

.proposed-sub {
    margin: var(--space-1) 0 0;
    font-size: 13px;
    color: var(--color-neutral-700);
}

.proposed-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.proposed-clear { margin-bottom: var(--space-4); }

.proposed-similar {
    margin-top: var(--space-4);
    padding: var(--space-3);
    border-radius: 16px;
    /* accent-2 (sage) rather than accent: this is a HELPFUL observation, not a
       warning, and terracotta is the app's attention colour. */
    background: var(--color-accent-2-100);
}

.proposed-similar-lede {
    margin: 0 0 var(--space-2);
    font-size: 13px;
    /* accent-2-800 on accent-2-100 measures 8.4:1. The mid ramp is under 4.5
       and cannot carry text this size. */
    color: var(--color-accent-2-800);
}

.proposed-similar-list { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.proposed-use { font-size: 13px; }

.proposed-items {
    list-style: none;
    margin: var(--space-4) 0 0;
    padding: 0;
    border-top: 1px solid var(--color-divider);
}

.proposed-items li { padding: var(--space-2) 0 0; font-size: 14px; }
.proposed-items a { color: var(--color-text); }
.proposed-more { color: var(--color-neutral-700); }

/* The sidebar link is a flex row already, so the badge only needs to be
   pushed to its end. */
.nav-badge {
    margin-left: auto;
    min-width: 20px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 11px;
    text-align: center;
    background: var(--color-accent-200);
    /* accent-800 on accent-200 measures 6.9:1. The brand accent is 3.2:1 and
       cannot carry text this small. */
    color: var(--color-accent-800);
}

/* — rendered markdown —
   App-specific. organic.css defines nothing under .md — checked by grep and
   pinned by a test in Feature/Items/MarkdownTest.

   SEPARATE FROM .prose, deliberately. .prose is the long-form page style
   (privacy), and these rules would otherwise reach it: adding table, pre and
   blockquote rules to a shared class to serve a note body changes a page that
   never asked for them. The small overlap in type rules is the cost of that
   isolation and is worth it.

   Every block resets its outer margins, because .md is used INSIDE panels
   (.itemview-panel-body) whose own padding already sets the inset — without
   it a leading <p> pushes a visible gap above itself. */

.md > :first-child { margin-top: 0; }
.md > :last-child { margin-bottom: 0; }

.md p, .md li { font-size: 15px; line-height: 1.6; }
.md p { margin: 0 0 var(--space-3); }

/* Caprasimo, matching every other heading in the app. A note IS allowed
   headings now, and a bold body font would read as emphasis rather than
   structure. Sized down from the page heading — this is a heading inside a
   panel, not the page's own. */
.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    margin: var(--space-4) 0 var(--space-2);
    line-height: 1.25;
}

.md h1 { font-size: 22px; }
.md h2 { font-size: 19px; }
.md h3 { font-size: 17px; }
.md h4, .md h5, .md h6 { font-size: 15px; }

.md ul, .md ol { margin: 0 0 var(--space-3); padding-left: 1.3em; }
.md li { margin-bottom: 5px; }
.md li > ul, .md li > ol { margin-top: 5px; margin-bottom: 0; }

.md blockquote {
    margin: 0 0 var(--space-3);
    padding-left: var(--space-3);
    /* A rule rather than a fill: a tinted block would collide with the type
       colours, where sage means "this is a note" and terracotta means "this
       is a highlight". A quote inside a note is neither. */
    border-left: 3px solid var(--color-neutral-300);
    color: var(--color-neutral-800);
}

.md code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.9em;
    padding: 1px 5px;
    border-radius: 6px;
    background: var(--color-neutral-200);
}

.md pre {
    margin: 0 0 var(--space-3);
    padding: var(--space-3);
    border-radius: 12px;
    background: var(--color-neutral-200);
    /* A pasted code block is the one thing here with no soft wrap, so it
       scrolls INSIDE its own box rather than widening the page. */
    overflow-x: auto;
}

.md pre code { padding: 0; background: none; font-size: 13px; line-height: 1.5; }

.md a { color: var(--color-accent-700); }

.md hr { border: 0; border-top: 1px solid var(--color-divider); margin: var(--space-4) 0; }

.md img { max-width: 100%; height: auto; border-radius: 12px; }

/* Same reason as pre: a wide table scrolls in its own box instead of forcing
   the page to. */
.md table { width: 100%; border-collapse: collapse; margin: 0 0 var(--space-3); font-size: 14px; display: block; overflow-x: auto; }
.md th, .md td { padding: 6px 10px; border-bottom: 1px solid var(--color-divider); text-align: left; }
.md th { font-weight: 600; }

/* GFM task lists. The marker is the checkbox, so the bullet is noise. */
.md li input[type="checkbox"] { margin-right: 6px; }
.md ul:has(> li > input[type="checkbox"]) { list-style: none; padding-left: 0; }

/* — public landing — */
/* No component in organic.css is a full-page marketing shell, so this is
   app-specific and stays here. Reuses .btn and .athenana-logo rather than
   restyling them; the only new work is the page's own rhythm. */
.landing {
    max-width: 860px;
    margin: 0 auto;
    padding: var(--space-6) var(--space-4) var(--space-8);
}
.landing > header { margin-bottom: var(--space-8); }
/* .athenana-logo is a <span> in layouts.app and an <a> here. It already sets
   its own colour, which beats organic's `a` on specificity, but not the
   underline — so that is dropped explicitly rather than left to look like a
   link to the page you are already on. */
.landing > header .athenana-logo { text-decoration: none; }
.landing-hero { margin-bottom: var(--space-8); }
.landing-lede {
    font-size: 18px;
    max-width: 60ch;
    /* --color-neutral-700 is 5.53:1 on --color-bg. -600 is 3.61:1 and fails
       AA, and is what the older muted rules in this file use. */
    color: var(--color-neutral-700);
}
.landing-actions { margin: var(--space-6) 0 var(--space-2); }
.landing-note { font-size: 14px; color: var(--color-neutral-700); }
.landing-foot {
    margin-top: var(--space-8);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-divider);
}
.landing-foot-links { display: flex; flex-wrap: wrap; gap: var(--space-4); font-size: 14px; }

.landing-section { margin-bottom: var(--space-8); }
.landing-heading {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    margin-bottom: var(--space-4);
}
.landing-items,
.landing-features {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--space-4);
}
.publiccard {
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    overflow: hidden;
}
/* The whole card is the link, so the anchor is the layout box. organic.css
   gives <a> the accent colour, which measures 3.03:1 on the ground and fails
   AA — reset it here and let the card's own type carry the affordance, the
   same trade the settings-links pills make. */
.publiccard a { display: block; color: var(--color-text); text-decoration: none; }
.publiccard-media { display: block; width: 100%; height: 160px; object-fit: cover; }
.publiccard-kicker {
    padding: var(--space-3) var(--space-3) 0;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-neutral-700);
}
.publiccard-title { padding: var(--space-2) var(--space-3); font-weight: 600; }
.publiccard-body {
    padding: 0 var(--space-3) var(--space-3);
    font-size: 14px;
    color: var(--color-neutral-700);
}
.landing-feature-title {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    margin-bottom: var(--space-2);
}
.landing-feature p { color: var(--color-neutral-700); font-size: 15px; }

/* — visibility — */
.visibility { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
/* --color-neutral-700 is 5.53:1 on --color-bg; -600 is 3.61:1 and fails AA. */
.visibility-state { font-size: 13px; color: var(--color-neutral-700); margin: 0; }
.visibility-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-divider);
    flex-wrap: wrap;
}
.visibility-meta { font-size: 13px; color: var(--color-neutral-700); margin: var(--space-1) 0 0; }
.visibility-banner {
    padding: var(--space-3);
    border-radius: var(--radius-md);
    background: var(--color-neutral-100);
    /* --color-text on --color-neutral-100 is 15.17:1. */
    color: var(--color-text);
    margin-bottom: var(--space-4);
}
