:root {
  color-scheme: dark;
  --bg: #101215;
  --surface: #191d22;
  --border: #2b323a;
  --text: #e8ecf1;        /* 15.8:1 on --bg */
  --muted: #a8b3bf;       /*  8.1:1 on --bg */
  --accent: #7cc4ff;      /*  9.4:1 on --bg */
  --ok: #6ee7a8;
  --bad: #ff9a8b;
  --warn: #ffcf7a;      /* 11.4:1 on --bg */
}
* { box-sizing: border-box; }
body {
  margin: 0;
  padding: 1.5rem 1rem 4rem;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}
main { max-width: 60rem; margin: 0 auto; }
h1 { font-size: 1.4rem; margin: 0 0 .25rem; letter-spacing: -.01em; }
.lede { color: var(--muted); margin: 0 0 1.5rem; }
a { color: var(--accent); }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem;
  margin-bottom: 1.25rem;
}
.panel h2 { font-size: 1rem; margin: 0 0 .75rem; }
dl.facts { display: grid; grid-template-columns: max-content 1fr; gap: .35rem 1rem; margin: 0; }
dl.facts dt { color: var(--muted); }
dl.facts dd { margin: 0; font-variant-numeric: tabular-nums; }
.state-ok { color: var(--ok); }
.state-bad { color: var(--bad); }
.scroller { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; min-width: 34rem; }
caption { text-align: left; color: var(--muted); padding-bottom: .5rem; }
th, td { text-align: left; padding: .55rem .6rem; border-bottom: 1px solid var(--border); }
th { color: var(--muted); font-weight: 600; font-size: .85rem; text-transform: uppercase; letter-spacing: .04em; }
td { font-variant-numeric: tabular-nums; }
tbody tr:last-child td { border-bottom: 0; }
.empty { color: var(--muted); margin: 0; }
.muted { color: var(--muted); }
footer { color: var(--muted); font-size: .85rem; margin-top: 2rem; }
/* A footer link that stands ALONE is the only way back from a policy screen, so
   it is a control and NFR-UX-RESP-002 measures it — it rendered 78x16 px until
   Phase 8's audit reached /privacy and /terms, which no gate had ever run on.
   A link inside a SENTENCE is not a control: it is sized by the prose around it,
   which is the exception WCAG's own target-size criteria carry and the touch gate
   applies. Four such links exist today -- Health endpoint: /api/health in the
   sign-in and heartbeat footers, the queue's two export links, and the base-URL
   link in the policy text -- and the gate PRINTS every link it excludes on every
   run, so that list is read from the output rather than trusted to this comment.
   Held by tests/e2e/a11y-matrix.e2e.ts. */
footer a.home { display: inline-flex; align-items: center; min-height: 44px; }

/* Navigation ------------------------------------------------------------- */
nav { max-width: 60rem; margin: 0 auto 1.5rem; }
nav ul {
  list-style: none; display: flex; flex-wrap: wrap; align-items: center;
  gap: .25rem .75rem; margin: 0; padding: 0 0 .5rem; border-bottom: 1px solid var(--border);
}
/* >=44px so a nav link is a real touch target, not a 19px line of text (NFR-UX). */
nav a {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 .25rem;
}
nav .nav-end { margin-left: auto; }
nav form { margin: 0; }

/* Buttons ---------------------------------------------------------------- */
/* >=44px tall so a touch target meets the v1 accessibility gate. */
.button {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: .6rem 1.1rem;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--text);
  font: inherit; text-decoration: none; cursor: pointer;
}
.button:hover { border-color: var(--accent); }
.button-primary { background: var(--accent); border-color: var(--accent); color: #0b1016; font-weight: 600; }
/* Quiet, but still a full-size target: the 44px gate is about the hand, not the
   emphasis. */
.button-quiet { padding: .35rem .8rem; color: var(--muted); }
.button-disabled { color: var(--muted); cursor: not-allowed; border-style: dashed; }
.button-disabled:hover { border-color: var(--border); }
.actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 1rem; }
.actions .button { flex: 1 1 auto; }

