/* BOARD drawer.
 *
 * No new colour system — everything is built from the existing tokens
 * (--bg, --bg-card, --bg-input, --border, --text, --text-dim, --accent).
 *
 * See plans/BOARD_PLAN.md §10 for the UX spec.
 */

.board-body {
    /* ---- task vs subtask weight -----------------------------------
     * The two values that separate a task from a subtask, kept together
     * because they are only meaningful relative to each other and to the
     * column behind them (--bg-card, #1a1d27).
     *
     *   column   #1a1d27
     *   subtask  #1f222b   +5 above the column — present, but recessed
     *   task     #31343f  +23 above the column — the solid object
     *
     * These started as one palette step apart (#252830 / #2a2d38) and read
     * as very nearly the same card, so the gap is now ~18 units rather than
     * 5. Off-palette on purpose: the ramp has no rung above --bg-hover, and
     * inventing one inline in two rules would have hidden the relationship
     * that is the whole point. Tune HERE, and keep the subtask above the
     * column — darker than its container reads as a hole, not a card. */
    --board-card-bg: #31343f;
    --board-subcard-bg: #1f222b;

    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    position: relative;
}

/* --------------------------------------------------------------- columns */

/* Horizontal scroll inside the drawer — the same idiom as
 * .unified-layout itself. A board at the 12-column cap therefore works at
 * a 520px drawer width without the columns squashing to slivers, which is
 * what any percentage-width layout would do. */
/* ----------------------------------------------------------- filter people */

/* The people offered under the filter box as you type.
 *
 * ⚠ AN OVERLAY, NOT A ROW. The first version was a strip of faces above the
 * columns, which pushed the whole board down the moment the filter opened —
 * chrome that moves the thing you are reading. Absolutely positioned, so it
 * floats over the board and nothing shifts.
 *
 * `hidden` needs the explicit rule: the attribute works through a
 * UA-stylesheet `display: none`, the weakest declaration there is, so the
 * `.mention-dropdown` class it shares would silently win. */
.board-filter { position: relative; }

.board-filter-people {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    min-width: 200px;
    max-height: 240px;
    overflow-y: auto;
    z-index: 40;
}
.board-filter-people[hidden] { display: none; }

