d35b309714
A non-Latin subject arrives as RFC 2047 encoded-words, which the send-log printed verbatim: unreadable, and one unbreakable run wide enough to push the Status column out of its card. Decode at journal time (UTF-8/ASCII; exotic charsets keep the raw header) and cap at 200 characters, then clip the column to one line with the full text in the tooltip so no subject can widen the row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
209 lines
12 KiB
CSS
209 lines
12 KiB
CSS
/* 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 (phase 14.A). Any rule
|
|
added here must therefore stay here: an inline style="..." attribute in a
|
|
template is blocked by that policy and silently does nothing. */
|
|
|
|
:root { color-scheme: light dark; }
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
margin: 0; padding: 2rem 1rem; background: #f6f7f9; color: #1b1f24;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
body { background: #14171a; color: #e6e8eb; }
|
|
.card { background: #1d2125 !important; border-color: #2b3138 !important; }
|
|
input { background: #14171a !important; color: inherit !important; border-color: #2b3138 !important; }
|
|
}
|
|
main { max-width: 42rem; margin: 0 auto; }
|
|
h1 { font-size: 1.4rem; margin: 0 0 1rem; }
|
|
.card {
|
|
background: #fff; border: 1px solid #e2e5e9; 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 #cfd4da; border-radius: 6px; background: #fff;
|
|
}
|
|
/* 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 <details> 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; }
|
|
main { max-width: 48rem; }
|
|
.card + .card { margin-top: 1.2rem; }
|
|
.flash { background: #ecfdf3; border: 1px solid #abefc6; color: #067647; padding: 0.7rem 1rem; border-radius: 8px; margin-bottom: 1.2rem; }
|
|
@media (prefers-color-scheme: dark) { .flash { background: #0d2818 !important; border-color: #1a5336 !important; color: #75d99b !important; } }
|
|
table { width: 100%; border-collapse: collapse; }
|
|
th, td { text-align: left; padding: 0.5rem 0.4rem; border-bottom: 1px solid #e2e5e9; }
|
|
@media (prefers-color-scheme: dark) { th, td { border-color: #2b3138 !important; } }
|
|
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, and a table sizes a
|
|
column to its longest unbreakable run — a long subject widens the row until
|
|
Status hangs past the card's edge. 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; the full subject stays in the tooltip. */
|
|
td.subject span {
|
|
display: block; max-width: 16rem;
|
|
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: #f0f2f4; border: 1px solid #e2e5e9; border-radius: 6px; padding: 0.7rem 0.8rem; margin: 0.3rem 0 0; }
|
|
@media (prefers-color-scheme: dark) { .code { background: #14171a !important; border-color: #2b3138 !important; } }
|
|
h2 { font-size: 1.05rem; margin: 0 0 0.4rem; }
|
|
.back { display: inline-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 #cfd4da; border-radius: 6px; background: #fff; color: inherit;
|
|
font-family: inherit;
|
|
}
|
|
textarea { resize: vertical; }
|
|
@media (prefers-color-scheme: dark) {
|
|
select, textarea { background: #14171a !important; color: inherit !important; border-color: #2b3138 !important; }
|
|
}
|
|
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); the marker is dropped because the pressed background
|
|
already shows the open state. */
|
|
.actions summary { display: inline-block; list-style: none; cursor: pointer; }
|
|
.actions summary::-webkit-details-marker { display: none; }
|
|
/* 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 48rem. 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 #e2e5e9; }
|
|
.app:last-child { padding-bottom: 0; }
|
|
@media (prefers-color-scheme: dark) { .app { border-color: #2b3138 !important; } }
|
|
.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; }
|
|
/* An open panel claims a row to itself: its textareas and number inputs want
|
|
the block's full width, not the width of the summary that opened them. */
|
|
.app .actions > details[open] { flex: 1 0 100%; }
|
|
details form { margin-top: 0.6rem; }
|
|
.credential { border-color: #f5c518; background: #fffbeb; }
|
|
@media (prefers-color-scheme: dark) { .credential { background: #2a2408 !important; border-color: #6b5a10 !important; } }
|
|
/* Panel navigation: rendered once from the layout, so it is present on every
|
|
authenticated page without each content template having to include it. */
|
|
/* Two deliberate rows, not one. The six page entries need about 660px and the
|
|
session block another 260px, against the 738px the panel is wide — so the bar
|
|
was wrapping on its own, and the session landed left-aligned directly under
|
|
the entries, reading as a third row of navigation. Shortening labels would buy
|
|
the ~200px back but undo the point of naming each entry after its page. Rows
|
|
rather than columns: a two-column grid of entries came out a third taller
|
|
(107px against 83px) and no easier to read.
|
|
The session sits on top, at the right edge, and the page entries below — the
|
|
usual arrangement, and the layout template puts the session first so the
|
|
reading and tab order follow what the eye sees rather than being flipped by a
|
|
CSS `order`. The cost is that Sign out is the bar's first tab stop; nothing
|
|
activates on focus, so that is a reordering, not a hazard. */
|
|
.nav {
|
|
display: flex; flex-direction: column; align-items: stretch;
|
|
gap: 0.5rem; margin-bottom: 1.2rem; padding-bottom: 0.6rem;
|
|
border-bottom: 1px solid #e2e5e9;
|
|
}
|
|
@media (prefers-color-scheme: dark) { .nav { border-color: #2b3138 !important; } }
|
|
.nav .links, .nav .session { display: flex; flex-wrap: wrap; gap: 0.2rem 0.9rem; align-items: center; }
|
|
/* Pushed to the far edge so the two rows read as two blocks rather than as one
|
|
ragged list that happened to wrap. */
|
|
.nav .session { justify-content: flex-end; }
|
|
/* 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 bar centres its items
|
|
instead of aligning them on the text baseline. Account is included: it is a
|
|
page like the others and would otherwise be the one bare word in the bar. */
|
|
.nav a, .nav [aria-current] {
|
|
display: inline-flex; align-items: center; gap: 0.4rem;
|
|
padding: 0.2rem 0.5rem; border-radius: 6px;
|
|
}
|
|
.nav [aria-current] {
|
|
font-weight: 600; color: #1b1f24; background: #e6ebf5; box-shadow: inset 0 -2px 0 #2563eb;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
.nav [aria-current] { color: #e6e8eb !important; background: #22303f !important; }
|
|
}
|
|
/* 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: inline-flex; align-items: center; gap: 0.4rem; }
|
|
/* 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; }
|
|
/* 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: #ecfdf3; color: #067647; border-color: #abefc6; }
|
|
.st-warn { background: #fffaeb; color: #b54708; border-color: #fedf89; }
|
|
.st-error { background: #fef3f2; color: #b42318; border-color: #fecdca; }
|
|
.st-unknown { background: #f0f2f4; color: #6b7280; border-color: #e2e5e9; }
|
|
@media (prefers-color-scheme: dark) {
|
|
.st-ok { background: #0d2818 !important; color: #75d99b !important; border-color: #1a5336 !important; }
|
|
.st-warn { background: #2e2308 !important; color: #f5c86b !important; border-color: #6b5210 !important; }
|
|
.st-error { background: #2d1211 !important; color: #f5a29b !important; border-color: #6b201a !important; }
|
|
.st-unknown { background: #22262b !important; color: #9aa3ad !important; border-color: #2b3138 !important; }
|
|
}
|
|
.code-row { display: flex; align-items: flex-start; gap: 0.5rem; }
|
|
.code-row .code { flex: 1; min-width: 0; }
|
|
/* 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 summary, .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: #eef1f5; color: #2563eb; border: 1px solid #cfd4da;
|
|
}
|
|
button.copy:hover, .actions button:hover, .actions summary:hover,
|
|
.actions a.danger:hover, .nav button:hover { background: #e2e7ee; }
|
|
button.copy { flex: none; margin-top: 0.3rem; }
|
|
.actions details[open] > summary { background: #dde3ec; }
|
|
.actions button.danger, .actions a.danger, .nav button.danger {
|
|
color: #b42318; background: #fef3f2; border-color: #fecdca;
|
|
}
|
|
.actions button.danger:hover, .actions a.danger:hover, .nav button.danger:hover { background: #fee4e2; }
|
|
@media (prefers-color-scheme: dark) {
|
|
button.copy, .actions button, .actions summary, .actions a.danger, .nav button {
|
|
background: #22262b !important; border-color: #2b3138 !important;
|
|
}
|
|
button.copy:hover, .actions button:hover, .actions summary:hover,
|
|
.actions a.danger:hover, .nav button:hover { background: #2b3138 !important; }
|
|
.actions details[open] > summary { background: #313841 !important; }
|
|
.actions button.danger, .actions a.danger, .nav button.danger {
|
|
color: #f5a29b !important; background: #2d1211 !important; border-color: #6b201a !important;
|
|
}
|
|
.actions button.danger:hover, .actions a.danger:hover, .nav button.danger:hover { background: #3d1a18 !important; }
|
|
}
|