/*
 * Shared components: the pieces more than one screen is built from.
 *
 * app.css is the shell and nothing else. This is everything the shell puts
 * inside itself, and later phases grow it: cards, data tables, status chips,
 * the quota meter, the tab strip. A component that only one screen ever uses
 * belongs in that screen's own file under pages/, not here.
 */

/* ==========================================================================
   Callouts
   TWO REGISTERS AND NO MORE. A note is informational and takes the accent
   triad; a real "this will fail" caution takes the danger triad. There is no
   warning hue in this system and the empty slot is deliberate: amber was
   retired from this brand, and reintroducing it for a middle state would put
   the retired colour back on the page in the one place readers look hardest.

   A slow call, a quota at 80% and a term expiring in a fortnight all feel like
   they want amber and all three are informational. Only something that has
   already failed, or definitely will, takes danger.

   THE LEADING BAR IS THE SIGNAL, not the ground. The ground is the 050 step,
   because a callout is a large block of colour sitting inside running text and
   at the 100 step it stops reading as an aside and starts reading as a
   different design. The bar carries the same meaning in a tenth of the area.
   ========================================================================== */

/* THE BOX IS CAPPED, NOT THE TEXT INSIDE IT. Capping the children instead
   leaves a full-width panel with its copy stopping two thirds of the way
   across, which reads as a block that failed to fill rather than as a measure.
   48rem gives a content column of about 45rem, which is the site's measure,
   so the line length is right and the box ends where the text does. */
.callout {
  max-inline-size: 48rem;
  padding: var(--mbx-space-4) var(--mbx-space-5);
  border: 1px solid var(--mbx-border);
  border-inline-start: 3px solid var(--mbx-note-bar);
  border-radius: var(--mbx-radius-md);
  background: var(--mbx-note-bg);
  margin-block-end: var(--mbx-space-6);
}

/* 16, not 12. Under text-box-trim a margin is the ENTIRE visible gap: the
   half-leading that used to pad both sides of a text block is exactly what the
   trim removes, so 12 renders about as tight as 4 used to. Chosen by looking
   at it, which is the rule in a trimmed scope, because every numeric check
   passes a value that is visibly wrong. */
.callout > * + * { margin-block-start: var(--mbx-space-4); }

/* One step under the card title it sits inside. It was 18px display, which
   after the card title came down to 16 would have left the aside shouting
   over the section holding it. */
.callout__title {
  font-weight: 600;
  font-size: var(--pnl-text-ui);
  line-height: var(--pnl-lh-ui);
  letter-spacing: var(--mbx-track-heading);
}

.callout p {
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  color: var(--mbx-ink-muted);
}

/* WHAT TO DO NOW, which is the third part of an error and the part most often
   missing. Set apart from the explanation because a customer reading a failure
   is scanning for the sentence with an instruction in it. */
.callout__remedy {
  color: var(--mbx-ink);
  font-weight: 600;
}

/* INSIDE A CARD IT TAKES THE CARD'S WIDTH. Its own 48rem cap is for a callout
   sitting on bare paper; inside a 52rem card that is already a measure, a
   second cap 4rem narrower just leaves a ragged right edge inside a straight
   one. */
.section > .callout { max-inline-size: none; }
/* A callout's bottom margin is for separating it from what follows. As the
   last thing in a card there is nothing following, and the margin renders as
   48px of empty card. */
.section > :last-child { margin-block-end: 0; }

/* A BUTTON IS NOT AN INLINE. This is the "underline" that appeared under the
   last line of the callout text and was not in any stylesheet: it was the
   BUTTON'S OWN TOP BORDER, and it moved with the text because it was always
   drawn across the button's width at the button's top edge.

   .btn is inline-flex with a fixed 40px height. Dropped into a paragraph it
   is an inline-level box, and under text-box-trim a paragraph holding no text
   trims to an 11px line box, so a 40px button overflows it by 29px: about 15
   of that goes upward and lands its 1px top border on the last line of the
   paragraph above. Measured: prose ends y=688, button box starts y=690, its
   own paragraph is 11px tall starting at y=704.

   Flex gives the button a block formatting context, so the paragraph is as
   tall as the button, the margin below works normally, and nothing overflows.
   :has(> .btn) leaves text-only remedies as ordinary paragraphs.

   THE GENERAL RULE: a fixed-height inline-flex control inside running text is
   a box that overflows its own line. Give it a flex or grid parent.

   AND THE SELECTOR NOW MATCHES THE RULE, corrected 2026-08-31. It read
   `.callout__remedy:has(> .btn)` - the general rule stated, then applied to the
   one class it had been found on. `add-done.php` ends its last card with a bare
   `<p>` holding the primary button, which is the identical construct, and the
   owner photographed the result: the button sitting hard against the bottom of
   the card with no air under it.

   Measured on that markup before the change: paragraph 10px, button 40px, and
   15px of it hanging BELOW its own paragraph's box - straight into the card's
   bottom padding, which is what ate the gap. `.section > :last-child` zeroes
   the margin that would otherwise have hidden it, so the overflow is all that
   is left.

   `p:has(> .btn)` covers both. A remedy IS a paragraph, so nothing is lost by
   dropping the narrower selector, and a rule that describes every case should
   not be written to match one of them. */
p:has(> .btn) {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mbx-space-3);
}

.callout--caution {
  background: var(--mbx-caution-bg);
  border-color: var(--mbx-danger-border);
  border-inline-start-color: var(--mbx-caution-bar);
}

.callout--success {
  background: var(--mbx-success-bg);
  border-color: var(--mbx-success-border);
  border-inline-start-color: var(--mbx-success-ink);
}

/* SOMETHING TRUE OF THIS CUSTOMER AND NOT OF THE NEXT ONE, which is the one
   kind of block on a card that has earned a colour without being a status.

   The calendar card carries a help box naming four hosts, and for a customer
   whose mailbox is on one of them three of those rows are noise and the fourth
   is the answer. When the panel can tell which, it says so directly - and the
   result has to look different from the general help underneath it, or it is
   two identical grey boxes and the reader has no reason to think the first one
   is about them.

   THE ACCENT, NOT CAUTION AND NOT SUCCESS. Amber is a fault and this is not one;
   green is a thing that has finished and this has not started. Blue is what the
   panel already paints an information mark in, so a tinted block in the same
   hue reads as the loudest member of a set the reader knows rather than a new
   kind of warning. Contrast is settled by tools/palette.py, not by eye.

   IT TAKES THE QUIET CALLOUT'S EYEBROW WHOLE, rather than a variant of it, and
   the first version did not: it fell through to the base eyebrow, which is grey
   with 8px under it and no mark, and it sat directly above a grey box whose
   eyebrow is ink with 16px and a blue i. So the block that was supposed to be
   the loud one had the quieter label of the two, which is the hierarchy exactly
   backwards and was obvious the moment it was looked at rather than reasoned
   about. Same label, same mark, same spacing; the tint and the bar are what
   differ, and that is enough. */
.callout--offer {
  background: var(--mbx-accent-soft);
  border-color: var(--mbx-accent-soft-border);
  border-inline-start-color: var(--mbx-accent);
}

/* THE SAME BOX WITH NOTHING TO SAY FOR ITSELF. Standing explanation is not a
   status: it is true on every visit, it needs no colour, and an accent bar down
   the side of it makes a permanent note look like a new one. Neutral stock and
   an even border, so what it holds still reads as one block set apart from the
   card, which is the whole job.

   Owner 2026-08-29, on two paragraphs under the deliverability chips: "These
   might look better boxed? And same font size." They were a `.hint` and a
   `.prose` stacked loose, which is two sizes of grey text in a row and reads
   exactly like what it was, a sentence added later next to a sentence that was
   already there. */
.callout--quiet {
  background: var(--mbx-bg-sunken);
  border-color: var(--mbx-border);
  border-inline-start-width: 1px;
  border-inline-start-color: var(--mbx-border);
}

/* AND ITS TEXT IS THE SAME COLOUR AS THE TEXT ABOVE IT. Owner 2026-08-30, on a
   grey aside hanging off the end of an FAQ answer: "This is a bad look and bad
   design - different colour of text. It should be boxed as some kind of
   additional information or something that makes sense - will need a title or
   eyebrow at least."

   Both halves, and the second is the one easy to skip. A box alone still leaves
   a paragraph of pale text sitting directly under a paragraph of ink, and the
   reader is left to work out what the shade is FOR - which is the same fault
   the two-box ruling of 2026-08-29 was about, and its conclusion was "both can
   be the same size and the same colour". The eyebrow is what says "this is set
   apart" now, so the colour no longer has to, and standing explanation is not
   less important than the sentence it qualifies.

   AND THE EYEBROW IS INK TOO. Owner 2026-08-30: "I don't think the eyebrow
   should be grey either. Black maybe?" It was left soft on the argument that
   uppercase micro type at full ink shouts over the paragraph it introduces,
   which is true of an eyebrow floating on a page and not of one inside a box
   that is already set apart. Here it is the block's title, and a title in the
   same grey the design has just been told to stop using is the same fault one
   size down. Nothing in this component is muted now: the mark is blue, the
   title is ink, the prose is ink. */
/* `.callout.callout--quiet`, WITH BOTH CLASSES, AND IT IS NOT DECORATION. The
   base eyebrow rule is `.callout p.callout__eyebrow`, which is (0,2,1); so is
   `.callout--quiet p.callout__eyebrow`, and it is declared EARLIER in this file
   than the rule it is trying to beat, so the tie goes the wrong way and the
   eyebrow stayed rgb(95,105,125) with ink set on the line above it. Doubling
   the class takes it to (0,3,1) and settles it outright, which is the same fix
   the flow rule in the docs section needed for the same reason. Measured both
   ways rather than reasoned about. */
.callout--quiet p,
.callout--offer p { color: var(--mbx-ink); }
.callout.callout--quiet p.callout__eyebrow,
.callout.callout--offer p.callout__eyebrow { color: var(--mbx-ink); }

/* ONE COLUMN. The eyebrow sits above its paragraph and nothing sits beside it.
 *
 * Owner 2026-08-30, on a version that put the label in a left-hand column when
 * the card was wide enough: "1 column for goodness sakes. Why on earth would
 * you think 2 was a good idea? It looks stupid and is stupid." Recorded because
 * the reasoning that produced it was plausible and still wrong: the label was
 * moved out to keep the prose at a readable measure in a spanning card without
 * leaving the box half empty. A margin note is a print device, and in a stack
 * of cards it reads as a table row that lost its neighbours.
 *
 * THE LINE LENGTH IS SOLVED BY CAPPING THE BOX, not the paragraph inside it.
 * `.hint` always capped itself at `--mbx-measure`, and a spanning card needs
 * that cap back: the help page's first card is about 72rem across, and a
 * paragraph filling it runs to roughly 135 characters a line, which is twice a
 * comfortable measure and worse to read than the grey text it replaced.
 *
 * Capping the paragraph alone leaves the box spanning with a column of text in
 * its left half and nothing in its right, which is the dead space the standing
 * ruling on full-width blocks is about. Capping the BOX ends both together, so
 * the note is a measure-wide block that fills itself. `.section > .callout`
 * clears the cap for callouts generally - a status box should reach the card's
 * edge - and this puts it back for the quiet one, which is prose. */
.callout--quiet,
.callout--offer,
.section > .callout--quiet,
.section > .callout--offer {
  max-inline-size: calc(var(--mbx-measure) + var(--mbx-space-4) * 2);
}

/* 16px EACH SIDE, NOT 24, AND THE TYPE CHECK IS WHY. A quiet note is nested
   inside a card that already has its own inset, so it does not need the same
   one again - and at 24 the text inside one of these in a 483px column comes to
   44 characters a line, one under the 45 the measure check allows. It caught
   that on the connector tab and the help page at 1440, both of which put these
   in a column about half the page wide. 16 gives back the two characters. */
.callout--quiet,
.callout--offer { padding-inline: var(--mbx-space-4); }

/* AN INFORMATION MARK ON THE LABEL. Owner 2026-08-30: "Perhaps an information
   icon would help it?" It does: the box says "set apart" and the eyebrow says
   what about, but neither says WHAT KIND of thing this is, and an i in a circle
   is the one glyph that needs no legend for that.
 *
 * IN CSS RATHER THAN IN THE MARKUP, which is the opposite of every other icon
 * in this panel and is deliberate. `icons.php` draws marks that NAME something
 * - a destination, a record, a state - and each is written at the one place it
 * belongs. This one is a property of the component: every quiet callout has it,
 * always, and there are twenty-five of them. A markup icon is twenty-five
 * chances to forget, and the twenty-sixth callout would be the one without it.
 *
 * `mask-image` and not `background-image`, so it takes `currentColor` and
 * follows the eyebrow rather than pinning a hex that a token change would leave
 * behind. Same geometry as the rest of the set: 20px box, 1.5px stroke, round
 * caps, and the dot drawn as a zero-length path exactly as `question` does. */
.callout--quiet .callout__eyebrow,
.callout--offer .callout__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--mbx-space-2);
}

