/* ==========================================================================
   TFB Platform — one stylesheet, no framework, no web fonts.
   Dark, dense, and built to be read on a phone in a driveway.
   ========================================================================== */

/* Colour tokens.
   Every ratio below is computed, not estimated, and the working is in
   ACCESSIBILITY.md. Two kinds of threshold apply and they are not the same
   number: body text needs 4.5:1 against the surface behind it (WCAG 1.4.3),
   and the *boundary* of anything you can operate needs 3:1 (WCAG 1.4.11).
   That second one is why --line and --control-line are separate tokens. */
:root {
  --bg:        #16181C;
  --panel:     #1E2127;
  --panel-2:   #23262D;

  /* Decorative rules only: table row separators, panel edges, the divider
     under a panel head. 1.25:1 against --panel, which is deliberate — these
     are not component boundaries and 1.4.11 does not reach them. Raising
     them to 3:1 would turn a dense table into a wireframe. */
  --line:      #2E323A;
  --line-soft: #262A31;

  /* The boundary of anything a person operates: an input, a select, a
     textarea, a button, the switch track. 3.17:1 against --panel-2,
     3.37:1 against --panel, 3.72:1 against --bg, so it clears 1.4.11 on
     every surface this application puts a control on. The old value was
     --line at 1.25:1, which meant an unfocused text box was invisible as a
     box: you could see the text inside it and had to infer the field. */
  --control-line: #6A7383;
  --control-line-hover: #8A93A4;

  /* The focus ring, and the one colour in this file a tenant cannot change.
     `--accent` is overridden per company by an inline style in base.html, it
     is validated as a hex string and nothing checks what it looks like, so a
     company that typed in their real brand navy used to get a focus ring at
     1.15:1 — invisible, on every control, for every keyboard user in that
     company. WCAG 1.4.11 asks 3:1 of a focus indicator and this is not a
     threshold to leave to whoever is holding the colour picker.
     12.93:1 against --panel, 14.25:1 against --bg, 12.14:1 against --panel-2.
     Same reasoning as the skip link below: a focus ring is a system
     affordance, not branding. See ACCESSIBILITY.md and app/contrast.py. */
  --focus-ring: #E8E6E1;

  /* Ink for anything sitting *on* the brand colour: the mark in the header,
     a primary button's label, the count inside an active tab. Black or
     white, chosen per tenant by `contrast.ink_for` and overridden by the one
     inline style in base.html; this literal is the right answer for the
     default accent.

     It exists because of D-123. The brand colour is no longer used as a text
     colour anywhere — not for a link, a label, a status word or a heading —
     and the four places text still touches it — the mark in the header, the
     mark on the sign-in card, a primary button and an active tab's count —
     are places where the text is *on* it and the ink is therefore chosen
     rather than fixed. (The switch thumb is a fifth use of this token, as a
     fill rather than as ink, for the same reason: it sits on the accent.) Taking the
     better of black and white never falls below 4.58:1 on any colour in
     sRGB, which is the whole reason this is safe to hand to a colour picker.
     See app/contrast.py:ink_for for the arithmetic. */
  --accent-ink: #000000;

  /* The brand colour on hover: the same colour moved 12% toward the opposite
     of its ink, so the label can only become easier to read, never harder.
     Overridden per tenant by the same inline style as --accent. This literal
     is `contrast.hover_fill("#E07A18")`. */
  --accent-hover: #E48A34;

  --text:      #E8E6E1;   /* 12.93:1 on --panel */
  --muted:     #9AA0A9;   /*  6.13:1 on --panel, 5.75:1 on --panel-2 */
  /* The default brand colour. No contrast threshold applies to it since
     D-123 — it is a fill, a border and a tint, never a glyph — and a tenant
     replaces it with anything they like. The two numbers that used to be
     here (5.35:1 as text, 5.90:1 for the ink on it) were true only of this
     value and only while it was used as text. */
  --accent:    #E07A18;
  --accent-dim:#8A4A0E;

  /* Was #3D8B6F (3.93:1) and #A84343 (2.73:1) on --panel. Both failed 4.5:1
     for text, and both are used as text far more than as fill: "is-good" on
     a money figure, "is-bad" on an overdue date, tag-won, tag-lost, the
     pause banner's own heading. Lightened until they clear 4.5:1 on the
     darkest and the lightest surface they land on, with the hue kept so a
     page still reads as the same product. The rgba() tints built from the
     old values are left alone on purpose: a tint is a fill, not text. */
  --good:      #4FA383;   /*  5.30:1 on --panel, 4.98:1 on --panel-2 */
  --bad:       #D47070;   /*  4.88:1 on --panel, 4.58:1 on --panel-2 */

  --radius: 3px;
  --gap: 16px;
  --topbar-h: 53px;   /* keep in step with .topbar-inner min-height */
  --tap: 44px;        /* iOS minimum touch target */
  /* WCAG 2.2 2.5.8 Target Size (Minimum). --tap is what a phone wants;
     this is the floor a mouse-driven desktop control may not go below. */
  --target-min: 24px;

  /* Safe-area insets go through variables rather than being called inline, so
     there is a single place to change them and a test harness can override
     them to emulate a device headless Chrome cannot report. */
  --sat: env(safe-area-inset-top, 0px);
  --sab: env(safe-area-inset-bottom, 0px);
  --sal: env(safe-area-inset-left, 0px);
  --sar: env(safe-area-inset-right, 0px);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
          Arial, "Noto Sans", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* Anything scrolled into view — a focused input, an anchor — clears the
     sticky bar instead of landing underneath it. Includes the status-bar inset
     because the bar sits below it (see .topbar). */
  scroll-padding-top: calc(var(--topbar-h) + var(--sat) + 8px);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { margin: 0 0 .35em; font-weight: 600; line-height: 1.2; }
h1 { font-size: 1.5rem; letter-spacing: -0.01em; }
h2 { font-size: 1rem; }
h3 { font-size: .875rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
p  { margin: 0 0 .8em; }

a { color: var(--text); text-decoration-color: var(--line); text-underline-offset: 2px; }
a:hover { text-decoration-color: var(--text); }

.muted { color: var(--muted); }
.small { font-size: .8125rem; }
.is-good { color: var(--good); }
.is-bad  { color: var(--bad); }

/* Focus is always visible. This is a work tool; people tab through it.

   There is no `outline: none` anywhere in this file and there must never be
   one without a replacement indicator in the same rule — tests/
   test_accessibility.py fails the build on a bare one.

   The ring is --focus-ring and never --accent, because --accent is the
   tenant's and comes with no contrast guarantee. At 12.93:1 on --panel it
   clears WCAG 1.4.11's 3:1 by a wide margin on every surface and on every
   company's palette, including the ones nobody has chosen yet. */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: 2px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ------------------------------------------------------------ skip link --
   The first thing in the tab order on every page. The signed-in nav is up to
   sixteen links, and without this a keyboard user tabbed through all of them
   on the way to the page body — on every single navigation, all day.

   It is in the document and focusable at all times (not display:none, which
   is not focusable), and only moves into view when it has focus. It lands
   below the sticky bar rather than under it, which is WCAG 2.2 2.4.11 Focus
   Not Obscured applied to the one control most likely to trip it.
   ------------------------------------------------------------------------ */
.skiplink {
  position: fixed;
  top: calc(var(--sat) + 8px);
  left: 8px;
  z-index: 100;
  /* Off-screen rather than hidden, so it keeps its place in the tab order. */
  transform: translateY(-200%);
  /* Deliberately NOT var(--accent).

     A tenant sets their own accent colour, it is validated as a hex string
     and nothing checks its contrast, and the demo company's is #2F7D6E —
     against which the dark ink this used to carry is 3.62:1, under the 4.5:1
     a short label needs. The skip link is a system affordance rather than
     branding, so it takes the panel surface and the body ink: 12.93:1 on
     every tenant's palette, including the ones nobody has chosen yet. The
     focus ring around it is still the accent, which only has to clear 3:1.
     See ACCESSIBILITY.md, "the tenant accent colour". */
  background: var(--panel);
  color: var(--text);                       /* 12.93:1 */
  border: 2px solid var(--control-line);
  font-size: .875rem; font-weight: 600;
  padding: 9px 13px;
  border-radius: var(--radius);
  text-decoration: none;
  /* --tap, not --target-min. 2.5.8 asks 24 and this is a keyboard affordance
     that a thumb will rarely find, but scripts/mobile_audit.py holds every
     control in this application to 44 on a phone and an exception here would
     be an exception somebody copies. */
  min-height: var(--tap);
  display: inline-flex; align-items: center;
}
.skiplink:focus {
  transform: translateY(0);
}

/* --------------------------------------------------------------- topbar -- */

/* The page ships viewport-fit=cover and an Apple translucent status bar, so
   when it runs installed on a phone the layout extends under the notch and the
   home indicator. Without these insets the sticky bar renders beneath the
   clock and its contents are unreadable and untappable. */
.topbar {
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
  padding-top: var(--sat);
}
.topbar-inner {
  max-width: 1400px; margin: 0 auto;
  display: flex; align-items: center; gap: var(--gap);
  min-height: 52px; flex-wrap: wrap;
  padding-left: max(var(--gap), var(--sal));
  padding-right: max(var(--gap), var(--sar));
}
.brand { display: flex; align-items: center; gap: 9px; text-decoration: none; padding: 8px 0; }
.brand-mark {
  background: var(--accent); color: var(--accent-ink);
  font-weight: 700; font-size: .75rem; letter-spacing: .06em;
  padding: 3px 6px; border-radius: 2px;
}
.brand-text { font-weight: 600; font-size: .9375rem; }

.nav-toggle {
  margin-left: auto; display: none;
  background: transparent; border: 1px solid var(--line); color: var(--text);
  font-size: 1.1rem; line-height: 1; padding: 7px 11px; border-radius: var(--radius);
  cursor: pointer;
}

.mainnav { display: flex; align-items: center; gap: 2px; flex: 1; flex-wrap: wrap; }
.mainnav a {
  padding: 7px 11px; font-size: .875rem; text-decoration: none;
  color: var(--muted); border-radius: var(--radius); white-space: nowrap;
}
.mainnav a:hover { color: var(--text); background: var(--panel-2); }
.mainnav a.is-active {
  color: var(--text);
  /* The tint follows the brand, the word does not. */
  background: rgba(224,122,24,.12);
}
/* Pushes whatever follows it to the far end of a flex row. `.nav-spacer` is
   the nav's own copy and is hidden when the nav collapses on a phone; any
   other page wants `.spacer`, which keeps working at every width. */
.nav-spacer, .spacer { flex: 1; }
/* Standalone links, not links inside a sentence, so WCAG 2.2 2.5.8
   applies and the 24px floor is theirs to meet. They were about 19px
   tall on a desktop. */
.nav-user, .nav-signout {
  font-size: .8125rem;
  display: inline-flex; align-items: center; min-height: var(--target-min);
  padding: 0 8px;
}
.nav-user { color: var(--muted); }

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

.page {
  max-width: 1400px; margin: 0 auto;
  padding: var(--gap);
  /* Keep content clear of the notch in landscape and the home indicator. */
  padding-left: max(var(--gap), var(--sal));
  padding-right: max(var(--gap), var(--sar));
  padding-bottom: calc(var(--gap) + var(--sab));
}

.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--gap); margin-bottom: var(--gap); flex-wrap: wrap;
}
.page-head p { margin: 0; }
.page-head-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.backlink {
  font-size: .8125rem; color: var(--muted); text-decoration: none;
  /* Same reason as .nav-user: it sits on a line of its own above the
     page heading, so it is a standalone target. */
  display: inline-flex; align-items: center; min-height: var(--target-min);
}
.backlink:hover { color: var(--text); }

.count-badge {
  font-size: .8125rem; font-weight: 500; color: var(--muted);
  background: var(--panel-2); border: 1px solid var(--line);
  padding: 1px 7px; border-radius: 2px; vertical-align: middle;
}
.pipeline-chip {
  font-size: .8125rem; color: var(--muted);
  border: 1px solid var(--line); padding: 6px 10px; border-radius: var(--radius);
}

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

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--gap);
  margin-bottom: var(--gap);
}
.panel-flush { padding: 0; overflow: hidden; }
.panel.narrow, .form.narrow { max-width: 620px; }
.panel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 12px;
  padding-bottom: 10px; border-bottom: 1px solid var(--line-soft);
}
.panel-head h2 { margin: 0; }
/* An empty state is a page, not an absence. Every one of them says what to
   do next and offers the control that does it, so the actions need a row of
   their own that wraps on a phone. See tests/test_empty_states.py. */
.empty { text-align: center; padding: 40px var(--gap); }
.empty h2 { font-size: 1rem; }
.empty p { max-width: 54ch; margin-left: auto; margin-right: auto; }
.empty-actions {
  display: flex; flex-wrap: wrap; gap: 8px;
  justify-content: center; margin-top: 14px;
}
.empty-actions form { margin: 0; }
@media (max-width: 460px) {
  /* Stacked full-width buttons, the same shape the page head takes. */
  .empty-actions { flex-direction: column; align-items: stretch; }
}
.error-panel { max-width: 520px; margin: 60px auto; text-align: center; }
/* Was 3rem, which was sized for the word "404". The error pages say what
   happened in a sentence now, and a sentence at 48px wraps to four lines and
   reads as shouting. */
.error-panel h1 { font-size: 1.625rem; color: var(--text); margin-bottom: .5em; }
.error-panel p { max-width: 46ch; margin-left: auto; margin-right: auto; }

.split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); align-items: start; }
.stack > *:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------------- stats -- */

.stat-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--gap); margin-bottom: var(--gap);
}
.stat {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px;
}
.stat-label {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin-bottom: 4px;
}
.stat-value { font-size: 1.75rem; font-weight: 600; line-height: 1.1; letter-spacing: -0.02em; }

/* --------------------------------------------------------------- tables -- */

