/*
 * Matrix workbench — the signed-in surface.
 *
 * The marketing pages lead with gradient headlines and a lot of air,
 * which is right for a page whose job is to persuade. These pages are
 * read by someone working a queue: they are scanned, not read, often for
 * hours, and the thing that matters is how quickly a state, a severity
 * and a timestamp can be picked out of a dense table.
 *
 * So this layer is deliberately plainer than the rest of the site. No
 * gradients, no decorative colour: hue is reserved for severity and
 * state, where it carries meaning. Rules are hairlines, labels are small
 * and lettered out, and every number is monospaced so digits line up in
 * a column. It sits on top of pratimana.css and uses its tokens, so the
 * two themes stay in step.
 *
 * Kept out of pratimana.css because that file is shared verbatim with
 * pratimana-website, -consulting and -security, none of which have a
 * workbench to style.
 */

/* ---------------------------------------------------------------- shell */

/* The `hidden` attribute is not self-enforcing. Its UA rule is
   [hidden]{display:none}, which any author rule setting display outranks
   — and this file sets display on nearly everything, so `el.hidden =
   true` on a .wb-field (display:grid) or the composer's lane switch
   (display:flex) did precisely nothing. The bounty input stayed on
   screen after switching away from Resolved, and a researcher was shown
   the internal-note button.

   Scoped to .wb rather than declared globally: the brand mark in the
   masthead deliberately overrides [hidden] to show its frames when the
   stylesheet is missing, and it sits outside .wb. */
.wb [hidden] { display: none !important; }

.wb {
  --wb-gutter: 28px;
  max-width: 1240px;
  margin: 0 auto;
  padding: 26px var(--wb-gutter) 80px;
}

.wb-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding-bottom: 16px;
  margin-bottom: 22px;
  border-bottom: 1px solid var(--hairline);
}

.wb-head h1 {
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 500;
  letter-spacing: 0;
  margin: 0;
}

.wb-head .wb-sub {
  font-size: 13px;
  color: var(--text-faint);
  margin: 5px 0 0;
}

.wb-head-actions { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }

/* The small lettered-out label that titles a group of fields. Formality
   here comes from restraint: no icon, no colour, just spacing. */
.wb-label {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-faint);
}
/* As a link ("All submissions →") it is also a tap target, and 17px of
   text is not one; padding brings it to the 24px floor without moving
   the text. */
a.wb-label { display: inline-block; padding: 4px 0; }

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

.wb-panel {
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: 10px;
}

.wb-panel + .wb-panel { margin-top: 16px; }

/* Two lighter weights than the panel, for a rail where everything was a
   panel and so nothing was. .wb-decision is the one thing a triager came
   to do: a filled box at 4px. .wb-section is everything else: a rule and
   a heading, no frame. Both keep .wb-panel-head and .wb-panel-body as
   their children so every id and every head control stays where the
   suites and the script expect it. */
.wb-decision{ background:var(--well); border:1px solid var(--rule-strong); border-radius:4px; padding:16px; }
.wb-section{ border-top:1px solid var(--hairline); padding-top:14px; }
/* Archiving is final, so the one section that does it is drawn in the
   hue the record reserves for that. */
.wb-decision.is-critical{ border-color: var(--danger); }
/* A decision box that follows a field group rather than heading a rail. */
.wb-section > .wb-decision{ margin-top: 14px; }
/* The first section of a column carries the strong rule the page
   opens on, the way a register's head does. */
.wb-section-lead{ border-top-color:var(--rule-strong); }
.wb-decision + .wb-section, .wb-section + .wb-section, .wb-panel + .wb-section{ margin-top:18px; }
.wb-section + .wb-panel, .wb-decision + .wb-panel{ margin-top:18px; }
.wb-decision > .wb-panel-head, .wb-section > .wb-panel-head{ padding:0 0 12px; border-bottom:0; }
.wb-decision > .wb-panel-body, .wb-section > .wb-panel-body{ padding:0; }

.wb-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--hairline-soft);
}

.wb-panel-head h2 {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 500;
  margin: 0;
}

.wb-panel-body { padding: 16px; }

/* ---------------------------------------------------------------- table */

/* Wide tables scroll inside their own container so the page body never
   scrolls sideways on a phone. */
