/**
 * kRead reader-shell styles (U10) -- brief, feed, publication browse,
 * unrecognised bucket, persistent navigation.
 *
 * Every color here is a var(--color-*) token from lifeos-base.css so both
 * themes work with zero rules in this file keyed off [data-theme] --
 * exactly what the packet asks for ("Build against the house theme
 * variables ... without a second stylesheet"). No fixed pixel widths wider
 * than a phone viewport are used anywhere below, and every wrapping
 * surface (preview text, candidate-key values, long titles) explicitly
 * allows wrapping so nothing forces the page wider than the viewport.
 */

/* ========================================================================
   U22 -- iOS reading ergonomics: type scale, touch floors, safe areas.

   kRead is read almost entirely on an iPhone, so this block adopts
   Apple's own numbers rather than inventing a scale. Three sources, and
   what each one decides here:

   - Apple HIG, Typography. iOS's default body text is 17pt and the
     smallest size it considers legible at arm's length is 11pt. The house
     stylesheet's 14px body sat between the two: legible, but a full step
     below what iOS itself uses for reading. --kread-body is 17.
   - Apple HIG, Layout ("Accessibility"): every hit target is at least
     44x44pt. Measured against that, the bottom nav (~26pt tall) and every
     .btn-sm (~30pt) were under it. --kread-tap-min is the floor, applied
     under `pointer: coarse` so a desktop mouse still gets compact chrome.
   - Safari's own behaviour, not a guideline: a focused form control under
     16px makes iOS zoom the page in and leave it there. Every input in
     this reader was 14px, which is why tapping the note box lurched.

   The reading size is separate from the UI size and larger than both
   (19px default). That is the reading-app convention -- Safari Reader,
   Apple News and Instapaper all set running text above the system body
   size -- and it is adjustable, because "a bit small" is a matter of
   eyesight and arm length that a fixed number cannot settle. The steps
   live on <html> as data-kread-textsize; static/js/textsize.js sets it
   before first paint.
   ======================================================================== */

:root {
    /* The platform's own UI face. On iOS this resolves to SF Pro, which is
       the face the sizes above were drawn for and which carries Apple's
       optical-size crossover (SF Text below 20pt, SF Display above) for
       free. It also costs no request and works offline -- the previous
       Roboto link was blocked by the reader's own CSP and never loaded. */
    --font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
                   "Helvetica Neue", Arial, sans-serif;

    /* Apple's iOS text styles. On iPhone at default zoom 1 CSS px is 1pt,
       so these are the HIG's numbers unconverted. */
    --kread-title1: 28px;      /* Title 1     */
    --kread-title2: 22px;      /* Title 2     */
    --kread-title3: 20px;      /* Title 3     */
    --kread-body: 17px;        /* Body        */
    --kread-callout: 16px;     /* Callout     */
    --kread-subhead: 15px;     /* Subheadline */
    --kread-footnote: 13px;    /* Footnote    */
    --kread-caption: 12px;     /* Caption 1   */

    --kread-tap-min: 44px;

    --kread-reading-size: 19px;
    --kread-reading-leading: 1.6;
}

:root[data-kread-textsize="s"]  { --kread-reading-size: 17px; }
:root[data-kread-textsize="m"]  { --kread-reading-size: 19px; }
:root[data-kread-textsize="l"]  { --kread-reading-size: 21px; }
:root[data-kread-textsize="xl"] { --kread-reading-size: 24px; }