/* Two table classes, on purpose, and the difference is the phone.
   `table.data` folds into labelled cards below 860px and therefore needs a
   data-label on every cell. `table.table` keeps its columns and scrolls
   inside .table-scroll — which is the honest answer for the console's
   thirteen columns, where hiding one means the figure you came for is the
   one that is gone.

   They share this type and border treatment because they were visibly
   different products before: `.table` had no rules at all, so the console,
   the support log and the support view rendered as browser-default tables —
   serif-ish metrics, no header fill, no row separators — inside panels
   styled by this sheet. */
table.data, table.table { width: 100%; border-collapse: collapse; font-size: .875rem; }
table.data th, table.table th {
  text-align: left; font-size: .6875rem; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); font-weight: 600;
  padding: 10px 12px; border-bottom: 1px solid var(--line);
  background: var(--panel-2); white-space: nowrap;
}
table.data td, table.table td {
  padding: 10px 12px; border-bottom: 1px solid var(--line-soft); vertical-align: top;
}
/* A row header carries the row's identity, so it is left-aligned body text
   rather than an uppercase column label. */
table.table tbody th[scope="row"], table.data tbody th[scope="row"] {
  text-transform: none; letter-spacing: 0; font-size: .875rem;
  color: var(--text); background: none; white-space: normal;
  border-bottom: 1px solid var(--line-soft); vertical-align: top;
}
table.data tbody tr:last-child td, table.table tbody tr:last-child td,
table.data tbody tr:last-child th, table.table tbody tr:last-child th { border-bottom: 0; }
table.data tbody tr:hover, table.table tbody tr:hover { background: var(--panel-2); }
table.data tr.is-inactive, table.table tr.is-inactive { opacity: .55; }

.rowlink { font-weight: 600; text-decoration: none; }
.rowlink:hover { color: var(--text); }
.sub { display: block; font-size: .75rem; color: var(--muted); }
.row-actions { white-space: nowrap; text-align: right; }

/* ---------------------------------------------------------- record cards --
   Purpose-built, not a table folded up. The generic table-to-stack transform
   printed a label above every field, which put one contact on a phone screen
   and made a 38-row list 38 screens long. Here the card face carries only what
   a rep scans for — who, where they work, how cold they are — with no field
   labels at all, and the rest sits behind a native <details>. Roughly five
   records per 390px screen.
   ------------------------------------------------------------------------ */

.cards {
  list-style: none; margin: 0 0 var(--gap); padding: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 10px;
  row-gap: 1px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line-soft);
}
.card:last-child { border-bottom: 0; }

.card-title {
  grid-row: 1; grid-column: 1;
  font-size: 1.0625rem; font-weight: 600; line-height: 1.25;
  text-decoration: none;
  overflow-wrap: anywhere;
}
.card-title:hover { color: var(--text); }
.card-status { grid-row: 1; grid-column: 2; justify-self: end; align-self: center; }

.card-sub {
  grid-row: 2; grid-column: 1 / -1;
  margin: 0; font-size: .875rem; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.card-more { grid-row: 3; grid-column: 1; min-width: 0; }
.card-more > summary {
  display: flex; align-items: center; gap: 8px;
  list-style: none; cursor: pointer;
  min-height: var(--tap);      /* the disclosure is the card's second target */
  margin-left: -4px; padding: 0 4px;
  border-radius: var(--radius);
}
.card-more > summary::-webkit-details-marker { display: none; }
.card-more > summary:hover { background: var(--panel-2); }
.card-meta { font-size: .8125rem; color: var(--muted); }
.card-chevron {
  width: 0; height: 0; flex: none;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--muted);
  transition: transform .12s linear;
}
.card-more[open] > summary .card-chevron { transform: rotate(180deg); }

.card-detail {
  display: grid;
  /* minmax(0,1fr), not 1fr: a plain 1fr track has an automatic minimum of its
     content's min-content width, so a long email address pushes the column
     past the card instead of wrapping inside it. */
  grid-template-columns: auto minmax(0, 1fr);
  gap: 3px 12px; margin: 2px 0 8px; font-size: .8125rem;
}
.card-detail dt { color: var(--muted); white-space: nowrap; }
.card-detail dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

/* start, not center: row 3 grows to hold the expanded disclosure, and a
   centred button drifts down the card as it opens. Aligning to the top keeps
   Edit level with the summary line whether the card is open or shut. */
.card-edit { grid-row: 3; grid-column: 2; align-self: start; justify-self: end; }

/* ----------------------------------------------------------------- tags -- */

.tag {
  display: inline-block; font-size: .6875rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em;
  padding: 2px 6px; border-radius: 2px;
  border: 1px solid var(--line); color: var(--muted); background: var(--panel-2);
  white-space: nowrap;
}
.tag-won, .tag-complete, .tag-active { color: var(--good); border-color: rgba(61,139,111,.5); }
.tag-lost, .tag-dormant, .tag-inactive, .tag-overdue { color: var(--bad); border-color: rgba(168,67,67,.5); }
/* In-flight states. Neutral ink and a neutral edge: --good and --bad are
   spoken for by won and lost, and the brand colour is not available to carry
   meaning any more (D-123). The word in the tag is what says which state it
   is, which was always true — the colour was never doing that work alone. */
.tag-engaged, .tag-in_progress, .tag-scheduled { color: var(--text); border-color: var(--control-line); }
.tag-plan-vip { color: var(--text); border-color: var(--control-line); }
/* A feature the plan does not include. Deliberately quiet rather than red:
   it is not an error, it is a thing this customer has not bought. */
.tag-locked { color: var(--muted); border-style: dashed; }

/* A tag is normally a label *on* something and a label has no target size to
   meet. The setup wizard is the one place a tag is itself the link — the six
   step chips across the top of every wizard page — and those measured 22px
   high in the browser, under the 24 WCAG 2.2 2.5.8 asks of a target. Sized
   on `a.tag` rather than in the wizard's own rules so the next page that
   makes a tag clickable inherits the floor instead of re-discovering it. */
a.tag {
  display: inline-flex; align-items: center;
  min-height: var(--target-min);
}

/* ------------------------------------------------------- lists and feed -- */

.queue, .feed, .bullets, .togglelist { list-style: none; margin: 0; padding: 0; }

.queue-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 0; border-bottom: 1px solid var(--line-soft); font-size: .875rem;
}
.queue-row:last-child { border-bottom: 0; }
.queue-name { font-weight: 600; text-decoration: none; }
a.queue-name:hover { color: var(--text); }
.queue-meta { color: var(--muted); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.queue-touch { font-size: .8125rem; color: var(--muted); white-space: nowrap; }
/* Company names are the first thing to get squeezed out when four items share
   one line, so give the ellipsis something to work with before it truncates. */
.queue-meta { min-width: 6ch; }

/* Grid, not flex. The three parts of a feed row need to reflow onto two lines
   on a narrow screen, and a flex row cannot do that without wrapping rules
   that fight the "date never wraps" requirement. Grid lets the date move to
   its own line by changing one template, with no risk of it landing on top of
   the summary. */
.feed-row {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr) auto;
  gap: 2px 10px;
  align-items: baseline;
  padding: 8px 0; border-bottom: 1px solid var(--line-soft); font-size: .875rem;
}
.feed-row:last-child { border-bottom: 0; }
.feed-type {
  grid-column: 1; font-size: .6875rem; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted); font-weight: 600;
}
.feed-agent_run { color: var(--text); font-weight: 700; }
/* min-width:0 lets the summary shrink; overflow-wrap keeps a long unbroken
   token (a URL, an address) from pushing the column wider than the panel. */
