/* ================= THE ALTERNATE UX — THE TEAM BOARD'S KANBAN =================
   Split from CSS/alt/alt-team.css, verbatim: that sheet keeps the Team Board's CHROME (context
   bar, panel shell, Team at a Glance, By Assignee, their density rules, the two hosted dialogs);
   this sheet is the BOARD — the lane canvas, a lane, its header, its cards, both densities. The
   split matters because a change to the board and a change to the settings dialog used to land
   in the same file, a merge conflict between work that has nothing to do with each other.
   Written for JS/alt/alt-team-board.js's markup — every selector is `a-tb-`. Shared primitives
   (`.a-row`, `.a-row-t`, `.a-check`, `.a-chip`, `.a-av`, `.a-empty`, `.a-add-row`) come from
   alt-tasks.css/alt-shell.css, both loaded BEFORE this sheet — source order is load order.
   The compact density lives in CSS/alt/alt-team-kanban-compact.css (loads after this one, scoped
   to `.a-tb-compact`/`.a-compact`) — comfy is what this sheet describes, compact is what that one
   overrides. The two exceptions are the Owned/Completed/Archived LIST rules near the foot, which
   are not the kanban and stayed for that reason.
   NOT HERE, DELIBERATELY: the density block targeting `.a-band`/`.at-glance`/`.a-stat`/
   `.at-person` — the two analytic bands, not the board — which is Team Board chrome and stayed
   with the chrome in alt-team.css. */

/* ================= 4 · THE BOARD =================
   Two size modifiers alt-team-board.js needs on shared primitives — `.a-input.sm`, `.a-av.sm` —
   live in CSS/alt/alt-components.css beside `.a-input`/`.a-select`/`.a-av.lg`, not here: a
   second page had already restated them and a third wanted `.a-av.sm` with neither sheet
   loaded. One definition, not three. */

/* The centre column is a scroll container, not a long page. `.a-centre` is a flex column with
   `overflow:hidden`, so the toolbar keeps its place and only the board underneath moves;
   `min-height:0` is what lets the flex child be SHORTER than its content instead of growing the
   page. */
.alt-app #altTeamToolbar{ flex:0 0 auto; }
.alt-app #altTeamView{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
/* ---------- THE BOARD IS A CONTAINER, NOT A CLEARING ----------
   `.a-tb-scroll` is ONE element shared by all four views (board/owned/completed/archived), so
   the frame below has to be conditional — a row list inside a board frame is a container around
   the wrong thing. It's spelled as `:not(:has(.a-tb-lanes))` on the OPT-OUT rather than
   `:has(.a-tb-lanes)` on the frame itself, because an engine that doesn't know `:has()` drops an
   invalid rule WHOLE — inverting it means the unsupported case loses only the opt-out (frame
   stays on three list views that didn't need it) rather than losing the frame the redesign was
   built around. Declared once, not duplicated behind `@supports`.
   The surface is mixed from two tokens (`--a-bg`/`--a-panel`), not picked, so the small step
   between them moves the right way in both themes without a second value to re-measure. The
   plain declaration ahead of the color-mix() line is not decoration — an engine without
   color-mix() discards the whole line, so without it the frame would have no background at
   all. */
.alt-app .a-tb-scroll{
  flex:1 1 auto; min-height:0; min-width:0; overflow-y:auto; overflow-x:hidden;
  display:flex; flex-direction:column; gap:var(--a-3);
  background:var(--a-bg);
  background:color-mix(in srgb, var(--a-bg) 55%, var(--a-panel) 45%);
  border:1px solid var(--a-line);
  /* 14px against the lane's own 9px, since the two radii are not concentric (12px of padding
     between them) — the outer only has to be the larger of the two. */
  border-radius:var(--a-r-lg);
  /* Replaces a bare `padding-bottom` that existed only to keep the last group off the viewport
     edge — inside a frame the same job needs an inset on all four sides. 12px is `--a-3`,
     matching the step `.a-centre` already puts above this element. The opt-out below restores
     the original single-side padding for the non-board views. */
  padding:var(--a-3);
  /* `.a-centre`'s own gap already separates the toolbar from this element; this adds a fourth
     step (12 + 4 = 16, `--a-4`) above the frame's top edge from two owners rather than one. */
  margin-top:var(--a-1);
}
/* ---------- AND THE THREE SECTIONED LISTS TAKE IT BACK OFF ----------
   `.a-tb-lanes` is the test because exactly one function (altTBLanesRow()) builds it, reached
   from the board view alone. Owned/Completed/Archived render `.a-tb-list`, and the two board
   empty states render `.a-empty` — all shed the frame. If this is ever replaced by a class,
   it's a one-token edit: altTBRenderView() already composes the className, so an `a-tb-board`
   marker there turns this into `.a-tb-scroll:not(.a-tb-board)` with no `:has()` dependency. */
.alt-app .a-tb-scroll:not(:has(.a-tb-lanes)){
  background:none; border:0; border-radius:0;
  padding:0 0 var(--a-4); margin-top:0;
}
/* The board's own row of controls. `.a-toolbar` already spaces it; this only stops it wrapping
   into two rows for the sake of one button and gives the grouping select a label slot. */
.alt-app .a-tb-bar{ padding-top:0; }
.alt-app .a-tb-group-by{ display:inline-flex; align-items:center; }
.alt-app .a-tb-bar .a-btn.on{
  background:var(--a-blue-soft); border-color:var(--a-blue); color:var(--a-blue-text);
}
/* The fold-all toggle's slot: `display:contents` is the whole rule. It's drawn twice per
   repaint (toolbar, then once the view knows which groups it drew) so it needs a fixed position
   to come back to, and it's often empty (ungrouped board, empty list) — `display:contents`
   means the span generates no box, so an empty slot leaves no trace and no stray `gap`. It
   carries no semantics, so removing its box removes nothing a screen reader needs. */
.alt-app .a-tb-fold-slot{ display:contents; }

/* ---------- a topic group ---------- */
.alt-app .a-tb-topic{ min-width:0; display:flex; flex-direction:column; gap:var(--a-2); }
.alt-app .a-tb-topic-h{ display:flex; align-items:center; gap:var(--a-2); min-width:0; }
/* `.a-group-h` is the shared disclosure header and a full-width block on the task list; here it
   shares a line with the band's lane toggle, so it takes only the space left instead of all of
   it. */
