/* ─────────────────────────────────────────────────────────────────────────────
   Application Stylesheet Manifest

   1. Design system (lib/theme): fonts, tokens, reset/base,
      components, and all four themes × palettes. Configured in
      config/initializers/theme.rb; the layout swaps theme stylesheets via
      Theme::ThemeLoader.
   2. Tally-specific styles layered on top. These may only style
      app-owned classes; shared primitives come from the design system.
   ───────────────────────────────────────────────────────────────────────────── */

/* ─── Design System (lib/theme) ─────────────────────────────────────────── */
@import url("/assets/theme/theme-2cef5d6c.css");

/* ─── App Styles (layered over the design system) ───────────────────────── */

/* ─── Assistant dock ────────────────────────────────────────────────────── */

/* The always-available assistant: a full-width drawer pinned to the bottom
   edge, layered over the page. Its column is the resize grip, the centered
   icon header, then the scrollable body. Collapsed, only the header shows.
   Every color is a theme token, so all themes, palettes, and modes are covered
   with no app-side color definitions. */
.chat-dock {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  color: var(--color-ink);
  border-block-start: var(--border-style);
  box-shadow: var(--shadow-lg);
}

/* The dock is fixed to the bottom edge, so the shell reserves its height: the
   content column scrolls clear of it and the sidebar (in-flow and off-canvas)
   stops above it. The assistant-chat controller keeps --assistant-dock-height
   equal to the rendered drawer; before it runs — and whenever the drawer is
   collapsed to its header — the header's own height (icon + padding + top
   border) is the reserve. The shell reads the design system's bottom inset. */
.app-shell:has(.chat-dock) {
  --app-layout-inset-block-end:
    var(--assistant-dock-height, calc(var(--size-icon) + var(--space-xs) * 2 + var(--border-width)));
}

/* The grip on the drawer's top edge: drag (or arrow keys) to change the body
   height, which the controller remembers. Hidden while collapsed. */
.chat-dock__resizer {
  block-size: var(--space-xs);
  cursor: ns-resize;
  touch-action: none;
}

.chat-dock:has(.chat-dock__body[hidden]) .chat-dock__resizer {
  display: none;
}

.chat-dock.is-resizing {
  user-select: none;
}

/* Icon-only toggle, centered so the bot glyph reads at any dock position. The
   theme draws keyboard focus itself, so the UA outline is off — a pointer click
   must not leave a ring on the toggle after it collapses the drawer. */
.chat-dock__header {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 100%;
  padding: var(--space-xs);
  border: 0;
  outline: none;
  background: none;
  color: var(--color-ink);
  cursor: pointer;
  transition: background var(--transition-fast) ease;

  &:hover {
    background: color-mix(in srgb, var(--color-surface), var(--color-ink) 6%);
  }

  /* Focused by pointer: no ring. Focused by keyboard: the theme's ring. */
  &:focus:not(:focus-visible) {
    outline: none;
    box-shadow: none;
  }

  &:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring-style);
  }
}

/* The scrollable, y-resizable region. It is a flex column so its content fills
   the chosen height: the transcript grows and scrolls in the middle while the
   conversation header and composer stay pinned, and even a short transcript
   still anchors the composer to the bottom. */
.chat-dock__body {
  display: flex;
  flex-direction: column;
  block-size: min(60dvh, 32rem);
  overflow: hidden;
  overscroll-behavior: contain;
}

.chat-dock__body[hidden] {
  display: none;
}

/* The dock's Turbo frame fills the body. The frame's children lay out in that
   column: the session list and notice own the whole area, while a conversation
   pins its header and composer and lets the transcript grow between them. */
.chat-dock__body > turbo-frame {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-block-size: 0;
}

/* ─── Assistant dock: session list (the threads step) ───────────────────── */

/* The dock's session step: a scrolling list with the new-session bar pinned to
   its bottom edge. */
.chat-sessions {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-block-size: 0;
}

.chat-sessions__list {
  display: grid;
  align-content: start;
  flex: 1 1 auto;
  min-block-size: 0;
  overflow: auto;
}

.chat-sessions__form {
  flex: none;
  margin: 0;
}

.chat-sessions__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-l);
  inline-size: 100%;
  padding: var(--space-m) var(--space-l);
  border: 0;
  border-block-end: var(--border-style);
  background: none;
  color: inherit;
  font: inherit;
  font-weight: var(--font-normal);
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition-fast) ease;

  &:last-child {
    border-block-end: none;
  }

  /* The row lights up under the pointer — the "you're over me" cue, tinted
     with the theme's accent rather than a flat gray. */
  &:hover {
    background: color-mix(in srgb, var(--color-surface), var(--color-accent-primary) 18%);
  }

  &:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring-style);
  }
}

/* The new-session bar mirrors the dock's icon header — a centered strip — with
   a plus and the context it starts in (e.g. "+ Spring Menu"). */
.chat-sessions__new {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  inline-size: 100%;
  padding: var(--space-s);
  border: 0;
  border-block-start: var(--border-style);
  background: var(--color-surface);
  color: var(--color-ink);
  font: inherit;
  font-weight: var(--font-bold);
  cursor: pointer;
  transition: background var(--transition-fast) ease;

  &:hover {
    background: color-mix(in srgb, var(--color-surface), var(--color-ink) 6%);
  }

  &:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring-style);
  }
}

