/* public/style.css -- Doni kanban client stylesheet.
   Every visual token below is transcribed verbatim from the approved
   03-UI-SPEC.md (Design System / Spacing / Typography / Color / Layout
   structure / Card anatomy / Interaction states / Copywriting Contract).
   No CDN, no webfont, no build step -- hand-authored vanilla CSS only. */

:root {
  /* --- Color: Dominant (60%) / Secondary (30%) / Accent (10%) / Destructive --- */
  --color-dominant: #F1F5F9;      /* slate-100: page background */
  --color-secondary-card: #FFFFFF; /* card surfaces, column body */
  --color-secondary-strip: #E2E8F0; /* column header bar, bucket-tab bar bg */
  --color-accent: #2563EB;        /* blue-600: primary CTA, active tab, focus ring, drag-over */
  --color-destructive: #DC2626;   /* red-600: archive/error only */

  /* --- Tag hash palette (6 slots, hash(tag) % 6) --- */
  --tag-1-bg: #E2E8F0; --tag-1-text: #334155; /* slate */
  --tag-2-bg: #CCFBF1; --tag-2-text: #0F766E; /* teal */
  --tag-3-bg: #FEF3C7; --tag-3-text: #92400E; /* amber */
  --tag-4-bg: #EDE9FE; --tag-4-text: #5B21B6; /* purple */
  --tag-5-bg: #FFE4E6; --tag-5-text: #9F1239; /* rose */
  --tag-6-bg: #ECFCCB; --tag-6-text: #3F6212; /* lime */

  /* --- Assignee chips (fixed: rock/paul/unassigned) --- */
  --assignee-rock-bg: #DBEAFE; --assignee-rock-text: #1E3A8A;
  --assignee-paul-bg: #FCE7F3;  --assignee-paul-text: #9D174D;
  --assignee-unassigned-border: #CBD5E1; --assignee-unassigned-text: #64748B;

  /* --- Neutral / border / muted tones used across interaction states --- */
  --border-default: #E2E8F0;
  --border-hover: #CBD5E1;
  --text-muted: #64748B;

  /* --- D-08 tokenization audit (09-dark-mode): promoted from inline hex so
     the dark-mode token flip can reach them. Light values below are
     byte-for-byte identical to what was previously inlined. --- */
  --color-text-body: #0F172A;
  --color-on-accent: #FFFFFF;
  --color-destructive-subtle-bg: #FFF1F2;
  --color-urgent: #D97706;
  --color-urgent-subtle-bg: #FEF3C7;
  --color-done: #059669;
  --shadow-card: 0 1px 2px rgba(15, 23, 42, 0.06);
  --shadow-dragging: 0 4px 10px rgba(15, 23, 42, 0.18);
  --shadow-dialog: 0 4px 16px rgba(15, 23, 42, 0.24);
  --overlay-scrim: rgba(15, 23, 42, 0.4);
  --toggle-track: rgba(15, 23, 42, 0.07);
  --toggle-chip: #FFFFFF;
  --shadow-toggle-chip: 0 1px 2px rgba(15, 23, 42, 0.14), 0 0 0 1px rgba(15, 23, 42, 0.04);

  /* --- Spacing scale (4px grid) --- */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px; /* reserved, unused this phase */
  --space-3xl: 64px; /* reserved, unused this phase */

  /* Exceptions still on the 4px grid */
  --chip-padding-v: 4px;
  --chip-padding-h: 8px;
  --card-padding: 12px;
  --column-min-width: 280px;
  --board-column-min-width: 440px;
  /* Board's border-box content width = 6 columns + 5 gaps (--space-md) +
     2 side paddings (--space-lg), matching .column-area, so the top bars
     (title-bar/tab-bar/filter-bar) can span the full horizontal-scroll
     width instead of stopping at the viewport edge. */
  --board-content-width: calc(6 * var(--board-column-min-width) + 5 * var(--space-md) + 2 * var(--space-lg));
  --drag-target-min-height: 44px;

  /* --- Typography (2 weights, 4 sizes) --- */
  --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-size-label: 11px;   --font-weight-label: 600; --line-height-label: 1.1;
  --font-size-body: 13px;    --font-weight-body: 400;  --line-height-body: 1.5;
  --font-size-heading: 15px; --font-weight-heading: 600; --line-height-heading: 1.2;
  --font-size-display: 20px; --font-weight-display: 600; --line-height-display: 1.2;
}

/* --- Dark mode (09-dark-mode, THEME-01/THEME-03) -------------------------
   Token-flip, not a restyle: only the color tokens above (never spacing/
   type) get a dark override. The dark value for every token is declared
   ONCE below as an intermediate --dark-* custom property, then assigned to
   the real token name from BOTH dark-activation paths via var()
   indirection -- this guarantees the explicit override
   (:root[data-theme="dark"]) and the OS-default path (the @media block)
   can never drift out of sync (D-02), because they both resolve the same
   underlying variable rather than each carrying its own copy of the hex. */
:root {
  --dark-color-dominant: #111827;
  --dark-color-secondary-card: #1F2937;
  --dark-color-secondary-strip: #374151;
  --dark-color-accent: #93C5FD;
  --dark-color-destructive: #FCA5A5;
  --dark-color-text-body: #F9FAFB;
  --dark-color-on-accent: #111827;
  --dark-color-destructive-subtle-bg: #450A0A;
  --dark-color-urgent: #FBBF24;
  --dark-color-urgent-subtle-bg: #3A2A08;
  --dark-color-done: #34D399;
  --dark-border-default: #374151;
  --dark-border-hover: #4B5563;
  --dark-text-muted: #A0AEC0;

  --dark-tag-1-bg: #334155; --dark-tag-1-text: #E2E8F0; /* slate */
  --dark-tag-2-bg: #134E4A; --dark-tag-2-text: #5EEAD4; /* teal */
  --dark-tag-3-bg: #78350F; --dark-tag-3-text: #FCD34D; /* amber */
  --dark-tag-4-bg: #4C1D95; --dark-tag-4-text: #C4B5FD; /* purple */
  --dark-tag-5-bg: #881337; --dark-tag-5-text: #FDA4AF; /* rose */
  --dark-tag-6-bg: #365314; --dark-tag-6-text: #BEF264; /* lime */

  --dark-assignee-rock-bg: #1E3A8A; --dark-assignee-rock-text: #93C5FD;
  --dark-assignee-paul-bg: #831843; --dark-assignee-paul-text: #F9A8D4;
  --dark-assignee-unassigned-border: #4B5563; --dark-assignee-unassigned-text: #A0AEC0;

  --dark-shadow-card: none;
  --dark-shadow-dragging: 0 4px 10px rgba(0, 0, 0, 0.5);
  --dark-shadow-dialog: 0 4px 16px rgba(0, 0, 0, 0.5);
  --dark-overlay-scrim: rgba(0, 0, 0, 0.6);
  --dark-toggle-track: rgba(0, 0, 0, 0.28);
  /* Derived, not a fixed hex, so the chip follows the injected palette
     (Phase 23 Plan 02, D-12): var() in a custom property resolves on :root,
     where the server-injected <style id="doni-palette"> palette values
     also land -- this needs no per-preset override of its own. */
  --dark-toggle-chip: color-mix(in srgb, var(--color-secondary-strip), #FFFFFF 14%);
  --dark-shadow-toggle-chip: 0 1px 2px rgba(0, 0, 0, 0.5);
}

:root[data-theme="dark"] {
  --color-dominant: var(--dark-color-dominant);
  --color-secondary-card: var(--dark-color-secondary-card);
  --color-secondary-strip: var(--dark-color-secondary-strip);
  --color-accent: var(--dark-color-accent);
  --color-destructive: var(--dark-color-destructive);
  --color-text-body: var(--dark-color-text-body);
  --color-on-accent: var(--dark-color-on-accent);
  --color-destructive-subtle-bg: var(--dark-color-destructive-subtle-bg);
  --color-urgent: var(--dark-color-urgent);
  --color-urgent-subtle-bg: var(--dark-color-urgent-subtle-bg);
  --color-done: var(--dark-color-done);
  --border-default: var(--dark-border-default);
  --border-hover: var(--dark-border-hover);
  --text-muted: var(--dark-text-muted);

  --tag-1-bg: var(--dark-tag-1-bg); --tag-1-text: var(--dark-tag-1-text);
  --tag-2-bg: var(--dark-tag-2-bg); --tag-2-text: var(--dark-tag-2-text);
  --tag-3-bg: var(--dark-tag-3-bg); --tag-3-text: var(--dark-tag-3-text);
  --tag-4-bg: var(--dark-tag-4-bg); --tag-4-text: var(--dark-tag-4-text);
  --tag-5-bg: var(--dark-tag-5-bg); --tag-5-text: var(--dark-tag-5-text);
  --tag-6-bg: var(--dark-tag-6-bg); --tag-6-text: var(--dark-tag-6-text);

  --assignee-rock-bg: var(--dark-assignee-rock-bg); --assignee-rock-text: var(--dark-assignee-rock-text);
  --assignee-paul-bg: var(--dark-assignee-paul-bg); --assignee-paul-text: var(--dark-assignee-paul-text);
  --assignee-unassigned-border: var(--dark-assignee-unassigned-border); --assignee-unassigned-text: var(--dark-assignee-unassigned-text);

  --shadow-card: var(--dark-shadow-card);
  --shadow-dragging: var(--dark-shadow-dragging);
  --shadow-dialog: var(--dark-shadow-dialog);
  --overlay-scrim: var(--dark-overlay-scrim);
  --toggle-track: var(--dark-toggle-track);
  --toggle-chip: var(--dark-toggle-chip);
  --shadow-toggle-chip: var(--dark-shadow-toggle-chip);
}

/* System mode (no explicit data-theme, or an unrecognized value): follow
   the OS. The :not() guard is what makes explicit Light/Dark overrides win
   over the OS preference. Every declaration below is IDENTICAL to the
   :root[data-theme="dark"] block above by construction (same --dark-*
   variable references) -- the two dark paths cannot drift. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) {
    --color-dominant: var(--dark-color-dominant);
    --color-secondary-card: var(--dark-color-secondary-card);
    --color-secondary-strip: var(--dark-color-secondary-strip);
    --color-accent: var(--dark-color-accent);
    --color-destructive: var(--dark-color-destructive);
    --color-text-body: var(--dark-color-text-body);
    --color-on-accent: var(--dark-color-on-accent);
    --color-destructive-subtle-bg: var(--dark-color-destructive-subtle-bg);
    --color-urgent: var(--dark-color-urgent);
    --color-urgent-subtle-bg: var(--dark-color-urgent-subtle-bg);
    --color-done: var(--dark-color-done);
    --border-default: var(--dark-border-default);
    --border-hover: var(--dark-border-hover);
    --text-muted: var(--dark-text-muted);

    --tag-1-bg: var(--dark-tag-1-bg); --tag-1-text: var(--dark-tag-1-text);
    --tag-2-bg: var(--dark-tag-2-bg); --tag-2-text: var(--dark-tag-2-text);
    --tag-3-bg: var(--dark-tag-3-bg); --tag-3-text: var(--dark-tag-3-text);
    --tag-4-bg: var(--dark-tag-4-bg); --tag-4-text: var(--dark-tag-4-text);
    --tag-5-bg: var(--dark-tag-5-bg); --tag-5-text: var(--dark-tag-5-text);
    --tag-6-bg: var(--dark-tag-6-bg); --tag-6-text: var(--dark-tag-6-text);

    --assignee-rock-bg: var(--dark-assignee-rock-bg); --assignee-rock-text: var(--dark-assignee-rock-text);
    --assignee-paul-bg: var(--dark-assignee-paul-bg); --assignee-paul-text: var(--dark-assignee-paul-text);
    --assignee-unassigned-border: var(--dark-assignee-unassigned-border); --assignee-unassigned-text: var(--dark-assignee-unassigned-text);

    --shadow-card: var(--dark-shadow-card);
    --shadow-dragging: var(--dark-shadow-dragging);
    --shadow-dialog: var(--dark-shadow-dialog);
    --overlay-scrim: var(--dark-overlay-scrim);
    --toggle-track: var(--dark-toggle-track);
    --toggle-chip: var(--dark-toggle-chip);
    --shadow-toggle-chip: var(--dark-shadow-toggle-chip);
  }
}

/* Per-instance dark theming used to live here as hardcoded :root-scoped
   dark-mode overrides, one per instance slug (gatinapp, bureaudulibre), each
   keyed off the server-set body[data-doni-instance] attribute via a
   relational CSS pseudo-class selector. Retired per D-12 (Phase 23 Plan 02,
   THEME-04):
   their exact values are now the gatinapp/libre presets' dark sets in
   public/palette.js, carried forward byte-for-byte and reached instead
   through the server-injected <style id="doni-palette"> (D-08's "today's
   dark palettes are kept exactly"), which resolves per-instance via the
   D-13 slug fallback with no code path left that keys off
   body[data-doni-instance] for color at all. */

* {
  box-sizing: border-box;
}

/* The `hidden` attribute must always win over explicit author `display`
   values (.column-area is `display: grid`; .tab-bar/.filter-bar are
   `display: flex`) -- without this, applyViewVisibility() sets `.hidden`
   on the board containers when switching to the Sprints view but they stay
   visible, because an author `display` rule beats the UA `[hidden]` rule.
   (05-sprints: Board|Sprints view toggle.) */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--color-dominant);
  font-family: var(--font-family);
  color: var(--color-text-body);
}

