/* Panel stylesheet. It lives in a file rather than in a <style> block in the
   layout so the panel's Content-Security-Policy can be a plain
   "default-src 'self'" with no inline-style exemption. Any rule
   added here must therefore stay here: an inline style="..." attribute in a
   template is blocked by that policy and silently does nothing. */

/* Colour tokens. Light values live on :root; the dark media query below
   reassigns the same names rather than re-declaring every rule that uses
   them, so a rule needs !important nowhere in this file — the custom
   property already carries the right value for the active scheme. */
:root {
  color-scheme: light dark;
  --bg: #f6f7f9; --fg: #1b1f24;
  --card-bg: #fff;
  --border: #e2e5e9;       /* dividers: card, table, app, nav, code, encrypt-fields */
  --control-border: #cfd4da; /* input, select, textarea, action buttons */
  --input-bg: #fff;
  --code-bg: #f0f2f4;
  --surface-bg: #eef1f5; --surface-bg-hover: #e2e7ee; --surface-open-bg: #dde3ec;
  --nav-active-bg: #e6ebf5;
  --flash-bg: #ecfdf3; --flash-border: #abefc6; --flash-fg: #067647;
  --credential-bg: #fffbeb; --credential-border: #f5c518;
  --danger-bg: #fef3f2; --danger-border: #fecdca; --danger-fg: #b42318; --danger-bg-hover: #fee4e2;
  --st-ok-bg: #ecfdf3; --st-ok-fg: #067647; --st-ok-border: #abefc6;
  --st-warn-bg: #fffaeb; --st-warn-fg: #b54708; --st-warn-border: #fedf89;
  --st-error-bg: #fef3f2; --st-error-fg: #b42318; --st-error-border: #fecdca;
  --st-unknown-bg: #f0f2f4; --st-unknown-fg: #6b7280; --st-unknown-border: #e2e5e9;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14171a; --fg: #e6e8eb;
    --card-bg: #1d2125;
    --border: #2b3138;
    --control-border: #2b3138;
    --input-bg: #14171a;
    --code-bg: #14171a;
    --surface-bg: #22262b; --surface-bg-hover: #2b3138; --surface-open-bg: #313841;
    --nav-active-bg: #22303f;
    --flash-bg: #0d2818; --flash-border: #1a5336; --flash-fg: #75d99b;
    --credential-bg: #2a2408; --credential-border: #6b5a10;
    --danger-bg: #2d1211; --danger-border: #6b201a; --danger-fg: #f5a29b; --danger-bg-hover: #3d1a18;
    --st-ok-bg: #0d2818; --st-ok-fg: #75d99b; --st-ok-border: #1a5336;
    --st-warn-bg: #2e2308; --st-warn-fg: #f5c86b; --st-warn-border: #6b5210;
    --st-error-bg: #2d1211; --st-error-fg: #f5a29b; --st-error-border: #6b201a;
    --st-unknown-bg: #22262b; --st-unknown-fg: #9aa3ad; --st-unknown-border: #2b3138;
  }
}
* { box-sizing: border-box; }
/* The scrollbar's width is reserved whether or not the page is long enough to
   need one. Without it a short page (Account) and a long one (Status) are laid
   out in viewports differing by the scrollbar's width, and everything on the
   page — the navigation column included — shifts sideways on the way between
   them. The panel is a set of pages an operator moves through, so that shift
   read as the layout jumping. */
html { scrollbar-gutter: stable; }
body {
  font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  margin: 0; padding: 2rem 1rem; background: var(--bg); color: var(--fg);
}
/* The two columns — navigation on the left, page on the right — centred as a
   pair. The navigation is a fixed width and the page keeps the measure it had:
   the column is added beside the content, not taken out of it. */
.shell { display: flex; justify-content: center; align-items: flex-start; gap: 1.75rem; }
/* One column for every page. It used to be the reading measure, 48rem, which
   the three pages holding data widened to 64rem — and since the shell centres
   the navigation and the page as a pair, that difference moved the navigation
   column and the left edge of every card on the way between two pages. The
   column is now the wider of the two everywhere, so nothing moves; the reading
   measure is kept inside it by the rule below rather than by the column.
   No auto margins: inside the shell they would eat the free space and push the
   navigation column off to the far edge, so the centring is the shell's job
   (justify-content above). */
