/* Presentation for the kanban module's web board UI (M-626): the boards index chrome and the board
   screen — columns left-to-right, card chips inside them. Content-only: the page renders inside the
   platform shell's content frame, and the shared primitives it uses (page header, buttons, form fields,
   the badge pill, the modal) all come from the platform layer. Everything here is scoped under the
   `kanban-` class prefix to avoid collisions.

   Colors, spacing, radii, shadows and type come from the platform design tokens (tokens.css), each with
   a literal fallback — themeable without touching markup (coding-standards §4/§12).

   MOBILE FIRST: the columns run on a horizontal rail that SCROLLS sideways inside its own region at a
   phone width (the native board idiom — a column never reflows into a taller stack, which would push the
   drag targets off-screen); the page itself never scrolls horizontally (§12). At ≥48rem — the app's one
   shell breakpoint — columns settle at their comfortable desktop width. */

/* ---- Board frame -------------------------------------------------------------------------------- */
.kanban-board {
    /* The horizontal scroll REGION: overflow lives here, never on the body. Padding keeps the last
       column's drop shadow and focus ring inside the scrollable box. */
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.kanban-board__columns {
    display: flex;
    align-items: flex-start;
    gap: var(--platform-space-sm, 0.5rem);
    margin: 0;
    padding: 0 0 var(--platform-space-md, 1rem);
    list-style: none;
    overflow-x: auto;
    /* Keep scrolled-to columns clear of the rounded frame edge while tabbing through. */
    scroll-padding-inline: var(--platform-space-xs, 0.25rem);
}

/* ---- One column ---------------------------------------------------------------------------------- */
/* A column IS a `.platform-card` (M-654, Dude): the markup carries the shared class, so the surface,
   border, radius, shadow AND the M-651 theme paint — the hairline fading across the top edge, the vertical
   wash — all arrive from the one card primitive rather than from a second definition of "what a box looks
   like" here (§3).

   ONE property is re-pointed, and it is the one a column must differ on: the card's `background-color`.
   A card paints itself on `--platform-color-surface`, which is exactly what the CHIPS inside this column
   are painted on — leave it and every chip vanishes into its own column. So the column takes the muted
   surface a step behind them, which is also what makes the stack read as cards ON something rather than a
   list of borders. The background IMAGE layers (topline, wash) are untouched by that, so the paint the
   card primitive brings still lands. */
.kanban-column {
    /* A phone shows one generous column plus a visible sliver of the next (the swipe affordance); a
       desktop column settles at a readable fixed width. Flex items refuse to shrink below it, so the
       RAIL scrolls instead of the columns squeezing. */
    flex: 0 0 clamp(13rem, 72vw, 17rem);
    display: flex;
    flex-direction: column;
    gap: var(--platform-space-xs, 0.25rem);
    min-width: 0;
    background-color: var(--platform-color-surface-muted, #ecf0f1);
    padding: var(--platform-space-sm, 0.5rem);
}

/* THE COLUMN'S TONE (M-654). `--platform-card-topline` is the token the card primitive's top hairline
   reads; at `:root` it is a no-op, so a column that said nothing would carry no line at all. Every column
   therefore declares the DEFAULT here — the theme's own accent — which is what "unset means the theme
   colour" means in practice: not "no colour", but "whatever this theme's accent is", re-tuned per theme
   because it is a token and not a hex.

   The swimlane view's column heads declare it too, so the two views of the same board agree on what a
   column's colour is. */
.kanban-column,
.kanban-lanes__state {
    --platform-card-topline: var(--platform-color-accent, #16a085);
}

/* An administrator's choice, as a palette SLUG that became a CSS selector (the shared ColourPalette) —
   never an inline style and never a raw hex, so each tone resolves through a design token and follows the
   theme instead of fighting it. One rule per slug, matching BOTH surfaces that carry a tone, so a column
   can never read teal on the board and something else in the lanes view. A value outside the palette
   matches nothing and leaves the theme accent — the honest fallback, since the validator refuses to store
   one in the first place. */
.kanban-board [data-colour="teal"] {
    --platform-card-topline: var(--platform-color-accent, #16a085);
}

.kanban-board [data-colour="green"] {
    --platform-card-topline: var(--platform-color-ok, #27ae60);
}

/* `blue` is the ONE slug with no role token behind it, so it borrows the dataviz ramp's mid step — the
   same place the absences badge takes its blue from, so the app is at least consistent with itself. The
   ramp is an ORDINAL scale though, and its hue is each theme's free choice: in `matrix`, `dracula` and
   `dark-blue` this step is not blue at all. That is a promise the palette cannot keep and M-656 exists to
   settle it properly; it is written down here rather than left to be re-discovered. */
.kanban-board [data-colour="blue"] {
    --platform-card-topline: var(--platform-color-viz-3, #4068c0);
}

.kanban-board [data-colour="amber"] {
    --platform-card-topline: var(--platform-color-warning, #d9822b);
}

.kanban-board [data-colour="red"] {
    --platform-card-topline: var(--platform-color-error, #cf222e);
}

.kanban-board [data-colour="grey"] {
    --platform-card-topline: var(--platform-color-text-muted, #7f8c8d);
}

@media (min-width: 48rem) {
    .kanban-column {
        flex-basis: 17rem;
    }
}

.kanban-column__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--platform-space-sm, 0.5rem);
    padding: var(--platform-space-xs, 0.25rem) var(--platform-space-xs, 0.25rem) 0;
}

.kanban-column__name {
    margin: 0;
    font-size: var(--platform-font-size-md, 1rem);
    font-weight: 600;
    color: var(--platform-color-text, #2c3e50);
    overflow-wrap: anywhere;
}

/* The queue-jump marker rides the name as a quiet pill; its title attribute carries the sentence. */
.kanban-column__jump {
    margin-inline-start: var(--platform-space-xs, 0.25rem);
}

.kanban-column__count {
    flex-shrink: 0;
}

/* ---- The card list = the drop surface ------------------------------------------------------------ */
.kanban-column__cards {
    display: flex;
    flex-direction: column;
    gap: var(--platform-space-xs, 0.25rem);
    margin: 0;
    padding: 0;
    list-style: none;
    border-radius: var(--platform-radius-lg, 0.5rem);
    /* No surface of its own since M-654: the COLUMN is the muted surface now, and a second one here would
       be a box drawn inside an identical box. The empty column still has its honest floor to aim at — the
       column card's own border and this min-height — and a drag still lights the drop ring below. */
    min-height: 3.5rem;
    transition: box-shadow 150ms ease;
}

.kanban-column__cards--drop-target {
    box-shadow: inset 0 0 0 2px var(--platform-color-accent, #16a085);
}

/* ---- One card chip ------------------------------------------------------------------------------- */
.kanban-card {
    background: var(--platform-color-surface, #ffffff);
    border: 1px solid var(--platform-color-border, #cfd8dc);
    border-radius: var(--platform-radius-md, 0.375rem);
    box-shadow: var(--platform-shadow-sm, 0 1px 2px rgba(44, 62, 80, 0.06));
    padding: var(--platform-space-sm, 0.5rem) var(--platform-space-sm, 0.5rem) var(--platform-space-xs, 0.25rem);
}

/* The whole chip is the drag surface — but only where a drag can actually begin. Wherever the render
   carries no write surface — a readonly-frozen board AND a mere reader's view of an editable one — the
   view puts on `kanban-board--readonly`, no controller is mounted, and the affordance disappears with
   the capability (M-205: difference by affordance, never prose). */
.kanban-board:not(.kanban-board--readonly) .kanban-card {
    cursor: grab;
}

.kanban-board:not(.kanban-board--readonly) .kanban-card:active {
    cursor: grabbing;
}

.kanban-board--readonly .kanban-card {
    cursor: default;
}

/* The facade's placeholder at the future drop location. */
.kanban-card--ghost {
    opacity: 0.45;
    border-style: dashed;
}

/* The title is also the edit-modal opener on a writable board — styled back to plain text so it reads as
   the card, not as a widget; the grab cursor plus the hover underline carry the affordance. */
.kanban-card__title {
    display: block;
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    text-align: start;
    color: inherit;
    overflow-wrap: anywhere;
}

button.kanban-card__title:hover {
    text-decoration: underline;
    text-decoration-color: var(--platform-color-border, #cfd8dc);
}

button.kanban-card__title:focus-visible {
    outline: 2px solid var(--platform-color-accent, #16a085);
    outline-offset: 1px;
}

/* ---- The chip's three trimmings (M-653) ---------------------------------------------------------
   A card reads top-down as: what it IS (the meta header), what it SAYS (title + body), and how OLD it is
   (the footer). Each trimming is optional in the markup, so none of them reserves space when its card
   carries nothing — a bare title card stays exactly as compact as it was before this restyle. */

/* The meta header: id at the start, priority next, the assignee disc pushed to the far end. `margin-left:
   auto` on the disc rather than `justify-content: space-between`, so the id and the priority stay a
   related PAIR at the start instead of being flung to opposite ends when the disc is absent. */
.kanban-card__head {
    display: flex;
    align-items: center;
    gap: var(--platform-space-xs, 0.25rem);
    margin-bottom: var(--platform-space-xs, 0.25rem);
}

/* The card's quotable handle ("M-653"). Monospace-ish tracking and a muted tone: it is an identifier to
   find the card by, never the thing to read first. */
/* The disc sits at the far END of the chip's head row. SCOPED to that row on purpose: the same
   `.kanban-card__avatar` block is reused by the swimlane header, where it is the FIRST child — an
   unscoped `margin-left: auto` there would shove the person's name and count across the lane. */
.kanban-card__head .kanban-card__avatar {
    margin-left: auto;
}

.kanban-card__id {
    flex-shrink: 0;
    color: var(--platform-color-text-muted, #57606a);
    font-size: var(--platform-font-size-xs, 0.75rem);
    font-variant-numeric: tabular-nums;
}

/* Two lines of the description, then an ellipsis. The full text stays in the DOM (the view does not
   truncate), so find-in-page still matches words the clamp hides. `-webkit-` prefixed properties are the
   interoperable spelling of line clamping — every engine we target implements the prefixed form, and the
   unprefixed `line-clamp` is listed alongside so this stops needing the prefix on its own. */
.kanban-card__body {
    margin: 0;
    color: var(--platform-color-text-muted, #57606a);
    font-size: var(--platform-font-size-sm, 0.875rem);
    line-height: 1.4;
    overflow: hidden;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

.kanban-card__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--platform-space-sm, 0.5rem);
    margin-top: var(--platform-space-xs, 0.25rem);
}

.kanban-card__age {
    color: var(--platform-color-text-muted, #57606a);
    font-size: var(--platform-font-size-xs, 0.75rem);
}

/* ---- The column's "there are more" footer (M-653) -----------------------------------------------
   A column renders its first N cards and then says so. Deliberately WORDS, not a silent cut: a column
   that quietly stops at 30 is indistinguishable from one that holds 30, which is the same class of lie
   as a truncation with no ellipsis. */
.kanban-column__more {
    display: flex;
    flex-direction: column;
    gap: var(--platform-space-xs, 0.25rem);
    margin-top: var(--platform-space-sm, 0.5rem);
    padding: var(--platform-space-sm, 0.5rem);
    border: 1px dashed var(--platform-color-border, #cfd8dc);
    border-radius: var(--platform-radius-md, 0.375rem);
    color: var(--platform-color-text-muted, #57606a);
    font-size: var(--platform-font-size-xs, 0.75rem);
    text-align: center;
}

.kanban-card__meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--platform-space-sm, 0.5rem);
    margin-top: var(--platform-space-xs, 0.25rem);
}

.kanban-card__priority {
    flex-shrink: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The assignee avatar: initials on a quiet accent-tinted disc; the full name travels in the title.
   A TEAM's disc (M-665h) carries no initials — it shows the app's shared people glyph instead, drawn by
   `.platform-principal-option--group` (principal-picker.css) and marked identically wherever a "who" is
   named. Nothing about that marking is repeated here; only the two things a DISC does differently from a
   row of text, below. */
.kanban-card__avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: var(--platform-radius-pill, 999px);
    background: var(--platform-color-accent-surface, #d1f2eb);
    color: var(--platform-color-accent-strong, #12876e);
    font-size: var(--platform-font-size-xs, 0.75rem);
    font-weight: 600;
    letter-spacing: var(--platform-letter-spacing, 0.01em);
}

/* HOST-CONTEXT CORRECTION — the shared marking assumes a label follows the glyph, and inside a disc
   nothing does: the trailing gap it leaves for one would push the glyph off centre, and a 0.6 opacity
   reads as smudged at this size against the accent-tinted fill. Both are the disc's business, not the
   marking's, which is why they are set here and not in principal-picker.css. */
.kanban-card__avatar.platform-principal-option {
    --platform-principal-glyph-opacity: 1;
}

.kanban-card__avatar.platform-principal-option::before {
    margin-inline-end: 0;
}

/* ---- Column quick-add control -------------------------------------------------------------------- */
.kanban-column__add {
    align-self: stretch;
    min-height: var(--platform-input-height, 1.9rem);
    font-weight: 600;
    line-height: 1;
}

@media (pointer: coarse) {
    .kanban-column__add {
        min-height: var(--platform-tap-target, 2.75rem);
    }
}

/* ---- Board meta badges + unavailable state ------------------------------------------------------- */
/* The badge row under the page header (M-629): source flavour + freeze pills, wrapping harmlessly on
   a narrow screen since neither badge carries action. */
.kanban-board__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--platform-space-xs, 0.25rem);
    margin-bottom: var(--platform-space-sm, 0.5rem);
}

/* A file board whose bound storage Location cannot serve it: an admin-fixable deployment state gets
   an admin-shaped notice on an error-tinted token surface — never a bare page, never a 500. */
.kanban-board__unavailable {
    margin-bottom: var(--platform-space-md, 1rem);
    padding: var(--platform-space-md, 1rem);
    border-radius: var(--platform-radius-lg, 0.5rem);
    background: var(--platform-color-error-surface, #fdecea);
    color: var(--platform-color-error-text, #c0392b);
}

.kanban-board__unavailable p:first-child {
    margin: 0;
    font-weight: 600;
}

.kanban-board__unavailable p + p {
    margin: var(--platform-space-xs, 0.25rem) 0 0;
    font-size: var(--platform-font-size-sm, 0.9rem);
}

/* The frozen board's admin hint (M-631): a quiet aside under the badges, with the unfreeze path linked. */
.kanban-board__frozen-hint {
    margin: calc(-1 * var(--platform-space-xs, 0.25rem)) 0 var(--platform-space-md, 1rem);
    font-size: var(--platform-font-size-sm, 0.9rem);
    color: var(--platform-color-text-muted, #636e6f);
}

/* ---- Modal forms (quick-add + edit) -------------------------------------------------------------- */
.kanban-card-form {
    max-width: none;
    gap: var(--platform-space-sm, 0.5rem);
}

.kanban-card-form .platform-form__row {
    align-items: flex-end;
}

/* The honest "new cards land in :column" note: quieter than the fields, inline with them. */
.kanban-card-form__hint {
    margin: 0;
    font-size: var(--platform-font-size-sm, 0.9rem);
    color: var(--platform-color-text-muted, #636e6f);
}

/* ---- Boards index ------------------------------------------------------------------------------- */
/* Nothing beyond the shared primitives is needed for the grid itself; only the create modal's form gets
   the modal-body treatment (fill the dialog instead of sitting at the reading measure). */
.kanban-board-form {
    max-width: none;
    gap: var(--platform-space-sm, 0.5rem);
}

/* ---- Board administration (M-631) ---------------------------------------------------------------- */
/* The administer-gated create/edit surface. The shared primitives (page header, breadcrumbs, static
   table, bulk-form bar, form fields) carry almost everything; only the page's own arrangement and its
   status/warning boxes are the module's to style — all token-driven (§4/§12). */
.kanban-admin__status,
.kanban-admin__warning {
    margin-bottom: var(--platform-space-md, 1rem);
    padding: var(--platform-space-md, 1rem);
    border-radius: var(--platform-radius-lg, 0.5rem);
    border: 1px solid transparent;
}

.kanban-admin__status {
    background: color-mix(in srgb, var(--platform-color-ok, #27ae60) 12%, var(--platform-color-surface, #ffffff));
    border-color: var(--platform-color-ok, #27ae60);
}

.kanban-admin__warning {
    background: color-mix(in srgb, var(--platform-color-warning, #d9822b) 14%, var(--platform-color-surface, #ffffff));
    border-color: var(--platform-color-warning, #d9822b);
}

/* The source toggle's fieldset reads as one answer, its two options side by side on a wide screen. */
.kanban-admin__source .platform-radio {
    margin-inline-end: var(--platform-space-md, 1rem);
}

/* The file-only binding group sits indented under the toggle it answers to, so the relationship reads
   without a word; `is-hidden` is what the shared driver-group helper flips (plus `disabled` inputs). */
.kanban-admin__binding {
    padding-inline-start: var(--platform-space-lg, 1.5rem);
    border-inline-start: 2px solid var(--platform-color-border, #cfd8dc);
}

.kanban-admin__binding.is-hidden {
    display: none;
}

/* The database-only pinned-people group answers the same toggle from the other side; same indent,
   same `is-hidden` flip (plus `disabled` inputs) from the shared driver-group helper. */
.kanban-admin__lane-pins {
    padding-inline-start: var(--platform-space-lg, 1.5rem);
    border-inline-start: 2px solid var(--platform-color-border, #cfd8dc);
}

.kanban-admin__lane-pins.is-hidden {
    display: none;
}

/* A database board has no folder bindings: while Database is selected on create, the folder column
   hides (the controller disables its inputs too, so nothing stale rides along in the POST). */
.kanban-admin--no-folders .kanban-admin__folder-input {
    display: none;
}

/* The Order cell: the derived position number beside the move pair — quiet, one line (§15). */
.kanban-admin__position {
    display: inline-block;
    min-width: 1.25rem;
    color: var(--platform-color-text-muted, #636e6f);
    font-size: var(--platform-font-size-sm, 0.9rem);
}

/* The column-tone picker is a compact-column control: it must not stretch the cell it sits in the way a
   100%-wide select would, and a colour name is a short word. */
.kanban-admin__colour {
    width: auto;
    min-width: 8rem;
}

/* WHAT THE FLAGS MEAN (M-654). Three checkbox columns with nothing but their header to explain them is
   the page Dude found: `Entry` decides where every new card on the board is born, and a header cannot say
   that. A definition list, because that is what this is — and on the page rather than in `title`
   attributes, which a touch device never shows. */
.kanban-admin__legend {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--platform-space-xs, 0.25rem) var(--platform-space-md, 1rem);
    margin: var(--platform-space-md, 1rem) 0 0;
    font-size: var(--platform-font-size-sm, 0.9rem);
}

.kanban-admin__legend dt {
    font-weight: 600;
    color: var(--platform-color-text, #2c3e50);
}

.kanban-admin__legend dd {
    margin: 0;
    color: var(--platform-color-text-muted, #636e6f);
}

/* On a phone the two-track grid gives the definition ~15 characters, so the pair stacks instead. */
@media (max-width: 30rem) {
    .kanban-admin__legend {
        grid-template-columns: 1fr;
    }

    .kanban-admin__legend dd {
        margin-block-end: var(--platform-space-xs, 0.25rem);
    }
}

/* The add-a-column modal's own form: plain stacked fields, so the modal reads top to bottom. */
.kanban-admin__add-state .platform-form__field {
    margin-block-end: var(--platform-space-sm, 0.5rem);
}

.kanban-admin__hint {
    margin-top: calc(-1 * var(--platform-space-xs, 0.25rem));
    font-size: var(--platform-font-size-sm, 0.9rem);
    color: var(--platform-color-text-muted, #636e6f);
}

/* The per-row delete forms are pure targets for their row's `form=`-attributed button — no layout. */
.kanban-admin__delete-form {
    display: none;
}

/* ---- Swimlanes (per-person grouping) ------------------------------------------------------------- */
/* The view toggle: two quiet pills, the current one lit. A link pair, not a widget — the mode is a
   URL fact (?lanes=0|1), so back/forward and sharing a link keep the chosen grouping. It always
   lives inside .kanban-view-bar, which owns the spacing around it. */
.kanban-view-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--platform-space-sm, 0.5rem);
    margin: 0 0 var(--platform-space-sm, 0.5rem);
}

.kanban-view-toggle {
    display: flex;
    gap: var(--platform-space-xs, 0.25rem);
}

.kanban-view-toggle a {
    padding: var(--platform-space-xs, 0.25rem) var(--platform-space-md, 1rem);
    border-radius: var(--platform-radius-pill, 999px);
    font-size: var(--platform-font-size-sm, 0.9rem);
    text-decoration: none;
    color: var(--platform-color-text-muted, #636e6f);
    background: var(--platform-color-surface-muted, #ecf0f1);
}

.kanban-view-toggle a[aria-current="true"] {
    background: var(--platform-color-accent-surface, #d1f2eb);
    color: var(--platform-color-accent-strong, #12876e);
}

/* The lanes frame mirrors the columns rail: states scroll horizontally as one grid so every lane's
   cells stay aligned with the head row above them — a lane never drifts out of its column. The
   head row's leading corner span (unstyled by design) occupies that first track, so state names
   land over their own cells. */
.kanban-lanes {
    display: flex;
    flex-direction: column;
    gap: var(--platform-space-sm, 0.5rem);
    overflow-x: auto;
    padding-bottom: var(--platform-space-md, 1rem);
}

/* Column widths match .kanban-column's clamp so switching views keeps the same rhythm. */
.kanban-lanes__head,
.kanban-lane__cells {
    display: grid;
    grid-template-columns: minmax(9rem, 12rem);
    grid-auto-flow: column;
    grid-auto-columns: clamp(13rem, 72vw, 17rem);
    gap: var(--platform-space-sm, 0.5rem);
    align-items: stretch;
}

@media (min-width: 48rem) {
    .kanban-lanes__head,
    .kanban-lane__cells {
        grid-template-columns: 14rem;
        grid-auto-columns: 17rem;
    }
}

.kanban-lanes__state {
    margin: 0;
    font-size: var(--platform-font-size-md, 1rem);
    font-weight: 600;
    color: var(--platform-color-text, #2c3e50);
    overflow-wrap: anywhere;
    /* The column's tone, in the same visual language as the flat view (M-654): the identical hairline
       gradient the card primitive paints — full strength at the centre, gone at both ends — rather than a
       solid `border-top`, which would be a second, slightly different way of saying the same thing. There
       is no card to hang it on here (a lane column is a grid track, not a box), so the head draws it. */
    padding-top: var(--platform-space-xs, 0.25rem);
    background-image: linear-gradient(90deg, transparent, var(--platform-card-topline), transparent);
    background-repeat: no-repeat;
    background-size: 100% 2px;
    background-position: top center;
}

.kanban-lane {
    border-top: 1px solid var(--platform-color-border, #cfd8dc);
    padding-top: var(--platform-space-sm, 0.5rem);
}

.kanban-lane__head {
    /* Frozen-pane label: the lanes grid scrolls horizontally under many columns, and a header that
         scrolled away with the content leaves rows anonymous (Dude, M-638 follow-up). Sticky keeps
         the person's name pinned at the container's left edge while their cells slide beneath; the
         surface background hides the chips passing underneath. width hugs the content so the sticky
         element doesn't stretch across the whole scrollable span. */
    position: sticky;
    left: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--platform-space-sm, 0.5rem);
    padding: 0 var(--platform-space-xs, 0.25rem) var(--platform-space-xs, 0.25rem);
    background: var(--platform-color-surface, #fff);
    border-radius: var(--platform-radius-sm, 0.25rem);
    width: max-content;
    max-width: 100%;
}

.kanban-lane__avatar {
    flex-shrink: 0;
}

.kanban-lane__name {
    margin: 0;
    font-size: var(--platform-font-size-sm, 0.9rem);
    font-weight: 600;
    color: var(--platform-color-text-muted, #636e6f);
    overflow-wrap: anywhere;
}

/* ---- The full card list (M-653) ------------------------------------------------------------------
   Where a column's "and N more" link goes. The board answers "what is in flight" with its first N cards
   per column; this answers "show me everything", so it is a plain list: no drag, no quick-add, no
   modals. Presentation only; the page is read-only by design. */

/* The column filter reuses the view-toggle's pill vocabulary rather than inventing a second one — both
   are "pick which slice of this board you are looking at", and a reader should not have to learn two
   shapes for one idea. It wraps, because a board may carry nine columns and a phone is 390px wide. */
.kanban-cards__filter {
    display: flex;
    flex-wrap: wrap;
    gap: var(--platform-space-xs, 0.25rem);
    margin-bottom: var(--platform-space-md, 1rem);
}

.kanban-cards__filter a {
    padding: var(--platform-space-xs, 0.25rem) var(--platform-space-md, 1rem);
    border-radius: var(--platform-radius-pill, 999px);
    font-size: var(--platform-font-size-sm, 0.9rem);
    text-decoration: none;
    color: var(--platform-color-text-muted, #636e6f);
    background: var(--platform-color-surface-muted, #ecf0f1);
}

.kanban-cards__filter a[aria-current="true"] {
    background: var(--platform-color-accent-surface, #d1f2eb);
    color: var(--platform-color-accent-strong, #12876e);
}

/* The card's quotable handle, in the same tabular, muted treatment the board chip gives it — the same
   identifier must not read as two different things on two screens. */
.kanban-cards__id {
    color: var(--platform-color-text-muted, #57606a);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The title cell is a row header (<th scope="row">), so it carries the row's weight; the excerpt below
   it is the same first-paragraph summary the board chip shows, one line here because a table row that
   grows to three lines stops being scannable. */
.kanban-cards__title {
    font-weight: 600;
    text-align: start;
}

.kanban-cards__excerpt {
    display: block;
    margin-top: 0.15rem;
    color: var(--platform-color-text-muted, #57606a);
    font-size: var(--platform-font-size-sm, 0.875rem);
    font-weight: 400;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    max-width: 48ch;
}

/* "2 weeks ago" is three words that must stay one line: left to wrap it turned a one-line row into a
   three-line one and stretched the table past its container on every row. */
.kanban-cards__age {
    white-space: nowrap;
}
