/* Issuebear app styles */
body {
  min-height: 100vh;
}

.brand-logo {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  vertical-align: middle;
  margin-right: 0.4rem;
}

.avatar {
  width: 28px;
  height: 28px;
  object-fit: cover;
}

.avatar-initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  font-size: 0.7rem;
}

/* The board view fills the window exactly: the page itself never scrolls. The
   body is a flex column (header / main / footer) so main takes the leftover
   height without any magic-number calc, and the board flexes to fill main. */
body.board-view {
  /* 100dvh (dynamic viewport height) tracks iOS Safari's shrinking/growing
     toolbar so the board's bottom edge — and its horizontal scrollbar — stay
     on-screen (ISS-135). 100vh is the fallback for browsers without dvh. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* The board view goes edge-to-edge: main carries no padding so the board's
   horizontal scrollbar spans the full window width and sits flush with the
   bottom. The header strip and breadcrumb re-add their own inset instead. */
body.board-view main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0;
}

body.board-view .board-topbar {
  padding: .6rem 1rem 0;
}

/* iPad / iOS Safari: on the board view <body> is `overflow: hidden; height: 100vh`
   so the page itself never scrolls (see body.board-view above). WebKit then fails
   to paint Bootstrap's fixed `.modal-backdrop` — it's appended straight to <body>,
   and Safari drops the fixed overlay's compositing layer, so the dim never appears:
   a task dialog opens but the board stays fully visible behind it (ISS-75).
   Promoting the backdrop to its own GPU layer forces WebKit to composite and paint
   it. Desktop is unchanged — the backdrop still dims to its usual 0.5 opacity. */
body.board-view .modal-backdrop {
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

/* "in {project}" link beside the board title — quiet, sits on the baseline. */
.board-project-link {
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--ink-3);
  text-decoration: none;
}

.board-project-link:hover {
  color: var(--ink-1);
  text-decoration: underline;
}

/* The link is now a dropdown trigger button — strip the button chrome so it
   keeps reading as the quiet inline link beside the board title. */
button.board-project-link {
  border: 0;
  background: none;
  padding: 0;
}

body.board-themed .board-project-link {
  color: var(--board-fg);
  opacity: .8;
}

body.board-themed .board-project-link:hover {
  opacity: 1;
}

/* The board fills the remaining height so its horizontal scrollbar sits at the
   bottom of the window; the board never scrolls vertically — each column's card
   list scrolls internally instead. Columns top-align (align-items: flex-start)
   so they size to their cards rather than stretching to the full height. */
.kanban-board {
  display: flex;
  flex-direction: row;
  gap: .75rem;
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: auto;
  overflow-y: hidden;
  align-items: flex-start;
  padding: 0 1rem;
}

.kanban-column,
.kanban-column-placeholder {
  flex: 0 0 300px;
  width: 300px;
}

/* A column grows with its cards but never taller than the board minus a 30px
   gap, keeping its bottom clear of the window edge; the card list scrolls
   inside once that cap is hit. */
.kanban-column {
  display: flex;
  flex-direction: column;
  max-height: calc(100% - 30px);
}

.kanban-column-header {
  cursor: grab;
  border-bottom: 0px;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}

.kanban-column-header .btn-link {
  color: var(--bs-body-color) !important;
}

.kanban-column-menu {
  font-size: 1.1rem;
  font-weight: 700;
}

.kanban-column>.card {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.kanban-column-placeholder>.card>.card-body {
  padding: 0.5rem;
}

.kanban-column>.card>.card-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
  padding: 0;
}

.kanban-cards {
  overflow-y: auto;
  overflow-x: hidden;
  flex: 1 1 auto;
  min-height: 0;
  padding: 8px;
}

/* Phone board layout (ISS-135): Trello-style. On a narrow screen a fixed 300px
   column left an awkward horizontal scroll with a sliver of the next column;
   instead columns go near-full-width and scroll-snap one per screen, with a
   small peek of the next column to signal there's more to swipe to. Safe-area
   insets keep the board clear of the notch and home indicator on iPhones
   (these resolve because base.html opts into viewport-fit=cover). */
@media (max-width: 576px) {

  .kanban-board {
    gap: .5rem;
    scroll-snap-type: x mandatory;
    padding-left: max(1rem, env(safe-area-inset-left));
    padding-right: max(1rem, env(safe-area-inset-right));
    padding-bottom: env(safe-area-inset-bottom);
  }

  .kanban-column,
  .kanban-column-placeholder {
    /* Near-full-width: one column fills the screen, the next peeks in. */
    flex-basis: 85vw;
    width: 85vw;
    scroll-snap-align: start;
  }
}

/* Task cards highlight their border on hover to signal they're clickable. The
   border stays 1px (no layout shift) and follows the card's rounded corners. */
.kanban-card {
  cursor: pointer;
  position: relative;
  transition: border-color .12s ease, box-shadow .12s ease;
  box-shadow: 0px 1px 0px 0px rgba(189, 189, 189, 0.75);
  /* ISS-69: cards are press-and-hold draggable on touch, so the hold must not
     begin selecting the card's text or pop the iOS callout menu. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.kanban-card:hover {
  border-color: var(--bs-primary);
  box-shadow: 0 .125rem .375rem rgba(0, 0, 0, .12);
}

/* Completion checkbox slide: the card body reserves a right-hand gutter, and
   slides right into it on hover (or whenever the task is complete) to open a
   slot on the left for the checkbox. Only empty padding passes the right edge,
   so nothing is clipped. */
.kanban-card-main {
  padding: 8px;
  /* Reserve the slide gutter on the right so the text has somewhere to slide
      into. Set here rather than via .p-2, whose !important would win. */
  padding-right: calc(8px + var(--check-gutter));
  transition: transform var(--dur-med) var(--ease);
}

/* When an assignee avatar is pinned top-right, reserve room for it plus the
   slide gutter, so the sliding text moves into the space to the avatar's left
   and never runs under it. */
.kanban-card-main.has-assignee {
  padding-right: calc(var(--check-gutter) + 38px);
}

.kanban-card:hover .kanban-card-main,
.kanban-card.is-complete .kanban-card-main {
  transform: translateX(var(--check-gutter));
}

/* The avatar is pinned to the top-right and never moves on slide. */
.kanban-card-assignee {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 1;
}

/* On cards the avatar is a small secondary marker, not the focus — keep it
   compact (matching the table/time-tracking convention) so it doesn't dominate
   one-line cards. The 38px reserve above is 8 + 22 + 8 to match. */
.kanban-card-assignee .avatar,
.kanban-card-assignee .avatar-initials {
  width: 22px;
  height: 22px;
  font-size: 0.65rem;
}

.kanban-card>.card-check {
  position: absolute;
  left: 9px;
  top: 9px;
  z-index: 1;
  opacity: 0;
  transform: scale(.7);
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.kanban-card:hover>.card-check,
.kanban-card.is-complete>.card-check {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}

/* A completed card reads as done: title muted and struck through. */
.kanban-card.is-complete .kanban-card-title {
  color: var(--ink-3);
  text-decoration: line-through;
}

/* Task priority dots (shared by board cards, lightbox sidebar, dashboard).
   "none" renders no dot at all — see the priority_icon macro in _priority.html. */
.priority-icon {
  display: inline-block;
  width: 0.65em;
  height: 0.65em;
  border-radius: 50%;
  margin-right: 0.35em;
  vertical-align: baseline;
}

.priority-urgent {
  background: var(--danger);
}

.priority-high {
  background: var(--warn);
}

.priority-normal {
  background: var(--route);
}

.priority-low {
  background: var(--ink-3);
}

/* Live "Clanker Working" pill: a distinct labelled pill (pulsing leading dot +
   "Clanker Working" label) shown at the bottom of a board card while an agent is
   actively working the task (a fresh 'working' AgentRun), and beside the task
   reference in the modal sidebar. It is intentionally NOT a bare dot so it never
   reads as the priority circle. The muted `.is-stale` variant marks a run whose
   heartbeat has gone quiet (still locking, "last seen … ago"). Appears/disappears
   live as the card/fragment refetches on the task.updated signal. Hovering reveals
   which clanker via the shared contact tooltip (data-member-id). */
.agent-pill-row {
  margin-bottom: var(--space-1, 0.25rem);
}

/* On the card the badge sits at the bottom, below due date — push it down. */
.clanker-badge-row {
  margin-top: var(--space-1, 0.25rem);
  margin-bottom: 0;
}

.clanker-badge {
  cursor: default;
}

/* Reference row in the modal sidebar: ref badge left, Clanker badge right. */
.task-ref-row {
  margin-bottom: var(--space-2, 0.5rem);
}

/* The ref badge carries its own bottom margin for the standalone layout; inside
   the centered row that margin offsets its centre and drops the Clanker badge
   below it. Move the spacing onto the row so the two badges align. */
.task-ref-row .task-ref-badge {
  margin-bottom: 0;
}

.task-ref-agent:empty {
  display: none;
}

/* The "Waiting on <name>" pill is too wide to share the reference row, so it
   sits on its own line just below it (still in the sidebar), left-aligned under
   the reference badge. */
.task-awaiting-row {
  margin-bottom: var(--space-2, 0.5rem);
}

.agent-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  font-size: var(--text-xs, 0.7rem);
  font-weight: 600;
  line-height: 1.4;
  color: var(--bs-success, #28a745);
  background: rgba(40, 167, 69, 0.12);
  border: 1px solid rgba(40, 167, 69, 0.35);
}

.agent-pill-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--bs-success, #28a745);
  animation: agent-pill-pulse 1.6s ease-in-out infinite;
}

.agent-pill.is-stale {
  color: var(--ink-3, #6c757d);
  background: rgba(108, 117, 125, 0.12);
  border-color: rgba(108, 117, 125, 0.35);
}

.agent-pill.is-stale .agent-pill-dot {
  background: var(--ink-3, #6c757d);
  animation: none;
}

/* "Waiting on <name>" pill: shown when an agent has paused a task pending a
   human reply (task.awaiting_input). Reuses .agent-pill's shape/sizing with a
   distinct warm colour so it reads apart from the green "Clanker Working" pill —
   no pulsing dot, since this is a static "blocked" state, not live activity. */
.agent-pill.awaiting-pill {
  color: var(--bs-warning-text, #997404);
  background: rgba(255, 193, 7, 0.15);
  border-color: rgba(255, 193, 7, 0.4);
}

@keyframes agent-pill-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.5);
    opacity: 1;
  }
  50% {
    box-shadow: 0 0 0 4px rgba(40, 167, 69, 0);
    opacity: 0.6;
  }
}

/* Task reference (e.g. WEB-1) shown on a board card when the board opts in. */
.card-ref {
  font-size: var(--text-xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--space-1);
}

/* Quiet reference chip at the top of the task modal sidebar — a button that
   copies the reference to the clipboard on click. */
.task-ref-badge {
  display: inline-block;
  font-size: var(--text-xs);
  color: var(--ink-2);
  background: var(--paper-sunk);
  border: none;
  border-radius: var(--radius-sm);
  padding: 2px 8px;
  margin-bottom: var(--space-2);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.task-ref-badge:hover {
  color: var(--ink-1);
}

.task-ref-badge:focus-visible {
  outline: 2px solid var(--route);
  outline-offset: 1px;
}

/* While dragging, freeze the slide/checkbox so it doesn't fight the drag. */
.kanban-board.is-dragging .kanban-card-main {
  transition: none;
}

/* The settle guard (added on drop, lifted on the next real pointer move — see
   settleDragHover in board-drag.js) reuses the mid-drag hover suppression so the
   card sliding into the vacated slot can't replay its checkbox reveal from the
   layout shift alone (ISS-100).

   Freeze each card's checkbox to its RESTING (non-hover) visibility for the whole
   drag + settle, so a card reflowing under a static cursor can never catch :hover
   and reveal a checkbox the user never asked for. WHICH card keeps a resting
   checkbox differs by checkbox mode, so the carve-out must too:
   - complete mode: the completed card keeps its permanent green tick;
   - select mode: the SELECTED card keeps its box — a completed-but-unselected
     card has NO resting checkbox here (it is opacity:0 until hovered/selected,
     see the [data-checkbox-mode="select"] rules below), so it must be suppressed
     too. The old single `:not(.is-complete)` rule wrongly exempted it, which let a
     completed card flicker a bare checkbox (hover reveal, but border suppressed)
     as it slid under a static cursor mid-drag in select mode. */
#board:not([data-checkbox-mode="select"]) .kanban-board.is-dragging .kanban-card:not(.is-complete)>.card-check,
#board:not([data-checkbox-mode="select"]) .kanban-board.is-drag-settle .kanban-card:not(.is-complete)>.card-check {
  display: none;
}

#board[data-checkbox-mode="select"] .kanban-board.is-dragging .kanban-card:not(.is-selected)>.card-check,
#board[data-checkbox-mode="select"] .kanban-board.is-drag-settle .kanban-card:not(.is-selected)>.card-check {
  display: none;
}

/* Drag-and-drop drop indicator. SortableJS tags the prospective drop slot with
   .sortable-ghost; render it as an empty dashed outline so it's obvious where a
   card or column will land. Its contents are hidden but keep their box so the
   slot matches the dragged item's size. The element following the cursor is the
   browser's native drag image. */
.kanban-card.sortable-ghost,
.kanban-column.sortable-ghost>.card {
  border: 2px dashed var(--bs-primary);
  background-color: rgba(var(--bs-primary-rgb), .08);
  box-shadow: none;
}

.kanban-card.sortable-ghost>*,
.kanban-column.sortable-ghost>.card>* {
  visibility: hidden;
}

.kanban-card.sortable-chosen,
.kanban-column.sortable-chosen {
  cursor: grabbing;
}

/* During a drag the board carries .is-dragging; native HTML5 drag never fires
   mouseleave, so anything passing under the cursor (the "+ Add task" button as
   the source column collapses, or reflowing cards) would stick in :hover.
   Suppress those hover states for the duration of the drag. */
.kanban-board.is-dragging .kanban-add-task {
  pointer-events: none;
}

.kanban-board.is-dragging .kanban-card:hover,
.kanban-board.is-drag-settle .kanban-card:hover {
  border-color: var(--bs-card-border-color);
  box-shadow: none;
}

/* ...and the checkbox-gutter text slide (is-complete cards keep their
   permanent slide; only the hover-driven one is suppressed mid-drag and while
   the drop settles). */
.kanban-board.is-dragging .kanban-card:hover:not(.is-complete) .kanban-card-main,
.kanban-board.is-drag-settle .kanban-card:hover:not(.is-complete) .kanban-card-main {
  transform: none;
}

/* Select mode: a completed card has NO resting checkbox (its box means
   "selected", not "complete"), so it must not slide during a drag either — the
   checkbox suppression above already hides its box, and without this the card
   would still translate its text into an empty gutter when it reflows under a
   static cursor mid-drag (the is-complete slide on line ~197 otherwise wins
   under the frozen :hover). Freeze the slide to resting for every non-selected
   card the whole drag + settle; the selected card keeps its persistent slide. */
#board[data-checkbox-mode="select"] .kanban-board.is-dragging .kanban-card:not(.is-selected) .kanban-card-main,
#board[data-checkbox-mode="select"] .kanban-board.is-drag-settle .kanban-card:not(.is-selected) .kanban-card-main {
  transform: none;
}

.kanban-card-container {
  background-color: var(--bs-card-cap-bg);
}

/* The task name acts as the modal heading: drop the field label and render the
   editable value at heading size. */
.task-title-field .text-muted.small {
  display: none;
}

.task-title-field .mb-3 {
  margin-bottom: 0 !important;
}

.task-title-field .task-title {
  font-size: 1.25rem;
  font-weight: 600;
}

/* Sidebar fields (due date / step / assignee) sit in a bordered right column. */
@media (min-width: 768px) {
  .task-sidebar {
    border-left: 1px solid var(--bs-border-color);
  }
}

/* Settings background-colour swatches. */
.board-swatch {
  width: 44px;
  height: 44px;
  border: 1px solid var(--bs-border-color);
  border-radius: .375rem;
  padding: 0;
  color: #fff;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

.board-swatch-default {
  background: #fff;
  color: var(--bs-secondary-color);
  font-size: .7rem;
}

.board-swatch.is-selected {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

/* Board background-colour theming (board view only). The chosen colour is set
   as a gradient on <body>; the top bar becomes a frosted translucent-white band
   and its text/links flip to white or black for contrast via --board-fg. */
body.board-light {
  --board-fg: #212529;
}

body.board-dark {
  --board-fg: #ffffff;
}

/* On a colour-themed board the header becomes a frosted translucent band so the
   board's colour reads through; its brand/nav/trail flip to the contrast
   colour the theme picked (--board-fg). */
body.board-themed .app-header {
  background: rgba(255, 255, 255, .14);
  backdrop-filter: saturate(1.1) blur(6px);
  border-bottom-color: rgba(255, 255, 255, .2);
}

body.board-themed .app-brand,
body.board-themed .app-nav a,
body.board-themed .app-nav .nav-pill-btn,
body.board-themed .org-chip,
body.board-themed .avatar-btn,
body.board-themed .board-topbar .btn-link {
  color: var(--board-fg) !important;
}

body.board-themed .app-nav a:hover,
body.board-themed .app-nav a.is-active,
body.board-themed .app-nav .nav-pill-btn:hover,
body.board-themed .app-nav .nav-pill-btn.is-active {
  background: rgba(255, 255, 255, .16);
}

/* The Projects dropdown lives inside .app-nav, so the flip above paints its
   menu items in --board-fg too. But the menu is a white popover, where that
   (e.g. white-on-a-dark-board) text is invisible — restore normal ink colours
   for the items, the same way board-topbar popovers do for their buttons. */
body.board-themed .app-nav .dropdown-menu .dropdown-item {
  color: var(--ink-2) !important;
}

/* The same flip's hover rule (.app-nav a:hover) paints a translucent-white
   fill, which vanishes against the white popover — so the menu items lose
   their hover highlight. Restore the normal dropdown hover wash here too. */
body.board-themed .app-nav .dropdown-menu .dropdown-item:hover,
body.board-themed .app-nav .dropdown-menu .dropdown-item:focus,
body.board-themed .app-nav .dropdown-menu .dropdown-item.is-active {
  color: var(--ink-1) !important;
  background: var(--paper-sunk) !important;
}

/* The org chip and avatar carry their own light fills, which would swallow the
   flipped (white-on-dark-board) text. On a themed board give them a translucent
   fill and a --board-fg outline so both read against the board colour. */
body.board-themed .org-chip {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .28);
}

body.board-themed .avatar-btn {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .28);
}

