/* Reusable components: buttons, form controls, state panels, notices, stats, badges. */

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* 44px min target: comfortably operable by touch and by imprecise pointing. */
  min-height: 2.25rem;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-raised);
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: background var(--transition), border-color var(--transition);
}

.btn:hover { background: var(--surface-sunken); }

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.btn--primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-contrast);
}

.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
}

.btn--ghost:hover {
  background: var(--surface-sunken);
  color: var(--text);
}

.btn--block {
  width: 100%;
}

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* ---------- form controls ---------- */

.picker__select {
  min-height: 2.25rem;
  max-width: 16rem;
  padding: var(--space-1) var(--space-3);
  background: var(--surface-raised);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-size: var(--text-sm);
}

.picker__select--compact { max-width: 9rem; }

/* ---------- state panels ---------- */

.state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 40vh;
  padding: var(--space-6) var(--space-4);
  text-align: center;
}

.state__title { margin: 0; }

.state__text {
  margin: 0;
  color: var(--text-muted);
  max-width: 46ch;
}

.state__detail {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-subtle);
  font-family: var(--font-mono);
}

.state__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: min(20rem, 100%);
}

/*
  Error panels are visually distinct from empty panels on purpose. An empty state invites
  you to create something; an error state tells you something failed. They must never be
  mistaken for one another.
*/
.state--error {
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--color-danger) 6%, var(--surface));
}

.state--forbidden {
  border-color: var(--color-warning);
  background: color-mix(in srgb, var(--color-warning) 6%, var(--surface));
}

.state--auth {
  gap: var(--space-4);
}

/* ---------- notices ---------- */

.notice {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border-left: 3px solid var(--color-info);
  background: var(--surface);
  margin-bottom: var(--space-5);
}

.notice--warn {
  border-left-color: var(--color-warning);
  background: color-mix(in srgb, var(--color-warning) 8%, var(--surface));
}

.notice__text {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ---------- stats ---------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-4);
  margin: 0;
}

.stat {
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.stat__label {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
}

.stat__value {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.stat__detail {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ---------- badges ---------- */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
  white-space: nowrap;
}

.badge--admin {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

.badge--version { font-family: var(--font-mono); }

/* ---------- dialogs ----------
   There was no modal CSS at all, which is why the first dialog rendered as a form partway
   down the page. An overlay needs to be taken out of flow explicitly; nothing about
   role="dialog" does that on its own. */

.modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: var(--overlay);
  overflow-y: auto;
}

.modal__panel {
  position: relative;
  width: 100%;
  max-width: 30rem;
  max-height: calc(100vh - var(--space-8));
  overflow-y: auto;
  padding: var(--space-5);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.modal__panel--wide { max-width: 46rem; }
.modal__panel--full { max-width: 64rem; }

.modal__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.modal__title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
}

.modal__close {
  flex: none;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  font-size: var(--text-lg);
  line-height: 1;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.modal__close:hover { background: var(--surface-sunken); color: var(--text); }

.modal__description {
  margin: 0 0 var(--space-4);
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.modal__form { display: flex; flex-direction: column; gap: var(--space-4); }

.modal__error {
  padding: var(--space-3);
  color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-danger) 35%, transparent);
  border-radius: var(--radius);
  font-size: var(--text-sm);
}

.modal__error[hidden] { display: none; }

.modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--border);
}

/* The page behind a dialog is inert. Without this the underlying call-to-action stays
   clickable and keeps competing for attention with the dialog on top of it. */
body.has-modal { overflow: hidden; }
[inert] { pointer-events: none; user-select: none; }
[inert], [inert] * { opacity: 0.55; }

/* ---------- form fields ---------- */

.field { display: flex; flex-direction: column; gap: var(--space-2); }

/* A CHECKBOX FIELD: control on the left, then a block that reads label-then-help.
   No `order` anywhere — the reading order is the DOM order, which is what stops helper
   text from being re-sorted in front of the label it describes. */
.field--check { flex-direction: row; align-items: flex-start; gap: var(--space-3); }
.field--check .field__check { margin-top: 0.15rem; flex: none; }
.field__check-body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
/* The label is the field's NAME, so it carries the field weight; the help is secondary and
   is typographically subordinate to it rather than merely below it. */
.field--check .field__label { margin: 0; cursor: pointer; }
.field--check .field__help { margin: 0; }

.field__label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text);
}

.field__input {
  width: 100%;
  padding: var(--space-3);
  font: inherit;
  font-size: var(--text-base);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  transition: border-color var(--transition), box-shadow var(--transition);
}

.field__input:focus-visible {
  outline: none;
  border-color: var(--color-info);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-info) 25%, transparent);
}

.field__input:disabled { background: var(--surface-sunken); color: var(--text-subtle); cursor: not-allowed; }

textarea.field__input { resize: vertical; min-height: 4.5rem; line-height: var(--leading-normal); }

.field__check { width: 1.1rem; height: 1.1rem; accent-color: var(--color-info); }

.field__help {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: var(--leading-normal);
}

.field__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }

@media (max-width: 40rem) {
  .modal { padding: 0; align-items: stretch; }
  .modal__panel {
    max-width: none;
    max-height: none;
    min-height: 100vh;
    border: 0;
    border-radius: 0;
  }
  .field__row { grid-template-columns: 1fr; }
  .modal__actions { flex-direction: column-reverse; }
  .modal__actions .btn { width: 100%; }
}
.btn--danger {
  color: var(--text-inverse);
  background: var(--color-danger);
  border-color: var(--color-danger);
}

.btn--danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-danger) 85%, black);
  border-color: color-mix(in srgb, var(--color-danger) 85%, black);
}
.picker__action {
  flex: none;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  font-size: var(--text-lg);
  line-height: 1;
  color: var(--text-muted);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.picker__action:hover { color: var(--text); border-color: var(--color-info); }
/* ---------- views ---------- */

.view { display: flex; flex-direction: column; gap: var(--space-4); }

/* ---------- toolbar ---------- */

.toolbar {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.toolbar__row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3); }
.toolbar__row--actions { align-items: center; justify-content: flex-end; border-top: 1px solid var(--border); padding-top: var(--space-3); }
.toolbar__group { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.toolbar__group--toggle { flex-direction: row; align-items: center; gap: var(--space-2); }
.toolbar__label { font-size: var(--text-xs); font-weight: var(--weight-medium); color: var(--text-muted); }
.toolbar__count { margin: 0; margin-right: auto; font-size: var(--text-sm); color: var(--text-muted); }

.toolbar__search {
  min-width: 14rem;
  padding: var(--space-2) var(--space-3);
  font: inherit;
  color: var(--text);
  background: var(--surface-sunken);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}

.field__input--inline { padding: var(--space-2) var(--space-3); font-size: var(--text-sm); width: auto; }
.field__input--multi { min-height: 6rem; }

/* ---------- table ---------- */

.table__scroll {
  overflow-x: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }

.table th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: var(--space-3);
  text-align: left;
  white-space: nowrap;
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border);
}

.table td { padding: var(--space-3); border-bottom: 1px solid var(--border); vertical-align: top; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--surface-sunken); }

.table__sort {
  font: inherit;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.table__sort:hover { color: var(--text); }
.table__sort--active { color: var(--text); }
.table__sortmark { margin-left: var(--space-1); font-size: 0.7em; color: var(--color-info); }

.table__row--done .table__titletext { text-decoration: line-through; color: var(--text-muted); }
.table__row--overdue .cell--overdue { color: var(--color-danger); font-weight: var(--weight-medium); }

.table__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font: inherit;
  font-weight: var(--weight-medium);
  text-align: left;
  color: var(--text);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.table__title:hover .table__titletext { color: var(--color-info); text-decoration: underline; }

.cell--check { width: 1px; }
.cell--seq { color: var(--text-subtle); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cell--title { min-width: 16rem; }
.cell--owner, .cell--due { white-space: nowrap; color: var(--text-muted); }
.cell--progress { min-width: 7rem; }
.cell--actions { white-space: nowrap; }
.cell__sub { display: block; margin-top: var(--space-1); font-size: var(--text-xs); color: var(--text-subtle); }
.cell__tags { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); margin-top: var(--space-1); }

/* ---------- chips ---------- */

.chip {
  display: inline-block;
  padding: 0.1rem var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  color: var(--text-muted);
}

.chip--tag { background: transparent; }
.chip--muted { color: var(--text-subtle); }
/* The priority chip's id-keyed colouring is gone — see the .prio block below. A workspace
   that renamed its priorities got nothing from these rules, silently. */

/* ---------- progress ---------- */

.progress {
  position: relative;
  height: 1.15rem;
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

/* DYNAMIC VALUES ARRIVE AS CUSTOM PROPERTIES, set through the CSSOM rather than as a style
   attribute — a strict style-src blocks the attribute and has never applied to the CSSOM.
   The RULE stays here, in CSS, where a rule belongs. */
.progress__fill { height: 100%; width: var(--fill, 0%); background: var(--color-success); transition: width var(--transition); }

.progress__text {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* ---------- bulk actions ---------- */

.bulkbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: color-mix(in srgb, var(--color-info) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--color-info) 35%, transparent);
  border-radius: var(--radius);
}

.bulkbar__count { margin: 0; margin-right: auto; font-weight: var(--weight-medium); }

/* ---------- notices ---------- */

.notice {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: var(--text-sm);
}

.notice p { margin: 0; margin-right: auto; }
.notice--success { border-color: color-mix(in srgb, var(--color-success) 40%, transparent); background: color-mix(in srgb, var(--color-success) 10%, var(--surface)); }
.notice--error { border-color: color-mix(in srgb, var(--color-danger) 40%, transparent); background: color-mix(in srgb, var(--color-danger) 10%, var(--surface)); }
.notice--warn { border-color: color-mix(in srgb, var(--color-warning) 40%, transparent); background: color-mix(in srgb, var(--color-warning) 10%, var(--surface)); }
.notice__dismiss { background: none; border: 0; font-size: var(--text-lg); line-height: 1; color: var(--text-muted); cursor: pointer; }

.btn--sm { padding: var(--space-1) var(--space-2); font-size: var(--text-xs); }

/* ---------- comments and details ---------- */

.comments__section { margin-top: var(--space-5); padding-top: var(--space-4); border-top: 1px solid var(--border); }
.comments__title { margin: 0 0 var(--space-3); font-size: var(--text-sm); font-weight: var(--weight-bold); }
.comments { list-style: none; margin: 0 0 var(--space-3); padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.comments__item { padding: var(--space-3); background: var(--surface-sunken); border-radius: var(--radius); }
.comments__meta { margin: 0 0 var(--space-1); font-size: var(--text-xs); color: var(--text-subtle); }
.comments__text { margin: 0; white-space: pre-wrap; }
.comments__empty { font-size: var(--text-sm); color: var(--text-subtle); }
.comments__compose { display: flex; gap: var(--space-2); }
.comments__compose .field__input { flex: 1; }

.modal__danger { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--border); }

.detail { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-4); font-size: var(--text-sm); }
.detail dt { color: var(--text-muted); }
.detail dd { margin: 0; }
.detail__description { margin: 0 0 var(--space-4); white-space: pre-wrap; }