/* AND IT NEEDS THE FULL 16, not the 8 an eyebrow takes elsewhere. Owner
   2026-08-30: "the spacing is VERY tight between the eyebrow/title and text."
   The 8px rule above was written for two stacked boxes, where 16 left the label
   floating between them rather than belonging to one. Inside a single box there
   is no second box to float between, and under text-box-trim a margin is the
   ENTIRE visible gap - the half-leading that used to pad a text block on both
   sides is exactly what the trim removes - so 8px renders about as tight as
   2px used to. 16 is also the next step on the 8px grid the type check
   enforces; 12 would be the eye's answer and would fail it. */
.callout--quiet .callout__eyebrow + p,
.callout--offer .callout__eyebrow + p { margin-block-start: var(--mbx-space-4); }

.callout--quiet .callout__eyebrow::before,
.callout--offer .callout__eyebrow::before {
  content: "";
  flex: none;
  inline-size: 1rem;
  block-size: 1rem;
  /* THE ACCENT, NOT THE EYEBROW'S GREY. Owner 2026-08-30: "The icon should not
     be grey." Right, and `currentColor` was the reflex rather than the answer:
     it made the mark follow the label, and a grey i in a grey line is one more
     piece of quiet text rather than the thing that says at a glance what kind
     of block this is. Blue is what every `.section__icon` in the panel already
     uses for exactly that job, so the callout now reads as part of the same
     set. The label stays soft; the mark carries the colour. */
  background-color: var(--mbx-accent);
  --mbx-info-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='10' cy='10' r='7.5'/%3E%3Cpath d='M10 9.2v4.6'/%3E%3Cpath d='M10 6.2v.01'/%3E%3C/svg%3E");
  mask: var(--mbx-info-mark) center / contain no-repeat;
  -webkit-mask: var(--mbx-info-mark) center / contain no-repeat;
}

/* WHAT WE FOUND, THEN WHAT IT MEANS - AND THEY ARE TWO BOXES, NOT ONE.
   They were one box with a rule across it, the finding in ink and the
   explanation in muted grey. Owner 2026-08-29: "Why are these different font
   colours?? If they are 2 different things (they appear to be) maybe a box
   each, with a small heading/eyebrow?" Exactly right: colour is a poor way to
   say "these are two kinds of thing", because the reader has to work out what
   the difference in shade is FOR. Two boxes say it structurally, and then both
   can be the same size and the same colour, which is what was asked for in the
   first place.

   THE EYEBROW IS WHAT MAKES THE SECOND BOX WORTH HAVING. A box with no label is
   a box; a box labelled DKIM sits under the DKIM chip and says which of the
   three records it is talking about. */
/* `.callout p.callout__eyebrow`, because `.callout p` above sets the body size
   and the muted colour and would otherwise win on specificity. */
.callout p.callout__eyebrow {
  font-size: var(--pnl-text-micro);
  line-height: var(--pnl-lh-micro);
  letter-spacing: var(--mbx-track-eyebrow);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--mbx-ink-soft);
}

/* Tight under its own label. The callout's standing space-4 between children is
   the gap between paragraphs, and an eyebrow is not a paragraph: at 16px it
   floats between the two boxes instead of belonging to one of them. */
.callout__eyebrow + p { margin-block-start: var(--mbx-space-2); }

/* The callout carries a standing bottom margin for when it interrupts a page.
   Stacked deliberately, the gap belongs to the stack. */
.deliv__notes { display: grid; gap: var(--mbx-space-4); }
.deliv__notes .callout { margin-block-end: 0; }

/* ==========================================================================
   The honeypot's hiding.

   NOT display:none, and not visibility:hidden. Both are the first thing a
   form-filling bot checks, and a trap a bot can see is not a trap. Off-canvas
   with a zero-size box keeps the field in the DOM, fillable by a script and
   unreachable by a person; the markup adds aria-hidden and tabindex="-1" so it
   is also absent from the accessibility tree and the tab order.

   That belt-and-braces matters: if the hiding ever fails, the field is still
   hidden from a screen reader, which is the user most likely to be caught by a
   trap intended for robots.
   ========================================================================== */
.trap {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* THE INPUT TOO, not just the wrapper. Clipped by the parent it painted
   nothing, but it kept its own 177x21 box, so anything measuring the FIELD
   rather than its container still found a real target sitting over the form.
   A trap that overlaps a real control is a trap that catches customers. */
.trap input,
.trap label {
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  border: 0;
}

/* ==========================================================================
   Status chips

   A WORD IN A CHIP, NEVER AN EMOJI AND NEVER A TICK. Emoji in a status column
   is the fastest way to make an audit trail look like a toy, and a tick says
   only "something passed" without saying what or when. Colour is reinforcement
   here and never the signal: the word carries the meaning, which is what
   WCAG 1.4.1 asks for and also what makes the column readable at a glance in a
   greyscale screenshot.

   TWO REGISTERS ONLY, matching the callouts. There is no amber in this system
   and the empty warning slot stays empty: a slow call, a quota at 80% and a
   term expiring in a fortnight are all informational.
   ========================================================================== */

.chip {
  block-size: var(--pnl-control-sm-h);          /* 32, clears the 24px target */
  display: inline-flex;
  align-items: center;
  gap: var(--mbx-space-2);
  padding-inline: var(--mbx-space-3);
  border-radius: var(--mbx-radius-pill);
  border: 1px solid var(--mbx-border);
  background: var(--mbx-bg-sunken);
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  font-weight: 600;
  white-space: nowrap;
}

/* A 6px dot, drawn with a pseudo-element rather than a character. A bullet
   glyph moves with the face and sits off the optical centre; a box does not. */
.chip::before {
  content: "";
  inline-size: 6px;
  block-size: 6px;
  border-radius: var(--mbx-radius-pill);
  background: currentColor;
  flex: none;
}

.chip--ok      { background: var(--mbx-success-bg); border-color: var(--mbx-success-border); color: var(--mbx-success-ink); }
.chip--bad     { background: var(--mbx-danger-bg);  border-color: var(--mbx-danger-border);  color: var(--mbx-danger-ink); }
.chip--note    { background: var(--mbx-note-fill);  border-color: var(--mbx-note-border);    color: var(--mbx-note-ink); }
.chip--quiet   { color: var(--mbx-ink-muted); }

/* No dot where the chip is a plain label rather than a state. */
.chip--plain::before { display: none; }

/* THE FILL GOES, THE WORD AND THE DOT STAY. For a column where one state is
   the overwhelming majority: an audit log's outcome is "Worked" on 49 rows in
   50, and 49 filled green pills is 49 pieces of decoration that the four red
   ones have to shout over. Stripped back, the failures are the only filled
   thing on the column and the eye finds them without looking.

   The colour stays on the text and the dot, so this is still a state and
   still not colour-only. Padding goes with the fill: a pill-shaped gap where
   no pill is drawn pushes the column wider than its content for nothing.

   NOT APPLIED TO chip--bad. The exception is supposed to be loud. */
.chip--bare {
  background: none;
  border-color: transparent;
  padding-inline: 0;
  font-weight: 400;
}

/* ==========================================================================
   The plan switch: off is Free, on is Pro

   A SWITCH RATHER THAN A BADGE, owner 2026-08-29. A pill states the plan; a
   switch states the plan and says it is the customer's to change. That second
   half is the whole reason for it, so the affordance has to read as operable:
   a real track, a real knob, and a hover and focus state.

   IT IS AN ANCHOR, AND EVERY CLICK IS SAFE. It goes to the plan page, where
   the price, the date and the consequence are stated and where the
   confirmation dialog lives. Nothing here can start a payment or stop one, so
   the customer who clicks it to find out what it does finds out for the price
   of a page load. See mbx_plan_switch() for the reasoning.

   THE WORD IS NOT DECORATION. Knob position and colour are both differences a
   greyscale screenshot and a colour-blind reader would lose, so "Free" and
   "Pro" sit beside the track and carry the meaning on their own, which is what
   WCAG 1.4.1 asks for. Same rule the chips above follow.

   SIZED OFF THE CHIP, so a row that has one and a row that has the other line
   up: the same 32px control box, and the track is 24px inside it. The knob is
   18px with a 3px inset (3 + 18 + 3 = 24), and travels 20px (3 + 18 + 20 + 3 =
   44).
   ========================================================================== */

.planswitch {
  block-size: var(--pnl-control-sm-h);          /* 32, matching .chip exactly */
  display: inline-flex;
  align-items: center;
  gap: var(--mbx-space-2);
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  font-weight: 600;
  white-space: nowrap;
  color: var(--mbx-ink-muted);
  text-decoration: none;
}

.planswitch__track {
  position: relative;
  inline-size: 44px;
  block-size: 24px;
  flex: none;
  border-radius: var(--mbx-radius-pill);
  border: 1px solid var(--mbx-border);
  background: var(--mbx-bg-sunken);
  transition: background-color 120ms ease, border-color 120ms ease;
}

.planswitch__knob {
  position: absolute;
  inset-block-start: 2px;                        /* 2 + 1px border = the 3px inset */
  inset-inline-start: 2px;
  inline-size: 18px;
  block-size: 18px;
  border-radius: var(--mbx-radius-pill);
  background: var(--mbx-bg-raised);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.28);
  transition: transform 120ms ease;
}

/* ON. The accent fill is the same one the note chip uses, so the two states of
   this control sit in a palette the rest of the panel already declares. */
.planswitch.is-on {
  color: var(--mbx-note-ink);
}

.planswitch.is-on .planswitch__track {
  background: var(--mbx-accent);
  border-color: var(--mbx-accent);
}

.planswitch.is-on .planswitch__knob {
  transform: translateX(20px);
}

/* OPERABLE, AND IT HAS TO LOOK IT. A switch nobody believes is clickable is
   just a badge that wastes 44 pixels. */
a.planswitch:hover .planswitch__track { border-color: var(--mbx-accent); }
a.planswitch:hover .planswitch__word  { text-decoration: underline; }

/* INSIDE THE MAILBOX LIST IT IS A PICTURE, because the row around it is
   already a link and an anchor inside an anchor is invalid HTML that the
   parser resolves by splitting the row. The row's own hover state covers it,
   and a click lands on the mailbox, where the header switch is live. */
.planswitch--static { cursor: inherit; }

/* THE ONE THAT ACTUALLY DOES SOMETHING, on the plan page, and it opens a
   confirmation rather than acting. A <button> needs its user-agent styling
   taken off before it can look like the anchor version; everything else is
   inherited from .planswitch above so the two cannot drift apart. */
.planswitch--action {
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-size: var(--pnl-text-sm);
  font-weight: 600;
  cursor: pointer;
}

.planswitch--action:hover .planswitch__track { border-color: var(--mbx-accent); }
.planswitch--action:hover .planswitch__word  { text-decoration: underline; }

/* THE KNOB DOES NOT MOVE ON PRESS, and that is deliberate rather than an
   oversight. It renders from the plan the API returned, so it cannot show Pro
   until Pro is true - the same rule that stops the `?paid=1` return announcing
   Pro from a query string. What a press does is open the dialog; the switch
   moves when the plan does. Nudging it under the finger acknowledges the press
   without claiming the plan changed. */
.planswitch--action:active .planswitch__knob { transform: translateX(2px); }
.planswitch--action.is-on:active .planswitch__knob { transform: translateX(18px); }

/* THE RING GOES ROUND THE WHOLE CONTROL, not round the track. Keyboard focus
   has to show what will be activated, and what will be activated is the link. */
.planswitch:focus-visible {
  outline: var(--mbx-focus-width) solid var(--mbx-focus);
  outline-offset: var(--mbx-focus-offset);
  border-radius: var(--mbx-radius-sm);
}

/* NOBODY NEEDS THE KNOB TO SLIDE. It is a state indicator, and the animation
   is a nicety that costs a vestibular reader something real. */
@media (prefers-reduced-motion: reduce) {
  .planswitch__track,
  .planswitch__knob { transition: none; }
}

/* ==========================================================================
   Deliverability: three states, and the date they were checked

   SPF, DKIM and DMARC are three separate answers and the reference panel
   collapses them into "Anti-spam 3/3" with three green ticks beside it. A
   customer with no DMARC record needs to know WHICH one is missing, because
   the three are fixed in different places and only one of them is usually the
   customer's own job.
   ========================================================================== */

.deliv { display: flex; flex-wrap: wrap; gap: var(--mbx-space-2); }


.deliv__item {
  display: inline-flex;
  align-items: center;
  gap: var(--mbx-space-1);
  font-family: var(--mbx-font-mono);
  font-size: var(--pnl-text-micro);
  line-height: var(--pnl-lh-micro);
  letter-spacing: var(--mbx-track-eyebrow);
  padding: var(--mbx-space-1) var(--mbx-space-2);
  border-radius: var(--mbx-radius-xs);
  background: var(--mbx-bg-sunken);
  color: var(--mbx-ink-muted);
}
.deliv__item--pass    { background: var(--mbx-success-bg); color: var(--mbx-success-ink); }
.deliv__item--fail    { background: var(--mbx-danger-bg);  color: var(--mbx-danger-ink); }
/* MISSING IS NOT FAILING, and they must not look alike. No DMARC record is
   extremely common, is fixable, and is not the same as one that is present and
   wrong. Colouring it red tells a customer something is broken when nothing
   is. */
.deliv__item--missing { background: var(--mbx-bg-sunken);  color: var(--mbx-ink-muted); }

/* AND NOT-CHECKED IS NEITHER OF THOSE. The API separates "there is no such
   record" from "the lookup could not be completed", and this panel used to
   flatten both into missing - so a mailbox nobody had ever run a DNS check on
   rendered three grey MISSING chips and the add flow told every new customer to
   go and fix records they may already have had. Dashed, and captioned "not
   checked yet" rather than "Checked never".

   Deliberately the faintest of the four. It is the absence of a measurement,
   not a measurement. */
.deliv__item--unknown {
  background: none;
  border: 1px dashed var(--mbx-border);
  color: var(--mbx-ink-soft);
  /* The border would otherwise push this chip 2px taller than its neighbours
     and break the row's shared baseline. */
  padding-block: calc(var(--mbx-space-1) - 1px);
}

/* ==========================================================================
   The sparkline

   INLINE SVG, drawn server-side, with no charting library and no inline style
   attribute: the strict CSP would refuse the latter, and the geometry lives in
   SVG attributes, which are markup rather than CSS and are unaffected.

   It is 14 days in about 96x24, which is why the list needs no chart of its
   own. It carries no axis and no label on purpose: it is a SHAPE, and the
   number beside it is the fact.
   ========================================================================== */

.spark { display: block; overflow: visible; }
.spark__bar { fill: var(--mbx-c-accent-200); }
.spark__bar--last { fill: var(--mbx-accent); }
/* A day with no calls still gets a mark, so a gap reads as zero rather than as
   missing data. Without it a quiet weekend looks like a broken chart. */
.spark__zero { fill: var(--mbx-border); }

/* ==========================================================================
   The empty state

   THREE PARTS, ALWAYS: what goes here, why it is empty, and the one action
   that fills it. Never a grey line of text. An empty region is the best
   teaching surface in any product, because the customer is looking at it with
   nothing else to read.
   ========================================================================== */

.empty {
  padding: var(--mbx-space-7) var(--mbx-space-5);
  text-align: center;
  border: 1px dashed var(--mbx-border-control);
  border-radius: var(--mbx-radius-md);
  background: var(--mbx-bg-raised);
}
/* An empty state IS the page, so its title keeps the h1 step rather than
   the card step. This is one of the two places a bigger heading is earning
   it rather than inheriting it. */
.empty__title {
  font-weight: 600;
  font-size: var(--pnl-text-lg);
  line-height: var(--pnl-lh-lg);
  letter-spacing: var(--mbx-track-heading);
}
.empty__body {
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  color: var(--mbx-ink-muted);
  max-inline-size: var(--mbx-measure);
  margin: var(--mbx-space-3) auto 0;
}
.empty__body + .empty__body { margin-block-start: var(--mbx-space-4); }
.empty .btn { margin-block-start: var(--mbx-space-5); }

/* ==========================================================================
   A section: a titled block of content inside the content column
   ========================================================================== */

/* ==========================================================================
   A SECTION IS A CARD.

   This was hairlines on bare paper, and the whole content column read as
   unfinished beside the navy rail. The charts already sat on .chartbox and
   were the only part of the panel that looked designed, which was the tell:
   the surface was doing the work, and everything without one looked like
   markup nobody had got to yet.

   IT IS ALSO WHAT MAKES A SECTION A THING. Separated only by a 48px gap, six
   settings blocks are one long scroll with headings in it; on cards they are
   six objects, and the eye can find the one it wants without reading.

   A SECTION THAT ALREADY CARRIES ITS OWN SURFACE OPTS OUT, via :has, rather
   than by a modifier every template would have to remember. A chart inside
   .chartbox inside a card is two borders 24px apart.
   ========================================================================== */
.section {
  padding: var(--pnl-card-pad);
  background: var(--mbx-bg-raised);
  border: 1px solid var(--mbx-border);
  border-radius: var(--mbx-radius-md);
  box-shadow: var(--mbx-shadow-1);
}

/* ---- A CARD IS NEVER CAPPED. THE TEXT INSIDE IT IS. ----------------------
   REVERSED 2026-08-26, and the reversal is the point. The first version of
   this card capped the SECTION at 52rem so it "ended where its content
   ended". On a 1440 monitor that produced a column of cards two thirds of
   the way across an empty page, with three different card widths stacked on
   top of each other because the wide ones opted out. The owner had already
   ruled against exactly this ("It should use all available width"), and the
   cap put it straight back.

   main.css line 381 states the rule this file should have followed: THE
   MEASURE GOES ON THE TEXT, NEVER ON THE CONTAINER. A capped card leaves a
   dead half with a border drawn round it. A full-width card holding capped
   prose reads as a designed column inside a filled page, and every card on
   the page shares one left edge and one right edge, which is the thing that
   makes a dashboard look built rather than assembled. */

/* Running prose inside a card keeps the site measure. Anything that is
   SCANNED rather than READ (a ledger, a table, a chart, a row of tiles) is
   not prose and takes the card. */
.section > p,
.section > .section__note,
.section > ul,
.section > ol { max-inline-size: var(--mbx-measure); }

/* Objects that already carry their own surface. A chart in .chartbox inside a
   card is two borders 24px apart; a ledger list is its own bordered rows. */
.section:has(> .mbxlist) {
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
}

/* THE CHART SECTION KEEPS ITS CARD AND THE CHARTBOX GIVES UP ITS OWN. The
   other way round left the heading stranded on bare paper above a card, so
   "Calls a day" sat outside its chart while "Worth a look" sat inside its
   card: two sections on one page built differently for no reason a reader
   could see. The section is the surface everywhere now. */
.section > .chartbox {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
/* NOT .subs. A ledger list keeps its card: only .chartbox and .mbxlist carry a
   surface of their own, and stripping it from the sessions list left that one
   section orphaned on bare paper between two cards, which looked like
   something had failed to load. It keeps the width opt-out above, because a
   row of columns still wants the window. */

/* The gap between cards is smaller than the gap between bare blocks was: the
   border is doing the separating now, so 48 of air on top of it is a hole. */
.section + .section { margin-block-start: var(--mbx-space-6); }

/* ==========================================================================
   THE DECK: the page's card grid.

   A page of full-width cards stacked one under another is better than a
   narrow column, and still not a dashboard. Two short cards side by side is
   what makes a wide monitor look USED rather than merely filled, and it is
   the single thing the reference panel does that we did not.

   TWELVE COLUMNS, and a card says how many it takes. Twelve divides by 2, 3
   and 4, so a half, a third and a quarter all land on the same gutters and
   every card on the page shares its edges with the cards above and below it.
   That shared edge is most of what "designed" means here.

   align-items: START, never stretch. Two cards of genuinely different length
   stretched to match leaves the shorter one with a pool of empty card under
   its last line, which is the fault this whole pass is about.

   ONE BREAKPOINT, at 1200. Below it everything is a full-width stack: at 1024
   a half card is 460px, and a ledger with a leader in it does not work there.

   HALF IS THE DEFAULT ABOVE 1200, AND FULL WIDTH IS THE CLAIM. Reversed
   2026-08-27. Owner: *"The boxes shouldn't be full width. You could do some
   kind of masonry layout with boxes though? If 2 fit side by side, they
   should."*

   He is right and the old default was the bug. A card paired up only if
   somebody remembered to type `section--half`, so the failure mode was a
   column of short boxes with half a monitor of paper beside them, and it was
   silent: forgetting the modifier produced a page that looked deliberate.
   Turned around, forgetting `--full` produces a card that is too narrow for
   its content, which is visible immediately and gets fixed.

   IT IS MASONRY NOW, AND THE PRIMITIVE IS MULTI-COLUMN. Owner 2026-08-29,
   looking at the connector tab: *"This should not be full width. We should have
   masonry layout on all pages, where practical and appropriate"* and *"The
   layout (order) should avoid too much blank space as much as possible."*

   `grid-template-rows: masonry` still does not exist -- feature-detected in
   Chrome 148, along with `grid-template-columns: masonry`, `display: masonry`
   and `masonry-template-tracks`, all false. A row grid cannot do this: two
   cards of different height leave the shorter one with a hole under it until
   the next row starts, and no amount of pairing by hand fixes a page whose card
   heights depend on the customer's data.

   grid-auto-flow: dense DOES NOT FIX IT EITHER, which is worth stating because
   it is the obvious reach. Dense fills holes in the GRID, and the space under a
   short card is not a hole in a row grid - it is the inside of an occupied row.
   Dense only reorders; it would have bought the reordering cost and none of the
   space back.

   `columns` DOES, AND IT ANSWERS THE OBJECTION THAT USED TO BE HERE. The old
   note rejected masonry partly because reordering would leave the eye and the
   keyboard disagreeing: DOM order is tab order. Multi-column has no such
   problem. It lays out column-major - down the first column, then down the
   second - in DOM order, so what the eye follows and what Tab follows are the
   same sequence. Nothing is reordered at all; the columns are simply allowed to
   end at different points.

   `column-fill: balance` IS THE DEFAULT AND IS THE FEATURE. The browser aims
   for two columns of equal height, which is precisely "avoid too much blank
   space" expressed as a layout rule rather than as a thing somebody has to
   remember while writing markup.

   `column-span: all` IS HOW A FULL-WIDTH CARD STILL WORKS. A spanner splits the
   flow: everything before it balances above, everything after balances below.
   So the danger card at the foot of a tab is still full width and still last,
   and a table too wide to halve still says so with one class.
   ========================================================================== */
/* ONE COLUMN BELOW THE BREAKPOINT, AND IT IS AN OVERFLOW FIX RATHER THAN A
   TIDY-UP. Twelve tracks and eleven 32px gaps need 352px of grid before a
   single card is drawn. The content column at 390 is 342: the fr tracks floor
   at 0 as designed, the gaps do not, and the grid rendered 352 wide inside
   342. So every card on every deck page was 10px wider than its own column,
   which showed up as a 14px right gutter against a 24px left one on a phone.
   Measured 2026-08-28 on /billing/ and the identity tab; the page did not
   scroll sideways, which is why it survived a gate and a measuring pass.

   Below 1200 every child is `grid-column: 1 / -1` anyway, so the twelve tracks
   were buying nothing at the widths where they cost something. */
.deck {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--mbx-space-6);
  align-items: start;
}

/* Full width is the DEFAULT for anything in a deck, not just for sections: a
   deck also holds the odd bare paragraph (the overview's "nothing needs your
   attention" line), and an unplaced item would be auto-placed into a single
   1/12th column and rendered one word per line.

   Inside the deck the grid owns the spacing, so the stacking margin has to go
   or every card after the first is pushed out of its row. */
.deck > * { grid-column: 1 / -1; }
.deck > .section { margin-block-start: 0; }

@media (min-width: 75em) {                      /* 1200 */
  /* THE GRID IS TURNED OFF, NOT CONFIGURED. A multi-column container has to be
     a block box; leaving `display: grid` on and adding `columns` gets the grid
     and no columns at all, silently. */
  .deck {
    display: block;
    columns: 2;
    column-gap: var(--mbx-space-6);
  }

  /* A CARD IS NEVER SPLIT DOWN THE MIDDLE. Without this a section that does not
     fit in the remaining column height continues in the next one, which reads
     as two broken cards rather than as one tall card.

     THE MARGIN REPLACES THE GRID GAP. Columns space themselves horizontally and
     do nothing vertically, so the stacking margin that `.deck > .section` had
     removed comes back - on every child, because a deck also holds the odd bare
     paragraph. */
  .deck > * {
    break-inside: avoid;
    margin-block-end: var(--mbx-space-6);
  }

  /* FULL WIDTH IS A SPANNER, and it still ends the flow above it: cards before
     it balance across both columns, cards after it start a fresh pair. That is
     what keeps the danger card at the foot of a tab full width AND last.

     IT NEEDS ITS OWN TOP MARGIN. This was `margin-block-start: 0`, guessing
     that a spanner would inherit the space under the balanced block above it.
     It does not: the last card in a column contributes no height beyond its own
     box, so the spanner started at EXACTLY the pixel the tallest column ended
     - measured on the connection tab, card bottom 1200, spanner top 1200 - and
     two cards sat welded together with only their padding between them. Owner
     saw it on three separate screens before it was fixed.

     The bottom margin from `.deck > *` still applies, so a spanner followed by
     more cards is spaced on both sides. Not on the first child, where there is
     nothing above to be spaced from. */
  .deck > .section--full {
    column-span: all;
    margin-block-start: var(--mbx-space-6);
  }
  .deck > .section--full:first-child { margin-block-start: 0; }

  /* Kept because several screens still say it. In a masonry deck a card that is
     explicitly half is simply a card: the columns decide the width, and the
     class is now documentation rather than instruction.

     `--third` and `--twothird` had no callers when this changed and cannot be
     expressed in two balanced columns. They are deliberately gone rather than
     left as classes that quietly do nothing. */
  .deck > .section--half    { column-span: none; }
}