body.board-themed .avatar-initials {
  background: rgba(255, 255, 255, .18);
  color: var(--board-fg);
}

/* The notification bell flips to the board foreground too, with a translucent
   hover (the light --paper-sunk hover would vanish on a dark board header). */
body.board-themed .notif-bell {
  color: var(--board-fg);
}

body.board-themed .notif-bell:hover {
  background: rgba(255, 255, 255, .16);
  color: var(--board-fg);
}

body.board-themed .board-topbar .btn-outline-secondary {
  --bs-btn-hover-bg: rgba(255, 255, 255, .18);
  --bs-btn-hover-color: var(--board-fg);
  --bs-btn-hover-border-color: var(--board-fg);
  --bs-btn-active-bg: rgba(255, 255, 255, .26);
  --bs-btn-active-color: var(--board-fg);
  --bs-btn-active-border-color: var(--board-fg);
  color: var(--board-fg);
  border-color: var(--board-fg);
}

/* ==========================================================================
   Settings pages (profile, manage organisation) — sticky section nav +
   panelled sections.
   ========================================================================== */
.settings-layout {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}

.settings-nav {
  position: sticky;
  top: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.settings-nav a {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border-left: 2px solid transparent;
  color: var(--ink-3);
  text-decoration: none;
  font-size: var(--text-sm);
}

.settings-nav a:hover {
  color: var(--ink-1);
  background: var(--paper-sunk);
}

.settings-nav a.is-active {
  color: var(--ink-1);
  background: var(--paper-sunk);
  border-left-color: var(--route);
  font-weight: 500;
}

.settings-sections section {
  scroll-margin-top: var(--space-5);
}

.settings-avatar {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}

/* Hairline-divided list + inline add/invite form, shared by the settings
   pages (API keys, org members & invitations). */
.settings-list {
  list-style: none;
  margin: 0 0 var(--space-2);
  padding: 0;
}

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--line-2);
}

.settings-row:last-child {
  border-bottom: 0;
}

.settings-row-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.settings-form {
  display: flex;
  align-items: stretch;
  /* button matches the input's height */
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line-2);
}

.settings-form input[type="text"],
.settings-form input[type="email"],
.settings-form input:not([type]) {
  max-width: 320px;
}

.settings-form .btn {
  white-space: nowrap;
  flex-shrink: 0;
}

/* Room to scroll the lower sections up to the top so the nav can reach them. */
.settings-sections {
  padding-bottom: 55vh;
}

@media (max-width: 768px) {
  .settings-layout {
    grid-template-columns: 1fr;
  }

  .settings-nav {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
  }
}

html {
  scroll-behavior: smooth;
}

/* Task comments — avatar + author/time + rendered markdown body, with quiet
   inline actions. Composer reuses the markdown editor (see editor.css). */
.task-comments {
  margin-top: var(--space-5);
  border-top: 1px solid var(--line-2);
  padding-top: var(--space-4);
}

.task-comments-title,
.task-section-title {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--space-3);
}

/* Relationship add popup: a wider menu and a segmented type picker. */
.relationship-add-menu {
  min-width: 19rem;
}

.rel-kind-picker {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  /* breathing room above the search input */
}

.rel-kind-option {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-1);
  border: 1px solid var(--line-1);
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: center;
  color: var(--ink-3);
  transition:
    border-color 0.12s ease,
    background-color 0.12s ease,
    color 0.12s ease;
}

.rel-kind-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.rel-kind-icon {
  display: inline-flex;
}

.rel-icon {
  width: 20px;
  height: 20px;
}

.rel-kind-label {
  font-size: var(--text-xs);
  line-height: 1.15;
}

.rel-kind-option:hover {
  border-color: var(--route);
  color: var(--ink-1);
}

.rel-kind-option:has(.rel-kind-input:checked) {
  border-color: var(--route);
  background: rgba(var(--bs-primary-rgb), 0.12);
  color: var(--route);
}

.rel-kind-option:has(.rel-kind-input:focus-visible) {
  outline: 2px solid var(--route);
  outline-offset: 1px;
}

/* Section header only when there is content (matches relationships). */
.checklists-title {
  display: none;
}

.task-checklists:has(#checklist-list > *) .checklists-title {
  display: block;
}

.relationship-group {
  margin-bottom: var(--space-3);
}

.relationship-group-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-3);
  margin-bottom: var(--space-1);
}

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

.relationship-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.relationship-ref {
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

.relationship-name {
  flex: 1;
  min-width: 0;
}

.comment-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.comment {
  display: flex;
  gap: var(--space-3);
}

/* While editing, the comment is just the full-width editor — not the
   avatar+body flex row, which would squeeze the editor to half width. */
.comment-editing {
  display: block;
}

.comment-avatar {
  flex-shrink: 0;
}

.comment-avatar .avatar,
.comment-avatar .avatar-initials {
  width: 28px;
  height: 28px;
  font-size: var(--text-xs);
}

.comment-main {
  flex: 1;
  min-width: 0;
}

.comment-meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: 2px;
}

.comment-author {
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--ink-1);
}

/* The posted time doubles as a "copy link to this comment" control. Reset the
   button chrome so it reads as the plain timestamp it replaces, with a subtle
   affordance on hover/focus. */
.comment-time {
  border: 0;
  background: none;
  padding: 0;
  font-family: inherit;
  font-size: var(--text-xs);
  color: var(--ink-3);
  cursor: pointer;
}

.comment-time:hover,
.comment-time:focus-visible {
  color: var(--ink-1);
  text-decoration: underline;
}

/* Brief flash when a comment is reached via a deep link (#comment-<id>). */
@keyframes comment-highlight-flash {
  from {
    background: var(--route-soft);
  }
  to {
    background: transparent;
  }
}

.comment-highlight {
  border-radius: var(--radius-sm);
  animation: comment-highlight-flash 2s var(--ease);
}

.comment-body {
  font-size: var(--text-base);
  color: var(--ink-1);
}

.comment-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-1);
}

.comment-action {
  border: 0;
  background: none;
  padding: 0;
  font-size: var(--text-xs);
  color: var(--ink-3);
  cursor: pointer;
}

.comment-action:hover {
  color: var(--ink-1);
  text-decoration: underline;
}

.comment-action.danger:hover {
  color: var(--danger);
}

.comment-composer {
  margin-bottom: var(--space-4);
}

/* Composer stays collapsed behind the "Add comment" button until requested. */
.comment-composer {
  display: none;
}

.task-comments.is-composing .comment-composer {
  display: block;
}

.task-comments.is-composing .comment-add-btn {
  display: none;
}

.comment-add-btn {
  margin-bottom: var(--space-4);
}

.comment-composer-actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-top: var(--space-2);
}

/* Description header reuses the section title; its field-show carries a
   redundant inline "Description" label that we suppress in favour of the h6. */
.task-description .field-label,
.task-description>div>.text-muted.small {
  display: none;
}

/* Task checklists — nested structural lists inside the lightbox. Quiet by
   default; drag handle, due, assignee and delete affordances surface on hover.
   Progress uses the route accent so completion reads at a glance. */
.task-checklists {
  margin-bottom: var(--space-3);
}

/* Card actions toolbar: the row of buttons (Add checklist, Upload file, …) that
   sits above the checklist + attachment lists. The section divider. */
.card-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-5);
  border-top: 1px solid var(--line-2);
  padding-top: var(--space-4);
  margin-bottom: var(--space-3);
}