.feed-summary { grid-column: 2; min-width: 0; overflow-wrap: anywhere; }
.feed-when { grid-column: 3; font-size: .75rem; white-space: nowrap; }

.bullets li { padding: 6px 0 6px 18px; position: relative; }
.bullets li::before {
  content: ""; position: absolute; left: 2px; top: 14px;
  width: 5px; height: 5px; background: var(--accent);
}

.togglelist li {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line-soft);
}
.togglelist li:last-child { border-bottom: 0; }

.deflist { display: grid; grid-template-columns: 130px 1fr; gap: 6px 12px; margin: 0; font-size: .875rem; }
.deflist dt { color: var(--muted); font-size: .8125rem; }
.deflist dd { margin: 0; }
/* The weekly report's figures are links — "11" linking to the eleven contacts
   it counts — and a two-character link is a 13x17 target. The touch block
   further down grows these to 44px, but it is `(pointer: coarse),
   (max-width: 860px)`, so a mouse on a 1280px screen got 13x17 and WCAG 2.2
   2.5.8 asks 24 of both of us. Floored here, unconditionally; the touch rule
   still raises it to 44 where a finger is involved. */
.deflist dd a {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--target-min); min-width: var(--target-min);
}

.notes { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line-soft); }
.notes p { white-space: pre-wrap; font-size: .875rem; }
.stub-lede { font-size: 1rem; color: var(--muted); max-width: 62ch; }
.legend { max-width: 68ch; }

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

label { display: block; font-size: .75rem; text-transform: uppercase;
        letter-spacing: .06em; color: var(--muted); margin-bottom: 4px; }

/* --control-line, not --line: the edge of a text box is the only thing that
   says a text box is there, so WCAG 1.4.11 asks 3:1 of it. See the token. */
input[type=text], input[type=email], input[type=password], input[type=search],
input[type=date], input[type=number], input[type=tel], input[type=file],
input[type=url], input:not([type]), select, textarea {
  width: 100%; background: var(--bg); color: var(--text);
  border: 1px solid var(--control-line); border-radius: var(--radius);
  padding: 8px 10px; font: inherit; font-size: .875rem;
}
input:hover, select:hover, textarea:hover { border-color: var(--control-line-hover); }
/* A second indicator under the ring: the field's own border takes the accent
   too. Redundant on purpose — if a tenant's accent is dark this border
   disappears and the --focus-ring outline above is still there, which is why
   the outline and not the border is the one 1.4.11 is satisfied by. */
input:focus, select:focus, textarea:focus { border-color: var(--accent); outline-offset: 0; }

/* Checkbox and radio: 24×24 is WCAG 2.2 2.5.8, and the browser default is
   about 13px. Sized here rather than per page because the bulk-select boxes
   on the prospecting table and the day boxes in the setup wizard were the
   two that were genuinely hard to hit with a mouse. accent-color keeps the
   native control — a hand-built one loses the indeterminate state and the
   platform's own high-contrast handling. */
input[type=checkbox], input[type=radio] {
  width: var(--target-min); height: var(--target-min);
  min-width: var(--target-min); min-height: var(--target-min);
  margin: 0; accent-color: var(--accent); cursor: pointer;
}

/* The branding step's colour picker. It had no rule at all, so it rendered
   at whatever the platform's default swatch size is — about 50x24 on one
   browser and 64x30 on another, which is the only control in the app whose
   size came from the operating system. */
input[type=color] {
  width: 56px; height: 34px; padding: 2px; cursor: pointer;
  background: var(--bg); border: 1px solid var(--control-line);
  border-radius: var(--radius);
}
textarea { resize: vertical; min-height: 80px; }
select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 15px) 50%, calc(100% - 10px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 28px;
}

.field { margin: 0 0 14px; }
.field-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 0 var(--gap); }
.form-actions { display: flex; align-items: center; gap: 10px; margin-top: 6px; flex-wrap: wrap; }
.form-actions-spacer { flex: 1; }
.danger-form { margin-top: -4px; }
.fieldset {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px var(--gap); margin: 0 0 var(--gap);
}
.fieldset legend {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); padding: 0 6px;
}

.filterbar {
  display: flex; gap: 8px; margin-bottom: var(--gap); flex-wrap: wrap;
  align-items: center;
}
.filterbar input[type=search] { flex: 2 1 220px; width: auto; }
.filterbar select { flex: 1 1 150px; width: auto; }

.inline-form {
  display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap;
  padding-bottom: 14px; border-bottom: 1px solid var(--line-soft);
}
.inline-form select { flex: 0 0 120px; width: auto; }
.inline-form input { flex: 1 1 160px; width: auto; }

/* -------------------------------------------------------------- buttons -- */

/* .btn's own fill is --panel-2 on --panel, which is 1.06:1 — invisible. The
   border is the whole affordance, so it is --control-line and not --line.
   min-height is the 2.5.8 floor; .btn-sm at 5px padding and 12px type came
   out at 26px, which passed, but only by accident of the font metrics. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; font-size: .8125rem; font-weight: 600;
  padding: 8px 13px; border-radius: var(--radius); cursor: pointer;
  min-height: var(--target-min);
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--control-line); text-decoration: none; white-space: nowrap;
}
.btn:hover { border-color: var(--control-line-hover); background: #292D35; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
/* Away from the ink, never toward it.
   This was `#F08A25`, a lighter copy of the *default* orange, and it did not
   follow the tenant — so a navy-branded company's button turned orange on
   hover while its label stayed the white chosen for navy: 2.51:1, on the one
   control the customer is actually pressing. `--accent-hover` is computed by
   `contrast.hover_fill`, which mixes the brand colour toward the opposite of
   its own ink, so the hover state's contrast is always *better* than the
   resting state's and the floor in `ink_for` holds for both. */
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-quiet { background: transparent; color: var(--muted); }
.btn-quiet:hover { color: var(--text); }
.btn-danger { background: transparent; color: var(--bad); border-color: var(--bad); }
.btn-danger:hover { background: rgba(168,67,67,.12); border-color: var(--control-line-hover); }
.btn-sm { padding: 5px 9px; font-size: .75rem; }
.btn-block { width: 100%; margin-top: 6px; }

/* A form that is being submitted.
   Not `disabled`: a disabled submit button is left out of the form data, so
   the outreach bulk bar — three buttons sharing name="action" with different
   values — would post with no action at all and the whole bulk approve would
   silently do nothing. aria-disabled tells assistive technology, the class
   stops the pointer, and app.js refuses the second submit event. */
.btn[aria-disabled="true"], .btn.is-pending {
  opacity: .7; cursor: progress; pointer-events: none;
}

/* -------------------------------------------------------- switch toggle -- */

.switch { display: inline-flex; align-items: center; margin: 0; cursor: pointer; }
.switch input {
  position: absolute; opacity: 0; margin: 0; cursor: pointer;
  /* Overrides the 24px floor on a checkbox: this one is invisible and the
     visible track plus its label carry the target. */
  width: 36px; height: 20px; min-width: 0; min-height: 0;
}
/* --control-line on the track, not --line. An off switch is the state you
   have to be able to see from across the switchboard, and at --line (1.25:1
   against the row behind it) the pill had no edge at all — the only thing
   distinguishing "off" from "no switch here" was a 14px grey dot. */
.switch-track {
  display: block; width: 36px; height: 20px; border-radius: 10px;
  background: #33373F; border: 1px solid var(--control-line); position: relative;
  transition: background-color .12s linear, border-color .12s linear;
}
.switch-thumb {
  position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 50%; background: var(--muted);
  transition: transform .12s linear, background-color .12s linear;
}
.switch input:checked + .switch-track { background: var(--accent); border-color: var(--accent); }
.switch input:checked + .switch-track .switch-thumb { transform: translateX(16px); background: var(--accent-ink); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.switch input:disabled + .switch-track { opacity: .5; cursor: wait; }
.switch-track.is-error { border-color: var(--bad); }

.cell-toggle { text-align: center; }
.col-feature { text-align: center; }
.col-feature span { display: inline-block; max-width: 82px; white-space: normal; line-height: 1.25; }

/* ----------------------------------------------------------- danger zone --
   Destructive actions live at the bottom of a record, visually quieter than
   the primary action and separated from it by the whole page. */
.danger-zone {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap); flex-wrap: wrap;
  border: 1px solid var(--line);
  border-left: 3px solid rgba(168, 67, 67, .55);
  border-radius: var(--radius);
  padding: 12px var(--gap);
  margin-bottom: var(--gap);
  background: var(--panel);
}
.danger-zone h2 { font-size: .875rem; margin-bottom: 2px; }
.danger-zone p { margin: 0; max-width: 52ch; }

/* ------------------------------------------------------- flashes/toasts -- */

.flashes { margin-bottom: var(--gap); }
.flash {
  border: 1px solid var(--line); border-left: 3px solid var(--muted);
  background: var(--panel); padding: 9px 12px; margin-bottom: 8px;
  font-size: .875rem; border-radius: var(--radius);
}
.flash-ok    { border-left-color: var(--good); }
.flash-error { border-left-color: var(--bad); }
.flash-warn  { border-left-color: var(--accent); }

.toast-host {
  position: fixed; z-index: 60;
  bottom: calc(16px + var(--sab));
  right: calc(16px + var(--sar));
  display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
  pointer-events: none;
}
.toast {
  background: var(--panel); border: 1px solid var(--line);
  border-left: 3px solid var(--good);
  padding: 9px 13px; border-radius: var(--radius); font-size: .8125rem;
  max-width: 320px;
}
.toast-error { border-left-color: var(--bad); }

/* ----------------------------------------------------------------- auth -- */

.body-auth { display: flex; min-height: 100vh; }
.auth-wrap { margin: auto; padding: var(--gap); width: 100%; max-width: 380px; }
.auth-card { background: var(--panel); border: 1px solid var(--line);
             border-radius: var(--radius); padding: 26px 22px; }
.auth-mark {
  background: var(--accent); color: var(--accent-ink); font-weight: 700;
  display: inline-block; padding: 4px 8px; letter-spacing: .06em;
  font-size: .8125rem; border-radius: 2px; margin-bottom: 14px;
}
.auth-card h1 { font-size: 1.25rem; }
.auth-card .field, .auth-card label { text-align: left; }
.auth-card input { margin-bottom: 12px; }

/* ------------------------------------------------------------ credbox -- */