/* Bilingual version footer (D-19/D-20/D-21, Phase 20 Plan 02): deliberately
   NO position property at all -- normal document flow at the end of the
   page, scrolling with the content, never over a board column. */
.app-footer {
  padding: var(--space-md) var(--space-lg);
  font-size: var(--font-size-label);
  color: var(--text-muted);
  text-align: center;
}

/* G-20-1 (Phase 20 Plan 10): the Medinix credit link inherits the muted
   footer color so it reads as part of the credit line in every theme; the
   underline marks it as a link. Tokens only, so dark/gatinapp flips reach
   it automatically. */
.app-footer a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.app-footer a:hover {
  color: var(--color-text-body);
}

.app-footer a:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 2px;
}

/* --- Layout structure (top -> bottom): title bar -> tab bar -> filter bar -> columns --- */

.title-bar {
  background: var(--color-secondary-strip);
  padding: var(--space-md) var(--space-lg);
  height: 56px;
  display: flex;
  align-items: center;
}

/* Board view only: keep the title-bar as wide as the board's scrollable
   content so it doesn't cut off at the viewport edge when scrolled right.
   Scoped to body.board-view so the Sprints view keeps a normal-width bar. */
body.board-view .title-bar {
  min-width: var(--board-content-width);
}

.app-title {
  margin: 0;
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-display);
  line-height: var(--line-height-display);
}

/* Instance/org tag in the logo -- lighter than the "doni" product name
   so multi-instance deployments read as "doni [<instance>]". */
.app-title-instance {
  color: var(--text-muted);
  font-weight: var(--font-weight-body);
}

/* Instance display name (Phase 23 Plan 01, INST-01, D-04): the
   applyBrandOverrides span in the header/login card -- truncates with an
   ellipsis so a 60-code-point stored name never pushes the header controls
   or overflows the login card. */
.app-title-name {
  display: inline-block;
  max-width: 40vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* Stored logo (Phase 23 Plan 03, THEME-06, D-04): the <img> applyBrandOverrides
   prefixes onto the header/login-card title markup -- scoped to ".app-title
   img" (UI-SPEC) so it applies through .auth-logo's wrapping to the login
   card automatically, with no second class or selector needed there. */
.app-title img,
.app-title-logo {
  max-height: 32px;
  width: auto;
  object-fit: contain;
  border-radius: 4px;
  vertical-align: middle;
  margin-right: var(--space-sm);
}

.tab-bar {
  background: var(--color-secondary-strip);
  display: flex;
  gap: var(--space-md);
  padding: 0 var(--space-lg);
  margin-bottom: var(--space-xl);
  /* Hidden in sprint view, so unconditional min-width is safe -- spans
     the board's full horizontal-scroll width (see --board-content-width). */
  min-width: var(--board-content-width);
}

.tab {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
  color: var(--text-muted);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: var(--space-sm) 0;
  cursor: pointer;
  min-height: var(--drag-target-min-height);
}

.tab.active {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

/* "Manage types" button (manage-types relocation; fix round 1 -- see
   below). Lives in .tab-bar itself, appended immediately after the .tab
   elements, IN FLOW with them (no margin-left: auto) -- .tab-bar is as
   wide as the horizontally-scrolling board (min-width:
   --board-content-width above), so pushing the button to the flex row's
   true trailing edge put it off-screen at any normal viewport width; a
   partner would never find it. In-flow, it scrolls together with the tabs
   it configures and stays adjacent to them at any viewport width and any
   number of types -- exactly the trade-off wanted.
   It still must not read as a tab: reuses .btn-save-description's look
   (same precedent as .manage-projects-btn) rather than .tab's underline
   treatment, and a thin separator line ahead of it reads as "an action
   after the list", not one more tab. The separator is a ::before drawn
   OUTSIDE the button (in the margin), not a border-left + padding-left on
   the button itself: on a solid accent background that extra left padding
   pushed the label visibly off-centre. align-self: center keeps it
   vertically centered against the taller .tab elements. */
.tab-bar .manage-types-btn {
  position: relative;
  align-self: center;
  margin-left: var(--space-md);
}

.tab-bar .manage-types-btn::before {
  content: '';
  position: absolute;
  left: calc(-1 * var(--space-md));
  top: 2px;
  bottom: 2px;
  border-left: 1px solid var(--border-default);
}

.filter-bar {
  position: sticky;
  top: 0;
  background: var(--color-secondary-card);
  border-bottom: 1px solid var(--border-default);
  padding: var(--space-sm) var(--space-lg);
  display: flex;
  /* Top-aligned, not stretched: when the Tags chips are open the wrap grows
     tall, and a stretched row pulled every other control (search box,
     segmented toggle) down to that full height. Each control carries its
     own 44px min-height, so the collapsed row still lines up. */
  align-items: flex-start;
  gap: var(--space-sm);
  z-index: 10;
  /* Hidden in sprint view, so unconditional min-width is safe -- spans
     the board's full horizontal-scroll width (see --board-content-width). */
  min-width: var(--board-content-width);
}

.error-banner {
  border: 1px solid var(--color-destructive);
  color: var(--color-destructive);
  background: var(--color-destructive-subtle-bg);
  padding: var(--space-sm) var(--space-md);
  margin: 0 var(--space-lg) var(--space-md);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.error-banner[hidden] {
  display: none;
}

.error-banner-text {
  flex: 1;
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}

.error-banner-retry {
  color: var(--color-on-accent);
  background: var(--color-destructive);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

/* Instance-freeze archive banner (Phase 30 Plan 02, D-04/D-05, CUT-05):
   persistent, never dismissible -- modeled on .notify-banner's spacing and
   radius tokens below, but a warning tone (--color-urgent / its subtle bg)
   rather than the neutral card surface, so it reads as a status notice
   rather than an actionable item; both variables already flip for dark
   mode (:root[data-theme=dark] above), so no separate dark-mode block is
   needed here. */
.archive-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs);
  margin: var(--space-sm) var(--space-lg);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--color-urgent);
  border-radius: 4px;
  background: var(--color-urgent-subtle-bg);
  color: var(--color-text-body);
  font-size: var(--font-size-body);
}
.archive-banner[hidden] { display: none; }
.archive-banner-link {
  color: var(--color-accent);
  text-decoration: underline;
  overflow-wrap: anywhere;
}

/* Reassignment notification banner (assignment-to-you) + card highlight. */
.notify-banner {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: var(--space-sm) var(--space-lg);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--color-accent);
  border-radius: 4px;
  background: var(--color-secondary-card);
  color: var(--color-text-body);
  font-size: var(--font-size-body);
}
.notify-banner[hidden] { display: none; }
.notify-banner-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}
.notify-banner-title { font-weight: var(--font-weight-label); }
.notify-banner-dismiss {
  background: var(--color-accent);
  color: var(--color-on-accent);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  cursor: pointer;
  font-size: var(--font-size-body);
  flex: 0 0 auto;
}
.notify-banner-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.notify-banner-item { display: flex; }
.notify-banner-link {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: var(--space-xs) var(--space-sm);
  border-radius: 4px;
  cursor: pointer;
  color: var(--color-text-body);
  font-size: var(--font-size-body);
  font-family: var(--font-family);
}
.notify-banner-link:hover { background: var(--color-secondary-strip); }
.notify-banner-id { color: var(--color-accent); font-weight: var(--font-weight-label); flex: 0 0 auto; }
.notify-banner-item-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.notify-banner-group-label {
  color: var(--text-muted);
  font-weight: var(--font-weight-label);
  font-size: var(--font-size-label);
  margin-top: var(--space-xs);
}
/* Phase 29 Plan 03 (D-17): the Ownership group's own dismiss -- same look
   as .notify-banner-dismiss above, at a smaller size, since it sits inline
   next to the notice text rather than in the banner's header row. */