/* ==========================================================================
   THE SEGMENTED SWITCH

   TWO OR THREE OPTIONS, SHOWN AT ONCE, WITH ONE OF THEM ON. It replaced two
   different things on the same day and the pair is what makes the case for it:
   a sentence under a port field explaining which number means which kind of
   TLS, and a `<details>` disclosure hiding the HTML half of a signature. Both
   were the interface describing a choice instead of offering one. Owner
   2026-08-29: "Would a switcher make more sense? Less text and simpler."

   IT IS RADIOS, NOT BUTTONS, and that is the whole reason it can be used for
   both. It posts a real value with no JavaScript, it is arrow-keyable as one
   control because the browser groups radios by name, and it survives a
   `data-async` swap replacing the markup underneath it.

   THE INPUT LIVES INSIDE THE LABEL, so the on state is `:checked + .face` and
   the component needs to know nothing about what it is wrapped in. Panels
   elsewhere on the page switch off the same radios through `:has()`.

   NOT `display: none` ON THE INPUT, which takes it out of the tab order and off
   a keyboard entirely. Clipped instead, so focus still lands on it and the
   focus ring is drawn on the face.
   ========================================================================== */

.segmented {
  /* THE GEOMETRY, DECLARED ONCE. Everything below is derived from these three,
     so a variant that changes its padding (--static does) carries the sliding
     chip with it rather than leaving it a pixel out. */
  --seg-n: 2;
  --seg-pad: 3px;
  --seg-gap: 2px;

  position: relative;
  isolation: isolate;
  /* GRID, NOT FLEX, SO THE HALVES ARE EQUAL. A chip that slides has to travel a
     known distance, and content-sized options make that distance depend on the
     words: "TLS" and "STARTTLS" are nothing like the same width. Equal tracks
     make the move exactly one track, whatever the labels say. */
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--seg-gap);
  padding: var(--seg-pad);
  background: var(--mbx-bg-sunken);
  border: 1px solid var(--mbx-border);
  border-radius: var(--mbx-radius-pill);
}

/* The count cannot be an inline style: `style-src 'self'` blocks style
   attributes, which is deliberate. See mbx_switch(). */
.segmented[data-count="3"] { --seg-n: 3; }

.segmented__opt { display: block; cursor: pointer; }

.segmented__in {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.segmented__face {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--mbx-space-2);
  padding: var(--mbx-space-2) var(--mbx-space-4);
  border-radius: var(--mbx-radius-pill);
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  /* 600 ON EVERY FACE, INCLUDING THE OFF ONE. Weighting only the on face
     changes the width of the widest label, and with equal tracks that resizes
     the whole control on every press: the thing jumps as it slides. The state
     is carried by the chip and the ink colour, neither of which costs layout. */
  font-weight: 600;
  color: var(--mbx-ink-muted);
  white-space: nowrap;
  transition: color 0.16s ease;
}

/* THE CHIP THAT MOVES, AND IT NOW ACTUALLY MOVES. Owner 2026-08-29, looking at
   the TLS switch: "they should slide left and right." It used to be painted on
   the checked face, so the raised surface vanished from one option and appeared
   on the other with nothing in between. The state changed; the control never
   showed the change happening, which is the entire affordance of a switch.

   ONE ELEMENT FOR THE WHOLE CONTROL rather than a background per face, because
   only a single element can be animated from one place to another.

   z-index: -1 UNDER isolation: isolate. The chip sits above the sunken track
   and below the label, and the isolation keeps that -1 inside this control
   instead of sending it behind the card it is standing on. */
.segmented::before {
  content: "";
  position: absolute;
  z-index: -1;
  /* Absolute insets resolve against the padding box, so `--seg-pad` here lands
     the chip exactly on the content box: the rectangle one face occupies. */
  inset-block: var(--seg-pad);
  inset-inline-start: var(--seg-pad);
  inline-size: calc(
    (100% - var(--seg-pad) * 2 - (var(--seg-n) - 1) * var(--seg-gap)) / var(--seg-n)
  );
  border-radius: var(--mbx-radius-pill);
  background: var(--mbx-bg-raised);
  box-shadow: 0 1px 2px rgb(15 23 42 / 0.10);
  /* Off quickly, settling slowly. Linear reads mechanical at this size. */
  transition: translate 0.24s cubic-bezier(0.22, 1, 0.36, 1);
}

/* WHERE THE CHIP GOES, ASKED OF THE CONTROL AND ANSWERED BY THE RADIO. The
   state already lives in the input. A script mirroring it into a class would be
   a second copy of the same fact, and the two would disagree the first time the
   markup was re-rendered underneath it by a `data-async` swap. */
.segmented:has(.segmented__opt:nth-child(2) .segmented__in:checked)::before {
  translate: calc(100% + var(--seg-gap)) 0;
}
.segmented:has(.segmented__opt:nth-child(3) .segmented__in:checked)::before {
  translate: calc(200% + var(--seg-gap) * 2) 0;
}

.segmented__in:checked + .segmented__face { color: var(--mbx-ink); }

@media (prefers-reduced-motion: reduce) {
  .segmented::before { transition: none; }
}

/* WITHOUT :has() THE CHIP CANNOT KNOW WHERE TO STAND, and a switch stuck on its
   first option is worse than one that never appeared. Engines without it get
   what this looked like before: no chip, and the face paints its own. */
@supports not selector(:has(*)) {
  .segmented::before { content: none; }
  .segmented__in:checked + .segmented__face {
    background: var(--mbx-bg-raised);
    box-shadow: 0 1px 2px rgb(15 23 42 / 0.10);
  }
}

.segmented__opt:hover .segmented__face { color: var(--mbx-ink); }

/* ON THE FACE, NOT ON THE INPUT, because the input is a clipped pixel in the
   corner and a focus ring drawn on it would appear nowhere near the control. */
.segmented__in:focus-visible + .segmented__face {
  outline: 2px solid var(--mbx-accent);
  outline-offset: 2px;
}

/* Something is in the version this option names. See signature-editor.php: the
   fault it exists for is a filled HTML signature nobody could see. */
.segmented__dot {
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--mbx-accent);
}

/* A switch behaves as a field in a form row: label above, control below. */
.field--switch { display: flex; flex-direction: column; gap: var(--mbx-space-2); }
.field--switch .segmented { align-self: start; }

/* A SWITCH THAT CANNOT BE SWITCHED. Owner 2026-08-29 on the plan column: "It's
   just a display thing, they can't switch/toggle it." So it is spans rather
   than inputs, and it must not invite a press: no pointer cursor, no hover
   change, and the whole thing is a shade quieter than the live control.

   BOTH WORDS, ONE LIT, which is what a lone "Pro" chip could not do. On the row
   of a Free mailbox the useful question is not only what it is on but what else
   there is, and the answer sits next to the Upgrade that acts on it. */
.segmented--static { --seg-pad: 2px; }
.segmented--static .segmented__face {
  padding: var(--mbx-space-1) var(--mbx-space-3);
  font-size: var(--pnl-text-micro);
  line-height: var(--pnl-lh-micro);
  letter-spacing: var(--mbx-track-eyebrow);
  text-transform: uppercase;
}
/* THE STATE COMES OFF THE ATTRIBUTE, NOT OFF A RADIO, because there is no radio
   here to ask. It is the same chip in the same place, and it never animates
   because it never changes without a page load. */
.segmented--static[data-plan="pro"]::before { translate: calc(100% + var(--seg-gap)) 0; }
.segmented--static .segmented__face.is-on { color: var(--mbx-ink); }

@supports not selector(:has(*)) {
  .segmented--static .segmented__face.is-on {
    background: var(--mbx-bg-raised);
    box-shadow: 0 1px 2px rgb(15 23 42 / 0.10);
  }
}

/* A FIELD THAT CHANGED WITHOUT BEING TOUCHED HAS TO SAY SO. Moving the TLS
   switch rewrites the port beside it, and a number that silently becomes a
   different number is the interface doing something behind the customer's back:
   they look away, look back, and the form no longer says what they typed.

   The tint fades out rather than in, so the eye is caught by the change and
   then left with a plain field. See assets/js/connection.js, which adds the
   class and removes it on animationend. */
@keyframes mbx-synced {
  from { background-color: var(--mbx-accent-soft); border-color: var(--mbx-accent); }
  to   { background-color: var(--mbx-bg-raised);   border-color: var(--mbx-border-control); }
}

/* Not disabled under reduced motion, deliberately: nothing here moves. It is a
   colour settling once over most of a second, which is neither a flash nor a
   translation, and removing it would take away the only signal that the number
   changed. */
.input.is-synced { animation: mbx-synced 900ms ease-out; }

/* The plan cell: the state, then whatever can be done about it. */
.planstate { vertical-align: middle; }
/* BOTH MARGINS, AND THE INLINE ONE IS THE ONE THAT WAS MISSING. Owner
   2026-08-30 on the mailbox list, "Gap missing?": on a Free row the Upgrade
   button sits BESIDE the Free/Pro switch rather than under it, and only a
   block-start margin was set - so the two touched. The block margin still does
   its job wherever the cell is narrow enough to wrap them. */
.planstate__act {
  margin-block-start: var(--mbx-space-2);
  margin-inline-start: var(--mbx-space-3);
  display: inline-flex;
}

/* ==========================================================================
   THE TILE ROW: the four figures a page opens on.

   A dashboard should answer "how are things" before it is read. A row of
   figures across the top does that in one glance and gives the page a strong
   horizontal opening, which is what our pages were missing: they all began
   with a card of prose, so the first thing on a control panel was reading.

   NO ICON SQUARES. The reference panel puts a pastel-tinted pictogram beside
   each figure and it is the most templated thing on its page: four icons that
   have to be drawn, kept and explained, telling the reader nothing the label
   below does not. Our register is typographic (app.css says so about the nav)
   and the figure itself is the graphic. It gets the display face at 36/40,
   which the type scale already reserves for exactly this.

   THE LABEL IS AN EYEBROW ABOVE, NOT A CAPTION BELOW. Above, the eye reads
   label-then-figure and lands on the figure last, which is where it should
   rest. Below, it reads figure-then-label and has to travel back up.
   ========================================================================== */
/* 9.5rem, NOT 11rem, AND THE DIFFERENCE IS ENTIRELY ABOUT THE PHONE. At 11rem
   a 390px screen fits exactly one tile per row, so four of them are 1,000px of
   summary to scroll past before reaching the thing the page is actually for.
   At 9.5rem two fit (2 x 152 + 16 gap = 320, inside the 342 available) and the
   row is 500px instead.

   Nothing changes above that. auto-fit collapses the tracks it cannot fill, so
   four tiles in a 1,112px column are still four equal columns; the smaller
   minimum only changes where the row is allowed to break. Both pages that use
   this carry exactly four. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: var(--mbx-space-4);
  margin-block-end: var(--mbx-space-6);
}

.tile {
  padding: var(--pnl-card-pad);
  background: var(--mbx-bg-raised);
  border: 1px solid var(--mbx-border);
  border-radius: var(--mbx-radius-md);
  box-shadow: var(--mbx-shadow-1);
  /* Every tile the same height whatever its note says, because a row of
     figures with a ragged bottom edge is the row not reading as a row. */
  block-size: 100%;
  display: flex;
  flex-direction: column;
}

.tile__label {
  font-family: var(--mbx-font-mono);
  font-size: var(--pnl-text-micro);
  line-height: var(--pnl-lh-micro);
  letter-spacing: var(--mbx-track-eyebrow);
  text-transform: uppercase;
  color: var(--mbx-ink-muted);
}

.tile__figure {
  font-family: var(--mbx-font-display);
  font-weight: 700;
  font-size: var(--pnl-text-display);
  line-height: var(--pnl-lh-display);
  letter-spacing: var(--mbx-track-display);
  font-variant-numeric: tabular-nums;
  color: var(--mbx-ink);
  margin-block-start: var(--mbx-space-2);
}

/* Pushed to the foot so it sits on one line across the row even when one
   tile's figure is a word and another's is a number. */
.tile__note {
  margin-block-start: auto;
  padding-block-start: var(--mbx-space-2);
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  color: var(--mbx-ink-muted);
}

/* A tile that is reporting a problem earns colour on the FIGURE only. The
   whole tile tinted red reads as an error state for the panel rather than a
   count of something that needs attention. */
.tile--attention .tile__figure { color: var(--mbx-danger-ink); }
.tile--good .tile__figure      { color: var(--mbx-success-ink); }

/* THE SECTION'S OWN VERTICAL RHYTHM. Under text-box-trim neither a paragraph
   nor the paragraph after it carries any margin at all, so a block of prose
   followed by a button rendered with the text running behind the button. A
   trimmed scope has to state its spacing; nothing is inherited. */
.section > * + * { margin-block-start: var(--mbx-space-4); }
.section > * + .callout,
.section > .callout + * { margin-block-start: var(--mbx-space-5); }

/* And whatever follows the last section. A "back to" link after a block of
   sections is in normal flow with no margin of its own, so under trim it sat
   on the caps of the button above it. */
.section + .auth__alt,
.section + p { margin-block-start: var(--mbx-space-6); }
/* THE NOTE FOLLOWS THE TITLE; ONLY AN ACTION GOES TO THE FAR EDGE.
   This was `justify-content: space-between` for everything, which was fine
   while the content column was capped at 1080px and became wrong the moment it
   was not: on a 1920 monitor "Pick the one you use" ended up a foot and a half
   from the heading it annotates, reading as an unrelated fragment.

   The distinction is the element. A `span` is an annotation and belongs beside
   the words it qualifies. An `a` is a header action, and a header action at the
   far edge is a convention worth keeping, because on the full-width screens it
   lines up with the right edge of the table it sits above. */
