795e945888
The list gave no hint which domains still needed records published — the verdict lived only on the domain page, one click away per domain. Each row now carries a badge with the worst of that domain's DKIM, SPF and DMARC checks, in the panel's shared ok/warn/error/unknown vocabulary, linking to that domain's DNS status card. The checks run concurrently across the listed domains: each carries its own timeout, so in series a dead resolver would multiply that wait by the number of domains and the list would look hung. They share the checker's cache with the domain page, so a repeat view costs no lookups and opening a domain after the list is free. A domain whose DKIM key cannot be read stays "unknown" rather than being reported as misconfigured — the missing half of the comparison is this server's, not the domain's. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
400 lines
24 KiB
CSS
400 lines
24 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. 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; }
|
|
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; }
|
|
/* 48rem is a reading measure: right for the forms and prose that make up most
|
|
of the panel. Page-specific overrides below widen or narrow it for the pages
|
|
that need something else. 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: 48rem; }
|
|
h1 { font-size: 1.4rem; margin: 0 0 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 three monitoring pages hold data instead of prose — seven columns of
|
|
send-log, and raw mail.log lines that are long by nature — and at the
|
|
default measure the send-log's Subject and Status were fighting over the
|
|
last inch while the log pages wrapped every second line. They get a wider
|
|
measure; every other page keeps the default one. The class comes from the
|
|
layout, which stamps the page name onto <main>. */
|
|
main.page-deliveries, main.page-mail_queue, main.page-system_log { max-width: 64rem; }
|
|
/* 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; }
|
|
.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 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 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; }
|
|
/* 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; }
|