.notify-banner-dismiss-one {
  background: var(--color-accent);
  color: var(--color-on-accent);
  border: none;
  border-radius: 4px;
  padding: 2px var(--space-xs);
  margin-left: auto;
  cursor: pointer;
  font-size: var(--font-size-label);
  flex: 0 0 auto;
}

.error-banner-dismiss {
  color: var(--color-destructive);
  background: none;
  border: none;
  font-size: var(--font-size-body);
  cursor: pointer;
  line-height: 1;
}

.column-area {
  display: grid;
  grid-template-columns: repeat(6, minmax(var(--board-column-min-width), 1fr));
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg) var(--space-lg);
}

.column {
  background: var(--color-secondary-card);
  border: 1px solid var(--border-default);
  min-width: var(--column-min-width);
  display: flex;
  flex-direction: column;
}

.column-header {
  background: var(--color-secondary-strip);
  padding: var(--space-lg);
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.column-count-badge {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.column-priority-hint {
  margin-left: var(--space-sm);
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.add-item-btn {
  color: var(--color-on-accent);
  background: var(--color-accent);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

.card-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  overflow-y: auto;
}

/* --- Card anatomy (BOARD-02): default / hover / dragging / saving --- */

.card {
  background: var(--color-secondary-card);
  border: 1px solid var(--border-default);
  box-shadow: var(--shadow-card);
  padding: var(--card-padding);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.card:hover {
  border-color: var(--border-hover);
}

.card.dragging {
  opacity: 0.6;
  transform: rotate(-2deg);
  box-shadow: var(--shadow-dragging);
}

/* Urgent: amber sibling of blocked below, orthogonal to priority -- renders
   wherever the card appears, same composition (border + subtle fill from
   dedicated tokens, already dark-mode-token-flipped). Placed BEFORE
   .card.blocked so equal-specificity source order makes blocked (red) win
   when both classes are set on the same card. */
.card.urgent {
  border-color: var(--color-urgent);
  background: var(--color-urgent-subtle-bg);
}

/* BOARD-11: blocked is an independent boolean flag, not a status -- it
   renders red wherever the card appears (per-bucket tabs and the All tab
   alike), composed from the existing destructive tokens (already
   dark-mode-token-flipped), never a hardcoded hex. */
.card.blocked {
  border-color: var(--color-destructive);
  background: var(--color-destructive-subtle-bg);
}

.card.notify-mine {
  border-left: 3px solid var(--color-accent);
}

/* Level-1 slim card (2026-07-09 three-state-card-view): the whole .card
   becomes a single compact flex row. Overrides .card's column layout and
   padding; .blocked (red) and .dragging (opacity/rotate) still apply since
   they live on the same element. */
/* The whole slim row opens the editor now (2026-09-22), so it reads as
   clickable. The drag handle keeps its own grab cursor below, which still
   signals that press-and-drag is the other thing this row does. */
.card.card-slim {
  cursor: pointer;
  flex-direction: row;
  align-items: center;
  gap: var(--space-sm);
  padding: 3px 8px;
}

/* The drag dots are the one always-visible affordance in the slim row
   (design: "the only thing that should be visible would be the three dots"),
   unlike the hover-only handle on the expanded card. */
.card.card-slim .drag-handle {
  opacity: 1;
  flex: 0 0 auto;
}

/* Title fills the row and truncates rather than wrapping, keeping every slim
   card exactly one line tall. */
.card-slim-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-body);
  color: var(--color-text-body);
}

/* Board slim row in-sprint cue: accent-colored title when the item is
   referenced by at least one sprint (item.inSprint, /api/board-computed).
   --color-accent already flips at :root[data-theme=dark] and the System
   dark-mode media path, so no separate dark override is needed. */
.card-slim-title-insprint {
  color: var(--color-accent);
}

/* Done indication (board-only, slim-only): a Done-status card that hasn't
   been archived yet dims + strikes through its title, distinguishing it
   from active work while it stays reachable for archiving (buildSlimCard
   gates this on opts.context !== 'sprint'). */
.card.card-slim.done {
  opacity: 0.5;
}

.card.done .card-slim-title {
  text-decoration: line-through;
}

/* Slim-row assignee pill IS the edit trigger (two-state card model,
   2026-07-09) -- cursor + a subtle hover cue so it reads as clickable. */
.slim-assignee {
  cursor: pointer;
}

.slim-assignee:hover {
  opacity: 0.8;
}

/* Editor header's close control -- collapses back to the slim row via
   discardExpanded(). Small/borderless to sit comfortably in .card-top-row
   alongside the id/status/assignee badges. */
.card-edit-done {
  flex: 0 0 auto;
  background: none;
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  cursor: pointer;
  color: var(--text-muted);
  font-size: var(--font-size-body);
  margin-left: auto;
}

.card.saving {
  animation: saving-pulse 1.2s ease-in-out infinite;
}

@keyframes saving-pulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 0.85; }
}

.column.drag-over {
  border: 2px dashed var(--color-accent);
}

/* BUG-008: within-column drop position indicator -- a thin accent line in
   the gap where the dragged card will land. */
.drop-indicator {
  height: 2px;
  margin: 2px 0;
  background: var(--color-accent);
  border-radius: 1px;
  pointer-events: none;
}

.drag-handle {
  color: var(--border-hover);
  opacity: 0;
}

.card:hover .drag-handle {
  opacity: 1;
}

.card-top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.card-id-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  letter-spacing: 0.02em;
  padding: var(--chip-padding-v) var(--chip-padding-h);
  border-radius: 999px;
  background: var(--color-accent);
  color: var(--color-on-accent);
  white-space: nowrap;
  flex: 0 0 auto;
}

/* Type badge (TYPE-BADGE): a subtle, tinted/outlined chip -- deliberately
   NOT the solid-accent .card-id-badge treatment, so it reads as a quiet
   type cue rather than a second id pill. Shown only where buckets are
   mixed (All tab / Sprints view); relies on the existing --border-default
   / --text-muted tokens, which already flip for dark theme + the
   gatinapp warm-orange override, so no separate dark block is needed. */
.card-type-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  letter-spacing: 0.02em;
  padding: var(--chip-padding-v) var(--chip-padding-h);
  border-radius: 999px;
  background: transparent;
  border: 1px solid var(--border-default);
  color: var(--text-muted);
  white-space: nowrap;
  flex: 0 0 auto;
}

/* Project badge on the slim card -- deliberately quieter than the type badge
   so it reads as context, not as the card's primary identity. */
.card-project-badge {
  font-size: var(--font-size-label);
  line-height: 1.6;
  padding: 0 var(--space-xs);
  border: 1px solid var(--border-default);
  border-radius: 10px;
  color: var(--text-muted);
  white-space: nowrap;
}

/* Done chip (board-only, slim-only): small outlined green "✓ Done" chip,
   appended after the assignee so it's the rightmost slim-row element;
   margin-left: auto hugs it to the row's right edge. --color-done already
   flips for dark via the light/dark indirection, so no separate dark
   block is needed. */
.card-done-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  padding: var(--chip-padding-v) var(--chip-padding-h);
  border-radius: 999px;
  background: transparent;
  border: 1px solid var(--color-done);
  color: var(--color-done);
  flex: 0 0 auto;
  margin-left: auto;
  white-space: nowrap;
}

.card-body-text {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  line-height: var(--line-height-body);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card-bottom-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

/* --- Chips: tags, assignee --- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  padding: var(--chip-padding-v) var(--chip-padding-h);
  border-radius: 999px;
}

.tag-chip-1 { background: var(--tag-1-bg); color: var(--tag-1-text); }
.tag-chip-2 { background: var(--tag-2-bg); color: var(--tag-2-text); }
.tag-chip-3 { background: var(--tag-3-bg); color: var(--tag-3-text); }
.tag-chip-4 { background: var(--tag-4-bg); color: var(--tag-4-text); }
.tag-chip-5 { background: var(--tag-5-bg); color: var(--tag-5-text); }
.tag-chip-6 { background: var(--tag-6-bg); color: var(--tag-6-text); }

/* Status badge (BOARD-11): a status is a derived label, not a column --
   same pill composition as .chip, colored from the existing tag token set
   (never a hardcoded hex) so it stays legible in both themes. One variant
   per SECTION_ORDER slug (STATUS_SLUG in app.js). */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  padding: var(--chip-padding-v) var(--chip-padding-h);
  border-radius: 999px;
}

.status-badge-backlog { background: var(--tag-1-bg); color: var(--tag-1-text); }
.status-badge-in-progress { background: var(--tag-3-bg); color: var(--tag-3-text); }
.status-badge-qa { background: var(--tag-4-bg); color: var(--tag-4-text); }
.status-badge-done { background: var(--tag-2-bg); color: var(--tag-2-text); }

/* Quick task 260708-wu3: sprint-view collapsed cards show priority instead
   of status. Same pill shape as .status-badge. Single accent style for all
   P1-P5 (do NOT color-code by priority level); the unprioritized variant
   mirrors .assignee-chip-unassigned's border-only, no-fill treatment. */
.priority-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  padding: var(--chip-padding-v) var(--chip-padding-h);
  border-radius: 999px;
  background: var(--color-accent);
  color: var(--color-on-accent);
}

.priority-badge-none {
  background: none;
  border: 1px solid var(--border-default);
  color: var(--text-muted);
}

.assignee-chip-rock { background: var(--assignee-rock-bg); color: var(--assignee-rock-text); }
.assignee-chip-paul { background: var(--assignee-paul-bg); color: var(--assignee-paul-text); }
.assignee-chip-unassigned {
  background: none;
  border: 1px solid var(--assignee-unassigned-border);
  color: var(--assignee-unassigned-text);
}

/* Removable tag chip `x` (ITEM-06, D-08): inherits the chip's own text
   color -- it must not read as destructive/error (06-UI-SPEC Color), since
   removing a tag is a routine, instantly-reversible edit. */