@media (max-width: 48rem) {
  .toolbar__row { flex-direction: column; align-items: stretch; }
  .toolbar__search { min-width: 0; width: 100%; }
  .bulkbar { flex-direction: column; align-items: stretch; }
}
/* ---------- dashboard ---------- */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--space-3); }

.stat {
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius);
}

.stat--wide { grid-column: 1 / -1; }
.stat--success { border-left-color: var(--color-success); }
.stat--danger { border-left-color: var(--color-danger); }
.stat--warning { border-left-color: var(--color-warning); }

.stat__value { margin: 0; font-size: var(--text-2xl); font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; line-height: 1; }
.stat__label { margin: var(--space-2) 0 0; font-size: var(--text-sm); font-weight: var(--weight-medium); }
.stat__detail { margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--text-muted); }

.progress--lg { height: 1.75rem; margin-top: var(--space-3); }
.progress--sm { height: 0.35rem; margin-top: var(--space-2); border-radius: var(--radius-sm); }
.progress--sm .progress__text { display: none; }

.dash__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: var(--space-4); }

.card {
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.card--wide { grid-column: 1 / -1; }
.card__title { margin: 0 0 var(--space-3); font-size: var(--text-sm); font-weight: var(--weight-bold); }
.card__hint { margin: calc(var(--space-3) * -1) 0 var(--space-3); font-size: var(--text-xs); color: var(--text-subtle); }
.card__empty { margin: 0; font-size: var(--text-sm); color: var(--text-subtle); }

.bars { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.bars__row { display: grid; grid-template-columns: minmax(6rem, 9rem) 1fr auto; align-items: center; gap: var(--space-3); font-size: var(--text-sm); }
.bars__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-muted); }
.bars__track { height: 0.6rem; background: var(--surface-sunken); border-radius: var(--radius-pill); overflow: hidden; }
.bars__fill { display: block; height: 100%; width: var(--fill, 0%); background: var(--bar, var(--color-info)); border-radius: var(--radius-pill); }
.bars__value { font-variant-numeric: tabular-nums; font-size: var(--text-xs); color: var(--text-muted); white-space: nowrap; }

.attention { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.attention__item { display: flex; align-items: center; gap: var(--space-3); font-size: var(--text-sm); }
.attention__link { flex: 1; text-align: left; font: inherit; color: var(--text); background: none; border: 0; padding: 0; cursor: pointer; }
.attention__link:hover { color: var(--color-info); text-decoration: underline; }
.chip--overdue { color: var(--color-danger); border-color: currentColor; }

/* ---------- board ---------- */

.board { display: flex; gap: var(--space-4); overflow-x: auto; padding-bottom: var(--space-3); align-items: flex-start; }

.board__column {
  flex: 0 0 17rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.board__head { display: flex; align-items: center; gap: var(--space-2); }
.board__swatch { width: 0.65rem; height: 0.65rem; border-radius: 50%; flex: none; background: var(--swatch, var(--border-strong)); }
.board__title { margin: 0; font-size: var(--text-sm); font-weight: var(--weight-bold); flex: 1; }
.board__count { font-size: var(--text-xs); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.board__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); min-height: 3rem; border-radius: var(--radius); }
.board__list--over { outline: 2px dashed var(--color-info); outline-offset: 3px; }
.board__empty { padding: var(--space-3); font-size: var(--text-xs); color: var(--text-subtle); text-align: center; }

/* COMPACT BY DEFAULT. Reported: "THE CARDS ARE TOO TALL. There is too much vertical
   whitespace for the amount of information displayed." The card is a flex column now with a
   small gap, so its rows sit against each other instead of each carrying its own margin. */
.card--task {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
/* The title and the priority on one line: the title takes the room, the chip keeps its size. */
.card__head { display: flex; align-items: flex-start; gap: var(--space-2); }
.card__head .card__open { flex: 1; min-width: 0; }
.card__head .chip--priority { flex: none; }
/* Progress and the status control share the last line. */
.card__foot { display: flex; align-items: center; gap: var(--space-2); }
.card__foot .progress { flex: 1; min-width: 0; }
.card--task[draggable="true"] { cursor: grab; }
.card--archived { opacity: 0.6; }
.card__open { font: inherit; font-weight: var(--weight-medium); text-align: left; color: var(--text); background: none; border: 0; padding: 0; cursor: pointer; width: 100%; }
.card__open:hover { color: var(--color-info); }
/* The margins are gone: the card's own gap separates its rows, so nothing is spaced twice. */
.card__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
/* SIZED TO ITS CONTENT, not to the card. `width: 100%` made a select the widest and loudest
   thing on every card, in a column that already says what the status is. It keeps its full
   padding — a smaller target would be a real cost, and density is not that. */
.card__status {
  flex: none;
  max-width: 100%;
  padding: var(--space-1) var(--space-2);
  font: inherit;
  font-size: var(--text-xs);
  color: var(--text-muted);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* ---------- matrix ---------- */

.matrix { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
.matrix__note { margin: 0; padding: var(--space-3); font-size: var(--text-sm); color: var(--text-muted); background: var(--surface-sunken); border-radius: var(--radius); }

.matrix__cell {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 12rem;
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--border-strong);
  border-radius: var(--radius);
}

.matrix__cell--danger { border-top-color: var(--color-danger); }
.matrix__cell--info { border-top-color: var(--color-info); }
.matrix__cell--warning { border-top-color: var(--color-warning); }
.matrix__cell--over { outline: 2px dashed var(--color-info); outline-offset: 3px; }

.matrix__head { display: grid; grid-template-columns: 1fr auto; gap: var(--space-1); }
.matrix__title { margin: 0; font-size: var(--text-base); font-weight: var(--weight-bold); }
.matrix__count { font-size: var(--text-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.matrix__subtitle { grid-column: 1 / -1; margin: 0; font-size: var(--text-xs); color: var(--text-muted); }
.matrix__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); flex: 1; }
.matrix__empty { font-size: var(--text-xs); color: var(--text-subtle); }
.matrix__advice { margin: 0; padding-top: var(--space-2); border-top: 1px solid var(--border); font-size: var(--text-xs); color: var(--text-subtle); }

@media (max-width: 52rem) {
  .matrix { grid-template-columns: 1fr; }
  .board__column { flex-basis: 14rem; }
}
/* ---------- calendar ---------- */

.cal__header { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.cal__title { margin: 0; font-size: var(--text-lg); min-width: 10rem; }
.cal__count { margin: 0; margin-left: auto; font-size: var(--text-sm); color: var(--text-muted); }

.cal { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface); }
.cal__weekdays { display: grid; grid-template-columns: repeat(7, 1fr); background: var(--surface-sunken); border-bottom: 1px solid var(--border); }
.cal__weekday { padding: var(--space-2); font-size: var(--text-xs); font-weight: var(--weight-bold); text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); text-align: center; }

.cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); }