.wb-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* The last 24px fade while there is more to the right, and come back
   once the reader has scrolled there. Matrix.ui.edges sets both
   classes and sets them only on a box that actually overflows.
   linear-gradient takes physical sides only, so right-to-left gets a
   rule of its own rather than a logical keyword that no engine
   parses. */
.wb-scroll.is-scrollable:not(.is-at-end) {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
}
[dir="rtl"] .wb-scroll.is-scrollable:not(.is-at-end) {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 24px), transparent);
          mask-image: linear-gradient(to left, #000 calc(100% - 24px), transparent);
}

.wb-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  min-width: 640px;
}

.wb-table th {
  text-align: left;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--text-faint);
  padding: 9px 14px;
  border-bottom: 1px solid var(--hairline);
  white-space: nowrap;
}

.wb-table td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--hairline-soft);
  vertical-align: middle;
}

.wb-table tbody tr:last-child td { border-bottom: none; }
.wb-table tbody tr:hover { background: var(--panel-raised); }

.wb-table a { color: var(--text); text-decoration: none; }
.wb-table a:hover { text-decoration: underline; text-underline-offset: 2px; }

/* Digits in a column have to line up, or the column cannot be scanned. */
.wb-num, .wb-table .wb-num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.wb-title-cell { font-weight: 500; }
.wb-dim { color: var(--text-faint); }
/* The two small sizes a page script may ask for, instead of an inline
   font-size: the scale's 12 and 13. */
.wb-sub { font-size: 12px; }
.wb-sub-md { font-size: 13px; }

/* -------------------------------------------------------------- severity */

/* Severity is a rank, so it reads as a bar rather than a word: four
   steps of the same shape, distinguishable at a glance and still
   labelled for anyone who cannot separate the hues. */
/* Severity is drawn by Matrix.ui.sev (assets/api.js; rules in
   matrix.css section 8): the four-cell scale from the [MATRIX] block and
   the word — 12px in a register, 14px in a page head. Nothing here. */
.report-sev { display: inline-flex; align-items: center; }

/* The docket: the facts a reader would cite, on one line, separated by
   middle dots. Same shape as the public record's (disclosure.html). The
   :not([hidden]) guards matter — the state and the severity are hidden
   until the report loads, and without them the line opens with a dot. */
.wb-docket{ display:flex; flex-wrap:wrap; align-items:center; gap:6px 0;
  font-family:var(--font-mono); font-size:12px; color:var(--text-dim); margin:0; }
.wb-docket > *:not([hidden]){ display:inline-flex; align-items:center; min-width:0; }
/* The dot leads the item it separates rather than trailing the one
   before it: the state is a bordered pill, and a trailing ::after
   would draw the dot inside its box. The `~` is what keeps a
   hidden first item from leaving a dot at the head of the line. */
.wb-docket > *:not([hidden]) ~ *:not([hidden])::before{ content:"\00b7"; color:var(--text-faint); margin:0 10px; }
/* A link in the docket is a link a thumb has to hit: 24px is the
   floor, and 12px of mono is 18. */
.wb-docket a{ display:inline-flex; align-items:center; min-height:24px; color:var(--text-dim); text-decoration:none; }
.wb-docket a:hover{ color:var(--text); text-decoration:underline; text-underline-offset:3px; }
.wb-docket .mx-sev{ font-family:var(--font-body); }
.wb-docket .mx-money{ color:var(--text); font-weight:600; }
/* The same floor for the way back, which used to get it from the label
   class it no longer carries. */
#reportBack{ display:inline-flex; align-items:center; min-height:24px; }

/* ----------------------------------------------------------------- state */

.state {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
  padding: 3px 8px;
  border-radius: 4px;
  border: 1px solid transparent;
  white-space: nowrap;
}

/* One colour per state, and a formal one: the reader tells "triaged"
   from "retesting" and "closed" from "resolved" at a glance, without a
   legend. Each takes a theme-aware token (assets/pratimana.css) so the
   pill holds contrast on both grounds; the tint and the border are mixed
   from the same colour. */