html {
    /* Stops Safari inflating text when the phone turns landscape, which
       otherwise silently overrides every size chosen above. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    font-size: var(--kread-body);
}

/* The 16px floor that keeps iOS from zooming the page on focus. It is a
   floor, not a size: anything already larger keeps its own. */
input,
select,
textarea,
.form-input,
.form-select,
.form-textarea {
    font-size: max(var(--kread-callout), 1em);
}

/* Touch floors. Scoped to coarse pointers so the desktop keeps the house
   proportions -- a 44px minimum on every small button reads as clumsy
   with a mouse and as correct with a thumb. */
@media (pointer: coarse) {
    .btn {
        min-height: var(--kread-tap-min);
        font-size: var(--kread-callout);
    }

    .btn-sm {
        min-height: var(--kread-tap-min);
        font-size: var(--kread-subhead);
        padding: var(--space-2) var(--space-4);
    }

    .btn-icon {
        min-width: var(--kread-tap-min);
        min-height: var(--kread-tap-min);
    }
}

/* ========================================================================
   Persistent navigation (packet 2a) -- bottom bar on phone, top bar on
   desktop. Both are rendered by every page (see templates/_nav.html); this
   file is what decides which one is visible at a given width.
   ======================================================================== */

/* Desktop: the top bar lives inside base.html's existing .nav-links (the
   {% block nav %} slot) -- no extra wrapper needed, just style the links. */
.nav-links .kread-nav-item {
    font-size: var(--kread-subhead);
    color: var(--color-text);
    padding: var(--space-2) 0;
    border-bottom: 2px solid transparent;
}

.nav-links .kread-nav-item.is-active {
    color: var(--color-primary);
    border-bottom-color: var(--color-primary);
}

.kread-bottom-nav {
    display: none;
}

@media (max-width: 768px) {
    /* Hide the desktop top bar's link row on phone width... */
    .nav-header .nav-links {
        display: none;
    }

    /* ...and show the fixed bottom bar instead. */
    .kread-bottom-nav {
        display: flex;
        justify-content: space-around;
        align-items: center;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 100;
        background: var(--color-bg);
        border-top: 1px solid var(--color-border);
        padding: var(--space-2) var(--space-1);
        padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
    }

    /* 44px is the HIG floor for a hit target, and these were about 26.
       display:flex + centring is what makes the whole 44 tappable rather
       than just the text's own line box. */
    .kread-bottom-nav .kread-nav-item {
        display: flex;
        align-items: center;
        justify-content: center;
        flex: 1;
        min-height: var(--kread-tap-min);
        font-size: var(--kread-footnote);
        color: var(--color-text-muted);
        text-align: center;
        padding: var(--space-1) var(--space-2);
        -webkit-tap-highlight-color: transparent;
        transition: color var(--transition-fast);
    }

    /* The tap highlight is off above, so pressed state has to be stated. */
    .kread-bottom-nav .kread-nav-item:active {
        color: var(--color-primary);
        opacity: 0.6;
    }

    .kread-bottom-nav .kread-nav-item.is-active {
        color: var(--color-primary);
        font-weight: 600;
    }

    /* Clear the fixed bottom bar so it never covers the last list item.
       The bar is now 44 + padding tall and sits above the home indicator,
       so the reserved strip has to include the same inset it does --
       env() returns a real number here only because base.html sets
       viewport-fit=cover. */
    main.page {
        padding-bottom: calc(60px + env(safe-area-inset-bottom, 0px) + var(--space-5));
    }

    /* Edge-to-edge under the Dynamic Island rather than letterboxed. */
    .nav-header,
    main.page {
        padding-left: max(var(--space-4), env(safe-area-inset-left, 0px));
        padding-right: max(var(--space-4), env(safe-area-inset-right, 0px));
    }
}

/* ========================================================================
   Shared item rows (brief / feed / issue list) -- a finite list of plain
   rows, not a card grid; the brief's job is to look finite, not busy.
   ======================================================================== */

.kread-brief-list,
.kread-feed-list,
.kread-issue-list,
.kread-publication-list {
    list-style: none;
    margin: 0 0 var(--space-5);
    padding: 0;
}

.kread-brief-item,
.kread-feed-item,
.kread-issue-item,
.kread-publication-item {
    padding: var(--space-4) 0;
    border-bottom: 1px solid var(--color-border);
}

.kread-brief-item:first-child,
.kread-feed-item:first-child,
.kread-issue-item:first-child,
.kread-publication-item:first-child {
    padding-top: 0;
}

.kread-item-title {
    font-size: var(--kread-body);
    font-weight: 600;
    line-height: 1.3;
    color: var(--color-text);
    overflow-wrap: break-word;
}

a.kread-item-title {
    display: block;
    text-decoration: none;
}

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

/* The whole row is the target, not just the headline's own line box.
   Tapping the publication name or the reason line and getting nothing is
   the small daily friction the HIG's 44pt rule is really about; a
   stretched pseudo-element gets the entire row without nesting the other
   three lines inside the anchor (which would flatten them for a screen
   reader into one long link name). */
.kread-brief-item,
.kread-feed-item,
.kread-issue-item {
    position: relative;
}

.kread-brief-item a.kread-item-title::after,
.kread-feed-item a.kread-item-title::after,
.kread-issue-item a.kread-item-title::after {
    content: "";
    position: absolute;
    inset: 0;
}

/* U8: the source line sits between the headline and the scorer's reason --
   a brief item without its publication reads as anonymous, and the bucket
   chip is what makes the composed shape (3 health, 2 investing, ...)
   visible at a glance rather than only in the code. */
.kread-item-source {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--kread-subhead);
    font-weight: 500;
    color: var(--color-text);
    margin-top: var(--space-1);
}

.kread-item-bucket {
    font-size: var(--kread-caption);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-tertiary);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm, 4px);
    padding: 1px var(--space-2);
    white-space: nowrap;
}

.kread-item-reason {
    font-size: var(--kread-subhead);
    line-height: 1.45;
    color: var(--color-text-muted);
    margin-top: var(--space-1);
}

/* The quality gate leaving a slot empty is intended behaviour, so the
   brief says so rather than looking short by accident (criteria.md:
   "A five-item brief on a thin day is the intended behaviour"). */
.kread-brief-note {
    font-size: var(--kread-footnote);
    color: var(--color-text-tertiary);
    padding: var(--space-3) 0 0;
}

.kread-item-meta {
    font-size: var(--kread-footnote);
    color: var(--color-text-tertiary);
    margin-top: var(--space-1);
}

.kread-publication-item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

.kread-empty {
    color: var(--color-text-muted);
    padding: var(--space-5) 0;
}

/* ========================================================================
   Brief end marker (R10 -- an explicit, visible end) -- deliberately
   plain, not celebratory. This is where the reading session stops.
   ======================================================================== */

.kread-brief-end {
    text-align: center;
    padding: var(--space-6) 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
}

.kread-end-marker {
    color: var(--color-text-muted);
    font-size: var(--kread-footnote);
    letter-spacing: 0.05em;
    margin: 0;
}

/* ========================================================================
   Feed day grouping -- native <details>/<summary>, no JS. Only the most
   recent day opens by default so a ten-day gap doesn't read as one long
   scroll (packet 2.).
   ======================================================================== */

.kread-day-group {
    border-bottom: 1px solid var(--color-border);
    padding: var(--space-3) 0;
}

.kread-day-group summary {
    cursor: pointer;
    font-weight: 600;
    font-size: var(--kread-body);
    display: flex;
    align-items: center;
    min-height: var(--kread-tap-min);
    padding: var(--space-2) 0;
    list-style: none;
}

.kread-day-group summary::-webkit-details-marker {
    display: none;
}

.kread-day-group summary::before {
    content: "▸";
    display: inline-block;
    margin-right: var(--space-2);
    color: var(--color-text-muted);
    transition: transform var(--transition-fast);
}

.kread-day-group[open] summary::before {
    transform: rotate(90deg);
}

.kread-day-count {
    color: var(--color-text-muted);
    font-weight: 400;
    font-size: var(--kread-footnote);
}

/* ========================================================================
   Unrecognised bucket (AE5) -- text-only preview + two classify forms.
   ======================================================================== */

.kread-unrecognised-item {
    margin-bottom: var(--space-4);
}

.kread-candidate-keys {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--space-1) var(--space-3);
    font-size: var(--kread-footnote);
    color: var(--color-text-muted);
    margin: var(--space-3) 0;
}

.kread-candidate-keys dt {
    font-weight: 600;
}

.kread-candidate-keys dd {
    margin: 0;
    overflow-wrap: anywhere;
    font-family: var(--font-family-mono);
}

.kread-preview {
    max-height: 240px;
    overflow-y: auto;
    overflow-x: hidden;
    overflow-wrap: anywhere;
    padding: var(--space-3);
    margin: var(--space-3) 0;
    background: var(--color-surface);
    border-radius: var(--radius-md);
    font-size: var(--kread-subhead);
}

.kread-preview p {
    margin: 0 0 var(--space-3);
}

.kread-preview p:last-child {
    margin-bottom: 0;
}

