/* ============================================================================
   justsmtp.css — the app's own layout: the chrome every page sits in, and the
   dashboard-specific pieces (quota meter, stat grid, activity chart).

   The split from components.css is by reuse, not by page: anything a second
   product could take unchanged lives there, anything that only means something
   for justsmtp lives here.

   Depends on tokens.css, base.css and components.css.
   ========================================================================= */

/* --------------------------------------------------------------- topbar -- */

.topbar {
  display: flex;
  align-items: center;
  height: 56px;
  border-bottom: 1px solid var(--line);
}

.brand {
  font: 600 15px var(--mono);
  text-decoration: none;
}

.brand span {
  color: var(--ink-3);
  font-weight: 400;
}

/* The mark, decorative, and kept out of the accessible name deliberately.

   THE COMMENT HERE USED TO SAY generated content never reaches the
   accessibility tree. It does: `content` participates in name-from-content, so
   this link's accessible name was "▣ justsmtp.com" and a reader that speaks the
   glyph announced "white square containing black small square, justsmtp.com".
   The alt-text form — `content: <string> / <alt>` with an empty alt — is what
   actually suppresses it: Chrome 113, Safari 17.4, Firefox 129.

   The bare declaration stays underneath for older browsers, which ignore the
   whole `@supports` block. They over-announce the mark, which is what they do
   today; they do not lose it. Same fix and same reasoning as the disclosure
   caret in the narrow chrome below. */
.brand::before {
  content: '▣ ';
  color: var(--green);
}

@supports (content: 'x' / '') {
  .brand::before {
    content: '▣ ' / '';
  }
}

.topbar .who {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
}

.topbar .who .em {
  font: 400 12px var(--mono);
  color: var(--ink-3);
}

/* ----------------------------------------------------------------- tabs --
   The dashboard's five sections. `aria-current="page"` marks the active one
   for assistive technology; `.on` styles it. Both are set together — the
   underline is not the accessible signal. */

.tabs {
  display: flex;
  gap: 2px;
  height: 44px;
  border-bottom: 1px solid var(--line);
  align-items: stretch;
}

