Files
selfpost/internal/web/view/static/panel.css
T
mixandClaude Opus 5.5 3ba0e9c691 phone-desktop-mode: a Menu with folded sections below 1024 px
The owner, 2026-10-16: "Вариант 1. Но меню выглядит длинной простынёй. Там
бы сделать сворачивание разделов." As the mockup of e0e30e2: the bar's
navbar-menu loses is-active, so Bulma hides it below 1024 px, and the new
touchnav partial (layout.html) takes its place in the brand - Menu, a
<details> whose panel holds Overview, each group as a fold (the current
one open, its page aria-current), and the user's group last with Account,
Help and Sign out (the same POST form as the bar's). The same Show* flags
as the bar decide what is there. panel.css takes the mockup's rules (the
touch navigation, a 1rem gutter for the page and the sibling strip below
1024 px). No script.

The cause, measured, is written in the roadmap row: every width under
1024 px had the always-open menu; "desktop site" (~980 px) added Bulma's
side-by-side columns from 769 px; Bulma's container has no side margin
below 1024 px.

Tests: the Menu marks the current group and page and the bar's menu has
no is-active (TestMenuHighlightsTheCurrentGroupEntryAndSibling), it offers
nothing the bar does not to a domain administrator (TestTouchMenuFollowsTheBar),
the shell's Sign out is counted twice on the password page;
TestPanelCSSIsTheMockupStylesheet. go vet + go test (26 packages) and make
e2e on the test server for this tree. CHANGELOG [Unreleased]; roadmap row
done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:03:18 +03:00