.credbox {
  border: 1px solid var(--accent-dim); border-left: 3px solid var(--accent);
  background: rgba(224,122,24,.06); border-radius: var(--radius);
  padding: 14px; margin: var(--gap) 0;
}
.credbox h2 { font-size: .8125rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.cred { display: flex; gap: 10px; align-items: baseline; margin-bottom: 6px; flex-wrap: wrap; }
.cred-label { font-size: .75rem; color: var(--muted); min-width: 150px; }
.cred code, code {
  font-family: var(--mono); font-size: .8125rem;
  background: var(--bg); border: 1px solid var(--line);
  padding: 3px 7px; border-radius: 2px; user-select: all;
}

/* ------------------------------------------------------------- account -- */

/* Secondary link under an auth card's button: "forgot your password", "back to
   sign in". Centred to match the block button above it. */
.auth-alt { margin: 14px 0 0; text-align: center; font-size: .8125rem; }
.auth-alt a { color: var(--muted); }
.auth-alt a:hover { color: var(--text); }

.field-hint { display: block; margin-top: 4px; }

/* A reset link is long and has no spaces, so it has to be told it may break
   mid-string or it widens the card past the viewport. */
.cred-wrap { overflow-wrap: anywhere; word-break: break-all; }

/* ---------------------------------------------------------------- team -- */

/* The per-user controls live inside the card's <details>, so three forms sit
   in a row that must wrap on a phone rather than overflow. */
.card-controls {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin: 4px 0 6px;
}
.card-controls form { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.card-controls select { width: auto; flex: 0 1 120px; margin: 0; }
.card-controls-note { grid-column: 1 / -1; margin: 2px 0 6px; }

/* A card list already inside a .panel does not need its own frame. */
.cards-flush { margin: 0; border: 0; background: none; border-radius: 0; }

/* A switched-off account stays legible but reads as inactive, the same way an
   inactive tenant row does on the switchboard. */
.card.is-inactive .card-title, .card.is-inactive .card-sub { opacity: .55; }

/* --------------------------------------------------------- prospecting -- */

/* Three tiers that have to be scannable as a ranking, in neutrals.
   Tier 1 was the brand colour, which made the ladder obvious and is no
   longer available (D-123). Ink and edge now fall together — bright with a
   visible edge, bright with a faint one, muted and dashed — so the column
   still reads top to bottom at a glance and the digit is not the only cue.
   The zones page sells the tier as the cadence; a ranking nobody can scan
   is that page's whole argument gone. */
.tag-tier1 { color: var(--text); border-color: var(--control-line); font-weight: 700; }
.tag-tier2 { color: var(--text); border-color: var(--line); }
.tag-tier3 { color: var(--muted); border-color: var(--line); border-style: dashed; }

.scrapetable td { vertical-align: top; }
.cell-fit select { width: auto; min-width: 110px; margin: 0; }
.cell-fit.is-error select { outline: 2px solid var(--bad); }

/* -------------------------------------------------------------- pause -- */

/* Loud on purpose. While this is up it is the most important fact on the
   screen, so it sits above the header's content, spans the full width, and
   does not scroll away with the page body. */
.pausebar {
  background: rgba(168, 67, 67, .16);
  border-bottom: 2px solid var(--bad);
  color: var(--text);
  padding: 10px var(--gap);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: .875rem;
}
.pausebar strong { color: var(--bad); }
.pausebar-who { color: var(--muted); font-size: .8125rem; flex: 1; min-width: 200px; }

/* --------------------------------------------------------------- tabs -- */

.tabbar {
  display: flex; gap: 2px; flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--gap);
}
.tab {
  padding: 8px 12px; text-decoration: none; color: var(--muted);
  border-bottom: 2px solid transparent; display: inline-flex;
  align-items: center; gap: 6px; min-height: var(--tap);
}
.tab:hover { color: var(--text); }
/* The underline is the brand, the label is not.
   A two-pixel rule in an unchecked colour cannot be the only mark of the
   active tab — for a dark-branded company it is about 1.1:1 against the
   panel, which is no mark at all. So the label also goes from --muted to
   --text and takes weight, and the markup carries `aria-current="page"`.
   Three cues, two of which do not depend on the tenant's colour. */
.tab.is-active { color: var(--text); font-weight: 600; border-bottom-color: var(--accent); }
.tab-count {
  font-size: .6875rem; background: var(--panel-2); color: var(--muted);
  border-radius: 10px; padding: 1px 7px; font-weight: 600;
}
/* 4.12:1 before: --accent on an 18% accent tint of --panel, at 11px, so
   it needed 4.5:1 and did not have it. Filled instead, and since D-123 the
   ink on that fill is chosen per tenant rather than being a fixed #16181C —
   so the pill is readable on any brand colour, and a solid pill reads more
   plainly as the live tab than tinted text did. */
.tab.is-active .tab-count { background: var(--accent); color: var(--accent-ink); }

/* ------------------------------------------------------------ outreach -- */

.bulkbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 12px; margin-bottom: 8px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* The words beside a checkbox or a radio are the choice itself, not the
   name of a field above one, so they are body text. The global `label` rule
   makes every label small uppercase grey — which is right over an input and
   wrong beside a tick box, where it turned "Select all on this page" and the
   seven weekday names in the setup wizard into column headings. */
.bulk-all, .togglelist label, .switch, .draft-pick {
  text-transform: none; letter-spacing: 0;
  font-size: .875rem; color: var(--text); margin-bottom: 0;
}
.bulk-all { display: inline-flex; align-items: center; gap: 8px; min-height: var(--tap); }
.bulk-all input { width: var(--target-min); height: var(--target-min); }

.draft { grid-template-columns: auto minmax(0, 1fr) auto; }
.draft .card-title { grid-column: 2; }
.draft .card-status { grid-column: 3; }
.draft .card-sub { grid-column: 2 / -1; }
.draft-pick { grid-row: 1; grid-column: 1; display: flex; align-items: center;
              min-height: var(--tap); min-width: var(--tap); }
.draft-pick input { width: var(--target-min); height: var(--target-min); }

/* Why this draft exists. The approver needs it before they need the text. */
.draft-why {
  grid-column: 2 / -1; margin: 2px 0 4px;
  /* Weight rather than colour. The approver needs this before they need the
     draft, and at --text with no weight it read as the first line of the
     draft rather than as a note about it. */
  font-size: .8125rem; color: var(--text); font-weight: 600;
}
.draft-warn { grid-column: 2 / -1; margin: 0 0 4px; display: flex; gap: 4px; flex-wrap: wrap; }
.draft-body { grid-column: 2 / -1; }

.draft-text {
  font-family: var(--font); font-size: .875rem; line-height: 1.5;
  white-space: pre-wrap; overflow-wrap: anywhere;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px; margin: 6px 0;
}

.draftactions { background: none; border: 0; }
.draft-actionrow {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 12px; border-bottom: 1px solid var(--line-soft);
}
.draft-actionrow form { display: inline; }
.editwrap > summary { list-style: none; cursor: pointer; }
.editwrap > summary::-webkit-details-marker { display: none; }
.editwrap .form { margin-top: 8px; width: 100%; }
/* Open, it takes a row of its own.
   `.draft-actionrow` is a wrapping flex row, so a closed <details> is a flex
   item the width of its "Edit" summary — about 185px. The form inside is
   `width: 100%` of *that*, which put the subject field, a twelve-row body
   textarea and the save button into a 185px column pinned to the right-hand
   edge of a 1248px row, with the button hanging 37px outside it. Found at
   1280px in a browser; nothing in the suite opens this editor and measures
   it. `flex-basis: 100%` makes the open editor wrap onto its own line and
   fill the card; the summary keeps its own width so the Edit control does
   not become a full-width button. */
.editwrap[open] { flex: 1 1 100%; }
.editwrap[open] > summary { width: fit-content; }

.snooze-all { margin: var(--gap) 0; text-align: center; }

@media (max-width: 600px) {
  .draft-actionrow { gap: 6px; }
  .draft-actionrow .btn { flex: 1 1 auto; }
}

/* ------------------------------------------------------------- paging -- */

.sortbar { display: flex; justify-content: flex-end; margin-bottom: 8px; }
.sortbar select { width: auto; min-width: 180px; margin: 0; }

.pager { margin: var(--gap) 0; text-align: center; }
.pager .btn { margin-bottom: 6px; }
.pager-count { margin: 0; }

@media (max-width: 600px) {
  .sortbar { justify-content: stretch; }
  .sortbar select { width: 100%; }
}

/* -------------------------------------------------------------- import -- */

/* One row per CSV column: the header we found, an arrow, and the field it
   maps to. Grid rather than flex so the arrows line up down the page however
   long the header names are. */
.maplist { list-style: none; margin: 0 0 var(--gap); padding: 0; }
.maprow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 8px; align-items: center;
  padding: 5px 0; border-bottom: 1px solid var(--line-soft);
}
.maprow:last-child { border-bottom: 0; }
.mapfrom {
  font-family: var(--mono); font-size: .8125rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.maparrow { color: var(--muted); }
.maprow select { width: 100%; margin: 0; }

@media (max-width: 600px) {
  /* The arrow stops earning its column on a phone; stack instead. */
  .maprow { grid-template-columns: minmax(0, 1fr); gap: 2px; padding: 8px 0; }
  .maparrow { display: none; }
}

/* ----------------------------------------------------------- audit log -- */

/* Wider action column than the activity feed: "password.reset_link_issued" is
   the longest name and clipping it would hide which action it was. */
.auditfeed .feed-row { grid-template-columns: 170px minmax(0, 1fr) auto; }
.audit-action {
  font-family: var(--mono); font-size: .6875rem; color: var(--muted);
  text-transform: none; letter-spacing: 0;
  overflow-wrap: anywhere;
}
.audit-actor { font-weight: 600; }
.audit-payload { display: block; font-family: var(--mono); overflow-wrap: anywhere; }

/* ---------------------------------------------------------- responsive -- */

@media (max-width: 1100px) {
  .col-feature span { max-width: 60px; font-size: .625rem; }
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; }
  .nav-toggle { display: block; }
  /* `flex: 0 0 100%` is the load-bearing declaration here, and `width: 100%`
     cannot replace it. The base rule sets `flex: 1`, i.e. flex-basis 0, and a
     flex item's basis wins over its width on the main axis — so the nav
     measured as zero-width, fitted on the same line as the brand, and never
     wrapped. It rendered as a ~59px column pinned to the right of the header,
     starting at the top of a bar that had grown 400px tall, with every label
     clipped mid-word. A full basis forces .topbar-inner to wrap and gives the
     panel its own row underneath the brand row. */
  .mainnav {
    display: none;
    flex: 0 0 100%;
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  /* The open nav is nine rows tall on a feature-rich tenant. Inside a sticky
     header that would cover most of a phone screen and stay there while you
     scroll, hiding the page heading and its buttons. Cap it to the space below
     the bar and let it scroll internally instead. */
  .mainnav.is-open {
    display: flex;
    max-height: calc(100vh - var(--topbar-h) - var(--sat));
    max-height: calc(100dvh - var(--topbar-h) - var(--sat));
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Its own surface and a visible edge, so it reads as a panel over the page
       rather than as loose links. The bar is opaque, but saying so here means
       the panel does not depend on inheriting it. */
    background: var(--panel);
    border-top: 1px solid var(--line);
    padding-bottom: calc(10px + var(--sab));
  }
  .mainnav a {
    padding: 11px 8px; border-bottom: 1px solid var(--line-soft); border-radius: 0;
    display: flex; align-items: center; min-height: var(--tap);
    /* Labels wrap rather than being clipped if the panel is ever narrow. */
    white-space: normal;
  }
  /* Scoped to the nav on purpose. Unscoped, this hid the spacer on every
     page that had borrowed the class, so the action buttons on the outreach
     and prospecting rows stopped being pushed to the right edge and sat
     wherever the label happened to end — ragged, on the width where tidiness
     matters most. */
  .mainnav .nav-spacer { display: none; }
  .nav-user { padding: 10px 8px; }
  .nav-signout { display: flex; align-items: center; min-height: var(--tap); padding: 0 8px; }

  /* Tables become stacked cards. Sideways scrolling on a phone is a
     non-starter for a tool used one-handed in a truck. */
  table.data, table.data tbody, table.data tr, table.data td { display: block; width: 100%; }
  table.data thead { display: none; }
  table.data tr {
    border-bottom: 1px solid var(--line);
    padding: 10px 12px;
  }
  table.data tbody tr:last-child { border-bottom: 0; }
  table.data td {
    border: 0; padding: 3px 0;
    display: flex; align-items: baseline; justify-content: space-between;
    /* wrap is required, not cosmetic: .sub below sets flex-basis:100% to claim
       its own line, and without wrapping it would sit on the same line and
       overflow — the same defect the activity feed had. Row gap stays 0 so a
       wrapped secondary line sits tight under its value. */
    flex-wrap: wrap; column-gap: 12px; row-gap: 0;
  }
  table.data td::before {
    content: attr(data-label);
    flex: 0 0 42%;
    font-size: .6875rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--muted); font-weight: 600;
  }
  table.data td:empty, table.data td[data-label=""]::before { display: none; }
  .row-actions { text-align: left; justify-content: flex-start; }
  .cell-toggle, .col-feature { text-align: left; }
  /* A secondary line (a job title, a tenant slug) belongs under its value, not
     beside it competing for the same few characters of width. */
  table.data td .sub { flex-basis: 100%; text-align: right; }
  /* Let the value itself shrink and wrap normally rather than forcing the row
     wider than the card. */
  table.data td > * { min-width: 0; }

  .field-grid { grid-template-columns: 1fr; }

  /* Date drops to its own line under the summary instead of sharing one. */
  .feed-row { grid-template-columns: 62px minmax(0, 1fr); }
  .feed-when { grid-column: 2; }

  /* The audit log's action names are long enough that a fixed first column
     leaves nothing for the actor. Stack all three instead. */
  .auditfeed .feed-row { grid-template-columns: minmax(0, 1fr); }
  .auditfeed .feed-type,
  .auditfeed .feed-summary,
  .auditfeed .feed-when { grid-column: 1; }

  /* Four items on one queue line leaves the company name a few pixels wide.
     Two lines: name and status on top, company and recency underneath. */
  .queue-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 2px 10px;
    align-items: center;
  }
  .queue-name  { grid-column: 1; grid-row: 1; }
  .queue-row .tag { grid-column: 2; grid-row: 1; justify-self: end; }
  .queue-meta  { grid-column: 1; grid-row: 2; min-width: 0; }
  .queue-touch { grid-column: 2; grid-row: 2; justify-self: end; }
}