.tag-remove-btn {
  background: none;
  border: none;
  color: inherit;
  margin-left: var(--space-xs);
  cursor: pointer;
  padding: 0;
  font: inherit;
  line-height: 1;
}

/* --- Expanded card / description edit state --- */

.card-expanded-panel {
  margin-top: var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Read-only expanded body (Phase 21 Plan 08, D-22, ACCESS-02, UI-SPEC
   Screen 7): the same flex/gap layout as the editable panel above (this
   class is added ALONGSIDE .card-expanded-panel, never in place of it) --
   title/description as plain text, tag chips, an assignee chip, status/
   priority badges and the project name, no inputs, no buttons. Card
   titles wrap and truncate exactly as for members (E7 long-text) -- this
   is a control-visibility change only, never a layout change. */
.card-readonly-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.card-readonly-title {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  color: var(--color-text-body);
  word-break: break-word;
}

.card-readonly-project {
  font-size: var(--font-size-label);
  color: var(--text-muted);
}

.card-readonly-badges {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex-wrap: wrap;
}

.card-readonly-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.card-readonly-description {
  font-size: var(--font-size-body);
  color: var(--color-text-body);
  white-space: pre-wrap;
  word-break: break-word;
}

/* Title row (ITEM-07, D-10/D-11/D-12) -- sits at the very top of the
   expanded panel, above Assignee/Tags/Description. */
.card-title-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.card-title-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

/* Mirrors .create-item-input's tokens verbatim (06-UI-SPEC "Title row"). */
.card-title-input {
  width: 100%;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Disabled gate -- now carried by the expanded card's single "Save"
   (disabled while there is nothing unsaved, and while the title is empty),
   and available to any other .btn-save-description instance that grows a
   disabled state; no new hex, existing accent token dimmed via opacity
   only. */
.btn-save-description:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Assignee row (ITEM-05, D-04) -- the segmented control reuses
   .filter-toggle-group/.filter-toggle/.filter-toggle.active verbatim
   (06-UI-SPEC Color section); this is the only new class this control
   needs. */
.card-assignee-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

/* Tags row (ITEM-06, D-07/D-08) -- .card-tags-applied holds the removable
   applied-tag chips; .card-tags-add (populated by Task 2) will hold the
   quick-pick + free-text add controls. Both sub-rows share the parent's
   column gap. */
.card-tags-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.card-tags-applied {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

/* Tag quick-pick + free-text add controls (ITEM-06, D-07/D-09). */
.card-tags-add {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: center;
}

/* Quick-pick "+ tag" chip -- outline treatment mirroring
   .assignee-chip-unassigned (06-UI-SPEC Color: NOT accent, subordinate to
   the applied tag chips' hash-palette color). */
.tag-quickpick-chip {
  background: none;
  border: 1px solid var(--border-default);
  color: var(--text-muted);
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  padding: var(--space-xs) var(--space-sm);
  border-radius: 999px;
  cursor: pointer;
}

.tag-quickpick-chip:hover {
  border-color: var(--border-hover);
}

/* Free-text tag input -- mirrors .create-item-input's tokens but stays
   inline/narrower (not full-width) since it sits beside quick-pick chips
   and the Add button. */
.tag-add-input {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  min-height: var(--drag-target-min-height);
  width: auto;
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Free-text tag "Add" button -- deliberately neutral (mirrors
   .btn-discard-changes), NOT accent (06-UI-SPEC Color: accent is never
   used for the free-text tag Add button). */
.tag-add-btn {
  color: var(--text-muted);
  background: none;
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

.card-expanded-actions {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.card-description {
  width: 100%;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  line-height: var(--line-height-body);
  border: 1px solid var(--border-default);
  padding: var(--space-sm);
  resize: vertical;
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.btn-save-description {
  color: var(--color-on-accent);
  background: var(--color-accent);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

.btn-discard-changes {
  color: var(--text-muted);
  background: none;
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

.btn-archive-item,
.btn-archive-confirm {
  color: var(--color-destructive);
  background: none;
  border: 1px solid var(--color-destructive);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

/* Convert confirm dialog's confirm button (IN-01): swapped in by
   openConvertDialog in place of the shared template's default
   .btn-archive-confirm -- convert preserves all content and is
   deliberately NOT destructive-colored (same rationale as
   .btn-convert-item below), so this copies .btn-restore-item's
   filled-accent treatment instead of the red archive/destructive one. */
.btn-convert-confirm {
  color: var(--color-on-accent);
  background: var(--color-accent);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

/* Restore button (ITEM-08, D-11): literal copy of .btn-save-description --
   filled accent, white text. Restore is explicitly NOT destructive-colored
   (UI-SPEC Color) -- never the --color-destructive red token. Pushed to
   the end of .card-bottom-row's flex layout (right-aligned) via
   margin-left: auto, same trick used elsewhere in this row. */
.btn-restore-item {
  color: var(--color-on-accent);
  background: var(--color-accent);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
  margin-left: auto;
}

.btn-restore-item:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Interim status control (D-01): a permanent Status <select> in the
   expanded card's actions row, changing status through the existing
   performMove/moveAction path. .card-status-select copies
   .filter-assignee-select's token set verbatim, same as .card-convert-select
   above it. */
.card-status-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

.card-status-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.card-status-select {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.card-status-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Priority edit control (SPRINT-12): a Priority <select> in the expanded
   card's actions row, appended in BOTH board and sprint context (D-04).
   .card-priority-select copies .card-status-select's token set verbatim
   (the first-ever UI to SET priority -- Phase 15 was drag-only). */
.card-priority-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

.card-priority-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.card-priority-select {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.card-priority-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Blocked edit control (SPRINT-12): genuinely new UI -- no checkbox
   markup existed anywhere in this stylesheet before this plan. Styled
   consistently with the row/label pattern of .card-status-row/
   .card-status-label; dark-mode-aware tokens only, no hardcoded hex.
   Appended in BOTH board and sprint context (D-04) -- the first-ever UI
   to SET blocked (Phase 15 was render-only). */
.card-blocked-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

.card-blocked-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.card-blocked-toggle {
  width: 18px;
  height: 18px;
  accent-color: var(--color-destructive);
  cursor: pointer;
}

/* Urgent edit control: a verbatim structural clone of the Blocked row/label/
   toggle above -- same layout, amber accent-color instead of destructive. */
.card-urgent-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

.card-urgent-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.card-urgent-toggle {
  width: 18px;
  height: 18px;
  accent-color: var(--color-urgent);
  cursor: pointer;
}

.card-assignee-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
  flex-shrink: 0;
}

.card-assignee-select {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
  /* Phase 21 Plan 08 (D-26, E6 long-text): a long active-user display name
     option must never widen the card editor beyond its container -- native
     <select> scrolling handles overflow, no custom container. Phase 21
     Plan 11 (G-21-6): a flex item's automatic minimum size is the width of
     its widest option, and a minimum always beats a conflicting maximum,
     so the zero minimum below is what actually makes the cap above hold. */
  max-width: 100%;
  min-width: 0;
}

.card-assignee-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* The Assign-to-me button keeps its natural one-line width; the select
   above absorbs all of the row's shrink instead (G-21-6). */
.card-assignee-row > .btn-save-description {
  flex-shrink: 0;
}

/* Project picker (projects feature) -- same row/token set as
   .card-assignee-row so the two read as one block of item metadata. */
.card-project-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}

.card-project-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  color: var(--text-muted);
  flex-shrink: 0;
}

.card-project-select {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
  /* Same guard as the assignee picker above (G-21-6): project names are
     user-supplied and unbounded, so this flex item needs the identical
     zero minimum for its width cap to hold. */
  max-width: 100%;
  min-width: 0;
}

.card-project-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Convert control (CONVERT-01): target-type <select> + Convert button in
   the expanded card's actions row. .card-convert-select copies
   .filter-assignee-select's token set verbatim (below). .btn-convert-item
   is NOT destructive-colored (unlike .btn-archive-item) -- convert
   preserves all content and is additive-adjacent, so it copies
   .btn-restore-item's filled-accent treatment instead. */
.card-convert-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

.card-convert-select {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.card-convert-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* FIX 4: small, muted label making it explicit the picker below chooses
   the item's TYPE (not e.g. a status/section). */
.create-type-label {
  display: block;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
  margin-bottom: var(--space-xs);
}

/* OMF-ALL-ADD: bucket picker in the All-tab "+ Add item" form, modeled on
   .card-convert-select (token colors only, dark-mode-aware via tokens).
   D-01: shares the same treatment with the priority dropdown that now
   appears in every column's "+ Add item" form -- one rule, no duplicated
   block. */
.create-item-bucket-select,
.create-item-priority-select,
.create-item-project-select {
  display: block;
  width: 100%;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  margin-bottom: var(--space-xs);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.create-item-bucket-select option,
.create-item-priority-select option,
.create-item-project-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.btn-convert-item {
  color: var(--color-on-accent);
  background: var(--color-accent);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

/* Disabled-until-different-type gate (260710-aec) -- mirrors
   .btn-save-description:disabled's opacity-only dim; the base rule's
   var(--color-accent)/var(--color-on-accent) tokens already flip per
   light/dark/gatinapp warm-orange dark, so no new hex/tokens needed. */
.btn-convert-item:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* "Move to sprint" control (SPRINT-11, 16-04): sprint-context-only,
   sitting where the board's sprint-membership control used to be.
   .card-move-to-sprint-select copies .card-convert-select's token set
   verbatim; the "Move to sprint" button reuses .btn-convert-item
   unmodified (same non-destructive, additive-adjacent treatment). */
.card-move-to-sprint-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

.card-move-to-sprint-select {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.card-move-to-sprint-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.card-move-to-sprint-empty {
  font-size: var(--font-size-body);
  color: var(--text-muted);
}

/* "Remove from sprint" control (regression guard, 16-04): sprint-context-
   only, a single button detaching the ticket from the CURRENT sprint
   only (the ticket stays in its bucket). Reuses .btn-archive-item
   unmodified -- the same destructive-adjacent (--color-destructive)
   token pair buildMissingRefCard's own "Remove from sprint" button
   already uses. */
.card-remove-from-sprint-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

/* --- Sprint membership block (SPRINT-08) -- "In sprints" chips + add
   picker on the expanded board card. .card-expanded-actions is a
   flex-wrap row of compact buttons/the Convert control; this block is
   qualitatively wider (a label + a wrapping chips row + a picker row), so
   it gets width: 100% to always occupy its own full line rather than
   crowding the Save/Discard/Archive/Convert buttons (13-RESEARCH
   Pitfall 6). The add picker's <select>/button reuse .card-convert-select
   and .btn-convert-item verbatim (see buildSprintMembershipControl). */
.card-sprint-membership {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.card-sprint-membership-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.card-sprint-membership-loading,
.card-sprint-membership-empty {
  font-size: var(--font-size-body);
  color: var(--text-muted);
}

.card-sprint-membership-error {
  font-size: var(--font-size-body);
  color: var(--color-destructive);
}

.card-sprint-membership-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.card-sprint-add-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  align-items: stretch;
}

.card-sprint-add-select {
  width: 100%;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.card-sprint-add-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Neutral, non-tag-hash chip variant (13-RESEARCH Pitfall 7): mirrors
   .assignee-chip-unassigned's border-only, no-fill treatment so "this
   ticket is in Sprint N" reads as a distinct semantic category from the
   six hash-colored .tag-chip-N tag chips, using the accent token (not the
   assignee-unassigned slate) so it doesn't visually conflate with the
   assignee chip either. */
.sprint-membership-chip {
  background: none;
  border: 1px solid var(--color-accent);
  color: var(--color-accent);
}

/* --- Inline "+ Add item" create form --- */

.create-item-form {
  border: 1px dashed var(--border-hover);
  border-radius: 4px;
  padding: var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.create-item-input {
  width: 100%;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.create-item-actions {
  display: flex;
  gap: var(--space-sm);
}

/* --- Custom confirm dialog (archive, T-03-08) --- */

.dialog-overlay {
  position: fixed;
  inset: 0;
  background: var(--overlay-scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.dialog-box {
  background: var(--color-secondary-card);
  border-radius: 4px;
  padding: var(--space-lg);
  max-width: 400px;
  width: 90%;
  box-shadow: var(--shadow-dialog);
}

.dialog-title {
  margin: 0 0 var(--space-sm);
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
}

.dialog-body {
  margin: 0 0 var(--space-md);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  line-height: var(--line-height-body);
}

.dialog-actions {
  display: flex;
  gap: var(--space-sm);
  justify-content: flex-end;
}

/* --- Empty states (Copywriting Contract) --- */

.column-empty-state {
  padding: var(--space-lg);
  text-align: center;
  color: var(--text-muted);
}

.column-empty-heading {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  margin: 0 0 var(--space-xs);
}

.column-empty-body {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  line-height: var(--line-height-body);
  margin: 0;
}

.clear-filters-link {
  color: var(--color-accent);
  cursor: pointer;
  background: none;
  border: none;
  font-size: var(--font-size-body);
  text-decoration: underline;
}

/* --- Filter bar controls (BOARD-05) --- */

/* Search field in the same recessed-track look as the segmented controls
   and the Tags button: a borderless 32px track, with 6px vertical margins
   so its footprint stays 44px and it lines up in the top-aligned bar.
   While focused it lifts to the raised-chip surface with an accent ring,
   so it's obvious where you're typing. */
.filter-search-input {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: none;
  border-radius: 8px;
  padding: 0 12px;
  height: 32px;
  margin: 6px 0;
  background: var(--toggle-track);
  color: var(--color-text-body);
  min-width: 200px;
}

.filter-search-input::placeholder {
  color: var(--text-muted);
  opacity: 1;
}

.filter-search-input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 0;
  background: var(--toggle-chip);
  box-shadow: var(--shadow-toggle-chip);
}

.filter-tag-chip:focus,
.card-convert-select:focus,
.card-status-select:focus,
.card-assignee-select:focus,
.card-priority-select:focus,
.card-blocked-toggle:focus,
.card-urgent-toggle:focus,
.card-move-to-sprint-select:focus,
.btn-convert-item:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* BUG-048: the chips live inside this wrapper now, behind the "Tags"
   toggle. Column layout so the revealed row drops BELOW the button rather
   than shoving the rest of the filter bar sideways. */
.filter-tag-wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-sm);
}

.filter-tag-head {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* The toggle reads as a filter-bar control, not a chip: the same recessed
   track as the segmented controls (a one-segment track, painted by an inset
   ::before so the 44px hit area stays), with a caret that flips while the
   chips are open. Once a tag filter is on, the label switches to the accent
   colour -- the whole point of showing the count while the chips are
   hidden. */
.filter-tag-toggle {
  position: relative;
  isolation: isolate;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: 500;
  line-height: var(--line-height-body);
  padding: var(--space-xs) 28px var(--space-xs) 12px;
  min-height: var(--drag-target-min-height);
  border: none;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}

.filter-tag-toggle::before {
  content: '';
  position: absolute;
  inset: 6px 0;
  z-index: -1;
  background: var(--toggle-track);
  border-radius: 8px;
}

.filter-tag-toggle::after {
  content: '';
  position: absolute;
  right: 12px;
  top: 50%;
  width: 5px;
  height: 5px;
  margin-top: -4px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
}

.filter-tag-toggle[aria-expanded="true"]::after {
  margin-top: -1px;
  transform: rotate(-135deg);
}

.filter-tag-toggle:hover,
.filter-tag-toggle[aria-expanded="true"] {
  color: var(--color-text-body);
}

.filter-tag-toggle.active {
  color: var(--color-accent);
  font-weight: 600;
}

.filter-tag-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: 8px;
}

.filter-tag-clear:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* Shown only while a tag filter is active AND the chips are collapsed --
   the one case where the filter cannot otherwise be cleared without
   reopening the whole wall. */
.filter-tag-clear {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  padding: var(--space-xs) var(--space-sm);
  border: none;
  background: none;
  color: var(--color-text-body);
  opacity: 0.7;
  text-decoration: underline;
  cursor: pointer;
}

.filter-tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: center;
}

.filter-tag-chip {
  border: none;
  cursor: pointer;
}

/* Selected-chip state uses the reserved accent color (UI-SPEC Color:
   "the active/selected state of a tag or assignee filter chip"). */
.filter-tag-chip.active {
  box-shadow: 0 0 0 2px var(--color-accent);
}

/* BUG-010: shared Show ID / Show priority filter-bar checkbox -- no
   heavy native-checkbox restyle, just alignment/spacing/tone to match
   the other filter-bar controls. */
.filter-checkbox {
  display: inline-flex;
  align-items: center;
  min-height: var(--drag-target-min-height); /* lines up with the 44px controls in a top-aligned bar */
  gap: 4px;
  font-size: var(--font-size-body);
  color: var(--text-muted);
  cursor: pointer;
}

/* Board-wide "No items match your filters." state -- spans the full
   four-column grid row rather than occupying a single grid cell. */
.filtered-empty-state {
  grid-column: 1 / -1;
  text-align: center;
  padding: var(--space-xl) var(--space-lg);
  color: var(--text-muted);
}

/* --- Archived overlay entry point (title bar, BOARD-07) -------------
   Left-aligned immediately after the Board|Sprints view toggle, with a
   space-md gap -- reads as part of the left-side nav group rather than
   sitting at the trailing edge. The theme segmented control is now
   left-aligned right after this button too (space-md gap), so the whole
   header is left-packed with no trailing auto-margin. A quiet text button
   (no border; muted, hover wash) -- a utility navigation action, not a
   primary CTA, and it must not read as another segmented control. */

.archived-btn {
  margin-left: var(--space-md);
  background: none;
  border: none;
  color: var(--text-muted);
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  font-family: var(--font-family);
  font-weight: 500;
  cursor: pointer;
  min-height: var(--drag-target-min-height);
  /* Hover wash inset to the toggles' ~32px visual height (same ::before
     trick as the segmented controls), keeping the 44px hit area. */
  position: relative;
  isolation: isolate;
}

.archived-btn:hover {
  color: var(--color-text-body);
}

.archived-btn:hover::before {
  content: '';
  position: absolute;
  inset: 6px 0;
  z-index: -1;
  background: var(--toggle-track);
  border-radius: 8px;
}

.archived-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: 8px;
}

/* .help-link (Phase 23 Plan 06, D-21): the header "Aide/Help" entry is an
   <a>, not a <button> -- it needs the anchor-specific properties buttons
   get for free (no underline, vertically centered icon-less label) on top
   of the .archived-btn treatment it already carries via its second class. */
.help-link {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

/* --- Archived overlay (BOARD-07, D-05/D-06/D-07/D-08) ----------------
   Reuses .dialog-overlay as-is for the scrim; .archived-overlay-box is a
   WIDER sibling of .dialog-box (720px cap vs. 400px) with its own
   header row and a scrollable body -- the overlay chrome itself does not
   scroll, only .archived-overlay-body does. */

.archived-overlay-box {
  background: var(--color-secondary-card);
  border-radius: 4px;
  max-width: 720px;
  width: 90vw;
  max-height: 80vh;
  box-shadow: var(--shadow-dialog);
  display: flex;
  flex-direction: column;
}

.archived-overlay-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-lg);
  border-bottom: 1px solid var(--border-default);
}

.archived-overlay-title {
  margin: 0;
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
}

.archived-overlay-close-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: var(--font-size-heading);
  line-height: 1;
  cursor: pointer;
  padding: 0;
}

.archived-overlay-close-btn:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.archived-overlay-body {
  overflow-y: auto;
  padding: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.archived-loading-state {
  text-align: center;
  color: var(--text-muted);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  margin: 0;
}

.archived-empty-state {
  text-align: center;
  color: var(--text-muted);
  padding: var(--space-lg) 0;
}

.archived-empty-heading {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  margin: 0 0 var(--space-xs);
}

.archived-empty-body {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  line-height: var(--line-height-body);
  margin: 0;
}

.archived-error-banner {
  border: 1px solid var(--color-destructive);
  color: var(--color-destructive);
  background: var(--color-destructive-subtle-bg);
  padding: var(--space-sm) var(--space-md);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.archived-error-banner-text {
  flex: 1;
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}

.archived-error-banner-retry {
  color: var(--color-on-accent);
  background: var(--color-destructive);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

/* Restore failure banner (ITEM-08) -- copies .archived-error-banner
   verbatim; inserted at the top of the overlay body (not via clearBody(),
   which would also wipe the grouped sections) so it coexists with the
   still-visible card list per UI-SPEC "the archived card stays in the
   overlay". */
.archived-restore-error-banner {
  border: 1px solid var(--color-destructive);
  color: var(--color-destructive);
  background: var(--color-destructive-subtle-bg);
  padding: var(--space-sm) var(--space-md);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.archived-restore-error-banner-text {
  flex: 1;
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}

.archived-restore-error-banner-retry {
  color: var(--color-on-accent);
  background: var(--color-destructive);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

/* One section per origin bucket that has >=1 archived item (D-07),
   in BUCKET_ORDER. Section header bar reuses --color-secondary-strip
   as a subtle divider background (UI-SPEC Color). */
.archived-section-header {
  background: var(--color-secondary-strip);
  padding: var(--space-sm) var(--space-md);
}

.archived-section-title {
  margin: 0;
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
}

.archived-card-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding-top: var(--space-sm);
}

/* Archived card (read-only, D-08): reuses .card's exact anatomy/padding;
   no drag-handle, no hover-drag affordance beyond the same border-darken
   .card:hover already provides. */
.archived-card {
  cursor: default;
}

/* --- Manage-projects overlay (projects-mgmt Task 5) -------------------
   Manage-projects rows: name field + actions, one per project. Reuses the
   archived-overlay box (.archived-overlay-box/-header/-body above), so
   only the row itself needs rules. */
.manage-project-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) 0;
}

.manage-project-name {
  flex: 1;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.manage-project-archived .manage-project-name {
  opacity: 0.55;
  font-style: italic;
}

/* Add-row input (Task 5 fix round 1): its own class, distinct from
   .manage-project-name, so paint()'s draft/focus snapshot-by-class can tell
   the add row apart from the per-project rename rows -- same look. */
.manage-project-add-name {
  flex: 1;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Optional custom-prefix field beside the new-category name: same field
   look as the name input, but fixed-width (a prefix is at most 6 chars) and
   monospaced-ish via letter-spacing so SOFT vs SOFT2 reads clearly. */
.manage-type-add-prefix {
  width: 120px;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  letter-spacing: 0.04em;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Manage-project write-failure banner (Task 5 fix round 1) -- copies
   .archived-restore-error-banner verbatim; the page-level #error-banner
   renders under this overlay's scrim, so it needs its own in-overlay
   banner (see showManageError in app.js). */
.manage-project-error-banner {
  border: 1px solid var(--color-destructive);
  color: var(--color-destructive);
  background: var(--color-destructive-subtle-bg);
  padding: var(--space-sm) var(--space-md);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.manage-project-error-banner-text {
  flex: 1;
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}

.manage-project-error-banner-retry {
  color: var(--color-on-accent);
  background: var(--color-destructive);
  border: none;
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  cursor: pointer;
}

/* --- Types view (Task 11) ----------------------------------------------
   Everything else the Types view needs already exists and is reused
   as-is: .manage-project-row/-name/-archived/-add-name/-error-banner*
   above for the rows/create-form/error banner, .sprints-view/-header/
   -title/-loading and .sprints-list further below for the view shell and
   row list, .btn-save-description/.btn-archive-item for the buttons, and
   .card-type-badge for the permanent-prefix chip. Only the create form's
   live prefix/sample-id hint (previewBucketPrefix in app.js) has nothing
   to reuse. */
.create-type-hint {
  margin: 0 0 var(--space-md);
  color: var(--text-muted);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}

/* --- Segmented controls: Board|Sprints|Projects view toggle (05-CONTEXT
   D-01), All|My items filter toggle (board + sprint filter bars), and the
   System|Light|Dark theme toggle (09-UI-SPEC). One shared look: a recessed
   track with a raised chip marking the active option -- replaces the old
   bordered box with a solid-accent active fill.
   The track and chip are painted by ::before pseudo-elements INSET from
   the real boxes, so the visible control is ~32px tall while every button
   keeps its full --drag-target-min-height hit area. isolation: isolate on
   the group scopes the pseudo-elements' z-index:-1 to it: the track
   (group::before) paints first, the chip (button::before) above it, the
   label text above both. Active state: .active (view/filter) or
   [aria-pressed="true"] (theme). --- */

.view-toggle-group,
.filter-toggle-group,
.theme-toggle-group,
.lang-toggle-group {
  position: relative;
  isolation: isolate;
  display: flex;
  padding: 0 3px;
}

.view-toggle-group::before,
.filter-toggle-group::before,
.theme-toggle-group::before,
.lang-toggle-group::before {
  content: '';
  position: absolute;
  inset: 6px 0;
  z-index: -1;
  background: var(--toggle-track);
  border-radius: 8px;
}

.view-toggle-group {
  margin-left: var(--space-lg);
}

.theme-toggle-group {
  /* Deliberately wide (not the usual --space-md gap): keeps this segmented
     control visually separated from the Board|Sprints view toggle so it
     doesn't read as a second view toggle. Still left-packed, not auto. */
  margin-left: calc(var(--space-xl) * 2);
}

/* Phase 20 (D-01): tokens only, no new colors -- same treatment as
   .view-toggle-group's own margin-left above, just a normal gap since the
   language toggle sits right next to the theme toggle it belongs with. */
.lang-toggle-group {
  margin-left: var(--space-md);
}

.view-toggle,
.filter-toggle,
.theme-toggle,
.lang-toggle {
  position: relative;
  background: none;
  color: var(--text-muted);
  border: none;
  padding: var(--space-xs) 12px;
  font-size: var(--font-size-body);
  font-family: var(--font-family);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  min-height: var(--drag-target-min-height);
}

.view-toggle:hover,
.filter-toggle:hover,
.theme-toggle:hover,
.lang-toggle:hover {
  color: var(--color-text-body);
}

.view-toggle.active,
.filter-toggle.active,
.theme-toggle[aria-pressed="true"],
.lang-toggle[aria-pressed="true"] {
  color: var(--color-text-body);
}

.view-toggle.active::before,
.filter-toggle.active::before,
.theme-toggle[aria-pressed="true"]::before,
.lang-toggle[aria-pressed="true"]::before {
  content: '';
  position: absolute;
  inset: 9px 0;
  z-index: -1;
  background: var(--toggle-chip);
  border-radius: 6px;
  box-shadow: var(--shadow-toggle-chip);
}

/* Person segment of the assignee filter (All | My items | Person): a
   <select> can't host the ::before chip, so it sits inside a
   span.filter-toggle that does. The select itself is chrome-less and
   fills the span, keeping the full 44px hit area; the caret is the span's
   ::after since appearance: none drops the native arrow. */
.filter-toggle-person {
  display: inline-flex;
  align-items: center;
  padding: 0;
}

.filter-toggle-person::after {
  content: '';
  position: absolute;
  right: 11px;
  top: 50%;
  width: 5px;
  height: 5px;
  margin-top: -4px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  pointer-events: none;
}

.filter-person-select {
  appearance: none;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  padding: var(--space-xs) 26px var(--space-xs) 12px;
  min-height: var(--drag-target-min-height);
  cursor: pointer;
}

.filter-person-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.filter-person-select:focus {
  outline: none;
}

.filter-person-select:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: 8px;
}

.view-toggle:focus-visible,
.filter-toggle:focus-visible,
.theme-toggle:focus-visible,
.lang-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: 8px;
}

/* Card expand control ("Details" pill): mirrors .theme-toggle's token-only
   pattern verbatim. Active/expanded state is keyed off [aria-expanded="true"],
   not a class. Every declaration below is a var() -- zero dark-specific rules
   needed; it inherits the dark flip automatically (D-08). */
.view-description-toggle {
  align-self: flex-end;
  display: inline-flex;
  align-items: center;
  border-radius: 4px;
  border: 1px solid var(--border-default);
  background: var(--color-secondary-strip);
  color: var(--color-text-body);
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--drag-target-min-height);
  cursor: pointer;
}

.view-description-toggle[aria-expanded="true"] {
  border-color: var(--border-hover);
  background: var(--color-accent);
  color: var(--color-on-accent);
}

.view-description-toggle:hover {
  border-color: var(--border-hover);
}

.view-description-toggle:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* --- Sprints top-level view (05-sprints, SPRINT-01: create + list) --- */

.sprints-view {
  padding: 0 var(--space-lg) var(--space-lg);
}

.sprints-view[hidden] {
  display: none;
}

.sprints-view-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-lg) 0;
}

.sprints-view-title {
  margin: 0;
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-display);
  line-height: var(--line-height-display);
}

.sprints-view-error {
  color: var(--color-destructive);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}

.sprints-view-loading {
  color: var(--text-muted);
  font-size: var(--font-size-body);
}

.sprints-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.sprints-list-item {
  background: var(--color-secondary-card);
  border: 1px solid var(--border-default);
  padding: 0;
}

.sprints-list-label {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
}

/* Clickable "Sprint NNN" list row (opens the detail view, 05-02) --
   same typography as .sprints-list-label but as a full-width <button> so
   the whole row is a click target with a visible focus ring. */
.sprints-list-open-btn {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--font-family);
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
  color: inherit;
  padding: var(--card-padding);
  min-height: var(--drag-target-min-height);
}

.sprints-list-open-btn:hover {
  background: var(--color-dominant);
}

.sprints-list-open-btn:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* --- Sprint detail view (05-02: open/add-by-ID/remove/resolved cards,
   SPRINT-02/03/04) --- */

/* Inline-editable sprint-name field (11-name-a-sprint, SPRINT-06) --
   identical row/input token set to .sprint-add-item-form/-input below; the
   Save button reuses .btn-save-description as-is (no new button class). */
.sprint-name-row {
  display: flex;
  gap: var(--space-sm);
  padding: var(--space-md) 0;
}

/* Right-align the Delete-sprint button within the name row (flex idiom)
   so it sits near the name field instead of the far edge of the
   .sprints-view-header (12-delete-a-sprint, SPRINT-07). */
.sprint-name-row .btn-archive-item {
  margin-left: auto;
}

.sprint-name-input {
  flex: 1;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

.sprint-add-item-form {
  display: flex;
  gap: var(--space-sm);
  padding: var(--space-md) 0;
}

.sprint-add-item-input {
  flex: 1;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Create-a-new-item-in-this-sprint row (FEAT-028) -- the companion to
   .sprint-add-item-form above. Same row/token set, so the two read as one
   control block: add an existing item by ID, or create a new one.
   .create-type-label and .create-item-bucket-select are reused from the
   board's composer rather than cloned, but both are declared display:block
   / width:100% up there for that form's VERTICAL column layout, so they are
   overridden back to inline sizing here for this horizontal row. */
.sprint-create-item-form {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
  padding: var(--space-md) 0;
  border-top: 1px solid var(--border-default);
}

.sprint-create-item-hint {
  flex-basis: 100%;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.sprint-create-item-form .create-type-label {
  display: inline;
  margin-bottom: 0;
}

.sprint-create-item-form .create-item-bucket-select,
.sprint-create-item-form .create-item-priority-select,
.sprint-create-item-form .create-item-project-select {
  display: inline-block;
  width: auto;
  margin-bottom: 0;
}

.sprint-create-item-input {
  flex: 1;
  min-width: 12rem;
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  min-height: var(--drag-target-min-height);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* Left-aligned filter bar above the sprint status columns (quick task
   260708-x3d) -- moves the All/My-items toggle out of the header
   (which was far-right, inconsistent with the board's left-aligned
   filter controls) into its own row, matching the board's placement. */
.sprint-filter-bar {
  display: flex;
  align-items: flex-start; /* see .filter-bar: open Tags must not drag the row */
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}

/* Sprint category tab bar (All + 6 buckets, quick task 260710-cxv) -- the
   sprint view's own tab strip, rendered above .sprint-filter-bar. Reuses
   .tab/.tab.active verbatim; deliberately NOT .tab-bar, whose min-width is
   board-only (--board-content-width) and would force horizontal scroll. */
.sprint-tab-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}

/* An archived project's tab, shown only when "Show archived" is on. Dimmed
   rather than hidden so it reads as present-but-retired; same treatment the
   board gives a Done card. */
.tab-archived {
  opacity: 0.55;
  font-style: italic;
}

/* Phase 23 Plan 04 (UX-06/07/08, D-22..D-27) -- the nav-layout toggle shared
   by the Projects view and Sprints-view category strips, and the two
   bracket-mode label pieces (.tab-name/.tab-count) that replace the old
   single templated tab-label text node. No new custom property: every value
   below is an EXISTING token. */

/* .tab-name holds the bracketed "[Name]" text; .tab-count is the SEPARATE
   muted open-work count (D-26) -- e.g. "[Website]" + " 4", never one string. */
.tab-count {
  color: var(--text-muted);
  margin-left: var(--space-xs);
}

/* The toggle button itself: same quiet, icon-only, ~32px-visual/44px-hit-area
   treatment as .archived-btn, with an aria-pressed="true" chip copied from
   the segmented controls' own [aria-pressed="true"]::before treatment
   (.theme-toggle/.lang-toggle above) since a plain icon button has no
   .active/[aria-pressed] chip of its own yet. */
.nav-layout-toggle {
  position: relative;
  isolation: isolate;
  background: none;
  border: none;
  color: var(--text-muted);
  padding: var(--space-xs) var(--space-sm);
  cursor: pointer;
  min-height: var(--drag-target-min-height);
  display: inline-flex;
  align-items: center;
}

.nav-layout-toggle svg {
  width: 16px;
  height: 16px;
}

.nav-layout-toggle:hover {
  color: var(--color-text-body);
}

.nav-layout-toggle:hover::before {
  content: '';
  position: absolute;
  inset: 6px 0;
  z-index: -1;
  background: var(--toggle-track);
  border-radius: 8px;
}

.nav-layout-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: 8px;
}

.nav-layout-toggle[aria-pressed="true"] {
  color: var(--color-text-body);
}

.nav-layout-toggle[aria-pressed="true"]::before {
  content: '';
  position: absolute;
  inset: 6px 0;
  z-index: -1;
  background: var(--toggle-chip);
  border-radius: 8px;
  box-shadow: var(--shadow-toggle-chip);
}

/* Dropdown mode's own row: the <select> plus the SAME toggle, in the exact
   visual slot the tab strip occupied. */
.nav-layout-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}

.nav-layout-select {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-xs) var(--space-sm);
  background: var(--color-secondary-card);
  color: var(--color-text-body);
  min-height: var(--drag-target-min-height);
  cursor: pointer;
}

.nav-layout-select option {
  background: var(--color-secondary-card);
  color: var(--color-text-body);
}

/* --- Sprint status columns (Phase 16, SPRINT-09/10) ---------------------
   Supersedes .sprint-detail-list's flat column (D-01): a 4-column grid
   analog of .column-area, reusing .column/.column-header/.column-count-badge
   verbatim for the column bodies (same --column-min-width token). Cards
   inside render via the shared buildCard (blocked-red + priority badge
   parity come for free, D-05) -- no new card-level CSS needed here. */
.sprint-status-columns {
  display: grid;
  grid-template-columns: repeat(4, minmax(var(--column-min-width), 1fr));
  gap: var(--space-md);
}

/* Unresolved references strip (D-01a) -- archived/deleted referenced ids
   have no status, so they sit below the four columns, not inside one. */
.sprint-unresolved-refs {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

.sprint-unresolved-refs-heading {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  color: var(--text-muted);
}

/* .sprint-ref-missing reuses the board's .card anatomy as-is (id badge,
   body text) for a referenced id that no longer resolves (D-06). */
.sprint-ref-missing {
  border-style: dashed;
  color: var(--text-muted);
}

.sprint-ref-missing .card-body-text {
  color: var(--text-muted);
  font-style: italic;
}

/* --- Sprint progress (08-sprint-progress, SPRINT-05) --------------------
   Shared by the Sprints list row and the sprint detail view (same DOM,
   built once by buildSprintProgressNode). Token-only, mirroring
   .theme-toggle's discipline: every color/spacing/type value below is a
   var(--...) token, so this inherits the Phase 09 dark flip with zero
   dark-specific rules. */
.sprint-progress {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.sprint-progress-bar {
  width: 80px;
  height: 6px;
  flex-shrink: 0;
  background: var(--color-secondary-strip);
  border: 1px solid var(--border-default);
  border-radius: 3px;
  overflow: hidden;
}

.sprint-progress-bar-fill {
  height: 100%;
  background: var(--color-accent);
}

.sprint-progress-text {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  color: var(--text-muted);
}

/* .sprints-list-item has padding: 0 (its .sprints-list-open-btn child
   carries the inset) -- so the appended progress node needs its own
   matching horizontal + bottom padding to avoid sitting flush against
   the row edge. The detail view already renders inside padded content,
   so no equivalent rule is needed there. */
.sprints-list-item .sprint-progress {
  padding: 0 var(--space-md) var(--space-sm);
}

/* --- Login page + header user menu (Phase 21 Plan 01, D-09/D-23) -------
   .auth-page/.auth-card share the .dialog-box material (surface color,
   its 4px corner radius, shadow) rather than a new surface treatment;
   .auth-error reuses the .error-banner palette. */

.auth-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-dominant);
  font-family: var(--font-family);
  color: var(--color-text-body);
}

.auth-card {
  background: var(--color-secondary-card);
  border-radius: 4px;
  box-shadow: var(--shadow-dialog);
  padding: var(--space-lg);
  max-width: 360px;
  width: 90%;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-md);
}

.auth-logo {
  text-align: center;
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-display);
  line-height: var(--line-height-display);
  margin: 0;
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.auth-field-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.auth-field-input {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  color: var(--color-text-body);
  border: 1px solid var(--border-default);
  background: var(--color-secondary-card);
  padding: var(--space-sm) var(--space-md);
  min-height: var(--drag-target-min-height);
  /* A long username/password must scroll within the single-line input
     rather than growing the card -- the input's own intrinsic width
     already does this; no overflow rule needed beyond not setting a
     fixed width larger than the card. */
}

.auth-field-input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: -1px;
}

.auth-error {
  border: 1px solid var(--color-destructive);
  color: var(--color-destructive);
  background: var(--color-destructive-subtle-bg);
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-body);
  margin: 0;
}

.auth-error[hidden] {
  display: none;
}

/* .auth-notice (Phase 21 Plan 06, D-16): the SAME #auth-error element,
   styled positively for login.password_set_flash -- text color
   var(--color-done), no destructive border/background. */
.auth-error.auth-notice {
  border: none;
  background: none;
  color: var(--color-done);
  padding: 0;
}

/* .auth-field-hint (Phase 21 Plan 06): Label-role, muted hint text under
   the set-password form's new-password field (set_password.hint). */
.auth-field-hint {
  font-size: var(--font-size-label);
  color: var(--text-muted);
  margin: calc(-1 * var(--space-xs)) 0 0;
}

.auth-submit-btn {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-label);
  color: var(--color-on-accent);
  background: var(--color-accent);
  border: none;
  padding: var(--space-sm) var(--space-md);
  min-height: var(--drag-target-min-height);
  cursor: pointer;
}

.auth-submit-btn:disabled {
  opacity: 0.7;
  cursor: default;
}

/* --- Header user menu (D-23) -------------------------------------------
   .user-menu-trigger mirrors .archived-btn's quiet-text-button treatment
   (hover wash, 44px hit area) with a max-width + ellipsis so a long
   display name never shifts the header layout. */

.user-menu {
  position: relative;
  margin-left: var(--space-md);
}

.user-menu-trigger {
  background: none;
  border: none;
  color: var(--text-muted);
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-body);
  font-family: var(--font-family);
  font-weight: 500;
  cursor: pointer;
  min-height: var(--drag-target-min-height);
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  position: relative;
  isolation: isolate;
}

.user-menu-trigger:hover {
  color: var(--color-text-body);
}

.user-menu-trigger:hover::before {
  content: '';
  position: absolute;
  inset: 6px 0;
  z-index: -1;
  background: var(--toggle-track);
  border-radius: 8px;
}

.user-menu-trigger.user-menu-signin {
  color: var(--color-accent);
}

.user-menu-panel {
  position: absolute;
  right: 0;
  top: 100%;
  margin-top: var(--space-xs);
  min-width: 200px;
  background: var(--color-secondary-card);
  box-shadow: var(--shadow-dialog);
  z-index: 100;
  display: flex;
  flex-direction: column;
  padding: var(--space-xs) 0;
}

.user-menu-item {
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  color: var(--color-text-body);
  background: none;
  border: none;
  text-align: left;
  padding: var(--space-sm) var(--space-md);
  cursor: pointer;
  width: 100%;
}

.user-menu-item:hover {
  background: var(--color-secondary-strip);
}

.user-menu-divider {
  border: none;
  border-top: 1px solid var(--border-default);
  margin: var(--space-xs) 0;
}

/* --- Profile overlay (Phase 21 Plan 04, D-23/D-24) ---------------------
   Reuses .archived-overlay-box/-header/-body chrome; .profile-body stacks
   its sections (Account, Change password) with --space-xl between them,
   each field a label + control stacked with --space-xs. */
.profile-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.profile-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  align-items: flex-start;
}

.profile-section-title {
  margin: 0;
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
}

.profile-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  width: 100%;
  max-width: 360px;
}

.profile-field-label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.profile-status {
  font-size: var(--font-size-body);
  margin: 0;
}

.profile-status.profile-status-success {
  color: var(--color-done);
}

.profile-status.profile-status-error {
  color: var(--color-destructive);
}

.profile-status[hidden] {
  display: none;
}

/* --- Custom brand color (Phase 23 Plan 02 Task 2, THEME-05, D-10/D-11) --
   .instance-color-inputs: the picker + hex text input + hint/error/preview
   stack, reusing .profile-field's own column layout. .instance-swatch-row:
   the two labelled preview panels side by side, wrapping rather than
   forcing the overlay to grow horizontally at any width (UI-SPEC backstop).
   .instance-swatch: the 48x32 "Aa" sample itself, colored entirely via
   inline style (background/color set by app.js from the SAME
   DoniPalette.deriveCustomPalette the server uses). */
.instance-color-inputs {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.instance-swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  max-width: 100%;
}

.instance-swatch {
  width: 48px;
  height: 32px;
  border-radius: 4px;
  border: 1px solid var(--border-default);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-body);
}

/* Logo/favicon thumbnail (Phase 23 Plan 03, THEME-06, D-01): the
   Administration overlay's Instance section preview -- same sizing as the
   header/login-card logo, [hidden] when no asset is stored (the empty
   thumbnail slot, never a broken/missing control). */
.instance-asset-thumb {
  max-height: 32px;
  width: auto;
  object-fit: contain;
  border-radius: 4px;
  display: block;
}

.instance-asset-thumb[hidden] {
  display: none;
}

/* --- API tokens (Phase 21 Plan 05, D-28) -------------------------------
   .token-reveal-value: the one-time token itself -- monospace, selectable,
   never truncated (the whole secret must stay copyable at any width). */
.token-reveal-value {
  display: block;
  font-family: monospace;
  font-size: var(--font-size-body);
  overflow-wrap: anywhere;
  user-select: all;
  background: var(--color-secondary-strip);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-sm) var(--space-md);
  margin: 0 0 var(--space-md);
}

.profile-tokens-error {
  color: var(--color-destructive);
  font-size: var(--font-size-body);
  text-align: center;
  padding: var(--space-lg) 0;
  margin: 0;
}

.profile-tokens-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-body);
}