418 lines
26 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* The panel's stylesheet: the brand layer (fonts, palette) followed by the
theme, taken rule for rule from the accepted mockups in
docs/assets/panel-redesign/ (shared/brand.css, panel/theme.css). A rule is
added here only after it exists there — a design change is made in the
mockup first (docs/plans/panel-redesign.md § The contract). The font files
and perf-edge.svg sit beside this file in /static. TestPanelCSSIsTheMockupStylesheet
holds this file to that. */
/* Brand layer shared by the three redesign directions: the panel's own fonts
and the palette of the mark (docs/assets/selfpost-proof.html). Each
direction maps these tokens onto its library's variables in its own
theme.css — nothing here knows about Pico, Tabler or Bulma.
Mockup note: no inline style="" and no inline <script> anywhere in this
folder, so every screen is drawable under the panel's CSP as it stands. */
@font-face {
font-family: "IBM Plex Sans"; font-style: normal; font-weight: 100 700;
font-display: swap;
src: url("ibm-plex-sans.woff2") format("woff2");
}
@font-face {
font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400;
font-display: swap;
src: url("ibm-plex-mono-400.woff2") format("woff2");
}
@font-face {
font-family: "IBM Plex Mono"; font-style: normal; font-weight: 600;
font-display: swap;
src: url("ibm-plex-mono-600.woff2") format("woff2");
}
:root {
--sp-sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--sp-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--sp-paper: #f4f2ed; /* page */
--sp-stamp-paper: #f3ede1; /* the mark's own paper */
--sp-sheet: #fff;
--sp-ink: #12161c;
--sp-ink-2: #2a313b;
--sp-muted: #5c6774; /* 5:1 on the paper and on the tinted cards; #6b7684 was 4.1:1 */
--sp-rule: #dedcd7;
--sp-rule-strong: #cbc8c1;
--sp-well: #efede9; /* code / read-only values */
--sp-brick: #7a3b2e;
--sp-brick-dark: #632f25;
--sp-brick-tint: #ede4de;
--sp-on-brick: #f6ede2;
--sp-ok: #2c6b43; --sp-ok-bg: #edf6ef; --sp-ok-rule: #c2e0cc;
--sp-warn: #8a5510; --sp-warn-bg: #fbf2e2; --sp-warn-rule: #ebd5a6;
--sp-fail: #b42318; --sp-fail-bg: #fbedea; --sp-fail-rule: #efccc4;
--sp-idle: #6b7684; --sp-idle-bg: #efede9; --sp-idle-rule: #dedcd7;
}
/* The accepted direction: C ("Counter", Bulma) as the base, the postal
signature of A (postmark, numbered mono section heads, the stamp's
perforation) and the icons of B. Bulma carries navbar, dropdowns, columns,
boxes, tables, tags, menu, forms; what follows is brand mapping plus the
handful of components that are SelfPost's own. */
:root {
--bulma-family-primary: var(--sp-sans);
--bulma-family-secondary: var(--sp-sans);
--bulma-family-code: var(--sp-mono);
--bulma-primary-h: 10deg; --bulma-primary-s: 45%; --bulma-primary-l: 33%;
--bulma-link-h: 10deg; --bulma-link-s: 45%; --bulma-link-l: 33%;
--bulma-success-h: 138deg; --bulma-success-s: 42%; --bulma-success-l: 30%;
--bulma-warning-h: 36deg; --bulma-warning-s: 85%; --bulma-warning-l: 55%;
--bulma-danger-h: 4deg; --bulma-danger-s: 76%; --bulma-danger-l: 40%;
--bulma-radius: 6px; --bulma-radius-large: 10px;
--bulma-body-size: 16px;
/* The type scale. Three sizes carry all running text; nothing a person has
to read is below 13px. check.py enforces the floor.
body 16px text, inputs, buttons, menus, table cells
s 14px supporting text: labels, help, notes, footers, mono values, logs
xs 13px upper-case mono micro-labels and tags only */
--sp-fs-s: .875rem;
--sp-fs-xs: .8125rem;
--bulma-link-text: var(--sp-brick);
--bulma-link-text-hover: var(--sp-brick-dark);
}
/* Bulma picks a button's text colour from the lightness of its fill; our brick
and our red are dark enough to land on the wrong side and get near-black
text (3:1). Solid buttons are white on colour; the light variants keep
Bulma's dark-on-tint. */
.button.is-primary:not(.is-light), .button.is-primary:not(.is-light):hover,
.button.is-danger:not(.is-light), .button.is-danger:not(.is-light):hover { color: #fff; }
a strong { color: inherit; }
html, body { background: var(--sp-paper); }
body { color: var(--sp-ink); min-height: 100vh; display: flex; flex-direction: column; }
main { flex: 1; }
code { background: var(--sp-well); color: var(--sp-ink); border-radius: 3px; padding: .1em .35em; font-size: .95em; }
.ti { font-size: 1.15em; vertical-align: -.12em; }
.button .ti { margin-right: .35rem; }
.sp-muted { color: var(--sp-muted); }
/* Bulma ships these at .75rem; on the scale they are s or xs. `sp-small`
replaces Bulma's `is-size-7` helper, which is .75rem and !important. */
.help, .label { font-size: var(--sp-fs-s); }
.tag, .menu-label { font-size: var(--sp-fs-xs); }
.menu-label { color: var(--sp-muted); } /* Bulma's grey is 4.2:1 on the paper */
.sp-small { font-size: var(--sp-fs-s); }
.sp-mono { font-family: var(--sp-mono); }
.sp-nowrap { white-space: nowrap; }
/* ---- navbar, the perforated edge under it, sibling pages below */
.navbar.is-primary { --bulma-navbar-height: 4rem; }
/* The name, not the stamp: at this size the stamp's perforation is under a
pixel and its brick field dissolves into the bar. The stamp stays where it
has room — sign-in, setup, the favicon. */
.navbar-brand img { max-height: none; height: 1.45rem; }
.navbar.is-primary .navbar-item, .navbar.is-primary .navbar-link { color: var(--sp-on-brick); font-weight: 500; }
.navbar.is-primary .navbar-link::after { border-color: var(--sp-on-brick); }
.navbar.is-primary .navbar-link .ti { margin-right: .4rem; }
/* Bulma shrinks dropdown items to .875rem; a menu entry is read as often as
the bar it hangs from, so it gets the bar's size. */
.navbar-dropdown { border-top: 0; box-shadow: 0 8px 24px rgb(18 22 28 / .14); font-size: 1rem; }
.navbar.is-primary .navbar-dropdown .navbar-item { color: var(--sp-ink); font-weight: 400; padding-right: 2rem; }
.navbar.is-primary .navbar-dropdown .navbar-item .ti { margin-right: .6rem; color: var(--sp-muted); }
.navbar.is-primary .navbar-dropdown .navbar-item.is-active { color: var(--sp-brick); font-weight: 600; background: var(--sp-brick-tint); }
.navbar.is-primary .navbar-dropdown .navbar-item.is-active .ti { color: var(--sp-brick); }
/* Sign out is a POST, so it is a form with a button rather than a link. Bulma
tints only a.navbar-item; the button is handed the same variables, and the
form stretches it across the menu, so it answers the pointer, the keyboard
and a press exactly like the entries above it. */
.navbar-dropdown form { display: flex; }
.navbar-dropdown button.navbar-item { flex: 1; cursor: pointer; }
.navbar-dropdown button.navbar-item:hover, .navbar-dropdown button.navbar-item:focus { --bulma-navbar-item-background-l-delta: var(--bulma-navbar-item-hover-background-l-delta); }
.navbar-dropdown button.navbar-item:active { --bulma-navbar-item-background-l-delta: var(--bulma-navbar-item-active-background-l-delta); }
.navbar.is-primary .sp-current > .navbar-link, .navbar.is-primary a.navbar-item.sp-current { background: rgb(0 0 0 / .18); }
/* Below 1024 px Bulma hides the bar's menu (navbar-menu without is-active)
and the touch navigation takes its place: Menu at the right of the brand
opens a panel over the page, its sections folded but the current one.
<details> all through, so it needs no script (owner, 2026-10-16: the menu
had been a sheet of every section open, filling the first screen). */
.sp-touchnav { display: none; }
.sp-touchnav summary { list-style: none; cursor: pointer; }
.sp-touchnav summary::-webkit-details-marker { display: none; }
.sp-touchnav summary::after { content: "›"; display: inline-block; margin-left: .5rem; font-size: 1.3rem; line-height: 1; transform: rotate(90deg); transition: transform .15s; }
.sp-touchnav[open] > summary::after, .sp-touchnav-group[open] > summary::after { transform: rotate(-90deg); }
.sp-touchnav > summary { display: flex; align-items: center; padding: 0 1rem; color: var(--sp-on-brick); font-weight: 500; }
.sp-touchnav-panel { position: absolute; top: 100%; left: 0; right: 0; z-index: 30; background: var(--sp-brick); box-shadow: 0 12px 28px rgb(18 22 28 / .22); padding-bottom: .4rem; }
.sp-touchnav-panel > a, .sp-touchnav-group > summary { display: flex; align-items: center; gap: .45rem; padding: .75rem 1rem; color: var(--sp-on-brick); font-weight: 500; }
.sp-touchnav-group > summary::after { margin-left: auto; }
.sp-touchnav-panel > a[aria-current], .sp-touchnav-group[open] > summary { background: rgb(0 0 0 / .18); }
.sp-touchnav-group a, .sp-touchnav-group button { display: flex; align-items: center; gap: .6rem; padding: .65rem 1rem .65rem 2rem; background: #fff; color: var(--sp-ink); border: 0; font: inherit; text-align: left; cursor: pointer; }
.sp-touchnav-group form { display: flex; }
.sp-touchnav-group button { flex: 1; }
.sp-touchnav-group .ti { color: var(--sp-muted); }
.sp-touchnav-group a[aria-current] { color: var(--sp-brick); font-weight: 600; background: var(--sp-brick-tint); }
.sp-touchnav-group a[aria-current] .ti { color: var(--sp-brick); }
/* The header is built like the mark: a brick field, then the stamp's paper
margin, and the perforation runs along the outer edge of that margin — round
holes, flat teeth, the mark's ink hairline (perf-edge.svg is one tooth and
one hole at the mark's 2.2 / 7.03 ratio). On the mark the margin is one
pitch deep and a tenth of the brick field's height; here that is 8px under
a 60px bar — a little over a tenth, so a 2.5px hole still reads as round. */
.sp-perf {
height: 8px;
background:
url("perf-edge.svg") repeat-x left bottom / 8px 4px,
linear-gradient(var(--sp-stamp-paper), var(--sp-stamp-paper)) no-repeat left top / 100% 4px;
}
.sp-subnav { border-bottom: 1px solid var(--sp-rule); margin-top: -1px; }
.sp-subnav .container { display: flex; gap: 1.8rem; overflow-x: auto; }
.sp-subnav a { color: var(--sp-muted); padding: .75rem 0 .65rem; white-space: nowrap; border-bottom: 2px solid transparent; }
.sp-subnav a .ti { margin-right: .4rem; }
.sp-subnav a:hover { color: var(--sp-ink); }
.sp-subnav a[aria-current] { color: var(--sp-ink); font-weight: 600; border-bottom-color: var(--sp-brick); }
/* ---- page head */
.sp-page { padding-block: 1.8rem 2.5rem; }
.sp-head { display: flex; align-items: center; gap: 1.2rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.sp-head .sp-grow { flex: 1; min-width: 14rem; }
.sp-kicker { font-family: var(--sp-mono); font-size: var(--sp-fs-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--sp-muted); }
.sp-kicker a { color: inherit; }
.sp-kicker a:hover { color: var(--sp-brick); }
.title { color: var(--sp-ink); font-weight: 600; }
.sp-head .title { margin: 0; overflow-wrap: anywhere; }
.sp-head .sp-lead { color: var(--sp-muted); margin-top: .2rem; max-width: 48rem; }
.sp-head .buttons { margin: 0; }
.sp-head .buttons > * { margin-bottom: 0; }
/* ---- postmark */
.sp-postmark {
flex: none; width: 6rem; height: 6rem; border-radius: 50%; transform: rotate(-9deg);
border: 2px solid currentColor; outline: 1px solid currentColor; outline-offset: 3px; margin: .3rem .5rem;
display: grid; place-content: center; text-align: center; color: var(--sp-ok);
font-family: var(--sp-mono); font-weight: 600; letter-spacing: .08em; line-height: 1.15;
}
.sp-postmark.sp-warn { color: var(--sp-warn); }
.sp-postmark.sp-fail { color: var(--sp-fail); }
.sp-postmark b { font-size: 1.3rem; display: block; }
/* Below the floor on purpose: an imprint, and the heading beside it says the same in words. */
.sp-postmark span { font-size: .6rem; display: block; }
/* ---- box with a numbered mono head */
.box { box-shadow: none; border: 1px solid var(--sp-rule); padding: 0; overflow: hidden; }
.box > .sp-box-head {
display: flex; flex-wrap: wrap; align-items: center; gap: .7rem; padding: .7rem 1.25rem; border-bottom: 1px solid var(--sp-rule);
}
.sp-box-head form.sp-end { justify-content: flex-end; }
/* Bulma's .input is 100% wide; in the filter row it takes a flex basis instead, so it sits beside the selects. */
.sp-box-head form.sp-end > .input { flex: 0 1 10.5rem; }
.sp-box-head .sp-no { font-family: var(--sp-mono); font-size: var(--sp-fs-xs); font-weight: 600; color: var(--sp-brick); }
.sp-box-head h2 { font-family: var(--sp-mono); font-size: var(--sp-fs-xs); font-weight: 600; letter-spacing: .1em; text-transform: uppercase; margin: 0; }
.sp-box-head .sp-end { margin-left: auto; color: var(--sp-muted); font-size: var(--sp-fs-s); display: flex; gap: .6rem; align-items: center; }
.sp-box-head .sp-help { color: var(--sp-muted); line-height: 1; }
.sp-box-head .sp-help:hover { color: var(--sp-brick); }
.sp-box-head .sp-end + .sp-help { margin-left: 0; }
.sp-box-head h2 + .sp-help { margin-left: auto; }
.notification { border: 1px solid transparent; padding: .8rem 1.25rem; }
.notification.is-success.is-light { border-color: var(--sp-ok-rule); }
.notification.is-danger.is-light { border-color: var(--sp-fail-rule); }
.notification .ti { margin-right: .5rem; }
.box > .sp-box-body { padding: 1.25rem; }
.box > .sp-box-body > :last-child { margin-bottom: 0; }
/* A table never sits bare in a box: the container lets a wide one scroll
sideways instead of being cut off by the box's rounded overflow. */
.box > .table-container, .box > .table-container > .table { margin: 0; }
.sp-box-foot .sp-end { margin-left: auto; }
/* A filter form in the head keeps full-size controls; the head gives up its
own padding to them instead of shrinking the text. */
.sp-box-head form.sp-end { margin-block: -.35rem; font-size: 1rem; }
.box > .sp-box-foot {padding: .7rem 1.25rem; border-top: 1px solid var(--sp-rule); color: var(--sp-muted); font-size: var(--sp-fs-s); display: flex; gap: 1rem; align-items: center; }
.box.sp-danger-zone { border-color: var(--sp-fail-rule); }
.box.sp-danger-zone .sp-box-head { background: var(--sp-fail-bg); border-color: var(--sp-fail-rule); }
.box.sp-credential { border-color: #e0c874; }
.box.sp-credential .sp-box-head { background: #fcf6e4; border-color: #e0c874; }
.table { background: transparent; }
.table thead th { font-family: var(--sp-mono); font-size: var(--sp-fs-xs); font-weight: 400; letter-spacing: .08em; text-transform: uppercase; color: var(--sp-muted); }
/* Addresses in tables carry a <wbr> after the @ (one template function), so a
long one breaks where it reads naturally and its column can still shrink;
`overflow-wrap: anywhere` would do it too, but mid-word. */
.table td, .table th { vertical-align: middle; padding: .55rem .6rem; }
.box > .table-container td:first-child, .box > .table-container th:first-child { padding-left: 1.25rem; }
.box > .table-container td:last-child, .box > .table-container th:last-child { padding-right: 1.25rem; }
.table td.sp-actions { text-align: right; white-space: nowrap; }
/* An action that changes state (New password, Delete) is a POST, so it is a form
with a button, not a link. The form sits inline like the links beside it and
the button is handed the link's colour and type; Bulma already resets the
button and shares the pointer and the focus ring between a and button, and a
link here has no hover colour of its own. */
.table td.sp-actions a, .table td.sp-actions form { margin-left: .9rem; }
.table td.sp-actions form { display: inline; }
.table td.sp-actions button { color: var(--bulma-link-text); font: inherit; }
.table td.sp-clip { max-width: 13rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tag { font-family: var(--sp-mono); font-weight: 600; letter-spacing: .04em; }
.sp-empty { padding: 2.2rem 1.25rem; text-align: center; color: var(--sp-muted); }
.sp-empty .ti { font-size: 2rem; display: block; margin-bottom: .4rem; color: var(--sp-rule-strong); }
/* ---- health cards */
/* Six cards: six, three or two columns — never a count that leaves one card alone on a row. */
.sp-health { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0; }
.sp-health > a {
display: flex; gap: .7rem; align-items: center; padding: .75rem 1rem; color: var(--sp-ink); min-width: 0;
border-right: 1px solid var(--sp-rule); border-bottom: 1px solid var(--sp-rule); margin: 0 -1px -1px 0;
}
.sp-health > a:hover { background: var(--sp-paper); }
.sp-health > a > span:last-child { min-width: 0; }
.sp-h-icon { flex: none; width: 2.1rem; height: 2.1rem; border-radius: 7px; display: grid; place-content: center; background: var(--sp-ok-bg); color: var(--sp-ok); }
.sp-h-icon .ti { font-size: 1.25rem; vertical-align: 0; }
.sp-health > a.sp-warn { background: var(--sp-warn-bg); }
.sp-health > a.sp-warn .sp-h-icon { background: #f6e2b8; color: #8a5510; }
.sp-health > a.sp-fail { background: var(--sp-fail-bg); }
.sp-health > a.sp-fail .sp-h-icon { background: #f5d3cc; color: var(--sp-fail); }
/* Each line is a block with its own tight line height; a verdict is two or
three words and never wraps — a wrapped card would stretch the whole row. */
.sp-h-name, .sp-h-value, .sp-h-sub { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sp-h-name { font-family: var(--sp-mono); font-size: var(--sp-fs-xs); letter-spacing: .08em; text-transform: uppercase; color: var(--sp-muted); line-height: 1.3; }
.sp-h-value { font-weight: 600; line-height: 1.3; }
.sp-h-sub { font-size: var(--sp-fs-s); color: var(--sp-muted); line-height: 1.3; }
/* ---- facts, records, side menu */
.sp-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 1rem 1.5rem; margin: 0; }
.sp-facts dt { font-family: var(--sp-mono); font-size: var(--sp-fs-xs); letter-spacing: .08em; text-transform: uppercase; color: var(--sp-muted); }
.sp-facts dd { margin: 0; overflow-wrap: anywhere; }
.sp-facts dd.sp-big { font-size: 1.5rem; font-weight: 600; line-height: 1.2; }
.sp-side { position: sticky; top: 1.5rem; }
.menu-label { font-family: var(--sp-mono); letter-spacing: .1em; }
.menu-list a { display: flex; align-items: center; gap: .55rem; }
.menu-list a .ti { color: var(--sp-muted); }
.menu-list a.is-active .ti { color: inherit; }
.menu-list a .tag { margin-left: auto; }
.sp-record { padding: 1.25rem; }
.sp-record + .sp-record { border-top: 1px solid var(--sp-rule); }
.sp-record-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .75rem; }
.sp-record-head h3 { font-size: 1rem; font-weight: 600; margin: 0; }
.sp-record-head small { margin-left: auto; color: var(--sp-muted); text-align: right; }
/* A record DNS already holds as shown is folded to its head: a <details>
whose summary is the head, opened without script. */
details.sp-record > summary { list-style: none; cursor: pointer; }
details.sp-record > summary::-webkit-details-marker { display: none; }
details.sp-record:not([open]) > summary { margin-bottom: 0; }
details.sp-record:not([open]) > summary small { display: none; }
details.sp-record[open] .sp-record-head small + .sp-record-toggle { margin-left: 0; }
.sp-record-toggle { margin-left: auto; display: inline-flex; align-items: center; gap: .3rem; color: var(--sp-brick); font-size: var(--sp-fs-s); white-space: nowrap; }
.sp-record-toggle span + span { display: none; }
details.sp-record[open] .sp-record-toggle span { display: none; }
details.sp-record[open] .sp-record-toggle span + span { display: inline; }
.sp-record .label, .sp-form .label { font-weight: 500; color: var(--sp-ink-2); margin-bottom: .25rem; }
.sp-record .label { font-weight: 400; color: var(--sp-muted); }
.input[readonly], .textarea[readonly] { font-family: var(--sp-mono); font-size: var(--sp-fs-s); background: var(--sp-well); box-shadow: none; }
/* Bulma sizes a control from its own font; the smaller mono text made the
value shorter than the Copy button joined to it. */
.input[readonly] { height: 2.5rem; }
.textarea[readonly] { min-height: 2.5rem; }
.textarea[readonly] { resize: none; min-height: 0; }
.sp-record .field:not(:last-child) { margin-bottom: .6rem; }
.sp-row { display: grid; grid-template-columns: 1fr 7rem; gap: .75rem; }
.sp-pair { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; }
/* ---- logs, timeline, help */
.sp-log {
margin: 0; padding: 1rem 1.25rem; background: #191c21; color: #d9d5cc; border-radius: 0;
font-family: var(--sp-mono); font-size: var(--sp-fs-s); line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere;
}
/* One line of a log: long ones wrap inside the pane, the rest of the line
indented under its start, so nothing scrolls sideways - a phone drew the
scroll bars over the text (owner, 2026-10-16). */
.sp-log .sp-ll { display: block; padding-left: 4ch; text-indent: -4ch; }
.sp-log .sp-w { color: #e0b15c; } .sp-log .sp-e { color: #ef8f82; } .sp-log .sp-d { color: #7f8894; }
.sp-timeline { list-style: none; margin: 0; padding: 0; }
.sp-timeline li { position: relative; padding: 0 0 1.1rem 1.6rem; border-left: 2px solid var(--sp-rule); margin-left: .4rem; }
.sp-timeline li:last-child { padding-bottom: 0; border-left-color: transparent; }
.sp-timeline li::before { content: ""; position: absolute; left: -7px; top: .25rem; width: 12px; height: 12px; border-radius: 50%; background: var(--sp-ok); box-shadow: 0 0 0 3px #fff; }
.sp-timeline li.sp-warn::before { background: #d9962b; }
.sp-timeline li.sp-fail::before { background: var(--sp-fail); }
.sp-timeline li.sp-pending::before { background: #fff; border: 2px solid var(--sp-rule-strong); }
.sp-timeline time { font-family: var(--sp-mono); font-size: var(--sp-fs-xs); color: var(--sp-muted); display: block; }
.sp-route { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; font-family: var(--sp-mono); font-size: var(--sp-fs-s); margin-top: .3rem; }
.sp-help-topic + .sp-help-topic { border-top: 1px solid var(--sp-rule); }
.sp-help-topic { padding: 1rem 1.25rem; }
.sp-help-topic h3 { font-weight: 600; margin-bottom: .3rem; }
.sp-list-check { list-style: none; margin: 0 0 1rem; }
.sp-list-check li { padding-left: 1.7rem; position: relative; margin-bottom: .45rem; }
.sp-list-check li .ti { position: absolute; left: 0; top: .15rem; color: var(--sp-fail); }
/* ---- signed-out pages */
.sp-signin { min-height: 100vh; margin: 0; }
.sp-signin-brand { background: var(--sp-brick); display: grid; place-content: center; text-align: center; padding: 3rem; position: relative; }
.sp-signin-brand::after {
content: ""; position: absolute; top: 0; bottom: 0; right: -7px; width: 7px; z-index: 1;
background: radial-gradient(circle at 0 7px, var(--sp-brick) 5px, transparent 5.6px); background-size: 7px 14px;
}
.sp-signin-brand img { width: 20rem; max-width: 100%; height: auto; }
.sp-signin-brand p { font-family: var(--sp-mono); letter-spacing: .18em; font-size: var(--sp-fs-xs); color: var(--sp-on-brick); margin-top: 1.6rem; }
.sp-signin-form { display: flex; flex-direction: column; justify-content: center; padding: 3rem; }
.sp-signin-form .sp-form { width: 22rem; max-width: 100%; margin-inline: auto; }
.sp-foot { color: var(--sp-muted); font-size: var(--sp-fs-s); padding-block: 1.5rem 0; }
.sp-foot a { color: inherit; text-decoration: underline; }
.sp-signin-form .sp-foot { text-align: center; margin-top: 2.5rem; }
@media (max-width: 1407px) { .sp-health { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 1023px) {
.sp-box-head form.sp-end { flex-wrap: wrap; }
.sp-touchnav { display: flex; margin-left: auto; }
/* Bulma gives a container side margins only from 1024 px: below it the
page met the screen's edge. */
.sp-subnav .container, .container.sp-page { padding-inline: 1rem; }
}
@media (max-width: 768px) {
.sp-health { grid-template-columns: repeat(2, 1fr); }
.sp-row, .sp-pair { grid-template-columns: 1fr; }
.sp-record-head { flex-wrap: wrap; }
.sp-record-head small { margin-left: 0; text-align: left; flex-basis: 100%; order: 1; }
details.sp-record[open] .sp-record-head small + .sp-record-toggle { margin-left: auto; }
.sp-postmark { display: none; }
.sp-cards { display: flex; flex-direction: column; padding-inline: .75rem; }
.sp-cards > .column { display: contents; }
.sp-cards .box { margin-bottom: 1.5rem; }
.sp-cards [data-order="1"] { order: 1; }
.sp-cards [data-order="2"] { order: 2; }
.sp-cards [data-order="3"] { order: 3; }
.sp-cards [data-order="4"] { order: 4; }
.sp-cards [data-order="5"] { order: 5; }
.sp-cards [data-order="6"] { order: 6; }
.sp-cards [data-order="7"] { order: 7; }
.sp-cards [data-order="8"] { order: 8; }
.sp-cards [data-order="9"] { order: 9; }
.sp-cards [data-order="10"] { order: 10; }
.sp-cards [data-order="11"] { order: 11; }
.sp-cards [data-order="12"] { order: 12; }
}
/* cards: two stacks of boxes that are read in rows - the odd ones on the left,
the even ones on the right, each stack closed up so a short box leaves no
gap beside a tall one. On a phone the stacks dissolve and the boxes come in
the order of their numbers: each box carries data-order, its own number (the
rules above, up to twelve), wherever it stands in its stack. */
/* sheet: what does not fit a table row, opened over the page from the row's
tag. It is the browser's own popover (the tag is a button with
popovertarget), so it needs no script: Esc, a click outside and the close
button all shut it, and focus returns to the tag. It rises from the bottom
edge across the whole window - the one component that is not laid out by the
page, hence the widths - with its content, a table of name and value, in a
column of reading width in the middle. */
.sp-sheet {
inset: auto 0 0 0; width: auto; margin: 0; padding: 0; border: 0; border-top: 3px solid var(--sp-brick);
max-height: 75vh; overflow: auto; background: #fff; color: inherit; box-shadow: 0 -12px 40px rgba(40, 20, 15, .22);
}
.sp-sheet-in { max-width: 44rem; margin: 0 auto; padding: 0 1.25rem; }
.sp-sheet::backdrop { background: rgba(40, 20, 15, .38); }
.sp-sheet-head { display: flex; align-items: center; gap: .7rem; padding: 1rem 0 .8rem; border-bottom: 1px solid var(--sp-rule); }
.sp-sheet-head h2 { font-size: 1.15rem; font-weight: 600; margin-right: auto; }
.sp-sheet-head button { border: 0; background: none; cursor: pointer; font-size: 1.3rem; color: var(--sp-muted); }
.sp-sheet-body { padding: .4rem 0 1.4rem; }
/* The sheet's content is a table of name and value: the name is a label, set like the dt of facts. */
.sp-sheet .table th { font-family: var(--sp-mono); font-size: var(--sp-fs-xs); font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--sp-muted); white-space: nowrap; vertical-align: top; width: 1%; padding-right: 2rem; }
.sp-sheet .table td { overflow-wrap: anywhere; }
/* A tag that opens a sheet is a button: it keeps the tag's look and says it can
be pressed with the dotted underline links to more detail carry. */
button.tag { border: 0; cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }