/* App-level styles for screens on the design-system shell.

   Everything visual comes from htmx-ds; this file is only for the few
   decisions the design system deliberately leaves to the app — where a
   card sits on an empty page, and nothing else. It loads AFTER the
   design system's stylesheet and must never restyle a component: if a
   rule here starts with a DS class name, the change belongs in htmx-ds.

   Kept separate from app.css on purpose. That file is the legacy UI's
   1,700 lines, and it defines .btn/.card/.chip of its own — loading it
   beside the design system would have them fight. It goes away with the
   last unmigrated screen; this file stays. */

.auth-wrap {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 1.5rem;
}

.auth-card {
  width: min(24rem, 100%);
}

.auth-form {
  display: grid;
  gap: 1rem;
}

.auth-submit {
  width: 100%;
}

/* The title row every screen opens with. The design system has no opinion
   about page composition — where a heading sits relative to its actions is
   the app's call — so this is one of the few rules that belongs here. */
.ui-page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.ui-page-head h1 {
  margin: 0;
}

.ui-page-head__actions {
  display: flex;
  gap: 0.5rem;
}

/* A single-column form. Fields bring their own spacing; this only stacks
   them and keeps the submit from stretching across a wide screen. */
.ui-form {
  display: grid;
  gap: 1rem;
  max-width: 32rem;
  justify-items: start;
}

/* Fields fill the form's width; only the submit sits at its natural size.
   Without this every button stretches across the column, which reads as a
   primary action even where it is not. */
.ui-form > .field {
  width: 100%;
}

/* Stacked page sections — a profile is several cards down one page. Like
   .ui-page-head this is page composition, which the design system
   deliberately leaves to the app. */
.ui-sections {
  display: grid;
  gap: 1.5rem;
}

/* A region that renders nothing still has to exist, so an out-of-band swap
   has something to land on — but it must not leave a gap the size of a
   card behind it while it is empty. */
.ui-sections > :empty {
  display: none;
}

/* A card heading that carries a status pill beside its title. */
.ui-card-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* Identity row: avatar beside name + email. */
.ui-identity {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.ui-identity__name {
  margin: 0;
  font-weight: 600;
}

.ui-identity p + p {
  margin: 0.15rem 0 0;
}

/* Buttons sharing a table cell — invite accept/reject, token rotate/delete.
   They wrap rather than force the table into a horizontal scroll. */
.ui-cell-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* A token's raw value beside its copy button. The value is long and must
   not widen the table, so it scrolls inside its own cell. */
.ui-token-value {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.ui-token-value code {
  overflow-x: auto;
  white-space: nowrap;
}

/* Checkbox rows (which boards a token reaches). The design system styles
   the control; this only pairs it with its text. */
.ui-checklist {
  display: grid;
  gap: 0.5rem;
}

.ui-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* The pending-invite bubble in the shell's action strip: a link wrapping a
   Badge, which needs nothing from us but to stop looking like body text. */
.ui-invites {
  display: inline-flex;
  text-decoration: none;
}

/* A page built from stacked regions — cards, a table, a message slot.
   Same reasoning as .ui-page-head: how a screen's sections sit relative
   to one another is composition, which the design system leaves to the
   app. */
.ui-sections {
  display: grid;
  gap: 1.5rem;
}

/* The same, one level in: the regions stacked inside a card's content. */
.ui-stack {
  display: grid;
  gap: 1.25rem;
}

/* Regions that are out-of-band swap targets stay in the document even with
   nothing to show, so a handler always has something to swap into — an
   empty #team-invites, an empty #assign-picker, an empty message slot.
   Without this they still claim a grid row, and the gap around a box with
   no content reads as a layout bug. */
.ui-sections > :empty,
.ui-stack > :empty {
  display: none;
}

/* The controls belonging to one row of a table. They wrap on a narrow
   screen rather than push the row into a horizontal scroll. */
.ui-row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* The column those controls live in carries buttons, not data. Pushing them
   to the trailing edge puts every row's controls on one axis instead of
   stranding them wherever an equal-share column happens to start.
   justify-content, not text-align: .ui-row-actions is a flex box that fills
   the cell, so aligning the text inside the cell would not move it. */
.ui-cell-actions .ui-row-actions {
  justify-content: flex-end;
}

/* A person in a list: avatar, then name above email. The email is what
   tells two people with the same display name apart, so it always shows —
   just quieter. */
.team-person {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}

.team-person__id {
  display: grid;
  gap: 0.1rem;
  min-width: 0;
}

.team-person__email {
  font-size: var(--text-sm);
}

/* ---- Workflow editor -------------------------------------------------

   Composition only: the tab strip is Buttons, the open step is a Card, the
   inputs are Fields. What is left is where those sit relative to each
   other, which is the app's call and nobody else's. */

/* Wider than .ui-form, which is sized for a stack of single inputs — this
   form also carries a tab strip and paired fields, and 32rem would fold a
   desktop into a phone layout. */
.wf-form {
  display: grid;
  gap: 1.5rem;
  max-width: 48rem;
}

.wf-steps {
  display: grid;
  gap: 1rem;
}

.wf-steps__head h2 {
  margin: 0 0 0.25rem;
}

.wf-steps__head p {
  margin: 0;
}

/* The strip scrolls itself instead of widening the document: a pipeline can
   have more steps than fit a phone, and a page that scrolls sideways makes
   every screen on it feel broken. */
.wf-steps__strip {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  padding-bottom: 0.25rem;
}

/* Tabs stay on one line so adding a step lengthens the strip (which
   scrolls) rather than growing it taller (which shifts the page). */
.wf-tab,
.wf-tab-add {
  flex: 0 0 auto;
  white-space: nowrap;
}

.wf-fields {
  display: grid;
  gap: 1rem;
}

/* Fields read as a pair (reads/writes, the two policies) sit side by side
   where there is room and stack where there is not. */
.wf-row {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}

.wf-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
}

/* Remove sits apart from the reorder controls: "delete this step" should
   never be the button next to the one you were aiming for. */
.wf-remove {
  margin-left: auto;
}

/* Save commits the whole workflow, so it is one button, not a bar. */
.wf-save {
  justify-self: start;
}

.wf-list-actions {
  text-align: right;
}

/* ------------------------------------------------------------------ */
/* Status chips — a domain palette, not a restyle                      */
/* ------------------------------------------------------------------ */

/* Chip's `Variant` is deliberately an open string appended as
   chip--<variant>, and adding domain variants in the consumer's own
   stylesheet is the documented extension hook. These are birgus's card
   statuses and its column-attention kinds; the DECISION about which one a
   card gets lives in internal/present, not here.

   The shape is copied from the design system's own semantic variants: a
   tint for the background, and the hue mixed toward the theme foreground
   for the text. Mixing toward the foreground darkens in light and lightens
   in dark, so one declaration is legible in both — the raw hue is tuned as
   a fill behind white text and is too pale to read as text on a tint. */

:root {
  --fam-progress: oklch(0.62 0.16 250);   /* blue — an agent is working */
  --fam-review: oklch(0.58 0.2 300);      /* violet — waiting on a reviewer */
  --fam-waiting: oklch(0.72 0.16 75);     /* amber — waiting on an answer */
  --fam-blocked: oklch(0.65 0.17 45);     /* orange — stuck, recoverable */
}

.chip--in_progress {
  background: color-mix(in oklab, var(--fam-progress), transparent 85%);
  color: color-mix(in oklab, var(--fam-progress), var(--foreground) 32%);
}

.chip--pending_approval,
.chip--in_review {
  background: color-mix(in oklab, var(--fam-review), transparent 85%);
  color: color-mix(in oklab, var(--fam-review), var(--foreground) 32%);
}

.chip--awaiting_input {
  background: color-mix(in oklab, var(--fam-waiting), transparent 85%);
  color: color-mix(in oklab, var(--fam-waiting), var(--foreground) 32%);
}

.chip--blocked {
  background: color-mix(in oklab, var(--fam-blocked), transparent 85%);
  color: color-mix(in oklab, var(--fam-blocked), var(--foreground) 32%);
}

.chip--errored {
  background: color-mix(in oklab, var(--destructive), transparent 85%);
  color: color-mix(in oklab, var(--destructive), var(--foreground) 32%);
}

.chip--done {
  background: color-mix(in oklab, var(--success), transparent 85%);
  color: color-mix(in oklab, var(--success), var(--foreground) 32%);
}

/* An archived card is shelved, not cancelled: it reads quietly rather than
   in whatever colour it froze in. */
.chip--archived {
  background: transparent;
  border-color: var(--border);
  color: var(--muted-foreground);
}

/* Column attention. Louder than the card chips on purpose — this is the
   one thing on a folded column that has to be noticed, so it is filled
   rather than tinted. */
.chip--attn-stuck,
.chip--attn-review,
.chip--attn-question {
  color: var(--destructive-foreground);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.chip--attn-stuck {
  background: var(--destructive);
}

.chip--attn-review {
  background: var(--fam-review);
}

.chip--attn-question {
  background: var(--fam-waiting);
  color: oklch(0.2 0 0); /* amber is too light to carry white text */
}

/* ------------------------------------------------------------------ */
/* Board page                                                          */
/* ------------------------------------------------------------------ */

/* The header is three groups that reflow rather than three columns that
   squeeze: the title takes the room it needs, the view controls sit at
   the end of that line while there is space for them, and the connect
   panel takes a line of its own. */
.board-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.75rem 1rem;
  margin-bottom: 1.25rem;
}

.board-head {
  flex: 1 1 20rem;
  min-width: 0;
}

.board-head__title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.board-head__title h1 {
  margin: 0;
  font-size: var(--text-2xl);
  min-width: 0;
}

/* The pencil is an editing affordance, not a call to action: it stays out
   of the way until the title is hovered or it is focused. Focus counts —
   a control that only appears on hover is one a keyboard cannot find. */
.board-head__edit {
  opacity: 0;
  transition: opacity 0.15s ease;
}

.board-head__title:hover .board-head__edit,
.board-head__edit:focus-visible {
  opacity: 1;
}

@media (hover: none) {
  .board-head__edit {
    opacity: 1;
  }
}

.board-head__desc {
  margin-top: 0.35rem;
  color: var(--muted-foreground);
  font-size: var(--text-sm);
}

.board-head__repos {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.5rem;
}

/* Editing takes the whole header row: the form is a stack of fields, and
   letting the view controls sit beside it would put a New-card button next
   to a half-finished rename. */
.board-head--editing {
  flex-basis: 100%;
}

.board-head__form {
  max-width: 32rem;
}

.board-head__form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

.board-header__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

/* ---- connect panel ---- */

/* Its own line under the title, full width, because the join message is a
   long single line that must not wrap (see CommandSnippet). */
.board-connect {
  flex-basis: 100%;
  display: grid;
  gap: 0.5rem;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: color-mix(in oklab, var(--muted), transparent 55%);
}

.board-connect__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.board-connect__id {
  color: var(--muted-foreground);
  font-size: var(--text-sm);
}

.board-connect__id code {
  font-family: var(--font-mono);
  color: var(--foreground);
}

.board-connect__name {
  width: 11rem;
}

.board-connect__token {
  width: auto;
  min-width: 9rem;
}

.board-connect__no-token {
  font-size: var(--text-sm);
}

/* ---- cards ---- */

/* position: relative on our own class, not on .board__ticket — a rule here
   starting with a design-system class name would be a restyle, and the
   stretched link needs a positioned ancestor that is ours to give. */
.board-card {
  position: relative;
  gap: 0.4rem;
}

/* The whole card is the click target, but the control is a real anchor:
   a click handler on the <article> would be invisible to the keyboard. */
.board-card__link {
  text-decoration: none;
  font-weight: 500;
  overflow-wrap: anywhere;
}

.board-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.board-card__link:focus-visible::after {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.board-card__error {
  color: color-mix(in oklab, var(--destructive), var(--foreground) 25%);
  font-size: var(--text-xs);
}

.board-card__meta {
  display: flex;
  gap: 0.5rem;
  color: var(--muted-foreground);
  font-size: var(--text-xs);
}

.board-card__ref {
  font-family: var(--font-mono);
}

/* Shelved, not deleted: still readable, clearly not in play. */
.board-card--archived {
  opacity: 0.6;
  border-style: dashed;
}

/* ------------------------------------------------------------------ */
/* Card page                                                           */
/* ------------------------------------------------------------------ */

/* Composition only. The bar is a PageBar, the lines are AnnotatedLines, the
   conversation is a Thread, the buttons are Buttons. What is left is where
   those sit relative to one another — two panes side by side, or one pane at
   a time — which is the app's call and nobody else's. */

.card-view {
  display: grid;
  gap: 1rem;
}

/* The pane switcher is a phone affordance. Hiding our own wrapper rather than
   the strip keeps this file out of the design system's class names. */
.card-view__tabs {
  display: none;
}

.card-view__panes {
  display: grid;
  gap: 1.5rem;
}

/* ---- desktop: content beside the conversation ---- */

@media (min-width: 900px) {
  /* Content takes the room it needs to be read; the conversation is a fixed
     column beside it. A conversation that grew with the content would put the
     composer below the fold on a long card, which is where the reader has to
     reach to act on what they just read. */
  .card-view__panes {
    grid-template-columns: minmax(0, 1fr) minmax(20rem, 28rem);
    align-items: start;
  }

  /* The third pane is the phone's; on a desktop the questions live in the
     dialog that opens itself, and the composer carries the way back into it. */
  .card-pane--answer {
    display: none;
  }

  /* The conversation sticks beside the content it is about, under the page
     bar. Its own height is bounded so the rail scrolls rather than the page. */
  .card-pane--chat {
    position: sticky;
    top: calc(var(--appshell-header-h, 0rem) + 4.5rem);
    display: grid;
    gap: 0.75rem;
    max-height: calc(100dvh - var(--appshell-header-h, 0rem) - 6rem);
    grid-template-rows: minmax(0, 1fr) auto;
  }

  /* Size the rail with the custom property Thread publishes, rather than
     overriding its rule. */
  .card-pane--chat .thread {
    --thread-max-h: 100%;
  }
}

/* ---- phone: one pane at a time ---- */

@media (max-width: 899px) {
  .card-view__tabs {
    display: block;
  }

  /* Which pane shows is server state: it arrives as data-tab and rides in the
     URL this view re-fetches itself from, so a live update cannot move it. */
  .card-view[data-tab="chat"] [data-pane="content"],
  .card-view[data-tab="chat"] [data-pane="answer"],
  .card-view[data-tab="answer"] [data-pane="content"],
  .card-view[data-tab="answer"] [data-pane="chat"],
  .card-view[data-tab="content"] [data-pane="answer"] {
    display: none;
  }

  /* The content tab is the exception, and deliberately. A reviewer reads the
     content, comments on a line, and decides — one flow — so the verdict has
     to be reachable without leaving the thing being judged. `display:
     contents` unwraps the conversation pane so its two children can answer
     for themselves: the timeline belongs on its own tab, the verdict does
     not. */
  .card-view[data-tab="content"] [data-pane="chat"] {
    display: contents;
  }

  .card-view[data-tab="content"] #card-chat-timeline {
    display: none;
  }

  /* Only the verdict docks. Every other composer state is a sentence about
     who holds the card, which the status chip in the page bar already says. */
  .card-view[data-tab="content"] .card-composer:not([data-state="review"]) {
    display: none;
  }

  .card-view[data-tab="content"] .card-composer[data-state="review"] {
    position: fixed;
    inset: auto 0 0;
    z-index: 10;
    border-radius: 0;
    border-inline: 0;
    border-bottom: 0;
    padding-bottom: calc(0.9rem + env(safe-area-inset-bottom));
  }

  /* The message box stays on the conversation tab. Docked, it would cover
     the content it is about the moment the keyboard opened. */
  .card-view[data-tab="content"] .card-composer[data-state="review"] .card-composer__field {
    display: none;
  }

  /* Room for the dock, so the last field is not permanently underneath it. */
  .card-view[data-tab="content"] .card-fields {
    padding-bottom: 6rem;
  }

  .card-pane--chat {
    display: grid;
    gap: 0.75rem;
  }

  .card-pane--chat .thread {
    --thread-max-h: 55vh;
  }

}

/* ---- page bar ---- */

.card-head__id {
  font-family: var(--font-mono);
  font-weight: 400;
  color: var(--muted-foreground);
}

/* ---- fields ---- */

.card-fields {
  display: grid;
  gap: 2rem;
}

.card-field__name {
  font-size: var(--text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted-foreground);
  margin: 0 0 0.5rem;
}

.card-field__empty {
  color: var(--muted-foreground);
  margin: 0;
}

/* Rendered field text wants a measure. Prose deliberately sets no width —
   it is as often a table cell as a page — so the reading width is set here,
   where the app knows this one is a page. */
.card-prose {
  max-width: 72ch;
}

.card-fields__note {
  color: var(--muted-foreground);
  font-size: var(--text-sm);
}

/* ---- the review gutter ---- */

/* A fence marker is a delimiter rather than content, and a heading is the
   shape of the text under it. Both are cues for a reviewer scanning for the
   line to comment on, which is why they are visible at all. */
.card-line--fence {
  color: var(--muted-foreground);
}

.card-line--heading {
  font-weight: 650;
}

/* Inline code inside a numbered line. Prose styles this by element, but a
   gutter line is not prose — it is one addressable row of it. */
.annotated-lines code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--muted);
  border-radius: var(--radius-sm);
  padding: 0.05em 0.3em;
}