/* align-items: CENTER, not baseline. A 32px button baseline-aligned to a 22px
   heading hangs below the heading's box and sits low in the row; centred, the
   two read as one line. Baseline is right for two runs of text and wrong the
   moment a control joins them. */
.section__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--mbx-space-3) var(--mbx-space-4);
  margin-block-end: var(--mbx-space-4);
}
.section__head > a.section__note,
.section__head > .section__action { margin-inline-start: auto; }
/* A standfirst under a section title needs a real gap for the same reason.
   With none, the title's descenders sit on the caps of the line below it. */
.section__title + .hint,
.section__title + .prose { margin-block-start: var(--mbx-space-3); }

/* Matches the h2 step. See the scale note in main.css: the display face is
   for the wordmark, the sign-in screen and a tile figure, not for every
   heading in a panel. */
.section__title {
  font-weight: 600;
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  letter-spacing: var(--mbx-track-heading);
}
.section__note {
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  color: var(--mbx-ink-soft);
}

/* ---- The card-title mark ------------------------------------------------
   Owner 2026-08-30: "card titles would benefit from appropriate icons. Would
   really elevate the design."

   THE SAME ARGUMENT AS THE NAV RAIL, ONE LEVEL UP. icons.php records why the
   rail got icons: five bare words are quiet, and quiet is not clear - the mark
   is what lets somebody find Billing without reading two other words first. A
   deck of cards has the same problem and worse, because a card title is one
   line of 16px semibold on a white field and there are five of them down a
   page. The mark gives each card a fixed point the eye can return to.

   IT MUST NOT BECOME DECORATION, which is the way this goes wrong. Every mark
   is chosen for the card's subject and no two cards on one screen may share
   one: the reading list gets a book and the contact card gets a speech bubble
   rather than a second envelope, because Mailboxes already spends that mark in
   the rail and one glyph meaning two things is worse than no glyph.

   SMALLER AND QUIETER THAN THE .wayfind TILE. That one is a row's primary
   affordance and carries accent ink on a tinted square. This sits beside a
   heading and must not outweigh it: no tile, muted ink, and it inherits the
   heading's own optical size. A card title that loses to its own icon is a
   card title nobody reads. */
.section__icon {
  flex: none;
  inline-size: 1.125rem;
  block-size: 1.125rem;
  color: var(--mbx-accent);
}

/* The product's own mark, where a card is about us rather than about a screen.
   Slightly larger than .section__icon because it is a filled two-colour mark
   rather than a 1.5px stroke and reads lighter at the same size. */
.section__logo {
  flex: none;
  inline-size: 1.375rem;
  block-size: 1.375rem;
  display: block;
}

/* A WORDMARK IS SIZED BY HEIGHT. .section__logo above is a square mark and sets
   both axes, which would scale a 2.7:1 wordmark on one axis and squash it - the
   one thing a logo must never have done to it. */
.section__wordmark {
  flex: none;
  block-size: 1.375rem;
  inline-size: auto;
  display: block;
}

.section__wordmark-link { flex: none; display: block; line-height: 0; }

/* The head is a flex row already, so the mark simply becomes its first item.
   The gap is tighter than the row's default because the mark belongs TO the
   title rather than sitting beside it as a separate thing. */
.section__head > :is(.section__icon, .section__logo) + .section__title { margin-inline-start: calc(var(--mbx-space-4) * -1 + var(--mbx-space-2)); }

.card {
  padding: var(--pnl-card-pad);
  background: var(--mbx-bg-raised);
  border: 1px solid var(--mbx-border);
  border-radius: var(--mbx-radius-md);
  box-shadow: var(--mbx-shadow-1);
}

/* ==========================================================================
   The quota figure

   THE ONLY PLACE A CEILING IS STYLED, matching mbx_quota() being the only
   place one is rendered. The figure is Martian Mono with tabular numerals so a
   column of them lines up digit under digit, and THE WINDOW LABEL SITS ON ITS
   OWN LINE beneath it.

   That second part is not layout preference. "56 / 1,000 last 24 hours" as one
   run reads as a sentence and the eye slides over the qualifier; on its own
   line, "last 24 hours" is unmissable, and being unmissable is the entire job
   of that phrase. It is what stops a customer reading the meter as the calendar
   day and concluding the product is broken when it disagrees with the chart.
   ========================================================================== */

.quota {
  font-family: var(--mbx-font-mono);
  font-size: var(--pnl-text-mono);
  line-height: var(--pnl-lh-mono);
  letter-spacing: var(--mbx-track-mono);
  font-variant-numeric: tabular-nums;
  color: var(--mbx-ink-muted);
}
.quota__n { color: var(--mbx-ink); font-weight: 400; }
.quota__win {
  display: block;
  font-family: var(--mbx-font-body);
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  letter-spacing: 0;
  color: var(--mbx-ink-soft);
}

/* ==========================================================================
   The call log, and every table built like it

   MOVED HERE FROM pages/charts.css, because a second page started using it.
   The activity log and the invoice list are the same object: a table of rows
   compared cell by cell down a column, with a scroller round it and a phone
   layout that turns each row into three lines. A component two unrelated
   pages need is not a page's component.
   ========================================================================== */

/* The log scrolls INSIDE its own container rather than pushing the page
   sideways. A table is the one thing on this panel that legitimately wants more
   width than the column has. */
.logwrap { overflow-x: auto; }
/* A region that scrolls has to be reachable by keyboard, and a div is not. */
.logwrap:focus-visible { outline-offset: -2px; }

.log { inline-size: 100%; border-collapse: collapse; font-size: var(--pnl-text-sm); line-height: var(--pnl-lh-sm); }
.log th, .log td { text-align: start; padding: var(--mbx-space-2) var(--mbx-space-3); border-block-end: 1px solid var(--mbx-border); white-space: nowrap; }
.log thead th {
  font-family: var(--mbx-font-mono);
  font-size: var(--pnl-text-micro);
  line-height: var(--pnl-lh-micro);
  letter-spacing: var(--mbx-track-eyebrow);
  text-transform: uppercase;
  color: var(--mbx-ink-soft);
  font-weight: 400;
}
.log__ms, .log__src { text-align: end; font-variant-numeric: tabular-nums; }
.log__src { color: var(--mbx-ink-soft); }

/* ---- The two-line time cell ----------------------------------------------
   A PRIMARY LINE AND THE FACT UNDER IT, which is the pattern that makes a
   seven-column table readable where seven columns of the same weight are a
   grey wall. The relative phrase is what somebody asks of the rows they can
   see; the timestamp is what they need when they are reconciling against
   something else.

   The row grows from 36px to 44px and the table gets SHORTER to read, because
   the eye now has a shape to follow down the column instead of a uniform
   ribbon. */
.log__when { color: var(--mbx-ink); }
.log__time { display: block; }
.log__ago { display: block; }
.log__stamp {
  display: block;
  font-size: var(--pnl-text-micro);
  line-height: var(--pnl-lh-micro);
  color: var(--mbx-ink-soft);
  font-variant-numeric: tabular-nums;
}

/* ---- What a partial outcome had to say -----------------------------------
   Under the chip rather than beside it, because it is a sentence and a
   sentence in a table column is a sentence that wraps into a ribbon. Only
   partial rows have one, so the column keeps its width on every other row.

   NOT nowrap. The rest of this table refuses to wrap because every cell is a
   token -- a time, a tool name, a duration. This one is prose and has to be
   allowed to break, so the cell is the one place white-space is reset. */
.log__note {
  display: block;
  max-inline-size: 22rem;
  margin-block-start: var(--mbx-space-1);
  font-size: var(--pnl-text-micro);
  line-height: var(--pnl-lh-micro);
  color: var(--mbx-ink-muted);
  white-space: normal;
  text-wrap: pretty;
}

/* The same scale of consequence as the tool mix, so a category means the same
   thing wherever it appears. */
.log__tool--read        { color: var(--mbx-ink-muted); }
.log__tool--write       { color: var(--mbx-ink); font-weight: 600; }
.log__tool--destructive { color: var(--mbx-accent-ink); font-weight: 600; }

/* ---- The log on a phone --------------------------------------------------
   A CONSIDERED OVERFLOW, NOT A SCROLLBAR (docs/information-architecture.md).
   Six columns do not fit in 390px and never will: left as a table it shows
   two and a half of them and hides the outcome, which is the column somebody
   scanning an audit trail is actually looking for. Below 46rem each row
   becomes three lines of its own.

   THE CELLS KEEP THEIR SEMANTICS. `display: block` on a `td` drops it out of
   the table box model but not out of the accessibility tree, so a screen
   reader still reads the row against its headers. The visible headers go,
   because the values are self-describing: a time, a tool name, a word in a
   chip, a duration, an address.
   -------------------------------------------------------------------------- */
@media (max-width: 46rem) {
  /* SCOPED AWAY FROM .log--admin, and the scoping is the fix rather than a
     preference. The rules below place each cell on a named grid row, which
     works because the call log has one cell of each class. The administrative
     tables have TWO .log__when cells - signed up, and last seen - so both
     landed on grid-row 2 and printed on top of each other. Seen at 390px on
     2026-08-29.

     Those tables scroll sideways instead, which is what .logwrap is for and is
     the honest answer for seven columns on a phone: there is no stacking of an
     account, a date, three numbers and two chips that is quicker to read than
     the table itself. */
  .log:not(.log--admin),
  .log:not(.log--admin) tbody,
  .log:not(.log--admin) tr,
  .log:not(.log--admin) td { display: block; }
  .log:not(.log--admin) thead {
    position: absolute;
    inline-size: 1px; block-size: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .log:not(.log--admin) tr {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0 var(--mbx-space-3);
    padding-block: var(--mbx-space-3);
    border-block-end: 1px solid var(--mbx-border);
  }
  .log:not(.log--admin) td { padding: 0; border: 0; white-space: normal; overflow-wrap: anywhere; }

  /* Line one: what it did, and whether it worked. */
  .log__tool { grid-column: 1; grid-row: 1; font-size: var(--pnl-text-mono); }
  .log__outcome { grid-column: 2; grid-row: 1 / span 3; align-self: start; }

  /* Line two: when, and on which mailbox. */
  .log__when { grid-column: 1; grid-row: 2; }
  .log__mbx  { grid-column: 1; grid-row: 3; color: var(--mbx-ink-muted); }

  /* Line three: how long it took, and where from. */
  .log__ms   { grid-column: 1; grid-row: 4; text-align: start; }
  .log__src  { grid-column: 2; grid-row: 4; text-align: end; }
}

.visually-hidden {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ==========================================================================
   The toggle. The W3Schools pattern from the global house rules: the checkbox
   is hidden with opacity rather than display:none, so it stays focusable and
   announced, and the visible switch is a sibling driven by :checked.
   ========================================================================== */

/* MOVED HERE FROM pages/mailbox.css, because the alert preferences started
   using it. A component two unrelated pages need is not a page's component. */

.toggle-row {
  display: flex;
  align-items: center;
  gap: var(--mbx-space-4);
  margin-block-end: var(--mbx-space-4);
}

.switch {
  position: relative;
  display: inline-block;
  inline-size: 3.25rem;                          /* 52 */
  block-size: 2rem;                              /* 32, clears the target min */
  flex: none;
}
.switch input { opacity: 0; inline-size: 0; block-size: 0; }

.slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: var(--mbx-c-line-strong);
  transition: background-color var(--mbx-dur-base) var(--mbx-ease);
}
.slider::before {
  position: absolute;
  content: "";
  block-size: 1.5rem;                            /* 24 */
  inline-size: 1.5rem;
  inset-inline-start: 0.25rem;
  inset-block-end: 0.25rem;
  background: var(--mbx-c-white);
  box-shadow: var(--mbx-shadow-1);
  transition: transform var(--mbx-dur-base) var(--mbx-ease);
}
.switch input:checked + .slider { background: var(--mbx-accent); }
.switch input:checked + .slider::before { transform: translateX(1.25rem); }
.switch input:focus-visible + .slider {
  outline: var(--mbx-focus-width) solid var(--mbx-focus);
  outline-offset: var(--mbx-focus-offset);
}

.slider.round { border-radius: var(--mbx-radius-pill); }
.slider.round::before { border-radius: 50%; }

/* Same reasoning. This is a full sentence describing what the switch does, and
   it wraps, so it is read rather than scanned and takes the prose step. */
.toggle-label {
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  max-inline-size: var(--mbx-measure);
}

/* ==========================================================================
   The copy field
   ========================================================================== */

.connector { display: grid; gap: var(--mbx-space-5); max-inline-size: 40rem; }

.copyfield { display: flex; flex-direction: column; gap: var(--mbx-space-2); }

.copyfield__row {
  display: flex;
  gap: var(--mbx-space-2);
  align-items: stretch;
}

/* min-inline-size: 0 so a long value shrinks the input rather than pushing the
   buttons off the end. A flex item's default min-width is its content, which
   for a 200-character config snippet is a very long way. */
.copyfield__input {
  flex: 1;
  min-inline-size: 0;
  background: var(--mbx-bg-sunken);
  font-size: var(--pnl-text-mono);
  letter-spacing: var(--mbx-track-mono);
}
/* readonly, not disabled: still focusable, still selectable, still announced.
   The cursor says it is not editable without taking away the ability to select
   it, which is the fallback when there is no clipboard API. */
.copyfield__input:read-only { cursor: text; }

.copyfield__copy.is-done {
  background: var(--mbx-success-bg);
  border-color: var(--mbx-success-200);
  color: var(--mbx-success-ink);
}

/* A multi-line value (a config snippet) gets a textarea's shape without being
   one: the input is single-line by nature, so long values scroll horizontally
   inside it, which is correct for something nobody reads and everybody copies. */
.copyfield__input { text-overflow: ellipsis; }

/* MOVED HERE FROM pages/mailbox.css, because two-factor enrolment started using
   it for the setup key and the recovery codes. Settings does not load the
   mailbox stylesheet, so on that page the row had no flex at all and the Copy
   button dropped underneath its own input.

   CAPPED HERE RATHER THAN BY WHOEVER CONTAINS IT. On the connector tab it sat
   inside .connector, which caps at the same 40rem, so this changes nothing
   there and stops a setup key running the full width of a 1900px column
   somewhere else.

   THE GAP UNDER THE HINT IS DOUBLED, and that is the text-box-trim rule again:
   trimming removes the half-leading but descenders still render below the
   trimmed box, so 8px of declared gap left the "yp" of "Type" sitting on the
   input's top border. Chosen by looking at it, because every numeric check
   passes the value that is visibly wrong. */
.copyfield { max-inline-size: 40rem; }
.copyfield .hint + .copyfield__row { margin-block-start: var(--mbx-space-2); }

/* ==========================================================================
   Disclosure lists, numbered procedures, and marked lists

   MOVED HERE FROM pages/mailbox.css on 2026-08-29, unchanged. They were written
   for the connector tab's client instructions and are three general components:
   an accessible `<details>` list, an ordered procedure, and a list whose marker
   is a short rule rather than a bullet. The help page needs all three, and a
   component in a page stylesheet is one the next screen either cannot have or
   has to load an unrelated page's CSS to get.
   ========================================================================== */

.clients { display: grid; gap: var(--mbx-space-3); max-inline-size: 48rem; }

.client {
  border: 1px solid var(--mbx-border);
  border-radius: var(--mbx-radius-md);
  background: var(--mbx-bg-raised);
}

.client__summary {
  min-block-size: var(--pnl-row-h);             /* 48 */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mbx-space-1) var(--mbx-space-3);
  padding: var(--mbx-space-3) var(--mbx-space-5);
  cursor: pointer;
  list-style: none;
}
.client__summary::-webkit-details-marker { display: none; }