/* --- Touch sizing -------------------------------------------------------
   Applied by pointer type rather than width: a small tablet with a mouse does
   not need 44px rows, and a large phone does. --tap is the iOS minimum. */
@media (pointer: coarse), (max-width: 860px) {
  .btn { min-height: var(--tap); display: inline-flex; align-items: center; justify-content: center; }
  .btn-sm { min-height: var(--tap); padding: 5px 12px; }
  .nav-toggle { min-width: var(--tap); min-height: var(--tap); }
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
  select, textarea { min-height: var(--tap); }
  /* Checkbox and radio are deliberately NOT grown to 44px here. They are
     24x24 at every width, which is what WCAG 2.2 2.5.8 asks (AA); the 44px
     of 2.5.5 is AAA, and where this application can reach it — the bulk
     select, the draft pick, the switch — it does so by wrapping the control
     in a 44px label rather than by inflating the box itself. A 44px native
     checkbox beside 13px type does not look like this product. */
  /* Inline links grow their hit area vertically rather than gaining borders or
     backgrounds, so the page looks the same and only the target changes.
     inline-flex + min-height hits exactly 44px regardless of font metrics. */
  .rowlink, .queue-name, .backlink, .brand, .nav-user, .nav-signout,
  .deflist dd a, table.data td a, .feed-summary a,
  .card-detail a, .card-controls a, .card-controls-note a {
    display: inline-flex; align-items: center; min-height: var(--tap);
  }
  /* The card title is the record's primary target, so it takes the full width
     of its grid cell rather than only the width of the name. */
  .card-title {
    display: flex; align-items: center; min-height: var(--tap);
  }
  /* The switch keeps its 36x20 look; the label around it carries the target,
     and the (invisible) checkbox is stretched to fill it so a tap anywhere
     inside registers even where the label's own hit testing does not reach. */
  .switch { min-height: var(--tap); min-width: var(--tap); justify-content: center; }
  .switch input { top: 0; left: 0; width: 100%; height: 100%; }
}

/* --- Phone cards --------------------------------------------------------
   Below 600px the label-beside-value split leaves the value about half a
   column, which wraps names like "Solveig Fitzwilliam" mid-word. Stack the
   label above a full-width value instead. */
@media (max-width: 600px) {
  table.data td {
    display: block;
    padding: 5px 0;
  }
  table.data td::before {
    display: block;
    flex: none;
    margin-bottom: 1px;
  }
  table.data td .sub { text-align: left; }
  table.data tr { padding: 12px; }
  /* Values that were right-aligned by the two-column layout go back to the
     left edge now that they own the full width. */
  table.data td, table.data td .rowlink { text-align: left; }
}