.kread-classify-form {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

/* ========================================================================
   Narrow-viewport safety net -- nothing in this file introduces a fixed
   width wider than a phone viewport, but this is the backstop.
   ======================================================================== */

@media (max-width: 480px) {
    .kread-candidate-keys {
        grid-template-columns: 1fr;
    }
}

/* ========================================================================
   U11 -- Article view, notes, and the archive gesture (R17, R18, R19,
   R42, R51, R62, R63).
   ======================================================================== */

.kread-article {
    /* ~65 characters at the default reading size. The old 42rem ran to
       about 85, past the 45-75 band that reading research (and Apple's
       own Books/News layouts) settle on. */
    max-width: 38rem;
    margin: 0 auto;
}

.kread-article-header {
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
    margin-bottom: var(--space-5);
}

.kread-article-title {
    font-size: var(--kread-title1);
    font-weight: 700;
    line-height: 1.2;
    text-wrap: balance;
    margin: 0 0 var(--space-2);
}

.kread-article-meta {
    margin: 0;
    font-size: var(--kread-footnote);
    color: var(--color-text-muted);
}

/* R42 -- explains a shortened issue rather than letting it read as a bug. */
.kread-tier-note {
    margin: var(--space-3) 0 0;
    padding: var(--space-2) var(--space-3);
    border-left: 3px solid var(--color-border);
    font-size: var(--kread-footnote);
    color: var(--color-text-muted);
}

.kread-badge-archived {
    font-size: var(--kread-caption);
    color: var(--color-text-tertiary);
}

/* R62 -- the undo affordance sits above the article, where the eye lands
   after the redirect, not at the bottom of a long page. */
.kread-undo-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3);
    margin-bottom: var(--space-4);
    background: var(--color-surface);
    border-radius: var(--radius-md);
    font-size: var(--kread-subhead);
}

.kread-article-body {
    font-size: var(--kread-reading-size);
    line-height: var(--kread-reading-leading);
    /* `anywhere`, not `break-word`. They break lines identically; the
       difference is that `anywhere` also lets the browser count a long word
       as narrow when working out the SMALLEST a container can be. Inside a
       newsletter's table cell that is the whole game — with `break-word` one
       long word sets a floor under the cell's width and the table grows
       until it no longer fits the phone. U22 changed this to `break-word` on
       taste and reintroduced exactly the sideways scrolling the original
       file docstring says this stylesheet exists to prevent. */
    overflow-wrap: anywhere;
}

/* Nothing a publisher wrote may be wider than the column it is read in.
   Newsletter HTML is full of `width="600"` and `style="width:600px"`, and a
   stylesheet `width` cannot beat an inline one — but `max-width` is a
   different property, so it caps the used width whatever the author asked
   for, without a specificity fight or an !important. */
.kread-article-body * {
    max-width: 100%;
}

.kread-article-body p {
    margin: 0 0 0.9em;
}

.kread-article-body h1,
.kread-article-body h2,
.kread-article-body h3 {
    /* Newsletter markup carries its own headings; scale them to the
       reading size so they stay in proportion at every text-size step. */
    font-size: 1.22em;
    line-height: 1.3;
    margin: 1.5em 0 0.5em;
}

.kread-article-body li {
    margin-bottom: 0.4em;
}

.kread-article-body img,
.kread-image-expanded {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-md);
}

/* Wide content inside a newsletter's own markup scrolls in its own box --
   the page body must never scroll sideways on a phone. */
.kread-article-body table,
.kread-article-body pre {
    display: block;
    max-width: 100%;
    overflow-x: auto;
}

/* ---------------------------------------------------------------------
   Layout tables are unrolled into one column on a phone (U23)
   ---------------------------------------------------------------------
   Almost every newsletter is built as nested tables at a fixed 600px, not
   because the content is tabular but because that is how HTML email has
   always been laid out. Giving those a sideways scroll box, as the rule
   above does, is right for a real data table and wrong for the paragraph
   Kevin is trying to read: the sentence keeps its 600px line and runs off
   the screen instead of wrapping, and raising the reading size makes it
   worse.

   So a LAYOUT table stops being a table on a narrow screen and becomes a
   stack of blocks, which is what its content wanted in the first place.
   Real data tables keep the scroll box, because unrolling a grid of numbers
   into one column destroys the only thing that made it readable. Telling
   the two apart needs to count rows and cells, which CSS cannot do -- the
   `kread-table-linear` class is put on by static/js/article.js.
   ------------------------------------------------------------------ */