/* Track Time popup: equal-width hours/minutes inputs. */
.time-input-row {
  display: flex;
  gap: var(--space-2);
}

.time-input-row .time-input {
  flex: 1 1 0;
  min-width: 0;
}

.time-input {
  display: flex;
  flex-direction: column;
  font-size: var(--text-xs);
  color: var(--ink-3);
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}

/* Task modal time-tracking section — a hairline-divided list echoing comments. */
.task-time {
  margin-top: var(--space-5);
  border-top: 1px solid var(--line-2);
  padding-top: var(--space-4);
}

.task-time-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}

.time-export-link {
  font-size: var(--text-xs);
  color: var(--ink-3);
  text-decoration: none;
}

.time-export-link:hover {
  color: var(--route);
  text-decoration: underline;
}

.time-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.time-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line-2);
  font-size: var(--text-sm);
}

.time-who {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 auto;
  min-width: 0;
}

.time-who-avatar .avatar,
.time-who-avatar .avatar-initials {
  width: 22px;
  height: 22px;
  font-size: 0.65rem;
}

.time-who-name {
  color: var(--ink-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.time-date {
  color: var(--ink-3);
  flex: none;
  width: 7rem;
}

.time-amount {
  color: var(--ink-1);
  font-variant-numeric: tabular-nums;
  flex: none;
  width: 4rem;
  text-align: right;
}

.time-actions {
  display: flex;
  gap: var(--space-2);
  flex: none;
  width: 6rem;
  justify-content: flex-end;
}

.time-action {
  border: 0;
  background: none;
  padding: 0;
  font-size: var(--text-xs);
  color: var(--ink-3);
  cursor: pointer;
}

.time-action:hover {
  color: var(--ink-1);
  text-decoration: underline;
}

.time-action.danger:hover {
  color: var(--danger);
}

.time-total {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  padding-top: var(--space-3);
  font-size: var(--text-sm);
}

.time-total-label {
  color: var(--ink-3);
  text-transform: uppercase;
  font-size: var(--text-xs);
  letter-spacing: 0.05em;
  font-weight: 600;
}

.time-total .time-amount {
  font-weight: 600;
}

/* Inline edit row inside the time list. */
.time-row-editing {
  display: block;
  padding: var(--space-3) 0;
}

.time-edit-fields {
  display: flex;
  gap: var(--space-2);
}

.time-edit-fields .time-input {
  flex: 1 1 0;
}

.time-edit-actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-top: var(--space-2);
}

/* Generic data table that sits padded inside a .panel (report + dashboard
   time tables) — never flush to the panel edge. */
.table-wrap {
  padding: 0 var(--space-5) var(--space-4);
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.data-table th {
  text-align: left;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line-2);
  white-space: nowrap;
}

.data-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line-2);
  color: var(--ink-1);
  vertical-align: middle;
}

.data-table tbody tr:hover {
  background: var(--paper-sunk);
}

.data-table tfoot td {
  font-weight: 600;
  border-top: 1px solid var(--line-1);
  border-bottom: 0;
}

.data-table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.cell-user {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
}

.cell-user .avatar,
.cell-user .avatar-initials {
  width: 22px;
  height: 22px;
  font-size: 0.65rem;
}