.state-new             { color: var(--blue);       border-color: color-mix(in srgb, var(--blue) 40%, transparent);       background: color-mix(in srgb, var(--blue) 12%, transparent); }
.state-triaging        { color: var(--warn);       border-color: color-mix(in srgb, var(--warn) 40%, transparent);       background: color-mix(in srgb, var(--warn) 12%, transparent); }
.state-needs_more_info { color: var(--sev-high);   border-color: color-mix(in srgb, var(--sev-high) 40%, transparent);   background: color-mix(in srgb, var(--sev-high) 10%, transparent); }
.state-triaged         { color: var(--accent);     border-color: color-mix(in srgb, var(--accent) 40%, transparent);     background: color-mix(in srgb, var(--accent) 12%, transparent); }
.state-retesting       { color: var(--violet);     border-color: color-mix(in srgb, var(--violet) 40%, transparent);     background: color-mix(in srgb, var(--violet) 12%, transparent); }
.state-resolved        { color: var(--ok);         border-color: color-mix(in srgb, var(--ok) 40%, transparent);         background: color-mix(in srgb, var(--ok) 12%, transparent); }
.state-closed          { color: var(--slate);      border-color: color-mix(in srgb, var(--slate) 45%, transparent);      background: color-mix(in srgb, var(--slate) 12%, transparent); }
.state-duplicate       { color: var(--umber);      border-color: color-mix(in srgb, var(--umber) 40%, transparent);      background: color-mix(in srgb, var(--umber) 10%, transparent); }
.state-informative     { color: var(--indigo);     border-color: color-mix(in srgb, var(--indigo) 40%, transparent);     background: color-mix(in srgb, var(--indigo) 10%, transparent); }
.state-not_applicable  { color: var(--text-faint); border-color: var(--rule-strong);                                     background: transparent; }
.state-spam            { color: var(--danger);     border-color: color-mix(in srgb, var(--danger) 40%, transparent);     background: color-mix(in srgb, var(--danger) 10%, transparent); }
.state-draft           { color: var(--text-faint); border-color: var(--rule-strong);                                     border-style: dashed; background: transparent; }

/* --------------------------------------------------------------- filters */

.wb-filters {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: 16px;
}
/* Everything in a filter row is 32px. .wb-input's own block asks for 40,
   which left the one date field standing 8px taller than the four
   controls beside it. */
.wb-filters .wb-input { min-height: 32px; height: 32px; padding: 4px 10px; font-size: 13px; }

/* Three shapes, one class the suites locate by text. A plain .wb-chip is
   a row action: a 13px underlined link, no box. With aria-pressed it is a
   text-toggle, the same rule as shell.css's .pill and matrix.css's
   .mx-toggle: 32px, 4px, pressed is ink with ground text. .wb-chip-danger
   is the destructive action, filled at rest, always behind a dialog. */