@media (max-width: 768px) {
    /* The publisher's fixed layout, overruled.
       Newsletter markup pins widths INLINE — `style="max-width:540px"`,
       `min-width`, `width:600px` — and an inline declaration beats any
       stylesheet rule on the same property however specific. `!important`
       is the only thing that wins, and this is the case it exists for: the
       page is being re-laid-out for a screen its author never targeted.
       Measured on a 40-article sample, this and the unrolling below take
       the articles that force sideways scrolling from 8 to 0.

       A genuine data table is exempt. It keeps its own scroll box (the rule
       further up), because squashing a grid of numbers into 361px destroys
       the alignment that made it a table in the first place. `.kread-table-
       scroll` is applied by static/js/article.js; in the sampled corpus it
       matched 1 table in 539, which is the ratio this whole section is
       built around. */
    .kread-article-body *:not(.kread-table-scroll):not(.kread-table-scroll *) {
        max-width: 100% !important;
        min-width: 0 !important;
    }

    .kread-article-body table.kread-table-linear,
    .kread-article-body table.kread-table-linear > thead,
    .kread-article-body table.kread-table-linear > tbody,
    .kread-article-body table.kread-table-linear > tfoot,
    .kread-article-body table.kread-table-linear > tr,
    .kread-article-body table.kread-table-linear > thead > tr,
    .kread-article-body table.kread-table-linear > tbody > tr,
    .kread-article-body table.kread-table-linear > tfoot > tr,
    .kread-article-body table.kread-table-linear > tr > td,
    .kread-article-body table.kread-table-linear > tr > th,
    .kread-article-body table.kread-table-linear > thead > tr > td,
    .kread-article-body table.kread-table-linear > thead > tr > th,
    .kread-article-body table.kread-table-linear > tbody > tr > td,
    .kread-article-body table.kread-table-linear > tbody > tr > th,
    .kread-article-body table.kread-table-linear > tfoot > tr > td,
    .kread-article-body table.kread-table-linear > tfoot > tr > th {
        display: block;
        width: auto;
        max-width: 100%;
        /* The author's fixed height belongs to a 600px layout that no
           longer exists. */
        height: auto;
        overflow-x: visible;
        white-space: normal;
    }

    /* A cell that was one column of a side-by-side pair now sits above the
       other, so its old right-hand padding reads as a stray indent. */
    .kread-article-body table.kread-table-linear td,
    .kread-article-body table.kread-table-linear th {
        padding-left: 0;
        padding-right: 0;
        text-align: left;
    }
}

/* R51 -- the collapsed placeholder. Deliberately full-width and tall
   enough to be a comfortable thumb target. */
.kread-image {
    margin: var(--space-4) 0;
}

.kread-image-toggle {
    display: block;
    width: 100%;
    min-height: 44px;
    padding: var(--space-3);
    background: var(--color-surface);
    color: var(--color-text-muted);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-md);
    font-size: var(--kread-footnote);
    text-align: left;
    cursor: pointer;
    transition: background var(--transition-fast);
}

.kread-image-toggle:hover {
    background: var(--color-bg);
    color: var(--color-text);
}

.kread-article-imagenote {
    font-size: var(--kread-footnote);
    color: var(--color-text-tertiary);
    margin: var(--space-4) 0 0;
}

/* Save / Print / Archive / Original / images toggle. Moved from below the
   body to the top of the article and PINNED: on a long newsletter the
   controls used to sit several screens down, so "archive this" meant
   scrolling to the end first. Sticky works here because the swipe wrapper
   clips with `overflow: clip`, not `hidden` (see the note on
   .kread-article-swipe below) — the same fact the swipe label relies on.
   56px is .nav-header's fixed height in lifeos-base.css; the bar parks
   directly beneath it. z-index sits under the nav's 100 so the two never
   fight, and the opaque background is what stops article text showing
   through while it is pinned. */
.kread-article-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
    padding: var(--space-3) 0;
    margin-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
    position: sticky;
    top: 56px;
    z-index: 50;
    background: var(--color-bg);
}