.cal__day {
  min-height: 6.5rem;
  padding: var(--space-2);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.cal__day:nth-child(7n) { border-right: 0; }
.cal__day--outside { background: var(--surface-sunken); }
.cal__day--outside .cal__daynum { color: var(--text-subtle); }
.cal__day--today { background: color-mix(in srgb, var(--color-info) 8%, var(--surface)); }
.cal__day--today .cal__daynum { color: var(--color-info); font-weight: var(--weight-bold); }
.cal__day--over { outline: 2px dashed var(--color-info); outline-offset: -2px; }

.cal__daynum { font-size: var(--text-xs); font-variant-numeric: tabular-nums; color: var(--text-muted); }
.cal__tasks, .cal__undated { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }

.cal__task {
  padding: 0.15rem var(--space-2);
  background: var(--surface-sunken);
  border-left: 2px solid var(--color-info);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
}

.cal__task--done { opacity: 0.6; border-left-color: var(--color-success); }
.cal__task--done .cal__tasklink { text-decoration: line-through; }
.cal__tasklink { display: block; width: 100%; font: inherit; text-align: left; color: var(--text); background: none; border: 0; padding: 0; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal__tasklink:hover { color: var(--color-info); }
.cal__date { width: 100%; margin-top: var(--space-1); font: inherit; font-size: var(--text-xs); color: var(--text-muted); background: transparent; border: 0; border-top: 1px solid var(--border); }
.cal__undated .cal__task { border-left-color: var(--text-subtle); }

/* ---------- timeline ---------- */

.gantt__range { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
.gantt__scroll { overflow-x: auto; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.gantt { min-width: 44rem; }

.gantt__head { display: grid; grid-template-columns: 14rem 1fr; align-items: center; padding: var(--space-2) var(--space-3); background: var(--surface-sunken); border-bottom: 1px solid var(--border); }
.gantt__headlabel { font-size: var(--text-xs); font-weight: var(--weight-bold); text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); }
.gantt__months { position: relative; height: 1.1rem; }
.gantt__month { position: absolute; left: var(--left, 0%); font-size: var(--text-xs); color: var(--text-subtle); white-space: nowrap; }

.gantt__rows { list-style: none; margin: 0; padding: 0; }
.gantt__row { display: grid; grid-template-columns: 14rem 1fr; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); }
.gantt__row:last-child { border-bottom: 0; }
.gantt__row--child .gantt__label { padding-left: var(--space-4); }
.gantt__label { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.gantt__link { flex: 1; min-width: 0; font: inherit; font-size: var(--text-sm); text-align: left; color: var(--text); background: none; border: 0; padding: 0; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gantt__link:hover { color: var(--color-info); }

.gantt__track { position: relative; height: 1.4rem; background: var(--surface-sunken); border-radius: var(--radius-sm); }
.gantt__bar { position: absolute; top: 0.15rem; left: var(--left, 0%); width: var(--width, 0%); height: 1.1rem; min-width: 2px; background: var(--color-info); border-radius: var(--radius-sm); overflow: hidden; }
.gantt__bar--done { background: var(--color-success); }
.gantt__bar--late { background: var(--color-danger); }
.gantt__bar--estimated { background: repeating-linear-gradient(45deg, var(--color-info), var(--color-info) 4px, transparent 4px, transparent 8px); border: 1px solid var(--color-info); }
.gantt__progress { height: 100%; width: var(--fill, 0%); background: rgb(255 255 255 / 0.45); }
.gantt__today { position: absolute; top: -0.2rem; bottom: -0.2rem; left: var(--left, 0%); width: 2px; background: var(--color-warning); }

.gantt__legend { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-4); font-size: var(--text-xs); color: var(--text-muted); }
.gantt__legend li { display: flex; align-items: center; gap: var(--space-2); }
.gantt__key { width: 1.4rem; height: 0.7rem; border-radius: var(--radius-sm); }
.gantt__key--bar { background: var(--color-info); }
.gantt__key--estimated { background: repeating-linear-gradient(45deg, var(--color-info), var(--color-info) 3px, transparent 3px, transparent 6px); border: 1px solid var(--color-info); }
.gantt__key--late { background: var(--color-danger); }
.gantt__key--done { background: var(--color-success); }

@media (max-width: 48rem) {
  .cal__day { min-height: 4.5rem; }
  .gantt__row, .gantt__head { grid-template-columns: 8rem 1fr; }
}
/* ---------- settings ---------- */

.tabs { display: flex; flex-wrap: wrap; gap: var(--space-1); border-bottom: 1px solid var(--border); }
.tabs__tab { padding: var(--space-3) var(--space-4); font: inherit; font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-muted); background: none; border: 0; border-bottom: 2px solid transparent; cursor: pointer; }
.tabs__tab:hover { color: var(--text); }
.tabs__tab--active { color: var(--color-info); border-bottom-color: var(--color-info); }
.tabs__panel { padding-top: var(--space-4); }

.panel { display: flex; flex-direction: column; gap: var(--space-4); }
.panel__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.panel__head .card__hint { margin: 0; margin-right: auto; }

.rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.rows__row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.rows__row--muted { opacity: 0.65; }
.rows__main { flex: 1; min-width: 12rem; }
.rows__title { margin: 0; font-size: var(--text-sm); font-weight: var(--weight-medium); }
.rows__sub { margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--text-muted); }
.rows__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.rows__note { font-size: var(--text-xs); color: var(--text-subtle); }

.card--danger { border-color: color-mix(in srgb, var(--color-danger) 40%, transparent); }
.card--danger .card__title { color: var(--color-danger); }
.actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }

.vocab { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.vocab__item { display: flex; align-items: center; gap: var(--space-3); font-size: var(--text-sm); }
.vocab__swatch { width: 0.7rem; height: 0.7rem; border-radius: 50%; flex: none; background: var(--swatch, var(--border-strong)); }
.vocab__label { flex: 1; }
.vocab__id { font-size: var(--text-xs); color: var(--text-subtle); }
.mono { font-family: var(--font-mono); }

/* ---------- the people picker ---------- */

.people { position: relative; display: flex; flex-direction: column; gap: var(--space-2); }
.people__chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.people__chips:empty { display: none; }

.people__chip { display: inline-flex; align-items: center; gap: var(--space-2); padding-inline-end: var(--space-1); }
.people__chip-name { font-weight: var(--weight-medium); }
.people__chip-email { color: var(--text-subtle); font-size: var(--text-xs); }
/* The source is a label, not a colour: "member" and "contact" mean different things and
   must be readable without seeing the difference between two greys. */
.people__chip-source { font-size: var(--text-xs); color: var(--text-subtle); border-inline-start: 1px solid var(--border); padding-inline-start: var(--space-2); }
.people__chip--member .people__chip-source { color: var(--color-accent); }
.people__chip--manual .people__chip-source { color: var(--color-warning); }
.people__chip-remove { background: none; border: 0; color: var(--text-muted); cursor: pointer; font-size: var(--text-md); line-height: 1; padding: 0 var(--space-1); }
.people__chip-remove:hover { color: var(--color-danger); }

/* OVER THE PAGE, NOT INSIDE THE FIELD.
   It was `position: absolute; top: 100%` inside `.modal__body`, which scrolls and — since the
   dialog was given a fixed height — clipped anything running past its edge. The names were
   rendered and unreachable. Fixed, because the coordinates are measured against the viewport;
   they arrive as custom properties through the CSSOM, so a list placed at runtime still needs
   no style attribute. The fallbacks keep it usable anywhere the coordinates cannot be
   measured. Above the dialog, which is 1200. */
.people__suggestions {
  list-style: none; margin: 0; padding: var(--space-1);
  position: fixed; z-index: 1300;
  top: var(--pop-top, 50%); left: var(--pop-left, 50%);
  min-width: var(--pop-width, 16rem);
  max-width: min(28rem, calc(100vw - 1rem));
  /* BOUNDED, AND IT SCROLLS INSIDE ITSELF. A list longer than the room available scrolls;
     it never runs off the screen and it never makes the dialog grow. */
  max-height: var(--pop-max, 16rem); overflow-y: auto;
  background: var(--surface-raised, var(--surface)); border: 1px solid var(--border-strong); border-radius: var(--radius);
  box-shadow: var(--shadow-lg, 0 8px 24px rgb(0 0 0 / 0.3));
}
.people__suggestions[hidden] { display: none; }
.people__option {
  display: flex; align-items: baseline; gap: var(--space-2); width: 100%;
  padding: var(--space-2) var(--space-3); background: none; border: 0; border-radius: var(--radius-sm);
  color: inherit; font: inherit; text-align: start; cursor: pointer;
}
.people__option:hover, .people__option--active { background: var(--surface-hover, var(--border)); }
.people__option-name { font-weight: var(--weight-medium); }
.people__option-email { color: var(--text-subtle); font-size: var(--text-xs); flex: 1; }
.people__option-source { font-size: var(--text-xs); color: var(--text-subtle); }
.people__option-source--member { color: var(--color-accent); }
.people__option-source--manual { color: var(--color-warning); }
.people__status { margin: 0; }

/* Suggested categories: real buttons, so keyboard and screen-reader behaviour comes for
   free rather than being reimplemented on a div. */
.card__empty-block { display: flex; flex-direction: column; gap: var(--space-3); }
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip--action { cursor: pointer; border-style: dashed; background: transparent; color: var(--text-muted); font: inherit; font-size: var(--text-xs); }
.chip--action:hover { color: var(--text); border-color: var(--border-strong); border-style: solid; }
/* ---------- tabbed, compact dialogs ---------- */

.modal__panel--full { max-width: 58rem; }

.modal__tabs { display: flex; gap: var(--space-1); border-bottom: 1px solid var(--border); margin-bottom: var(--space-1); }

.modal__tab {
  padding: var(--space-2) var(--space-4);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  cursor: pointer;
}

.modal__tab:hover { color: var(--text); }
.modal__tab--active { color: var(--color-info); border-bottom-color: var(--color-info); }

/* THE COMPACT GRID. Ordinary fields take one column of a responsive track; a date or a
   percentage is sized for its content rather than stretched across the dialog. */
/* A DETERMINISTIC THREE COLUMNS, not auto-fit.
   auto-fit is what produced the stair-step: the column count changed with the dialog's width,
   so which field landed beside which was a matter of arithmetic rather than of meaning —
   Project could end up nowhere near Category. A fixed count makes the grouping a decision.
   The grid supports the information hierarchy; it does not get to decide it. */
.modal__tabpanel {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3) var(--space-4);
  align-items: start;
}