.wb-chip {
  font-family: var(--font-body);
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  padding: 4px 0;
  /* WCAG 2.5.8's floor in both directions: a plain chip has no box, so a
     short word like "Edit" measured 23px wide and a thumb missed it. */
  min-height: 24px;
  min-width: 24px;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--hairline);
  cursor: pointer;
  transition: text-decoration-color .12s ease, color .12s ease;
}
.wb-chip:hover { text-decoration-color: var(--text); }
.wb-chip + .wb-chip { margin-inline-start: 12px; }
.wb-chip[aria-pressed] {
  display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 12px;
  border: 1px solid var(--hairline); border-radius: 4px; background: transparent; color: var(--text-dim);
  line-height: 1; text-decoration: none;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.wb-chip[aria-pressed] + .wb-chip[aria-pressed] { margin-inline-start: 0; }
/* Only an UNPRESSED toggle lifts on hover, and the :not() is what says
   so. Written `.wb-chip[aria-pressed]:hover` this rule scored (0,3,0) —
   :hover counts in the class column — against the pressed rule's
   (0,2,0), so hovering a selected chip repainted its label var(--text):
   the colour of its own fill. White on white in dark, black on black in
   light, 1.00:1, the word gone for as long as the pointer rested on it.
   Every filter on /organization#triage and /superadmin#access, both
   lanes on a report and the publications tabs are this chip. */
.wb-chip[aria-pressed]:not([aria-pressed="true"]):hover { color: var(--text); border-color: var(--rule-strong); }
.wb-chip[aria-pressed="true"] { background: var(--text); color: var(--void); border-color: var(--text); }
.wb-chip .n { font-family: var(--font-mono); font-size: 12px; }
.wb-chip:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
.wb-chip.wb-chip-danger {
  display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px;
  border: 1px solid transparent; border-radius: 4px; background: var(--danger-fill); color: #fff;
  text-decoration: none;
}
.wb-chip.wb-chip-danger:hover { filter: brightness(1.08); }
/* A native checkbox or radio with its label, for what used to be a chip. */
.wb-check { display: inline-flex; align-items: center; gap: 8px; min-height: 32px; margin-inline-end: 14px; font-size: 13px; cursor: pointer; }
/* Drawn at 16px, hit at 24. A native checkbox ignores author padding
   and border alike — measured, not assumed — so its box and its target
   are the same 15px square, under the 24px floor WCAG 2.5.8 sets and
   under a thumb. appearance:none hands the box back: a transparent 4px
   border carries the target out to 24 while the square a reader sees
   stays 16, the inset ring is clipped to that square, and the tick
   comes back as a mark in the ground colour over the ink fill. */
.wb-check input { margin: 0; accent-color: var(--text); }
.wb-check input[type="checkbox"] {
  -webkit-appearance: none; appearance: none;
  box-sizing: content-box;
  width: 16px; height: 16px;
  border: 4px solid transparent; border-radius: 8px;
  background-color: transparent;
  background-clip: padding-box; background-origin: padding-box;
  background-repeat: no-repeat; background-position: center; background-size: 16px 16px;
  box-shadow: inset 0 0 0 1px var(--rule-strong);
  cursor: pointer;
}
.wb-check input[type="checkbox"]:checked {
  background-color: var(--text);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.6 8.4l2.8 2.8 6-6.4' fill='none' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  box-shadow: inset 0 0 0 1px var(--text);
}
[data-theme="light"] .wb-check input[type="checkbox"]:checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.6 8.4l2.8 2.8 6-6.4' fill='none' stroke='%23FFFFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
/* -2px, not -3px: the ring is var(--text) and so is a checked box's fill,
   and at -3px only one pixel of ground stood between them. Two is the gap
   every other ring on Matrix keeps, and the ring stays inside the 24px
   target, so a table cell cannot clip it. */
.wb-check input[type="checkbox"]:focus-visible { outline: 2px solid var(--text); outline-offset: -2px; }
.wb-check input:disabled { opacity: .45; cursor: not-allowed; }
/* (2) Buttons inside the workbench: 40px in a form, 32px in a register or a
   composer; nothing lifts. (A ghost's strong rule, and its ink on hover,
   come from pratimana.css and matrix.css on every page now.) */
.wb .btn { min-height: 40px; padding: 0 16px; border-radius: 4px; font-weight: 500; font-size: 14px; }
.wb .btn-sm, .wb-table .btn, .composer .btn, .wb-panel-head .btn { min-height: 32px; padding: 0 12px; font-size: 13px; }

.wb-search {
  flex: 1 1 200px;
  min-width: 0;
  height: 32px;
  padding: 0 11px;
  border-radius: 4px;
  border: 1px solid var(--rule-strong);
  background: var(--well);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 13px;
}
.wb-search:focus { outline: 0; border-color: var(--text); }

/* ------------------------------------------------------- report / detail */

.wb-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 22px;
  align-items: start;
}

/* The rail stays beside the thread on a tablet — narrower, so the
   finding keeps a readable measure — and only stacks on a phone, where
   there is no room for two columns at all. It used to stack under
   1000px, which put the panels above the finding on most laptops with
   the browser zoomed. */
@media (max-width: 1100px) {
  .wb-split { grid-template-columns: minmax(0, 1fr) 250px; gap: 18px; }
}
@media (max-width: 720px) {
  .wb-split { grid-template-columns: minmax(0, 1fr); }
  /* The metadata rail leads on a narrow screen: on a phone you are
     checking a state, not reading a thread top to bottom. */
  .wb-rail { order: -1; }
}

.wb-rail { position: sticky; top: 84px; }
@media (max-width: 720px) { .wb-rail { position: static; } }

/* The report page keeps its panels — the details, the triage controls,
   the assessment, the assignment — on the left, where the shell keeps
   its rail, so the eye finds them where it finds them everywhere else. */