.kread-notes {
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

.kread-notes-heading {
    font-size: var(--kread-title3);
    font-weight: 600;
    margin: 0 0 var(--space-3);
}

.kread-note-list {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.kread-note {
    padding: var(--space-3);
    background: var(--color-surface);
    border-radius: var(--radius-md);
    font-size: var(--kread-subhead);
    overflow-wrap: anywhere;
}

.kread-note-form textarea {
    width: 100%;
    padding: var(--space-3);
    margin-bottom: var(--space-2);
    background: var(--color-bg);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font: inherit;
    /* At or above 16px, so focusing it does not make iOS zoom the page. */
    font-size: var(--kread-callout);
    resize: vertical;
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ===========================================================================
   U19 -- routing to other stores (R20, AE8)
   ===========================================================================
   Marking is instant, landing is not: a scheduled job does the delivery.
   So the state is SHOWN rather than implied by the button, and the three
   states are told apart by colour as well as by word -- a "failed" that
   reads the same as "landed" at a glance is the failure Kevin never
   notices.
   ------------------------------------------------------------------------ */

.kread-routing {
    padding-top: var(--space-4);
    margin-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

.kread-routing-list {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.kread-routing-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    background: var(--color-surface);
    border-radius: var(--radius-md);
    font-size: var(--kread-subhead);
}

.kread-routing-dest {
    font-weight: 600;
}

.kread-routing-ref,
.kread-routing-error {
    font-size: var(--kread-caption);
    color: var(--color-text-muted);
    overflow-wrap: anywhere;
}

.kread-badge-queued,
.kread-badge-processing {
    font-size: var(--kread-caption);
    color: var(--color-text-tertiary);
}

/* The theme tokens, with no literal fallback: lifeos-base.css defines both
   of these in light and dark, and a hardcoded fallback is exactly the
   thing that renders correctly in one theme and illegibly in the other. */
.kread-badge-processed {
    font-size: var(--kread-caption);
    color: var(--color-success);
}

.kread-badge-failed {
    font-size: var(--kread-caption);
    font-weight: 600;
    color: var(--color-error);
}

.kread-routing-form {
    display: flex;
    gap: var(--space-2);
    align-items: center;
}

.kread-routing-form select {
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font: inherit;
    font-size: var(--kread-callout);
}

/* ===========================================================================
   U16 -- the offline explanation page (R24, AE9)
   ===========================================================================
   Reads as a calm statement, not an error. Nothing has broken and nothing
   is lost -- the browser cleared a copy -- so it gets no alert styling and
   no red. An error colour here would teach Kevin to distrust a reader that
   is working exactly as designed.
   ------------------------------------------------------------------------ */

.kread-offline {
    max-width: 34rem;
    padding: var(--space-6) 0;
}

.kread-offline-heading {
    font-size: var(--kread-title2);
    font-weight: 600;
    margin: 0 0 var(--space-3);
}

.kread-offline-body {
    margin: 0 0 var(--space-5);
    color: var(--color-text-muted);
    line-height: 1.6;
}

/* ===========================================================================
   U22 -- the swipe gesture (R19, revisited)
   ===========================================================================
   The gesture shipped in U11 archived on a swipe in EITHER direction, bound
   only to the article page, with no feedback of any kind. On an iPhone that
   is three separate problems:

   1. A rightward swipe is iOS's own back gesture. Binding archive to it put
      the reader in a race with the system -- and in an installed
      Home-Screen app, where there is no browser chrome, that gesture is the
      ONLY way back. Archive is now leftward only, which is also the
      direction iOS Mail and every other list app uses for a row action.
   2. Without `touch-action`, Safari has to guess whether a horizontal drag
      belongs to the page or to the element, and it guesses "page". Setting
      `pan-y` on the surface says: vertical is yours, horizontal is mine.
      This is the single change that makes the gesture register at all.
   3. Nothing moved under the finger, so a swipe that fell short of the
      threshold was indistinguishable from a reader that had no gesture. The
      surface now tracks the finger and the action behind it arms visibly.

   The action panel is deliberately calm -- surface grey, no destructive red.
   Archiving is filing, not deleting, and the undo window is ten minutes.
   --------------------------------------------------------------------- */

.kread-swipe {
    position: relative;
    overflow: hidden;
}

.kread-swipe-action {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 0 var(--space-4);
    background: var(--color-surface);
    font-size: var(--kread-subhead);
    font-weight: 600;
    color: var(--color-text-tertiary);
    /* Never a tap target: it exists to be revealed, and a stray tap on it
       must not archive anything. */
    pointer-events: none;
}

/* Past the commit threshold. Colour AND weight change together, because a
   reveal that only deepens in shade is not a signal on a bright pavement. */
.kread-swipe.is-armed .kread-swipe-action {
    color: var(--color-primary);
}

.kread-swipe-surface {
    position: relative;
    background: var(--color-bg);
    /* The line that makes the whole thing work -- see (2) above. */
    touch-action: pan-y;
    -webkit-tap-highlight-color: transparent;
    -webkit-touch-callout: none;
}

/* Only while a gesture is settling, so the surface tracks the finger
   frame-for-frame during the drag itself. */
.kread-swipe.is-settling .kread-swipe-surface {
    transition: transform 0.2s ease-out;
}

/* The committed row leaves: it collapses rather than just fading, so the
   list closes up and the eye is not left holding a gap. */
.kread-swipe.is-leaving {
    transition: opacity 0.2s ease, transform 0.2s ease;
    opacity: 0;
    transform: translateX(-100%);
}

/* The article is one tall element, so a vertically-centred label would sit
   somewhere off-screen. Sticky keeps it beside the thumb wherever the page
   happens to be scrolled -- but `overflow: hidden` would make this element
   the label's scroll container, and a container that never scrolls makes
   sticky a no-op. `overflow: clip` clips identically WITHOUT becoming one.
   The `hidden` line stays as the fallback: an older engine ignores `clip`,
   keeps the clipping, and merely parks the label at the top. */
.kread-article-swipe {
    overflow: hidden;
    overflow: clip;
}

.kread-article-swipe .kread-swipe-action {
    align-items: flex-start;
}

.kread-article-swipe .kread-swipe-label {
    position: sticky;
    top: 45vh;
}

@media (prefers-reduced-motion: reduce) {
    .kread-swipe.is-settling .kread-swipe-surface,
    .kread-swipe.is-leaving {
        transition: none;
    }
}

/* Brief and feed rows carry their padding on the item. A translating
   surface would slide out from under it and leave the row's own background
   showing, so for swipeable rows the padding moves inside. */
.kread-brief-item.kread-swipe,
.kread-feed-item.kread-swipe {
    padding: 0;
}

.kread-brief-item.kread-swipe > .kread-swipe-surface,
.kread-feed-item.kread-swipe > .kread-swipe-surface {
    padding: var(--space-4) var(--space-2);
}

/* ---------------------------------------------------------------------
   Undo, after a swipe on a list
   ---------------------------------------------------------------------
   The article page redirects into its own undo bar (R62). A list does not
   navigate, so the same ten-minute window needs somewhere to live. It sits
   above the bottom nav rather than over it -- covering the nav with a
   transient strip is how a reader loses its way out.
   ------------------------------------------------------------------ */

.kread-toast {
    position: fixed;
    left: var(--space-4);
    right: var(--space-4);
    bottom: calc(72px + env(safe-area-inset-bottom, 0px));
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    max-width: 32rem;
    margin: 0 auto;
    padding: var(--space-3) var(--space-4);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    font-size: var(--kread-subhead);
    color: var(--color-text);
}

.kread-toast-message {
    overflow-wrap: break-word;
}

@media (min-width: 769px) {
    /* No bottom nav to clear at desktop width. */
    .kread-toast {
        bottom: var(--space-5);
    }
}

/* ===========================================================================
   U22 -- the reading-size control
   ===========================================================================
   Two steppers rather than a slider or a menu: the whole interaction is
   "a bit bigger", and a stepper answers that in one tap without covering
   the text being judged. The current step is named, not just implied, so
   the control says where it already is before it is touched.
   --------------------------------------------------------------------- */

.kread-textsize {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    margin-left: auto;
}

.kread-textsize-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--kread-tap-min);
    min-height: var(--kread-tap-min);
    padding: 0 var(--space-2);
    background: transparent;
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font-family: inherit;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color var(--transition-fast), border-color var(--transition-fast);
}

.kread-textsize-btn:active:not(:disabled) {
    color: var(--color-primary);
    border-color: var(--color-primary);
}

.kread-textsize-btn:disabled {
    opacity: 0.35;
    cursor: default;
}

/* The two ends of the range, drawn at the sizes they set. */
.kread-textsize-btn[data-kread-textsize-step="-1"] { font-size: var(--kread-footnote); }
.kread-textsize-btn[data-kread-textsize-step="1"]  { font-size: var(--kread-title3); }

.kread-textsize-value {
    min-width: 3.5em;
    text-align: center;
    font-size: var(--kread-caption);
    color: var(--color-text-tertiary);
}

/* The header becomes a row: title, then meta and the control side by side. */
.kread-article-headrow {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-2);
}