.modal__tabpanel--hidden { display: none; }

.field--full { grid-column: 1 / -1; }
.field--narrow { max-width: 11rem; }

/* THE TITLE ESTABLISHES THE TOP OF THE FORM, and Important rides on its row.
   The title takes every column but the last; the flag takes the last. It was the other way
   round, which left the checkbox alone in a left column while the title began a third of the
   way across the dialog — the single most visible symptom of a form laid out by grid cell
   rather than by meaning. */
.field--grow { grid-column: 1 / -2; }
.field--flag { grid-column: -2 / -1; align-self: end; }
/* The checkbox reads as one control with its word, at the end of the title's row. */
.field--flag.field--check { padding-bottom: var(--space-2); }

/* Denser than a standalone form: the point of the Task tab is that everything ordinary fits
   without scrolling the page. */
.modal__tabpanel .field { gap: var(--space-1); }
.modal__tabpanel .field__input { padding: var(--space-2) var(--space-3); font-size: var(--text-sm); }
.modal__tabpanel textarea.field__input { min-height: 3.5rem; }
.modal__tabpanel .field__help { font-size: 0.7rem; }
.modal__tabpanel .field--check { align-items: flex-start; }

/* THE RECURRENCE TAB IS TWO COLUMNS. Its fields pair naturally — pattern with interval, the
   two schedule controls, the two ways a series ends — and three columns would split those
   pairs across rows for no gain. Less scrolling, better grouping, faster comprehension. */
.modal__tabpanel[data-tab="recurrence"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* Between the two, two columns: the pairs that matter — Project/Category, Start/Due — still
   land together, and nothing is squeezed to an unusable width. */
@media (max-width: 62rem) {
  .modal__tabpanel { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 40rem) {
  /* Narrow screens scroll, which is normal there. One column, full-size controls — and the
     title row stops being a row, because there is only one column to share. */
  .modal__tabpanel { grid-template-columns: 1fr; }
  .field--narrow { max-width: none; }
  .field--flag, .field--grow { grid-column: 1 / -1; }
  .modal__tabs { overflow-x: auto; }
}
/* ---------- the notification bell ---------- */

.bell { position: relative; display: inline-flex; align-items: center; gap: var(--space-1); }
.bell__icon { font-size: var(--text-md); line-height: 1; }
/* The count is TEXT, not a coloured dot: a dot says nothing to a screen reader, and the
   button's accessible name says what the number counts. */
.bell__count { min-width: 1.25rem; padding: 0 var(--space-1); border-radius: 999px; background: var(--color-accent); color: var(--surface); font-size: var(--text-xs); font-weight: var(--weight-medium); text-align: center; }
.bell__count--error { background: var(--color-danger); }
.bell--unread { color: var(--text); }

.chip--success { color: var(--color-success, #10b981); border-color: currentColor; }
.chip--error { color: var(--color-danger); border-color: currentColor; }
/* Waiting on somebody, rather than wrong. A pending account is nobody's fault and the
   colour says "look at this", not "this is broken". */
.chip--warn { color: var(--color-warning); border-color: currentColor; }

/* A statement of fact about how access was obtained, not a condition to act on. Warning
   banners are reserved for the latter; this is one compact line above the view. */
.access-note {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0 0 var(--space-4); padding: var(--space-2) var(--space-3);
  background: var(--surface-sunken); border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--text-xs); color: var(--text-muted);
}
.access-note__badge {
  flex: none; padding: 0.1rem var(--space-2); border-radius: var(--radius-pill);
  background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent);
  font-weight: var(--weight-medium);
}

/* Why a control is switched off. Rendered beside the field and joined to its accessible
   description, because a greyed-out box with no explanation is the thing people file bugs
   about. */
.field__reason { margin: 0; font-size: var(--text-xs); color: var(--color-warning); }
.field__reason[hidden] { display: none; }
.field__input[aria-disabled="true"], .field__check[aria-disabled="true"] { opacity: 0.55; }

/* ---------- compact data tables ----------
   Settings holds short structured datasets — six statuses, five priorities, a handful of
   contacts. Rendering them as full-width rows put a river of empty space between the label
   and the internal id, so a six-row list filled a desktop screen. A table gives each value
   a column of its own content's width and lets the eye scan down instead of across.

   INFORMATION PRIORITY, left to right: human label → meaning → usage/actions → internal id.
   The id stays visible because it is real administrative metadata, but it is last, muted
   and monospaced, because it is looked up rather than read. */

.datatable__scroll { overflow-x: auto; }
.datatable { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.datatable th {
  text-align: start; padding: var(--space-2) var(--space-3);
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-subtle);
  border-bottom: 1px solid var(--border-strong); white-space: nowrap;
}
.datatable td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); vertical-align: middle; }
.datatable tr:last-child td { border-bottom: 0; }
.datatable tbody tr:hover { background: var(--surface-sunken); }
.datatable__row--muted { opacity: 0.6; }

/* The name column carries the weight and takes the slack, so every other column stays at
   its content's width instead of being stretched apart. */
.datatable__name { width: 100%; display: table-cell; }
.datatable__name .vocab__swatch { display: inline-block; margin-inline-end: var(--space-2); vertical-align: middle; }
.datatable__primary { display: block; font-weight: var(--weight-medium); }
.datatable__secondary { display: block; font-size: var(--text-xs); color: var(--text-muted); }

.datatable__num { text-align: end; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text-muted); }
.datatable__when { white-space: nowrap; color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* SECONDARY BY CONSTRUCTION. */
.datatable__id { white-space: nowrap; font-size: var(--text-xs); color: var(--text-subtle); }
.datatable__actions { white-space: nowrap; text-align: end; }
.datatable__actions > * + * { margin-inline-start: var(--space-2); }

.btn--sm { min-height: 1.75rem; padding: var(--space-1) var(--space-3); font-size: var(--text-xs); }
.chip--open { color: var(--color-info); border-color: currentColor; }
.card--tight { padding: var(--space-4); }

/* Efficient use of desktop space: the short vocabularies sit side by side rather than each
   claiming a full-width band of its own. */
.cards--split { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: var(--space-4); align-items: start; }

@media (max-width: 48rem) {
  .cards--split { grid-template-columns: 1fr; }
}

/* Presence: a dot AND a word. Colour alone says nothing to a screen reader, and the word
   is what the accessible name carries. */
.presence { display: flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
.presence__dot { font-size: 0.7rem; line-height: 1; }
.presence__dot--online { color: var(--color-success, #10b981); }
.presence__dot--away { color: var(--color-warning); }
.presence__dot--muted { color: var(--text-subtle); }

/* ---------- visibility ---------- */
/* The level is a small statement of fact beside a name, not a warning. Each level gets its
   own hue so a list is scannable, but the WORD is always present — the colour is never the
   only thing carrying the meaning. */
.chip--level { margin-inline-start: var(--space-2); font-size: var(--text-xs); }
.chip--level-public { color: var(--color-accent); border-color: currentColor; }
.chip--level-people { color: var(--text-muted); border-color: currentColor; }
.chip--level-private { color: var(--color-warning); border-color: currentColor; }

.levelrow { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3); }
.levelrow .card__hint { flex: 1; min-width: 16rem; margin: 0; }

/* ---------- the notification matrix ----------
   THREE COMPACT MATRICES SIDE BY SIDE, each owning its own columns. The width is already
   there; the previous layout left half of it empty and paid for that with a page several
   screens tall.

   auto-fit + minmax lets the browser decide how many sections fit — three on a wide desktop,
   two on a tablet, one on a phone — rather than a pixel width chosen to suit one screenshot. */
.matrixgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--space-4) var(--space-5);
  align-items: start;
}

.matrixgrid__section { min-width: 0; }
.matrixgrid__title {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
}

/* THE NOTIFICATION MATRIX — Event | In app | Email.

   IT IS DELIBERATELY NOT CALLED `.matrix`. It was, and the Eisenhower view already owned
   that name with `display: grid; grid-template-columns: 1fr 1fr`. Two unrelated components
   sharing a class is not a naming untidiness — the table inherited `display: grid` and
   stopped being a table, so `table-layout` and the column widths below applied to nothing,
   the header became one grid item and the body another, and the two ended up in different
   columns with no shared geometry at all. That is exactly what alignment-by-accident looks
   like when the accident stops. `.notifmatrix` cannot collide.

   ONE COLUMN DEFINITION, IN ONE PLACE. `table-layout: fixed` plus the <colgroup> means the
   header row and every event row resolve to the SAME columns by construction — not because
   two sets of widths happen to agree today. No cell declares a width, so there is nothing
   to drift. */
.notifmatrix { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: var(--text-sm); }

/* The whole column definition. Event takes what is left; the channels are sized to their
   headings and stay put however long a label is. */
.notifmatrix__col--event { width: auto; }
.notifmatrix__col--channel { width: 3.5rem; }

.notifmatrix th, .notifmatrix td { padding: var(--space-1) var(--space-2); border-bottom: 1px solid var(--border); }
.notifmatrix thead th {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-subtle);
  font-weight: var(--weight-medium);
}
.notifmatrix tbody tr:last-child th, .notifmatrix tbody tr:last-child td { border-bottom: 0; }