.wb-split-rail-left { grid-template-columns: 300px minmax(0, 1fr); }
.wb-split-rail-left .wb-rail { order: -1; }
@media (max-width: 1100px) { .wb-split-rail-left { grid-template-columns: 250px minmax(0, 1fr); } }
/* On a phone the finding leads. The rail's order:-1 above is what put
   the panels first at every width; cancelling it here is the whole fix,
   and it is scoped to this page's layout so publications.html keeps its
   own order. */
@media (max-width: 720px) {
  .wb-split-rail-left { grid-template-columns: minmax(0, 1fr); }
  .wb-split-rail-left .wb-rail { order: 0; }
  .wb-split-rail-left { padding-bottom: 128px; }
}

/* The decision bar, on a phone only. Three numbers are load-bearing:
   z-index 40 keeps it under the account chip (which acct-dock.js pins at
   70), the button sits at the end behind 150px of start padding so it
   never lands on that chip whatever its label says, and the 128px of
   bottom padding above keeps the composer reachable over both. */
.wb-sticky-bar{ display:none; }
@media (max-width: 720px) {
  .wb-sticky-bar{
    display:flex; align-items:center;
    position:fixed; inset-inline:0; bottom:0; height:56px; z-index:40;
    justify-content:flex-end;
    background:var(--well); border-top:1px solid var(--rule-strong);
    padding-inline:150px 16px;
  }
  .wb-sticky-bar[hidden]{ display:none; }
}

.wb-meta { display: grid; gap: 13px; }
.wb-meta-row { display: grid; gap: 3px; }
.wb-meta-row .v { font-size: 13px; word-break: break-word; }
/* The report's facts: a term and its value on a rule each. A fact the
   record cannot state is omitted by the caller, never dashed. */
.wb-facts{ margin:0; border-top:1px solid var(--rule-strong); }
.wb-facts > div{ display:grid; gap:3px; padding:10px 0; border-bottom:1px solid var(--hairline); }
.wb-facts dt{ font-size:12px; color:var(--text-faint); }
.wb-facts dd{ margin:0; font-size:13px; word-break:break-word; }

/* --------------------------------------------------------------- thread */

.thread { display: grid; gap: 2px; }

.thread-item {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr);
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline-soft);
}
.thread-item:last-child { border-bottom: none; }

.thread-avatar {
  width: 30px; height: 30px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: var(--panel-raised);
  border: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
}

.thread-head {
  display: flex; align-items: baseline; gap: 9px;
  flex-wrap: wrap;
  margin-bottom: 5px;
}

/* Two modifiers the report page uses. A comment has no avatar there —
   the name is the identification — and an event is one ruled line of
   when, who and what. publications.html builds the avatar form from the
   same rules above, so neither of these touches it. */
.thread-item.is-plain{ grid-template-columns: minmax(0,1fr); }
.thread-item.is-row{
  grid-template-columns: minmax(0,1fr);
  min-height: 44px; align-content: center;
  padding: 10px 0; border-bottom: 1px solid var(--hairline);
}
.thread-item.is-row .thread-line{
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 10px; font-size: 13px;
}
.thread-item.is-row .thread-when{ font-size: 12px; }
.thread-item .mx-money{ font-weight: 600; }
.thread-who { font-size: 13px; font-weight: 600; }
.thread-when { font-family: var(--font-mono); font-size: 11px; color: var(--text-faint); }
.thread-body { font-size: 14px; line-height: 1.62; white-space: pre-wrap; word-break: break-word; }

/* The finding, rendered from the reporter's Markdown. The measure and
   the size are the thread's, so the report reads as one document; the
   block rules keep a list, a code block or a quote from arriving with
   the browser's default margins around it. */
.finding-prose { font-size: 14px; line-height: 1.62; word-break: break-word; margin-top: 6px; }
.finding-prose > :first-child { margin-top: 0; }
.finding-prose > :last-child { margin-bottom: 0; }
.finding-prose p, .finding-prose ul, .finding-prose ol, .finding-prose blockquote { margin: 0 0 .7em; }
.finding-prose ul, .finding-prose ol { padding-left: 1.4em; }
.finding-prose li + li { margin-top: .2em; }
.finding-prose h1, .finding-prose h2, .finding-prose h3, .finding-prose h4, .finding-prose h5, .finding-prose h6 {
  font-size: 14px; font-weight: 600; letter-spacing: 0; margin: 1em 0 .35em;
}
.finding-prose code { font-family: var(--font-mono); font-size: .92em; background: var(--panel-raised); border-radius: 4px; padding: 1px 4px; }
.finding-prose pre { background: var(--panel-raised); border: 1px solid var(--hairline); border-radius: 8px; padding: 10px 12px; overflow-x: auto; margin: 0 0 .7em; }
.finding-prose pre code { background: none; padding: 0; font-size: 13px; white-space: pre; }
.finding-prose blockquote { border-left: 2px solid var(--hairline); padding: 0 0 0 12px; color: var(--text-dim); }
.finding-prose hr { border: 0; height: 1px; background: var(--hairline-soft); margin: 1em 0; }
.finding-prose a { color: var(--text); text-decoration: underline; text-underline-offset: 2px; word-break: break-all; }
.finding-prose img { max-width: 100%; }

/* The reporter revising their own report, in place of the rendered
   finding. The fields are the submission form's, so the same text the
   reporter wrote comes back to them to change. */
.edit-form { margin: 0; }
.edit-form > p { font-size: 13px; margin: 0 0 14px; }
.edit-form .wb-textarea { min-height: 120px; font-family: var(--font-mono); font-size: 13px; }

/* An internal note is the triage team's working record. It is marked
   unmistakably, because posting one into the wrong lane is the mistake
   this interface most needs to prevent. */
.thread-item.is-internal {
  background: color-mix(in srgb, var(--warn) 5%, transparent);
  border-left: 2px solid var(--warn);
  padding-left: 12px;
  margin-left: -14px;
}

.tag-internal {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--warn);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
  border-radius: 3px;
  padding: 1px 5px;
}

/* A timeline event is not a message — it is smaller, quieter, and has no
   author bubble. */
.thread-item.is-event { padding: 9px 0; }
.thread-item.is-event .thread-body { font-size: 12px; color: var(--text-faint); }
.thread-event-dot {
  width: 30px; display: grid; place-items: center;
}
.thread-event-dot span {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-faint);
}

/* ---------------------------------------------------------------- forms */

.wb-field { display: grid; gap: 6px; margin-bottom: 14px; }
.wb-field > label { font-size: 12px; color: var(--text-dim); }

.wb-input, .wb-select, .wb-textarea {
  width: 100%;
  min-height: 40px;
  border-radius: 4px;
  border: 1px solid var(--rule-strong);
  background: var(--well);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.4;
  padding: 8px 12px;
  transition: border-color .12s ease;
}
.wb-input::placeholder, .wb-textarea::placeholder { color: var(--text-faint); }
.wb-textarea { min-height: 96px; resize: vertical; line-height: 1.6; }
.wb-input:focus, .wb-select:focus, .wb-textarea:focus { outline: 0; border-color: var(--text); }
/* The native select without the operating system's box: its own chevron,
   drawn in ink for each theme (a listbox — [multiple] — has no chevron).
   color-scheme, set in matrix.css, lets the dropdown follow the theme. */
.wb-select:not([multiple]) {
  appearance: none; -webkit-appearance: none; padding-inline-end: 34px; cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23FFFFFF' stroke-width='1.5' stroke-linecap='square' stroke-linejoin='miter'><path d='m6 9 6 6 6-6'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 16px;
}
[dir="rtl"] .wb-select:not([multiple]) { background-position: left 10px center; padding-inline-end: 34px; }
[data-theme="light"] .wb-select:not([multiple]) {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='1.5' stroke-linecap='square' stroke-linejoin='miter'><path d='m6 9 6 6 6-6'/></svg>");
}
.wb-table .wb-input, .wb-table .wb-select { min-height: 32px; padding: 4px 10px; font-size: 13px; }
/* The line above is a shorthand, so it resets the room the chevron is
   drawn in and the arrow lands on the option text. Give it back. */
.wb-table .wb-select:not([multiple]) { padding-inline-end: 34px; }

.wb-actions { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }

/* The lane switch on the comment box. Which lane is active has to be
   obvious without reading, so the whole composer takes the internal
   colour when internal is selected. */