.card-line__marker {
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  font-weight: 600;
}

.card-annotation {
  display: grid;
  gap: 1rem;
  min-width: min(22rem, 70vw);
}

.card-comment {
  display: grid;
  gap: 0.5rem;
}

.card-comment__meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

.card-comment__author {
  font-weight: 600;
  color: var(--foreground);
}

.card-comment__tools {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 0.5rem;
}

.card-comment__edit summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--muted-foreground);
}

.card-comment__edit form {
  display: grid;
  gap: 0.5rem;
  justify-items: start;
  margin-top: 0.5rem;
}

/* ---- conversation ---- */

.card-event__actor {
  font-weight: 600;
  color: var(--foreground);
}

/* A human's own actions read differently from an agent's: this is the one
   place a person appears in a column otherwise written by machines. */
.card-event__actor[data-actor="you"] {
  color: var(--primary);
}

/* The kind is a word, not a colour — a bare coloured dot says nothing to a
   screen reader. The colour rides alongside it. */
.card-event__kind {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--muted-foreground);
}

.card-event__kind[data-category="review"] {
  color: var(--primary);
}

.card-event__kind[data-category="error"] {
  color: var(--destructive);
}

.card-event__time {
  margin-left: auto;
  white-space: nowrap;
}

/* ---- composer ---- */