main { flex: 1 1 auto; min-width: 0; width: 100%; max-width: 64rem; }
/* 48rem is a reading measure: right for the forms and prose that make up most
   of the panel, and too narrow for the pages that are tables of data. Every
   page therefore keeps its content at that measure, centred in the column, and
   the pages made of data opt out (main.wide below) and take the column whole.
   The cap is on <main>'s children rather than on .card so a page's heading, its
   back link and the version footer travel with the cards: capping the cards
   alone would leave the heading spanning the column with the card it belongs to
   floating in the middle — the misalignment that gave login and setup their own
   narrow column (main.page-login further down). */
main > * { max-width: 48rem; margin-left: auto; margin-right: auto; }
/* The four monitoring pages are the ones that opt out: six columns of send
   log, raw mail.log lines that are long by nature — which at the measure left
   Subject and Status fighting over the last inch and wrapped every second log
   line — and a single delivery's page, which stands two cards side by side over
   a block of those same log lines. The class is declared by the page itself
   (the "wide" block in layout.html) rather than derived from the page name: a
   page's name is the navigation entry it belongs under, which several pages
   share, and how wide a page needs to be is a property of that page alone. */
main.wide > * { max-width: none; }
/* Longhands, not the margin shorthand: the shorthand would re-zero the auto
   side margins above and pin the heading to the column's edge, a measure's
   worth to the left of the card under it. */
h1 { font-size: 1.4rem; margin-top: 0; margin-bottom: 1rem; }
/* The full mark, on the two pages that have no navigation bar to carry the
   compact one. It takes the column's width so its edges line up with the card
   below it, capped at that column's own 24rem; height stays automatic because
   the stamp's proportions are part of the mark and it is never scaled unevenly.
   The subtitle stops resolving below 280px, which a 320px viewport still clears
   once the body's padding is taken off. */
.mark { display: block; width: 100%; max-width: 24rem; height: auto; margin-bottom: 1.4rem; }
.card {
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 1.5rem; margin: 0 auto;
}
.card.narrow { max-width: 24rem; }
label { display: block; font-weight: 600; margin: 0.9rem 0 0.3rem; }
input {
  width: 100%; padding: 0.55rem 0.7rem; font-size: 1rem;
  border: 1px solid var(--control-border); border-radius: 6px; background: var(--input-bg); color: inherit;
}
/* One vocabulary for actions. Anything that performs an action looks like a
   button: a <button>, or an <a> carrying .btn/.danger where the action is a
   plain navigation (the delete confirmation page, the full queue view).
   Several of these used to render as bold blue text instead — a POST wrapped
   in form.inline, a disclosure toggle, the delete links — which read as links
   and left two appearances for the same kind of control. They all get the
   button look now: filled for a card's own action, and the compact outlined
   variant further down where actions cluster (table rows, the nav bar). Bare
   <a> is left for links that read as part of a sentence or a list. */