.chat-sessions__body {
  display: grid;
  gap: var(--space-1xs);
  min-inline-size: 0;
}

.chat-sessions__title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-sessions__context,
.chat-sessions__time {
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

.chat-sessions__time {
  flex-shrink: 0;
}

/* ─── Assistant dock: conversation ──────────────────────────────────────── */

.chat-conversation__header {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--space-s);
  padding: var(--space-s) var(--space-l);
  border-block: var(--border-style);
  background: var(--color-accent-primary);
  color: var(--color-ink-on-accent);
}

.chat-conversation__back {
  display: inline-flex;
  align-items: center;
  color: inherit;
  text-decoration: none;

  &:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring-style);
  }
}

.chat-conversation__heading {
  display: grid;
  gap: var(--space-1xs);
  min-inline-size: 0;
}

.chat-conversation__context {
  font-size: var(--text-xs);
  opacity: 0.85;
}

.chat-conversation__label {
  overflow: hidden;
  font-weight: var(--font-bold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-conversation__error {
  padding-inline: var(--space-l);
}

.chat-conversation__prompt {
  font-weight: var(--font-bold);
}

.chat-conversation__composer {
  display: grid;
  flex: none;
  gap: var(--space-xs);
  padding: var(--space-m) var(--space-l);
  background: var(--color-surface);
  border-block-start: var(--border-style);
}

/* ─── Assistant dock: not configured ────────────────────────────────────── */

.chat-notice {
  display: grid;
  gap: var(--space-m);
  justify-items: start;
  padding: var(--space-l);
}

.chat-notice__text {
  margin: 0;
}

/* Auto-contrast for the accent conversation header. */
@supports (color: color-contrast(red vs white, black)) {
  .chat-conversation__header {
    color: var(--color-ink-on-primary);
  }
}

/* ─── Assistant dock: transcript ────────────────────────────────────────── */

/* The transcript grows to fill the dock body and scrolls internally, so the
   conversation header and composer stay put and the composer anchors to the
   bottom even for a short transcript. Messages stack from the top rather than
   spreading. On the standalone conversation page the parent is not a flex
   column, so it simply grows to its content. */
.chat-thread {
  display: grid;
  align-content: start;
  flex: 1 1 auto;
  gap: var(--space-l);
  min-block-size: 0;
  overflow: auto;
  padding: var(--space-l);
}

.chat-thread__message {
  border-inline-start: var(--border-size-thick) solid var(--color-ink-faded);
  padding-inline-start: var(--space-m);
}

.chat-thread__message--user {
  border-color: var(--color-accent-primary);
}

.chat-thread__body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A brand-new transcript: the placeholder stands in for the messages and
   centers itself in the space the first turn will fill. */
.chat-thread__placeholder {
  display: grid;
  justify-items: center;
  gap: var(--space-s);
  padding-block: var(--space-2xl);
  color: var(--color-ink-faded);
  text-align: center;
}

.chat-thread__placeholder-icon {
  inline-size: var(--space-xl);
  block-size: var(--space-xl);
  opacity: 0.5;
}

.chat-thread__placeholder-text {
  max-inline-size: 44ch;
  margin: 0;
}

.chat-thread:has(> .chat-thread__placeholder) {
  align-content: center;
}

.chat-thread__approval,
.chat-thread__error {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-s);
  padding: var(--space-m);
  border: var(--border-style);
}

.chat-thread__approval pre {
  align-self: stretch;
  max-height: 14rem;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.chat-thread__status {
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

.chat-page {
  max-width: 60rem;
  margin-inline: auto;
}

/* ─── Action brief ──────────────────────────────────────────────────────── */
.brief__subtitle {
  color: var(--color-ink-faded);
}

.brief__summary p {
  margin-block: 0 var(--space-s);

  &:last-child {
    margin-block-end: 0;
  }
}

.brief__summary ul {
  margin-block: 0 var(--space-s);
  padding-inline-start: var(--space-l);
  list-style: disc;
}

.brief__summary li {
  margin-block-end: var(--space-xs);

  &:last-child {
    margin-block-end: 0;
  }
}

.brief-section__heading {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

/* A stat tile that jumps to its section: the count is the day's at-a-glance
   summary, so clicking it lands on the highlights it tallies. The link resets
   the base anchor (no underline bleeding into the tile) and carries the
   scroll offset so a targeted section clears the sticky topbar. */
.brief-stat {
  display: block;
  color: inherit;
  text-decoration: none;
}

.brief-section {
  scroll-margin-block-start: var(--space-l);
}

.brief-list {
  display: grid;
  gap: var(--space-l);
}

/* The rule between highlights sits off the text and the list gap carries the
   rhythm to the next item, mirroring the work rows. */
.brief-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-l);
  padding-block-end: var(--space-l);
  border-block-end: var(--border-style);

  &:last-child {
    border-block-end: none;
    padding-block-end: 0;
  }
}

.brief-item__body {
  display: grid;
  gap: var(--space-1xs);
  min-inline-size: 0;
}

.brief-item__title {
  margin: 0;
  font-size: var(--text-lg);
}

.brief-item__client {
  margin: 0;
  font-size: var(--text-sm);
}

.brief-item__client a {
  font-weight: var(--font-bold);
}

.brief-item__reason {
  color: var(--color-ink-faded);
}

.brief-item__actions {
  display: flex;
  flex-shrink: 0;
  gap: var(--space-s);
}

/* Narrow brief: the stat rail leads, so the day's counts read before the
   narrative and the highlights. Breakpoint matches .detail__grid.
   Breakpoint: var(--bp-detail) = 896px (literal; var() is unsupported in size
   queries). */
@container (width < 896px) {
  .brief .detail__aside {
    order: -1;
  }
}

/* ─── Work list ─────────────────────────────────────────────────────────── */

/* Work list color strategy — one color per meaning, so a chip never means two
   things. Status is a neutral state chip (default tone: surface + ink). Danger
   is reserved for overdue, warning for needs-a-reply. Client and origin are
   plain text, not chips. All colors come from the theme tokens
   (`--color-*`), so they exist for every theme, palette, and light/dark mode
   without any app-side color definitions. */
.work__subtitle {
  color: var(--color-ink-faded);
}

/* The chip filter form sits between the subtitle and the work list; it carries
   its own bottom margin so it does not crowd the rows that follow. */
.work__filters {
  margin-block-end: var(--space-l);
}

.work__heading {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

/* The row list is a single-column grid with 1fr auto-rows: with an indefinite
   height every row resolves to the tallest row's content height, so all
   discussions and tasks render at one adaptive height — no fixed height, no
   JS. The list gap supplies the row rhythm; a row carries no padding of its
   own, so the card padding never doubles. */
.task-list {
  display: grid;
  gap: var(--space-l);
  grid-auto-rows: 1fr;
  container-type: inline-size;
}

/* The add/edit task form, an app-owned composition: the operator's fields
   stack on the form-fields rhythm, and the form closes its section — a rule
   off the task list (or its empty line) above, air toward the provenance
   description list below. */
.task-list + .task-form,
.detail-empty + .task-form {
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-l);
  border-block-start: var(--border-style);
}

.task-form + dl {
  margin-block-start: var(--space-l);
}

.task-row {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: var(--space-l);
  padding-block-end: var(--space-l);
  border-block-end: var(--border-style);

  &:last-child {
    border-block-end: none;
    padding-block-end: 0;
  }
}

.task-row__body {
  display: grid;
  align-content: start;
  gap: var(--space-1xs);
  min-inline-size: 0;
}

.task-row__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-m);
  margin-block-end: var(--space-s);
}

/* References in the meta row — the client — are plain
   text, not chips, so the chips stay few and loud. */
.task-row__meta-text {
  font-size: var(--text-sm);
}

.task-row__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-block-end: var(--space-s);
}

.task-row__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-s);
  margin: 0;
  font-size: var(--text-lg);

  /* The urgency meter sits before the title and fills its height, so the
     segments read as a full-height status bar on the row's headline. */
  .urgency-meter {
    align-self: stretch;
    align-items: stretch;

    .urgency-meter__segment {
      block-size: auto;
      align-self: stretch;
    }
  }
}

.task-row__client a {
  font-weight: var(--font-bold);
}

/* The client leads the side cluster, sitting on the same line as the meta
   row. The side's gap is the block rhythm between cluster items; a meta line
   cancels it out and lands on the smallest spacing above the line it follows,
   so the client, its project, and the waiting line read as one block before
   the action. */
.task-row__side .task-row__client,
.task-row__side .task-row__meta-text:has(+ .task-row__waiting) {
  margin-block-end: calc(var(--space-1xs) - var(--space-m));
}

.task-row__notes,
.task-detail__notes {
  color: var(--color-ink-faded);
}

/* The note is a quiet sub-line under the title: small type, a hair of top
   spacing from the title, and no bottom margin (the body grid provides the
   gap to the next block). */
.task-row__notes {
  font-size: var(--text-sm);
  margin-block-start: var(--space-1xs);
  margin-block-end: 0;
}

.task-detail__notes {
  margin-block: var(--space-m);
}

/* The due date / waiting label sit on the same line as the meta row's badges,
   so they share the badge's height envelope and gap to read as one line. */
.task-row__due,
.task-row__waiting {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding-block: var(--space-1xs);
  line-height: 1;
  font-size: var(--text-sm);
}

/* Overdue due dates flag the work that already passed. */
.task-row__due--overdue {
  color: var(--color-danger);
  font-weight: var(--font-bold);
}

/* How long the client has been waiting for a reply on a reply-due topic. */
.task-row__waiting {
  color: var(--color-ink-faded);
}

/* The triage rationale callout: why this topic ranks, at a glance. Shrinks
   to its content rather than stretching across the row. */
.task-row__rationale {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  margin: var(--space-s) 0 var(--space-xs);
  padding: var(--space-s) var(--space-m);
  width: fit-content;
  border-inline-start: var(--border-size-thick) solid var(--color-warning);
  background: color-mix(in srgb, var(--color-warning), var(--color-surface) 88%);
  color: var(--color-ink);
  font-size: var(--text-sm);
}

.task-row__rationale-icon {
  flex-shrink: 0;
  margin-block-start: 0.125rem;
  color: var(--color-warning);
}

/* The work summary under a topic: icon-led counts of what is proposed, open,
   and late, plus the next due date. Pinned to the bottom of the row so it
   reads against the action on the right; the narrow container query keeps it
   in the stacked order instead. */
.task-row__workload {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-m);
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-ink-faded);
}

.task-row__workload .icon {
  inline-size: 1rem;
  block-size: 1rem;
}

.task-row__workload-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1xs);
}

.task-row__workload-item--danger {
  color: var(--color-danger);
  font-weight: var(--font-bold);
}

/* The two columns stretch to the row height, so a discussion and a task row
   share the same content-driven height and the action sits on the bottom line
   opposite the work summary. No fixed height anywhere.
   Breakpoint: var(--bp-work-row) = 780px (literal; var() is unsupported in
   size queries). */
@container (width >= 780px) {
  .task-row:has(.task-row__workload) .task-row__body {
    display: flex;
    flex-direction: column;
  }

  .task-row:has(.task-row__workload) .task-row__workload {
    margin-block-start: auto;
  }

  .task-row__side > .button,
  .task-row__side > .task-row__actions,
  .task-row__side > .composer__actions {
    margin-block-start: auto;
  }
}

.task-row__actions {
  display: flex;
  flex-shrink: 0;
  gap: var(--space-s);
}

/* The start-timer form is pure transport: display: contents promotes its
   button into the action cluster, so the fixed square keeps its size when the
   labelled buttons beside it stretch to share the line at narrow widths. */
.task-row__timer {
  display: contents;
}

/* Right-aligned cluster: due date / waiting label above the actions. */
.task-row__side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-m);
  flex-shrink: 0;
}

/* Stacked rows rebuild the card: the due/waiting line leads, then the title
   block, then the actions. Flattening the body and side clusters into the
   row's column flow lets order place the due date above the title and keeps
   the action buttons on their own full-width line, equal in size.
   Breakpoint: var(--bp-work-row) = 780px (literal; var() is unsupported in
   size queries). */
@container (width < 780px) {
  .task-row {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-s);
  }

  .task-row__body,
  .task-row__side {
    display: contents;
  }

  .task-row__meta {
    order: 0;
    margin-block-end: 0;
  }

  .task-row__due,
  .task-row__waiting {
    order: 1;
    margin-block-end: calc(var(--space-1xs) - var(--space-s));
  }

  .task-row__title {
    order: 2;
  }

  .task-row__notes,
  .task-row__rationale,
  .task-row__workload {
    order: 3;
  }

  .task-row__notes {
    margin-block: 0;
  }

  .task-row__rationale {
    margin-block: 0;
  }

  .task-row__side .task-row__client {
    order: 1;
    margin-block: 0 calc(var(--space-1xs) - var(--space-s));
  }

  .task-row__actions,
  .task-row__side > .button {
    order: 4;
  }

  .task-row__actions .button:not(.button--icon) {
    inline-size: 100%;
  }
}

/* ─── Urgency meter ─────────────────────────────────────────────────────── */
.urgency-meter {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  align-self: center;
}

.urgency-meter__segment {
  inline-size: 0.375rem;
  block-size: 0.875rem;
  border-radius: var(--radius-s);
  background: color-mix(in srgb, var(--color-ink), transparent 88%);
}

.urgency-meter__segment--on {
  background: var(--color-accent-primary);
}

.urgency-meter--severity-medium .urgency-meter__segment--on {
  background: var(--color-warning);
}

.urgency-meter--severity-high .urgency-meter__segment--on {
  background: var(--color-danger);
}

/* Inside a page title the meter fills the headline's full height and sits off
   the title text — the header title is a flex row (see header.css), so the
   meter stretches to the row and the segments run edge to edge. */
.header__title .urgency-meter {
  flex: none;
  align-self: stretch;
  align-items: stretch;

  .urgency-meter__segment {
    block-size: auto;
    align-self: stretch;
  }
}

/* ─── Attachments ───────────────────────────────────────────────────────── */
.attachment-list {
  display: grid;
  gap: var(--space-m);
}

.attachment {
  display: flex;
  gap: var(--space-m);
  padding: var(--space-m);
  border: var(--border-style);
  background: var(--color-surface);
  color: var(--color-ink);
  text-decoration: none;
}

.attachment:hover {
  background: color-mix(in srgb, var(--color-accent-secondary), var(--color-surface) 92%);
}

.attachment__icon {
  color: var(--color-accent-primary);
}

.attachment__body {
  display: grid;
  gap: var(--space-1xs);
}

.attachment__name {
  font-weight: var(--font-bold);
}

.attachment__meta {
  font-size: var(--text-sm);
  color: var(--color-ink-faded);
}

.attachment__transcript {
  margin: 0;
  padding: var(--space-s) var(--space-m);
  border-inline-start: var(--border-style);
  color: var(--color-ink-faded);
}

.attachment-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
  gap: var(--space-xs);
}

.media-attachment {
  display: grid;
  gap: var(--space-s);
  min-inline-size: 0;
  padding: var(--space-m);
  border: var(--border-style);
  background: var(--color-surface);
}

.media-attachment__player {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.media-attachment__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-s);
  font-size: var(--text-sm);
}

.media-attachment__name {
  font-weight: var(--font-bold);
}

.media-attachment__duration,
.media-attachment__download {
  color: var(--color-ink-faded);
  text-decoration: none;
}

.media-attachment__transcript {
  margin: 0;
  padding: var(--space-s) var(--space-m);
  border-inline-start: var(--border-style);
  color: var(--color-ink-faded);
}

.media-attachment__error {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-danger);
}

.media-attachment__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
}

.attachment-tile {
  display: block;
  aspect-ratio: 1 / 1;
  border: var(--border-style);
  background: var(--color-surface);
  overflow: hidden;
  color: var(--color-ink);
  text-decoration: none;
}

.attachment-tile:hover {
  border-color: var(--color-accent-primary);
}

.attachment-tile__thumb {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.attachment-tile__placeholder {
  display: grid;
  place-items: center;
  gap: var(--space-1xs);
  inline-size: 100%;
  block-size: 100%;
  color: var(--color-ink-faded);
}

.attachment-tile__filetype {
  font-size: var(--text-xs);
  font-weight: var(--font-black);
  letter-spacing: 0.08em;
}

/* ─── Discussions & messages ────────────────────────────────────────────── */
.stack {
  display: grid;
  gap: var(--space-l);
}

.discussion__heading {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-m);
}

.discussion__title {
  margin: 0;
  font-size: var(--text-lg);
}

.discussion__project {
  margin-block-start: var(--space-1xs);
  margin-block-end: 0;
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

.discussion__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.discussion__messages {
  display: grid;
  gap: var(--space-m);
}

.message {
  display: grid;
  gap: var(--space-1xs);
  max-inline-size: 46rem;
  min-inline-size: 0;
  padding: var(--space-m);
  border: var(--border-style);
  background: var(--color-surface);
}

.message--outgoing {
  margin-inline-start: auto;
  background: color-mix(in srgb, var(--color-accent-secondary), transparent 85%);
}

.message__meta {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  font-size: var(--text-sm);
}

.message__sender {
  font-weight: var(--font-bold);
}

.message__time {
  color: var(--color-ink-faded);
}

.message__body {
  overflow-wrap: anywhere;
}

.message__body--plain {
  white-space: pre-wrap;
}

.message__body--rich img {
  max-width: 100%;
  height: auto;
}

.message__body--rich a {
  color: var(--color-accent-primary);
}

.message__original {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1xs);
  margin-top: var(--space-1xs);
  font-size: var(--text-sm);
  color: var(--color-ink-faded);
  text-decoration: none;
}

.message__original:hover {
  color: var(--color-accent-primary);
}

/* ─── Client timeline ────────────────────────────────────────────────────
   One line per event: a fixed time column, the event, then its links. Rows
   wrap on narrow screens rather than overflow. */
.timeline {
  display: grid;
  gap: var(--space-s);
  margin: 0;
  padding: 0;
  list-style: none;
}

.timeline__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-s);
}

.timeline__time {
  flex: 0 0 auto;
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.timeline__event {
  flex: 1 1 auto;
  min-inline-size: 0;
}

.timeline__links {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  font-size: var(--text-sm);
}

.timeline__links a {
  color: var(--color-accent-primary);
}

/* ─── Original message (email-client view) ──────────────────────────────── */
.original__envelope {
  display: grid;
  grid-template-columns: minmax(3.5rem, max-content) 1fr;
  gap: var(--space-1xs) var(--space-l);
  align-items: baseline;
  border-block-end: var(--border-style);
  font-size: var(--text-sm);
}

.original__envelope dt {
  color: var(--color-ink-faded);
  font-size: var(--text-xs);
  font-weight: var(--font-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.original__envelope dd {
  overflow-wrap: anywhere;
}

.original__body {
  overflow-wrap: anywhere;
}

.original__body img {
  max-width: 100%;
  height: auto;
}

.original__body a {
  color: var(--color-accent-primary);
}

.original__attachments {
  display: flex;
  flex-direction: column;
  gap: var(--space-1xs);
  border-block-start: var(--border-style);
}

/* ─── Provenance ────────────────────────────────────────────────────────── */
.provenance {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1xs);
  font-size: var(--text-sm);
  color: var(--color-ink-faded);
}

/* Standing alone in a detail aside, the provenance line aligns with the card
   text above it; inside a card the card's own padding does the job. */
.detail__aside > .provenance {
  padding-inline: var(--space-l);
}

/* Closing a discussion card, the provenance line sits apart from the thread. */
.discussion .provenance {
  margin-block: var(--space-l) 0;
}

.provenance__icon {
  color: var(--color-accent-primary);
}

.provenance-list {
  display: grid;
  gap: var(--space-xs);
}

/* ─── Composer ──────────────────────────────────────────────────────────── */
.composer__hint {
  color: var(--color-ink-faded);
}

.composer__input {
  display: block;
  inline-size: 100%;
  box-sizing: border-box;
  margin-block: var(--space-m);
  padding: var(--space-m);
  font: inherit;
  border: var(--border-style);
  background: var(--color-surface);
  resize: vertical;
}

.composer__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
}

.composer__hint--warning {
  color: var(--color-accent-tertiary);
}

.composer__history {
  margin-block: var(--space-l) 0;
  padding-block-start: var(--space-m);
  border-block-start: var(--border-style);
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

.composer__history summary {
  cursor: pointer;
}

.composer__version {
  padding-block-start: var(--space-s);
}

.composer__version p {
  white-space: pre-wrap;
}

/* The chat-style composer: the reply input sits under the conversation like a
   chat, separated from the messages above by a rule rather than its own card. */
.composer--chat {
  display: grid;
  gap: var(--space-m);
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-l);
  border-block-start: var(--border-style);
  position: relative;
}

/* The on-demand "suggest a reply" affordance: a bare icon floating inside the
   textarea's top-right corner, so the assist rides the input without a button
   or toolbar row of its own. */
.composer__suggest {
  position: absolute;
  top: calc(var(--space-xl) + var(--space-xs));
  right: var(--space-1xs);
}

/* No button behavior: no lift, no shadow, no color fade — the only feedback is
   the icon itself picking up the accent color. */
.composer__suggest .button {
  transition: none;
}

.composer__suggest .button:hover:not(:disabled),
.composer__suggest .button:active:not(:disabled) {
  transform: none;
  box-shadow: none;
  color: var(--color-ink);
}

.composer__suggest .icon {
  transition: color var(--transition-fast) ease;
}

.composer__suggest .button:hover .icon {
  color: var(--color-accent-primary);
}

/* The card owns the padding on the discussion page; on the task page the
   composer is a bare block, so it brings its own inset to line up with the
   conversation card above it. */
.detail__main > .composer--chat {
  padding-inline: var(--space-l);
  padding-block-end: var(--space-l);
}

/* ─── Detail pages (discussion, task) ───────────────────────────────────── */

/* A detail page reads as one main column of working content beside a quiet
   column of context, instead of a tall stack of full-width cards. The grid
   collapses to a single column until there is room for the aside.
   Breakpoint: var(--bp-detail) = 896px (literal; var() is unsupported in size
   queries). */
.detail {
  container-type: inline-size;
}

.detail__grid {
  display: grid;
  gap: var(--space-l);
  align-items: start;
}

@container (width >= 896px) {
  .detail__grid {
    grid-template-columns: minmax(0, 1fr) minmax(18rem, 22rem);
  }
}

.detail__main,
.detail__aside {
  display: grid;
  gap: var(--space-l);
  align-content: start;
  min-inline-size: 0;
}

/* The who/when/what line under the page header: quiet, wrapping, and
   separated by middots, so the header stays the loudest thing on the page.
   The middots trail each fact (except the last) instead of leading the next,
   so a wrapped line can never start with a stray separator. */
.detail__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1xs) 0;
  margin-block: calc(var(--space-l) * -0.5) var(--space-l);
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

.detail__meta-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1xs);
}

.detail__meta-item:not(:last-child)::after {
  content: "·";
  margin-inline: var(--space-s);
  color: var(--color-ink-faded);
}

.detail__meta a {
  color: var(--color-ink);
}

/* A due date that already passed flags itself in the meta line. */
.detail__meta-item--overdue {
  color: var(--color-danger);
  font-weight: var(--font-bold);
}

/* A narrow content column keeps the meta on wrapping rows instead of one
   fact per line: the client and its project sit together, the status chips
   group on shared rows, and wrapped facts never begin with a stray separator
   (no middots here at all). Because the middots are gone, the wrapped items
   get their own column gap so adjacent facts never collide on a shared row.
   The line also stops pulling itself up against the header, so the header's
   bottom margin is fully respected above it. Queried against the content
   column (main), not the viewport.
   Breakpoint: --device-md = 768px (literal; var() is unsupported in size
   queries). */
@container (width <= 768px) {
  .detail__meta {
    gap: var(--space-xs) var(--space-s);
    margin-block-start: 0;
  }

  .detail__meta-item:not(:last-child)::after {
    content: none;
  }
}

/* The topic's running summary, an agent-written lead paragraph between the
   meta line and the working cards. Quiet, but distinct from the meta so it
   reads as the discussion's abstract, not another fact. */
.detail__summary {
  margin-block: 0 var(--space-l);
  max-inline-size: 65ch;
}

/* A quiet inline empty state inside a detail section. */
.detail-empty {
  margin: 0;
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

/* Actions that close a plain card section (filing, flags), with the same
   rhythm as the composer actions but without the composer's own insets. */
.detail__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-block-start: var(--space-l);
}

/* ─── Send-from addresses ───────────────────────────────────────────────── */
.send-from {
  display: grid;
  margin-block-end: var(--space-l);
}

.send-from__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  padding-block: var(--space-s);
  border-block-end: var(--border-style);

  &:last-child {
    border-block-end: none;
  }
}

.send-from__main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}

.send-from__address {
  font-weight: 600;
}

.send-from__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}

.send-from__code {
  width: 7rem;
}

/* ─── Connection health ─────────────────────────────────────────────────── */
/* Why a connection is failing: the ingest error reads as danger, a broken send
   path as a warning (ingest still runs when only sending fails). */
.connection-health__error,
.connection-health__warning {
  margin: 0;
}

.connection-health__error {
  color: var(--color-danger);
}

.connection-health__warning {
  color: var(--color-warning);
}

.connection-health__error + .connection-health__warning {
  margin-block-start: var(--space-s);
}

/* ─── Folders to watch (connection folder tree) ─────────────────────────── */
/* The cached mailbox hierarchy as a native checkbox tree: a nested list with
   one independent checkbox per folder, so checking a node never implies its
   children (IMAP has no such cascade). Branches collapse on their chevron
   (explorer-style), indentation marks depth, and every checkbox lines up at
   the right edge regardless of depth. */