.alt-app .a-tb-topic-h .a-group-h{ flex:1 1 auto; width:auto; min-width:0; }
/* The lane controls are secondary to the topic's own name, so they sit at the trailing edge and
   stay quiet until the group is hovered — they act on what is already on screen. */
.alt-app .a-tb-topic-acts{ margin-left:auto; display:inline-flex; gap:var(--a-1); flex:0 0 auto; }
.alt-app .a-tb-topic-acts .a-btn{ opacity:.7; }
.alt-app .a-tb-topic:hover .a-tb-topic-acts .a-btn,
.alt-app .a-tb-topic-acts .a-btn:focus-visible{ opacity:1; }
/* Except when it's lit — an action is worth nothing until reached for, but the fold-all toggle
   also REPORTS (lit = every lane in this band is open), and `aria-pressed` carries that whatever
   the opacity is. Fading the lit state to 70% would leave the visible half of that report
   readable only on hover, while the announced half stayed true. The unlit state stays quiet. */
.alt-app .a-tb-topic-acts .a-btn.a-fold-all[aria-pressed="true"]{ opacity:1; }
/* The retired-topics header is the same disclosure as a group's, one level out. */
.alt-app .a-tb-retired-h{
  display:flex; align-items:center; gap:var(--a-2); align-self:flex-start;
  padding:5px 9px; border:1px dashed var(--a-line); border-radius:var(--a-r-sm);
  background:transparent; color:var(--a-dim); cursor:pointer;
  font:600 12px/normal var(--a-body);
}
.alt-app .a-tb-retired-h:hover{ color:var(--a-text); border-color:var(--a-line-strong); }
.alt-app .a-tb-retired-h:focus-visible{ outline:2px solid var(--a-ring); outline-offset:2px; }
.alt-app .a-tb-retired-h .cv{ display:flex; }
.alt-app .a-tb-retired-h .cv > svg{ width:14px; height:14px; }
.alt-app .a-tb-retired-h .n{ font-variant-numeric:tabular-nums; opacity:.7; }

/* ---------- the row of lanes ----------
   Horizontal and scrolled, one row per topic. `overscroll-behavior-x:contain` keeps a sideways
   flick inside the board instead of navigating the browser back.
   `position:relative` is what stops the board scrolling the WHOLE PAGE — a scroll container only
   clips what it is the containing block for, and an absolutely-positioned `.a-sr`/`.sr-only`
   descendant (used on a completion lane's screen-reader label) takes its containing block from
   the nearest POSITIONED ancestor, not the nearest scroller. Without this rule that ancestor was
   `main.a-page`, outside the scroller entirely — measured: a single off-screen span pushed
   `documentElement.scrollWidth` to 4.4x the viewport at 390px, clipped with nothing to scroll to
   at 1024px, invisible only at 1440+ where every lane fit on screen. Fixed on the CONTAINER
   rather than the badge, because the property belongs to the scroller — patching the badge only
   means the next author's badge leaks the same way. Costs no stacking context: `z-index` stays
   `auto`, and every `.a-sr` here keeps its static position either way. */
.alt-app .a-tb-lanes{
  display:flex; align-items:flex-start; gap:var(--a-2); min-width:0; position:relative;
  overflow-x:auto; overscroll-behavior-x:contain; padding-bottom:var(--a-1);
}
/* `--a-tb-lane-w`/`--a-tb-card-gap` are declared ON THE LANE, not on `.alt-app` or
   `.a-tb-lanes`, for two reasons: every override below targets this same `.a-tb-lane` selector
   at identical or higher specificity, so a variable here wins the same cascade the plain
   declarations already relied on; and `--a-tb-card-gap` is read by `.a-tb-cards`, a DESCENDANT
   that inherits it, keeping the wide-lane formula and the card gap reading one number rather than
   two. 274px/6px are today's values — this just re-derives the existing geometry. */
.alt-app .a-tb-lane{
  /* 250px: proven already by the 1023px breakpoint below, which has always narrowed a
     comfortable lane to exactly this width and still holds a card's title, blurb, tags,
     assignee and date. Comfy still scrolls sideways at this width (about five and a half lanes
     at 1456px) — the density that fits a WHOLE board is compact, a two-line 224px row that
     fits six; that split of labour (comfy buys legibility with width, compact buys the board
     with height) is deliberate, not a compromise either density failed to reach. */
  --a-tb-lane-w:220px; --a-tb-card-gap:6px;
  /* `--a-tb-lane-pad` is a third variable here for the same reason as the first two: the lane's
     horizontal inset is read by THREE rules that must agree to the pixel — this padding, the
     `.wide` width formula below, and `.a-tb-lane-hr`'s negative margin. Written as a literal in
     three places, a narrower density would have to find and change all three, and a missed one
     fails SILENTLY (an overhang, or a wide lane whose two columns don't match its own single
     column). ANY SHEET NARROWING THE LANE MUST SET THIS, NEVER `padding` DIRECTLY. */
  --a-tb-lane-pad:var(--a-2);
  flex:0 0 auto; width:var(--a-tb-lane-w); min-width:0; display:flex; flex-direction:column;
  gap:var(--a-2);
  /* Top padding is gone, the other three are not — `.a-tb-lane-hr` is a BAND now (fills the
     lane's width, carries its own surface, closes with a rule), and a band can't start 8px
     below the lane's border. The horizontal inset stays because the wide-lane formula and
     `.collapsed`'s own padding both subtract it; the band reaches past it with a negative
     margin of the same variable — one rule to read, not five children to re-inset. */
  padding:0 var(--a-tb-lane-pad) var(--a-tb-lane-pad); border-radius:var(--a-r);
  background:var(--a-panel); border:1px solid var(--a-line-soft);
}
/* The wide lane holds two card columns at the cards' ORDINARY width, never a narrower one — the
   point of widening a lane is two full cards side by side, not two squeezed ones. A normal
   card's width is the lane minus its own padding and border:
     card = --a-tb-lane-w - 2 * --a-tb-lane-pad - 2px (the 1px border each side)
   so working the subtraction backwards from two cards plus the gap between them:
     wide lane = 2 * --a-tb-lane-w - 2 * --a-tb-lane-pad - 2px + --a-tb-card-gap
   Checked at both geometries this file declares (274/8/6 and 250/8/6): the resulting card width
   matches the single-column width above exactly, in both cases. Reads the PADDING VARIABLE
   rather than a literal, so a density that narrows the inset gets a wide lane whose columns
   still match its single column, instead of silently 2px off. */