/* The caret, drawn rather than typed, so it rotates with the state and needs
   no second glyph. */
.client__summary::after {
  content: "";
  margin-inline-start: auto;
  inline-size: 7px;
  block-size: 7px;
  border-inline-end: 1.5px solid var(--mbx-ink-soft);
  border-block-end: 1.5px solid var(--mbx-ink-soft);
  rotate: 45deg;
  translate: 0 -2px;
  transition: rotate var(--mbx-dur-fast) var(--mbx-ease);
  flex: none;
}
.client[open] .client__summary::after { rotate: 225deg; translate: 0 2px; }

.client__summary:focus-visible {
  outline: var(--mbx-focus-width) solid var(--mbx-focus);
  outline-offset: calc(var(--mbx-focus-offset) * -1);
  border-radius: var(--mbx-radius-md);
}

/* Body leading, because this wraps: "Cursor, VS Code, Zed, or anything else
   that speaks MCP" is a sentence, not a one-word label. */
.client__name { font-weight: 600; font-size: var(--pnl-text-ui); line-height: var(--pnl-lh-body); }
.client__what { font-size: var(--pnl-text-sm); line-height: var(--pnl-lh-sm); color: var(--mbx-ink-soft); }

.client__body {
  padding: 0 var(--mbx-space-5) var(--mbx-space-5);
  border-block-start: 1px solid var(--mbx-border);
  padding-block-start: var(--mbx-space-4);
}
.client__body > * + * { margin-block-start: var(--mbx-space-4); }

/* A callout's bottom margin is for separating it from what follows. As the last
   thing in a disclosure there is nothing following, and it renders as 48px of
   empty panel under the answer. Same rule `.section` already carries, for the
   same reason. */
.client__body > :last-child { margin-block-end: 0; }

/* PROSE LEADING, because these wrap and are READ. The panel's default 15/20 is
   the UI size, derived for a label, a cell or a button: one line, scanned in a
   pass. A sentence that runs to two or three lines at that ratio is tight
   enough that the eye starts losing the return sweep, which is exactly what the
   grid checker's role banding is for and exactly what it caught here. */
.client__body p {
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  max-inline-size: var(--mbx-measure);
}

/* A numbered procedure. The marker is the browser's, because a hand-drawn one
   loses the automatic numbering that makes a step list a step list.
 *
 * `ol.steps-list`, NOT `.steps-list`, AND THE TYPE SELECTOR IS LOAD-BEARING.
 * main.css resets `ul[class], ol[class] { list-style: none }`, which is (0,1,1)
 * and beats a bare class - so this rule lost and every step list in the panel
 * rendered with no numbers at all. The connector tab's "Open Settings, then
 * Connectors" procedure has been a set of unnumbered paragraphs since it
 * shipped. Matching the reset's specificity and coming later in the cascade is
 * what wins it back; `!important` would work too and would be a worse habit to
 * leave lying about in a component file.
 */
ol.steps-list {
  list-style: decimal;
  padding-inline-start: var(--mbx-space-5);
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  max-inline-size: var(--mbx-measure);
}
ol.steps-list li + li { margin-block-start: var(--mbx-space-2); }

/* THE WIDE VARIANT, for a steps list inside a .section--full. Same rule as
   .splitcols: the card spans the page, `max-inline-size` above caps the list at
   the site measure, and the result is a numbered column with an empty half
   beside it.

   Two columns rather than an uncapped one, because a step set 1250px wide is
   as unreadable as any other line that long. The cap is lifted only because
   the column has taken over bounding it.

   THE LIST-ITEM PADDING GOES BACK ON EACH COLUMN. `padding-inline-start` on the
   <ol> indents the whole grid, which puts the second column's marker in the
   middle of the card rather than at its column's edge. */
@media (min-width: 60em) {
  ol.steps-list--wide {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--mbx-space-7);
    max-inline-size: none;
    padding-inline-start: 0;
    align-items: start;
  }
  ol.steps-list--wide > li { margin-inline-start: var(--mbx-space-5); }
  ol.steps-list--wide li + li { margin-block-start: 0; }
}

/* A list whose marker is a short accent rule rather than a bullet.
 *
 * The rule reads as an enumeration without the visual weight of a disc, which
 * matters where every item is a full sentence rather than a word. `list-style:
 * none` is safe here because these are read as prose; a list that needed to be
 * ANNOUNCED as a list would need a role back.
 *
 * THE TOP MARGIN IS SET ON THE LIST ITSELF and not left to a `> * + *` rule on
 * whatever contains it. Both selectors are one class deep, so the later one
 * wins on source order - and a bare `margin: 0` on the list was later, which put
 * the first item level with the label above it.
 */
.marklist {
  display: grid;
  gap: var(--mbx-space-2);
  margin: var(--mbx-space-3) 0 0;
  padding: 0;
  list-style: none;
}

.marklist li {
  position: relative;
  padding-inline-start: var(--mbx-space-5);
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  max-inline-size: var(--mbx-measure);
}

.marklist li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.6em;
  inline-size: 0.75rem;
  block-size: 2px;
  border-radius: 1px;
  background: var(--mbx-accent);
}

/* THE LAST ITEM IS OFTEN THE ONE PEOPLE WANT AND NEVER GET: what a thing does
   NOT do. Where that is how the list is written, `--last` marks it out by the
   colour of its rule rather than by a second list, so it reads as the end of the
   same sentence rather than as a warning. */
.marklist--last li:last-child::before { background: var(--mbx-border-control); }

/* ==========================================================================
   Split columns inside a full-width card

   THE RULE THIS EXISTS FOR. Owner 2026-08-30: "Any box that is not width
   restricted should not width restrict its content - it looks stupid." A
   `.section--full` spans about 1250px; `.section > p` caps prose at the site
   measure, roughly 65ch; so a spanning card holding two paragraphs drew a
   border round an empty right-hand half.

   UNCAPPING THE TEXT IS THE WRONG FIX and is worth saying so here, because it
   is the obvious one. A paragraph set 1250px wide is unreadable - the measure
   is not decoration, it is what stops the eye losing its place on the return
   sweep. The card has to be given something to DO with the width instead.

   So: two columns, each holding text at its own natural measure, and the cap
   lifted only inside them because the column is now the thing bounding the
   line. Below 60em it is one column and the cap comes back, because at that
   width the card is not wide enough to have the problem.
   ========================================================================== */

.splitcols { display: grid; gap: var(--mbx-space-5); }

/* THE SPACING RULE THE COLUMNS BROKE, PUT BACK. Card children are spaced by
   `.section > * + *`, which is a DIRECT-child selector - so wrapping two
   paragraphs in a column div silently removed every gap between them and the
   first render had the address overlapping the line beneath it. Anything that
   nests content one level deeper inside a card has to restate this; .callout
   and .client__body both already do, which is the pattern rather than a
   workaround. */
/* EVERY LEVEL, NOT JUST THE FIRST. `.splitcols > * > * + *` was written for a
   column holding paragraphs directly, and the moment the contact card grew a
   wrapper so its two blocks could stack, the paragraphs inside those blocks
   dropped out of reach of it and overlapped - the SAME overlap this rule was
   added to fix, one nesting level down.
   
   A depth-counting selector is the wrong shape for the problem. `.splitcols *`
   spaces adjacent siblings at any depth, which is what "restate the card's own
   flow rule inside this component" actually means. */
.splitcols * + * { margin-block-start: var(--mbx-space-4); }

/* The two stacked blocks in a column want more air between them than the
   paragraphs inside each block do, or the card reads as one long run. */
.contact__text > * + * { margin-block-start: var(--mbx-space-6); }

/* Grid gaps already space these; a margin on top of a gap doubles it. */
.splitcols > *,
.checklist li,
.checklist li > * { margin-block-start: 0; }

/* The label belongs to the block under it, so it sits tighter than the
   general step above. */
.splitcols__label + * { margin-block-start: var(--mbx-space-2); }

/* THE ONE ACTION ON THE CARD, SIZED LIKE ONE. It was the opening words of a
   sentence, which is where a support address goes to be skimmed past. */
/* 28px, NOT 24. At 18px a 24px line is a ratio of 1.33 and the typography gate
   wants 1.35-1.80 on prose - a legal-looking number that reads tight, which is
   the whole reason that check measures ratios per role rather than per element.
   28 is the next step on the 4px grid and lands at 1.56. */
.contact__address {
  font-size: var(--pnl-text-lg);
  line-height: 1.75rem;
  font-weight: 600;
}
.contact__address a { text-decoration-thickness: 1px; text-underline-offset: 3px; }

/* FIFTY-FIFTY, ART LEFT. Owner 2026-08-30: "just 2 columns with 50:50 split",
   with the robot in the left one. The earlier build put the art in a third
   column between the two text blocks, where it read as a divider rather than as
   the thing the card leads with. `--half` is the default two-track rule anyway;
   it is named so the intent is on the element rather than implied by an
   absence. */
.splitcols--half { align-items: center; }

.contact__art { display: flex; justify-content: center; align-items: center; }

/* The two text blocks stack in the right column, which is what makes the split
   a half rather than a third each. They are two halves of one instruction. */
.contact__text { display: grid; gap: var(--mbx-space-6); }

/* A CUTOUT ON ALPHA, placed directly on the card. No box, no border, no radius
   and no background: the marketing site's brand notes record an owner rejection
   of exactly that treatment - a render inside a rounded box on warm paper frames
   a grey rectangle and looks like a child did it. The image IS the shape. */
.contact__art img {
  display: block;
  inline-size: 100%;
  max-inline-size: 22.5rem;
  block-size: auto;
}

/* Below the split it sits between the two blocks and must not tower over
   them, so it is capped smaller rather than filling the column. */
@media (max-width: 59.99em) {
  .contact__art img { max-inline-size: 14rem; }
}

/* ==========================================================================
   Checklist
   Things to collect, not facts to read. `.marklist` puts a short accent rule in
   front of each item, which is right for a list of statements inside a
   paragraph of argument and wrong here: these are three things somebody is
   meant to go and find before they write to us, and a dash says nothing about
   that where a tick says "one of them done".

   THE MARK IS THE ONLY ONE IN THE SET THAT MEANS SOMETHING. Every other glyph
   names a destination or a subject; this one reports a state. It is drawn on
   the same 20px, 1.5px-stroke geometry all the same, so it belongs to the set.
   ========================================================================== */