/* Alignment only. The geometry is the colgroup's job, and these must never take it back. */
.notifmatrix__event { text-align: start; font-weight: var(--weight-regular); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.notifmatrix__channel { text-align: center; }

/* Dense is not the same as tiny. The row is compact; the target somebody has to hit is not —
   the checkbox keeps a full-size hit area inside a short row. */
.notifmatrix__channel .field__check { margin: 0; width: 1rem; height: 1rem; }
.notifmatrix__channel label, .notifmatrix__channel .field__check { cursor: pointer; }
.notifmatrix tbody tr:hover { background: var(--surface-sunken); }

/* ---------- a dialog that does not move under you ----------
   The head, the tab strip and the actions are FIXED; only the content between them scrolls.
   Before this the whole panel scrolled and every tab was a different height, so changing tab
   moved the Save button somebody was already reaching for. */
.modal__panel {
  display: flex;
  flex-direction: column;
  /* The panel itself no longer scrolls — .modal__body does. */
  overflow: hidden;
}
.modal__form { flex: 1; min-height: 0; }
.modal__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* Room for a focus ring against the scroll edge, and for a dropdown to open without
     being clipped the instant it appears. */
  padding: var(--space-1) var(--space-1) var(--space-2);
  margin: calc(var(--space-1) * -1) calc(var(--space-1) * -1) 0;
}
.modal__head, .modal__tabs, .modal__actions, .modal__description { flex: none; }
/* A TABBED DIALOG HOLDS ONE SHAPE ACROSS ITS TABS.
   A MINIMUM WAS NOT ENOUGH. The body had a floor and no ceiling, so it still grew to fit
   whichever panel was showing — the Task tab is tall, Recurrence is short, and changing tab
   resized the dialog and moved everything under the pointer. Three tabs of three different
   heights need ONE height, not a minimum each of them clears by a different amount.
   Bounded by the viewport so a short screen never gets a dialog taller than itself, and with
   a floor of its own so a very short screen still leaves something to work in. */
.modal__panel--full .modal__body {
  flex: none;
  height: max(14rem, min(30rem, calc(100vh - 16rem)));
}
.modal__actions { background: var(--surface-raised); }

@media (max-width: 40rem) {
  /* On a narrow screen the dialog is effectively the page. Give it the whole height rather
     than a floating card with two scroll regions competing. */
  .modal__panel { max-height: calc(100vh - var(--space-4)); }
  /* The fixed height is RELEASED here: on a narrow screen the dialog is effectively the page,
     where one tall scrolling column is right and an inner fixed height would create two
     scroll regions competing with each other. */
  .modal__panel--full .modal__body { flex: 1; height: auto; }
}

/* ---------- polish: restraint over decoration ----------
   Everything here removes something rather than adding it. The complaints these answer were
   all the same shape — space that carries no information, and controls stretched far past
   the width of what they hold. */

/* A card is a grouping, not a poster. Tightened so a settings panel reads as a list of
   related facts rather than a stack of billboards. */
.card { padding: var(--space-4); }
.card--tight { padding: var(--space-3) var(--space-4); }
.card__hint { max-width: 78ch; }
.card + .card { margin-top: var(--space-3); }

/* A definition list of short values does not need a full-width column each. */
.detail { grid-template-columns: minmax(8rem, max-content) 1fr; column-gap: var(--space-4); }

/* Inputs sized for their content. A date field stretched across a dialog reads as an
   invitation to type an essay into it. */
.field__input[type="date"], .field__input[type="number"] { max-width: 11rem; }
.field__input--inline { max-width: 12rem; }

/* Actions sit in the same place on every panel: at the end of the row they belong to. */
.panel__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3); }
.panel__head .card__hint { flex: 1; min-width: 16rem; margin: 0; }

/* Focus is always visible, including on a control that is otherwise borderless. Removing an
   outline without replacing it is how a keyboard user loses their place. */
.btn:focus-visible, .field__input:focus-visible, .field__check:focus-visible,
.modal__tab:focus-visible, .nav__link:focus-visible, .tabs__tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  /* Motion is decoration here. Somebody who has asked for less of it gets none. */
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}

/* ---------- the people dock ----------
   A persistent panel in the bottom-right: collapsed it is one pill, expanded it is who is
   here and what is waiting. It never covers what somebody is working on, and on a narrow
   screen it becomes a sheet across the bottom rather than a floating card fighting for
   half the viewport. */

.dock {
  position: fixed;
  inset-block-end: var(--space-4);
  inset-inline-end: var(--space-4);
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2);
  /* The dock is not a dialog. Only its own controls take pointer events, so the page
     underneath stays fully usable around it. */
  pointer-events: none;
}
.dock > * { pointer-events: auto; }

.dock__pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.25rem;
  padding: var(--space-2) var(--space-4);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text);
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
.dock__pill:hover { background: var(--surface-sunken); }

.dock__panel {
  display: flex;
  flex-direction: column;
  width: min(22rem, calc(100vw - var(--space-6)));
  max-height: min(30rem, calc(100vh - var(--space-8)));
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  /* Restrained elevation. It is a dock, not a modal. */
  box-shadow: var(--shadow);
  overflow: hidden;
}

.dock__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
  flex: none;
}
.dock__title { margin: 0; flex: 1; font-size: var(--text-sm); font-weight: var(--weight-bold); }
.dock__count { margin: 0; font-size: var(--text-xs); color: var(--text-muted); }
.dock__close, .dock__back {
  flex: none; width: 1.75rem; height: 1.75rem; display: grid; place-items: center;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--text-muted); cursor: pointer; font-size: var(--text-md); line-height: 1;
}
.dock__close:hover, .dock__back:hover { background: var(--surface-sunken); color: var(--text); }

.dock__body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-2); }
.dock__body--conversation { display: flex; flex-direction: column; padding: 0; }

.dock__section {
  margin: var(--space-2) var(--space-2) var(--space-1);
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-subtle);
}
.dock__list { list-style: none; margin: 0; padding: 0; }
.dock__empty { margin: var(--space-3) var(--space-2); font-size: var(--text-sm); color: var(--text-muted); }
.dock__error { margin: var(--space-3) var(--space-2); font-size: var(--text-sm); color: var(--color-danger); display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

.dock__person {
  display: flex; align-items: center; gap: var(--space-2); width: 100%;
  padding: var(--space-2); font: inherit; font-size: var(--text-sm); text-align: start;
  background: none; border: 0; border-radius: var(--radius); color: var(--text); cursor: pointer;
}
.dock__person:hover { background: var(--surface-sunken); }
.dock__person-main { flex: 1; min-width: 0; }
.dock__person-name { display: block; }
.dock__person-preview {
  display: block; font-size: var(--text-xs); color: var(--text-subtle);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Presence is a word to a screen reader and a dot to everybody else — the dot is never the
   only thing carrying the meaning, because every row is also grouped under "Online". */
.dock__dot { flex: none; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--text-subtle); }
.dock__dot--on { background: var(--color-success, #10b981); }
.dock__dot--off { background: var(--border-strong); }

.dock__badge {
  flex: none; min-width: 1.25rem; padding: 0 var(--space-1); border-radius: var(--radius-pill);
  background: var(--accent); color: var(--accent-contrast);
  font-size: var(--text-xs); font-weight: var(--weight-bold); text-align: center;
}

.dock__messages { flex: 1; min-height: 0; overflow-y: auto; list-style: none; margin: 0; padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.dock__message { max-width: 85%; align-self: flex-start; }
.dock__message--mine { align-self: flex-end; text-align: end; }
.dock__message-text {
  margin: 0; padding: var(--space-2) var(--space-3); border-radius: var(--radius-lg);
  background: var(--surface-sunken); font-size: var(--text-sm);
  /* Line breaks are preserved exactly as typed, and the text is never treated as markup. */
  white-space: pre-wrap; overflow-wrap: anywhere; text-align: start;
}
.dock__message--mine .dock__message-text { background: var(--accent-soft, var(--surface-sunken)); }
.dock__message-meta { margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--text-subtle); }

.dock__compose { flex: none; display: flex; gap: var(--space-2); padding: var(--space-2); border-top: 1px solid var(--border); }
.dock__input {
  flex: 1; min-width: 0; resize: none;
  padding: var(--space-2) var(--space-3);
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  font: inherit; font-size: var(--text-sm);
}

.dock__toasts { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-2); }
.dock__toast {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
  font-size: var(--text-sm); color: var(--text-muted);
}

@media (max-width: 40rem) {
  /* A sheet across the bottom, not a floating card covering half a phone. */
  .dock { inset-inline: var(--space-2); inset-block-end: var(--space-2); align-items: stretch; }
  .dock__panel { width: 100%; max-height: 70vh; }
  .dock__pill { justify-content: center; }
}

/* ---------- toasts ----------
   Short-lived, bottom-left so they never sit under the people dock, and restrained: a toast
   that has to be dismissed is a toast that will be resented. */
.toasts {
  position: fixed;
  inset-block-end: var(--space-4);
  inset-inline-start: var(--space-4);
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: min(28rem, calc(100vw - var(--space-6)));
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--color-info);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  pointer-events: auto;
}
.toast--warn { border-inline-start-color: var(--color-warning); }
.toast--success { border-inline-start-color: var(--color-success, #10b981); }
.toast__text { margin: 0; font-size: var(--text-sm); }
.toast__dismiss {
  flex: none; background: none; border: 0; color: var(--text-muted);
  font-size: var(--text-md); line-height: 1; cursor: pointer; padding: 0 var(--space-1);
}
.toast__dismiss:hover { color: var(--text); }

@media (max-width: 40rem) {
  /* Above the dock's sheet, not underneath it. */
  .toasts { inset-inline: var(--space-2); inset-block-end: calc(var(--space-8) + var(--space-4)); max-width: none; }
}

/* You, in the people list: present, marked as you, and not something to click. */
.dock__self { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2); font-size: var(--text-sm); color: var(--text-muted); }

/* ---------- the features page ----------
   Grouped capability cards in one responsive grid. auto-fit decides how many fit, so it is
   several across on a wide desktop and one on a phone without a pixel width chosen to suit
   a single screen. Compact by construction: the point is that it scans. */
.features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: var(--space-4);
  align-items: start;
}

.features__head { margin-bottom: var(--space-5); }

.features__group {
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  min-width: 0;
}

.features__grouphead { display: flex; gap: var(--space-3); align-items: flex-start; margin-bottom: var(--space-3); }
.features__icon {
  flex: none; width: 1.75rem; height: 1.75rem; display: grid; place-items: center;
  border-radius: var(--radius); background: var(--accent-soft, var(--surface-sunken));
  color: var(--accent); font-size: var(--text-sm); line-height: 1;
}
.features__grouptitle { margin: 0; font-size: var(--text-md); font-weight: var(--weight-bold); }
.features__groupsummary { margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--text-muted); }