/* Report filter bar — labelled fields on one aligned row. */
.time-report-filters {
  display: flex;
  gap: var(--space-3);
  align-items: flex-end;
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

.time-report-filters .filter-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.time-report-filters label {
  font-size: var(--text-xs);
  color: var(--ink-3);
}

.time-report-filters .form-control,
.time-report-filters .form-select {
  min-width: 9.5rem;
}

/* Match the Apply button's height to the inputs beside it (controls use a
   slightly taller vertical padding than .btn). */
.time-report-filters button[type="submit"] {
  padding-top: 0.45rem;
  padding-bottom: 0.45rem;
}

.cell-ref {
  font-size: var(--text-xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  margin-right: var(--space-1);
}

/* Dashboard "Latest tasks" rows fill the panel width: the result-row stretches
   and pushes the status pill + assignee avatar to the right edge. */
.plain-row--flush {
  padding: 0;
}

.plain-row--flush .result-row {
  flex: 1 1 auto;
  width: 100%;
  padding: var(--space-3) var(--space-5);
}

.result-assignee {
  flex: none;
  display: inline-flex;
}

.result-assignee .avatar,
.result-assignee .avatar-initials {
  width: 24px;
  height: 24px;
  font-size: 0.7rem;
}

/* Inline add/edit row: a grown input with ✓/✕ controls pinned at the end.
   Used by the per-checklist "Add an item" row, which stays collapsed behind a
   toggle. (The "Add checklist" form uses the canonical .popmenu-form.) */
.inline-add {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.checklist-additem .inline-add {
  display: none;
}

.checklist-additem.is-adding .checklist-add-item {
  display: flex;
}

.checklist-additem.is-adding [data-additem-toggle] {
  display: none;
}

.inline-add .form-control {
  flex: 1;
  min-width: 0;
}

.inline-add-save,
.inline-add-cancel {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line-1);
  border-radius: var(--radius-sm);
  background: var(--paper-1);
  cursor: pointer;
  font-size: var(--text-sm);
  line-height: 1;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.inline-add-save {
  color: var(--ok);
}

.inline-add-save:hover {
  background: var(--ok-soft);
}

.inline-add-cancel {
  color: var(--ink-3);
}

.inline-add-cancel:hover {
  background: var(--paper-sunk);
  color: var(--ink-1);
}

.checklist {
  margin-bottom: var(--space-4);
}

.checklist-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.checklist-name {
  font-weight: 600;
  font-size: var(--text-base);
  color: var(--ink-1);
  cursor: text;
  border-radius: var(--radius-sm);
  padding: 1px 4px;
  margin-left: -4px;
}

.checklist-name:hover {
  background: var(--paper-sunk);
}

.checklist-count {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.checklist-del,
.checklist-item-del {
  flex-shrink: 0;
  border: 0;
  background: none;
  color: var(--ink-4);
  line-height: 1;
  padding: 0 var(--space-1);
  transition: opacity var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

/* The checklist-header delete stays hover-revealed; the per-item delete is
   always shown (alongside the due/assignee icons below). */
.checklist-del {
  opacity: 0;
}

.checklist-head:hover .checklist-del {
  opacity: 1;
}

.checklist-del:hover,
.checklist-item-del:hover {
  color: var(--danger);
}

.checklist-progress {
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--paper-sunk);
  overflow: hidden;
  margin-bottom: var(--space-2);
}

.checklist-progress>span {
  display: block;
  height: 100%;
  background: var(--route);
  border-radius: inherit;
  transition: width var(--dur-med) var(--ease);
}

.checklist-items {
  list-style: none;
  margin: 0;
  padding: 0;
}

.checklist-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
}

.checklist-drag {
  flex-shrink: 0;
  color: var(--ink-4);
  cursor: grab;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.checklist-item:hover .checklist-drag {
  opacity: 1;
}

.checklist-check {
  flex-shrink: 0;
  accent-color: var(--route);
  cursor: pointer;
}

.checklist-text {
  flex: 1;
  min-width: 0;
  color: var(--ink-1);
  cursor: text;
  border-radius: var(--radius-sm);
  padding: 1px 4px;
  margin-left: -4px;
}

.checklist-text:hover {
  background: var(--paper-sunk);
}

.checklist-item.is-done .checklist-text {
  text-decoration: line-through;
  color: var(--ink-3);
}

/* In-place editors: the input wraps the footprint of the text it replaces
   (flex:1) so nothing around it moves and it reads as "editing this text",
   not "a form appeared". */
.checklist-text-edit {
  display: flex;
  flex: 1;
  min-width: 0;
  margin: 0;
}

.checklist-inline-input {
  flex: 1;
  min-width: 0;
  font: inherit;
  color: var(--ink-1);
  background: var(--field-bg-focus);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-sm);
  padding: 1px 4px;
  margin-left: -5px;
}

.checklist-inline-input:focus {
  outline: none;
  border-color: var(--route);
  box-shadow: 0 0 0 2px var(--route-ring);
}

.inline-edit-input-bold {
  font-weight: 600;
}

/* Right-aligned item controls: due + assignee icon popdowns, then delete. */
.checklist-item-controls {
  flex-shrink: 0;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.checklist-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  padding: 0 4px;
  border: 0;
  background: none;
  border-radius: var(--radius-sm);
  color: var(--ink-4);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.checklist-icon-btn:hover {
  background: var(--paper-sunk);
  color: var(--ink-2);
}

/* The due/assignee icon buttons are always visible (an unset icon is just
   muted), so the actions on a checklist item are discoverable without hovering. */
.checklist-icon {
  width: 15px;
  height: 15px;
}

.checklist-due {
  font-size: var(--text-xs);
  color: var(--route);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.checklist-icon-btn .avatar,
.checklist-icon-btn .avatar-initials {
  width: 22px;
  height: 22px;
  font-size: var(--text-xs);
}

/* Item due / assignee popups use the canonical .popmenu-form / .popmenu rows. */

.checklist-additem {
  margin-top: var(--space-2);
}

.checklist-additem-toggle {
  border: 0;
  background: none;
  padding: 1px 4px;
  margin-left: -4px;
  color: var(--ink-3);
  font-size: var(--text-sm);
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.checklist-additem-toggle:hover {
  background: var(--paper-sunk);
  color: var(--ink-1);
}

/* Card badge: checklist completion count, sits below the card title. */
.card-checklist-badge {
  display: inline-block;
  font-size: var(--text-xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  margin-top: var(--space-1);
}

/* ==========================================================================
   Notification bell + dropdown
   ========================================================================== */
.notif-bell-wrap {
  position: relative;
}

.notif-bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 0;
  background: none;
  color: var(--ink-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.notif-bell:hover {
  background: var(--paper-sunk);
  color: var(--ink-1);
}

.notif-bell-icon {
  width: 20px;
  height: 20px;
}

.notif-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 600;
  color: #fff;
  background: var(--danger);
  border-radius: var(--radius-pill);
  line-height: 1;
}

.notif-badge.is-empty {
  display: none;
}

.notif-dropdown {
  width: 320px;
  max-height: 420px;
  overflow-y: auto;
  padding: 0;
}

.notif-dropdown-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3);
  border-bottom: 1px solid var(--line-2);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.notif-readall,
.notif-read {
  border: 0;
  background: none;
  color: var(--route);
  font-size: var(--text-xs);
  cursor: pointer;
  padding: 0;
}

.notif-readall:hover,
.notif-read:hover {
  text-decoration: underline;
}

.notif-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.notif-item {
  padding: var(--space-3);
  border-bottom: 1px solid var(--line-2);
}

.notif-item.is-unread {
  background: var(--route-soft);
}

/* Avatar of the actor sits to the left; the title/body/meta stack to its right. */
.notif-row {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
}

.notif-avatar {
  flex: 0 0 auto;
}

.notif-main {
  min-width: 0;
  flex: 1 1 auto;
}

/* The notification's timestamp, formatted in the user's locale/timezone. */
.notif-meta {
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--ink-3);
}

.notif-link {
  color: var(--ink-1);
  font-weight: 500;
  font-size: var(--text-sm);
  text-decoration: none;
}

.notif-link:hover {
  text-decoration: underline;
}

.notif-body {
  font-size: var(--text-sm);
  color: var(--ink-2);
  margin-top: 2px;
}

.notif-read {
  margin-top: var(--space-1);
}

.notif-empty,
.notif-foot {
  display: block;
  padding: var(--space-3);
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.notif-foot {
  text-align: center;
  border-top: 1px solid var(--line-2);
}

.notif-page .notif-item {
  border-bottom: 1px solid var(--line-1);
}

/* Task attachments (upload via the card-actions "Upload file" button or by
   dropping files on the modal — no visible file field). */
.attachment-list {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
}

.attachment {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line-2);
}

.attachment-thumb {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.attachment-glyph {
  width: 40px;
  text-align: center;
  font-size: var(--text-lg);
  flex-shrink: 0;
}

.attachment-name {
  flex: 1;
  min-width: 0;
  color: var(--ink-1);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.attachment-name:hover {
  text-decoration: underline;
}

.attachment-size {
  flex-shrink: 0;
}

.attachment-del {
  flex-shrink: 0;
  border: 0;
  background: none;
  color: var(--ink-4);
  line-height: 1;
  padding: 0 var(--space-1);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.attachment:hover .attachment-del {
  opacity: 1;
}

.attachment-del:hover {
  color: var(--danger);
}

.attachment-error {
  color: var(--danger);
  font-size: var(--text-sm);
  padding: var(--space-2) 0;
}

/* Drop-to-attach highlight while dragging files over the open task modal. */
#task-modal-content.is-dropping {
  outline: 2px dashed var(--route);
  outline-offset: -6px;
}

#task-modal-content.is-dropping::after {
  content: "Drop files to attach";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--route-soft);
  color: var(--route);
  font-weight: 600;
  border-radius: var(--radius-lg);
  pointer-events: none;
  z-index: 5;
}

/* ── Task-modal canonical components ───────────────────────────────────────
   Pick popup (.popmenu + .popmenu-item), form popup (.popmenu-actions),
   inline text edit (.inline-edit-*), and sidebar metadata boxes (.sidebar-box).
   See docs/superpowers/specs/2026-05-31-task-modal-popups-design.md */

/* Both popup surfaces lift off the page: solid elevated background, a defined
   border, and a soft shadow for contrast. A small popper offset keeps them
   visually tethered to the trigger that opened them. */
.popmenu,
.popmenu-form {
  background: var(--paper-2);
  border: 1px solid var(--line-1);
  box-shadow: 0 6px 20px rgba(28, 27, 25, 0.18);
}

.popmenu {
  padding: var(--space-1);
  min-width: 15rem;
  max-height: 280px;
  overflow-y: auto;
}

.popmenu-form {
  padding: var(--space-2);
  min-width: 16rem;
}

/* Scope the flex stacking to the shown state only — a bare `display: flex` on
   .popmenu-form would override Bootstrap's `.dropdown-menu { display: none }`
   and leave every form popup rendered open in normal flow. */
.popmenu-form.show {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Fixed-height pick row with a fixed leading slot, so rows with an avatar and
   rows without (Unassigned / No reminder) are the same height and align. */
.popmenu-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  height: 34px;
  border: 0;
  background: none;
  text-align: left;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--ink-1);
  font-size: var(--text-sm);
  cursor: pointer;
  text-decoration: none;
}

.popmenu-item:hover {
  background: var(--paper-sunk);
  text-decoration: none;
}

.popmenu-item.is-on {
  background: var(--route-soft);
  color: var(--route);
}

.popmenu-lead {
  flex: 0 0 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.popmenu-lead .avatar,
.popmenu-lead .avatar-initials {
  width: 22px;
  height: 22px;
  font-size: var(--text-xs);
}

.popmenu-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.popmenu-check {
  flex: 0 0 auto;
  color: var(--route);
  font-size: var(--text-xs);
}

/* Card quick-actions context menu (ISS-9). Reuses the .dropdown-menu.popmenu
   surface but is positioned manually by card-menu.js (fixed, at the cursor)
   rather than by Bootstrap's Popper, so it overrides position + margin. It is
   shown by toggling .show (Bootstrap's .dropdown-menu.show { display:block }). */
.card-quick-menu {
  position: fixed;
  margin: 0;
  z-index: 1080;
  min-width: 200px;
  max-width: 260px;
  /* Fit all items rather than capping at the base .popmenu 280px — the menu grew
     past that as we added actions. Only scroll when the viewport itself is too
     short to hold the panel; card-menu.js keeps such a scrollbar usable. */
  max-height: calc(100vh - 1rem);
}
/* The drill-in chevron / back arrow sits muted at the row edge. */
.card-quick-menu .cm-chevron {
  color: var(--ink-3);
}
.card-quick-menu .cm-action.danger .popmenu-label {
  color: var(--danger);
}

/* Board multi-select + Bulk Edit (ISS-2). In "select" checkbox mode the card /
   row checkboxes select tasks rather than completing them. Selection is shown in
   the brand "route" colour so it stays visually distinct from the green
   completion tick (a task can be both completed and selected).

   The card checkbox behaves like the completion checkbox does normally: hidden
   until you hover the card, then kept revealed once the task is SELECTED (the way
   a completed card keeps its checkbox visible in complete mode). It is NOT shown
   permanently on every card, and — because in select mode the card checkbox means
   "selected", not "complete" — a completed task neither forces the checkbox open
   nor paints the green completion tick on the card. Completion still reads from
   the struck-through title and is set from the modal sidebar. */

/* Stop completion from driving the card checkbox in select mode: a completed card
   that is neither hovered nor selected hides its checkbox and doesn't slide, just
   like an incomplete one (overrides the is-complete reveal + slide rules above). */
#board[data-checkbox-mode="select"] .kanban-card.is-complete:not(:hover):not(.is-selected) > .card-check {
  opacity: 0;
  transform: scale(.7);
  pointer-events: none;
}
#board[data-checkbox-mode="select"] .kanban-card.is-complete:not(:hover):not(.is-selected) .kanban-card-main {
  transform: none;
}

/* A selected card keeps its checkbox revealed and its text slid, even off-hover. */
#board[data-checkbox-mode="select"] .kanban-card.is-selected > .card-check {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}
#board[data-checkbox-mode="select"] .kanban-card.is-selected .kanban-card-main {
  transform: translateX(var(--check-gutter));
}

/* The card/row checkbox shows the green completion tick everywhere EXCEPT select
   mode, where it reflects selection: suppress the green fill/tick on a card or
   row that is completed-but-not-selected so it reads as an empty, tickable box. */
#board[data-checkbox-mode="select"] .kanban-card > .card-check.is-checked:not(.is-selected) .card-check-box,
#board[data-checkbox-mode="select"] .board-table-row .card-check.is-checked:not(.is-selected) .card-check-box {
  background: var(--paper-1);
  border-color: var(--line-3);
}
#board[data-checkbox-mode="select"] .kanban-card > .card-check.is-checked:not(.is-selected) .card-check-tick,
#board[data-checkbox-mode="select"] .board-table-row .card-check.is-checked:not(.is-selected) .card-check-tick {
  opacity: 0;
  stroke-dashoffset: 17;
}
.card-check.is-selected .card-check-box {
  background: var(--route);
  border-color: var(--route);
}
.card-check.is-selected .card-check-tick {
  opacity: 1;
  stroke-dashoffset: 0;
}
.kanban-card.is-selected {
  outline: 2px solid var(--route);
  outline-offset: 1px;
}
.board-table-row.is-selected {
  background: var(--route-soft);
}