ul.checklist {
  display: grid;
  gap: var(--mbx-space-3);
  margin: 0;
  padding: 0;
  list-style: none;
  max-inline-size: none;
}

.checklist li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--mbx-space-3);
  align-items: start;
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
}

/* Optically centred on the first line of text rather than hung from the box
   top, which sits it a couple of pixels high next to a 24px line. */
.checklist__icon {
  inline-size: 1.125rem;
  block-size: 1.125rem;
  margin-block-start: 0.1875rem;
  color: var(--mbx-accent);
  flex: none;
}

.splitcols__label {
  margin: 0 0 var(--mbx-space-2);
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  font-weight: 600;
  color: var(--mbx-ink);
}

@media (min-width: 60em) {
  .splitcols {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--mbx-space-7);
    align-items: start;
  }

  /* The column bounds the line now, so the site measure would only ever cut it
     short of the column's own edge and reintroduce the gap. */
  .splitcols :is(p, ul, ol) { max-inline-size: none; }
}

/* ==========================================================================
   Wayfinding list
   A list of DESTINATIONS, each with a name, a reason to go, and somewhere it
   goes. Not the same thing as .marklist, which is a prose list of facts.

   IT REPLACED .marklist ON THE HELP PAGE AND THAT WAS THE WHOLE FAULT. Owner,
   2026-08-30, of those two blocks: "These look shockingly bad - everything has
   to be beautifully designed like an expert designer would do." He is right,
   and the interesting part is WHY it looked unstyled rather than merely plain.

   Every row was a destination wearing a bullet. A dash and a default underlined
   link say "here is a sentence with a word in it that happens to be blue"; what
   the reader is actually being offered is a place to go, four times over, next
   to a card of real disclosure rows that plainly WERE designed. The component
   was wrong for the job, so no amount of tuning the bullet would have fixed it.

   THE WHOLE ROW IS THE TARGET, via the same stretched-link pattern as .mbxrow -
   a 400px row with a two-word hit area is a row people mis-click. The anchor
   stays the only interactive thing in it, so nothing has to be lifted above it.

   ICON RATHER THAN BULLET, AND THE SAME ICON THE RAIL USES. Somebody reading
   "Activity is every call any client has made" learns where Activity is from
   the pulse mark beside it, because that is the mark they will look for in the
   sidebar afterwards. A decorative set would have taught them nothing.
   ========================================================================== */

/* `ul.wayfind`, NOT `.wayfind`, AND THE TYPE SELECTOR IS LOAD-BEARING - the
   same trap ol.steps-list documents a few hundred lines up, met again.
   `.section > ul` is (0,1,1) and a bare class is (0,1,0), so the card's measure
   cap won and the rows stopped two thirds of the way across a full-width card,
   leaving a dead third with a border round it. Matching the specificity and
   coming later in the cascade is what wins it back.

   NOT PROSE, SO NOT CAPPED. The site measure is right for a run of sentences
   and wrong for a component: these rows manage their own measure through their
   column widths. */
ul.wayfind {
  display: grid;
  margin: var(--mbx-space-3) 0 0;
  padding: 0;
  list-style: none;
  max-inline-size: none;
}

.wayfind__item {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--mbx-space-1) var(--mbx-space-4);
  align-items: start;
  padding-block: var(--mbx-space-4);
  border-block-end: 1px solid var(--mbx-border);
  transition: background-color var(--mbx-dur-fast) var(--mbx-ease);
}

/* The last rule would draw against the card's own padding, which reads as a
   stray line rather than as the end of the list. */
.wayfind__item:last-child { border-block-end: 0; }

/* NEGATIVE INLINE MARGIN, so the hover band reaches the card's edges rather
   than stopping short of them and looking like a misaligned box. The padding
   puts the content back where it was.

   KEYED TO --pnl-card-pad, NOT TO THE SPACE STEP THAT CURRENTLY EQUALS IT.
   Both are 24px today; if the card padding ever moves, a hardcoded space step
   would leave the band overhanging or short by the difference, which is the
   kind of drift nobody looks for because the row still renders.

   NO RADIUS ON THE BAND. A rounded background under a straight 1px divider
   leaves the hairline crossing two curved corners, which reads as a rendering
   fault rather than as a style. The focus ring below carries its own. */
.wayfind__item {
  margin-inline: calc(var(--pnl-card-pad) * -1);
  padding-inline: var(--pnl-card-pad);
}

.wayfind__item:hover { background: var(--mbx-bg-sunken); }

/* THE TILE IS WHAT MAKES IT LOOK BUILT. A bare 20px stroke icon floating beside
   16px text reads as an afterthought; the same icon centred in a soft tinted
   square reads as a component. 2.25rem is the smallest square that does not
   crowd a 20px icon. */
.wayfind__mark {
  display: grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border-radius: var(--mbx-radius-sm);
  background: var(--mbx-bg-sunken);
  border: 1px solid var(--mbx-border);
  color: var(--mbx-accent);
  grid-row: 1 / 3;
  transition: background-color var(--mbx-dur-fast) var(--mbx-ease),
              border-color var(--mbx-dur-fast) var(--mbx-ease);
}

.wayfind__item:hover .wayfind__mark {
  background: var(--mbx-accent-soft);
  border-color: var(--mbx-accent);
}

/* The icon carries no size of its own here: mbx_icon() defaults to the rail's
   class, and this component passes its own precisely so the rail's colour and
   sizing are not inherited into a context that wants neither. */
.wayfind__icon { display: block; inline-size: 1.25rem; block-size: 1.25rem; }

/* Mono, tabular, and the same leading-zero form the marketing site's steps
   use, because "in the order it is useful" is a claim the numbers have to
   actually make. A bare 1/2/3/4 in the body face would read as a bullet. */
.wayfind__num {
  font-family: var(--mbx-font-mono);
  font-size: var(--pnl-text-sm);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--mbx-accent);
}

.wayfind__title {
  display: inline-flex;
  align-items: center;
  gap: var(--mbx-space-2);
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  font-weight: 600;
  color: var(--mbx-ink);
  text-decoration: none;
}

/* NO UNDERLINE UNTIL HOVER, which is the opposite of the rule everywhere else
   on this site and is correct here for one reason: the row is the link. An
   underline on the title would be the second affordance on a target that
   already has a hover band and a mark, and three affordances for one action
   reads as decoration. */
.wayfind__item:hover .wayfind__title { color: var(--mbx-accent-ink); }

.wayfind__title::after {
  content: "";
  position: absolute;
  inset: 0;
}

.wayfind__out {
  inline-size: 0.875rem;
  block-size: 0.875rem;
  color: var(--mbx-ink-muted);
  flex: none;
}

.wayfind__note {
  margin: 0;
  grid-column: 2;
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  color: var(--mbx-ink-muted);
  max-inline-size: var(--mbx-measure);
}

/* THE INDEX ROW, for a .wayfind on a card that spans the page.
   
   TRIED AS A 2x2 GRID FIRST AND IT WAS WORSE THAN WHAT IT REPLACED. Four rows
   in two columns puts each note in about 330px, which turned two-sentence
   descriptions into five cramped lines - denser than the single column and
   harder to read, which is the wrong trade in both directions at once.
   
   A full-width card wants a full-width ROW: mark, title and note on one line,
   with the titles in a column of their own. That is an index, it aligns down
   the page, and the note gets a real measure instead of a gutter's leftovers.
   The title column is sized to the longest label rather than guessed, so
   "Settings and security" sets it and nothing wraps.
   
   Below 60em every one of these reverses: the title column collapses, the note
   drops under it, and the component is the single-column list again. */
@media (min-width: 60em) {
  .wayfind--index .wayfind__item {
    grid-template-columns: auto minmax(0, 12.5rem) minmax(0, 1fr);
    align-items: baseline;
    column-gap: var(--mbx-space-5);
  }

  /* The mark spans one row now rather than two, and centring it on the text
     baseline row rather than the box keeps it level with the title. */
  .wayfind--index .wayfind__mark {
    grid-row: auto;
    align-self: center;
  }

  /* NO MEASURE CAP, and that is the rule rather than an exception. Owner
     2026-08-30: a box that is not width restricted must not width restrict its
     content. The note is the third column of an index row, so the ROW is what
     uses the width and the note is already bounded by the two columns to its
     left - it lands around 60-70ch on a 1250px card without being told to. A
     cap on top of that would leave a visible dead strip at the card's right
     edge, which is the exact fault being fixed. */
  .wayfind--index .wayfind__note {
    grid-column: 3;
    max-inline-size: none;
  }
}

/* The focus ring belongs to the row, not to the two words inside it, or a
   keyboard user gets a rectangle round the title and no sense of what they
   are about to open. */
.wayfind__title:focus-visible { outline: none; }
.wayfind__item:has(.wayfind__title:focus-visible) {
  outline: 2px solid var(--mbx-focus);
  outline-offset: -2px;
  border-radius: var(--mbx-radius-sm);
}


/* ==========================================================================
   The sister-service block
   See includes/services-365i.php for why it exists, why it is on the overview
   and nowhere else, and why the artwork is our own mascot rather than 365i's
   branding.

   THREE ACROSS, THEN TWO, AND THE LAYOUT IS THE RANKING. The owner named
   WordPress hosting, web hosting and domains as the big three; leaving five
   equal tiles would have thrown that away. The first three take a row of three
   and the last two share a row beneath at a lower weight, so somebody scanning
   sees the ranking without reading a word of it.

   THE ART IS A CUTOUT ON ALPHA, placed straight on the card with no box, no
   border and no radius. The marketing site's brand notes record an owner
   rejection of the framed treatment; a rounded box round a render on warm paper
   frames a grey rectangle.
   ========================================================================== */

/* ROOM ROUND THE HEAD. Owner 2026-08-30, of this block's top: "This looks a
   bit bunched up doesn't it?" It did: the section head's own 16px bottom margin
   is set for a card whose next element is a list of rows, and here the next
   element is a paragraph that then runs straight into a grid of cards, so three
   things sat in 16px steps with nothing to separate the introduction from the
   offer. The lead gets air on both sides.

   THE WORDMARK ALSO NEEDED ITS OWN GAP. The negative nudge that pulls a square
   20px icon closer to its title is wrong for a 60px wordmark - at that width it
   read as one run of ink - so it is excluded from that rule below. */
.svc .section__head { margin-block-end: var(--mbx-space-5); }
.svc__lead { margin-block-start: 0; margin-block-end: var(--mbx-space-6); }