.features__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.features__item { padding-top: var(--space-3); border-top: 1px solid var(--border); }
.features__title { margin: 0; font-size: var(--text-sm); font-weight: var(--weight-medium); }
.features__detail { margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-normal); }
.features__go { margin-top: var(--space-2); }

/* Always reachable, never shouting. */
.footer__link { color: var(--text-muted); text-decoration: none; font-size: var(--text-sm); }
.footer__link:hover { color: var(--text); text-decoration: underline; }

/* ---------- the delivery history ----------
   The name leads and the address follows on its own line, always. A row that shows only a
   display name cannot answer "which address did this go to", which is the question a
   delivery failure actually raises. */
.delivery__who { line-height: var(--leading-snug); }
.delivery__name { display: block; }
.delivery__address { display: block; font-size: var(--text-xs); color: var(--text-muted); word-break: break-all; }

/* COMPACT TO SCAN, COMPLETE WHEN OPENED. Five columns carry the answer to "what happened, to
   whom, on which channel, how did it end"; the eleven diagnostic facts are one keystroke away
   on EVERY row — including the accepted ones, which are the rows people actually argue about.
   The toggle is the When cell, so the control costs no column of its own. */
.delivery__toggle {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: 0;
  font: inherit;
  font-size: var(--text-xs);
  color: var(--text);
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
}
.delivery__toggle:hover { color: var(--accent); }
.delivery__caret { color: var(--text-subtle); }

.delivery__detailrow > td {
  padding-top: 0;
  background: var(--surface-sunken);
}

.delivery__facts {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
}
.delivery__facts dt { color: var(--text-subtle); white-space: nowrap; }
.delivery__facts dd { margin: 0; word-break: break-word; }

/* ---------- the colour picker ----------
   A button the size of a button, showing the colour AND its name. What it replaces was a
   thin sliver of native <input type="color"> that said nothing about being clickable.
   Every colour arrives as --swatch through the CSSOM, never as a style attribute. */
.colorpick { position: relative; display: inline-block; }

.colorpick__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  min-width: 10rem;
  text-align: left;
}
.colorpick__trigger:hover { border-color: var(--border-strong); }
.colorpick__trigger:disabled { opacity: 0.55; cursor: not-allowed; }

/* The swatch is the point. It is a real square, not an edge. */
.colorpick__swatch {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: var(--radius-sm);
  background: var(--swatch, var(--border-strong));
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.18);
}
.colorpick__name { flex: 1; }
.colorpick__caret { color: var(--text-subtle); font-size: var(--text-xs); }

/* OVER THE PAGE, NOT INSIDE THE FIELD. The panel is attached to the body while it is open,
   so it escapes .modal__body — which scrolls, and which was both clipping the palette and
   growing to contain it, making the dialog jump the moment somebody opened a colour.
   Fixed, because the coordinates are measured against the viewport. The position arrives as
   custom properties through the CSSOM, so a panel placed at runtime still needs no style
   attribute. The fallbacks keep it usable anywhere the coordinates cannot be measured. */
.colorpick__panel {
  position: fixed;
  z-index: 1200;
  top: var(--pop-top, 50%);
  left: var(--pop-left, 50%);
  max-height: var(--pop-max, min(24rem, calc(100vh - 2rem)));
  overflow-y: auto;
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md, 0 8px 24px rgb(0 0 0 / 0.18));
}
.colorpick__panel[hidden] { display: none; }

/* THE FAMILY SELECTOR, above the swatches. A real <select>, so it is native on a phone and
   reachable by keyboard without this stylesheet having to reinvent a menu. */
.colorpick__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.colorpick__palettelabel {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-subtle);
}
.colorpick__palette {
  padding: var(--space-1) var(--space-2);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
/* UNDERSTATED ON PURPOSE. Adopting a palette is a deliberate act, not the obvious next step
   in choosing one category's colour, so it reads as a link rather than competing with the
   swatches for attention. */
.colorpick__setdefault {
  padding: 0;
  font: inherit;
  font-size: var(--text-xs);
  color: var(--accent);
  background: none;
  border: 0;
  text-decoration: underline;
  cursor: pointer;
}
.colorpick__setdefault:disabled { opacity: 0.55; cursor: not-allowed; }

.colorpick__grid { display: grid; grid-template-columns: repeat(6, 1.75rem); gap: var(--space-2); }

/* THE COLOUR IN WORDS. A ring around a swatch is not a signal everybody can see, and
   "Selected: Soft Blue · Pastel" is the answer the control exists to give. */
.colorpick__selected {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--text-subtle);
}
.colorpick__option {
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--swatch);
  cursor: pointer;
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.18);
}
.colorpick__option:hover { transform: scale(1.08); }
/* The chosen one is marked by a RING, not only by being slightly larger — a size
   difference is not a signal somebody can rely on. */
.colorpick__option--chosen { border-color: var(--text); outline: 2px solid var(--surface); outline-offset: -4px; }

.colorpick__customrow { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); }
.colorpick__customlabel { font-size: var(--text-xs); color: var(--text-subtle); }
.colorpick__custom {
  width: 7rem;
  padding: var(--space-1) var(--space-2);
  font: inherit;
  font-size: var(--text-sm);
  font-family: var(--font-mono, monospace);
  color: var(--text);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* ---------- category identity on a task card ----------
   ONE MEANING PER ELEMENT. The card's colour is its CATEGORY and nothing else — priority
   stays a separate chip, the assignee stays a separate badge, the title stays the loudest
   thing on the card. A card flood-filled with a saturated colour would take that meaning
   away from all three.

   RESTRAINT BY CONSTRUCTION: a 3px edge at full strength, and a wash of the same colour at
   ~7% behind the card. `color-mix` keeps the wash relative to the card's own surface, so it
   is a tint in a light theme and a shade in a dark one without a second set of colours. */
.card--categorized {
  border-left: 3px solid var(--category-color, var(--border-strong));
  /* THE WHOLE CARD IS ONE COLOUR.
     The wash is published as a custom property so everything INSIDE the card can derive from
     it. That is the defect this fixes: nested controls kept the global sunken surface, so at
     any real strength a coloured card had near-black boxes cut into it.
     The strength comes from the CATEGORY, per card. The 7% fallback is what this treatment
     has always used, so a category with none recorded looks exactly as it always did. */
  --card-wash: color-mix(in srgb, var(--category-color, transparent) var(--category-strength, 7%), var(--surface));
  background: var(--card-wash);
  /* Computed against the category's own luminance when the wash is strong enough for the
     category to be what the text sits on. Below that the theme's colour is already right. */
  color: var(--card-ink, var(--text));
}

.card__category {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
/* CATEGORY IS NEVER COLOUR ALONE. The dot is the colour; the name beside it is what makes
   the card readable to somebody who cannot tell the dots apart. */
.card__categorydot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--category-color, var(--border-strong));
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.2);
}

/* ---------------------------------------------------------------------------
   THE PALETTE PREVIEW in My settings.
   A dropdown of family NAMES asks somebody to choose between "Soft" and "Muted" from two
   words. The colours are the whole decision, so they are on the page beside the control —
   and named underneath, because colour is never the only identity.
   --------------------------------------------------------------------------- */

.palettepreview { margin-top: var(--space-3); }
.palettepreview__swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.palettepreview__swatch {
  display: block;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-sm);
  background: var(--swatch);
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.18);
}
.palettepreview__names {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--text-subtle);
}

/* ---------------------------------------------------------------------------
   A SELECT YOU CAN ADD TO.
   The offer to create one is the last OPTION in the list — where somebody who cannot find
   their project is already looking — rather than a button beside the field. Choosing it opens
   a dialog over the form, which is why nothing here needs to make room for an inline row.
   --------------------------------------------------------------------------- */

.addable__status {
  margin: var(--space-1) 0 0;
  font-size: var(--text-xs);
  color: var(--text-subtle);
}