/* The Bulk Edit popmenu is a floating panel anchored under the toolbar button,
   positioned by board-select.js and revealed via .show (like the card menu). */
.bulk-edit-menu {
  position: fixed;
  margin: 0;
  z-index: 1080;
  min-width: 200px;
  max-width: 260px;
  /* Fit all items rather than capping at the base .popmenu 280px. Only scroll
     when the viewport is too short; board-select.js keeps that scrollbar usable. */
  max-height: calc(100vh - 1rem);
}
.bulk-edit-menu .cm-chevron {
  color: var(--ink-3);
}
.bulk-edit-menu .cm-action.danger .popmenu-label {
  color: var(--danger);
}

/* Form popup: full-width Save (+ optional full-width secondary), stacked, with
   a little breathing room above it separating it from the inputs. */
.popmenu-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.popmenu-actions .btn {
  width: 100%;
}

/* Custom-fields filter popup: it can group many fields, so cap its height and
   scroll. Each field is a small labelled fieldset separated by a hairline. */
.filter-custom-menu {
  max-height: 70vh;
  overflow-y: auto;
}

.filter-custom-field {
  border: 0;
  border-top: 1px solid var(--line-1);
  margin: 0;
  padding: var(--space-2) 0 0;
}

.filter-custom-field:first-of-type {
  border-top: 0;
  padding-top: 0;
}

.filter-custom-legend {
  float: none;
  width: auto;
  margin-bottom: var(--space-1);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-2);
}

/* Inline text edit: the display reads like plain text; clicking swaps it for an
   input of the same footprint, so nothing reflows. The display and input share
   an identical box model — inline-block, border-box, the same line-height, 1px
   border (transparent on the display), padding and -5px offset — so swapping one
   for the other changes neither height nor horizontal position. */
.inline-edit-display,
.inline-edit-input {
  display: inline-block;
  box-sizing: border-box;
  line-height: 1.35;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 1px 4px;
  margin-left: -5px;
}

.inline-edit-display {
  cursor: text;
}

.inline-edit-display:hover {
  background: var(--paper-sunk);
}

.inline-edit-form {
  display: flex;
  flex: 1;
  min-width: 0;
  margin: 0;
}

.inline-edit-input {
  flex: 1;
  min-width: 0;
  font: inherit;
  line-height: 1.35;
  color: var(--ink-1);
  background: var(--field-bg-focus);
  border: 1px solid var(--field-border);
}

.inline-edit-input:focus {
  outline: none;
  border-color: var(--route);
  box-shadow: 0 0 0 2px var(--route-ring);
}

/* Sidebar metadata boxes: each field is a bordered box that doubles as the
   popup trigger; hover signals "clickable" without a button look. */
.sidebar-field {
  margin-bottom: var(--space-1);
}

/* Sidebar group header (e.g. "Custom fields"), matching the main-column
   section titles but tightened for the denser sidebar. */
.sidebar-section-title {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: var(--space-3) 0 var(--space-2);
}

.sidebar-box {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--paper-1);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.sidebar-box:hover {
  background: var(--paper-sunk);
  border-color: var(--line-1);
}

/* Open-state highlight: while its popup is open, EVERY popup trigger — a sidebar
   box or a button (Add checklist, the labels +, the checklist due/assignee
   icons) — stays highlighted so it reads as the anchor the popup belongs to.
   Bootstrap sets aria-expanded="true" on the toggle while the dropdown is open. */
.sidebar-box[aria-expanded="true"] {
  background: var(--route-soft);
  border-color: var(--route);
}

[data-checklist-add][aria-expanded="true"],
.label-add[aria-expanded="true"],
.checklist-icon-btn[aria-expanded="true"] {
  background: var(--route-soft);
  border-color: var(--route);
  color: var(--route);
}

.sidebar-box-label {
  font-size: var(--text-xs);
  color: var(--ink-3);
  margin-bottom: 0;
  line-height: 1.3;
}

.sidebar-box-value {
  font-size: var(--text-sm);
  color: var(--ink-1);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* Completion sidebar field: not a popup trigger, so reset the pointer cursor and
   hover style so it reads as a static field containing an interactive checkbox. */
.sidebar-complete-box {
  cursor: default;
}

.sidebar-complete-box:hover {
  background: var(--paper-1);
  border-color: var(--line-2);
}

/* Reset the title-alignment margin-top on the toggle when used in the sidebar box
   (the original offset assumes the checkbox sits beside a 1.25rem heading). */
.sidebar-complete-box .card-check-static {
  margin-top: 0;
}

/* Status label next to the checkbox in the sidebar value row. */
.sidebar-complete-status {
  font-size: var(--text-sm);
  color: var(--ink-2);
}

/* Labels chip row above the description (no header). The `+` add control leads
   so the popover stays anchored as chips are added/removed. */
.label-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

/* Reserve a label chip's height on the labels line (chips are taller than the
   bare "Labels +" button) so adding the first label doesn't push the
   description down. */
.task-labels {
  min-height: 1.625rem;
}

/* Toasts (notifications + copy feedback), bottom-right. */
.toast-container {
  z-index: 1090;
}

.issuebear-toast {
  background: var(--paper-2);
  border: 1px solid var(--line-1);
  border-radius: var(--radius-md);
  box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.15);
}

.issuebear-toast-clickable {
  cursor: pointer;
}

/* Danger variant: a left-accent so a failed write (a save that couldn't reach
   the server, ISS-91; a failed bulk edit) reads as a problem, not a notice. */
.issuebear-toast-danger {
  border-left: 3px solid var(--danger);
}

.issuebear-toast-title {
  font-weight: 600;
  color: var(--ink-1);
}

.issuebear-toast-text {
  color: var(--ink-2);
  font-size: var(--text-sm, 0.875rem);
  margin-top: 2px;
}

/* Keyboard focus for pick-list rows. */
.popmenu-item:focus-visible,
.label-toggle:focus-visible {
  outline: 2px solid var(--route);
  outline-offset: -2px;
  background: rgba(var(--bs-primary-rgb), 0.1);
}

/* Board filter bar: a full-width wrapping row of filter pills sitting between the
   board title and the columns (a sibling of, not inside, the kanban flex row). */
.board-filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 1rem 0.75rem;
}

.filter-pill {
  display: inline-flex;
}

/* Pills are rounded, quiet by default, and signal their own state: hover and the
   open dropdown lift slightly; an applied filter takes the route accent. We set
   focus/active explicitly so the bare .btn never shows Bootstrap's default blue
   focus ring or grey mouse-down flash. */
.filter-pill>.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px solid var(--line-1);
  border-radius: 999px;
  background: var(--paper-2);
  color: var(--ink-1);
  font-size: var(--text-sm);
  font-weight: 500;
  padding: 0.2rem 0.7rem;
  box-shadow: none;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.filter-pill>.btn::after {
  content: "";
  width: 0;
  height: 0;
  border-left: 0.28em solid transparent;
  border-right: 0.28em solid transparent;
  border-top: 0.3em solid currentColor;
  opacity: 0.55;
}

.filter-pill>.btn:hover {
  background: var(--paper-sunk);
  border-color: var(--ink-2);
  color: var(--ink-1);
}

.filter-pill>.btn:focus,
.filter-pill>.btn:active,
.filter-pill.show>.btn {
  background: var(--paper-sunk);
  border-color: var(--route);
  color: var(--ink-1);
  box-shadow: none;
}