ul.svc__grid {
  display: grid;
  gap: var(--mbx-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
  max-inline-size: none;
}

/* THE ARTWORK IS THE HERO, NOT A THUMBNAIL. The first build set it at 96px and
   the renders read as decoration: at that size you cannot see that the robot is
   holding up a website, plugging in a server or planting a signpost, which is
   the entire reason each one was staged. Owner's bar for this set was that the
   images be outstanding, and an outstanding image shown at 96px is a wasted one.
   The featured cards give it a real block of the card and the text sits under
   it, which is the shape of a product card and the shape that lets the art do
   its job. */
.svc__card {
  position: relative;
  display: grid;
  gap: var(--mbx-space-4);
  align-items: center;
  padding: var(--mbx-space-5);
  border: 1px solid var(--mbx-border);
  border-radius: var(--mbx-radius-md);
  background: var(--mbx-bg-raised);
  transition: border-color var(--mbx-dur-fast) var(--mbx-ease),
              box-shadow var(--mbx-dur-fast) var(--mbx-ease);
}

.svc__card:hover {
  border-color: var(--mbx-accent);
  box-shadow: var(--mbx-shadow-2);
}

/* A CUTOUT ON ALPHA, placed straight on the card: no box, no border, no radius
   and no tinted plate behind it. The marketing site's brand notes record an
   owner rejection of the framed treatment, and the reasoning holds here - a
   rounded panel behind a render frames a rectangle the render does not have. */
.svc__art {
  display: block;
  inline-size: 100%;
  max-inline-size: 11rem;
  block-size: auto;
  margin-inline: auto;
  /* A LIFT ON HOVER, on transform only. The card already changes its border and
     shadow; the art rising a little is what makes the whole thing feel like one
     object responding rather than a border animating on its own. */
  transition: transform var(--mbx-dur-base) var(--mbx-ease);
}
.svc__card:hover .svc__art { transform: translateY(-4px); }

@media (prefers-reduced-motion: reduce) {
  .svc__art, .svc__card { transition: none; }
  .svc__card:hover .svc__art { transform: none; }
}

.svc__body { align-self: start; }

.svc__title {
  margin: 0;
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  font-weight: 600;
}

.svc__title a {
  display: inline-flex;
  align-items: center;
  gap: var(--mbx-space-2);
  color: var(--mbx-ink);
  text-decoration: none;
}

/* The whole card is the target. Same stretched-link pattern as .mbxrow and
   .wayfind: a card with a two-word hit area is a card people mis-click. */
.svc__title a::after { content: ""; position: absolute; inset: 0; }
.svc__card:hover .svc__title a { color: var(--mbx-accent-ink); }

.svc__out {
  inline-size: 0.875rem;
  block-size: 0.875rem;
  color: var(--mbx-ink-muted);
  flex: none;
}

.svc__note {
  margin: var(--mbx-space-2) 0 0;
  font-size: var(--pnl-text-sm);
  line-height: var(--pnl-lh-sm);
  color: var(--mbx-ink-muted);
  max-inline-size: none;
}

/* Narrow: art above text, one card per row. */
@media (min-width: 40em) {
  ul.svc__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* THREE ACROSS ONLY WHEN THERE IS ROOM FOR IT, which at 60em there is not.
   Two tracks of a six-track grid inside a 1024px card is about 300px, and
   "Managed WordPress hosting" wraps to two lines in it - flagged by the
   typography gate and visibly cramped besides. The three-across row starts at
   75em, and between 40 and 75 the cards sit two to a row where the title fits
   on one line. */
@media (min-width: 75em) {
  ul.svc__grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }

  /* Three across on the first row: 6 tracks, 2 each. */
  .svc__card { grid-column: span 2; }

  /* THE TWO MINOR ONES SHARE THE SECOND ROW, three tracks each. They are the
     two the owner ranked last, so they get the wide-and-short treatment - art
     beside the text rather than above it - and read as a footnote to the three
     above rather than as a broken third row. */
  .svc__card--minor {
    grid-column: span 3;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
  }
  .svc__card--minor .svc__art { max-inline-size: 7.5rem; margin-inline: 0; }
}


/* ==========================================================================
   THE DANGER ZONE AND ITS CONFIRMATION DIALOG.

   MOVED HERE FROM pages/mailbox.css ON 2026-08-31, AND THE MOVE WAS THE FIX.

   `.confirm` is used on three pages. Only ONE of them loaded the sheet these
   rules were written in: mailbox.php lists `pages/mailbox`, settings.php lists
   `pages/plan` and `pages/settings`, and admin/account.php lists `pages/admin`.
   So the customer's own close-account dialog and both administrative
   confirmations were rendering with NO STYLING AT ALL - the panel had no
   padding, no surface and no shadow, and `.confirm__panel > * + *` was never
   applied, so the title, the paragraphs, the hint and the buttons drew on top
   of one another inside a 177px box.

   Found by opening the comp dialog and looking at it. Every suite passed
   throughout: they assert the WORDS in a dialog, and the words were all
   present and all in the same place.

   IT IS A COMPONENT AND IT BELONGS IN THE COMPONENT SHEET, which every page
   loads. A component whose styles live in one page's stylesheet works until
   the second page uses it, and then fails in a way nobody sees because the
   markup is right and the test is green.
   ========================================================================== */

/* A rule above it rather than a red box round it. The section is not dangerous;
   the button in it is, and boxing the whole thing in danger colours makes the
   heading shout at somebody who came to read it. */
.danger {
  margin-block-start: var(--mbx-space-8);
  padding-block-start: var(--mbx-space-6);
  border-block-start: 1px solid var(--mbx-danger-border);
}

/* UNTIL confirm.js RUNS THIS IS ORDINARY CONTENT, and that is the whole
   no-JavaScript path: the consequence is on the page and the plain submit
   works. `.is-modal` is added by the script, and only then does it become a
   dialog that has to be opened. */
.confirm { border: 0; padding: 0; background: none; color: inherit; max-inline-size: 34rem; }

/* WHITE-SPACE IS RESET, AND IT HAS TO BE. A dialog is declared wherever its
   opener lives and is then lifted into the top layer, so it inherits from a
   cell it no longer sits in. The identity list's `.sub__when` sets
   `white-space: nowrap` to keep a date on one line; the removal dialog declared
   inside it inherited that, so two full sentences refused to wrap inside a
   544px box and the modal came up with a horizontal scrollbar under it. Owner
   2026-08-29: "That has a scrollbar??"

   ON THE COMPONENT RATHER THAN ON THAT ONE CELL, because the next dialog will
   be declared somewhere else with its own inherited surprises. A modal is a
   document, not a continuation of the row it was written in. */
.confirm { white-space: normal; }

/* A <dialog> IS display:none UNTIL SOMETHING OPENS IT, by UA rule, and without
   JavaScript nothing ever does. So the "renders inline as an ordinary block"
   fallback that confirm.js and both partials describe had never actually
   worked: the panel inside was unreachable, and the rule below styling
   .confirm:not(.is-modal) .confirm__panel was unreachable with it.

   On the regenerate confirmation that was survivable, because its fallback
   submit sits outside the dialog and the consequence is stated on the page
   above. On the removal confirmation it was not: the typed acknowledgement
   lives INSIDE the dialog, so with JavaScript off there was no way to remove a
   mailbox at all.

   display:block ALONE IS NOT ENOUGH, and the second half cost a test to find.
   The UA sheet also gives every dialog `position:absolute; margin:auto` with
   the inline insets pinned, for the modal case. Shown inline that takes the
   panel OUT OF FLOW: it contributes nothing to the page height and floats over
   whatever is beneath it. Measured before the fix, the confirming button sat
   at y=1602 in a document 1360px tall, which is unreachable rather than merely
   ugly, and is why the no-JavaScript run could not click it.

   An author rule beats a UA rule whatever the specificity. .is-modal, added by
   the script, puts all of it back. */
.confirm:not(.is-modal) {
  display: block;
  position: static;
  inline-size: auto;
  block-size: auto;
  margin: 0;
}

.confirm.is-modal { display: none; }
.confirm.is-modal[open] { display: block; }

.confirm::backdrop { background: var(--mbx-scrim); }

.confirm__panel {
  padding: var(--mbx-space-6);
  background: var(--mbx-bg-raised);
  border-radius: var(--mbx-radius-lg);
  box-shadow: var(--mbx-shadow-3);
}
.confirm:not(.is-modal) .confirm__panel {
  box-shadow: none;
  border: 1px solid var(--mbx-border);
  padding: var(--mbx-space-5);
  margin-block-start: var(--mbx-space-4);
}

/* The other. In a destructive dialog the title is the question being asked,
   and it is the one thing that must not be skimmed. */
.confirm__title {
  font-weight: 600;
  font-size: var(--pnl-text-lg);
  line-height: var(--pnl-lh-lg);
  letter-spacing: var(--mbx-track-heading);
}
.confirm__panel > * + * { margin-block-start: var(--mbx-space-4); }
.confirm__panel p {
  font-size: var(--pnl-text-body);
  line-height: var(--pnl-lh-body);
  color: var(--mbx-ink-muted);
  max-inline-size: var(--mbx-measure);
}

/* CANCEL FIRST IN THE DOM, so it is where the keyboard lands and where the
   script puts focus. It is also first visually here, which is the arrangement
   that does not reward muscle memory for the destructive one. */
.confirm__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mbx-space-3);
  margin-block-start: var(--mbx-space-6);
}

/* ==========================================================================
   THE DISCLOSURE

   For the sign-in details on the calendar form, which most customers never
   touch: on Fastmail and on iCloud one app password covers mail and calendar,
   so the ordinary form is ONE field and the rest is behind this.

   A <details> RATHER THAN A CHECKBOX AND SOME JAVASCRIPT. It is keyboard
   operable, announced as expandable, and works with the script switched off,
   which is the same bar every other control on this panel meets.

   THE MARKER IS REPLACED RATHER THAN HIDDEN. `list-style: none` alone leaves
   Safari drawing its own triangle from `::-webkit-details-marker`, so both are
   turned off and one chevron is drawn here - otherwise the summary renders with
   two arrows on one platform and one on every other.
   ========================================================================== */

.disclose {
  border: 1px solid var(--mbx-border);
  border-radius: var(--mbx-radius-md);
  background: var(--mbx-bg-sunken);
  margin-block-end: var(--mbx-space-5);
}

.disclose__summary {
  display: flex;
  align-items: center;
  gap: var(--mbx-space-3);
  padding: var(--mbx-space-4) var(--mbx-space-5);
  cursor: pointer;
  font-size: var(--pnl-text-ui);
  line-height: var(--pnl-lh-ui);
  font-weight: 600;
  color: var(--mbx-ink);
  list-style: none;
  border-radius: var(--mbx-radius-md);
}
.disclose__summary::-webkit-details-marker { display: none; }

/* THE CHEVRON IS A BORDER BOX ROTATED, not an SVG and not a character. A glyph
   would come from whatever font resolved and sit on a different baseline on
   each platform; this is two borders and it is identical everywhere. */
.disclose__summary::before {
  content: "";
  inline-size: 0.5em;
  block-size: 0.5em;
  border-inline-end: 2px solid var(--mbx-ink-muted);
  border-block-end: 2px solid var(--mbx-ink-muted);
  transform: rotate(-45deg);
  transition: transform var(--mbx-dur-fast) var(--mbx-ease);
  flex: none;
}
.disclose[open] > .disclose__summary::before { transform: rotate(45deg); }

.disclose__summary:focus-visible {
  outline: var(--mbx-focus-width) solid var(--mbx-focus);
  outline-offset: var(--mbx-focus-offset);
}

.disclose__body {
  padding: 0 var(--mbx-space-5) var(--mbx-space-5);
  border-block-start: 1px solid var(--mbx-border);
  padding-block-start: var(--mbx-space-4);
}
.disclose__body > * + * { margin-block-start: var(--mbx-space-4); }

@media (prefers-reduced-motion: reduce) {
  .disclose__summary::before { transition: none; }
}

/* ==========================================================================
   THE TAG LIST

   What a calendar server actually supports, named rather than counted.

   THE PANEL'S STANDING RULE IS THAT NO STRING A CUSTOMER READS STATES A COUNT
   OF TOOLS, because the catalogue moves and this side cannot derive it. This
   does not break that rule, it is what makes it survivable: the API sends the
   list it built by running the real registration against the capabilities it
   measured on THAT server, so the names are the answer rather than a figure
   somebody typed. It is also the one place in the product where two customers
   legitimately see different ones.

   MONO, BECAUSE THESE ARE IDENTIFIERS. A tool name is a value from the machine,
   the same class of thing as a hostname in the ledger above it, and setting it
   in the body face would present it as prose the customer could paraphrase.
   ========================================================================== */

.taglist {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mbx-space-2);
  list-style: none;
  padding: 0;
  margin: 0;
}

/* AND THE RESET ABOVE BEAT THE CALLOUT'S OWN RHYTHM, which is how thirteen tool
   names came to sit flush against the sentence introducing them with no gap at
   all. `.callout > * + *` sets the space between a callout's children and is
   one class deep; `.taglist { margin: 0 }` is one class deep too and comes two
   thousand lines later, so the reset won and the computed margin-top was
   exactly 0px. The reset itself is right - a `ul` arrives with a browser margin
   that has nothing to do with this design - so the fix is to say which of the
   two wins rather than to weaken either.

   space-3 rather than the callout's standing space-4: the sentence and the list
   are one object, a label and the thing it labels, and at 16px the list floats
   free of the sentence that explains it. 12px is larger than the 8px between
   the tag rows, so the block still reads as separate from the prose. */
.callout > * + .taglist { margin-block-start: var(--mbx-space-3); }

.tag {
  padding: 0.125rem var(--mbx-space-3);
  border: 1px solid var(--mbx-border);
  border-radius: var(--mbx-radius-sm);
  background: var(--mbx-bg);
  font-family: var(--mbx-font-mono);
  font-size: var(--pnl-text-mono);
  line-height: var(--pnl-lh-mono);
  letter-spacing: var(--mbx-track-mono);
  color: var(--mbx-ink-muted);
  white-space: nowrap;
}

/* ==========================================================================
   A TIGHTER LEDGER

   For a reference list rather than a record: the three calendar addresses on
   the connect form are examples somebody is about to copy, not facts about
   this mailbox. Same component so the two read as one family, less air so a
   list of three does not out-weigh the form it is helping with.
   ========================================================================== */

.ledger--tight { gap: var(--mbx-space-2); }

/* AND ITS VALUES BREAK ANYWHERE, because one of them is now 66 characters. The
   ledger's own rows hold a hostname or a count and fit; these are whole CalDAV
   endpoints, and StackMail's carries a path and an address in it. A URL has no
   spaces to break at, so without this it sets the width of the callout and
   pushes the card sideways at 390px. */
.ledger--tight .ledger__value { overflow-wrap: anywhere; }

/* A LEDGER VALUE THAT IS A SENTENCE. The mono face is what tells a reader the
   right-hand column holds something the machine chose - a hostname, a count, a
   timezone name. One row on the calendar card holds a phrase instead, and in
   mono it reads as a literal string somebody could type. */
.ledger__value--prose {
  font-family: var(--mbx-font-body);
  font-size: var(--pnl-text-ui);
  line-height: var(--pnl-lh-ui);
  letter-spacing: normal;
  color: var(--mbx-ink-muted);
}

/* ==========================================================================
   THE FOOT OF A CARD

   A secondary action separated from what it acts on. Used by the calendar's
   disconnect, which must never be mistakable for the danger card at the foot
   of the same tab: that one removes a mailbox, this one removes a diary.

   THE RULE ABOVE IT IS DOING THE SEPARATING, so the button can stay quiet
   rather than reaching for a colour to say "different". */
.section__foot {
  margin-block-start: var(--mbx-space-6);
  padding-block-start: var(--mbx-space-5);
  border-block-start: 1px solid var(--mbx-border);
}
.section__foot > .hint { margin-block-start: var(--mbx-space-3); }