/* Forms ------------------------------------------------------------------ */
form.stack { display: flex; flex-direction: column; align-items: flex-start; gap: .5rem; }
form.stack label { color: var(--muted); }
input[type='text'] {
  min-height: 44px; width: 100%; max-width: 24rem; padding: .5rem .75rem;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); color: var(--text); font: inherit;
}
input[type='text']:focus-visible { border-color: var(--accent); }

/* Screen-reader-only text, for table headers with no visible label. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* Panels that carry a state --------------------------------------------- */
.panel-bad { border-color: var(--bad); }
.panel-bad h2 { color: var(--bad); }
.panel-warn { border-color: var(--warn); }
.panel-warn h2 { color: var(--warn); }
.note { margin: .75rem 0 0; font-size: .9rem; }
/* AC-011-2's over-ceiling notice. health-page.tsx has carried these two class
   names since Phase 6 with NO rule behind them, so the banner painted as an
   ordinary paragraph — recorded as a residue by Phase 7 and handed to Phase 8's
   polish pass by name. Deliberately in panel-warn's language rather than a new
   one: the same surface and border, with --warn (11.7:1 on --surface, above the
   >=4.5:1 gate) on a thickened left edge carrying the state. Held by
   health-and-capability.e2e.ts, which asserts the PAINT — the banner's computed
   background and left border differing from an ordinary paragraph's on the same
   page — and not the class attribute, because a class name that matches no rule
   is exactly how this went six phases unnoticed. */
.banner {
  margin: 0 0 1.25rem; padding: .85rem 1rem;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface);
}
.banner-warn { border-color: var(--warn); border-left-width: 4px; }
code { font-size: .95em; word-break: break-all; }

/* Queue screen ----------------------------------------------------------- */
.filters { max-width: none; margin: 0 0 1rem; display: flex; flex-wrap: wrap; gap: .4rem; }
.filters .filter {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 .8rem;
  border: 1px solid var(--border); border-radius: 999px;
  color: var(--muted); text-decoration: none; font-size: .9rem;
}
.filters .filter:hover { border-color: var(--accent); }
.filters .filter-on { color: #0b1016; background: var(--accent); border-color: var(--accent); font-weight: 600; }

ul.posts, ul.targets { list-style: none; margin: 0; padding: 0; }
.post { border: 1px solid var(--border); border-radius: 10px; padding: .85rem; margin-bottom: .75rem; }
.post:last-child { margin-bottom: 0; }
.post-head { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; }
.post-when { font-variant-numeric: tabular-nums; }
.post-cost { margin-left: auto; font-variant-numeric: tabular-nums; }
/* FR-026's warning made visible without becoming an alarm: the link rate is
   worth noticing at a glance, and --warn is already measured against --bg for
   the >=4.5:1 gate (NFR-UX-A11Y-003). */
.post-cost-link { color: var(--warn); }
.cost-note { font-size: .8rem; letter-spacing: .02em; }
.post-attachments { font-size: .9rem; }
.post-text { margin: .6rem 0; overflow-wrap: anywhere; }
/* FR-028's held-row download links. 44 px min-height on the anchors, because
   they are controls and NFR-UX-RESP-002 measures every visible one. */
.held-media { margin: .5rem 0 0; }
.held-media a { min-height: 44px; display: inline-flex; align-items: center; }

/* FR-062/FR-138 thumbnails. FIXED 64 px squares with object-fit, so a row's
   height does not depend on the aspect ratio of what the Operator attached —
   a tall portrait image would otherwise make one row three times another's,
   and NFR-PERF-002's LCP is measured on a populated screen. Wrapping keeps
   four attachments (X's per-post ceiling) inside 360 px. */
ul.thumbs { list-style: none; margin: .6rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: .4rem; }
img.thumb {
  display: block; width: 64px; height: 64px; object-fit: cover;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg);
}
blockquote.post-text { margin: .6rem 0; padding-left: .75rem; border-left: 3px solid var(--border); }
.targets { display: flex; flex-direction: column; gap: .35rem; }
.target { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .6rem; font-size: .9rem; }
.target-platform { font-weight: 600; }
.target-reason { color: var(--bad); overflow-wrap: anywhere; }
.target form { margin: 0; }
.post-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }

/* Status badges. Every colour here is one of the palette tokens already
   measured against --bg for the >=4.5:1 gate; the badge background is the
   surface, so the ratio is unchanged. */