.composer { border: 1px solid var(--hairline); border-radius: 4px; padding: 13px; }
.composer.is-internal { border-color: color-mix(in srgb, var(--warn) 45%, transparent); background: color-mix(in srgb, var(--warn) 4%, transparent); }
.composer .lanes { display: flex; gap: 6px; margin-bottom: 10px; }

/* ------------------------------------------------------------ empty state */

/* One blank state, shared with Matrix.ui.blank(): the "[ ]" cell in a
   column of its own, the sentence beside it, and anything further — a
   second line, at most one action — stacked under the sentence. The
   dashed 9px frame is gone: it drew a container around the one thing
   the page has nothing to put in. The mark's column is fixed rather
   than auto so the loading state, which draws a bar instead of a mark,
   starts its text at the same place and the two do not jump when one
   replaces the other. "[ ]" is three advances of the mono face at
   26px. */
.state-note {
  display: grid;
  grid-template-columns: 47px minmax(0, 1fr);
  column-gap: 14px;
  align-items: baseline;
  padding: 28px 0;
  text-align: start;
}
.state-note > .mx-blank-mark { grid-column: 1; grid-row: 1; }
/* Everything else goes in the text column, including children a caller
   appends after emptyState() has returned. */
.state-note > :not(.mx-blank-mark) { grid-column: 2; }
/* A .btn is inline-flex; blockified as a grid item it would stretch the
   whole column. */
.state-note > .btn { justify-self: start; }
.state-title { font-size: 16px; font-weight: 500; margin: 0; color: var(--text-dim); }
.state-body {
  font-size: 13px;
  color: var(--text-faint);
  margin: 7px 0 0;
  max-width: 52ch;
  line-height: 1.6;
}

/* ---------------------------------------------------------- loading state */

/* Matrix.ui.loading() renders this while a fetch is in flight, so a page
   never sits on an empty table that reads as "no reports" for the second
   or two before the real answer lands. Same padding and the same text
   column as the empty state, so the two do not jump when one replaces
   the other. The bar breathes rather than spins — a spinner promises
   progress it cannot see — and holds still under reduced motion. */
.state-pulse {
  display: block;
  width: 120px; height: 4px; border-radius: 2px;
  margin: 0 0 12px;
  background: linear-gradient(90deg, var(--hairline) 0%, var(--text-faint) 50%, var(--hairline) 100%);
  background-size: 200% 100%;
  animation: wb-pulse 1.4s ease-in-out infinite;
}
@keyframes wb-pulse { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) {
  .state-pulse { animation: none; background: var(--hairline); }
}

/* --------------------------------------------------------------- utility */