.tabs a {
  display: flex;
  align-items: center;
  padding: 0 14px;
  font-size: 13.5px;
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.tabs a:hover {
  color: var(--ink);
}

.tabs a.on {
  color: var(--ink);
  border-bottom-color: var(--green);
  font-weight: 500;
}

/* ------------------------------------------------------- narrow chrome --
   Below the breakpoint the tab row is replaced by the disclosure in
   page.html, whose summary names the page you are on.

   THE DEFECT THIS FIXES. At 390px the chrome made the document ~135px wider
   than the viewport, so every dashboard page scrolled sideways — and because
   the chrome is inside the scrolling document, the navigation travelled with
   the content. Two separate overflows, and both are answered here: the tab
   row (six items, one flex line, no wrap) and the topbar (brand, address and
   Sign out, all nowrap in a row that cannot shrink them).

   Four treatments were drawn and compared at
   docs/designs/explorations/2026-08-17-001-dashboard-chrome-390/. This is D.
   The rejected ones are worth knowing: scrolling the tab row leaves the tabs
   past the edge invisible, because a phone renders no scrollbar; wrapping it
   costs ~40px of height on every page, on the device with the least of it,
   and forced the address out to pay for it. */

.nav-menu {
  display: none;
}

/* The topbar's own overflow, and NOT inside the media query below.
   Scoping it to phone widths was the first version of this fix and it was
   wrong: the row is three nowrap items, so a long enough address overflows at
   any width it does not fit — a 60-character address plus the brand and the
   Sign out button needs ~640px, which a half-screen desktop window does not
   have. The rule is about the content, so it applies at every width.

   The address is ellipsized rather than dropped: it is the only place the
   dashboard says which account you are signed into, and a customer with two
   accounts has no other way to tell them apart. Nothing changes for an address
   that fits — ellipsis engages only under constraint.

   min-width: 0 is the part that does the work. A flex item's default minimum
   is auto, meaning its content, so without this the row simply keeps its old
   width and text-overflow never engages. */
.topbar .who {
  min-width: 0;
}

.topbar .who .em {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 650px, not the 525px the row actually needs (six tabs ≈ 477px of content
   box plus the wrap's 48px of padding). Sitting on the measured edge would
   mean the first longer word in any translation reintroduces the overflow
   silently, and the labels here are catalogue entries rather than fixed
   strings.

   Reads as "649px and below", which is what max-width means; the prose
   elsewhere says "below 650px" and the two differ only on a fractional
   viewport under zoom, where the tab row renders and fits anyway. */
@media (max-width: 649px) {
  .tabs {
    display: none;
  }

  .nav-menu {
    display: block;
    border-bottom: 1px solid var(--line);
  }

  /* Both spellings: `list-style` is how Firefox and modern Chrome drop the
     triangle, the pseudo-element is how Safari does. Neither alone removes
     it everywhere, and the marker left in place sits beside the caret below
     as a second, contradictory affordance. */
  .nav-menu summary {
    list-style: none;
    cursor: pointer;
    height: 44px;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13.5px;
    color: var(--ink);
  }

  .nav-menu summary::-webkit-details-marker {
    display: none;
  }

  /* GENERATED CONTENT DOES REACH THE ACCESSIBILITY TREE. An earlier version of
     this rule claimed the opposite and was wrong: content on ::after
     participates in the summary's name-from-content, so the disclosure button
     was announced as "Overview ▾" — and a reader that speaks the glyph says
     "black down-pointing small triangle", which is the double announcement the
     rule was supposed to avoid. <details> already exposes its expanded state.

     The alt-text form `content: '▾' / ''` is the fix, and the plain
     declaration is kept underneath it as the fallback: alt text is recent
     enough (Chrome 113, Safari 17.4, Firefox 129) that older browsers would
     drop the whole declaration and render no affordance at all. @supports
     keeps them showing a caret that is merely over-announced, rather than
     showing nothing. */
  .nav-menu summary::after {
    content: '▾';
    color: var(--ink-3);
  }

  .nav-menu details[open] summary::after {
    content: '▴';
  }

  @supports (content: 'x' / '') {
    .nav-menu summary::after {
      content: '▾' / '';
    }

    .nav-menu details[open] summary::after {
      content: '▴' / '';
    }
  }

  /* 44px, not the 36px this was. This is the phone navigation — every
     destination but one is reached through these six rows, and they are the
     smallest touch targets in the app. Matches the summary above, which was
     already 44px.

     14px and not 13: measured at 42px with 13px of padding, because the line
     box is ~16px rather than the 13.5px font size. The number in a comment
     like this is worth checking rather than deriving. */
  .nav-menu a {
    display: block;
    padding: 14px 0;
    font-size: 13.5px;
    color: var(--ink-2);
    text-decoration: none;
    border-top: 1px solid var(--line);
  }

  .nav-menu a.on {
    color: var(--ink);
    font-weight: 500;
  }
}

/* ----------------------------------------------------------------- page -- */

.page {
  padding: 36px 0 90px;
}

.page-head {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  margin-bottom: 26px;
}

.page-head h1 {
  font-size: 26px;
  letter-spacing: -.02em;
}

.page-head .as {
  font: 400 12px var(--mono);
  color: var(--ink-3);
  padding-bottom: 4px;
}

.page-head .r {
  margin-left: auto;
}

/* The head is one flex row with an action at its end, and on the domain page
   the heading is a domain: one unbroken token in a mono face, which cannot wrap
   and so pushes the "Re-check records" button off the right edge. Off the edge
   rather than into a scrollbar — the button is clipped and there is no way to
   reach it, so the page's primary action is gone at a phone width. Reported
   2026-08-04, seen at 420px.

   Wrapping the row is what keeps the button on the page; `overflow-wrap` on the
   heading is what stops a long name being wider than the screen on its own. */
@media (max-width: 560px) {
  .page-head {
    flex-wrap: wrap;
    align-items: flex-start;
  }

  .page-head h1 {
    overflow-wrap: anywhere;
    min-width: 0;
  }
}

/* ---------------------------------------------------------------- flash --
   Server-rendered result of the last POST. A live region so it is announced
   without stealing focus; role="alert" on the error variant, because a failed
   action is worth interrupting for and a successful one is not. */

.flash {
  padding: 12px 16px;
  border-radius: var(--r);
  border: 1px solid var(--line-2);
  font-size: 13.5px;
  margin-bottom: 20px;
}

.flash.ok {
  color: var(--green);
  border-color: oklch(from var(--green) l c h / .35);
  background: var(--green-soft);
}

.flash.err {
  color: var(--red);
  border-color: oklch(from var(--red) l c h / .35);
  background: var(--red-soft);
}

/* ------------------------------------------------------------- past due --
   Payment overdue (spec 013 R4). Outside <main> and above it, so it is the
   first thing under the navigation on every page rather than part of any one
   page's content.

   Amber and not red, at every rung including the last. Red is what this app
   uses for something that has already failed — a bounced message, a declined
   charge — and an overdue invoice is a thing the customer can still fix. The
   wording escalates through the rungs; the colour does not need to. */

.past-due {
  margin: 0 0 20px;
  padding: 12px 16px;
  border-radius: var(--r);
  border: 1px solid oklch(from var(--amber) l c h / .35);
  background: var(--amber-soft);
  color: var(--amber);
  font-size: 13.5px;
}

.past-due a {
  color: inherit;
  text-decoration: underline;
  white-space: nowrap;
}

/* ---------------------------------------------------------------- quota -- */

.quota {
  padding: 22px 24px;
}

/* The eyebrow: attempt-4's label for every card on the overview, and the reason
   the cards read without headings. Uppercase mono at 11px is small enough to
   sit above a 30px figure without competing with it, and the letter-spacing is
   what keeps it legible at that size rather than decorative. */
.eyebrow {
  font: 500 11px var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.quota .hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.quota .row1 {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.quota .n {
  font: 700 30px var(--mono);
  letter-spacing: -.02em;
}

.quota .of {
  font: 400 15px var(--mono);
  color: var(--ink-3);
}

.quota .plan {
  font: 400 12.5px var(--mono);
  color: var(--ink-2);
}

.quota .foot {
  display: flex;
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
  margin-top: 10px;
}

.quota .foot .r {
  margin-left: auto;
}

/* The bar is decorative: the same numbers are stated in text above it, so it
   carries aria-hidden and <progress> semantics are not needed.

   Width comes from a class, NOT an inline style. The CSP is `style-src 'self'`
   with no `unsafe-inline`, so a `style="width:62%"` attribute is dropped by the
   browser and the meter renders empty — silently, with nothing in the markup to
   explain it. Twenty-one classes in 5% steps cost about 700 bytes, are fully
   static and cacheable, and the rounding is invisible on a 200px bar. The exact
   figure is stated in text beside the meter regardless, which is what a
   customer actually reads. */
.meter {
  height: 8px;
  border-radius: 999px;
  background: var(--bg-2);
  margin-top: 16px;
  overflow: hidden;
}

.meter i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, oklch(from var(--green) calc(l - .09) c h), var(--green));
}

/* Over the plan allowance, the meter stops being reassuring. */
.meter.over i {
  background: linear-gradient(90deg, oklch(from var(--amber) calc(l - .09) c h), var(--amber));
}

/* Fill, in 5% steps. The server rounds to the nearest and emits one class. */
.meter i.w0 { width: 0; }
.meter i.w5 { width: 5%; }
.meter i.w10 { width: 10%; }
.meter i.w15 { width: 15%; }
.meter i.w20 { width: 20%; }
.meter i.w25 { width: 25%; }
.meter i.w30 { width: 30%; }
.meter i.w35 { width: 35%; }
.meter i.w40 { width: 40%; }
.meter i.w45 { width: 45%; }
.meter i.w50 { width: 50%; }
.meter i.w55 { width: 55%; }
.meter i.w60 { width: 60%; }
.meter i.w65 { width: 65%; }
.meter i.w70 { width: 70%; }
.meter i.w75 { width: 75%; }
.meter i.w80 { width: 80%; }
.meter i.w85 { width: 85%; }
.meter i.w90 { width: 90%; }
.meter i.w95 { width: 95%; }
.meter i.w100 { width: 100%; }

/* ------------------------------------------------------------ stat grid -- */

.grid {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 16px;
  margin-top: 16px;
}

.stats3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-top: 16px;
}

/* The admin overview has four figures where the customer's has three, and
   attempt-4 draws them as one row. Same tile, same gap, one more column. */
.stats3.four {
  grid-template-columns: repeat(4, 1fr);
}

.stat {
  padding: 18px 20px;
}

.stat .k {
  font: 500 11px var(--mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.stat .v {
  font: 700 26px var(--mono);
  letter-spacing: -.02em;
  margin-top: 8px;
}

/* The share reads on the sub line now, not beside the value. It keeps its
   colour there: a percentage is the only figure on this card that can be good
   or bad, and the count above it is neither. */
.stat .s .pct {
  color: var(--green);
}

.stat.bad .s .pct {
  color: var(--red);
}

/* ---------------------------------------------------------------- chart --
   Bars are decorative; the same series is available as a table on the usage
   page, and the container carries the summary in text. Nothing here is the
   only way to reach a number.

   Bar heights have the same constraint as the quota meter: `style="height:..."`
   is dropped under `style-src 'self'`. WP6 emits a step class per bar rather
   than an inline height — see the .meter block above for the reasoning. */

.chart {
  padding: 20px 24px 16px;
}

.chart .hd {
  display: flex;
  align-items: baseline;
  margin-bottom: 16px;
}

.chart .hd .k {
  margin-left: auto;
  font: 400 11px var(--mono);
  color: var(--ink-3);
}

/* The chart takes the whole row and the health strip sits under it. Grid rather
   than two siblings so the gap matches every other pair of cards on the page
   without a margin that has to be kept in step by hand. */
.chart-row {
  display: grid;
  gap: 16px;
  margin-top: 16px;
}

.health {
  padding: 16px 20px;
}

.bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 120px;
}

.bars i {
  flex: 1;
  background: var(--green-dim);
  border-radius: 2px 2px 0 0;
  min-height: 2px;
}

.bars i:hover,
.bars i.hot {
  background: var(--green);
}

/* The step classes WP2's note promised. Twentieths rather than the meter's
   twentieths-of-100: a bar is a share of the tallest bar, not of a quota, so
   the peak day is h20 whatever it sold. h0 is a day with nothing in it —
   min-height on .bars i keeps it visible as a floor rather than as a gap. */
.bars i.h0 { height: 0%; }
.bars i.h1 { height: 5%; }
.bars i.h2 { height: 10%; }
.bars i.h3 { height: 15%; }
.bars i.h4 { height: 20%; }
.bars i.h5 { height: 25%; }
.bars i.h6 { height: 30%; }
.bars i.h7 { height: 35%; }
.bars i.h8 { height: 40%; }
.bars i.h9 { height: 45%; }
.bars i.h10 { height: 50%; }
.bars i.h11 { height: 55%; }
.bars i.h12 { height: 60%; }
.bars i.h13 { height: 65%; }
.bars i.h14 { height: 70%; }
.bars i.h15 { height: 75%; }
.bars i.h16 { height: 80%; }
.bars i.h17 { height: 85%; }
.bars i.h18 { height: 90%; }
.bars i.h19 { height: 95%; }
.bars i.h20 { height: 100%; }

.stat .s {
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
  margin-top: 4px;
}

table.data td.r,
table.data th.r {
  text-align: right;
}

/* The subject is the one cell whose content has no bound. Clipped rather than
   wrapped: a two-line subject makes its row twice the height of the four around
   it, and a list whose rows are different heights is harder to scan than one
   whose longest subject is cut short. */
table.data td.subj {
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.recent {
  margin-top: 16px;
}

/* ------------------------------------------------------- first run (08) --
   The onboarding card a customer sees before they have anything. Scoped under
   .onb throughout: public.css already owns a .step, for the landing page's
   three-step explainer, and the two are different layouts. They never load
   together — public.css is only linked for public chrome — but a name that
   means two things is one stylesheet change away from being wrong. */

.onb {
  margin-top: 26px;
  padding: 30px 34px;
  position: relative;
  overflow: hidden;
}

.onb::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(420px 200px at 20% 0%, oklch(from var(--green) l c h / .06), transparent 70%);
}

.onb h2 {
  font-size: 20px;
  letter-spacing: -.015em;
  position: relative;
}

.onb .sub {
  color: var(--ink-2);
  font-size: 13.5px;
  margin-top: 6px;
  position: relative;
}

.onb .list {
  margin-top: 26px;
  position: relative;
}

.onb .step {
  display: grid;
  grid-template-columns: 40px 1fr auto;
  gap: 18px;
  align-items: start;
  padding: 20px 0;
  border-top: 1px dashed var(--line-2);
}

.onb .step .dot {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  display: flex;
  align-items: center;
  justify-content: center;
  font: 600 12px var(--mono);
  color: var(--ink-3);
  background: var(--bg);
}

.onb .step.done .dot {
  background: var(--green);
  border-color: var(--green);
  color: var(--green-ink);
}

.onb .step.now .dot {
  border-color: var(--green);
  color: var(--green);
}

.onb .step h3 {
  font-size: 15.5px;
}

.onb .step.done h3 {
  color: var(--ink-3);
  text-decoration: line-through;
  text-decoration-color: var(--line-2);
}

.onb .step p {
  font-size: 13px;
  color: var(--ink-2);
  margin-top: 4px;
  max-width: 56ch;
}

.onb .step p code {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
}

/* The step's heading and its state on one line, which is where attempt-4 puts
   them. The badge is at the far end rather than beside the heading so the four
   of them line up down the card and can be read as a column without reading the
   headings at all. */
.onb .step .line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.onb .step .act {
  padding-top: 2px;
}

.strip {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 16px;
  padding: 16px 22px;
}

.strip .v {
  margin-top: 6px;
  font-size: 15px;
}

/* 15px, not the 22px this was. The figure is a zero on a screen whose whole
   point is that nothing has been sent yet, and setting it at the size the
   populated overview uses for a real month's total gives it a weight it has
   not earned. */
.strip .n {
  font: 600 15px var(--mono);
}

.strip .t {
  font: 400 12px var(--mono);
  color: var(--ink-3);
}

.strip .r {
  margin-left: auto;
}

.stuck {
  text-align: center;
  font-size: 13.5px;
  color: var(--ink-3);
  margin-top: 24px;
}

.page-head.col {
  display: block;
}

@media (max-width: 820px) {
  .onb .step {
    grid-template-columns: 40px 1fr;
  }

  .onb .step .act {
    grid-column: 2;
  }
}

/* Three labels, not two: attempt-4 marks the middle of the window as well as
   its ends, which is what lets a bar be located by eye rather than by counting
   from the left. space-between rather than the margin-left:auto this had, since
   that only ever positioned a last child. */
.axis {
  display: flex;
  justify-content: space-between;
  font: 400 10px var(--mono);
  color: var(--ink-3);
  margin-top: 8px;
}

/* ----------------------------------------------------------- empty state --
   View 08 (first run) and view 14 (edge states). An empty list is a place the
   customer has arrived at deliberately, so it says what to do next rather
   than only that there is nothing here. */

.empty {
  padding: 56px 24px;
  text-align: center;
}

/* Monospace, because every glyph the views use is drawn from the protocol
   rather than from an icon set: "250 —" is a real SMTP reply, "AUTH ∅" and
   "MX ∅" name the record that is missing. A proportional face breaks the
   alignment those depend on to read as terminal output. Decoration only —
   the partial marks it aria-hidden. */
.empty .glyph {
  font: 400 22px var(--mono);
  color: var(--ink-3);
  margin-bottom: 14px;
}

.empty h2 {
  font-size: 18px;
  margin-bottom: 8px;
}

/* The link out of an empty page sits under the state, not inside it: the
   shared partial has to stay renderable where there is no page to go back
   to, so activity's "back to the first page" is a sibling. */
.empty-more {
  text-align: center;
  padding: 0 24px 32px;
  font-size: 13px;
}

.empty p {
  color: var(--ink-3);
  font-size: 13.5px;
  max-width: 46ch;
  margin: 0 auto 20px;
}

/* ---------------------------------------------------------------- banners --
   View 14's warning and failure treatments (spec 004 R13).

   The reply code on the left is the point of the whole component. Every other
   banner on the web opens with "something went wrong"; this one opens with
   `452 4.2.2` or `NXDOMAIN`, which is the exact string already sitting in the
   customer's own mail log — so the page they are reading and the log they are
   debugging from name the same event, and searching either one finds the
   other. It is nowrap for that reason: a wrapped reply code is not a reply
   code any more. */

.banner {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  border-radius: var(--r-lg);
  padding: 16px 18px;
  border: 1px solid;
  margin: 14px 0 18px;
}

.banner .code {
  font: 600 12px var(--mono);
  white-space: nowrap;
  /* nowrap alone is not enough — this is a flex item, and the message beside it
     is long enough that the browser shrinks the code instead of the prose.
     Screenshots showed "452 4.2.2" clipped to "452 4.2.", which is not a reply
     code any more and is exactly what a customer would search their log for. */
  flex-shrink: 0;
  padding-top: 1px;
}

.banner .msg {
  font-size: 13.5px;
}

/* The direct child only. The remedy list under it emphasises a token
   mid-sentence, and a `display: block` reaching that far turned every fix line
   into three stacked fragments — caught in a screenshot, invisible in the
   markup. */
.banner .msg > b {
  display: block;
  margin-bottom: 3px;
  font-size: 14px;
}

.banner .msg p {
  color: var(--ink-2);
  font-size: 13px;
}

.banner .act {
  margin-left: auto;
  flex-shrink: 0;
  padding-top: 2px;
}

/* Amber for a state the account is in and can leave, red for one that has
   already failed. A quota reached is the first: nothing is lost, the sender
   retries, and colouring it red would tell a customer to panic about mail
   that is going to arrive. */
.banner.warn {
  background: var(--amber-soft);
  border-color: oklch(from var(--amber) l c h / 0.4);
}

.banner.warn .code {
  color: var(--amber);
}

.banner.err {
  background: var(--red-soft);
  border-color: oklch(from var(--red) l c h / 0.4);
}

.banner.err .code {
  color: var(--red);
}

/* Narrow, the action stops being something pushed to the right and becomes the
   line under the message. Left where it was it takes a third of the width from
   a paragraph that already has none — screenshots at 420px showed the title
   broken across five lines beside a button reading in one. */
@media (max-width: 700px) {
  .banner {
    flex-wrap: wrap;
  }

  .banner .msg {
    flex: 1 1 100%;
    order: 2;
  }

  .banner .act {
    order: 3;
    margin-left: 0;
    padding-top: 6px;
  }
}

/* The remedy line under a failure banner. Monospace and loosely leaded
   because what it contains is something to be typed or pasted into a DNS
   panel, not read as prose. */
.fix {
  border-top: 1px dashed var(--line-2);
  margin-top: 14px;
  padding: 12px 4px 0;
  font: 400 12.5px/1.9 var(--mono);
  color: var(--ink-3);
}

.fix b {
  color: var(--ink-2);
  font-weight: 500;
}

/* ---------------------------------------------------------- error pages --
   Here rather than in public.css, because an error can be raised on a
   dashboard route as easily as a marketing one and public.css is not loaded
   there. Everything else the error page uses — `.reply`, `.btn` — is already
   in components.css for the same reason. */

.err-shell {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52vh;
  padding: 64px 24px;
  text-align: center;
  position: relative;
  overflow: hidden;
}

/* Decorative bloom, same device as the landing hero. */
.err-shell::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(460px 260px at 50% 34%, oklch(from var(--green) l c h / .05), transparent 70%);
}

.err-shell > div {
  position: relative;
  max-width: 520px;
}

.err-shell h1 {
  font-size: 26px;
  letter-spacing: -.015em;
  margin-bottom: 10px;
}

.err-shell p {
  color: var(--ink-2);
  font-size: 14.5px;
  margin-bottom: 26px;
}

/* The 500's "your mail is still sending" line, given a panel of its own. It
   answers the question the reader arrived with, so it cannot be a third
   paragraph of grey body text below the one that caused it. */
.err-shell .reassure {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-1);
  padding: 14px 18px;
  color: var(--ink);
  text-align: left;
}

.err-shell .ref {
  font: 400 12px/1.6 var(--mono);
  color: var(--ink-3);
  margin-top: -12px;
}

.err-shell .ref code {
  font-family: var(--mono);
  color: var(--ink-2);
}

.err-shell .actions {
  display: flex;
  gap: 10px;
  justify-content: center;
}

/* The outlined status code. `-webkit-text-stroke` is what draws it, and it is
   prefixed-only — so the transparent fill is applied only where the stroke is
   supported. Without the guard an unsupporting engine renders an invisible
   130px heading, which is the worst of both. */
.code-big {
  font: 700 clamp(80px, 14vw, 130px)/1 var(--mono);
  letter-spacing: -.05em;
  margin: 22px 0 6px;
  color: var(--ink-3);
}

@supports (-webkit-text-stroke: 1px black) {
  .code-big {
    color: transparent;
    -webkit-text-stroke: 1.5px var(--ink-3);
  }
}

.transcript {
  margin: 26px auto 0;
  max-width: 380px;
  text-align: left;
  font: 400 11.5px/1.8 var(--mono);
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 14px 18px;
  background: var(--bg-sunk);
}

.transcript .c { color: var(--green); }
.transcript .e { color: var(--red); }
.transcript .w { color: var(--amber); }

/* --------------------------------------------------------------- narrow --
   One breakpoint. The dashboard's two multi-column grids collapse; everything
   else is already single-column or wraps on its own. */

@media (max-width: 900px) {
  .grid,
  .stats3,
  .stats3.four {
    grid-template-columns: 1fr;
  }
}

/* ------------------------------------------------------------------ auth --
   The sign-in surface (view 04). Two states, one card treatment: the form and
   the "check your email" confirmation. Both live here rather than in
   components.css because the centred single-card shell is a page shape, not a
   reusable component — nothing else in the product is one card in the middle of
   an empty viewport.

   .card, .input, .field-label and .field-error are already in components.css
   and are used unchanged; what follows is only the shell and the two pieces the
   prototype adds. */

.auth-shell {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  min-height: 56vh;
  position: relative;
  overflow: hidden;
}

/* Same decorative bloom as the landing hero and the error page, so the three
   full-viewport states read as one family. */
.auth-shell::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(480px 280px at 50% 30%, oklch(from var(--green) l c h / .06), transparent 70%);
}

.auth-shell .stack {
  position: relative;
  display: grid;
  gap: 28px;
  width: min(420px, 100%);
}

.auth-shell .card {
  padding: 32px;
  box-shadow: 0 24px 60px var(--shadow);
}

.auth-shell .card .reply-line {
  font: 500 11.5px var(--mono);
  letter-spacing: .04em;
  color: var(--green);
  margin-bottom: 12px;
}

.auth-shell .card h1 {
  font-size: 22px;
  letter-spacing: -.015em;
  margin-bottom: 6px;
}

.auth-shell .card .sub {
  color: var(--ink-3);
  font-size: 13.5px;
  margin-bottom: 24px;
}

.auth-shell .actions {
  margin-top: 16px;
}

.auth-shell .btn.primary {
  width: 100%;
  height: 42px;
  font-size: 14px;
}

.auth-shell .btn.ghost {
  width: 100%;
}

.fine {
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
  text-align: center;
  margin-top: 20px;
}

/* check-your-email state */

.check {
  text-align: center;
}

.check .icon {
  width: 52px;
  height: 52px;
  margin: 6px auto 18px;
  border: 1px solid var(--line-2);
  border-radius: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--green);
  background: var(--green-soft);
}

.check p {
  color: var(--ink-2);
  font-size: 14px;
  margin-bottom: 22px;
}

.check b {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 13px;
}

/* ---------------------------------------------------------------------------
   View 11 — SMTP credentials (spec 004 T22).

   The generate form, the one-time reveal, and the list. Nothing here is a
   proportion, so nothing here needs the quantised-class treatment the chart
   does — but the same rule applies for the same reason: no style attribute
   anywhere, because style-src is 'self'.
   ------------------------------------------------------------------------- */

.gen {
  display: flex;
  align-items: flex-end;
  gap: 14px;
  padding: 18px 20px;
  flex-wrap: wrap;
}

.gen .f {
  flex: 1 1 260px;
  min-width: 0;
}

.gen .field-label .opt {
  text-transform: none;
  letter-spacing: 0;
  color: var(--ink-3);
}

/* The reveal is the one card on the site that is meant to stop the eye. */
.reveal {
  border-color: var(--amber);
}

.warnbar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 9px 20px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--amber);
  background: var(--amber-soft);
  border-bottom: 1px solid var(--line-2);
}

/* What happened reads as ink; what it costs keeps the amber. Both in the bar
   would make the amber mean "this bar" rather than "this is the only time". */
.warnbar .what {
  color: var(--ink);
  font-weight: 500;
}

.reveal .kv {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 8px 16px;
  align-items: baseline;
  margin-bottom: 16px;
}

.reveal .kv .k {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-3);
}

/* The value wraps rather than scrolling: a password cut off at the edge of a
   narrow window is one a customer copies incompletely and cannot check. */
.reveal .kv .v {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink);
  overflow-wrap: anywhere;
  user-select: all;
}

.reveal .actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
  flex-wrap: wrap;
}

/* The revoke confirmation, rendered in place under the pair it is about
   (spec 004 T23). */
.btn.sm {
  padding: 4px 10px;
  font-size: 12px;
}

tr.confirm td {
  background: var(--red-soft);
  border-top: 0;
}

tr.confirm .msg {
  color: var(--ink);
  font-size: 13px;
  margin-right: 12px;
}

tr.confirm .inline {
  display: inline-flex;
  margin-right: 8px;
}

/* ---------------------------------------------------------------------------
   View 09 — domains (spec 004 T24).

   The add form, the hint under it, and the list. Same rule as everywhere else
   on this sheet: no style attribute, because style-src is 'self'. The
   prototype's `style="margin-top:16px"` on the list card is `.card.mt` here for
   exactly that reason.
   ------------------------------------------------------------------------- */

.add {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  padding: 20px 22px;
  flex-wrap: wrap;
}

.add .f {
  flex: 1 1 260px;
  min-width: 0;
}

/* Sits inside the same card as the form, under it — which is why the padding
   repeats the form's horizontal inset rather than being a card body. */
.hint {
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
  padding: 0 22px 16px;
}

.card.mt {
  margin-top: 16px;
}

/* The domain itself is the row's subject, so it carries the weight. Mono,
   because a domain is something the customer will compare character by
   character against what is in their DNS. */
.dom {
  font: 600 13.5px var(--mono);
}

/* ---------------------------------------------------- view 10 — domain --
   The detail page (spec 004 T28, T29). Records to publish, and the removal
   confirmation under them.

   No `.copy` button, unlike the prototype. Copy-to-clipboard needs script, and
   R5 says every one of these pages works without it — a button that silently
   does nothing for a customer with scripting off is worse than no button, and
   the values are selectable text either way.
   ------------------------------------------------------------------------- */

.crumb {
  font: 400 12px var(--mono);
  color: var(--ink-3);
  margin-bottom: 14px;
}

.crumb a:hover {
  color: var(--green);
}

/* Under the head rather than inside it: the head is a flex row and this is a
   full-width line beneath the whole of it. */
.meta {
  font: 400 12px var(--mono);
  color: var(--ink-3);
  margin: -18px 0 0;
}

/* Four columns in the prototype; three here, because there is no copy button
   and no per-record badge — see the template for why the badge is absent. */
.rec {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 16px;
  align-items: center;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}

.rec:last-child {
  border-bottom: 0;
}

.rec .kind {
  font: 600 11px var(--mono);
  letter-spacing: .1em;
  color: var(--ink-2);
}

.rec .kind small {
  display: block;
  font-weight: 400;
  color: var(--ink-3);
  letter-spacing: 0;
  margin-top: 3px;
}

/* overflow-wrap, not a scrollbar: a DKIM host is long, and a record the
   customer cannot see all of is a record they will mistype. */
.rec .val {
  font: 400 12px/1.7 var(--mono);
  color: var(--ink-2);
  overflow-wrap: anywhere;
}

.rec .val b {
  color: var(--ink);
  font-weight: 500;
}

.rec .val .arrow {
  color: var(--ink-3);
}

.note {
  padding: 14px 18px;
  font: 400 12px var(--mono);
  color: var(--ink-3);
  border-top: 1px dashed var(--line-2);
  margin: 0;
}

.danger {
  border-color: color-mix(in oklch, var(--red) 40%, transparent);
}

.danger .hd,
.danger .eyebrow {
  color: var(--red);
}

.danger .bd {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 16px 18px;
  flex-wrap: wrap;
}

/* The prose is the label of the control beside it, so it is what takes the
   room and the button keeps its own width. Below 320px + a button the two
   stack, which is the only arrangement left. */
.danger .bd .what {
  flex: 1 1 320px;
}

/* Named at the weight of a heading without being one: the card already has its
   eyebrow, and a second <h> here would put "Delete this account" into the
   document outline as a sibling of "Settings". */
.danger .what .t {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 4px;
}

.danger p {
  font-size: 13px;
  color: var(--ink-2);
  margin: 0;
}

/* -------------------------------------------------- view 12 — activity --
   The filter bar is a GET form, so it is laid out as a row of labelled
   controls rather than as a form column: the labels sit above their control at
   the same size as a table header, because they name a column of the table
   below as often as they name a field. */
/* The bar the filter form sits in, outside the card the rows are in. attempt-4
   separates the two, and the separation is the point: inside the card, a row of
   selects above a table reads as a header the table has grown rather than as a
   control over what the table contains. */
.filterbar {
  margin-bottom: 16px;
}

/* Scoped to .filterbar, not applied to .filters itself. Four other screens use
   the same form inside their card and were not part of this task; widening the
   selector would restyle all of them from a change nobody reviewed against
   their prototypes. */
.filterbar .filters {
  padding: 0 2px 14px;
  border-bottom: 0;
}

.filters {
  display: flex;
  gap: 10px;
  padding: 14px 16px;
  align-items: flex-end;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
}

/* Not a button. Apply is the action and this is the way out of one; giving them
   the same weight makes a customer think twice about which they wanted. */
.filters .reset {
  font: 400 12.5px var(--mono);
  color: var(--ink-3);
  padding-bottom: 8px;
}

.filters .reset:hover {
  color: var(--ink-2);
}

.filters .f label {
  display: block;
  font: 500 10px var(--mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 5px;
}

/* The date inputs share .input with every other field on the site, which is
   38px tall; in this row they have to match the selects and the button. */
.filters .input {
  width: auto;
  height: 32px;
  padding: 0 10px;
  font-size: 12.5px;
}

/* Pushed to the far end rather than following the last field, so Apply stays
   in the same place as fields are added — and it wraps to its own line before
   it crowds them. The pair moves as one: the margin was on Apply alone, and on
   a six-field bar that left the reset link stranded on a line of its own under
   the first control. */
.filters .ends {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
}

/* A bar beside the card, not a row inside it, which is where attempt-4 puts it.
   The count and the two controls are about the list rather than in it, and as
   the card's last row the count read as a total the table had produced. */
.pager {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 2px 0;
  font: 400 12px var(--mono);
  color: var(--ink-3);
  flex-wrap: wrap;
}

/* The end of the list, drawn rather than dropped. Dim enough to read as
   unavailable at a glance and still a shape, so the live control beside it does
   not move when the page changes. */
.pager .btn.off {
  color: var(--line-2);
  border-color: var(--line);
  cursor: default;
}

.pager .btns {
  margin-left: auto;
  display: flex;
  gap: 8px;
}

/* --------------------------------------------- view 12 — message detail --
   One message, its envelope and its delivery timeline. Capped at 820px rather
   than filling the column: every value here is a single short line, and a grid
   stretched to a wide monitor puts the key and its value a hand's width apart. */
.detail {
  max-width: 820px;
}

/* Flex, unlike the plain `.card .hd` this overrides: the badge, the subject
   and the identifier are three different kinds of thing on one line. */
.detail .hd {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}

/* The subject over its identifier, and the badge pushed to the far end by this
   block rather than by a margin on the badge itself — the badge is shared with
   every table on the site and must not carry one screen's layout. */
.detail .hd .who {
  margin-right: auto;
  min-width: 0;
}

.detail .hd .subj {
  display: block;
  font-weight: 600;
  font-size: 15px;
}

.detail .hd .id {
  display: block;
  font: 400 11px var(--mono);
  color: var(--ink-3);
  margin-top: 3px;
}

/* The eyebrow over the envelope grid, which attempt-4 labels. Padded to line up
   with the grid's own cells rather than with the card edge. */
.eyebrow.sec {
  display: block;
  padding: 14px 20px 0;
}

/* Two columns, and the count of cells has to stay even for the dashed rule
   between them to land right — `nth-child(2n)` is what drops the trailing
   border, so an odd cell leaves a rule pointing at nothing. */
.env {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0;
  border-bottom: 1px solid var(--line);
}

.env > div {
  padding: 12px 20px;
  border-right: 1px dashed var(--line-2);
}

.env > div:nth-child(2n) {
  border-right: 0;
}

.env .k {
  font: 500 10px var(--mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* overflow-wrap, because an envelope address or a provider id is one
   unbreakable token and will otherwise push the grid wider than its cell. */
.env .v {
  font: 400 12.5px var(--mono);
  margin-top: 4px;
  overflow-wrap: anywhere;
}

.nobody {
  padding: 10px 20px;
  border-bottom: 1px solid var(--line);
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
}

/* ------------------------------------------------------------- timeline --
   A three-column grid — time, node, what happened — rather than a list with
   markers, because the times have to right-align into a column for two events
   a second apart to be readable as such. The connecting line is a ::before on
   every event but the last, positioned at the node's centre (150px column +
   14px gap / 2 - 1). */
.tl {
  padding: 20px 20px 8px;
}

.tl .ev {
  display: grid;
  grid-template-columns: 150px 16px 1fr;
  gap: 0 14px;
  position: relative;
  padding-bottom: 22px;
}

.tl .ev .t {
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
  text-align: right;
  padding-top: 1px;
}

.tl .ev .node {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 2px solid var(--line-2);
  background: var(--bg-1);
  margin-top: 2px;
  position: relative;
  z-index: 1;
}

/* The node's colour reinforces the event's own label and never carries the
   meaning alone — the same rule the badges are built on. An event class this
   sheet has no rule for keeps the neutral node, which is deliberate: an
   unrecognised provider event is not a failure. */
.tl .ev.ok .node {
  border-color: var(--green);
  background: var(--green);
}

.tl .ev.warn .node {
  border-color: var(--amber);
  background: var(--amber);
}

.tl .ev.err .node {
  border-color: var(--red);
  background: var(--red);
}

.tl .ev:not(:last-child)::before {
  content: '';
  position: absolute;
  left: 157px;
  top: 14px;
  bottom: -2px;
  width: 1px;
  border-left: 1px dashed var(--line-2);
}

.tl .ev .what b {
  font-size: 13.5px;
  font-weight: 600;
}

/* The upstream's own words, boxed so they read as quoted rather than as ours.
   max-width holds a long diagnostic to a readable measure. */
.tl .ev .what .resp {
  display: block;
  font: 400 11.5px/1.7 var(--mono);
  color: var(--ink-3);
  margin-top: 4px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 8px 12px;
  max-width: 520px;
}

/* An alpha variant of --red, written out for the reason the error badge's
   border is: tokens.css defines the colour, and a one-off transparency of it
   is not a new colour. */
.tl .ev.err .what .resp {
  color: var(--red);
  border-color: oklch(from var(--red) l c h / .3);
}

/* One column below the fold of a phone, and the timeline's time column
   narrowed with its connecting line moved to match (90 + 14/2 - 1). */
@media (max-width: 760px) {
  .env {
    grid-template-columns: 1fr;
  }

  .env > div {
    border-right: 0;
  }

  .tl .ev {
    grid-template-columns: 90px 16px 1fr;
  }

  .tl .ev:not(:last-child)::before {
    left: 97px;
  }
}

/* The subject cell of the activity list, which links to the message.
   Not coloured green at rest: every row would be, and a table where every cell
   is an accent has no accent. But not undecorated either — every row being a
   link is exactly the case where nothing else marks one, and a link a customer
   has to hover to discover is a link most of them never find. So the rule stays
   and only its colour drops back, which leaves the affordance visible without
   depending on hue to carry it. */
table.data td a.subj {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--line-2);
  text-underline-offset: 3px;
}

table.data td a.subj:hover {
  color: var(--green);
  text-decoration-color: var(--green);
}

/* -------------------------------------------------- view 13 — settings --
   Two shapes, and the rest is already in the sheet. `.rowset` is a labelled
   key/value list with a control on the right; `.plan` is the plan strip above
   the same `.meter` the overview draws. `.note`, `.danger` and every `.btn`
   variant come from view 10 and components.css unchanged. */

.rowset > div {
  display: grid;
  grid-template-columns: 180px 1fr auto;
  gap: 18px;
  align-items: center;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line);
}

.rowset > div:last-child {
  border-bottom: 0;
}

.rowset .k {
  font: 500 11px var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.rowset .v {
  font-size: 14px;
}

/* overflow-wrap, not a scrollbar, for the same reason as the DNS records
   above: an email address the customer cannot see all of is one they cannot
   check. */
.rowset .v.mono {
  font-family: var(--mono);
  font-size: 13px;
  overflow-wrap: anywhere;
}

.rowset .v .sub {
  display: block;
  font: 400 12px var(--mono);
  color: var(--ink-3);
  margin-top: 3px;
}

.plan {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 18px 18px 0;
  flex-wrap: wrap;
}

.plan .name {
  font: 600 12px var(--mono);
  letter-spacing: .14em;
  text-transform: uppercase;
}

.plan .price {
  font: 700 26px var(--mono);
  letter-spacing: -.02em;
}

.plan .price small {
  font-size: 12px;
  font-weight: 400;
  color: var(--ink-3);
}

.plan .use {
  font: 400 12px var(--mono);
  color: var(--ink-3);
}

.plan .act {
  margin-left: auto;
  display: flex;
  gap: 10px;
}

/* The shared .meter carries a top margin for the overview's quota card, where
   it sits under text. Here it sits under the plan strip and needs the card's
   own side padding instead. */
.plan-card .meter {
  height: 6px;
  margin: 16px 18px 18px;
}

/* A control that is busy for a second and a control that will never be a
   control both sit at opacity .5, and only one of them is worth waiting for.
   Screenshots of this page showed "Change plan" reading as clickable next to
   the link beside it, which is the exact confusion R26 exists to prevent — so
   these borrow the dashed rule the note under this card already uses to mean
   "not built yet". */
.rowset .btn[aria-disabled='true'],
.plan .btn[aria-disabled='true'],
.danger .btn[aria-disabled='true'] {
  border-style: dashed;
  background: transparent;
}

@media (max-width: 700px) {
  .rowset > div {
    grid-template-columns: 1fr;
    gap: 6px;
  }

  /* The control drops under its row rather than stretching across it: a
     full-width "Change at epass" reads as the page's primary action, which is
     the opposite of what a disabled control should look like. */
  .rowset > div > .btn {
    justify-self: start;
  }

  .plan .act {
    margin-left: 0;
  }
}

/* ------------------------------------------ view 16 — inbound message detail --
   Sections down one card, in the order the questions get asked: what was this,
   what did we make of it, did it really come from them, what did it say, what
   happened next, and can I still have the original. Everything here layers over
   the view 12 detail card above rather than replacing it — the header, envelope
   grid and timeline are shared, because they are the same two facts about two
   directions of mail. */
.sec {
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}

/* The card's own border closes the last section; a second rule under it reads
   as a section that failed to render. */
.sec.last {
  border-bottom: 0;
}

.sec h3 {
  font: 500 10px var(--mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 11px;
}

/* -------------------------------------------------------- spam verdict --
   A gauge when there is a score, words when there is not, and the two branches
   share no markup at all. That is deliberate: the failure this view exists to
   prevent is an unscored message rendering as 0.0, and a shared numeric slot
   with an empty value is exactly how that happens. */
.verdict {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.verdict .score {
  font: 600 22px var(--mono);
  color: var(--ink);
}

.verdict .score.flag {
  color: var(--amber);
}

/* Italic and in the muted ink, so it reads as prose rather than as a value that
   failed to load. */
.verdict .na {
  font: 400 14px var(--sans);
  font-style: italic;
  color: var(--ink-3);
}

.verdict .of {
  font: 400 12px var(--mono);
  color: var(--ink-3);
}

.na-why {
  font-size: 12.5px;
  color: var(--ink-3);
  margin-top: 9px;
  max-width: 64ch;
}

.na-why b {
  color: var(--ink-2);
  font-weight: 500;
}

/* Named .gauge rather than .bar: components.css already gives .bar to the code
   sample's chrome, and .bars to the overview sparkline. */
.gauge {
  flex: 1;
  min-width: 180px;
  height: 6px;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--line);
  position: relative;
  overflow: hidden;
}

.gauge i {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  background: var(--green);
}

.gauge i.flag {
  background: var(--amber);
}

/* The flag threshold, as a hairline down the track. Overflow is hidden on the
   parent, so it is inset rather than overhanging like the prototype's. */
.gauge u {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--line-2);
}

/* Widths as classes, in fives. The CSP carries no unsafe-inline for styles, so
   an inline width attribute is dropped by the browser and every gauge renders
   empty — the same reason .bars enumerates its heights. */
.gauge i.p0 { width: 0%; }
.gauge i.p5 { width: 5%; }
.gauge i.p10 { width: 10%; }
.gauge i.p15 { width: 15%; }
.gauge i.p20 { width: 20%; }
.gauge i.p25 { width: 25%; }
.gauge i.p30 { width: 30%; }
.gauge i.p35 { width: 35%; }
.gauge i.p40 { width: 40%; }
.gauge i.p45 { width: 45%; }
.gauge i.p50 { width: 50%; }
.gauge i.p55 { width: 55%; }
.gauge i.p60 { width: 60%; }
.gauge i.p65 { width: 65%; }
.gauge i.p70 { width: 70%; }
.gauge i.p75 { width: 75%; }
.gauge i.p80 { width: 80%; }
.gauge i.p85 { width: 85%; }
.gauge i.p90 { width: 90%; }
.gauge i.p95 { width: 95%; }
.gauge i.p100 { width: 100%; }

.gauge u.m0 { left: 0%; }
.gauge u.m5 { left: 5%; }
.gauge u.m10 { left: 10%; }
.gauge u.m15 { left: 15%; }
.gauge u.m20 { left: 20%; }
.gauge u.m25 { left: 25%; }
.gauge u.m30 { left: 30%; }
.gauge u.m35 { left: 35%; }
.gauge u.m40 { left: 40%; }
.gauge u.m45 { left: 45%; }
.gauge u.m50 { left: 50%; }
.gauge u.m55 { left: 55%; }
.gauge u.m60 { left: 60%; }
.gauge u.m65 { left: 65%; }
.gauge u.m70 { left: 70%; }
.gauge u.m75 { left: 75%; }
.gauge u.m80 { left: 80%; }
.gauge u.m85 { left: 85%; }
.gauge u.m90 { left: 90%; }
.gauge u.m95 { left: 95%; }
.gauge u.m100 { left: 100%; }

/* ---------------------------------------------------- authentication --
   Four states, four colours, and `unknown` gets its own rather than sharing
   `none`'s. One is a fact about us — we did not check — and the other is a
   claim about the sender, and folding them together answers "did this really
   come from them" with something that is not an answer. */
.auth {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.auth .a {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font: 400 12px var(--mono);
  padding: 6px 11px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg);
}

.auth .a b {
  font-weight: 600;
}

.auth .a.pass b { color: var(--green); }
.auth .a.fail b { color: var(--red); }
.auth .a.unknown b { color: var(--amber); }
.auth .a.none b { color: var(--ink-3); }

/* --------------------------------------------------------- headers --
   Scrolls rather than growing: a References chain runs to dozens of lines, and
   a card whose height is set by attacker-supplied text is a card that pushes
   the delivery timeline off the screen. */
.hdrs {
  font: 400 12px/1.75 var(--mono);
  color: var(--ink-2);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 12px 14px;
  max-height: 220px;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.hdrs .n {
  color: var(--ink-3);
}

/* ----------------------------------------------------- attachments --
   Metadata only. The bytes are never inlined and never previewed: the declared
   type is a stranger's assertion, and the one place it is allowed to matter is
   as text in the `ty` cell. */
.atts {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.att {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg);
}

.att .nm {
  font: 400 12.5px var(--mono);
  overflow-wrap: anywhere;
}

.att .ty {
  font: 400 11px var(--mono);
  color: var(--ink-3);
}

.att .sz {
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
  margin-left: auto;
}

/* ------------------------------------------ caused by your own setting --
   Amber rather than red. Nothing went wrong: the system did exactly what the
   customer configured, and colouring it as a fault would send them looking for
   a bug instead of for the switch. */
.cause {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  padding: 13px 16px;
  border: 1px solid oklch(from var(--amber) l c h / .32);
  background: var(--amber-soft);
  border-radius: var(--r);
  font-size: 13px;
}

.cause .m {
  color: var(--amber);
  font: 600 13px var(--mono);
  line-height: 1.3;
}

.cause a {
  color: var(--green);
}

/* --------------------------------------------------------- retention --
   Two states and they do not share a control. Expired offers nothing at all
   rather than a greyed button: a control that fails when pressed is worse than
   an absent one, because it costs a click to learn the same thing. */
.ret {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--ink-2);
}

.ret .clock {
  font: 400 12px var(--mono);
  color: var(--ink-3);
}

.expired {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  padding: 13px 16px;
  border: 1px dashed var(--line-2);
  background: var(--bg);
  border-radius: var(--r);
  font-size: 13px;
  color: var(--ink-3);
}

.expired b {
  color: var(--ink-2);
  font-weight: 500;
}

/* ------------------------------------------------ view 17 — refused mail --
   A log of events, not a table of messages. The whole visual argument of this
   view is that a refusal is not a message with fields missing: there is no
   subject, no size and nothing to open, so it is drawn as a stack of entries
   rather than as rows under the same headings as the list next door.

   `.reply` is components.css's pill, unchanged — the colour already follows the
   reply class, which is the one thing a sender acted on. */

.subnav {
  display: flex;
  gap: 16px;
  margin-top: 18px;
  /* The filter bar under it used to be a card, which brought its own gap. */
  margin-bottom: 18px;
  font: 400 13px var(--sans);
  color: var(--ink-3);
}

.subnav a {
  color: var(--ink-3);
  text-decoration: none;
  padding-bottom: 4px;
  border-bottom: 1px solid transparent;
}

.subnav a:hover {
  color: var(--ink);
  border-bottom-color: var(--line-2);
}

.log {
  padding: 4px 0;
}

.log .ev {
  display: grid;
  grid-template-columns: 132px 1fr;
  gap: 0 18px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line);
}

.log .ev:last-child {
  border-bottom: 0;
}

.log .ev .when {
  font: 400 12px var(--mono);
  color: var(--ink-3);
  padding-top: 3px;
}

/* Where in the conversation we said no. Deliberately quiet: it qualifies the
   reply beside it rather than competing with it. */
.log .stage {
  display: inline-flex;
  align-items: center;
  font: 500 10px var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  color: var(--ink-3);
  margin-left: 10px;
}

/* The reply the sending server was given, verbatim (spec 007 R5).

   `white-space: pre-wrap` and `overflow-wrap: anywhere` rather than the `nowrap`
   + ellipsis a table cell would take: a customer's use for this line is to paste
   it to whoever is trying to reach them, so it wraps to as many lines as it
   needs and never renders a truncation they could paste by mistake. There is
   deliberately no `max-height` for the same reason.

   Ordinary text selection, deliberately. `user-select: all` was tried and
   removed: it makes the reply one atomic unit, so a customer cannot take the
   part of a long reply they wanted, and on touch this is the tallest element in
   every row — a scroll gesture starting on it would select rather than scroll.
   Selecting the text is the substitute for the copy button R14 rules out, and
   plain selection is the form of it that works everywhere. */
.log .ev .reply-text {
  margin: 11px 0 0;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-2);
  font: 400 12px/1.55 var(--mono);
  color: var(--ink-2);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A refusal from before the column existed. Body font, italic and quieter, on
   the same rule as `.env .none`: this is our sentence about the record, not a
   value out of it — and it must not be mistaken for a reply that read this way. */
.log .ev .reply-text.none {
  font: italic 400 12.5px/1.5 var(--sans);
  color: var(--ink-3);
  background: transparent;
  border-style: dashed;
}

/* Overrides the message detail's `.env`, which is an unscoped two-column grid
   of labelled cells. Here the envelope is a run of short facts, several of
   which may be absent, so it wraps rather than reserving a cell for each. */
.log .ev .env {
  display: flex;
  flex-wrap: wrap;
  gap: 0 26px;
  margin-top: 11px;
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
  padding: 0;
  border: 0;
}

.log .ev .env b {
  color: var(--ink-2);
  font-weight: 400;
}

/* A field the conversation never reached. In the body font and italic, because
   it is our words about the record rather than a value out of it. */
.log .ev .env .none {
  font-family: var(--sans);
  font-style: italic;
}

.log .ev .why {
  margin-top: 11px;
  font-size: 12.5px;
  color: var(--amber);
  display: flex;
  gap: 8px;
  align-items: baseline;
}

.log .ev .why a {
  color: var(--green);
}

@media (max-width: 760px) {
  .log .ev {
    grid-template-columns: 1fr;
    gap: 8px;
  }
}

/* ------------------------------------------------- view 18 — inbound volume --
   A month with no mail is a row reading zero, and the styling has to make that
   read as a fact rather than as a rendering fault — hence a visible empty bar
   and a worded cell where the average would be, not two blanks.

   Bar widths are classes for the reason recorded on `.gauge` above: the CSP
   carries no unsafe-inline for styles, so an inline width is dropped by the
   browser and every bar renders empty. */

.totals {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--line);
  border-bottom: 1px solid var(--line);
}

.totals > div {
  background: var(--bg-1);
  padding: 14px 16px;
}

.totals .k {
  font: 500 10px var(--mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.totals .v {
  font: 400 20px var(--mono);
  margin-top: 6px;
  letter-spacing: -0.01em;
}

.totals .v small {
  font-size: 12px;
  color: var(--ink-3);
  margin-left: 4px;
}

table.data.vol td.mon {
  font: 400 12.5px var(--mono);
  color: var(--ink-2);
}

table.data.vol td.bar-c {
  width: 34%;
  padding-right: 24px;
}

table.data.vol td.bar-c i {
  display: block;
  height: 8px;
  border-radius: 999px;
  background: var(--green);
  min-width: 2px;
}

/* A zero month's bar is drawn rather than omitted: an absent element leaves the
   cell empty, which is the shape a missing row would have. */
table.data.vol tr.zero td.bar-c i {
  background: var(--line-2);
  width: 100%;
  height: 1px;
  border-radius: 0;
}

table.data.vol tr.zero td.mon,
table.data.vol tr.zero td.num {
  color: var(--ink-3);
}

table.data.vol td.zlabel {
  font-size: 12px;
  font-style: italic;
  color: var(--ink-3);
}

.foot {
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  font: 400 12px var(--mono);
  color: var(--ink-3);
}

table.data.vol td.bar-c i.w0 { width: 0%; }
table.data.vol td.bar-c i.w5 { width: 5%; }
table.data.vol td.bar-c i.w10 { width: 10%; }
table.data.vol td.bar-c i.w15 { width: 15%; }
table.data.vol td.bar-c i.w20 { width: 20%; }
table.data.vol td.bar-c i.w25 { width: 25%; }
table.data.vol td.bar-c i.w30 { width: 30%; }
table.data.vol td.bar-c i.w35 { width: 35%; }
table.data.vol td.bar-c i.w40 { width: 40%; }
table.data.vol td.bar-c i.w45 { width: 45%; }
table.data.vol td.bar-c i.w50 { width: 50%; }
table.data.vol td.bar-c i.w55 { width: 55%; }
table.data.vol td.bar-c i.w60 { width: 60%; }
table.data.vol td.bar-c i.w65 { width: 65%; }
table.data.vol td.bar-c i.w70 { width: 70%; }
table.data.vol td.bar-c i.w75 { width: 75%; }
table.data.vol td.bar-c i.w80 { width: 80%; }
table.data.vol td.bar-c i.w85 { width: 85%; }
table.data.vol td.bar-c i.w90 { width: 90%; }
table.data.vol td.bar-c i.w95 { width: 95%; }
table.data.vol td.bar-c i.w100 { width: 100%; }

@media (max-width: 760px) {
  .totals {
    grid-template-columns: repeat(2, 1fr);
  }

  table.data.vol td.bar-c {
    display: none;
  }
}

/* ------------------------------ view 19 — receiving settings on a domain --
   A settings row is a control and an explanation, side by side, because every
   one of these five decides what happens to somebody's mail and none of them
   is self-explanatory from its label alone. The explanation is not help text
   that can be collapsed away: "receive mail flagged as spam" off is a setting
   that silently discards mail, and a customer has to be able to read that
   before they flip it rather than after. */
.recv .set {
  display: grid;
  /* `minmax(0, 1fr)`, not `1fr`. A bare `1fr` is `minmax(auto, 1fr)` and so
     refuses to go below the column's min-content — which here is the threshold
     row: a 68px label, a 110px box and a tag that must not wrap, about 300px
     together. On a 390px screen that made the panel wider than the page and
     pushed it off the right edge (found 2026-08-17 by the narrow-viewport test
     in DomainFlowTests). Zero as the minimum lets the column shrink; the
     `flex-wrap` on `.thr` below is what it shrinks into. */
  grid-template-columns: 42px minmax(0, 1fr);
  gap: 12px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--line);
}

.recv .set:last-of-type {
  border-bottom: 0;
}

.recv .set .ctl {
  padding-top: 2px;
}

.recv .set .txt b {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
}

.recv .set .txt p {
  margin: 5px 0 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 74ch;
}

/* A native checkbox, scaled up and tinted, rather than the prototype's drawn
   toggle. The toggle there is a label wrapping a hidden input, which is fine
   without script — but `accent-color` gets the same affordance out of the
   control a screen reader and a keyboard already understand, and there is
   nothing to keep in sync when the two disagree. */
.recv .set .ctl input[type='checkbox'] {
  width: 18px;
  height: 18px;
  accent-color: var(--green);
}

/* One threshold: its label, its box, and what it is doing now. */
.recv .thr {
  display: flex;
  align-items: center;
  gap: 8px 10px;
  margin-top: 10px;
  /* Only ever takes effect below ~430px, where the three parts no longer fit
     on one line. The tag is the part that drops, which is the right one to
     lose from the row: the label and the box are the control. */
  flex-wrap: wrap;
}

.recv .thr label {
  min-width: 68px;
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
}

.recv .thr .input {
  width: 110px;
}

/* The tag beside a threshold — the whole of R20 in one span. `default` and
   `set` are deliberately different colours as well as different words: a
   customer scanning the panel should be able to see which of the two boxes
   they have made a decision about without reading either. */
.recv .tag {
  font: 400 11.5px var(--mono);
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
}

.recv .tag.is-default {
  color: var(--ink-3);
  background: var(--bg-2);
}

.recv .tag.is-set {
  color: var(--green);
  background: var(--green-soft);
}

.recv .actions {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 15px 18px;
  border-top: 1px solid var(--line);
  flex-wrap: wrap;
}

.recv .actions .r {
  font: 400 12px var(--mono);
  color: var(--ink-3);
}

/* The confirmation before receiving is switched off (R23). Two lists, because
   the point of it is the second one: what does NOT change is the thing a
   customer is afraid of, and burying it in a paragraph under the warning is
   how "stop accepting mail" gets read as "delete my mail". */
.recv .confirm {
  padding: 18px;
}

.recv .confirm h4 {
  margin: 0 0 10px;
  font-size: 14px;
}

.recv .confirm ul {
  margin: 0;
  padding-left: 18px;
  max-width: 78ch;
}

.recv .confirm li {
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-2);
  margin-bottom: 7px;
}