.alt-app .a-tb-lane.wide{
  width:calc(2 * var(--a-tb-lane-w) - 2 * var(--a-tb-lane-pad) - 2px + var(--a-tb-card-gap));
}
/* A collapsed lane is a readable strip and nothing more — the name turns with the column so it
   stays readable rather than reducing to a chevron somebody has to hover to identify, and the
   strip stays wide enough to be a comfortable click target. */
.alt-app .a-tb-lane.collapsed{ width:38px; padding:var(--a-2) 4px; }
.alt-app .a-tb-lane.collapsed .a-tb-lane-h{
  flex-direction:column; height:auto; gap:6px; padding:2px 0; justify-content:flex-start;
}
.alt-app .a-tb-lane.collapsed .a-tb-lane-h .nm{
  writing-mode:vertical-rl; text-orientation:mixed; max-height:220px;
  overflow:hidden; text-overflow:ellipsis;
}
/* `.a-tb-lane-flag` came out of this list with the chip itself — an `.sr-only` span is already
   off screen, so hiding it in a collapsed lane would say nothing and would take the words away
   from the one reader still relying on them. */
.alt-app .a-tb-lane.collapsed .a-tb-cards,
.alt-app .a-tb-lane.collapsed .a-tb-add{ display:none; }
/* A folded lane never builds this control in the first place (altTBLaneColumn() only appends it
   when not collapsed) — kept as its OWN rule rather than merged into the selector list above,
   which alt-team-board-lanes.test.js §14 asserts against verbatim. */
.alt-app .a-tb-lane.collapsed .a-tb-lane-more{ display:none; }
/* Same belt-and-braces, one control later: a folded lane can carry a RETAINED `wide` class from
   before collapse (ALT_TB_LANE_WIDE keeps the entry so reopening restores it), and the `.wide`
   formula must not win that fight — a folded lane is a 38px strip regardless. Kept as its own
   rule so nothing here has to be re-derived from a selector list a future edit might merge. */
.alt-app .a-tb-lane.collapsed.wide{ width:38px; }
.alt-app .a-tb-lane.collapsed .a-tb-lane-w{ display:none; }

/* The header row. `.a-tb-lane-h` is a <button> and the width toggle can't nest inside one, so
   this wrapper is the lane's actual first child now — every existing descendant selector for
   `.a-tb-lane-h` still matches through the extra layer. */
.alt-app .a-tb-lane-hr{
  display:flex; align-items:center; gap:4px; min-width:0;
  /* `width:auto` replaces `width:100%` because a stretch-aligned flex child already fills the
     line box, and 100% of the content box plus the negative margin would overflow by 16px. */
  margin:0 calc(-1 * var(--a-tb-lane-pad)); width:auto;
  padding:var(--a-1) var(--a-2);
  background:var(--a-raised);
  border-bottom:1px solid var(--a-line-soft);
  /* Inner radius is the outer minus the 1px border it sits inside (9 - 1 = 8) — the band is
     flush against the lane's border on three sides, so the curves ARE concentric here. */
  border-radius:calc(var(--a-r) - 1px) calc(var(--a-r) - 1px) 0 0;
}
/* ---------- WHAT A LANE HEADER'S COLOUR IS ALLOWED TO SAY ----------
   Three treatments only — the wireframe's five (maroon Blocked, purple Monitor, blue On Hold)
   are overruled by the owner's written direction: the board already carries a status chip, a
   tag vocabulary and late-date red, and a fourth colour language on lane names would be the one
   of the four that means nothing the data model can check.
   Neither hook below is a NAME — `data-lane-completes` is `laneCompletesTasks(lane)` (the
   `is_completion_lane` flag) and `data-lane-first` is rendered position; a name match would
   colour the wrong column on most of the real estate, where a lane literally named "Complete"
   is not always the completion lane. Nor is either hook `board_swimlanes`' own default-lane
   column — that column is DEAD (zero readers, zero writers, always false), not merely unused,
   which is the more dangerous shape: an absent column announces itself the moment anyone
   queries it, a dead one silently never matches.
   No precedence rule between the two attributes, and there must not be one: altTBLanesRow()
   already resolves "first lane is also the completion lane" upstream, by skipping a completion
   lane when picking `isFirstLane`. A CSS rule making one beat the other would guard a state the
   markup cannot produce. */

/* ---------- 4.5:1 IS THE CONSTRAINT THAT SHAPES BOTH TREATMENTS BELOW ----------
   The lane count and chevron are 11px text, so the 4.5:1 floor applies, not the 3:1 large-text
   allowance. Every figure below was MEASURED in the page with alpha composited down the
   ancestor chain, both themes. The ordinary header has almost no headroom (`--a-faint` at 11px
   on `--a-raised` clears the floor by 0.11), so the count's legibility is a property of the BAND
   underneath it — any repaint of the band repays that 0.11 immediately. Do not design a new band
   fill at the edge of the floor; measure it. */
/* First in board order — restrained neutral gray, fill removed on purpose: a fill one step above
   the ordinary band cost the count 0.29-0.48 of contrast (under 4.5) for a difference that was,
   measured, barely perceptible. The treatment is the rule alone: `--a-line-strong`, the 3:1
   control-grade token, 2px, full lane width — visible without a second colour vocabulary, and it
   touches no text, so the count measures identical to every ordinary lane.
   `box-shadow:inset` rather than a border-bottom, because a border is part of the box: 2px where
   every other band has 1px would make this band a pixel taller and the intake lane one pixel out
   of line with the five beside it. An inset shadow paints inside the box and moves nothing. */
.alt-app .a-tb-lane[data-lane-first] .a-tb-lane-hr{
  box-shadow:inset 0 -2px 0 var(--a-line-strong);
}
/* The completion lane — green, because it is the only lane that finishes work, the same green
   its own `.sr-only` "Completes tasks" states in words. Name, count and chevron move to
   `--a-green` together: a green band with a `--a-text` name would read as a highlight applied TO
   the header, not the header's own colour. */
.alt-app .a-tb-lane[data-lane-completes] .a-tb-lane-hr{
  background:var(--a-green-soft);
  box-shadow:inset 0 -2px 0 var(--a-green);
}
.alt-app .a-tb-lane[data-lane-completes] .a-tb-lane-h{ color:var(--a-green); }
/* ---------- AND THE COUNT IS NOT QUIETED BY OPACITY, BECAUSE IT CANNOT AFFORD TO BE ----------
   `opacity:.72` measured a 3.11/3.32 contrast failure on the one lane this redesign deliberately
   draws attention to — the green alone measures 5.00/5.24; the alpha was the whole of it. No
   other colour can carry the recession either (measured: `--a-faint` fails dark too, `--a-dim`
   is brighter than the name above it) — every legal colour on this band is at or above the
   name's own contrast, so colour has run out of room to say "quieter" here. The recession is
   structural instead: weight 600 against the name's 700, zero letter-spacing against `.06em`,
   and `margin-left:auto` — the same signals the ordinary header uses IN ADDITION to colour, here
   carrying it alone. */
.alt-app .a-tb-lane[data-lane-completes] .a-tb-lane-h .cv,
.alt-app .a-tb-lane[data-lane-completes] .a-tb-lane-h .n{ color:var(--a-green); }

/* A folded lane is one strip, not a band on top of a strip, and this rule is LAST on purpose —
   it ties with the two treatments above at equal specificity and must win, or a folded completion
   lane would be green-filled end to end. What it does not touch is the text: `.a-tb-lane-h`'s own
   `color` is a different selector, so a folded completion lane still turns its name green. */
.alt-app .a-tb-lane.collapsed .a-tb-lane-hr{
  margin:0; width:100%; padding:0;
  background:transparent; border-bottom:0; border-radius:0; box-shadow:none;
}

.alt-app .a-tb-lane-h{
  display:flex; align-items:center; gap:6px; min-width:0; width:100%;
  padding:3px 4px; border:0; border-radius:var(--a-r-sm);
  background:transparent; color:var(--a-text); cursor:pointer; text-align:left;
  font:700 11px/normal var(--a-body); letter-spacing:.06em; text-transform:uppercase;
  /* §18's pointer floor, 24px everywhere, not only on a phone — this is an 11px caption in a
     3px-padded button (256x20 measured) that folds a whole swimlane, pressed often and missed
     easily. The box grows rather than a pseudo-element because the header owns its own row and
     has nothing to overlap. Declared here rather than in alt-responsive.css because that sheet
     names no team-board selector. */
  min-height:24px;
}
/* The hover had to change with the band: `--a-raised` is now the ordinary band's OWN colour, so
   on four of six lanes the hover would repaint the header the shade it already was. A tint of the
   header's own text colour works on all three treatments instead of needing a fourth fixed
   surface right on green, gray and neutral. `--a-float` first for engines that discard the
   color-mix() line whole — wrong on the green band but visible, and the fallback path rather
   than the path. */
.alt-app .a-tb-lane-h:hover{
  background:var(--a-float);
  background:color-mix(in srgb, currentColor 12%, transparent);
}
.alt-app .a-tb-lane-h:focus-visible{ outline:2px solid var(--a-ring); outline-offset:1px; }
/* The chevron TURNS, it does not swap — altTBLaneColumn() (JS/alt/alt-team-board.js) emits one
   `chev` glyph in every state with `aria-expanded` on the header, matching the four rotating
   chevrons elsewhere in the suite. Without the rule below a collapsed lane draws a DOWN
   chevron, stating the opposite of its own state. `--a-quick`, the clock every chevron takes,
   so a disclosure and its arrow don't run at two speeds; reduced motion is answered at the
   token (alt-tokens.css), no @media here. It rotates in the collapsed strip too: the strip
   turns the NAME with `writing-mode`, but the chevron is the control's state and still points
   the way out of the fold. */
.alt-app .a-tb-lane-h .cv{ display:flex; flex:0 0 auto; color:var(--a-faint);
  transition:transform var(--a-quick) var(--a-ease); }
.alt-app .a-tb-lane-h[aria-expanded="false"] .cv{ transform:rotate(-90deg); }
.alt-app .a-tb-lane-h .cv > svg{ width:13px; height:13px; }
.alt-app .a-tb-lane-h .nm{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* The count is the lane's size, not its name — quiet, and in the tabular face so a column of
   them lines up. */
.alt-app .a-tb-lane-h .n{
  margin-left:auto; flex:0 0 auto; font-size:11px; font-weight:600; letter-spacing:0;
  color:var(--a-faint); font-variant-numeric:tabular-nums;
}

/* ---------- the lane width toggle ----------
   Secondary to the lane name it sits beside, like `.a-tb-topic-acts` to a topic's name — quiet
   until actually looked at. Shape and states reproduce `.a-theme-seg-b`/`.a-search-scope-b`
   (alt-shell.css) rather than sharing them: a single icon toggle, not a segmented group, so it
   takes their square-button geometry without their pill wrapper. 24x24 is the §18 floor by
   construction, matching `.a-tb-lane-h`'s own min-height. */
.alt-app .a-tb-lane-w{
  display:inline-flex; align-items:center; justify-content:center;
  width:24px; height:24px; flex:0 0 auto; padding:0;
  border:1px solid transparent; border-radius:var(--a-r-sm);
  background:transparent; color:var(--a-faint); cursor:pointer;
  transition:background var(--a-quick) var(--a-ease), color var(--a-quick) var(--a-ease),
             border-color var(--a-quick) var(--a-ease);
}
.alt-app .a-tb-lane-w .cv{ display:flex; }
.alt-app .a-tb-lane-w .cv > svg{ width:13px; height:13px; }
.alt-app .a-tb-lane-w:hover{ color:var(--a-text); background:var(--a-raised); }
/* The raised chip — the same three-signal pressed state `.a-theme-seg-b[aria-pressed="true"]`
   uses (own surface, control-grade edge, elevation), so a wide lane's toggle is identifiable to
   a reader who can't use colour alone, in either theme. */
.alt-app .a-tb-lane-w[aria-pressed="true"]{
  background:var(--a-float); color:var(--a-blue-text);
  border-color:var(--a-line-strong); box-shadow:var(--a-shadow-sm);
}
.alt-app .a-tb-lane-w:focus-visible{ outline:2px solid var(--a-ring); outline-offset:1px; }

/* `.a-tb-lane-flag` was here and is gone — the chip it painted is now a plain `.sr-only` span
   (altTBLaneColumn()): the words survive for a reader who can't use the green, and the pill no
   longer restates on screen what the green header and the lane's own name already say. Nothing
   styles that span here — `.sr-only` (CSS/components.css) takes it off screen entirely. */

/* ---------- the cards in a lane ---------- */
.alt-app .a-tb-cards{
  display:flex; flex-direction:column; gap:var(--a-tb-card-gap); min-height:48px; min-width:0;
  max-height:none; border-radius:var(--a-r-sm);
  /* A drop target has to be a target before anything is over it. 48px is derived, not picked:
     the shortest droppable thing is a COMPACT row (32-44px), so a floor under 44 would make the
     empty target smaller than the smallest card it accepts; 48 clears that by one gap. It is a
     FLOOR, not the empty lane's own height (`.a-tb-lane-empty` sets that). `transition` on the
     outline only, since a background change on a full lane would repaint every card in it. */
  outline:1px dashed transparent; outline-offset:-1px;
  transition:outline-color var(--a-quick) var(--a-ease), background var(--a-quick) var(--a-ease);
}
/* Where the card will land, while it is being dragged. Without it a drag is a guess. */
.alt-app .a-tb-cards.over{ outline-color:var(--a-blue); background:var(--a-blue-soft); }
/* A wide lane flows its cards into two columns, never reorders them — DOM order stays the only
   order there is, because drag/drop and keyboard navigation both read it and CSS must not
   contradict either. `grid-auto-flow:row` (the default) keeps the same left-to-right,
   top-to-bottom reading the single column already has, wrapped at two. `minmax(0,1fr)` keeps
   the two columns exactly equal and `--a-tb-lane-w`-driven, matching `.wide`'s own arithmetic.
   Forbidden on this class for the same DOM-order reason: `order`, `grid-auto-flow:column`,
   `dense`, `direction:rtl`, `row-reverse`, `column-reverse`. */
.alt-app .a-tb-lane.wide .a-tb-cards{
  display:grid; grid-template-columns:repeat(2, minmax(0,1fr));
  gap:var(--a-tb-card-gap); align-content:start;
}
/* ---------- AN EMPTY LANE IS A PLACE, NOT A GAP ----------
   It's a flex COLUMN because it has two children (title + hint spans, contract §7.1) — two
   inline spans in a `text-align:center` block read as one run of text wrapping wherever it
   fits; a column makes them a headline and a hint instead of a ragged sentence.
   No decorative icon, though the wireframe has one (a document glyph, a party popper for
   Complete) — an icon here is the largest thing in the box and says nothing the two lines
   don't, and a party popper over a board with no finished work reads as mockery.
   88px is measured, not chosen: the two lines come to ~49px of content at this width plus 12px
   padding each end (73px); 88 is the next step up that reads as a deliberate region rather than
   text with a border round it, and still 60px shorter than a single comfy card. */
.alt-app .a-tb-lane-empty{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
  min-height:88px; padding:var(--a-3) var(--a-2);
  text-align:center; color:var(--a-faint); font-size:11px;
  border:1px dashed var(--a-line-soft); border-radius:var(--a-r-sm);
}
/* The headline carries the weight, the hint carries the instruction — the only reason the
   markup splits them. `--a-dim` not `--a-text`: an empty lane is not a thing to look at first,
   and the lane's own header is already the brightest text in the column. */
.alt-app .a-tb-lane-empty-title{ font-size:12px; font-weight:600; color:var(--a-dim); }
/* `22ch` stops the hint setting the lane's own rag — uncapped, the longest hint fills the
   content box edge to edge and reads as a paragraph; capped, it breaks near its own middle.
   `ch` so it follows the reader's chosen application font rather than assuming one. */
.alt-app .a-tb-lane-empty-hint{ font-size:11px; line-height:1.4; max-width:22ch; }
/* The empty state is one message, not a message and a blank column beside it, so it spans both
   tracks of the wide grid rather than looking broken in the first. */
.alt-app .a-tb-lane.wide .a-tb-lane-empty{ grid-column:1 / -1; }

/* "View all N tasks  +M more" / "Show less" — the OKR Kanban's own compaction affordance
   (.okr-area-more, CSS/okr.css), copied because it is the design the owner asked this lane to
   match: full width at the foot of the lane, reading as the end of that lane's list rather than
   an action on the last card. */
.alt-app .a-tb-lane-more{
  width:100%; margin:0; padding:7px 8px; font:inherit; font-size:11px; cursor:pointer;
  background:var(--a-raised); border:1px solid var(--a-line-soft); border-radius:var(--a-r-sm);
  color:var(--a-faint);
}
.alt-app .a-tb-lane-more:hover{ border-color:var(--a-blue); color:var(--a-text); }
.alt-app .a-tb-lane-more:focus-visible{ outline:2px solid var(--a-ring); outline-offset:2px; }
/* How much is behind it, so the offer is worth weighing before it is pressed. */
.alt-app .a-tb-lane-more-n{ color:var(--a-blue-text); font-weight:600; }

/* The comfy card is spacious on purpose. Measured: 150.9-183.5px at 1440px, of which 16px is
   padding and 20px the four inter-block gaps — the rest is the five facts themselves. Padding
   went from `8px 9px` to a square 10, since an optically square inset reads as a card and 9/8
   was an unchosen rectangle; costs 4px of height, the only height this pass adds.
   The gap stays at 5px — the seam between five blocks whose own line boxes already carry
   leading; widening it is how a spacious card becomes a sparse one. */
.alt-app .a-tb-card{
  display:flex; flex-direction:column; gap:5px; min-width:0; padding:10px;
  border-radius:var(--a-r-sm); background:var(--a-raised); border:1px solid var(--a-line-soft);
  cursor:grab;
}
.alt-app .a-tb-card:hover{ border-color:var(--a-line-strong); }
.alt-app .a-tb-card:focus-visible{ outline:2px solid var(--a-ring); outline-offset:1px; }
.alt-app .a-tb-card:active{ cursor:grabbing; }
/* A card somebody may not edit is not draggable, and the cursor has to say so before they try.
   Not dimmed: it is somebody else's work, not disabled work. */
.alt-app .a-tb-card.readonly{ cursor:default; }
/* The card in flight. Without it a drag looks like nothing has been picked up — the card stays
   solid under the cursor and the only feedback is the lane it is over. */
.alt-app .a-tb-card.dragging{ opacity:.5; }
/* ---------- the card that was just created ----------
   A single pulse, then nothing — creating a task rebuilds the board and restores scroll, so the
   new card lands correctly but out of sight; altTBApplyFlash() scrolls to it and adds this for
   one animation. Deliberately NOT a selected state: a card still lit a minute later stops
   meaning "you just made this" and starts meaning "this one is special".
   `--dur-flash` (CSS/tokens.css) is the shared token for exactly this gesture. The ring is
   `box-shadow`, not `border`, so the card doesn't change size and shift the lane mid-animation. */
@keyframes altTbNewCard{
  0%   { box-shadow:0 0 0 2px var(--a-blue); background:var(--a-blue-soft); }
  70%  { box-shadow:0 0 0 2px var(--a-blue); background:var(--a-blue-soft); }
  100% { box-shadow:0 0 0 2px transparent;   background:var(--a-raised); }
}
.alt-app .a-tb-card.is-new,
.alt-app .a-tb-row.is-new{ animation:altTbNewCard var(--dur-flash,1.1s) var(--ease-out,ease-out) 1; }
/* The scroll still happens. Somebody who asked for less motion asked not to be animated AT, not
   to be left hunting for the card they just made — the pulse becomes a static ring instead. */
@media (prefers-reduced-motion: reduce){
  .alt-app .a-tb-card.is-new,
  .alt-app .a-tb-row.is-new{ animation:none; box-shadow:0 0 0 2px var(--a-blue); }
}
.alt-app .a-tb-card-top{ display:flex; align-items:flex-start; gap:7px; min-width:0; }
.alt-app .a-tb-card-top .a-check{ flex:0 0 auto; margin-top:1px; }
/* The title is the control that opens the task, so it is a button and it wraps — a truncated
   title is a card you have to open to identify. Two lines, then an ellipsis. */
.alt-app .a-tb-card-t{
  flex:1 1 auto; min-width:0; text-align:left; padding:0; border:0; background:none;
  color:var(--a-text); font:600 13px/1.35 var(--a-body); cursor:pointer;
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
}
.alt-app .a-tb-card-t:hover{ color:var(--a-blue-text); }
.alt-app .a-tb-card-t:focus-visible{ outline:2px solid var(--a-ring); outline-offset:2px; border-radius:2px; }
.alt-app .a-tb-card-desc{
  font-size:11px; line-height:1.45; color:var(--a-faint); min-width:0;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.alt-app .a-tb-card-who{ display:flex; align-items:center; gap:5px; min-width:0; }
.alt-app .a-tb-card-who .nm{
  font-size:11px; font-weight:600; color:var(--a-blue-text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* ---------- THE FOUR FACTS, IN ONE BOX ----------
   `display:contents` is the whole comfy rule — the wrapper around assignee/tags/foot is not a
   layout here, its three children lay out in the card's own column exactly as before it
   existed. Compact turns the same element into a single flex row, which is where a card's
   height actually goes. One element, two behaviours: the alternative (a second card builder for
   compact) is how one density quietly loses a control the other has. */
.alt-app .a-tb-card-meta{ display:contents; }
.alt-app .a-tb-card-tags{ display:flex; flex-wrap:wrap; gap:4px; min-width:0; }
.alt-app .a-tb-card-tags .a-chip{ height:19px; padding:0 7px; font-size:10px; max-width:100%; }
.alt-app .a-tb-card-foot{ display:flex; align-items:center; gap:5px; flex-wrap:wrap; min-width:0; }
/* The foot is two clusters, not one queue — the date is a FACT, the chips beside it are
   CONTROLS (comment count opens the thread, lane chip opens the move menu); left-packing them
   together made the last line a different length on every card. One auto margin pins the date
   left and lets the cluster settle at the right edge. `margin-right` on the date, not
   `margin-left:auto` on the first chip, since the chips are optional and not reliably adjacent
   to the date — a dateless card simply starts its cluster at the left, correctly. */
.alt-app .a-tb-card-foot > .a-tb-due{ margin-right:auto; }
.alt-app .a-tb-due{
  font-size:11px; color:var(--a-faint); font-variant-numeric:tabular-nums; white-space:nowrap;
}
/* Late is the one fact on a card that is urgent, so it is the one thing on it that is red. */
.alt-app .a-tb-due.late,.alt-app .a-row-due.late{ color:var(--a-red); font-weight:600; }
.alt-app .a-tb-card-foot .a-chip{ height:19px; padding:0 6px; font-size:10px; }
.alt-app .a-tb-card-foot .a-chip > svg{ width:12px; height:12px; }
/* §18 — a floor applies to a TARGET, not a label, and the foot holds both. 19px is right for
   the OKR badge (nobody presses it); it was also reaching the comment chip and lane control,
   which are buttons. `button.a-chip{min-height:24px}` (alt-responsive.css) is the floor; this
   selector was beating it on specificity, not on a wrong value. Costs no height: the lane
   control already sets the row. */
.alt-app .a-tb-card-foot button.a-chip{ height:auto; min-height:24px; }
/* ---------- YOU ARE WATCHING THIS ONE ----------
   A statement, not a control — takes no focus, needs no keyboard equivalent, since watching
   starts/stops on the task itself. Named with `.a-sr` text, not a bare glyph, and blue (this
   suite's "concerns you" colour, not the red reserved for late).
   `.noglyph` is a bridge: altIcon() answers an empty <svg> for an unknown name, and an invisible
   badge looks broken — until `eye` lands in JS/components/alt-icons.js, a CSS ring draws
   instead, deliberately, rather than a second inline eye SVG duplicating the icon set. */
.alt-app .a-tb-watch{
  display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto;
  width:16px; height:16px; color:var(--a-blue-text);
  /* Measured, not decoration: `.a-sr` is `position:absolute` with `clip`, which hides the paint
     but still counts toward its containing block's scrollable overflow. With no positioned
     ancestor between the badge and `main.a-page`, one such span pushed the PAGE's scrollWidth
     by 172px at 768px — a document-wide horizontal scrollbar. `position:relative` makes this box
     the containing block instead, so the overflow has nowhere to go. Any `.a-sr` inside an
     unpositioned inline box carries the same trap. */
  position:relative;
}
.alt-app .a-tb-watch > svg{ width:14px; height:14px; }
.alt-app .a-tb-watch.noglyph{ width:12px; height:12px; }
.alt-app .a-tb-watch.noglyph::before{
  content:''; width:11px; height:8px; border:1.6px solid currentColor;
  border-radius:60% 60% 60% 60% / 80% 80% 80% 80%;
}
/* ---------- THE CARD'S LANE, WHICH IS ALSO HOW IT MOVES ----------
   The statement and the control are one thing — one chip of chrome instead of a full-width
   `<select>` per card, same capability through the same altTBMoveTask(). Almost nothing is
   authored here: it wears `.a-chip.a-chip-st` (alt-components.css), which already gives the
   button its cursor, hover border, focus ring, ellipsis and status colour
   (`--a-st-line`/`--a-st-text` from altStatusKind()) — a card the reader may or may not edit
   reads alike, because they're the same component. No authored z-index: the menu it opens is
   placed by altMenuOpen() through layerNextZ(). */

/* The card is a flex COLUMN, so a chip left alone stretches the lane's width — the select again
   in different clothes. `max-width` overrides the chip's own 170px cap the other way: a long
   lane name must ellipsis inside the column, not past it. */
.alt-app .a-tb-card .a-tb-lane-btn,
.alt-app .a-tb-card .a-chip-st{ align-self:center; max-width:100%; }
/* ---------- AND IT NO LONGER PRINTS THE LANE'S NAME ----------
   The column above the card already says it — a chip repeating "Blocked" under the Blocked
   header spent a whole line of card height restating a heading two inches above it. What's kept
   is every part of the guarantee sys_product-rules.md defends: the lane is still on the card in
   the chip's `aria-label`, its `title`, the card's own accessible name and this colour; the
   completion lane still names itself from its flag inside the menu; `m`/Enter/Space still open
   that menu. `.dot` and the border take `--a-st-text`/`--a-st-line` from `st-<kind>`, so the
   colour that carried the lane before still carries it. `min-width` reaches the §18 floor in the
   other axis; `button.a-chip` (alt-responsive.css) owns the height. */
.alt-app .a-tb-card .a-chip-st.mini{
  gap:3px; padding:0 5px; min-width:24px; justify-content:center; flex:0 0 auto;
}
.alt-app .a-tb-card .a-chip-st.mini > svg{ width:11px; height:11px; }
/* A read-only card's chip is not pressable, so it is the dot alone — the floor is on a TARGET
   and this is a label. §18. */
.alt-app .a-tb-card span.a-chip-st.mini{
  min-width:0; width:14px; height:14px; padding:0; border-radius:50%;
  position:relative;                 /* it holds an `.a-sr` too — see .a-tb-watch above */
}
/* The menu is open and this is what opened it — the chip holds emphasis while the reader is
   choosing, so the popover is anchored to something that still looks pressed. */
.alt-app .a-tb-lane-btn[aria-expanded="true"]{
  border-color:var(--a-st-line); color:var(--a-text); background:var(--a-float);
}
/* A lane that is not on this board any more — renamed or removed under the task. Not an error,
   a fact the card has to state, so it is dashed rather than red; the menu is the way out. */
.alt-app .a-tb-lane-btn.off-board{ border-style:dashed; }
/* The destinations. The completion lane's `completes` arrives in the menu's own `.k` hint slot,
   so the only thing to set is a width the lane names do not fight over. */
.alt-app .a-tb-lane-menu{ min-width:190px; }

/* ---------- adding a task in the lane ----------
   `.a-add-row` is the shared dashed "add here" control, reused rather than rebuilt. One thing
   doesn't survive the move: alt-tasks.css sizes its icon via a nested-button selector that
   matches nothing here, since the glyph is a direct child — and an <svg> with a viewBox and no
   dimensions expands to fill its box (measured: ~120px tall, an empty lane three times a full
   one's height). */
.alt-app .a-tb-add{ min-width:0; }
.alt-app .a-tb-add > .a-add-row{
  margin-bottom:0; padding:6px 9px; font-size:12px; justify-content:center; gap:6px;
}
.alt-app .a-tb-add > .a-add-row > svg{ width:14px; height:14px; flex:0 0 auto; }
.alt-app .a-tb-add .a-add-live{ margin-bottom:0; flex-wrap:wrap; gap:5px; }
.alt-app .a-tb-add-input{ flex:1 1 100%; width:100%; }
.alt-app .a-tb-add-more{ flex:0 0 auto; }

/* ---------- the sectioned lists: Owned, Completed, Archived ---------- */
.alt-app .a-tb-list{ display:flex; flex-direction:column; gap:var(--a-2); min-width:0; }
/* A row here carries a title block, a date, an avatar and its actions on ONE line, centred
   against each other rather than top-aligned — the shared `.a-row` assumes a two-line block;
   this one is one line. */
.alt-app .a-tb-list .a-tb-row{ align-items:center; }
.alt-app .a-tb-row{ min-width:0; }
/* The actions sit at the row's end and stay quiet until hovered/focused — three buttons at full
   strength on every row makes a list of them unreadable. */
.alt-app .a-tb-row-acts{ display:inline-flex; align-items:center; gap:var(--a-1); flex:0 0 auto; }
.alt-app .a-tb-row-acts .a-btn{ opacity:.6; }
.alt-app .a-tb-row:hover .a-tb-row-acts .a-btn,
.alt-app .a-tb-row:focus-within .a-tb-row-acts .a-btn{ opacity:1; }
/* A completed row is still legible — struck-through titles at half opacity make a Completed view
   unreadable to the person reviewing the week; the tick and date already say it's done. */
.alt-app .a-tb-row.done .a-row-t{ color:var(--a-dim); }

/* ---------- density: THE KANBAN'S COMPACT RULES LEFT THIS FILE ON 2026-09-12 ----------
   Comfy is all this sheet describes; every `.a-tb-compact`/`.a-compact` rule the BOARD needs is
   the other file's. The two rules just below did NOT move with it and are not an exception to
   that: Owned/Completed/Archived are `.a-row` LISTS drawn by `.a-tb-list` above, and nothing the
   card grid does reaches them — putting a list rule in the kanban's compact sheet would satisfy
   a pattern while moving a rule away from the thing it describes. */

/* ---------- COMPACT ON THE THREE LISTS ----------
   Owned/Completed/Archived draw `.a-row`, so they take alt-shell.css's shared compact rules —
   one of which is written for the TASK LIST's markup (`.a-row-meta > *{display:none}` with
   `.a-row-tags` restored). A team row puts its lane chip and tags DIRECTLY in the meta box and
   has no `.a-row-tags`, so the shared rule would empty it; these two put the row's own chips
   back and cap them. */
.alt-app.a-compact .a-tb-row .a-row-meta > .a-chip{ display:inline-flex; }
.alt-app.a-compact .a-tb-row .a-row-meta > .a-chip:nth-child(n+4){ display:none; }


/* ================= THE BOARD AT EVERY WIDTH =================
   These two breakpoints have to live here, not in alt-team.css (which loads first) — a media
   query adds no specificity, so a plain rule in THIS file would beat an `@media` override
   written in THAT one purely on source order, and the lanes would stop narrowing below 1023px
   with nothing on screen to say why. A rule and its overrides belong in one file.
   The principle: A BOARD IS A HORIZONTAL THING AND STAYS ONE — no mobile fallback layout. What
   changes with the window is how many lanes are on screen at once, via `.a-tb-lanes`'s own
   sideways scroll. Nothing is hidden, no lane dropped, no width turns the kanban into a list. */
@media (max-width:1023px){
  .alt-app .a-tb-lane{ --a-tb-lane-w:250px; }
}
@media (max-width:479px){
  /* On a phone one lane fills the width, so a swipe moves between lanes rather than showing
     slivers of three. `calc`, not a number: the row's own padding is the only thing between the
     lane and the band's edge. */
  .alt-app .a-tb-lane{ --a-tb-lane-w:calc(100% - 8px); min-width:220px; }
  .alt-app .a-tb-lane.collapsed{ width:38px; min-width:38px; }
  /* The board frame's padding charges rent a phone can't afford: the page is already inset 12px
     a side (alt-responsive.css), so a 12px frame padding too leaves the lane row 340px of a
     390px screen, 12px each side spent restating a boundary the border and radius already draw.
     At `--a-2` the lane gets 340px instead of 332 — a whole tag chip back on the one layout with
     none to spare. The radius steps down a tier with it: 14px around an 8px inset reads as a
     bubble. */
  .alt-app .a-tb-scroll:has(.a-tb-lanes){ padding:var(--a-2); border-radius:var(--a-r); }
}

/* ================= PINNED, OR THE WHOLE PAGE SCROLLS =================
   Default is pinned and nothing below applies: alt-shell.css makes this experience a
   fixed-height frame above 1024px (`.alt-app{height:100vh; overflow:hidden}`), so the context
   bar and the two analytic bands stay on screen and the board scrolls inside it.
   Unpinned adopts the arrangement the shell already has below 1024px (fixed height dropped, page
   scrolls normally) — reached here at a second width rather than kept as a second layout.
   SPECIFICITY, not source order, is what makes it win: `.alt-app{height:100vh}` sits inside a
   media query in a sheet that loads first, and a media query adds no specificity — beating it on
   source order alone would be an accident waiting to be undone by a file reorder.
   `.a-tb-unpinned` is on <body>, put there by altTBApplyHeaderPin() only on the board page, so
   nothing here reaches the calendar, notes or the task list. */
/* The gutter is reserved in both states so pressing the pin cannot move the page sideways.
   Unpinned, the DOCUMENT scrolls, and the moment it does the viewport loses the scrollbar's
   width — measured: every band narrowed by 11px and Filters jumped left on a press that was
   meant to change only what scrolls. `scrollbar-gutter:stable` holds the space in both states.
   This sheet loads on team_board.html alone, so the rule against `html` cannot reach another
   page. */
html{ scrollbar-gutter:stable; }
.alt-app.a-tb-unpinned{ height:auto; min-height:100vh; overflow:visible; }
/* The centre column clips by default so its own child can be the scroller; unpinned there is no
   inner scroller to clip for. */
.alt-app.a-tb-unpinned .a-centre{ overflow:visible; }
/* `flex:1 0 auto`, not `0 0 auto` — the latter collapsed the frame to the height of its own
   content on a window taller than the board (measured: 394px of empty page below it at 1289px).
   Grow (1) fills the window when the board is short; no-shrink (0) lets it exceed the window
   when the board is tall; `min-height:auto` is what allows that second half — both behaviours
   from one declaration, sized from the window rather than any number in this file. */
.alt-app.a-tb-unpinned #altTeamView{ flex:1 0 auto; min-height:auto; }
/* And the board stops being a scroller — `overflow-y:visible` rather than removing the rule, so
   `.a-tb-lanes`'s own horizontal scroll (a different axis, a different element) is untouched.
   Both axes have to move together: `overflow-y:visible` ALONE does nothing while
   `overflow-x:hidden` sits beside it (one non-visible axis computes the other to `auto`) —
   measured, the board kept its own nested scrollbar until both were released. */
.alt-app.a-tb-unpinned .a-tb-scroll{ flex:1 0 auto; min-height:0; overflow:visible; }
/* The utility rail scrolled itself inside the frame; with no frame it grows with the page like
   everything else — a sticky rail over a scrolling board is a different feature nobody asked
   for. */
.alt-app.a-tb-unpinned .a-right{ overflow-y:visible; }