.board-filter-person {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    background: none;
    border: none;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

/* Smaller than the card view's 28px assign target: this is a menu row, and
 * the row itself is the hit area.
 *
 * ⚠ THE CENTRING HAS TO BE DECLARED HERE. `.board-person` sets a size, a
 * radius and a colour but no `display` or alignment — in the card view it is
 * a <button>, and the UA stylesheet centres a button's own text for free.
 * This is a <span>, which inherits none of that, so the initials sat in the
 * top-left corner of the circle. Anything else reusing `.board-person` on a
 * non-button needs these three lines too. */
.board-filter-face {
    width: 22px;
    height: 22px;
    font-size: 9px;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    pointer-events: none;   /* the ROW takes the click, not the face */
}

.board-filter-person-name {
    font-size: 12px;
    color: var(--text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.board-filter-person.is-on .board-filter-person-name { color: var(--text); }

.board-columns {
    display: flex;
    gap: 0;   /* spacing comes from .board-gutter, which is a hover target */
    align-items: flex-start;
    overflow-x: auto;
    overflow-y: hidden;
    /* Pixel-aligned with WRITE. `.write-doc-row` is `padding: 10px 12px 0`
       inside a zero-padding `.write-body`, so the "Drop or pick a
       document" pill's top edge sits 10px below the drawer body and its
       left edge 12px in. The column tops land on exactly the same two
       lines, so switching between the drawers doesn't shift the eye.
       EDIT's `.edit-v1-project-row` uses the same 10/12 for the same
       reason — this is the family rhythm, not a one-off. */
    padding: 10px 12px 14px 12px;
    flex: 1 1 auto;
    min-height: 0;
}

.board-column {
    flex: 0 0 236px;
    width: 236px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    max-height: 100%;
    min-height: 0;
}

.board-column-head {
    display: flex;
    align-items: center;
    gap: 8px;
    /* FIXED height, not padding-derived. The rename input is taller than
       the 11px label it replaces, so a padding-sized header grew by a few
       pixels the moment you clicked to rename — which read as the column
       swelling into a larger lozenge than its neighbours. A fixed box means
       nothing inside can change the column's measurements. */
    /* 38px, not 30. At 30 the 13px-tall name had ~8px of air above and
       below it, which read as cramped against the column's top edge — and
       the first card sat flush against the header's bottom with no band
       between them at all. 38 gives ~12px either side of the name, which
       is the 12px rhythm BREAKDOWN and GENERATE already use. Still fixed
       rather than padding-derived, so the rename input cannot change it. */
    height: 38px;
    box-sizing: border-box;
    /* 18px, not 10px, and the number is derived rather than chosen: it is
       where a CARD's title sits. A card is inset 8px by the column body's
       padding, plus its own 1px border, plus its own 9px padding = 18px
       from the column's inner edge. Matching it means the header's title
       sits directly above the card titles beneath it, and the header's ×
       directly above the cards' ×s — one vertical line down each side of
       the column instead of two staggered ones. The header also stays
       symmetric, 18 left / 18 right. */
    padding: 0 18px;
    flex: 0 0 auto;
    position: relative;   /* containing block for the out-of-flow × */
    /* The whole header is the rename hit target — but `pointer`, not
       `text`.
       A caret advertised a text field that does not exist until you click,
       and it fought the drag: the header is also the column's drag handle,
       so the resting cursor promised the one thing the gesture is least
       likely to be. `pointer` says "this does something", true of both
       actions, and it is what the CARD already uses for the same
       click-or-drag pair — and what Trello puts on a list header.
       Mid-drag, `body.board-dragging` takes over with `grabbing`. */
    cursor: pointer;
}

.board-column-name {
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Inherits the header's `pointer`; declared so the name cannot revert
       to a caret on its own. The rename INPUT that replaces it gets a real
       text cursor from the UA — which is the point at which a caret is
       finally honest. */
    cursor: pointer;
}

.board-column-meta {
    font-size: 11px;
    color: var(--text-dim);
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
}

/* Inline rename input — sized and positioned to sit exactly where the
 * label was, so committing doesn't shift the header. */
.board-column-rename {
    flex: 1 1 auto;
    min-width: 0;
    height: 20px;              /* fits the 30px header with 5px either side */
    box-sizing: border-box;
    background: var(--bg-input);
    border: 1px solid var(--accent);
    border-radius: 4px;
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    line-height: 18px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0 5px;
    outline: none;
}

/* Delete reuses the sub-folder chip's action slot — `.subfolder-actions`
 * + `.subfolder-action-slot` + `.subfolder-delete-btn`. No new component,
 * no new colour: `--text-dim` at rest, `--danger` on hover, like every
 * other delete in the app.
 *
 * ⚠ Scoped to the HEADER, not the column. `.board-column:hover
 * .subfolder-actions` is a descendant selector, so it matched every
 * `.subfolder-actions` inside the column — including one per card. Hovering
 * anywhere in a column lit up every card's × at once AND armed the
 * column-delete up in the header, which is both noisy and slightly
 * dangerous: you shouldn't arm "delete this column" by moving the mouse
 * over a card at the bottom of it. Each × is now revealed by hovering the
 * thing it deletes, and nothing else. */
.board-column-head:hover .subfolder-actions { opacity: 1; }

/* The × SWAPS with the count rather than sitting beside it.
 *
 * Taking it out of flow means it reserves no width, so the count sits
 * flush right at rest instead of floating with 22px of empty slot beside
 * it. `right: 10px` lands it exactly where the count was, matching the
 * title's 10px on the left.
 *
 * `visibility: hidden` on the count — not `display: none` — because it
 * PRESERVES the count's width. The header therefore cannot reflow when the
 * two swap, so nothing twitches as the mouse crosses a column. */
.board-column-head .subfolder-actions {
    position: absolute;
    right: 18px;
    top: 0;
    bottom: 0;
    align-items: center;
}
/* ⚠ Only where there is actually an × to swap IN. The spine columns
 * render no delete button, so an unscoped rule blanked their count into
 * empty space on hover — a number vanishing with nothing taking its place.
 * `:has()` keys the swap to the presence of the thing swapping in. */
.board-column-head:has(.subfolder-actions):hover .board-column-meta {
    visibility: hidden;
}

/* Right-align the glyph inside its 22px box so the box's edge IS the
 * glyph's edge — the shared component centres it, which would leave the ×
 * ~7px shy of where the arithmetic above puts it. Scoped to the board:
 * in BREAKDOWN the glyphs sit in a run of three (+ ✎ ×) where centring is
 * what makes them read as one set, and changing the component would move
 * every sub-folder chip in the app. The 22px hit target is untouched
 * either way; only the glyph moves within it. */
.board-column-head .subfolder-action-slot,
.board-card-actions .subfolder-action-slot { justify-content: flex-end; }

.board-column-body {
    /* 4px above the first card so the header reads as its own band rather
       than as the lid of the card stack. */
    padding: 4px 8px 10px 8px;
    overflow-y: auto;
    min-height: 44px;
    flex: 1 1 auto;
}

/* ------------------------------------------------------- insert a column */

/* The insert affordance lives BETWEEN columns rather than as a block at
 * the far right. Two reasons: on a wide board the end block had to be
 * scrolled to, and "we need a stage between Prep and Shoot" is what adding
 * a column almost always means — so the affordance belongs in the gutter,
 * where the column will go.
 *
 * `align-self: stretch` makes the whole vertical strip a hover target,
 * not just the glyph. It also carries the row's spacing, which is why
 * `.board-columns` has `gap: 0`.
 *
 * Clicking one creates a real column at standard width immediately and
 * drops it into inline rename — there is no intermediate "new column"
 * shell, because a column that looks different while being named is a
 * second thing to design and a second thing to get wrong. */
.board-gutter {
    flex: 0 0 14px;
    align-self: stretch;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 8px;
    position: relative;
}

.board-gutter-plus {
    /* A bare mark, not a bordered circle. One glyph does not need a chip
       around it, and three of them on a resting board would read as
       furniture rather than as "you can put something here". */
    background: none;
    border: none;
    width: 14px;
    height: 16px;
    line-height: 16px;
    font-size: 14px;
    color: var(--text-dim);
    cursor: pointer;
    padding: 0;
    text-align: center;
    opacity: 0;
    transition: opacity 0.12s ease, color 0.12s ease;
}
/* ⚠ These three are a specificity trap of exactly the kind documented
 * further down this file. `:hover` propagates to ancestors, so pointing at
 * the glyph ALSO hovers the gutter — both rules match, and the (0,3,0)
 * container rule beat the (0,2,0) ones on `opacity`, so the glyph could
 * never reach full strength under its own pointer or on keyboard focus.
 * The two victims are given a matching ancestor so they win on order. */
.board-gutter:hover .board-gutter-plus { opacity: 0.75; }
.board-gutter .board-gutter-plus:focus-visible { opacity: 1; }
.board-gutter .board-gutter-plus:hover { opacity: 1; color: var(--accent); }

/* ---------------------------------------------------------- blank board */

/* Matches WRITE's "A beautiful blank page." and CANVAS's "A beautiful
 * blank canvas." exactly — one centred node, 16px / 500 / 1.4 /
 * --text-dim. Typography lives here on the single element, no nested
 * overrides, same as both siblings.
 *
 * Positioned to centre in the column BODY region rather than the whole
 * drawer, so the three column headers still read as structure above it.
 * `pointer-events: none` so it never swallows a click meant for the
 * gutter insert affordances underneath.
 *
 * Only ever shown on an UNTOUCHED board (version 0, no cards), which is
 * always exactly the three short default columns — so the clearance below
 * the header row is a known quantity, not an estimate that has to survive
 * arbitrary content: 10 row padding + 1 border + 38 fixed header + 4 body
 * padding = 53, plus 3 of breathing room. */
.board-blank {
    position: absolute;
    left: 0;
    right: 0;
    top: 56px;
    bottom: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 32px 24px;
    font-family: inherit;
    font-size: 16px;
    font-weight: 500;
    line-height: 1.4;
    color: var(--text-dim);
    pointer-events: none;
}
.board-blank[hidden] { display: none; }

/* --------------------------------------------------------------- status */

.board-status {
    padding: 6px 16px 12px 16px;
    font-size: 12px;
    color: var(--text-dim);
    flex: 0 0 auto;
}
.board-status.board-status-error { color: var(--danger); }

/* ------------------------------------------------------------------ cards */

/* Deliberately NOT a fixed height. Cards vary, but only across a bounded
 * set: title (1-3 lines) plus, later, a single meta row that never wraps.
 * That gives a handful of discrete heights rather than arbitrary ones, so
 * a column reads as ordered without forcing a one-line card to carry the
 * whitespace of a three-line one.
 *
 * (The gallery tiles ARE uniform — but they're images, where uniformity is
 * what makes a grid scannable. Text is the opposite: a forced height either
 * truncates or pads.) */
/* The solid object. Weight is what tells you this is a task before you have
 * read the number or noticed an effort figure. Both values live on
 * `.board-body` — see the note there. */
.board-card {
    position: relative;
    background: var(--board-card-bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 7px 9px;
    margin-bottom: 6px;
    /* Pointer, not text. This said `text` from when clicking a card
       started an inline rename; clicking now OPENS the card, so a caret
       was promising an edit that no longer happens there. The column
       header keeps `text` because clicking it genuinely does still rename
       in place.
       Not `grab` either, despite cards being draggable — grab would
       advertise dragging as the primary action, and it isn't; opening is.
       Once a drag actually starts, `body.board-dragging` forces `grabbing`
       across the whole document (see the rule further down). */
    cursor: pointer;
}
/* A piece of something else, so it reads as less substantial than the card
 * it belongs to. Sits just above the column rather than on it — close
 * enough to look recessed, never darker, which would read as a hole. */
.board-card.is-sub { background: var(--board-subcard-bg); }

.board-card:hover { border-color: rgb(var(--accent-rgb) / 0.5); }

.board-card-title {
    font-size: 13px;
    line-height: 1.35;
    color: var(--text);
    word-break: break-word;
    /* Room for the hover control without the text running under it. 24px, not
       16px: measured, the slot is 22px wide once revealed, so 16 left the
       glyph sitting 6px over the words — which is how the restore ↻ came to
       be "lost over the title text". Sized off the measurement rather than
       the guess, with 2px to spare. */
    padding-right: 24px;
}

/* The card's number, inline before its title — the same shape the open card
 * uses, so a card reads the same whether you are looking at it on the board
 * or inside it. Dim and a size down: it is a handle you quote, not part of
 * the sentence, and it must not compete with the title for the eye. It sets
 * its own right margin rather than relying on a space character, so the gap
 * survives however the title wraps. */
.board-card-num {
    color: var(--text-dim);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    margin-right: 5px;
    /* ⚠ A FIXED SLOT, so titles do not stagger down the column as numbers
     * gain digits — #7 and #148 in the same column would otherwise start
     * their titles at different x, and a column of ragged left edges reads
     * as a mistake even when nobody can say why.
     *
     * Sized for four characters (`#999`), not five: three digits covers a
     * board's realistic life — numbers are per-board, and a production
     * board runs to hundreds, not thousands — and reserving space for a
     * #9999 that will never arrive taxes every card that ever renders.
     * Past 999 the slot simply grows and that one card's title starts a
     * character later, which is the right way round for the trade.
     *
     * inline-block because min-width does not apply to inline elements. */
    display: inline-block;
    min-width: 3ch;
}

/* The action slot again — third use of the same component. Pinned to the
 * card's top-right rather than in flow, so it never affects the height.
 *
 * `right: 9px` mirrors the card's own 9px horizontal padding, so the ×
 * is inset from the card's edge by exactly as much as the title is — it
 * sits comfortably inside its box rather than crowding the border. It
 * also lands the glyph 18px from the column's inner edge, which is where
 * the header's × now sits too. Both glyphs are right-aligned inside their
 * 22px slots (see the rule above), which is what makes the arithmetic
 * exact rather than a guess at the glyph's width. */
.board-card-actions {
    position: absolute;
    top: 3px;
    right: 9px;
}

/* A card carrying a conversation gets a dot, not a count. A number in a
 * bubble is a second thing to read on every card that has one, and the
 * board's argument is that a card is a line of text you can scan.
 *
 * Purple is not a new meaning: --flag is already the has-comments colour
 * everywhere else in Create (comments.css — "has-comments or is-flagged:
 * bubble goes purple either way").
 *
 * ⚠ IT SITS IN THE META ROW, NEXT TO THE INITIALS, AND IT DOES NOT MOVE.
 * The first version put it in the top-right corner and slid it left on
 * hover to clear the ×. Two things wrong with that: a thing that moves
 * when you approach it reads as interactive, and what it moved to reveal
 * was DELETE — so the affordance it advertised and the button it uncovered
 * were the two furthest-apart actions on the card.
 *
 * Beside the initials it says what it means: there is a note here, for
 * these people. `pointer-events: none` because it is a status mark, not a
 * control — it also keeps it out of `_armCardDrag`, which only skips
 * button/input/textarea. */
.board-card-note-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--flag, #a855f7);
    flex: 0 0 auto;
    pointer-events: none;
}
.board-card:hover .subfolder-actions { opacity: 1; }

/* Shared by the card rename field and the quick-add composer — the same
 * box as a card, so committing one doesn't shift the column. */
.board-card-input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    background: var(--bg-input);
    border: 1px solid var(--accent);
    border-radius: 6px;
    padding: 7px 9px;
    margin-bottom: 6px;
    color: var(--text);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.35;
    resize: none;
    overflow: hidden;
    outline: none;
}
.board-card-input::placeholder { color: var(--text-dim); }

/* ------------------------------------------------------- quick-add */

/* Sits at the foot of the column; cards land above it, so a burst of
 * entry stays in the order it was typed. No padding of its own — the
 * button carries the card's exact box, including its 6px bottom margin,
 * so the spacing rhythm continues unbroken from the last card. */
.board-composer { }

/* The + is the CARD'S OWN BOX, exactly — same 1px border, same 6px
 * radius, same 7px/9px padding, same 13px/1.35 type, same 6px bottom
 * margin. Three consequences, in ascending order of importance:
 *
 *  1. The dashed ghost is an honest preview: a card-shaped outline in the
 *     slot the card will occupy. (This is why the composer keeps its box
 *     while the gutter + doesn't — a gutter creates a 236px COLUMN, far
 *     too big to ghost meaningfully, so there a bare glyph is right. Here
 *     the thing being created is small enough to draw.)
 *
 *  2. The + sits exactly where the title's first character will be, so
 *     clicking it puts the caret where the glyph was. The gesture reads as
 *     one continuous thing rather than a button that becomes a field.
 *
 *  3. It fixes a real jump. The button was 28px and the input it swaps to
 *     is 34px, so clicking + shunted the column 6px before a single
 *     character had been typed. Identical boxes, no movement — from faint
 *     ghost, to focused field, to finished card. */
.board-composer-btn {
    display: block;
    width: 100%;
    box-sizing: border-box;
    background: none;
    border: 1px dashed transparent;
    border-radius: 6px;
    padding: 7px 9px;
    margin-bottom: 6px;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.35;
    text-align: left;
    cursor: pointer;
    /* Always present, just very faint — NOT hover-revealed. It used to
       appear on column hover, which meant hovering a card made an
       unrelated control materialise at the foot of the column. Being
       permanent removes a state rather than re-scoping one, and settles
       the empty-column dead end without the `:first-child` special case
       that caused an earlier specificity bug. */
    opacity: 0.3;
    transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.board-composer-btn:hover {
    opacity: 1;
    color: var(--accent);
    border-color: rgb(var(--accent-rgb) / 0.4);
}

/* ------------------------------------------------------------------- drag */

/* The drop indicator — a 3px accent bar showing exactly where the card
 * will land. Copied in spirit from `.subfolder-reorder-indicator`, which
 * exists because per-item hit-testing was tried in BREAKDOWN and rejected:
 * the user had to land on the right half of the right chip, and a few
 * pixels out was a silent no-op. Hit-testing at the container and drawing
 * the insertion point covers every Y position. */
.board-drop-indicator {
    height: 3px;
    margin: 0 0 6px 0;
    background: var(--accent);
    border-radius: 2px;
    box-shadow: 0 0 6px rgb(var(--accent-rgb) / 0.6);
    pointer-events: none;
}

/* The card left behind while its ghost follows the cursor. Same 0.4 as
 * `.subfolder-chip.dragging`. */
.board-card-dragging { opacity: 0.4; }

/* The ghost under the cursor. Fixed-position so it escapes the column's
 * `overflow-y: auto` — a ghost clipped by its own column would vanish the
 * moment you dragged it toward another one, which is the entire gesture. */
.board-card-ghost {
    position: fixed;
    z-index: 10060;
    margin: 0;
    pointer-events: none;
    opacity: 0.9;
    box-shadow: 0 6px 20px rgb(0 0 0 / 0.45);
    border-color: var(--accent);
}

/* ------------------------------------------------------------ card modal */

/* Overrides on the app's generic `.modal-dialog`, which is 420px — right
 * for a confirm, too narrow for notes. Everything else (overlay, close
 * button, label, input) is the shared component untouched. */


/* The title is a field that doesn't look like one until you're in it —
 * it reads as the card's heading, and only shows its edges on focus. A
 * bordered input here would make the dialog read as a form to fill in
 * rather than a card to look at. */




/* --------------------------------------------------------- column drag */

/* Vertical twin of `.board-drop-indicator`, sized to fill the gutter it
 * sits in so the bar spans the column's height rather than floating as a
 * stub next to it. */
.board-col-drop-indicator {
    width: 3px;
    align-self: stretch;
    margin: 0 auto;
    background: var(--accent);
    border-radius: 2px;
    box-shadow: 0 0 6px rgb(var(--accent-rgb) / 0.6);
    pointer-events: none;
}

.board-column-dragging { opacity: 0.4; }

/* Fixed so the ghost escapes `.board-columns`' horizontal scroll — a
 * ghost clipped by the scroller would disappear the moment you dragged a
 * column toward the edge, which is exactly when you need to see it. */
.board-column-ghost {
    position: fixed;
    z-index: 10060;
    pointer-events: none;
    opacity: 0.9;
    box-shadow: 0 6px 20px rgb(0 0 0 / 0.45);
    border-color: var(--accent);
    overflow: hidden;
}

/* While a drag is live, EVERY element reports `grabbing`.
 *
 * Both ghosts used to carry `cursor: grabbing` and it never once applied: a
 * ghost has `pointer-events: none` (it must, or it would hit-test against
 * itself), so the cursor is whatever sits UNDERNEATH the pointer. Dragging a
 * card across a column header therefore showed a text caret, and dragging a
 * COLUMN showed one for the whole gesture, since the header you grab is
 * itself `cursor: text` for rename.
 *
 * `!important` on a descendant selector is the only thing that beats the
 * per-element cursors underneath. It is scoped to a body class that exists
 * only while a pointer is genuinely down — every teardown path clears it,
 * including `pointercancel` — so it cannot leak into the resting page.
 */
body.board-dragging,
body.board-dragging * { cursor: grabbing !important; }

/* ⚠ And suppress SELECTION, not just the cursor.
 *
 * A pointer drag is still a text-drag as far as the browser is concerned,
 * so dragging a card swept a selection across every column name and card
 * title it passed over — the board lit up blue mid-gesture. The app's own
 * `body.prompt-wrapper-resizing` sets `user-select: none` beside its
 * cursor for exactly this reason; mine set only the cursor.
 *
 * On the BODY rather than on the cards, because the selection is made by
 * the pointer sweeping across the document, not by the element it started
 * on — and it must lift the moment the drag ends, which is why it rides
 * the same transient class rather than living on `.board-card`. */
body.board-dragging {
    -webkit-user-select: none;
    user-select: none;
}

/* ------------------------------------------------- card meta: who + when */

/* Renders only when there is something in it — a card with just a title
 * stays a line of text. One row, fixed height, never wraps: that is what
 * keeps card heights to a handful of discrete values rather than
 * arbitrary ones. */
.board-card-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 6px;
    min-height: 20px;
}

.board-card-due {
    font-size: 11px;
    color: var(--text-dim);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
/* The only colour a card carries on its own. Overdue is the one thing a
 * producer needs to see without reading. */
.board-card-due.is-overdue { color: #c9a66b; }

.board-card-who {
    display: flex;
    align-items: center;
    margin-left: auto;      /* assignees sit right, dates left */
}

.board-who-chip {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--bg-hover);
    border: 1px solid var(--bg-card);   /* separates the overlap */
    color: var(--text-dim);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.02em;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: -6px;      /* overlap, so three fit in the corner */
    flex: 0 0 auto;
}
.board-who-chip:first-child { margin-left: 0; }
.board-who-more { background: none; border-color: transparent; }

/* ------------------------------------------------------ modal: who + when */



/* Toggle chips. Off is the same muted circle the card uses; on takes the
 * project accent, so the card and the picker read as the same object. */
.board-person {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 10px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.board-person:hover { border-color: var(--accent); color: var(--text); }
.board-person.is-on {
    background: rgb(var(--accent-rgb) / 0.22);
    border-color: var(--accent);
    color: var(--text);
}


/* ----------------------------------------------------------------- points */

/* A bare number on the card. No unit suffix — whether a point is a day, a
 * half-day or a feeling is the team's decision, not the software's. */
.board-card-points {
    font-size: 11px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    flex: 0 0 auto;
}

/* Sub-card marks. Both sit leftmost in the meta row and both borrow the
 * points chip's measurements — a card face is a line of text you scan, and
 * a third type size on it would make it a form. Never both on one card: the
 * depth cap is what guarantees that. */
/* Stands in for the Effort chips on a sub-card, which carries none — the
 * estimate is made once, on the parent. Says where it went rather than
 * leaving an empty row that reads as a missing feature. */
.board-cv-points-note {
    font-size: 12px;
    color: var(--text-dim);
    align-self: center;
}

/* Always rendered, empty or not: it is the reason every due date in a
 * column starts on the same vertical line. Sized for the widest effort the
 * scale allows ("12e"). */
.board-card-effort-slot {
    flex: 0 0 auto;
    /* 4ch, not 3ch, because this slot now holds either `99e` or a
       subtask's `/003`. Sizing it to the wider of the two is what keeps
       the due dates in a column aligned when tasks and subtasks sit next
       to each other — which, the whole point of the slot being here, is
       the alignment it was reserved for in the first place. */
    min-width: 4ch;
    display: inline-flex;
}

.board-card-parent,
.board-card-progress {
    font-size: 11px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    flex: 0 0 auto;
}
/* Notes dot, subtask marker and initials travel together on the right, so
 * none of them can disturb the date column they used to sit in front of.
 * One auto margin on the group — three separate ones would share the free
 * space and strand each mark at a third of the card. */
.board-card-marks {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    flex: 0 0 auto;
}
/* All children done takes the accent — the one thing on a card worth
 * noticing at a glance, and the same colour the board uses for "on" state
 * everywhere else. Nothing else in the meta row is coloured, so it carries
 * without a background or a border. */
.board-card-progress.is-complete { color: var(--accent); }

/* Done, but not really — a parent in the done column with a child that was
 * reopened afterwards. Amber rather than red: this is a discrepancy to
 * notice, not an error to fix immediately, and the board is not accusing
 * anybody. It is the only state the move rule cannot prevent, so saying it
 * plainly is the whole job. */
.board-card-progress.is-stale { color: #d9a441; }


/* Same toggle grammar as the people chips, sized for 1-3 characters.
 * Clicking the selected one clears the estimate — otherwise there is no
 * way back to "no idea yet" without a separate control for it. */
.board-point {
    min-width: 28px;
    height: 28px;
    padding: 0 6px;
    border-radius: 14px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.board-point:hover { border-color: var(--accent); color: var(--text); }
.board-point.is-on {
    background: rgb(var(--accent-rgb) / 0.22);
    border-color: var(--accent);
    color: var(--text);
}

/* ------------------------------------------------------------ card refs */

/* Attachments reuse `.mention-pill`, the component the prompt editor and
 * agent composer already use for a referenced file — so a ref looks like a
 * ref wherever it turns up. Only the sizing is board-specific. */

.board-ref-pill {
    max-width: 100%;
    font-size: 10px;
    padding: 1px 6px;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}
.board-ref-glyph { flex: 0 0 auto; opacity: 0.7; font-size: 9px; }
.board-ref-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* A deleted file leaves its pill behind rather than vanishing, so a card
 * never quietly loses the thing it was about. Same tombstone treatment as
 * the prompt viewer's ghost pills. */
.board-ref-pill.is-missing {
    opacity: 0.55;
    border-style: dashed;
}
.board-ref-pill.is-missing .board-ref-label { text-decoration: line-through; }


/* Landing feedback, matching the accent halo other drop targets use. */
.board-card.pointer-drop-hover,

/* ================================================================
   The card view — the drawer's second view, not an overlay
   ================================================================ */

/* Two views, one drawer, CSS-toggled — the idiom EDIT uses for its two
 * stacked shells. Being a view rather than a modal is what lets ASSETS
 * stay visible beside an open card, which is what makes dragging a file
 * onto it possible at all. */
.board-body[data-view="board"] .board-card-view { display: none; }
.board-body[data-view="card"] .board-columns,
.board-body[data-view="card"] .board-blank { display: none; }

/* ⚠ THIS ELEMENT NO LONGER SCROLLS. It is the shell: a two-row flex column
 * holding the title row and, below it, the box that does the scrolling.
 * Padding lives on those two children, not here — see `.board-cv-scroll`. */
.board-card-view {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    min-height: 0;
    flex: 1 1 auto;
}

/* The scrolling box. Everything except the number and title lives in here,
 * which is what makes the header immovable rather than merely pinned. */
.board-cv-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    padding: 0 18px 16px 18px;   /* the 18px the columns use */
    gap: 2px;
}

/* ⚠ A SCROLLING FLEX COLUMN MUST STOP ITS CHILDREN SHRINKING. Flex items
 * default to `flex-shrink: 1`, so when the card grew taller than the drawer
 * the browser did the flex thing rather than the scroll thing: it squeezed
 * whatever could give. What gave was the notes field — the one item with a
 * fixed height — so writing more comments made the DESCRIPTION smaller, an
 * inversion nobody would ever ask for.
 *
 * `overflow-y: auto` above does not prevent this. Overflow only happens
 * once the content cannot be made to fit, and shrinking is the browser
 * making it fit. Pinning the children is what turns "squeeze" into
 * "scroll".
 *
 * ⚠ This rule must sit on whichever element SCROLLS. It moved from
 * `.board-card-view` to `.board-cv-scroll` when the header came out of the
 * scrolling box; left behind on the shell it would have pinned the header
 * and the scroller and gone on protecting nothing, and the notes field
 * would have started eating the description again. */
.board-cv-scroll > * { flex-shrink: 0; }

/* The whole row is the way back — the same grammar as `.drawer-header`,
 * which collapses its drawer when clicked. A lone 12px chevron was too
 * small a target to be the only exit from a full-drawer view. */
/* Drawer-header actions, in AGENT's "New chat" slot and to its
 * measurements — one small text control after the drawer's own title.
 * BACK while a card is open, GROUP otherwise; never both. */
.board-header-actions {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    /* Anchor for the group menu. Without it the menu resolves against
       `.drawer-header` and `left: 0` lands it under the ✦ glyph, half a
       header away from the control that opened it. */
    position: relative;
    /* Padded hit zone extending past the text, like AGENT's. The negative
     * margin keeps it from displacing the title. */
    padding: 8px 10px;
    margin: -8px -10px;
}
.board-back-btn {
    background: transparent; border: none; padding: 0;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 8px; letter-spacing: 0.5px; text-transform: uppercase;
    line-height: 10px; height: 12px;
    cursor: pointer;
    display: flex; align-items: center;
    /* No transform. See .agent-new-chat-btn: the 1px nudge that used to
       live there was aligning to a pill that has since moved, and this
       button is geometrically identical to the DEMO 0.1 lozenge beside
       it, so `align-items: center` already lines the words up. */
}
.board-back-btn:hover { color: var(--accent); }
.board-back-btn[hidden] { display: none; }

/* The number, now on the card's title row rather than a line above it.
 * Baseline alignment against a 17px title, so the two read as one phrase
 * — "#3 Location recce" — instead of a label and a heading. */
/* ⚠ THE NUMBER AND TITLE DO NOT MOVE, BECAUSE THEY ARE NOT IN THE
 * SCROLLING BOX. A long description or a run of comments used to carry the
 * title off the top of the drawer, and then the only thing telling you
 * which card you were reading was gone — with a board of numbered tasks
 * that are deliberately terse, "which one is this" is a real question.
 *
 * ⚠ `position: sticky` was the first attempt and it is NOT good enough
 * here, which measuring alone will not tell you. Sampled programmatically
 * it was flawless — identical y at every scroll offset, zero gap above it.
 * Under a real trackpad it visibly bounced: momentum scrolling is driven by
 * the compositor while a sticky element is repositioned on the main thread,
 * so it lags a frame and snaps. The fix is structural rather than a
 * compositing hint — a sibling above the scroller cannot lag, because
 * nothing is asking it to move. Same call as the synced-video header in the
 * COMPASS demo, for the same reason.
 *
 * No background needed now, and no negative margins: nothing passes behind
 * this row, so there is nothing to cover. */
.board-cv-title-row {
    display: flex;
    align-items: baseline;
    gap: 7px;
    padding: 10px 18px 8px;
}
.board-cv-num {
    flex: 0 0 auto;
    font-size: 11px;
    letter-spacing: 0.08em;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    /* The title carries a -6px left margin so its text sits on the content
       edge when its border is hidden; the number matches it. */
    margin-left: -6px;
    padding-left: 6px;
}

/* Reads as the card's heading; shows its edges only when you are in it. */
.board-cv-title {
    width: 100%; box-sizing: border-box;
    background: none; border: 1px solid transparent;
    border-radius: var(--radius);
    padding: 4px 6px; margin: 4px 0 10px -6px;
    color: var(--text); font-family: inherit;
    font-size: 17px; line-height: 1.3; font-weight: 500;
    resize: none; overflow: hidden; outline: none;
}
.board-cv-title:hover { border-color: var(--border); }
.board-cv-title:focus { border-color: var(--accent); background: var(--bg-input); }

/* ---- the one field ------------------------------------------------ */

/* Text and file pills together, like the CREATE prompt card. Deliberately
 * NOT a textarea: a textarea cannot hold a pill, and the whole point is
 * that a ref lives in the sentence that mentions it. */
/* The wrapper carries the height and the drag handle; the editor fills
 * it. Resize must NOT sit on the contenteditable — style.css records that
 * CREATE tried it and the handle rendered but did nothing, because the
 * flex column overrode any dragged height. */
.board-cv-editor-wrap {
    position: relative;
    height: 150px;
    display: flex;
    flex-direction: column;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: border-color 0.1s;
}
.board-cv-editor-wrap:focus-within { border-color: var(--accent); }

.board-cv-editor {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    background: none;
    border: none;
    padding: 8px 10px;
    color: var(--text);
    font-size: 13px;
    line-height: 1.5;
    outline: none;
    white-space: pre-wrap;
    word-break: break-word;
}

.board-cv-editor.is-empty::before {
    content: attr(data-placeholder);
    color: var(--text-dim);
    pointer-events: none;
}
.board-cv-editor .mention-pill { margin: 0 1px; vertical-align: baseline; }
.board-cv-editor .mention-pill.is-missing {
    opacity: 0.55; border-style: dashed; text-decoration: line-through;
}

/* Landing feedback when a file is dragged over the field. */
.board-cv-editor-wrap.pointer-drop-hover,
.board-card.pointer-drop-hover {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px rgb(var(--accent-rgb) / 0.25);
}

/* ---- who / effort / when ------------------------------------------ */

/* Who, Effort and When on ONE line when the drawer is wide enough, which
 * it usually is if you are looking at a board. `flex-wrap` is the whole
 * responsive story: at narrow widths When drops first, then Effort. */
.board-cv-fields {
    display: flex;
    gap: 22px;
    align-items: flex-start;
    flex-wrap: wrap;
    margin-top: 16px;
}
.board-cv-field { display: flex; flex-direction: column; }
.board-cv-field .modal-label { margin-bottom: 6px; }

/* Two rows of six, both grids — twelve people, twelve effort steps. They
 * are the same shape on purpose: the pair reads as one block rather than
 * two unrelated controls that happen to sit together. */
/* ⚠ Who is flex, not grid. A 6-column grid always reserved six slots, so
 * a three-person team pushed Effort a full three columns to the right for
 * nothing. Flex-wrap with a max-width gives the same 6-per-row shape when
 * full, and hugs its content when not — so Effort starts immediately after
 * the last face. */
/* Both are single rows that wrap only when they must. A board drawer is
 * usually wide — it is showing columns — so the common case is Who,
 * Effort and When all on one line. Neither grid reserves space it is not
 * using, so Effort starts immediately after the last face.
 *
 * When the drawer IS narrow, `flex-wrap` degrades in a sensible order:
 * the fields row wraps first (When drops below), then each control wraps
 * within itself. Nothing is ever clipped or scrolled sideways. */
.board-cv-people,
.board-cv-points {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.board-person, .board-point {
    width: 28px; height: 28px; padding: 0;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-family: inherit; font-size: 10px; font-weight: 600;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.board-person { border-radius: 50%; }
.board-point  { border-radius: 6px; font-size: 11px; font-weight: 500;
                font-variant-numeric: tabular-nums; }
.board-person:hover, .board-point:hover { border-color: var(--accent); color: var(--text); }
.board-person.is-on, .board-point.is-on {
    background: rgb(var(--accent-rgb) / 0.22);
    border-color: var(--accent);
    color: var(--text);
}

/* Only appears when there are more people than the grid shows. */
.board-cv-search {
    margin-top: 6px;
    width: 197px;              /* the grid's width: 6 x 28 + 5 x 5 */
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    font-family: inherit; font-size: 11px;
    padding: 4px 8px; outline: none;
}
.board-cv-search:focus { border-color: var(--accent); }

/* Sized and coloured as a sibling of the Who and Effort chips rather than
 * a form input: same 28px height, same border, same muted-until-set
 * grammar. Unset it reads as a placeholder; set, it takes the accent
 * border and full-strength text, exactly like a selected chip. */
.board-cv-due {
    width: auto;
    height: 28px;
    box-sizing: border-box;
    margin-bottom: 0;
    padding: 0 8px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    color-scheme: dark;      /* or the native picker is a white slab */
}
.board-cv-due:hover { border-color: var(--accent); }
.board-cv-due.is-set { color: var(--text); border-color: var(--accent); }
.board-cv-due:focus { outline: none; border-color: var(--accent); }

.board-cv-meta {
    margin-top: 16px;
    /* Takes the slack, so the two buttons in `.board-cv-foot` stay together
       at the right regardless of how long this line is. */
    margin-right: auto;
    font-size: 12px;
    color: var(--text-dim);
}

/* ---- the @ picker -------------------------------------------------- */

/* Anchored to `.board-cv-editor-wrap` (see the markup note). Nothing else
 * is overridden: the rows, thumbs, name and type badge are all the shared
 * `.mention-item` component, so this picker and the prompt editor's are
 * the same thing rather than two lookalikes that drift apart. */
.board-card-view .mention-dropdown { max-height: 220px; }
.board-card-view .mention-dropdown.hidden { display: none; }

/* ---- the attachment count on the card face ------------------------- */

.board-card-clip {
    font-size: 11px; color: var(--text-dim);
    font-variant-numeric: tabular-nums; flex: 0 0 auto;
}

/* Footer: who made it, and the one destructive action, named. */
/* Sub-cards in the card view. The list the card face deliberately does not
 * carry — a parent with five children would be a paragraph on a board whose
 * argument is that a card is a line of text. */
.board-cv-kin { margin-top: 20px; position: relative; }
.board-cv-kin-list { margin-top: 6px; }
.board-cv-kin-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 3px 0;
}
.board-cv-kin-name {
    flex: 1 1 auto;
    min-width: 0;
    background: none; border: none; padding: 0;
    color: var(--text);
    font: inherit; font-size: 13px;
    text-align: left;
    cursor: pointer;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.board-cv-kin-name:hover { color: var(--accent); }
/* A finished sub-card reads as finished without a second control saying so
 * — the state already lives in which column it sits in. */
.board-cv-kin-row.is-done .board-cv-kin-name {
    color: var(--text-dim);
    text-decoration: line-through;
}
.board-cv-kin-detach {
    flex: 0 0 auto;
    background: none; border: none; padding: 0 2px;
    color: var(--text-dim);
    font-size: 14px; line-height: 1;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s ease, color 0.12s ease;
}
.board-cv-kin-row:hover .board-cv-kin-detach { opacity: 1; }
/* Detach, not delete — so it must not wear the danger colour that every ×
 * on a card and column does. The card survives; only the link goes. */
.board-cv-kin-detach:hover { color: var(--text); }

/* Field + button as one control, matching `.board-cv-comment-new` directly
 * below it — the two composers in this card should not disagree about how you
 * commit a line of text. */
.board-cv-kin-add {
    display: flex;
    align-items: flex-end;
    gap: 8px;
}
/* Same 30px lock the Post button uses, so the pair line up. */
.board-cv-kin-add-btn {
    flex: 0 0 auto;
    height: 30px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    line-height: 1;
    margin-top: 4px;
}

.board-cv-kin-input {
    width: 100%;
    box-sizing: border-box;
    height: 30px;
    margin-top: 4px;
    padding: 5px 8px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-input);
    color: var(--text);
    font: inherit; font-size: 13px;
    outline: none;
}
.board-cv-kin-input:focus { border-color: var(--accent); }

.board-cv-kin-pick-btn {
    background: none; border: none; padding: 0;
    margin-top: 8px;
    color: var(--text-dim);
    font: inherit; font-size: 12px;
    cursor: pointer;
}
.board-cv-kin-pick-btn:hover { color: var(--accent); }
.board-cv-kin-menu {
    position: absolute;
    left: 0;
    top: 100%;
    min-width: 240px;
    max-height: 260px;
    overflow-y: auto;
    font-size: 12px;
    z-index: 10080;
}
.board-cv-kin-menu[hidden] { display: none; }
.board-cv-kin-empty { padding: 6px 10px; color: var(--text-dim); }

/* Notes on a card. Deliberately quieter than the card's own fields — this
 * is a running conversation under the work, not another property of it. */
.board-cv-comments { margin-top: 20px; }
.board-cv-thread {
    margin-top: 6px;
    /* ⚠ NO max-height, and no scroller of its own. It had both, on the
     * reasoning that "the thread scrolls, the card does not" — which was
     * the wrong way round. A scrollbar inside a scrollbar makes a reader
     * choose which one they are in, and it capped a conversation at about
     * six notes while the whole drawer sat half empty below it.
     *
     * The card scrolls now (see `.board-card-view > *`), so the thread can
     * simply be as long as it is. */
}
.board-cv-thread-empty {
    color: var(--text-dim);
    font-size: 12px;
    padding: 4px 0;
}
.board-cv-note { padding: 6px 0; }
.board-cv-note + .board-cv-note { border-top: 1px solid var(--border); }
.board-cv-note-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 2px;
}
/* Same dot, same meaning, one level down: the card mark answers "have I
 * seen this card", this answers "which of these eight". */
.board-cv-note-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--flag, #a855f7);
    flex: 0 0 auto;
    /* Optically centred against the name's cap-height rather than its
       baseline, which is what `align-items: baseline` on the head would
       otherwise do to a round 5px mark. */
    align-self: center;
    margin-right: 1px;
}
.board-cv-note-who { font-size: 12px; color: var(--text); }
.board-cv-note-when { font-size: 11px; color: var(--text-dim); }
.board-cv-note-edited { font-size: 11px; color: var(--text-dim); font-style: italic; }

/* ---- editing and deleting your own note ---------------------------- */

/* Hover-revealed, and pushed to the right of the row by `auto` so the head
 * reads name-then-time regardless of whether the controls are showing. Kept
 * off the tab order only visually — they are real buttons, so a keyboard
 * user still reaches them; `opacity` alone would leave them clickable while
 * invisible, hence also gating on hover for pointer users only. */
.board-cv-note-acts {
    margin-left: auto;
    display: inline-flex;
    gap: 2px;
    opacity: 0;
    transition: opacity 0.12s ease;
}
.board-cv-note:hover .board-cv-note-acts,
.board-cv-note-acts:focus-within { opacity: 1; }

.board-cv-note-act {
    background: none;
    border: none;
    padding: 0 3px;
    font-size: 12px;
    line-height: 1;
    color: var(--text-dim);
    cursor: pointer;
    /* Buttons inherit the UA's own font otherwise, which shapes ✎ and ×
       differently from every other glyph in the drawer — the same trap the
       list-view action column hit. */
    font-family: inherit;
}
.board-cv-note-act:hover { color: var(--text); }
.board-cv-note-del:hover { color: var(--danger, #ef4444); }

.board-cv-note-edit { margin-top: 4px; }
.board-cv-note-edit-input {
    width: 100%;
    box-sizing: border-box;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 5px 7px;
    font-family: inherit;
    font-size: 13px;
    color: var(--text);
    resize: none;
    overflow: hidden;
}
.board-cv-note-edit-input:focus {
    outline: none;
    border-color: rgb(var(--accent-rgb) / 0.6);
}
.board-cv-note-edit-acts {
    display: flex;
    justify-content: flex-end;
    gap: 6px;
    margin-top: 4px;
}
.board-cv-note-text {
    font-size: 13px;
    color: var(--text);
    /* Somebody's typing, including their line breaks. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.board-cv-comment-new {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    margin-top: 8px;
}
/* 30px is the app's control height — the same lock `.card-actions .btn.btn-sm`
 * uses so text buttons line up with the 30px icon buttons beside them. The
 * field and the button are one control here, so they get one height; a
 * bare .btn-sm is ~24px and sat visibly short against the field. */
.board-cv-comment-input {
    flex: 1 1 auto;
    height: 30px;
    min-height: 30px;
    max-height: 140px;
    box-sizing: border-box;
    padding: 5px 8px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-input);
    color: var(--text);
    font: inherit;
    font-size: 13px;
    line-height: 18px;
    resize: none;
}
.board-cv-comment-input:focus {
    outline: none;
    border-color: var(--accent);
}
.board-cv-comment-send {
    flex: 0 0 auto;
    height: 30px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    line-height: 1;
}

/* `baseline`, not `center`. Centring aligns the BOXES, and the button's box
 * is taller than the bare text beside it by its own padding and border — so
 * its label floated above "Added by …" by roughly that padding, and the two
 * read as different rows despite being one. Baseline puts the two sets of
 * words on the same line, which is what the row is for. */
.board-cv-foot {
    display: flex;
    align-items: baseline;
    /* ⚠ `auto` on the meta, NOT `space-between`. With two buttons here now,
       space-between would spread three children evenly and strand Archive in
       the middle of the row; pushing the meta left keeps the pair together at
       the right, which is where both endings belong. */
    justify-content: flex-start;
    gap: 12px;
    margin-top: 18px;
}
.board-cv-delete { flex: 0 0 auto; }

/* Bullets and links inside the notes field. Kept to the same rhythm as
 * the body text — a card note is a note, not a document. */
.board-cv-editor ul { margin: 4px 0; padding-left: 20px; }
.board-cv-editor li { margin: 2px 0; }
.board-cv-editor a { color: var(--accent); text-decoration: underline;
                     text-underline-offset: 2px; }
.board-cv-editor a:hover { color: var(--accent-hover); }

.board-cv-editor ol { margin: 4px 0; padding-left: 22px; }
/* A quote is the client's words, not ours — set apart by a rule rather
 * than by italics, which would fight the pills sitting inside it. */
.board-cv-editor blockquote {
    margin: 4px 0;
    padding: 2px 0 2px 10px;
    border-left: 2px solid var(--border);
    color: var(--text-dim);
}
/* A person in the @ picker gets initials where a file gets a thumbnail,
 * so the two kinds of row are told apart before you read them. */
.board-mention-person {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--bg-hover);
    color: var(--text-dim);
    font-size: 9px;
    font-weight: 600;
}
/* The effort total is marked so it reads as effort, not a second count. */
.board-col-effort { opacity: 0.85; }

/* ================================================================
   Swimlanes — a view over the same board, never a stored entity
   ================================================================ */

/* The control is invisible at rest and appears on hover of the board,
 * like every other affordance here. Once a grouping is ON it becomes
 * STATE — a persistent chip naming the axis — so the board never silently
 * lies about why it is arranged the way it is. */
/* ⚠ `.board-group-bar` is gone — the row it occupied was pushing the card
 * down to carry two controls that now live in the drawer header. */
/* Trigger reads like BACK and AGENT's New chat — same 8px header-action
 * size. It carries its own state: "Group" at rest, the axis name when one
 * is on, accent when active. No second chip, nothing to clear. */
.board-group-btn {
    background: transparent; border: none; padding: 0;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 8px; letter-spacing: 0.5px; text-transform: uppercase;
    line-height: 10px; height: 12px;
    cursor: pointer;
    display: flex; align-items: center;
    opacity: 0.8;
    transition: opacity 0.12s ease, color 0.12s ease;
}
.board-group-btn:hover { opacity: 1; color: var(--accent); }
.board-group-btn[hidden] { display: none; }
/* Active grouping is INFORMATION, not an affordance — full opacity and the
 * accent whether or not anything is hovered. */
.board-group-btn.is-on { opacity: 1; color: var(--accent); }

/* Same grammar as Group, because it is the same kind of thing: a control
 * that is also a statement about what you are looking at. Silent while the
 * board shows everything; accent the moment it is holding something back,
 * so the board can never quietly show less than it has. */
.board-subcards-btn {
    background: transparent; border: none; padding: 0;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 8px; letter-spacing: 0.5px; text-transform: uppercase;
    line-height: 10px; height: 12px;
    cursor: pointer;
    display: flex; align-items: center;
    opacity: 0.8;
    transition: opacity 0.12s ease, color 0.12s ease;
}
.board-subcards-btn:hover { opacity: 1; color: var(--accent); }
.board-subcards-btn[hidden] { display: none; }
.board-subcards-btn.is-on { opacity: 1; color: var(--accent); }
/* Grouping by parent makes the sub-cards the lanes, so the toggle has
 * nothing left to say. Dimmed rather than removed: it vanishing at the
 * moment you group would read as a bug, and its tooltip explains itself. */
.board-subcards-btn:disabled { opacity: 0.35; cursor: default; }
.board-subcards-btn:disabled:hover { color: var(--text-dim); opacity: 0.35; }

/* Filter. The word is the same 8px caps control as its neighbours; the field
 * is what appears on demand, so at rest this costs one word of header. */
.board-filter { display: inline-flex; align-items: center; }
.board-filter[hidden] { display: none; }

.board-filter-btn {
    background: transparent; border: none; padding: 0;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 8px; letter-spacing: 0.5px; text-transform: uppercase;
    line-height: 10px; height: 12px;
    cursor: pointer;
    display: flex; align-items: center;
    opacity: 0.8;
    transition: opacity 0.12s ease, color 0.12s ease;
    /* A query can be long; the row must not grow to fit it. */
    max-width: 160px; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
}
.board-filter-btn:hover { opacity: 1; color: var(--accent); }
.board-filter-btn[hidden] { display: none; }
.board-filter-btn.is-on { opacity: 1; color: var(--accent); }

/* Sized to the row it lives in, not to the text it holds — the header is a
 * 12px band and a normal input would double its height. */
.board-filter-input {
    height: 16px;
    width: 150px;
    box-sizing: border-box;
    padding: 0 5px;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--bg-input);
    color: var(--text);
    font-family: inherit;
    font-size: 10px;
    outline: none;
}
.board-filter-input:focus { border-color: var(--accent); }
.board-filter-input[hidden] { display: none; }
/* Chrome draws its own clear affordance on type=search, at a size built for
   a 14px field. Ours is 16px tall and Escape already clears. */
.board-filter-input::-webkit-search-cancel-button { display: none; }

/* The archive toggle is the same control, so it is literally the same
 * styling — a second 8px caps word in the header row. It is absent until
 * something is archived, which is what keeps it minimal: a board that never
 * archives anything never grows a control for it. */
.board-archived-btn {
    background: transparent; border: none; padding: 0;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 8px; letter-spacing: 0.5px; text-transform: uppercase;
    line-height: 10px; height: 12px;
    cursor: pointer;
    display: flex; align-items: center;
    opacity: 0.8;
    transition: opacity 0.12s ease, color 0.12s ease;
}
.board-archived-btn:hover { opacity: 1; color: var(--accent); }
.board-archived-btn[hidden] { display: none; }
.board-archived-btn.is-on { opacity: 1; color: var(--accent); }

/* An archived card, while the archive is being shown. Faded and desaturated
 * rather than restyled — it is the same card, put away, and it has to stay
 * legible enough to decide whether to bring it back. The dashed edge is what
 * says "not currently part of this board" at a glance. */
.board-card.is-archived {
    opacity: 0.55;
    border-style: dashed;
}
.board-card.is-archived:hover { opacity: 0.85; }
/* Restore is the positive action, so it takes the accent on hover where the
 * archive × takes --danger from the shared action-slot component. One control
 * or the other is present, never both — see `_renderCard`. */
.board-card-unarchive:hover { color: var(--accent); }
/* ↻ is a taller glyph than × at the same font-size, so it sits a touch low in
 * the 12px slot the two share. One pixel up, measured rather than guessed. */
.board-card-unarchive { transform: translateY(-1px); }

/* The two endings, in the card view's footer. Archive is an ordinary button
 * because it is the ordinary thing; Delete keeps --danger and stays rightmost.
 * Restore takes the accent border so the reversal reads as the positive act
 * rather than as another neutral control. */
.board-cv-archive.is-restore {
    border-color: rgb(var(--accent-rgb) / 0.55);
    color: var(--accent);
}

/* Our own menu, because macOS draws a native <select> popup that no CSS
 * reaches into — its checkmark column is sized for the system font and sat
 * out of line against an 8px control. `.asset-context-menu` supplies the
 * shell and `.ctx-icon`'s fixed 18px column aligns ticked and unticked rows
 * by construction, which is the whole reason for the swap. */
.board-group-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    min-width: 120px;
    font-size: 12px;
    /* Above the drawer chrome; `.drawer-header` is the positioned ancestor
       and already carries `overflow: visible`. */
    z-index: 10080;
}
.board-group-menu[hidden] { display: none; }
.board-group-menu .ctx-icon { font-size: 11px; }


/* Grouped layout: names once at the top, then one strip per lane. Columns
 * are a fixed 236px, so the strips align with the header without any
 * measuring — which is exactly why the header can live outside the lanes
 * instead of repeating inside each one. */
.board-columns[data-grouped="true"] {
    display: block;
    overflow-x: auto;
    overflow-y: auto;
}
.board-lane-headrow,
.board-lane-strip {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    min-width: min-content;
}
.board-lane-colname {
    flex: 0 0 236px;
    width: 236px;
    box-sizing: border-box;
    padding: 0 18px 6px 18px;
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.board-lane { margin-bottom: 14px; }

/* The lane's own name sits above its strip rather than in a left gutter —
 * a gutter would cost 120px of every column's width on a drawer that is
 * already the narrowest place this board gets shown. */
.board-lane-label {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 0 6px 2px;
    font-size: 12px;
    color: var(--text);
    position: sticky;
    left: 0;                /* stays put while the columns scroll sideways */
}
.board-lane-count {
    font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums;
}

.board-lane-cell {
    flex: 0 0 236px;
    width: 236px;
    box-sizing: border-box;
    min-height: 44px;
    padding: 6px 8px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 8px;
}

/* A folder pill reads as a folder, not a file — same pill chrome, its own
 * glyph, and it opens the folder rather than previewing anything, because
 * there is no single thing to preview. */
.board-cv-editor .mention-pill[data-ref^="folder_"] .board-ref-glyph,
.mention-pill[data-ref^="folder_"] .board-ref-glyph { opacity: 0.85; }

/* ------------------------------------------------------------------- tags */

/* A row of its own under Who / Effort / When, because tags are unbounded
 * where those three are not. Full width, wrapping. */
.board-cv-tags-field { margin-top: 16px; }

.board-cv-tags {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
}

/* Off is the tag's colour as an outline, on fills it — the same on/off
 * grammar the people and effort chips use, in the label's own colour so a
 * tag reads the same wherever it appears. Not a pill of solid colour at
 * rest: a card view of eight bright tags would shout over the title, and
 * the title is what you opened the card to read. */
.board-tag {
    --tag-colour: #6f8aa5;
    height: 24px;
    padding: 0 10px;
    border-radius: 999px;
    background: none;
    border: 1px solid var(--tag-colour);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 11px;
    font-weight: 500;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.12s, color 0.12s;
}
.board-tag:hover { color: var(--text); }
.board-tag.is-on {
    background: var(--tag-colour);
    border-color: var(--tag-colour);
    color: #14161c;          /* dark ink on a mid-tone fill, not white */
    font-weight: 600;
}

/* Neutral, because it makes nothing yet — it opens the field that does. */
.board-tag-add {
    --tag-colour: var(--border);
    color: var(--text-dim);
}
.board-tag-add:hover { --tag-colour: var(--accent); color: var(--text); }

/* Only present while the create field is open. Sits tight against the chip
 * it belongs to, so it reads as that tag's cross rather than a separate
 * control in the row. */
.board-tag-del {
    width: 18px;
    height: 18px;
    margin-left: -4px;
    padding: 0;
    background: none;
    border: none;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 14px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
.board-tag-del:hover { color: var(--danger); }

.board-tag-new {
    height: 24px;
    padding: 0 10px;
    border-radius: 999px;
    background: var(--bg-input);
    border: 1px solid var(--accent);
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    outline: none;
    min-width: 120px;
}

/* A tag offered in the filter overlay. The row is the hit area, so the chip
 * inside it is decoration — same rule as the face beside a person. */
.board-filter-tag { pointer-events: none; }

/* ---------------------------------------------------------------- blocked */

/* ⚠ AN INSET LEFT EDGE, NOT A BORDER. `.board-card` already uses `border`
 * as its hover channel — `:hover` sets `border-color` to the accent on all
 * four sides — so a red border would turn GREEN under the cursor, on the one
 * card that most needs to keep saying otherwise. `box-shadow` is a channel
 * nothing else on a resting card uses, and it costs no layout.
 *
 * Red is free. The only colours a card carries are overdue `#c9a66b` and
 * stale-progress `#d9a441`, both warm ambers picked so the board "is not
 * accusing anybody". Blocked is the one state that should. */
.board-card.is-blocked { box-shadow: inset 3px 0 0 #c04a4a; }

/* Blocked AND overdue coexist without collision: this is the edge, that is
 * the date's colour. Different channels, deliberately. */

.board-cv-blocked-field { margin-top: 16px; }
.board-cv-blocked {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 6px;
    flex-wrap: wrap;
}

/* Off is quiet — most cards are not blocked and the control should not
 * imply otherwise. On is the same red as the card edge, so the two read as
 * one fact stated in two places. */
.board-blocked-toggle {
    height: 24px;
    padding: 0 10px;
    border-radius: 999px;
    background: none;
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 11px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.board-blocked-toggle:hover { border-color: #c04a4a; color: var(--text); }
.board-blocked-toggle.is-on {
    background: rgb(192 74 74 / 0.18);
    border-color: #c04a4a;
    color: #e8b4b4;
    font-weight: 600;
}

.board-blocked-note {
    flex: 1 1 220px;
    min-width: 160px;
    height: 24px;
    padding: 0 10px;
    border-radius: 999px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    outline: none;
}
.board-blocked-note:focus { border-color: #c04a4a; }

/* ------------------------------------------------------------------- flow */

/* Aging. A 2px bar along the card's bottom edge that darkens as the card
 * sits still — §9's "sediment, not alarm". No number, no badge, no red: the
 * card is not in trouble, it has simply been there a while, and a board
 * where every card shouts is a board nobody reads.
 *
 * ⚠ `::after`, not a border and not a box-shadow. The border is the hover
 * channel and the inset shadow is now Blocked's left edge — a third meaning
 * on either would collide with one of them. A blocked card that is also old
 * shows both marks, on different edges, which is correct: they are different
 * facts. */
.board-card.is-aging::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    /* 25% at the threshold → 60% at saturation, per §9. */
    background: var(--text-dim);
    opacity: calc(0.25 + (var(--age, 0) * 0.35));
    pointer-events: none;
}
/* The card is the positioning context for it. */
.board-card { position: relative; }

/* One line under the board. Quiet enough to ignore, present enough to
 * answer "how are we doing" without opening anything. */
.board-foot {
    flex: 0 0 auto;
    padding: 6px 12px 10px 12px;
    font-size: 11px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.board-foot[hidden] { display: none; }

/* History, in the card view. A word until asked for. */
.board-cv-history { margin-top: 20px; }
.board-cv-history-toggle {
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
    font-size: 10px;
    letter-spacing: 3px;
    text-transform: uppercase;
    color: var(--text-dim);
    opacity: 0.8;
    cursor: pointer;
}
.board-cv-history-toggle:hover { color: var(--text); opacity: 1; }
.board-cv-history-list { margin-top: 8px; }
.board-cv-history-list[hidden] { display: none; }
.board-cv-history-row {
    font-size: 12px;
    color: var(--text-dim);
    padding: 3px 0;
}