.folder-tree {
  display: grid;
  gap: var(--space-1xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.folder-tree .folder-tree {
  margin-block-start: var(--space-1xs);
  margin-inline-start: var(--space-l);
}

.folder-tree__node {
  display: grid;
  gap: var(--space-1xs);
}

/* A collapsed branch hides its subfolders; the chevron turns to point right. */
.folder-tree__node--collapsed > .folder-tree {
  display: none;
}

.folder-tree__row {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.folder-tree__toggle,
.folder-tree__spacer {
  flex: none;
  inline-size: var(--size-icon);
  block-size: var(--size-icon);
}

/* Bare chevron control: no chrome, the glyph carries the affordance. */
.folder-tree__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-ink-faded);
  cursor: pointer;
}

.folder-tree__chevron {
  inline-size: 1rem;
  block-size: 1rem;
  transform: rotate(90deg);
  transition: transform var(--transition-fast) ease;
}

.folder-tree__node--collapsed > .folder-tree__row .folder-tree__chevron {
  transform: rotate(0deg);
}

/* The label spans the row so the checkbox hugs the right edge, aligned across
   every depth; the folder icon and name stay at the start. */
.folder-tree__label {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  min-inline-size: 0;
  cursor: pointer;
}

.folder-tree__name-wrap {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-inline-size: 0;
}

.folder-tree__icon {
  flex: none;
  inline-size: 1rem;
  block-size: 1rem;
  color: var(--color-ink-faded);
}

.folder-tree__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The composer's send-from picker: shown only when the connection offers more
   than the login address, so the 80% composer is unchanged. */
.composer__from {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  margin-block-end: var(--space-s);
  font-size: var(--text-sm);
}

/* ─── Working notes ─────────────────────────────────────────────────────── */
.note-list {
  display: grid;
}

.note {
  padding-block: var(--space-m);
  border-block-end: var(--border-style);
}

.note:first-child {
  padding-block-start: 0;
}

.note:last-child {
  padding-block-end: 0;
  border-block-end: none;
}

.note__body {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.note__meta {
  margin: 0;
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

.note__body + .note__meta {
  margin-block-start: var(--space-xs);
}

/* The add-note form closes the notes section, separated by a rule from the
   list above it. */
.note-form {
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-l);
  border-block-start: var(--border-style);
}

.note-form__input {
  display: block;
  inline-size: 100%;
  box-sizing: border-box;
  padding: var(--space-m);
  font: inherit;
  border: var(--border-style);
  background: var(--color-surface);
  resize: vertical;
}

.note-form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-block-start: var(--space-s);
}

/* ─── Topbar global timer ─────────────────────────────────────────────── */
/* The account's always-visible timer between the burger and the search: a
   squared chip that owns start/pause/resume/stop and a tabular ticking clock.
   It mirrors the search input — surface, border, and shadow — so the two read
   as one topbar control; the same padding-block keeps them the same height.
   The subject ellipses; the clock never wraps. */
.timer-bar {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  inline-size: 100%;
  padding-block: var(--space-s);
  padding-inline: var(--space-m);
  border: var(--border-style);
  border-radius: var(--radius-s);
  background: var(--color-surface);
  box-shadow: var(--shadow-md);
  white-space: nowrap;
  min-width: 0;
}

.timer-bar__clock {
  font-variant-numeric: tabular-nums;
  font-weight: var(--font-bold);
}

.timer-bar__subject {
  max-inline-size: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

/* Anchor the controls to the bar's trailing edge: the bar spans the topbar's
   free space, so the clock and subject lead and the actions trail. */
.timer-bar__actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  margin-inline-start: auto;
}

/* The action forms are pure transport; letting them generate boxes would add
   line-height above the icon buttons and push the bar past the search input's
   height. display: contents promotes each button to a direct flex child. */
.timer-bar__actions form {
  display: contents;
}

/* Bare icon control: no background, border, or shadow — the glyph carries
   the affordance, so the bar reads as chrome rather than a stack of buttons. */
.timer-bar__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  block-size: var(--size-icon);
  border: none;
  background: none;
  box-shadow: none;
  color: var(--color-ink);
  line-height: 0;
  cursor: pointer;
}

.timer-bar__button--start {
  color: var(--color-success);
}

.timer-bar__button--pause {
  color: var(--color-warning);
}

.timer-bar__button--stop {
  color: var(--color-danger);
}

.timer-bar__button--start:hover:not(:disabled) {
  color: color-mix(in srgb, var(--color-success), var(--color-ink) 30%);
}

.timer-bar__button--pause:hover:not(:disabled) {
  color: color-mix(in srgb, var(--color-warning), var(--color-ink) 30%);
}

.timer-bar__button--stop:hover:not(:disabled) {
  color: color-mix(in srgb, var(--color-danger), var(--color-ink) 30%);
}

.timer-bar__button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.timer-bar__button:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring-style);
}

/* The settled tracked-total on a discussion's time card. */
.time-total {
  margin-block: 0 var(--space-m);
  font-size: var(--text-lg);
  font-weight: var(--font-bold);
}

/* ─── Flags (needs attention) ───────────────────────────────────────────── */
.flag {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-s);
  padding-block: var(--space-s);
}

.flag + .flag {
  border-block-start: var(--border-style);
}