.wb-row { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
/* minmax(0, 1fr) rather than 1fr: a grid track's default minimum is
   its content, and .wb-table carries min-width:640px so it can hold its
   columns. Without the 0 the track grew to 640 and took the page with
   it — the table scrolled inside .wb-scroll as intended, and the page
   scrolled sideways anyway. */
.wb-stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
.wb-hr { height: 1px; background: var(--hairline-soft); border: 0; margin: 16px 0; }

.wb-banner.is-ok { border-left-color: var(--ok); }
.wb-banner {
  border: 1px solid var(--hairline);
  border-left: 2px solid var(--warn);
  border-radius: 4px;
  padding: 12px 14px;
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.6;
  margin-bottom: 20px;
}

@media (prefers-reduced-motion: reduce) {
  .wb-chip { transition: none; }
}

/* Attachments and similar-report lists on the report page. */
.attachments, .similar { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.attachments li { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 14px; }
.attachments .att-name { font-weight: 600; word-break: break-all; }
.similar li { display: grid; gap: 4px; font-size: 14px; }
.similar .wb-chip { justify-self: start; }

/* What a program has masked from the public copy of a report, and the
   passage a triager has picked out to mask next. The excerpt is set in
   the mono face for the same reason an asset is: it is a quotation of
   exact characters, and the reader is checking the edges of it. */
.redactions { list-style: none; margin: 0 0 12px; padding: 0; display: grid; gap: 10px; }
.redactions li { display: grid; gap: 3px; font-size: 13px; padding-bottom: 10px; border-bottom: 1px solid var(--hairline-soft); }
.redactions li:last-child { border-bottom: 0; padding-bottom: 0; }
.redactions .reason { font-size: 14px; word-break: break-word; }
.redactions .wb-chip { justify-self: start; margin-top: 4px; }
.redaction-pick { display: grid; gap: 4px; margin-bottom: 11px; padding: 9px 11px; border: 1px solid var(--hairline); border-radius: 4px; }
.redaction-pick .excerpt { font-family: var(--font-mono); font-size: 13px; word-break: break-word; }
/* Money, wherever it appears in the workbench. */
.money { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.money.is-negative { color: var(--danger); }
/* A word where a borrowed status pill used to be: Paid, Owner, In, Active,
   Published. Dashed is not yet or no longer in force (Requested, Expired,
   Draft, Off, private); struck is over (Cancelled, Declined, Deleted,
   Disabled). The .state pill is a report's status only. */
.wb-word { font-size: 13px; color: var(--text); white-space: nowrap; }
.wb-word + .wb-word { margin-inline-start: 10px; }
.wb-word.is-dashed { color: var(--text-dim); border-bottom: 1px dashed var(--text-faint); }
.wb-word.is-struck { color: var(--text-faint); text-decoration: line-through; }

/* Figure tiles, shared by the console, earnings and superadmin pages. */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.stat-tile { background: var(--panel); border: 1px solid var(--hairline); border-radius: 10px; padding: 14px 16px; }

/* A field at the measure its content is read at, rather than the width
   of the page: a name, a date, a code. */
.wb-w-40{ max-width: 40ch; }
.wb-w-12{ max-width: 12ch; }
.stat-tile .num { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; margin-bottom: 4px; }

/* A control the person may see but not use — the panel's read-only tier
   disables with a reason in the title rather than hiding. */
.wb-chip:disabled, .wb-select:disabled { opacity: .45; cursor: not-allowed; }

/* ---- scope rules (organization-dashboard, submissions, report) ---- */
.rule-form{ display:flex; flex-wrap:wrap; gap:14px 18px; margin-top:14px; align-items:flex-start; }
.rule-form-col{ display:flex; flex-direction:column; gap:6px; min-width:180px; }
.rule-form-wide{ flex:1 1 260px; }
.rule-pick{
  max-height:220px; overflow:auto; min-width:250px; padding:6px 8px; border:1px solid var(--hairline); border-radius:9px;
  background:var(--panel-raised); font-size:13px;
}
.rule-pick details{ margin:0; }
.rule-pick summary{ display:flex; align-items:center; gap:8px; cursor:pointer; padding:4px 2px; list-style:none; }
.rule-pick summary::-webkit-details-marker{ display:none; }
.rule-pick summary::before{ content:"›"; display:inline-block; width:10px; color:var(--text-faint); transition:transform .15s ease; }
.rule-pick details[open] summary::before{ transform:rotate(90deg); }
.rule-pick label.rule-leaf{ display:flex; align-items:center; gap:8px; padding:3px 2px 3px 22px; cursor:pointer; color:var(--text-dim); }
.rule-pick label.rule-leaf.top{ padding-left:2px; color:var(--text); }
.rule-pick label.rule-leaf:hover{ color:var(--text); }
.rule-pick input{ accent-color:var(--text); margin:0; }
.rule-pick .wb-num{ font-size:12px; }
.rule-targets{ margin-top:4px; }
/* Tokens, not the dark theme's hex: #e85862 is --danger in dark, but on
   the light ground it is 3.50:1 where the light --danger is 6.5:1. The
   dark theme renders exactly as before. */
.wb-chip.rule-out, .rule-kind.rule-out{ color:var(--danger); border-color:color-mix(in srgb, var(--danger) 55%, transparent); }
.wb-chip.rule-remind, .rule-kind.rule-remind{ color:var(--blue); border-color:color-mix(in srgb, var(--blue) 55%, transparent); }
.rule-kind{
  display:inline-flex; align-items:center; padding:2px 8px; border-radius:6px; border:1px solid var(--hairline);
  font-family:var(--font-mono); font-size:11px; letter-spacing:0; text-transform:none; white-space:nowrap;
}
.rule-hit{ display:flex; flex-direction:column; gap:3px; padding:6px 0; }
.rule-hit + .rule-hit{ border-top:1px solid var(--hairline-soft); }
.rule-hit .rule-kind{ align-self:flex-start; }
.rule-hit .note{ color:var(--text-dim); font-size:13px; }