.recv .confirm li b {
  color: var(--ink);
}

.recv .confirm .row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
}

/* ------------------------------------------------ view 19 — admin users --
   The admin surface reuses the dashboard's card, table, badge and button
   vocabulary wholesale; what is here is only what has no dashboard
   equivalent. The prototype styles it as a second product with its own
   chrome — a red masthead, a mono wordmark, one nav row — and that difference
   is the point: an operator looking at somebody else's account should never be
   in doubt about which of the two surfaces they are on. */

.admin-mark {
  margin: 0;
  padding: 6px 12px;
  text-align: center;
  background: oklch(from var(--red) l c h / .08);
  border-bottom: 1px solid oklch(from var(--red) l c h / .35);
  color: var(--red);
  font: 400 11px var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
}

.topbar.admin .brand {
  font-family: var(--mono);
}

.topbar.admin .brand .cursor {
  color: var(--green);
}

/* The one word that says which surface this is, in the colour the masthead
   above uses for the same reason. */
.topbar.admin .brand .scope {
  font-weight: 400;
  color: var(--red);
}

/* The search grows and the chips sit beside it, so the filter row reads as one
   control rather than as a form with an afterthought. */
.filters .f.grow {
  flex: 1 1 240px;
  max-width: 380px;
}

.filters .chips {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.filters .chips a {
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: 5px 12px;
  font: 500 12px var(--mono);
  color: var(--ink-2);
  text-decoration: none;
}

.filters .chips a:hover {
  color: var(--ink);
}

.filters .chips a.on {
  color: var(--green);
  background: var(--green-soft);
  border-color: var(--green-edge);
}

/* The hint is a whole row of its own under the controls, not a note beside
   one: it describes what the search does, and reading it should not depend on
   how the row happened to wrap. */
.filters .hint {
  flex-basis: 100%;
  margin: 0;
}

/* Seven columns do not fit a phone and will not be made to. The table scrolls
   sideways inside the card rather than reflowing, because every column here is
   a number or a status an operator compares down the column. */
.scroll-x {
  overflow-x: auto;
}

table.data.admin {
  min-width: 960px;
}

table.data.admin td.warn {
  color: var(--amber);
}

table.data.admin td.err {
  color: var(--red);
}

/* Why an account is stopped, under the badge that says it is. Wrapped rather
   than truncated: a reason nobody can read is a reason nobody wrote down. */
table.data.admin .reason {
  display: block;
  margin-top: 4px;
  font: 400 11px var(--mono);
  color: var(--ink-3);
}

table.data.admin .plan {
  display: flex;
  gap: 6px;
  align-items: center;
}

table.data.admin td.act {
  white-space: nowrap;
}

table.data.admin td.act .inline {
  display: inline-block;
}

table.data.admin td.act > * {
  margin-left: 6px;
}

.btn.sm,
.select.sm {
  height: 26px;
  padding: 0 9px;
  font-size: 11.5px;
}

.btn.sm {
  line-height: 24px;
}

.btn.sm.ok {
  border-color: var(--green-edge);
  color: var(--green);
}

/* ------------------------------------------ view 19 — suspend, confirmed --
   The account being acted on, in the type the rest of the surface uses for an
   address, so the heading and the row that led here read as the same value. */

.page-head .em,
.card .bd .em,
.add-lede .em {
  font-family: var(--mono);
  font-size: .92em;
  color: var(--ink);
}

.card .bd.col {
  display: block;
}

.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 18px;
  margin: 0 0 20px;
  font-size: 13px;
}