@media (max-width: 460px) {
  :root { --gap: 12px; }
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
  .stat-value { font-size: 1.375rem; }
  h1 { font-size: 1.25rem; }
  .page-head-actions { width: 100%; }
  .page-head-actions .btn { flex: 1; text-align: center; }
  .deflist { grid-template-columns: 1fr; gap: 0 0; }
  .deflist dt { margin-top: 8px; }
  .toast-host { left: 12px; right: 12px; align-items: stretch; }
  .toast { max-width: none; }
}

/* Motion is decoration here, never information. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------- owner console -------- */

/* States the console reports. Colour carries meaning here, so each one is
   also a word in the markup — a reader who cannot tell the orange from the
   red still gets "Trial expired" and "Every run failing" in text. */
.tag-ok, .tag-paying, .tag-connected { color: var(--good); border-color: rgba(61,139,111,.5); }
.tag-failing, .tag-expired, .tag-revoked, .tag-unpaid, .tag-canceled {
  color: var(--bad); border-color: rgba(168,67,67,.5);
}
.tag-warning, .tag-trialing, .tag-stale, .tag-error {
  color: var(--text); border-color: var(--control-line);
}
/* "Never run" and "no billing record" are not faults — they are a company
   that has not got there yet, and red would send somebody to fix nothing. */
.tag-never, .tag-none, .tag-disconnected { color: var(--muted); border-style: dashed; }

/* The one tile on the console worth looking at.
   `--bad`, not the brand colour: this is a fixed semantic token at 4.88:1 on
   --panel, it is the same red the failing rows already use, and D-123 is
   about the *brand* colour rather than about colour. The border carries it
   too, because the first version of this fix set only the text colour, which
   resolved to the value the tile already had — a rule that did nothing on
   the one tile that needed to shout. */
.stat-warn { border-color: var(--bad); }
.stat-warn .stat-value { color: var(--bad); }

/* The console's table is wider than a phone and there is no useful way to
   fold thirteen columns. Scrolling it is honest; hiding columns would mean
   the figure you came for is the one that is gone. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll table { min-width: 900px; }

.plainlist { list-style: none; margin: 0; padding: 0; }
.plainlist > li { padding: 8px 0; border-bottom: 1px solid var(--line); }
.plainlist > li:last-child { border-bottom: 0; }

/* A read-only block of text meant to be selected and copied. Monospace
   because every use of it so far is markdown or a command. */
.codebox {
  width: 100%; font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: .8125rem; line-height: 1.5;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 3px; padding: 10px;
  resize: vertical;
}

/* Support view banner. Same loudness as the pause bar and the same reason:
   while it is up it is the most important fact on the screen, because
   somebody is reading a customer's account. */
.banner {
  padding: 10px var(--gap); margin-bottom: var(--gap);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: .875rem; border-radius: 3px;
}
.banner-warning {
  background: rgba(224, 122, 24, .14);
  border: 1px solid rgba(224, 122, 24, .5);
}
.banner form { margin: 0; }

/* ------------------------------------------------- brand-following tints --
   Three decorative fills that were hard-coded `rgba(224,122,24,…)` — the
   default accent, frozen — so on a navy-branded page they painted orange.
   D-123 says the brand colour is for decorative accents, and a tint is one.

   `@supports` and not the two-declaration trick. A declaration containing
   `var()` parses fine everywhere, so a browser without `color-mix` accepts
   it and *discards* the literal above it, then fails at computed-value time
   and falls back to the property's initial value — `transparent` — rather
   than to the previous declaration. The first version of this did exactly
   that and the comment claimed otherwise. `@supports` asks the question the
   fallback was trying to ask, and the plain rules above stay in force for
   anything that answers no.

   `color-mix(… , transparent)` interpolates premultiplied, so the result is
   the accent at that alpha rather than a colour mixed with black.
   ------------------------------------------------------------------------ */
@supports (background: color-mix(in srgb, red 50%, transparent)) {
  .mainnav a.is-active { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .credbox { background: color-mix(in srgb, var(--accent) 6%, transparent); }
  .banner-warning {
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  }
}

/* The "open a support view" disclosure. Collapsed by default so the reason
   field is a deliberate step rather than something to tab past. */
.support-start > summary { cursor: pointer; display: inline-block; }
.support-start[open] > summary { margin-bottom: 8px; }
.support-start form { margin: 0; max-width: 320px; }
.support-start label { display: block; font-size: .75rem; color: var(--muted); margin-bottom: 4px; }
.support-start input { width: 100%; margin-bottom: 6px; }

@media (max-width: 720px) {
  .table-scroll table { min-width: 760px; }
}

/* --------------------------------------------------------- help centre -- */

.helpsearch { display: flex; gap: 8px; margin-bottom: var(--gap); flex-wrap: wrap; }
.helpsearch input { flex: 1; min-width: 200px; margin: 0; }

.helplist { list-style: none; margin: 0; padding: 0; }
.helplist > li { padding: 10px 0; border-bottom: 1px solid var(--line); }
.helplist > li:last-child { border-bottom: 0; }
.helplist a { font-weight: 600; display: block; }
.helplist .muted { font-size: .875rem; }

/* Readable measure. A help article at full desktop width is about 140
   characters a line, which is roughly twice what anybody reads comfortably. */
.helpbody { max-width: 68ch; }
.helpbody p { margin: 0 0 1em; line-height: 1.65; }
.helpbody h2 { margin: 1.5em 0 .5em; font-size: 1.0625rem; }
.helpbody ol.steps { margin: 0 0 1em 1.25em; padding: 0; }
.helpbody ol.steps > li { margin-bottom: .5em; line-height: 1.6; }
.helpbody .bullets { margin: 0 0 1em 1.25em; }
.helpbody .bullets > li { margin-bottom: .35em; line-height: 1.6; }

/* The thing in an article most worth not missing. A left bar rather than a
   colour alone, so it still reads as set apart in greyscale. */
.helpnote {
  border-left: 3px solid var(--accent);
  padding: 8px 0 8px 12px;
  background: var(--panel-2);
  margin: 0 0 1em;
}

/* ------------------------------------------------------------- billing -- */

/* Two plan cards side by side on a desktop, stacked on a phone. `auto-fit`
   with a minimum rather than a media query, because the number of plans is
   a product decision and this should not need editing when there are three. */
.plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--gap);
}
.plan-card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
  background: var(--panel-2);
}
/* The plan they are on. A border rather than a fill: the brand colour is a
   decorative accent here (D-123) and the words "Your plan" are what
   actually says it, for anybody who cannot tell the two borders apart. */
.plan-card.is-current { border-color: var(--accent); }
.plan-price { font-size: 1.75rem; font-weight: 600; margin: 4px 0; letter-spacing: -0.02em; }
.plan-card .bullets { margin: 10px 0; }
.plan-card .bullets li { padding: 3px 0; font-size: .875rem; }
.plan-card form { margin-top: 10px; }
