/* ============================================================================
   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(80% .19 152 / .35);
  background: var(--green-soft);
}

.flash.err {
  color: var(--red);
  border-color: oklch(68% .19 25 / .35);
  background: var(--red-soft);
}

/* ---------------------------------------------------------------- quota -- */

.quota {
  padding: 22px 24px;
}

.quota .row1 {
  display: flex;
  align-items: baseline;
  gap: 14px;
}

.quota .n {
  font: 700 30px var(--mono);
  letter-spacing: -.02em;
}

.quota .plan {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 12px;
}

.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(70% .16 152), var(--green));
}

/* Over the plan allowance, the meter stops being reassuring. */
.meter.over i {
  background: linear-gradient(90deg, oklch(70% .14 82), 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;
}

.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;
}

.stat .v .pct {
  font-size: 13px;
  color: var(--green);
  font-weight: 400;
}

.stat.bad .v .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 .t {
  font-weight: 600;
  font-size: 14px;
}

.chart .hd .k {
  margin-left: auto;
  font: 400 11px var(--mono);
  color: var(--ink-3);
}

.bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 120px;
}

.bars i {
  flex: 1;
  background: oklch(45% .1 152);
  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 {
  text-align: right;
}

.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(80% .19 152/.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);
}

.onb .step .status {
  font: 400 11.5px var(--mono);
  color: var(--ink-3);
  padding-top: 6px;
}

.onb .step.done .status {
  color: var(--green);
}

.onb .step .act {
  padding-top: 2px;
}

.strip {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 16px;
  padding: 16px 22px;
}

.strip .n {
  font: 700 22px var(--mono);
}

.strip .t {
  font: 400 12px var(--mono);
  color: var(--ink-3);
}

.strip .r {
  margin-left: auto;
}

.teaser {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 16px;
}

.teaser .card {
  padding: 20px 22px;
}

.teaser h3 {
  font-size: 14.5px;
  margin-bottom: 6px;
}

.teaser p {
  font-size: 12.5px;
  color: var(--ink-2);
}

.teaser a {
  font: 400 12px var(--mono);
  color: var(--green);
  text-decoration: none;
  display: inline-block;
  margin-top: 12px;
}

.page-head.col {
  display: block;
}

@media (max-width: 820px) {
  .teaser {
    grid-template-columns: 1fr;
  }

  .onb .step {
    grid-template-columns: 40px 1fr;
  }

  .onb .step .act {
    grid-column: 2;
  }
}

.axis {
  display: flex;
  font: 400 10px var(--mono);
  color: var(--ink-3);
  margin-top: 8px;
}

.axis .r {
  margin-left: auto;
}

/* ----------------------------------------------------------- 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(80% 0.13 82 / 0.4);
}

.banner.warn .code {
  color: var(--amber);
}

.banner.err {
  background: var(--red-soft);
  border-color: oklch(68% 0.19 25 / 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(80% .19 152 / .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;
}

.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: oklch(11% .008 165);
}

.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 {
    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(80% .19 152 / .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 oklch(0% 0 0 / .4);
}

.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 {
  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);
}

.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 {
  color: var(--red);
}

.danger .bd {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 16px 18px;
  flex-wrap: wrap;
}

.danger p {
  font-size: 13px;
  color: var(--ink-2);
  flex: 1 1 320px;
  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. */
.filters {
  display: flex;
  gap: 10px;
  padding: 14px 16px;
  align-items: flex-end;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
}

.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. */
.filters .apply {
  margin-left: auto;
}

.pager {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  font: 400 12px var(--mono);
  color: var(--ink-3);
  border-top: 1px solid var(--line);
  flex-wrap: wrap;
}

.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);
}

.detail .hd .subj {
  font-weight: 600;
  font-size: 15px;
}

.detail .hd .id {
  margin-left: auto;
  font: 400 11px var(--mono);
  color: var(--ink-3);
}

/* 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(68% .19 25 / .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(80% .13 82 / .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;
  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;
}