/* ---------------------------------------------------------------------------
   THE CATEGORY COLOUR STRENGTH CONTROL.
   The preview is the REAL card treatment with real content on it, because the question the
   cap exists for — is the title still readable — cannot be answered by a swatch.
   --------------------------------------------------------------------------- */

.strength__row { display: flex; align-items: center; gap: var(--space-3); }
.strength__slider { flex: 1; min-width: 0; }
.strength__value {
  min-width: 3rem;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  text-align: right;
}
.strength__preview { margin-top: var(--space-3); max-width: 22rem; }
.strength__card { pointer-events: none; }
.strength__caution {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--warn-text, var(--text-muted));
}

/* ---------------------------------------------------------------------------
   IMPORTANCE.
   An OUTLINE, and deliberately not a background or a left edge: the card's colour already
   means its category and the chip already means its priority. A third fact needs a third
   device, or one of them loses its meaning.
   --------------------------------------------------------------------------- */

.card--important {
  outline: 2px solid var(--important);
  outline-offset: -1px;
}
.row--important > td:first-child { box-shadow: inset 3px 0 0 var(--important); }
.row--important > td { background: color-mix(in srgb, var(--important) 6%, transparent); }

/* NEVER COLOUR ALONE — the word, for anybody who cannot make out the outline. */
.chip--important {
  color: var(--important-ink, var(--important));
  background: color-mix(in srgb, var(--important) 14%, transparent);
  border-color: color-mix(in srgb, var(--important) 40%, transparent);
}

/* ---------------------------------------------------------------------------
   EVERYTHING INSIDE A COLOURED CARD IS THE SAME COLOUR AS THE CARD.
   Reported: "the fields in the cards on the task list show black background." The inline
   controls kept var(--surface-sunken) — nearly black in a dark theme — so a washed card had
   boxes cut into it. Each nested surface is a nudge away from the card's OWN wash instead, so
   the card reads as one object at any strength.
   --------------------------------------------------------------------------- */

.card--categorized .card__status {
  color: inherit;
  background: color-mix(in srgb, var(--card-wash) 88%, var(--border-strong));
  border-color: color-mix(in srgb, var(--card-wash) 60%, var(--border-strong));
}
.card--categorized .card__open { color: inherit; }
.card--categorized .card__category,
.card--categorized .card__meta { color: inherit; }
/* The chips keep their own meaning — priority is priority — but sit on the card's colour
   rather than on a surface that is no longer behind them. */
.card--categorized .chip {
  background: color-mix(in srgb, var(--card-wash) 82%, var(--border-strong));
  border-color: color-mix(in srgb, var(--card-wash) 55%, var(--border-strong));
}

/* ---------------------------------------------------------------------------
   THE CATEGORY SLIVER ON THE TASK LIST.
   The same fact the Kanban card's coloured edge reports, so it is the same device: a thin
   edge in the category's own colour, beside the task's NAME. No wash — a colour behind a
   dense table of text is a different judgement from a colour on a card, and what was asked
   for was the sliver.
   --------------------------------------------------------------------------- */

.table__titlecell { position: relative; }
.table__titlecell--categorized { box-shadow: inset 3px 0 0 var(--category-color, var(--border-strong)); padding-left: var(--space-3); }
.cell__category { color: var(--text-muted); }

/* ---------------------------------------------------------------------------
   ASSIGNEE AVATARS.
   Compact circles of initials beside the task title, because a card is not wide enough for a
   name and nowhere near wide enough for an address. Three faces then a count; every full name
   lives in the stack's accessible name, so the compression is lossless somewhere.
   --------------------------------------------------------------------------- */

.avatars { display: inline-flex; flex: none; align-items: center; }
/* Overlapped slightly, so four circles cost less than four circles' width. */
.avatars .avatar + .avatar, .avatars__more { margin-left: -0.35rem; }
.avatar, .avatars__more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border-radius: 50%;
  font-size: 0.62rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.01em;
  color: var(--accent-contrast, #fff);
  background: var(--accent);
  /* The ring separates overlapping circles from each other and from the card beneath. */
  box-shadow: 0 0 0 2px var(--card-wash, var(--surface));
}
.avatars__more { color: var(--text); background: var(--surface-sunken); }

/* ---------------------------------------------------------------------------
   PRIORITY.
   COLOUR + SHAPE + LABEL, and the shape is the part that survives everything. The colouring
   used to be keyed on the literal ids `urgent`/`high`/`medium`, so a workspace that renamed
   its priorities got no colour at all and nobody found out. The level comes from the
   workspace's own weights now, and the stylesheet colours by level.
   --------------------------------------------------------------------------- */

.prio { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); }
.prio__bars { display: inline-flex; align-items: flex-end; gap: 1px; height: 0.7rem; }
.prio__bar {
  width: 3px;
  height: 100%;
  border: 1px solid var(--prio-color, var(--text-subtle));
  border-radius: 1px;
  background: transparent;
}
/* FILLED IS THE SIGNAL. Four bars always render, so "one" and "one of four" cannot look the
   same, and an empty bar is an outline rather than a gap. */
.prio__bar--on { background: var(--prio-color, var(--text-subtle)); }
.prio__label { color: var(--prio-color, var(--text-muted)); font-weight: var(--weight-medium); }

.prio[data-level="1"] { --prio-color: var(--text-subtle); }
.prio[data-level="2"] { --prio-color: var(--text-muted); }
.prio[data-level="3"] { --prio-color: var(--color-warning); }
.prio[data-level="4"] { --prio-color: var(--color-danger); }

@media (forced-colors: active) {
  /* Author colour is discarded here, which is exactly where a colour-only signal disappears.
     The bars are borders, so the rank is still countable. */
  .prio__bar { border-color: CanvasText; }
  .prio__bar--on { background: CanvasText; }
  .prio__label { color: CanvasText; }
}

/* ---------------------------------------------------------------------------
   AN EDIT CONFLICT IS A DECISION, NOT AN ERROR.
   Somebody else changed the thing you were editing. Nobody did anything wrong, so this does not
   look like a failure — it looks like a question, with the answers beside it and the form still
   holding everything that was typed.
   --------------------------------------------------------------------------- */

.modal__conflict {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-warning) 8%, transparent);
}
.modal__conflict[hidden] { display: none; }
.modal__conflictsay { margin: 0; font-size: var(--text-sm); }
.modal__conflictactions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }

/* ============================================================
   TASK FORM LAYOUTS — five arrangements of one form.

   THE BUG ALL THREE BROKEN LAYOUTS SHARED. `.modal__tabpanel` is a
   THREE-COLUMN GRID, and the field width modifiers are grid rules
   written for it: `field--grow` is `grid-column: 1 / -2`,
   `field--flag` is `grid-column: -2 / -1; align-self: end`, and
   `field--narrow` caps a field at 11rem. Reparenting those fields
   into a new container left both layout systems running at once —
   the two-column split became one cell of the old grid a third of
   the dialog wide, the guided rail rendered above the panel instead
   of beside it, and in every non-grid layout `align-self: end` threw
   Important to the far right on a row of its own.

   So each layout OWNS its arrangement, and the modifiers are reset
   wherever the grid they were written for no longer exists.
   ============================================================ */

/* THE RESET. Scoped to the layouts, so the tabbed form the modifiers were written for keeps
   them exactly as they were. Anything reparented takes the width of the column it is in. */
.tf-pop .field,
.tf-ledger .field,
.tf-step__panel .field,
.tf-strip .field,
.tf-sheet .field {
  grid-column: auto;
  max-width: none;
  align-self: stretch;
}

/* Help text is a whole sentence and must never set a column's width. */
.tf-ledger .field__help,
.tf-step__panel .field__help,
.tf-sheet .field__help { max-width: 44ch; }

/* ---- 01 · command bar ---------------------------------------
   THE TASK'S OWN WORDS, THEN WHAT IS TRUE ABOUT IT.

   Height is independent of field count, and — the part the first
   attempt missed — so is the amount somebody has to READ. The title
   leads, the description is one line until it is wanted, and the rest
   is two short rails of chips that show their VALUES. A chip that is
   set looks different from one that is not, so the rail doubles as a
   summary of the task rather than as a printout of its schema.

   Nothing here reserves space for something that is not there. */
.modal__body--command { display: flex; flex-direction: column; gap: var(--space-3); }
.modal__body--command > .field { grid-column: auto; max-width: none; }

/* THE TITLE IS THE THING BEING CREATED, not Field #1.
   Borderless with a single heavy rule under it — the focus ring is
   untouched, the label is still there for assistive technology, and
   `required` still drives the submit button exactly as before. */
.tf-cmd__title { display: block; }
.tf-cmd__title .field__label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.tf-cmd__title .field__input {
  width: 100%;
  padding: var(--space-1) 0 var(--space-2);
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  letter-spacing: -.02em;
  line-height: var(--leading-tight);
  color: var(--text);
  background: none;
  border: 0;
  border-bottom: 2px solid var(--text);
  border-radius: 0;
}
.tf-cmd__title .field__input::placeholder { color: var(--text-subtle); font-weight: var(--weight-normal); }
.tf-cmd__title .field__input:focus-visible { border-bottom-color: var(--accent); }