.profile-tokens-table th {
  text-align: left;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  color: var(--text-muted);
  padding: var(--space-xs) var(--space-sm);
  border-bottom: 1px solid var(--border-default);
}

.profile-tokens-table td {
  padding: var(--space-xs) var(--space-sm);
  border-bottom: 1px solid var(--border-default);
}

/* Long token labels are truncated with an ellipsis; the full label lives
   in the cell's own title attribute. */
.profile-tokens-label-cell {
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Admin panel overlay (Phase 21 Plan 06, D-19..D-22) ----------------
   Same overlay chrome as Profile (.archived-overlay-box/-header/-body);
   .admin-body stacks the create-user button above the table, mirroring
   .profile-body's section gap. .admin-table reuses .profile-tokens-table's
   material verbatim -- same column treatment, same border-bottom rows. */
.admin-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-body);
}

.admin-table th {
  text-align: left;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  color: var(--text-muted);
  padding: var(--space-xs) var(--space-sm);
  border-bottom: 1px solid var(--border-default);
}

.admin-table td {
  padding: var(--space-xs) var(--space-sm);
  border-bottom: 1px solid var(--border-default);
  vertical-align: top;
}

/* Disabled-user rows (D-21): reduced opacity, mirroring
   .manage-project-archived's existing convention for a deactivated row. */