button, a.btn, a.danger {
  display: inline-block; margin-top: 1.2rem; padding: 0.6rem 1.1rem;
  font: inherit; font-size: 1rem; font-weight: 600; text-decoration: none;
  color: #fff; background: #2563eb; border: 0; border-radius: 6px; cursor: pointer;
}
button:hover, a.btn:hover { background: #1d4ed8; }
.error { color: #b42318; margin: 0.6rem 0 0; font-weight: 600; }
.muted { color: #6b7280; }
.topbar { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 1.2rem; }
.topbar .actions { display: flex; gap: 0.9rem; align-items: baseline; }
/* form.inline only exists so a POST can sit next to other content without a
   form's block layout; its button is styled like any other. */
form.inline { display: inline; margin: 0; }
/* The signed-out pages are a single card and nothing else, and .card.narrow
   centres itself inside whatever holds it — so at the panel's usual width the
   card floated in the middle while the mark and the heading stayed at the far
   left, three alignments on a page with four elements. Narrowing the column to
   the card's own width makes the three line up and puts the block as a whole in
   the middle of the page. */
main.page-login, main.page-setup { max-width: 24rem; }
.card + .card { margin-top: 1.2rem; }
.flash { background: var(--flash-bg); border: 1px solid var(--flash-border); color: var(--flash-fg); padding: 0.7rem 1rem; border-radius: 8px; margin-bottom: 1.2rem; }
table { width: 100%; border-collapse: collapse; }
/* A table column is at least as wide as the longest unbreakable run inside it,
   and the panel's tables are full of runs with nothing to break on: email
   addresses, domains, queue ids. One 40-character recipient was enough to widen
   the send-log past its card and hang Status over the edge. Cells may break mid
   word, so a column can always be squeezed to the width available. */
th, td { text-align: left; padding: 0.5rem 0.4rem; border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }
/* The exception: a timestamp broken across two lines is unreadable, and it is
   short enough to never be the reason a row does not fit. */
td.time { white-space: nowrap; }
th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; color: #6b7280; }
td.actions { text-align: right; }
/* Subject is the one cell whose text we do not control. Breaking mid word (the
   rule above) keeps it inside the card, but a long subject would do it by
   growing the row several lines tall, which buries the rows around it. So the
   subject is clipped to one line instead, with the whole of it in the tooltip.
   The clamp sits on an inner block box rather than the cell because max-width
   on a <td> is only advisory in the automatic table layout. */
td.subject span {
  display: block; max-width: 18rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.code { display: block; white-space: pre-wrap; word-break: break-all; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
        font-size: 0.85rem; background: var(--code-bg); border: 1px solid var(--border); border-radius: 6px; padding: 0.7rem 0.8rem; margin: 0.3rem 0 0; }
h2 { font-size: 1.05rem; margin: 0 0 0.4rem; }
/* The back link is a block so it takes the measure and lines up with the
   heading above it and the card below: auto side margins centre a block, but
   they do nothing for an inline-block, which would have stayed at the column's
   edge while everything around it moved in. Its own text stays at the left of
   that block — only the click target grows to the line, as it does for the
   navigation entries. */
.back { display: block; margin-bottom: 1rem; }
/* Build version, closing every authenticated page. Quiet on purpose: it is
   reference material, not something to read on the way past. */
.version { margin-top: 1.6rem; text-align: right; font-size: 0.8rem; color: #6b7280; }
select, textarea {
  width: 100%; padding: 0.55rem 0.7rem; font-size: 1rem;
  border: 1px solid var(--control-border); border-radius: 6px; background: var(--input-bg); color: inherit;
  font-family: inherit;
}
textarea { resize: vertical; }
button.danger, a.danger { background: #b42318; }
button.danger:hover, a.danger:hover { background: #912018; }
/* The disclosure toggle is an action too, so it is drawn as a button (see the
   compact rule below); it carries no marker because the pressed background
   already shows the open state. */
.actions > label.toggle { display: inline-block; cursor: pointer; }
/* Applications are a list of blocks, not table rows. As a table it fell apart:
   four columns of which the last held six controls — two of them <details>
   panels with textareas — never fit the panel's default width. The controls
   wrapped into a staircase, .code on the login cell grew into a slab as tall
   as the row, and the two text cells sat on the baseline halfway down it. One
   block per application gives the identity a line of its own and the
   controls a row of their own, at the width they actually need. */
.apps { list-style: none; margin: 1.2rem 0 0; padding: 0; }
.app { padding: 0.9rem 0; border-top: 1px solid var(--border); }
.app:last-child { padding-bottom: 0; }
.app-login { margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600; }
.app-addr { margin: 0.15rem 0 0; word-break: break-all; }
.app .actions { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.7rem; }
/* Mode and rate limit open under the whole row rather than inside it. As
   <details> the panel sat where its summary was, so opening one cut the row of
   four controls in half and pushed the rest below a block of fields — the
   buttons moved every time a panel opened or closed. The panels are therefore
   the last children of the row, and the checkbox that opens each one is
   visually hidden earlier in the row with its label drawn as the button. The
   checkbox stays in the tab order and keeps its focus ring on the label, so it
   works from the keyboard, and being pure CSS it also works with JavaScript
   blocked, as <details> did. */
.app .actions > .panel-toggle {
  position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none;
}
.app .actions > .panel { display: none; flex: 1 0 100%; }
.app .actions > .t-mode:checked ~ .panel-mode,
.app .actions > .t-limit:checked ~ .panel-limit { display: block; }
.app .actions > .t-mode:checked ~ .for-mode,
.app .actions > .t-limit:checked ~ .for-limit { background: var(--surface-open-bg); }
.app .actions > .t-mode:focus-visible ~ .for-mode,
.app .actions > .t-limit:focus-visible ~ .for-limit { outline: 2px solid #2563eb; outline-offset: 2px; }
.panel form { margin-top: 0.6rem; }
/* A panel's own submit is a form button, not one of the controls in the row
   above, so it takes back the spacing the compact .actions rule zeroes out:
   without it Save sits flush against the field it saves and against the
   Remove button under it, reading as one stack of edges. */
.app .actions > .panel button { margin-top: 0.9rem; }
/* Where a panel has two of them (save the limit, remove it) they share one row
   under the fields, which the row itself spaces off instead of each button.
   The buttons align on their tops rather than stretching: one of the two is
   wrapped in a form, and that wrapper's margin would otherwise make the row
   taller than a button and stretch the unwrapped one to match it. */
.app .actions > .panel .panel-buttons {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0.4rem; margin-top: 0.9rem;
}
.app .actions > .panel .panel-buttons button,
.app .actions > .panel .panel-buttons form { margin-top: 0; }
.credential { border-color: var(--credential-border); background: var(--credential-bg); }
/* Panel navigation: rendered once from the layout, so it is present on every
   authenticated page without each content template having to include it. */
/* A column down the left edge rather than a bar across the top. The six page
   entries need about 660px and the session block another 260px, against the
   738px the panel was wide, so as a bar it had to be split over two rows — and
   even then it cost the top of every page. Standing it up removes that: the
   entries share one left edge to scan down, the session sits at the foot where
   it is out of the way, and there is room between them for the current page's
   own sections (.sections below), which is what makes the long pages navigable.
   Sticky, so both lists stay in view while the page scrolls past them. The
   layout template lists the blocks in the order they are drawn, so reading and
   tab order follow the eye without a CSS `order`. */
.nav {
  position: sticky; top: 2rem; align-self: flex-start;
  flex: none; width: 13.5rem;
  display: flex; flex-direction: column; align-items: stretch; gap: 0.75rem;
  /* A viewport shorter than the column would otherwise cut off whatever hangs
     below the fold — with no page scroll left to reach it, since the column is
     stuck to the viewport. */
  max-height: calc(100vh - 4rem); overflow-y: auto;
}
.nav .links, .nav .session { display: flex; flex-direction: column; gap: 0.1rem; }
/* The session is the column's last block and the only one that is not
   navigation, so it is ruled off from the entries above it. */
.nav .session { padding-top: 0.75rem; border-top: 1px solid var(--border); }
/* The signed-in administrator's name: a label above the two controls, not one
   of them. It may be long and there is nothing to break it on, so it is allowed
   to break mid word rather than widen the column. */
.nav .session .muted { padding: 0 0.6rem; font-size: 0.85rem; overflow-wrap: anywhere; }
/* The mark is a link, but not one of the column's entries: it takes none of the
   padding and rounding the entry rule below applies, so its own edge lines up
   with the entries' icons rather than sitting half a step inside them. */
.nav .brand { padding: 0; }
/* The mark takes the column's full width, so its edges line up with the entries
   below it rather than ending halfway across — the same way .mark fills the
   card column on the pages that carry no navigation. width/height are on the
   element too, so the column reserves the right height before the SVG has
   loaded; height stays automatic because the stamp's proportions are part of
   the mark — it is never scaled unevenly. */
.nav .brand img { display: block; width: 100%; height: auto; }
/* Each entry pairs an icon with its label, so the entry itself is a flex row
   rather than a run of text — that is also why the entries centre their items
   instead of aligning them on the text baseline. In the column the entry fills
   the width, so the whole strip is the click target and the hover and active
   backgrounds read as one stack of bars. Account is included: it is a page like
   the others and would otherwise be the one bare word in the column. */
/* No underline: in a row the entries were separated by space alone and the
   underline was what marked them as links, but a column of them reads as a
   list of links already, and eight underlines down the left edge is a lot of
   line for no added meaning. The hover background carries the affordance. */
.nav a, .nav [aria-current] {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.35rem 0.6rem; border-radius: 6px; text-decoration: none;
}
.nav a:hover { background: var(--surface-bg); }
/* The marker moved with the bar: a rule under the entry made sense in a row,
   where the entries sat side by side; standing up, the current entry is marked
   down its leading edge, which is the edge every entry shares. */
.nav [aria-current] {
  font-weight: 600; color: var(--fg); background: var(--nav-active-bg); box-shadow: inset 2px 0 0 #2563eb;
}
/* Sign out carries an icon too, so it needs the same row layout; its padding
   and colours come from the compact button rule further down. */
.nav button { display: flex; align-items: center; gap: 0.5rem; }
/* The icons draw in the entry's own colour, so the active entry's darker text,
   a link's blue and Sign out's red all carry through without a rule apiece. */
.nav .icon { width: 1rem; height: 1rem; flex: none; }
/* The current page's own sections, listed under the page entries. The domain
   page and the status page are eight and nine cards tall, and the only way to
   the card you came for was to scroll past all the ones you did not. Only pages
   that long carry an index — it comes from the page's own "sections" template
   (see layout.html), so a page with two cards renders nothing here. */
.sections {
  display: flex; flex-direction: column; gap: 0.1rem;
  padding-top: 0.75rem; border-top: 1px solid var(--border);
}
.sections-title {
  margin: 0 0 0.25rem 0.6rem;
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: #6b7280;
}
/* Quieter and a step in from the page entries: this is an index of one page,
   subordinate to the list of pages above it. */
.nav .sections a {
  padding: 0.2rem 0.6rem 0.2rem 0.9rem;
  font-size: 0.85rem; color: #6b7280; text-decoration: none;
}
.nav .sections a:hover { color: var(--fg); }
/* panel.js marks the section the page is scrolled to. Without JavaScript
   nothing is marked and the list is still a working index. */
.nav .sections a.current { color: var(--fg); font-weight: 600; background: var(--nav-active-bg); }
/* Jumping to a card should not leave it touching the top edge of the window. */
.card[id] { scroll-margin-top: 1rem; }
/* Below the width the two columns need (13.5rem of navigation, 1.75rem of gap
   and the 48rem measure, plus the body's padding — the measure, not the column
   it sits in: a page column short of its 64rem is only a page column that has
   not reached its cap, while a measure that no longer fits is a page that has),
   the column lies back down
   into a bar above the page — the same wrapping rows it used to be. A drawer
   behind a hamburger would save more height, but it would need script to open,
   and the panel's navigation is six entries: they fit. */
@media (max-width: 66rem) {
  .shell { flex-direction: column; align-items: stretch; gap: 1.2rem; }
  main { margin: 0 auto; }
  .nav {
    position: static; width: auto; max-height: none; overflow-y: visible;
    flex-direction: row; flex-wrap: wrap; align-items: center; gap: 0.4rem 1.2rem;
    padding-bottom: 0.6rem; border-bottom: 1px solid var(--border);
  }
  /* Lying down there is no column for the mark to span, and at its full width
     it would take a row to itself above the entries, so it goes back to the
     compact size it had — enough to read beside them. */
  .nav .brand img { width: 110px; }
  /* Each block keeps its own group of entries together and wraps as one; the
     rules that separated the blocks vertically become the space between them. */
  .nav .links, .nav .session, .nav .sections {
    flex-direction: row; flex-wrap: wrap; align-items: center;
    gap: 0.2rem 0.6rem; padding-top: 0; border-top: 0;
  }
  .sections-title { margin: 0; }
}
/* Status badges: one vocabulary (ok/warn/error/unknown) shared by the server
   status page and the per-domain DNS checks, so a colour means the same thing
   everywhere. The class suffix is the check's own status value. */
.st {
  display: inline-block; padding: 0.05rem 0.45rem; border-radius: 999px;
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  vertical-align: middle; border: 1px solid transparent;
}
.st-ok { background: var(--st-ok-bg); color: var(--st-ok-fg); border-color: var(--st-ok-border); }
.st-warn { background: var(--st-warn-bg); color: var(--st-warn-fg); border-color: var(--st-warn-border); }
.st-error { background: var(--st-error-bg); color: var(--st-error-fg); border-color: var(--st-error-border); }
.st-unknown { background: var(--st-unknown-bg); color: var(--st-unknown-fg); border-color: var(--st-unknown-border); }
/* In the domain list the badge is also the link to that domain's DNS section,
   so it must not pick up the link colour and underline that would fight with
   the badge's own palette. */
a.st, a.st:hover { color: inherit; text-decoration: none; }
a.st:hover { filter: brightness(1.08); }
/* Usage bars on the status page's machine card. <meter> rather than a div sized
   from the reading, because the CSP forbids inline styles (see the note at the
   top of this file) and a bar's length has to travel on an attribute. The
   element grades itself from low/high/optimum, so the colour matches the
   badges' meaning without this file restating the thresholds — and a browser
   that does not render meters falls back to the percentage beside it, which is
   printed either way. */
meter { width: 5rem; height: 0.7rem; vertical-align: middle; margin-right: 0.4rem; }
/* The card's own two narrow columns. Cells may break mid word by default (see
   the th, td rule above), which the detail column needs and these two must not
   have: the resource names and the readings are short, and the long detail
   beside them would otherwise win the width and leave "Memory" broken across
   two lines. */
.metric { white-space: nowrap; }
.code-row { display: flex; align-items: flex-start; gap: 0.5rem; }
.code-row .code { flex: 1; min-width: 0; }

/* Two cards abreast, on the one page that has a pair of them worth reading
   together (a delivery's facts beside its history). auto-fit with a minimum
   rather than a media query: the pair falls into one column exactly when a
   column would drop below the width its content needs, which is also what
   happens inside the narrow column the navigation lies down at. */
.split {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: 1.2rem; margin-top: 1.2rem;
}
/* The gap already spaces the cards inside the grid; the general .card + .card
   rule would add a second gap's worth on top of it, and only to the second
   card, which in one column reads as an uneven stack. The blocks around the
   grid keep their spacing from it instead. */
.split > .card + .card { margin-top: 0; }
.split + .card { margin-top: 1.2rem; }
/* The subject heads a delivery's page, and it is the one heading in the panel
   whose text we do not control: it may be a hundred characters with nothing to
   break on. It wraps to as many lines as it needs (this is the page's name, not
   a table cell to clip) but never widens the column. */
h1.subject { overflow-wrap: anywhere; }
/* The line under it: sender, recipient and outcome. Wrapping rather than
   clipping, since either address may be long and both are the point. The
   vertical margins are longhands for the reason given at the h1 rule above —
   the shorthand would re-zero the auto side margins that keep a page's blocks
   centred on the measure. It sits close under the heading: the two are one
   statement of what this page is about. */
.route {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
  margin-top: -0.5rem; margin-bottom: 1rem;
}
.route .addr { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9rem; overflow-wrap: anywhere; }
.route .arrow { color: #6b7280; }
/* A card's short facts, as a grid of tiles rather than the label-over-value
   stack the rest of the panel uses. That stack is right for the pages where a
   value is a DNS record or a password to copy — one per line, full width — and
   wrong for six values of a few characters each, which down a narrow column
   would be a page of mostly empty rows. Same auto-fit as .split: as many
   columns as fit, and one when nothing else does. */
.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: 0.5rem; margin-top: 1rem; }
.fact { min-width: 0; padding: 0.5rem 0.7rem; border-radius: 8px; background: var(--surface-bg); }
.fact-label {
  display: block; font-size: 0.7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em; color: #6b7280;
}
/* Nothing here is guaranteed to have a space in it — domains, logins and queue
   ids are all one run — so a value may break mid word rather than push its tile
   wider than the column it shares. */
.fact-value { display: block; margin-top: 0.1rem; overflow-wrap: anywhere; }
.fact-value.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; }
/* A message's history: the steps down a line, each with the time it happened,
   the status it reached and what that means. The line is the list's own left
   border and the dots sit on top of it, so nothing has to be positioned against
   a fixed row height — a step's text is two lines or five depending on what
   there is to say about it. */
.timeline { list-style: none; display: flex; flex-direction: column; gap: 1.1rem;
            margin: 1rem 0 0; padding: 0.2rem 0 0.2rem 1.4rem; border-left: 2px solid var(--border); }
.event { position: relative; min-width: 0; }
.event::before {
  content: ""; position: absolute; left: -1.85rem; top: 0.3rem;
  width: 0.65rem; height: 0.65rem; border-radius: 50%;
  background: var(--card-bg); border: 2px solid var(--control-border);
}
/* The dot carries the step's own verdict, in the same four colours as the badge
   beside it — so the shape of the history is readable down the line alone. */
.event.lvl-ok::before { border-color: var(--st-ok-fg); background: var(--st-ok-bg); }
.event.lvl-warn::before { border-color: var(--st-warn-fg); background: var(--st-warn-bg); }
.event.lvl-error::before { border-color: var(--st-error-fg); background: var(--st-error-bg); }
/* The step that has not happened yet — a queued message's delivery report — is
   the one hollow dot, and its text is quiet: it is what to expect, not a record
   of anything. */
.event.pending { opacity: 0.7; }
.event.pending::before { background: var(--card-bg); border-style: dashed; }
.event-time { margin: 0; font-size: 0.75rem; color: #6b7280; }
.event-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin: 0.15rem 0 0; font-weight: 600; }
.event-detail { margin: 0.2rem 0 0; font-size: 0.9rem; }
/* A message's own mail.log lines, as a table of when and what. The time column
   is sized to its content — width:1% is as narrow as the automatic layout will
   take a column, which with the nowrap on td.time settles on exactly the stamp
   — so everything left over goes to the text, which is the column that needs
   it. */
table.log { margin-top: 1rem; }
table.log th:first-child, table.log td.time { width: 1%; }
/* The log's own text: monospace, because it is full of addresses, response
   codes and identifiers that are compared character by character rather than
   read as words. pre-wrap keeps the spacing a server's reply was written with
   while still letting a long line wrap inside the cell instead of widening it
   (the th/td rule above allows the break). */
table.log td.log-text {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem; white-space: pre-wrap;
}
/* Compact outlined button: same affordance as the filled one but quiet enough
   that several can sit together without shouting — the Copy buttons beside a
   value, the controls of a table row or of an application block. Sign out
   overrides this with .danger below since signing out is a deliberate,
   singular action. .actions is the shared hook: a cell that holds controls, or
   the control row of an application. */
button.copy, .actions button, .actions > label.toggle, .actions a.danger, .nav button {
  margin: 0; padding: 0.45rem 0.7rem; font-size: 0.8rem; font-weight: 600;
  border-radius: 6px; white-space: nowrap;
  background: var(--surface-bg); color: #2563eb; border: 1px solid var(--control-border);
}
button.copy:hover, .actions button:hover, .actions > label.toggle:hover,
.actions a.danger:hover, .nav button:hover { background: var(--surface-bg-hover); }
button.copy { flex: none; margin-top: 0.3rem; }
.actions button.danger, .actions a.danger, .nav button.danger {
  color: var(--danger-fg); background: var(--danger-bg); border-color: var(--danger-border);
}
.actions button.danger:hover, .actions a.danger:hover, .nav button.danger:hover { background: var(--danger-bg-hover); }

/* The optional "encrypt this download" block on the backup and export forms.
   Its label is the one checkbox in the panel, so it opts out of the
   block-level label rule above and sits on one line with its box; the fields it
   reveals are indented under it to read as its consequence rather than as three
   more fields of the form. panel.js hides the inner block until the box is
   ticked (and empties it when unticked); without JavaScript everything stays
   visible, which the server handles identically. The import form reuses the
   same indented .encrypt-fields look for its password field, but reveals it
   by file extension instead of a checkbox (see panel.js). */
.encrypt { margin-top: 1.2rem; }
.encrypt label.check {
  display: flex; align-items: center; gap: 0.5rem; margin: 0; font-weight: 600;
}
.encrypt label.check input { width: auto; margin: 0; }
.encrypt-fields {
  margin-top: 1.2rem; margin-left: 1.6rem; padding-left: 0.9rem;
  border-left: 2px solid var(--border);
}
.encrypt .encrypt-fields { margin-top: 0; }
.encrypt-fields label { margin-top: 0.7rem; }
.encrypt-fields .muted { margin: 0.5rem 0 0; font-size: 0.85rem; }