.filter-pill>.btn:focus-visible {
  outline: 2px solid var(--route);
  outline-offset: 1px;
}

/* Active filter = a SOLID route fill with on-route text. The bar sits on the
   board background, which may be a dark theme, so a translucent tint (route-soft)
   would let the dark show through and leave route-coloured text unreadable. A
   solid fill reads clearly on any board colour. */
.filter-pill.is-active>.btn,
.filter-pill.is-active.show>.btn {
  border-color: var(--route);
  color: var(--route-on);
  background: var(--route);
  font-weight: 600;
}

.filter-pill.is-active>.btn:hover {
  background: var(--route-hover);
  border-color: var(--route-hover);
  color: var(--route-on);
}

.filter-keyword {
  min-width: 15rem;
}

/* Reset-all: a quiet text button at the end of the bar. It sits directly on the
   board background (not a solid pill), so on a themed board it must take the
   board foreground colour to stay readable. */
.filter-reset {
  border: 0;
  background: none;
  color: var(--ink-2);
  font-size: var(--text-sm);
  text-decoration: underline;
  padding: 0.2rem 0.5rem;
}

.filter-reset:hover {
  color: var(--ink-1);
  background: none;
}

body.board-themed .filter-reset {
  color: var(--board-fg);
  opacity: 0.85;
}

body.board-themed .filter-reset:hover {
  color: var(--board-fg);
  opacity: 1;
}

.filter-reset:focus-visible {
  outline: 2px solid var(--route);
  outline-offset: 1px;
}

/* Global HTMX activity indicator: a small "Working…" pill in the bottom-right
   while a request is in flight. JS toggles .is-active (after a short delay so
   quick requests never flash). Sits just above the toast stack. */
.htmx-activity {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 1090;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.8rem;
  border-radius: 999px;
  background: var(--ink-1);
  color: #fff;
  font-size: var(--text-sm);
  box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.3);
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.htmx-activity.is-active {
  opacity: 1;
  transform: translateY(0);
}

.htmx-activity .spinner-border {
  width: 1rem;
  height: 1rem;
  border-width: 2px;
}

/* Realtime disconnect banner (ISS-65). Fixed across the top of the viewport
   while any SSE stream is down; the [hidden] attribute keeps it out of the
   layout otherwise. Sits above modals/menus (z-index 1100) so an outage is
   never obscured, and reuses the danger palette so it reads as a problem. */
.rt-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--danger);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 500;
  box-shadow: var(--shadow-1);
}

.rt-banner .spinner-border {
  width: 1rem;
  height: 1rem;
  border-width: 2px;
}

/* Table view — a grey "card" wrapper (like the board background) holding white
   rows that read as cards, matching the kanban surface colours. The wrapper
   fills the remaining height and scrolls internally on BOTH axes (the page
   itself never scrolls in board view), so its horizontal scrollbar stays
   visible at the bottom and a 1rem margin keeps it clear of the window edge. */
body.board-view #board {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

.board-table-wrap {
  flex: 1 1 auto;
  min-height: 0;
  margin: 0 1rem 1rem;
  padding: 0 var(--space-3) var(--space-2);
  background: var(--paper-sunk);
  border-radius: var(--radius-md);
  overflow: auto;
}

.board-table {
  /* width is set inline to the exact sum of the fixed column widths so columns
     never stretch or shrink with the column count. */
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0 var(--space-1);
}

/* Header sticks to the top of the scroll area; its grey fill matches the wrapper
   so scrolling rows pass cleanly underneath. */
.board-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--paper-sunk);
  color: var(--ink-2);
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: left;
  padding: var(--space-2);
  white-space: nowrap;
}

.board-th-check {
  width: 44px;
}

.board-th-sort {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  display: inline-flex;
  gap: .25rem;
  align-items: center;
}

.board-th-sort:hover {
  color: var(--ink-1);
}

.board-sort-ind {
  font-size: .75em;
}

/* Rows render as white cards floating on the grey wrapper. */
.board-table-row>td {
  background: var(--paper-1);
  padding: var(--space-2);
  vertical-align: middle;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  box-shadow: 0 1px 0 0 rgba(0, 0, 0, .04);
}

.board-table-row>td:first-child {
  border-top-left-radius: var(--radius-sm);
  border-bottom-left-radius: var(--radius-sm);
}

.board-table-row>td:last-child {
  border-top-right-radius: var(--radius-sm);
  border-bottom-right-radius: var(--radius-sm);
}

.board-table-row {
  cursor: pointer;
}

.board-table-row:hover>td {
  background: color-mix(in srgb, var(--paper-1) 92%, var(--ink-1));
}

/* Completed rows: muted text only — NO strikethrough. */
.board-table-row.is-complete>td {
  color: var(--ink-3);
}

.board-td-check {
  width: 44px;
  text-align: center;
  padding: 0 !important;
}

/* Member columns show a centered avatar (no name) — header label centered too.
   The header selector is qualified to outrank `.board-table thead th`. */
.board-td-assignee,
.board-td-requester {
  text-align: center;
}

.board-table thead th.board-th-assignee,
.board-table thead th.board-th-requester {
  text-align: center;
}

/* The completion checkbox sits static (always visible) in the table. */
.board-td-check .card-check {
  position: static;
  opacity: 1;
}

.board-td-labels {
  white-space: normal;
}

/* Group section header sits on the grey wrapper. */
.board-table-group-row th {
  background: transparent;
  color: var(--ink-2);
  font-weight: 600;
  padding: var(--space-2) var(--space-2) var(--space-1);
}

.board-group-toggle {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  display: inline-flex;
  gap: .4rem;
  align-items: center;
}

.board-group-caret {
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .1s ease;
}

.board-group-toggle[aria-expanded="true"] .board-group-caret {
  transform: rotate(90deg);
}

/* Column picker: cap the list so it scrolls and the Apply/Revert actions stay
   visible (the popmenu-form itself has no max-height). */
.board-columns-list {
  max-height: 16rem;
  overflow-y: auto;
}

/* The Columns/Group popovers live inside .board-topbar, which paints its buttons
   with light (board) text for the dark bar. Inside the white popover that would
   be invisible, so restore normal ink colours for outline buttons there. */
.board-topbar .dropdown-menu.popmenu-form .btn-outline-secondary,
.board-topbar .dropdown-menu.popmenu .btn-outline-secondary {
  color: var(--ink-1);
  border-color: var(--line-1);
}

/* Column-picker drag handle. */
.board-column-row {
  display: flex;
  align-items: center;
  gap: .5rem;
}

.col-drag-handle {
  cursor: grab;
  color: var(--ink-3);
  letter-spacing: -2px;
  user-select: none;
}

/* Step meanings: four stacked category drop-lists. Each list keeps a minimum
   height so an empty category is still a visible, droppable target. */
.step-meaning-group {
  margin-bottom: .5rem;
}

.step-meaning-header {
  font-size: .8rem;
  font-weight: 600;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: .03em;
  margin: .25rem 0;
}

.step-meaning-list {
  min-height: 2.25rem;
  padding: 0;
  border: 1px dashed var(--line, #d9d9d9);
  border-radius: .375rem;
}

.step-meaning-list:empty::after,
.step-meaning-list .step-meaning-row {
  /* keep rows flush inside the dashed well */
}

.step-meaning-row {
  cursor: default;
}

/* View switcher: inline SVG so the button matches the height of the sibling
   toolbar buttons (a block SVG collapses to the 16px icon, losing the text
   line-box). The active item keeps a single visible border (no extra inset
   ring), raised above its neighbour so its border isn't clipped by the group. */
.view-switcher svg {
  display: block;
}

.view-switcher .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  /* match the sibling btn-sm toolbar buttons exactly */
  padding-top: 0;
  padding-bottom: 0;
  border: 1px solid var(--board-fg, var(--bs-border-color));
}

.view-switcher .btn.btn-primary {
  position: relative;
  z-index: 1;
}

/* ==========================================================================
   Dashboard: up-next hero, queue buckets, highlight cards
   ========================================================================== */
.up-next-card {
  display: block;
  padding: var(--space-4) var(--space-5);
  text-decoration: none;
  color: inherit;
}

.up-next-card:hover {
  border-color: var(--line-3);
}

.up-next-title {
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--ink-1);
}

.up-next-meta {
  margin-top: var(--space-1);
  font-size: var(--text-base);
  color: var(--ink-2);
}

.dashboard-section-head {
  margin-top: var(--space-5);
  margin-bottom: var(--space-3);
}

.highlight-row {
  display: flex;
  gap: var(--space-3);
  overflow-x: auto;
  padding-bottom: var(--space-1);
}

.highlight-row:focus-visible {
  outline: 2px solid var(--route);
  outline-offset: 1px;
}