.admin-table tr.admin-row-disabled {
  opacity: 0.55;
}

/* .admin-actions-cell (Plan 07): the trailing actions cell can hold up to
   four buttons (Edit, Disable/Enable, Sign out everywhere, Generate link) --
   wraps rather than overflowing a narrow viewport. */
.admin-actions-cell {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

/* Long display names: ellipsis + the full name in the cell's own title
   attribute (never clipped invisibly). */
.admin-display-name-cell {
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A long granted-project list WRAPS inside its cell -- never clipped
   (D-21, distinct from the display-name cell above). */
.admin-access-cell {
  max-width: 240px;
  overflow-wrap: anywhere;
}

/* .create-user-access-list: the create-user dialog's "All projects"
   checkbox + one checkbox per declared project. */
.create-user-access-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  max-height: 160px;
  overflow-y: auto;
}

.create-user-access-list label {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--font-size-body);
  color: var(--color-text-body);
}

/* .link-reveal: the one-time set-password link -- same monospace/
   selectable/never-truncated treatment as .token-reveal-value above,
   reused verbatim under its own name (Artifacts table, D-17). */
.link-reveal {
  display: block;
  font-family: monospace;
  font-size: var(--font-size-body);
  overflow-wrap: anywhere;
  user-select: all;
  background: var(--color-secondary-strip);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: var(--space-sm) var(--space-md);
  margin: 0 0 var(--space-md);
}