.card-composer {
  display: grid;
  gap: 0.75rem;
  justify-items: start;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
}

.card-composer__head {
  margin: 0;
  font-weight: 600;
}

.card-composer__note {
  margin: 0;
  color: var(--muted-foreground);
  font-size: var(--text-sm);
}

.card-composer__form,
.card-composer__field {
  width: 100%;
}

/* The verdict buttons wrap rather than push the composer into a sideways
   scroll — this column is narrow by design. */
.card-composer__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* ---- archive ---- */

/* Two steps: the disclosure, then the card's own number typed out. It sits at
   the foot of the content pane, as far from the verdict buttons as the page
   allows. */
.card-archive {
  margin-top: 1rem;
  border-top: 1px solid var(--border);
  padding-top: 1rem;
}

.card-archive__summary {
  cursor: pointer;
  color: var(--muted-foreground);
  font-size: var(--text-sm);
}

.card-archive[open] .card-archive__summary {
  margin-bottom: 0.75rem;
}

/* ---- errors ---- */

.card-error__meta {
  display: flex;
  gap: 0.4rem;
  font-size: var(--text-sm);
  margin: 0 0 0.5rem;
}

.card-error-log {
  margin-top: 1rem;
}

.card-error-log summary {
  cursor: pointer;
  color: var(--muted-foreground);
  font-size: var(--text-sm);
}

.card-error-log__entry {
  margin-top: 1rem;
}

/* ---- questions ---- */

.card-answers {
  display: grid;
  gap: 1.5rem;
}

.card-question {
  display: grid;
  gap: 0.75rem;
}

.card-question__anchor {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--muted-foreground);
}

/* `important` is birgus's markdown vocabulary, not a tone the design system
   ships. Prose renders an unknown tone as the neutral note and documents the
   custom property as the way to give it one, which is this whole override. */
.prose .callout--important {
  --callout-tone: var(--primary);
}