.flag__detail {
  color: var(--color-ink);
}

/* ─── Entity lists (directories) ─────────────────────────────────────────── */
/* A directory index (clients, projects, connections, admin accounts and
   identities) is a flat list of rows: a linked name, its attributes as meta
   and body lines, and the actions on the right. It is the counterpart to the
   work rows (.task-row), which carry urgency and workload instead of a
   plain attribute set. */
.list {
  display: grid;
  container-type: inline-size;
}

.list-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-l);
  padding-block: var(--space-l);
  border-block-end: var(--border-style);

  &:last-child {
    border-block-end: none;
  }
}

.list-row__body {
  display: grid;
  gap: var(--space-1xs);
  min-inline-size: 0;
}

.list-row__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-m);
  margin-block-end: var(--space-xs);
}

.list-row__title {
  margin: 0;
  font-size: var(--text-lg);
}

.list-row__subtitle {
  margin: 0;
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

.list-row__side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-s);
  flex-shrink: 0;
}

.list-row__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-s);
}

/* Breakpoint: var(--bp-work-row) = 780px (literal; var() is unsupported in
   size queries). */
@container (width < 780px) {
  .list-row {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-s);
  }

  .list-row__side {
    align-items: flex-start;
  }

  .list-row__actions {
    justify-content: flex-start;
  }
}



/* Settings & the remote API page: consistent stacked cards and a plain,
   no-fieldset checkbox group for token capabilities. */
.settings__empty {
  color: var(--color-muted);
}

.settings__save {
  margin-block-start: var(--space-m);
}

.api-tokens__form {
  display: grid;
  gap: var(--space-m);
  max-width: var(--bp-work-row);
}

.api-tokens__scopes-title {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
}

.api-tokens__scopes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s) var(--space-l);
}

.api-tokens__scope {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

/* The Agents page: the token form sits beneath the issued list in one card, and
   each app-called capability is its own card with a status badge in the
   header. */
.agent-tokens__form {
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-l);
  border-block-start: 1px solid var(--color-surface);
}

.agent-tokens__form-title {
  margin: 0 0 var(--space-s);
  font-size: var(--text-md);
}

/* The one-time panel for a freshly issued token: a highlighted block at the
   top of the token card with the masked value and its copy control. */
.agent-tokens__new {
  display: grid;
  gap: var(--space-s);
  margin-block-end: var(--space-l);
  padding: var(--space-m);
  border: 1px solid var(--color-accent-primary);
  border-radius: var(--radius-m);
  background: var(--color-surface);
}

.agent-tokens__new-title {
  margin: 0;
  font-size: var(--text-md);
}

.agent-tokens__new-hint,
.agent-tokens__new-warning {
  margin: 0;
  font-size: var(--text-sm);
}

.agent-tokens__new-warning {
  color: var(--color-danger);
}

.agent-tokens__new-value {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  flex-wrap: wrap;
}

.agent-tokens__new-mask {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.1em;
}

.agent-provider__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-s);
}

.form-field__hint--error {
  color: var(--color-danger);
}

/* The search results page: one card per kind, each holding the ranked hits.
   Cards stack on the app's `.stack` rhythm; a hit is a directory-style row
   (search-result) with the record's title, the query's highlighted snippet,
   and a meta line naming the owner client. The search field closes its section
   so the summary line under it has air. */
.search__form {
  margin-block-end: var(--space-l);
}

.search-results {
  display: grid;
}

.search-section__count {
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

.search-result {
  padding-block: var(--space-l);
  border-block-end: var(--border-style);

  &:last-child {
    border-block-end: none;
  }
}

.search-result__title {
  margin: 0;
  font-size: var(--text-lg);

  mark {
    padding: 0;
    background: none;
    color: inherit;
    font-weight: var(--font-weight-semibold);
    text-decoration: underline;
  }
}

.search-result__snippet {
  margin: var(--space-1xs) 0 0;
  color: var(--color-ink-faded);
  font-size: var(--text-sm);

  mark {
    padding: 0;
    background: none;
    color: var(--color-ink);
  }
}

.search-result__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1xs) 0;
  margin: var(--space-1xs) 0 0;
  color: var(--color-ink-faded);
  font-size: var(--text-sm);
}

/* The middots trail each fact (except the last), like the detail meta line,
   so a wrapped line can never start with a stray separator. */
.search-result__meta > span:not(:last-child)::after {
  content: "·";
  margin-inline: var(--space-s);
  color: var(--color-ink-faded);
}

/* ─── Contacts ───────────────────────────────────────────────────────────── */

/* A person's handles on the client page: each handle is a badge paired with a
   small remove button, grouped tight so the meta line's own gap separates the
   handles, not the parts of one handle. */
.handle-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

/* The inline "add a handle" form on a contact row (and the first-handle fields
   on the new-contact form): the kind select, the value field, and the submit
   sit on one wrapping line with a real gap instead of flowing flush. */
.handle-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-s);
}

/* ─── Admin memberships ───────────────────────────────────────────────────── */
/* The add-member form and each row's inline role editor: labelled selects and
   a button on one wrapping line, the button aligned to the inputs' baseline.
   The add form closes its section above the member list. */
.membership-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-s) var(--space-m);
}

.membership-form--add {
  margin-block-end: var(--space-l);
}