/* --- Docs pages (Phase 23 Plan 05, DOCS-02/DOCS-03, D-15/D-19/D-20/D-21) ---
   /docs/api (and, plan 06, /docs/guide) reuse .title-bar's exact material
   for their header, .dialog-box's card material for the content column,
   and .admin-table's header/row/border look for rendered Markdown tables.
   No new custom property this phase -- every value below is an existing
   token. */

.docs-body {
  background: var(--color-dominant);
}

.docs-header {
  justify-content: space-between;
}

.docs-home-link {
  text-decoration: none;
  color: inherit;
  display: inline-flex;
  align-items: center;
}

.docs-main {
  display: block;
  padding: var(--space-lg);
}

/* .docs-page-head (Phase 23 Plan 06 adds the crosslinks nav): a wrapping
   flex row so the h1 sits left and .docs-crosslinks sits on the right on
   the same line; the optional fr-note (API page only) takes the full
   width on its own line below via flex-basis: 100%. */
.docs-page-head {
  max-width: 1000px;
  margin: 0 auto var(--space-md);
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.docs-title {
  margin: 0 0 var(--space-sm);
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-display);
  line-height: var(--line-height-display);
  flex: 1 1 auto;
}

/* .docs-crosslinks (Phase 23 Plan 06, D-21): pushed to the right edge of
   .docs-page-head by the auto left margin; .docs-cross-link reuses the
   same quiet-link treatment as .help-link above (no new custom property). */