.highlight-card {
  flex: 0 0 16rem;
  display: block;
  padding: var(--space-3) var(--space-4);
  text-decoration: none;
  color: inherit;
}

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

.highlight-kind {
  font-size: var(--text-xs);
  color: var(--ink-3);
}

.highlight-title {
  font-weight: 500;
  margin-top: var(--space-1);
  color: var(--ink-1);
}

.highlight-meta {
  font-size: var(--text-sm);
  margin-top: var(--space-1);
}

.activity-feed-main {
  min-width: 0;
}

.activity-feed-meta {
  font-size: var(--text-sm);
}

@media (max-width: 576px) {
  .highlight-card {
    flex-basis: 80vw;
  }
}

/* Stat strip: four count tiles drilling through to /tasks. Wraps on mobile. */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.stat-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: var(--paper-1);
  text-decoration: none;
  color: inherit;
}

.stat-tile:hover {
  border-color: var(--line-3);
}

.stat-tile-num {
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--ink-1);
  font-variant-numeric: tabular-nums;
}

.stat-tile-num.stat-overdue {
  color: var(--danger);
}

.stat-tile-label {
  font-size: var(--text-sm);
  color: var(--ink-3);
}

@media (max-width: 576px) {
  .stat-strip {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Shared two-line task row (dashboard queue / needs-attention / tasks list).
   Line 1 is a flex row (fixed-width ref · flex name · right-aligned priority
   pill); line 2 is a fixed-column grid so every datum aligns vertically down a
   list and empty cells keep their slot. The whole row is the deep-link. */
.task-row {
  display: block;
  padding: var(--space-2) var(--space-4);
  text-decoration: none;
  color: var(--ink-1);
  border-bottom: 1px solid var(--line-2);
}

.plain-list>li:last-child .task-row {
  border-bottom: 0;
}

.task-row:hover {
  background: var(--paper-sunk);
}

.task-row-line {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.task-row-ref {
  flex: 0 0 auto;
  width: 64px;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.task-row-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Labelled priority pill: coloured dot + name, so priority is legible without
   relying on colour alone. The inner .priority-icon keeps its solid dot colour;
   the pill itself carries a soft tint of the same hue. */
.priority-pill {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  padding: 1px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink-2);
  background: var(--paper-sunk);
}

.priority-pill .priority-icon {
  vertical-align: middle;
}

.priority-pill.priority-urgent {
  background: var(--danger-soft);
  color: var(--danger);
}

.priority-pill.priority-high {
  background: var(--warn-soft);
  color: var(--warn);
}

.priority-pill.priority-normal {
  background: var(--route-soft);
  color: var(--route);
}

.priority-pill.priority-low {
  background: var(--paper-sunk);
  color: var(--ink-2);
}

/* Line 2: a FIXED-column meta grid so every datum sits at the same x on every
   row — assignee (under the reference) · board (under the title) · due · contact
   · comments · updated. Fixed widths (not fr) mean a long value in one row's
   cell can't shift the columns on another row; the columns are left-packed and
   `updated` is last so its varying width never moves the cells before it. */
.task-row-meta {
  display: grid;
  grid-template-columns: 64px 150px 90px 190px 52px 1fr;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
  font-size: var(--text-xs);
}

.task-row-cell {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* `updated` sits in the trailing flexible column, right-aligned, so it lands at
   the far right under the priority pill — its varying width never shifts the
   fixed cells before it. */
.task-row-updated {
  justify-self: end;
}

.task-row-contact .contact-person {
  color: var(--ink-3);
}

.task-row-assignee .avatar,
.task-row-assignee .avatar-initials {
  width: 1.25rem;
  height: 1.25rem;
  font-size: var(--text-xs);
}

/* On phones the rigid grid relaxes: line 2 wraps inline instead of holding
   columns, so nothing overflows horizontally. */
@media (max-width: 576px) {
  .task-row-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    padding-left: 0;
  }

  .task-row-cell:empty {
    display: none;
  }

  .task-row-updated {
    text-align: left;
  }
}

/* Section "See all" drill-through links (needs-attention + each queue bucket). */
.dashboard-section-head--row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.dashboard-see-all-inline {
  flex: 0 0 auto;
  font-size: var(--text-sm);
}

/* /tasks: filter bar + list. The bar reuses the board's .board-filter-bar /
   .filter-pill components verbatim; on the /tasks page it sits flush with the
   content column rather than the board's left/right inset. */
.content-page .board-filter-bar {
  padding: 0 0 var(--space-4);
}

.tasks-count {
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
}

/* Phones (≤576px): pills already wrap (flex-wrap on .board-filter-bar). Keep the
   keyword search input from forcing horizontal page scroll by letting it shrink
   to the available width instead of its 15rem desktop min. */
@media (max-width: 576px) {
  .board-filter-bar {
    gap: var(--space-2);
  }

  .filter-keyword {
    min-width: 0;
    width: 100%;
  }
}

/* ==========================================================================
   Projects overview: section heads, card stats, three-dot menus
   ========================================================================== */
.project-section {
  margin-top: 2rem;
}

.project-section-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.board-card-stats {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  font-size: var(--text-sm);
  color: var(--ink-3);
  margin-top: var(--space-1);
}

.stat-overdue {
  color: var(--danger);
  font-weight: 500;
}

.board-card-menu {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  z-index: 2;
}

.board-card-menu .btn-ghost {
  background: var(--paper-1);
  box-shadow: var(--shadow-1);
}

/* The project-line action button reads as a real button — the card menu sits on
   a coloured cover and has its own treatment, but this one floats on the page. */
.project-section-head .dropdown {
  flex: 0 0 auto;
}

.project-section-head .btn-ghost {
  border: 1px solid var(--line-1);
  background: var(--paper-2);
  color: var(--ink-2);
}

.project-section-head .btn-ghost:hover {
  background: var(--paper-sunk);
  color: var(--ink-1);
}

.popmenu-item-danger,
.popmenu-item-danger:hover {
  color: var(--danger);
}

/* ==========================================================================
   Onboarding wizard shell (auth/onboarding, first org, first project).
   A minimal branded surround: quiet background, centred brand mark, step
   progress, and a single card — no app navbar, no auth split-panel.
   ========================================================================== */
.onboard-body {
  min-height: 100vh;
  background: var(--paper-sunk);
}

.onboard-shell {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  max-width: 560px;
  margin: 0 auto;
  padding: 3rem 1rem 4rem;
}

.onboard-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
}

.onboard-logo {
  width: 40px;
  height: 40px;
}

.onboard-wordmark {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--ink-1);
}

.onboard-card {
  background: var(--paper-1);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
  padding: 2rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.onboard-card h1 {
  font-size: 1.5rem;
  font-weight: 600;
  margin-bottom: 0.75rem;
}

/* The shell centres the step indicator between the brand and the card. */
.onboard-shell .onboard-steps {
  justify-content: center;
  margin-bottom: 0;
}

/* Onboarding wizard progress: three fixed steps; the current one is
   route-filled, completed ones quiet. */
.onboard-steps {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  list-style: none;
  padding: 0;
  margin: 0 0 1.25rem;
  font-size: var(--text-sm);
  color: var(--ink-3);
}

.onboard-steps li {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.onboard-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  background: var(--paper-sunk);
  font-weight: 600;
}

.onboard-steps li.is-current {
  color: var(--ink-1);
  font-weight: 600;
}

.onboard-steps li.is-current .onboard-step-num {
  background: var(--route);
  color: var(--route-on);
}

.onboard-steps li.is-done {
  color: var(--ink-2);
}

/* Utility: render stored newlines as line breaks (e.g. postal addresses) */
.pre-line {
  white-space: pre-line;
}

/* intl-tel-input: the wrapper is inline-block by default, which collapses the
   .form-control inside it — stretch it to fill the form row. */
.iti {
  width: 100%;
}

/* Contact overview: full-width brand-colour bar at the top of the page
   (rendered only when the contact has a colour — see contacts/detail.html). */
.contact-color-bar {
  height: 15px;
  border-radius: var(--radius-sm);
  margin-bottom: 1rem;
}

/* Titled content section: the heading sits ABOVE the card, not inside it.
   Used on the contact overview so each block reads as a labelled section
   rather than a card with an embedded header. */
.content-section {
  margin-bottom: var(--space-5);
}

.content-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.content-section-head h2 {
  margin: 0;
}

/* Contact details laid out across the full page width: label/value pairs flow
   into as many columns as fit rather than stacking in one tall column. */
.detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-4) var(--space-5);
  padding: var(--space-5);
  margin: 0;
}

.detail-grid>div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.detail-grid dt {
  font-size: var(--text-sm);
  color: var(--ink-3);
}

.detail-grid dd {
  margin: 0;
  color: var(--ink-1);
  overflow-wrap: anywhere;
}