.badge {
  display: inline-block; padding: .1rem .5rem; border-radius: 999px;
  border: 1px solid var(--border); font-size: .8rem; letter-spacing: .02em;
}
.badge-scheduled { color: var(--accent); border-color: var(--accent); }
.badge-held { color: var(--warn); border-color: var(--warn); }
.badge-publishing { color: var(--warn); border-color: var(--warn); }
.badge-published { color: var(--ok); border-color: var(--ok); }
.badge-failed { color: var(--bad); border-color: var(--bad); }
.badge-cancelled { color: var(--muted); }
.badge-deleted_on_platform { color: var(--muted); }
.badge-partial { color: var(--muted); }
/* FR-152's stale flag. --warn is already measured against --bg for the >=4.5:1
   gate, and the flag is a WORD as well as a colour: a colour-only signal fails
   NFR-UX-A11Y and tells a monochrome reader nothing. */
.badge-stale { color: var(--warn); border-color: var(--warn); }

/* FR-153's reply drafts ---------------------------------------------------
   Every interactive element here is >=44px on BOTH axes because the touch gate
   measures both and the derived control set finds these by focusability — a
   .button-quiet inside a draft is as much a real target as one anywhere else.
   flex-wrap throughout so the 360px viewport never scrolls horizontally: the
   overflow gate measures the document, and a form row that cannot wrap is the
   usual way a screen fails it. */
.drafts { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }
.draft {
  border: 1px solid var(--border); border-radius: 10px; padding: .9rem;
  background: var(--bg); display: grid; gap: .6rem;
}
/* The stale border is an ADDITION to the badge, never a replacement for it. */
.draft-stale { border-color: var(--warn); }
.draft-head { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; }
.draft-target {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 .25rem; color: var(--accent);
}
.draft-remind { font-size: .85rem; }
/* pre-wrap so a draft reads exactly as it will post — collapsing its line
   breaks would show the Operator something other than what they are copying. */
.draft-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.draft-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; }
.draft-actions form {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
}
.draft-id-label { color: var(--muted); font-size: .85rem; }
/* Narrower than the shared input[type='text'] 24rem so the id field and its
   submit button sit on one row on a phone rather than pushing the button to a
   line of its own — and it still inherits the 44px min-height. */
.draft-id-input { max-width: 14rem; }
.draft-busy { font-size: .9rem; }
.draft-media a { min-height: 44px; display: inline-flex; align-items: center; }

.panel-ok { border-color: var(--ok); color: var(--text); }

textarea {
  min-height: 8rem; width: 100%; max-width: 40rem; padding: .5rem .75rem;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); color: var(--text); font: inherit; resize: vertical;
}
textarea:focus-visible { border-color: var(--accent); }
input[type='datetime-local'] {
  min-height: 44px; padding: .5rem .75rem;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); color: var(--text); font: inherit;
}
/* A bare <select> renders 19 px tall in Chromium, which fails NFR-UX-RESP-002's
   44 px touch target. Found by the v1-gate e2e on the FIRST run after Phase 5's
   disposition control landed — and by the pre-existing queue-spine gate test as
   well as the new one, which is the gates doing their job. Styled to match the
   other text controls so the edit form reads as one thing. */
select {
  min-height: 44px; padding: .5rem .75rem; width: 100%; max-width: 40rem;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); color: var(--text); font: inherit;
}
select:focus-visible { border-color: var(--accent); }
form.stack { width: 100%; }
details summary { cursor: pointer; color: var(--accent); min-height: 44px; display: flex; align-items: center; }
pre { overflow-x: auto; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: .75rem; }

@media (max-width: 30rem) {
  body { padding: 1rem .75rem 3rem; }
  dl.facts { grid-template-columns: 1fr; gap: .1rem; }
  dl.facts dd { margin-bottom: .5rem; }
  /* At 360 px the sign-in controls stack rather than squeezing below the
     44 px target. */
  .actions { flex-direction: column; }
  /* Same reasoning for the per-post controls: two 44 px buttons side by side
     do not fit 360 px minus padding without one of them shrinking. */
  .post-actions { flex-direction: column; align-items: stretch; }
  .post-cost { margin-left: 0; }
}