/* THE DESCRIPTION COSTS ONE LINE UNTIL IT HAS SOMETHING TO SAY. */
.tf-cmd__descblock { display: block; }
.tf-cmd__descopen {
  display: block; width: 100%; text-align: left;
  font: inherit; font-size: var(--text-sm);
  color: var(--text-subtle);
  background: none; border: 0; border-radius: var(--radius-sm);
  padding: var(--space-1) 0;
  cursor: text;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tf-cmd__descopen:hover { color: var(--text-muted); }
.tf-cmd__descopen--set { color: var(--text-muted); }
.tf-cmd__desc[hidden] { display: none; }
.tf-cmd__desc .field__label { font-size: var(--text-xs); color: var(--text-subtle); }

/* THE RAILS. Two rows, wrapping — state first, then what is most
   likely to be reached for next. */
.tf-cmd__rail { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.tf-cmd__rail:empty { display: none; }
.tf-cmd__rail--secondary { padding-top: var(--space-1); }
.tf-cmd__slot { position: relative; display: inline-flex; }
.tf-cmd__slot[hidden] { display: none; }
/* A toggle chip IS its checkbox. The field still exists and is still
   read; it simply has no panel, so it is never shown. */
.tf-cmd__silent { display: none; }

.tf-chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font: inherit; font-size: var(--text-xs); font-weight: var(--weight-medium);
  color: var(--text-muted); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  cursor: pointer; white-space: nowrap;
  max-width: 16rem;
}
.tf-chip__text { overflow: hidden; text-overflow: ellipsis; }
.tf-chip:hover { border-color: var(--border-strong); color: var(--text); }

/* SET LOOKS DIFFERENT FROM UNSET. That difference is the whole reason
   the rail can be read instead of parsed. */
.tf-chip--set {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: var(--weight-bold);
}
.tf-chip--set:hover { border-color: var(--accent-hover); color: var(--accent-hover); }

/* A FLAG IS NOT JUST ANOTHER VALUE. Important and Urgent use the
   importance colour the table rows and board cards already use. */
.tf-chip--flag {
  border-color: color-mix(in srgb, var(--important) 45%, transparent);
  background: color-mix(in srgb, var(--important) 14%, transparent);
  color: var(--important-ink, var(--important));
  font-weight: var(--weight-bold);
}
.tf-chip--flag:hover { border-color: var(--important); color: var(--important-ink, var(--important)); }

/* The door to the rest. Dashed, because it opens something rather
   than stating something. */
.tf-chip--add { border-style: dashed; }

.tf-chip__dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--dot, currentColor); flex: none; }
/* Inside a chip the avatars carry no card behind them, so the ring
   that separates them is the chip's own background. */
.tf-chip .avatar, .tf-chip .avatars__more {
  width: 1.15rem; height: 1.15rem; font-size: 0.55rem;
  box-shadow: 0 0 0 2px var(--accent-soft, var(--surface));
}

/* WHAT A CHIP OPENS.
   Fixed, so it is measured against the viewport and neither clipped by
   the dialog's scrolling panel nor added to its scroll height. It stays
   INSIDE the dialog in the DOM — a panel on the body would fall out of
   the modal's tab trap and out of its inert handling. */
.tf-pop {
  position: fixed; z-index: 1300;
  top: var(--pop-top, 50%); left: var(--pop-left, 50%);
  width: max-content; min-width: 15rem;
  max-width: min(26rem, calc(100vw - 1rem));
  max-height: var(--pop-max, min(22rem, calc(100vh - 2rem)));
  overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--space-4);
  padding: var(--space-4);
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.tf-pop[hidden] { display: none; }
.tf-pop .field__help { max-width: 34ch; }
.tf-pop .field__input { width: 100%; }

/* ---- 02 · two columns ---------------------------------------
   The split OWNS the panel — the panel is a plain block here, so
   there is only one layout system arranging the fields. */
.modal__body--ledger .modal__tabpanel { display: block; }
.modal__body--ledger .modal__tabpanel--hidden { display: none; }

.tf-ledger {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 17rem;
  gap: var(--space-4) var(--space-5);
  align-items: start;
}
.tf-ledger__content, .tf-ledger__meta { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
/* The title still leads and Important still rides on its row — a deliberate pairing, not an
   accident of the grid it used to live in. */
.tf-ledger__content .field--flag { align-self: flex-start; }
/* The disclosure toggle and its fields span both columns, beneath them. */
.tf-ledger > .modal__more, .tf-ledger > .modal__advanced { grid-column: 1 / -1; }
.tf-ledger > .modal__advanced { display: flex; flex-direction: column; gap: var(--space-4); }
@media (max-width: 52rem) { .tf-ledger { grid-template-columns: 1fr; } }

/* ---- 03 · guided steps --------------------------------------
   THE RAIL SITS BESIDE THE FORM, NOT ABOVE IT. The dialog stacks
   its children, so a vertical rail rendered as a full-width strip
   above the panel — which is a tab strip wearing a rail's clothes.
   The form becomes a two-column grid: rail down the left for the
   whole height, everything else in the right column. */
.modal__form--rail { display: grid; grid-template-columns: 10rem minmax(0, 1fr); column-gap: var(--space-5); }
.modal__form--rail > * { grid-column: 2; }
.modal__form--rail > .tf-rail { grid-column: 1; grid-row: 1 / -1; }

.tf-rail { display: flex; flex-direction: column; gap: 2px; align-self: start; }
.tf-step {
  display: flex; align-items: baseline; gap: var(--space-2);
  font: inherit; font-size: var(--text-sm); text-align: left; cursor: pointer;
  color: var(--text-muted); background: none; border: 0;
  border-left: 2px solid transparent; padding: var(--space-2) var(--space-3);
}
.tf-step:hover { color: var(--text); }
.tf-step--active {
  color: var(--text); font-weight: var(--weight-bold);
  border-left-color: var(--color-info); background: var(--surface-sunken);
}
.tf-step__num { font-size: var(--text-xs); color: var(--text-subtle); font-variant-numeric: tabular-nums; }

/* Two even columns, so paired fields sit together instead of one per row. */
.tf-step__panel { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); align-items: start; }
.tf-step__panel > .field--grow, .tf-step__panel > .field--full { grid-column: 1 / -1; }
.tf-step__panel > .field[data-field="description"] { grid-column: 1 / -1; }
.tf-step__panel > .field--flag, .tf-step__panel > .field--check { align-self: end; }
@media (max-width: 46rem) {
  .modal__form--rail { grid-template-columns: 1fr; }
  .modal__form--rail > *, .modal__form--rail > .tf-rail { grid-column: 1; grid-row: auto; }
  .tf-rail { flex-direction: row; flex-wrap: wrap; }
  .tf-step__panel { grid-template-columns: 1fr; }
}

/* ---- 04 · sentence ------------------------------------------
   Fields flow inline, each introduced by a connecting word. THE
   WORD AND ITS FIELD ARE ONE UNIT — they were separate flex items,
   so the line wrapped between them and left "for" stranded at the
   end of a row with its field on the next. And a field's help text
   is a sentence of its own: inside a sentence it is noise, so it is
   available on demand rather than printed inline. */
.tf-sentence {
  margin: 0 0 var(--space-4); display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: var(--space-3); font-size: var(--text-base);
}
.tf-sentence__slot { display: flex; align-items: baseline; gap: var(--space-2); margin: 0; min-width: 0; }
.tf-sentence__lead { color: var(--text-muted); white-space: nowrap; align-self: flex-end; padding-bottom: var(--space-2); }
.tf-sentence__slot .field { display: flex; flex-direction: column; gap: var(--space-1); }
.tf-sentence__slot .field__label { font-size: var(--text-xs); color: var(--text-subtle); margin: 0; }
.tf-sentence__slot .field__input { min-width: 8rem; }
.tf-sentence__slot[data-slot="title"] { flex: 1 1 18rem; }
.tf-sentence__slot[data-slot="title"] .field__input { width: 100%; font-size: var(--text-lg); font-weight: var(--weight-bold); }
/* Help text belongs to the field, and inside a sentence it breaks the line it belongs to. */
.tf-sentence__slot .field__help { display: none; }

.tf-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--border);
}
.tf-strip .field__help { max-width: 40ch; }

/* ---- 06 · full sheet ----------------------------------------
   Twelve columns, three widths only. A field may not choose its own
   width — that is what stops the stair-step. */
.tf-sheet { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--space-4); align-items: start; }
.tf-sheet__cell { min-width: 0; }
.tf-sheet__cell--3 { grid-column: span 3; }
.tf-sheet__cell--6 { grid-column: span 6; }
.tf-sheet__cell--12 { grid-column: span 12; }
@media (max-width: 60rem) { .tf-sheet__cell--3 { grid-column: span 6; } }
@media (max-width: 40rem) { .tf-sheet__cell--3, .tf-sheet__cell--6 { grid-column: span 12; } }

/* The layout picker in My settings. */
.tf-pick { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--space-3); }
.tf-pick__option {
  display: flex; flex-direction: column; gap: var(--space-1); text-align: left;
  font: inherit; cursor: pointer; padding: var(--space-3);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--text);
}
.tf-pick__option:hover { border-color: var(--border-strong); }
.tf-pick__option[aria-pressed="true"] { border-color: var(--color-info); border-width: 2px; background: var(--surface-sunken); }
.tf-pick__name { font-weight: var(--weight-bold); font-size: var(--text-sm); }
.tf-pick__tag { font-size: var(--text-xs); color: var(--color-info); }
.tf-pick__detail { font-size: var(--text-xs); color: var(--text-muted); line-height: var(--leading-normal); }