.facts dt {
  font: 400 11px var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  align-self: center;
}

.facts dd {
  margin: 0;
  font-family: var(--mono);
  font-size: 13px;
}

.acts {
  display: flex;
  gap: 12px;
  align-items: center;
  margin-top: 18px;
}

/* ------------------------------------------ view 09 — add a domain, retitled --
   The add form moved below the list in attempt-4, so it needs a heading and a
   sentence of its own: at the top of the page it was the only thing in its
   card and needed neither. The horizontal inset repeats `.add`'s rather than
   using `.card .bd`, because the form, the hint and these two sit in one card
   and have to line up down its left edge. */

.add-title {
  margin: 0;
  padding: 18px 22px 0;
  font-size: 14px;
  font-weight: 600;
}

.add-lede {
  margin: 0;
  padding: 6px 22px 0;
  font-size: 13px;
  color: var(--ink-2);
  max-width: 68ch;
}

/* The explicit way into a row, beside the linked name that was already there.
   Mono and small: it is a control, not the row's subject. */
table.data td .go {
  font: 400 12px var(--mono);
  white-space: nowrap;
}

/* ------------------------------------------------------- theme switch --
   Three buttons in the topbar, one per colour-scheme choice. Small, quiet,
   and the same control on all three chromes.

   aria-pressed carries the current choice, and [aria-pressed="true"] is what
   styles it — so the state a reader announces and the state you can see are
   the same attribute rather than two things that can drift apart. */
.theme-switch {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* The word "Theme" is dropped below the width where the topbar starts to
   crowd; the three buttons say what they are. */
.theme-switch .lbl {
  font: 400 11px var(--mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.theme-switch .grp {
  display: inline-flex;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  overflow: hidden;
}

.theme-switch button {
  appearance: none;
  border: 0;
  border-right: 1px solid var(--line-2);
  background: transparent;
  color: var(--ink-3);
  font: 400 11.5px var(--sans);
  padding: 4px 9px;
  cursor: pointer;
  transition: color 120ms, background-color 120ms;
}

.theme-switch button:last-child {
  border-right: 0;
}

.theme-switch button:hover {
  color: var(--ink);
  background: var(--bg-2);
}

.theme-switch button[aria-pressed="true"] {
  color: var(--green);
  background: var(--green-soft);
}

@media (max-width: 720px) {
  .theme-switch .lbl {
    display: none;
  }
}

/* The curl block on the API keys screen (spec 012 T22). It scrolls sideways
   rather than wrapping, which is the opposite of the reveal's rule above and
   for the opposite reason: a wrapped password is copied incompletely, while a
   wrapped shell command is copied with a line break in the middle of a header
   and pasted as two commands. */
pre.snippet {
  margin: 0;
  padding: 14px 16px;
  background: var(--bg-well);
  border: 1px solid var(--line);
  border-radius: 4px;
  font: 400 12.5px/1.65 var(--mono);
  color: var(--ink-2);
  overflow-x: auto;
}

/* The webhooks add form (spec 012 T23). `.gen` on its own is a one-line row —
   a field and a button — which is all the credentials and API key forms need.
   This one carries a URL, six checkboxes and a button, so the same class
   stacks when it is given `col`. A second form class would have duplicated the
   padding and the field sizing to change one property. */
.gen.col {
  flex-direction: column;
  align-items: stretch;
  gap: 18px;
}

.gen.col .f,
.gen.col .btn {
  flex: 0 0 auto;
  align-self: flex-start;
}

.gen.col .f {
  width: 100%;
  max-width: 520px;
}

.evs {
  border: 0;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.evs legend {
  padding: 0;
  margin-bottom: 9px;
}

/* One event: its box, its name, and what it means. The hint is on the same
   line rather than under the name, because six two-line rows read as a wall
   and the point of the hint is that "accepted" and "delivered" are not the
   same event. It wraps to its own line below ~520px, where it no longer
   fits. */
.ev {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  cursor: pointer;
}

/* A native checkbox tinted with accent-color, for the reason recorded on the
   inbound settings toggle: it is the control a keyboard and a screen reader
   already understand, and nothing has to be kept in sync when a drawn one
   disagrees with it. */
.ev input[type='checkbox'] {
  width: 16px;
  height: 16px;
  accent-color: var(--green);
  /* baseline alignment puts a bare box a little high against the text next to
     it; the control has no baseline of its own to align. */
  align-self: center;
}

.ev .n {
  font: 500 13.5px var(--mono);
  color: var(--ink);
}

.ev .h {
  font-size: 13px;
  color: var(--ink-3);
}

/* The webhooks list is the one table with two controls in its right-hand cell.
   Left to wrap they stack, which doubles the row height and puts "delete"
   underneath the button that does not delete anything. */
table.data td.r {
  white-space: nowrap;
}

/* A bare <form> is display:block, so the form and the link either side of it
   take a line each and the row is twice the height of the one above. The admin
   table solves this on its own `td.act`; this is the same fix for the ordinary
   one. */
table.data td.r .inline {
  display: inline-block;
}

table.data td.r > * + * {
  margin-left: 6px;
}

/* ------------------------------------------------------------- billing --
   The plan card's two additions (spec 012 T24). attempt-4 draws the plan
   buttons in a flex row under the meter and has no equivalent of the
   scheduled-change line, because its account has no change pending. */

/* One form per button, and a bare <form> is display:block — the same trap the
   webhooks row hit. Without the flex here the three plan buttons stack. */
.plans {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 14px;
}

/* The downgrade already asked for, with the way out beside it. Bordered rather
   than another card, so it reads as a state of the plan above it and not as a
   fourth section of the page. */
.sched {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 14px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--bg-2);
  font-size: 14px;
  color: var(--ink-2);
}

.sched b {
  color: var(--ink);
  font-weight: 600;
}

/* The processor's own wording under the badge, on its own line: an invoice that
   failed is the one row on this table with a reason, and beside the badge it
   would push the amount column out of line on every other row. */
table.data td .sub {
  display: block;
  margin-top: 4px;
  font-size: 12.5px;
  color: var(--ink-3);
}