.docs-crosslinks {
  display: flex;
  gap: var(--space-sm);
  margin-left: auto;
}

.docs-cross-link {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

.docs-api-fr-note {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--font-size-body);
  flex: 1 0 100%;
}

/* Multi-language pages (plan 06): only the block matching the active
   <html lang> shows; fr is the fallback when lang is neither fr nor en
   (matching the account/instance-default resolution chain elsewhere). The
   API reference has a single, unmarked block (no data-docs-lang) and is
   unaffected by this rule -- it always shows. */
.docs-lang-block[data-docs-lang] {
  display: none;
}
html[lang="en"] .docs-lang-block[data-docs-lang="en"] {
  display: block;
}
html:not([lang="en"]) .docs-lang-block[data-docs-lang="fr"] {
  display: block;
}

/* .docs-page: a flex row that wraps -- the sidebar TOC stacks above the
   content column at a narrow viewport instead of overlapping it (backstop
   consideration, UI Considerations "Docs page at a narrow viewport"). */
.docs-page {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xl);
  max-width: 1000px;
  margin: 0 auto;
  align-items: flex-start;
}

.docs-toc {
  width: 220px;
  flex: 0 0 220px;
  position: sticky;
  top: var(--space-lg);
}

.docs-toc-heading {
  margin: 0 0 var(--space-sm);
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
}

.docs-toc ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.docs-toc li a {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  line-height: var(--line-height-label);
  color: var(--text-muted);
  text-decoration: none;
}

.docs-toc li a:hover,
.docs-toc li a:focus-visible {
  color: var(--color-accent);
}

.docs-toc-h3 {
  padding-left: var(--space-md);
}

.docs-content {
  flex: 1 1 480px;
  max-width: 760px;
  padding: var(--space-lg);
  background: var(--color-secondary-card);
  border-radius: 4px;
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-body);
  line-height: var(--line-height-body);
}

.docs-content > :first-child {
  margin-top: 0;
}

.docs-content h2,
.docs-content h3 {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
  margin: var(--space-xl) 0 var(--space-sm);
}

.docs-content p,
.docs-content ul,
.docs-content ol,
.docs-content blockquote,
.docs-table-wrap {
  margin: 0 0 var(--space-md);
}

.docs-content ul,
.docs-content ol {
  padding-left: var(--space-lg);
}

.docs-content pre,
.docs-content code {
  background: var(--color-secondary-strip);
  border-radius: 4px;
  font-family: monospace;
}

.docs-content code {
  padding: 2px 4px;
}

.docs-content pre {
  padding: var(--space-sm) var(--space-md);
  overflow-x: auto;
  margin: 0 0 var(--space-md);
}

.docs-content pre code {
  background: none;
  padding: 0;
  white-space: pre;
}

/* .docs-table-wrap: a wide table scrolls horizontally inside the content
   column -- the page itself never scrolls horizontally (UI Considerations
   "Docs code blocks and tables wider than the content column"). */
.docs-table-wrap {
  overflow-x: auto;
}

.docs-content table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-body);
}

.docs-content th,
.docs-content td {
  text-align: left;
  padding: var(--space-xs) var(--space-sm);
  border-bottom: 1px solid var(--border-default);
}

.docs-content th {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-label);
  color: var(--text-muted);
}

.docs-content .align-center { text-align: center; }
.docs-content .align-right { text-align: right; }

.docs-content blockquote {
  border-left: 3px solid var(--border-hover);
  padding-left: var(--space-md);
  color: var(--text-muted);
}

.docs-content a {
  color: var(--color-accent);
  text-decoration: underline;
}

.docs-content hr {
  border: none;
  border-top: 1px solid var(--border-default);
  margin: var(--space-lg) 0;
}
