Files
jarl/internal/web/static/assets/style.css
T
adamandClaude Opus 5.5 fef916b7ab Encrypted DNS: several host names, views per listen address, what named uses a certificate for
The setup takes several host names (one certificate for all; the health check says when the served certificate misses one), stored in the settings. Each listen address gets its own views for the host names' A/AAAA records (--address ADDR=VIEW,..., a view checkbox per address in the form), always local to the view, with a warning when a public view would get a private address; a view no longer given loses the address, other records of the names stay, and the form starts from where the records are now. Certificates (page and jarl cert show) say what named uses each for: DNS over TLS or HTTPS, on which addresses and ports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:49:49 +01:00

598 lines
34 KiB
CSS

/* Jarl web UI. Plain CSS, light and dark themes. */
:root {
--bg: #f6f7f9;
--panel: #ffffff;
--text: #1d2330;
--muted: #5f6b7d;
--hatch: rgba(95, 107, 125, 0.13);
--border: #d9dee6;
--accent: #2f5fb3;
--accent-text: #ffffff;
--accent-soft: #e5edfa;
--ok: #1e7a46;
--ok-soft: #e3f4ea;
--warn: #8a5a00;
--warn-soft: #fdf2d9;
--err: #b3261e;
--err-soft: #fbe7e5;
--info-soft: #eef1f6;
--code-bg: #f0f2f5;
--add: #e6f6ea;
--add-text: #135c2c;
--del: #fde8e7;
--del-text: #8c1d18;
--hunk: #e8eefb;
--shadow: 0 1px 2px rgba(20, 30, 50, 0.08);
--radius: 6px;
--vc-green: #3d8160; --vc-teal: #2f7c80; --vc-purple: #7a5ba8; --vc-pink: #a8557f;
--vc-orange: #a8652f; --vc-yellow: #8a7424; --vc-red: #b0534c; --vc-grey: #6b7280;
--mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #14171c;
--panel: #1c2027;
--text: #e3e7ee;
--muted: #9aa5b5;
--hatch: rgba(154, 165, 181, 0.12);
--border: #2f3540;
--accent: #6d9be8;
--accent-text: #0f1520;
--accent-soft: #23314a;
--ok: #59c287;
--ok-soft: #173326;
--warn: #e5b450;
--warn-soft: #3a2f15;
--err: #f08077;
--err-soft: #3c1f1d;
--info-soft: #232833;
--code-bg: #242933;
--add: #183a25;
--add-text: #9fe3b5;
--del: #43201e;
--del-text: #f7b2ac;
--hunk: #23314a;
--shadow: none;
--vc-green: #8fcfa9; --vc-teal: #87cdd0; --vc-purple: #bca6e6; --vc-pink: #e6a6c6;
--vc-orange: #ebbb8f; --vc-yellow: #e3d08e; --vc-red: #eba29c; --vc-grey: #aab2bf;
color-scheme: dark;
}
}
:root[data-theme="dark"] {
--bg: #14171c; --panel: #1c2027; --text: #e3e7ee; --muted: #9aa5b5; --border: #2f3540; --hatch: rgba(154, 165, 181, 0.12);
--accent: #6d9be8; --accent-text: #0f1520; --accent-soft: #23314a; --ok: #59c287; --ok-soft: #173326;
--warn: #e5b450; --warn-soft: #3a2f15; --err: #f08077; --err-soft: #3c1f1d; --info-soft: #232833;
--code-bg: #242933; --add: #183a25; --add-text: #9fe3b5; --del: #43201e; --del-text: #f7b2ac; --hunk: #23314a;
--shadow: none; color-scheme: dark;
--vc-green: #8fcfa9; --vc-teal: #87cdd0; --vc-purple: #bca6e6; --vc-pink: #e6a6c6;
--vc-orange: #ebbb8f; --vc-yellow: #e3d08e; --vc-red: #eba29c; --vc-grey: #aab2bf;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font: 14px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, sans-serif;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code, pre, .mono, textarea.mono, input.mono { font-family: var(--mono); font-size: 13px; }
pre { background: var(--code-bg); padding: 10px 12px; border-radius: var(--radius); overflow: auto; margin: 0; white-space: pre; }
h1 { font-size: 20px; margin: 0 0 4px; }
h2 { font-size: 16px; margin: 18px 0 8px; }
h3 { font-size: 14px; margin: 14px 0 6px; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.nowrap { white-space: nowrap; }
.warn-text { color: var(--warn); }
.right { text-align: right; }
.hidden { display: none !important; }
/* Layout */
header.top {
display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
background: var(--panel); border-bottom: 1px solid var(--border);
padding: 8px 16px; position: sticky; top: 0; z-index: 10;
}
header.top .brand { display: inline-flex; align-items: center; gap: 8px; color: var(--text); text-decoration: none; }
header.top .brand .logo { display: block; transition: transform 0.15s; }
header.top .brand .brand-name { display: block; color: var(--text); }
header.top .brand:hover { text-decoration: none; }
header.top .brand:hover .brand-name { color: var(--accent); }
header.top .brand:hover .logo { transform: scale(1.06); }
nav.main { display: flex; gap: 2px; flex-wrap: wrap; }
nav.main a { padding: 6px 10px; border-radius: var(--radius); color: var(--text); }
nav.main a:hover { background: var(--info-soft); text-decoration: none; }
nav.main a.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
header.top .spacer { flex: 1; }
header.top form.search input { width: 220px; }
header.top .user { color: var(--muted); }
main { padding: 16px; max-width: 1400px; margin: 0 auto; }
.pending-bar {
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
background: var(--warn-soft); color: var(--text); border-bottom: 1px solid var(--border);
padding: 8px 16px;
}
.pending-bar strong { color: var(--warn); }
.pending-bar .apply-now { margin-left: auto; }
/* The review/live switch in the header. */
.mode-toggle { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 500; background: transparent; color: var(--muted); border: 1px solid var(--border); }
.mode-toggle:hover { color: var(--text); border-color: var(--accent); }
.mode-toggle .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); }
.mode-toggle.live { color: var(--warn); border-color: var(--warn); background: var(--warn-soft); }
.mode-toggle.live .dot { background: var(--warn); }
.page-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.page-head .grow { flex: 1; min-width: 200px; }
.actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* Panels and cards */
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; box-shadow: var(--shadow); margin-bottom: 14px; }
.panel > h2:first-child, .panel > h3:first-child { margin-top: 0; }
/* Tiles share a row and grow to fill it, so a wrapped tile never sits
alone beside an empty space. */
.cards { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }
.cards > * { flex: 1 1 170px; min-width: 0; }
.card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; box-shadow: var(--shadow); }
.card .label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; }
.card .value { font-size: 18px; font-weight: 600; margin-top: 2px; }
.card .sub { color: var(--muted); font-size: 12px; margin-top: 4px; }
/* Tables */
.table-wrap { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; background: var(--panel); }
th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
th { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; white-space: nowrap; }
tr:hover td { background: var(--info-soft); }
td.data { font-family: var(--mono); font-size: 13px; word-break: break-all; }
td.name { font-family: var(--mono); font-size: 13px; white-space: nowrap; }
a.dnssec-lock { display: inline-flex; vertical-align: -1px; margin-left: 6px; color: var(--ok); text-decoration: none; }
a.dnssec-lock.off { color: var(--warn); }
tr.same-name-next td { border-bottom-color: color-mix(in srgb, var(--border) 40%, transparent); }
td.actions-cell { white-space: nowrap; text-align: right; width: 1%; }
tr.editing td { background: var(--accent-soft); }
tr.pending-add td { background: var(--add); }
tr.pending-change td, tr.pending-disable td, tr.pending-enable td { background: var(--warn-soft); }
tr.op-failed td { background: var(--err-soft); }
tr.op-failed td:first-child { box-shadow: inset 3px 0 0 var(--err); }
tr.pending-delete td { background: var(--del); text-decoration: line-through; color: var(--muted); }
tr.disabled td:not(.actions-cell), tr.disabled td strong { color: var(--muted); }
/* Disabled records: hatched, so they stand out from live and pending rows. */
tr.disabled td { background-image: repeating-linear-gradient(135deg, transparent 0 7px, var(--hatch) 7px 9px); }
.badge.off { background: var(--muted); color: var(--panel); }
button.toggle { min-width: 5.2em; }
.empty { color: var(--muted); padding: 18px; text-align: center; }
/* Forms */
input, select, textarea, button {
font: inherit; color: var(--text);
}
input[type=text], input[type=password], input[type=number], input[type=search], input:not([type]), select, textarea {
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 5px 8px; min-width: 0;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }
textarea { width: 100%; min-height: 80px; resize: vertical; }
.form-grid { display: grid; grid-template-columns: minmax(140px, 200px) 1fr; gap: 8px 14px; align-items: start; }
.form-grid > label { padding-top: 6px; color: var(--muted); }
.form-grid .help { grid-column: 2; margin-top: -4px; color: var(--muted); font-size: 12px; }
.form-grid input, .form-grid select { width: 100%; max-width: 560px; }
.form-grid input[type=checkbox] { width: auto; }
.inline-form { display: flex; gap: 6px; flex-wrap: wrap; align-items: flex-end; }
.inline-form .f { display: flex; flex-direction: column; gap: 2px; }
.inline-form .f label:not(.check) { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.3px; }
.inline-form .grow { flex: 1; min-width: 220px; }
.inline-form .grow input { width: 100%; }
.hint { color: var(--muted); font-size: 12px; margin-top: 4px; }
.check { display: inline-flex; gap: 6px; align-items: center; cursor: pointer; }
button, .button {
display: inline-flex; align-items: center; gap: 6px;
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
padding: 5px 12px; cursor: pointer; white-space: nowrap; color: var(--text);
}
button:hover, .button:hover { background: var(--info-soft); text-decoration: none; }
button.primary, .button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); font-weight: 600; }
button.primary:hover { filter: brightness(1.08); background: var(--accent); }
button.danger { color: var(--err); border-color: var(--err); }
button.danger:hover { background: var(--err-soft); }
button.small, .button.small { padding: 2px 8px; font-size: 12px; }
button.link { border: none; background: none; color: var(--accent); padding: 0 4px; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
/* Badges and messages */
.badge { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 11px; font-weight: 600; background: var(--info-soft); color: var(--muted); white-space: nowrap; }
.badge.ok { background: var(--ok-soft); color: var(--ok); }
.badge.warn { background: var(--warn-soft); color: var(--warn); }
.badge.err { background: var(--err-soft); color: var(--err); }
.badge.accent { background: var(--accent-soft); color: var(--accent); }
.msg { border-radius: var(--radius); padding: 8px 12px; margin: 8px 0; border: 1px solid transparent; }
.msg.err { background: var(--err-soft); color: var(--err); border-color: var(--err); }
.msg.warn { background: var(--warn-soft); color: var(--warn); }
.msg.ok { background: var(--ok-soft); color: var(--ok); }
.msg.info { background: var(--info-soft); }
.msg ul { margin: 4px 0 0; padding-left: 18px; }
.problems { list-style: none; padding: 0; margin: 6px 0; }
.problems li { padding: 4px 8px; border-left: 3px solid var(--border); margin-bottom: 3px; background: var(--info-soft); border-radius: 0 var(--radius) var(--radius) 0; }
.problems li.error { border-color: var(--err); background: var(--err-soft); }
.problems li.warning { border-color: var(--warn); background: var(--warn-soft); }
.problems li .where { color: var(--muted); font-size: 12px; margin-left: 6px; }
/* Diff */
.diff { background: var(--code-bg); border-radius: var(--radius); overflow: auto; font-family: var(--mono); font-size: 12.5px; line-height: 1.4; }
.diff div { white-space: pre; padding: 0 10px; }
.diff .add { background: var(--add); color: var(--add-text); }
.diff .del { background: var(--del); color: var(--del-text); }
.diff .hunk { background: var(--hunk); color: var(--accent); }
.diff .file { font-weight: 700; padding-top: 6px; }
/* Tabs */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 12px; flex-wrap: wrap; }
.tabs a { padding: 7px 12px; border: 1px solid transparent; border-bottom: none; border-radius: var(--radius) var(--radius) 0 0; color: var(--muted); }
.tabs a.active { background: var(--panel); border-color: var(--border); color: var(--text); font-weight: 600; margin-bottom: -1px; }
/* Modal */
.modal-back { position: fixed; inset: 0; background: rgba(10, 15, 25, 0.45); display: flex; align-items: flex-start; justify-content: center; padding: 60px 16px; z-index: 50; overflow: auto; }
.modal { background: var(--panel); border-radius: 8px; border: 1px solid var(--border); width: min(760px, 100%); padding: 18px 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.25); }
.modal.wide { width: min(1100px, 100%); }
.modal h2 { margin-top: 0; }
.modal .buttons { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
/* Toasts */
#toasts { position: fixed; bottom: 16px; right: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 100; max-width: min(480px, calc(100% - 32px)); }
.toast { background: var(--panel); border: 1px solid var(--border); border-left: 4px solid var(--accent); padding: 10px 14px; border-radius: var(--radius); box-shadow: 0 4px 16px rgba(0,0,0,0.18); }
.toast.ok { border-left-color: var(--ok); }
.toast.err { border-left-color: var(--err); }
.toast.warn { border-left-color: var(--warn); }
/* Login */
.login { max-width: 360px; margin: 10vh auto; }
.login-logo { display: block; margin: 8px auto 10px; }
.login h1 { text-align: center; margin: 0 0 8px; }
.wordmark { display: block; width: 220px; height: auto; margin: 0 auto; color: var(--text); }
.wordmark-rule .fill { fill: var(--accent); }
.wordmark-rule .line { fill: none; stroke: var(--accent); stroke-width: 2.6; }
.wordmark-rule .dns { fill: var(--accent); font: 700 12.5px system-ui, sans-serif; letter-spacing: 0.5px; }
.login .tag { text-align: center; color: var(--muted); margin-bottom: 18px; }
.login form { display: flex; flex-direction: column; gap: 10px; }
.login input { width: 100%; padding: 8px 10px; }
.login button { justify-content: center; padding: 8px; }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; }
.grants { font-family: var(--mono); font-size: 12px; }
.copy-box { display: flex; gap: 6px; align-items: center; }
.login.wide { max-width: 520px; }
.totp-steps { padding-left: 20px; }
.totp-steps li { margin-bottom: 12px; }
.totp-qr { margin: 10px 0; }
.totp-qr img { display: block; width: 200px; height: 200px; background: #fff; border-radius: var(--radius); }
.totp-confirm { display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
input.totp-code { font-family: var(--mono); font-size: 18px; letter-spacing: 0.15em; max-width: 14em; }
.recovery-codes { font-family: var(--mono); font-size: 15px; columns: 2; max-width: 360px; }
.copy-box input { flex: 1; font-family: var(--mono); }
.ds-record { margin: 12px 0 16px; }
.ds-record > .copy-box { max-width: 900px; }
.ds-record .kv { margin-top: 8px; align-items: center; grid-template-columns: max-content minmax(0, 1fr); }
.ds-record .kv dd { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
.ds-record .kv dd .copy-box { flex: 1 1 200px; max-width: 640px; min-width: 0; }
.copy-box input { min-width: 0; }
td.break { word-break: break-all; }
.sortable-row { display: flex; align-items: center; gap: 8px; }
@media (max-width: 700px) {
.form-grid { grid-template-columns: 1fr; }
.form-grid .help { grid-column: 1; }
header.top form.search input { width: 140px; }
}
/* ---- Statistics charts. Colors from the validated reference palette
(categorical slots 1-3, sequential blue), checked against this UI's
panel surfaces in both themes. */
:root {
--series-1: #2a78d6;
--series-2: #eb6834;
--series-3: #1baf7a;
--chart-grid: #eceef2;
--chart-axis: #c9cfd8;
/* Sequential ramp, low to high (blue 100 -> 700). */
--heat-0: #cde2fb; --heat-1: #9ec5f4; --heat-2: #6da7ec; --heat-3: #3987e5;
--heat-4: #256abf; --heat-5: #184f95; --heat-6: #0d366b;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--series-1: #3987e5;
--series-2: #d95926;
--series-3: #199e70;
--chart-grid: #272c34;
--chart-axis: #3a414d;
/* In dark, low values recede into the surface: the ramp runs dark -> light. */
--heat-0: #0d366b; --heat-1: #184f95; --heat-2: #256abf; --heat-3: #3987e5;
--heat-4: #6da7ec; --heat-5: #9ec5f4; --heat-6: #cde2fb;
}
}
:root[data-theme="dark"] {
--series-1: #3987e5;
--series-2: #d95926;
--series-3: #199e70;
--chart-grid: #272c34;
--chart-axis: #3a414d;
--heat-0: #0d366b; --heat-1: #184f95; --heat-2: #256abf; --heat-3: #3987e5;
--heat-4: #6da7ec; --heat-5: #9ec5f4; --heat-6: #cde2fb;
}
.refreshing { opacity: 0.6; transition: opacity 0.2s; }
/* Secondary row actions show when the row is hovered or focused; touch
screens, which can't hover, always show them. Opacity keeps them in the
layout (no shifting) and reachable with Tab. */
tr .reveal { opacity: 0; transition: opacity 0.1s; }
tr:hover .reveal, tr:focus-within .reveal { opacity: 1; }
@media (hover: none) { tr .reveal { opacity: 1; } }
/* Blocking steps, in order, on the Blocking page. */
.steps-line { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; margin: 4px 0 10px; }
.step-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border: 1px solid var(--border); border-radius: 12px; font-family: var(--mono); font-size: 13px; background: var(--panel); }
.step-chip.accent { border-color: var(--accent); }
.step-tools { display: inline-flex; gap: 2px; margin-left: 2px; }
.step-tools button { padding: 0 5px; min-width: 0; }
h2.section { margin: 24px 0 10px; font-size: 18px; }
.filter-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--panel); }
.segmented a { padding: 4px 12px; color: var(--text); border-right: 1px solid var(--border); text-decoration: none; font-variant-numeric: tabular-nums; }
.segmented a:last-child { border-right: none; }
.segmented a:hover { background: var(--info-soft); }
.segmented a.active { background: var(--accent); color: var(--accent-text); font-weight: 600; }
.card.stat .value.big { font-size: 26px; font-weight: 600; line-height: 1.2; }
.chart-card { margin: 0 0 14px; }
.chart-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
.chart-head .grow { flex: 1; min-width: 200px; }
.chart-head h2 { margin: 0; font-size: 16px; }
/* Two or three columns; the last cards stretch to fill their row, so no
card is left alone beside an empty space. */
.chart-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }
.chart-grid > :last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (min-width: 1100px) {
.chart-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.chart-grid > :last-child:nth-child(odd) { grid-column: auto; }
.chart-grid > :last-child:nth-child(3n+1) { grid-column: 1 / -1; }
.chart-grid > :last-child:nth-child(3n+2) { grid-column: span 2; }
}
.chart-grid > .panel h2 { margin: 0; font-size: 16px; }
.list-sub { margin: 2px 0 8px; }
.chart { position: relative; outline: none; }
.chart:focus-visible { box-shadow: 0 0 0 2px var(--accent); border-radius: var(--radius); }
.chart svg { display: block; overflow: visible; }
.chart .grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart .axis { stroke: var(--chart-axis); stroke-width: 1; }
.chart .tick, .chart .end-label { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.chart .end-label { fill: var(--text); }
.chart .line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart .area { stroke: none; fill-opacity: 0.1; }
.chart .dot { stroke: var(--panel); stroke-width: 2; }
.chart .crosshair { stroke: var(--muted); stroke-width: 1; }
.chart .hit { fill: transparent; cursor: crosshair; }
.chart .s1, .key.s1 { stroke: var(--series-1); }
.chart .s2, .key.s2 { stroke: var(--series-2); }
.chart .s3, .key.s3 { stroke: var(--series-3); }
.chart .area.s1, .chart .dot.s1 { fill: var(--series-1); }
.chart .area.s2, .chart .dot.s2 { fill: var(--series-2); }
.chart .area.s3, .chart .dot.s3 { fill: var(--series-3); }
.key.line { display: inline-block; width: 14px; height: 0; border-top: 2px solid; vertical-align: middle; margin-right: 6px; }
.key.line.s1 { border-color: var(--series-1); }
.key.line.s2 { border-color: var(--series-2); }
.key.line.s3 { border-color: var(--series-3); }
.chart-legend { list-style: none; display: flex; gap: 14px; margin: 2px 0 0; padding: 0; font-size: 13px; color: var(--text); flex-wrap: wrap; }
.chart-tip { position: absolute; z-index: 5; pointer-events: none; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.16); padding: 6px 10px; font-size: 13px; white-space: nowrap; }
.chart-tip .tip-head { color: var(--muted); font-size: 12px; margin-bottom: 2px; }
.chart-tip .tip-row strong { font-variant-numeric: tabular-nums; }
.chart-table .scroll, .chart-table .table-wrap { max-height: 360px; overflow: auto; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.heatmap-wrap { position: relative; }
.heatmap { display: grid; grid-template-columns: 36px repeat(24, minmax(10px, 1fr)); gap: 2px; outline: none; }
.heatmap:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.hm-corner, .hm-col, .hm-row { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.hm-row { display: flex; align-items: center; }
.hm-cell { height: 22px; border-radius: 3px; }
.hm-cell:hover, .hm-cell:focus { outline: 2px solid var(--text); outline-offset: -1px; }
.hm-cell.empty, .hm-swatch.empty { background: transparent; box-shadow: inset 0 0 0 1px var(--chart-grid); }
.heat-0 { background: var(--heat-0); } .heat-1 { background: var(--heat-1); } .heat-2 { background: var(--heat-2); }
.heat-3 { background: var(--heat-3); } .heat-4 { background: var(--heat-4); } .heat-5 { background: var(--heat-5); }
.heat-6 { background: var(--heat-6); }
.hm-scale { display: flex; align-items: center; gap: 2px; margin-top: 10px; flex-wrap: wrap; }
.hm-scale .muted { margin: 0 6px; }
.hm-swatch { display: inline-block; width: 18px; height: 10px; border-radius: 2px; }
.bar-list { list-style: none; margin: 0; padding: 0; }
.bar-list li { padding: 4px 0; }
.bar-text { display: flex; gap: 8px; justify-content: space-between; font-size: 13px; }
.bar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: 12.5px; min-width: 0; }
.bar-value { white-space: nowrap; font-variant-numeric: tabular-nums; }
.bar-track { height: 6px; margin-top: 3px; }
.bar-fill { height: 6px; background: var(--series-1); border-radius: 0 3px 3px 0; min-width: 2px; }
@media (max-width: 700px) {
.heatmap { grid-template-columns: 30px repeat(24, minmax(6px, 1fr)); gap: 1px; }
.hm-cell { height: 16px; }
.chart-grid { grid-template-columns: 1fr; }
}
/* Address match list suggestions. */
.aml-suggest { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; margin-top: 6px; }
.aml-suggest .muted { margin-left: 4px; }
.aml-suggest .muted:first-child { margin-left: 0; }
button.chip { padding: 1px 8px; font-size: 12px; font-family: var(--mono); border-radius: 999px; background: var(--info-soft); border: 1px solid var(--border); color: var(--text); }
button.chip:hover { background: var(--accent-soft); border-color: var(--accent); }
.aml-problems .msg { margin: 4px 0 0; padding: 3px 8px; }
.range-hints .msg { margin: 4px 0 0; padding: 3px 8px; }
.range-hint { margin-top: 4px; color: var(--muted); }
.range-hint summary { cursor: pointer; }
.range-hint .prefixes { display: flex; flex-wrap: wrap; gap: 2px 12px; margin: 2px 0 0 14px; }
.range-hint .prefixes span { white-space: nowrap; }
/* Zone name and record previews. */
.segmented button { border: none; border-right: 1px solid var(--border); border-radius: 0; background: transparent; padding: 4px 12px; color: var(--text); }
.segmented button:last-child { border-right: none; }
.segmented button:hover { background: var(--info-soft); }
.segmented button.active { background: var(--accent); color: var(--accent-text); font-weight: 600; }
.name-preview { margin-top: 6px; font-size: 13px; }
.name-preview .msg, .will-add .msg { margin: 4px 0 0; padding: 3px 8px; }
.will-add { margin-top: 8px; font-size: 13px; }
.will-add code { font-family: var(--mono); background: var(--code-bg); padding: 1px 6px; border-radius: 4px; word-break: break-all; }
@media (prefers-reduced-motion: reduce) {
header.top .brand .logo { transition: none; }
header.top .brand:hover .logo { transform: none; }
}
/* a zone tab's previous content while the next tab loads */
.zone-body.stale { opacity: 0.5; transition: opacity 0.15s 0.1s; pointer-events: none; }
/* the "← Zones" link above a page's heading */
.back-link { font-size: 13px; margin-bottom: 2px; }
/* view colours (the default is the link colour) */
.vc-green { color: var(--vc-green); } .vc-teal { color: var(--vc-teal); } .vc-purple { color: var(--vc-purple); }
.vc-pink { color: var(--vc-pink); } .vc-orange { color: var(--vc-orange); } .vc-yellow { color: var(--vc-yellow); }
.vc-red { color: var(--vc-red); } .vc-grey { color: var(--vc-grey); } .vc-default { color: var(--accent); }
.swatches { display: flex; flex-wrap: wrap; gap: 8px; padding: 2px 0; }
.swatch { width: 24px; height: 24px; min-width: 0; padding: 0; border-radius: 50%; background: currentColor; border: 2px solid var(--panel); box-shadow: 0 0 0 1px var(--border); cursor: pointer; }
.swatch:hover { background: currentColor; }
.swatch.selected { box-shadow: 0 0 0 2px currentColor; }
.swatch:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
tr.selected td { background: var(--accent-soft); }
/* the page footer with the Jarl build */
.shell { min-height: 100vh; display: flex; flex-direction: column; }
.shell > main { flex: 1; width: 100%; }
footer.site { padding: 12px 16px 16px; text-align: center; font-size: 12px; color: var(--muted); }
footer.site a { color: var(--muted); text-decoration: underline; text-decoration-color: var(--border); text-underline-offset: 2px; }
footer.site a:hover { color: var(--accent); text-decoration-color: currentColor; }
/* a request in progress */
.spinner { display: inline-block; width: 11px; height: 11px; margin-right: 6px; vertical-align: -1px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; border-top-color: var(--border); background: var(--accent-soft); } }
/* the queries a name server check sends */
.query-log { list-style: none; margin: 6px 0; padding: 0; font-size: 13px; }
.query-log li { padding: 3px 0; display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
.query-log code { background: var(--code-bg); padding: 1px 6px; border-radius: 4px; overflow-wrap: anywhere; }
.query-log li.pending code { opacity: 0.85; }
.err-text { color: var(--err); }
ul.plain { list-style: none; margin: 8px 0; padding: 0; }
.code { tab-size: 4; }
textarea.code { width: 100%; white-space: pre; overflow-wrap: normal; overflow-x: auto; }
details.view-picker { position: relative; }
details.view-picker summary { cursor: pointer; padding: 5px 8px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel); min-width: 12em; max-width: 24em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
details.view-picker[open] .view-picker-list { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; min-width: 14em; max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
ul.plain li { padding: 3px 0; }
.query-details { margin: 8px 0; }
/* Internet exposure findings */
.findings { display: flex; flex-direction: column; gap: 8px; }
.finding { display: flex; gap: 12px; align-items: flex-start; padding: 10px 12px; border: 1px solid var(--line); border-left-width: 3px; border-radius: 6px; }
.finding.warning { border-left-color: var(--warn); }
.finding.error { border-left-color: var(--err); }
.finding.acked, .finding.info { border-left-color: var(--line); color: var(--muted); }
.finding .actions { flex-shrink: 0; }
.risk-confirm { margin-top: 8px; }
a.card.link { color: inherit; text-decoration: none; }
a.card.link:hover { border-color: var(--accent, var(--link)); }
/* An option's value as shown: laid out like the editor (no wrapping, a
long line scrolls; same padding and an invisible border), so the text
stays put when Edit opens it. */
pre.optval { padding: 5px 8px; border: 1px solid transparent; background: none; white-space: pre; overflow-x: auto; max-width: min(72ch, 50vw); }
/* ...and the editor in a table cell gets the same width, so the column
doesn't jump when Edit opens. */
td textarea.code { min-width: min(72ch, 50vw); }
/* Certificates: the "check the names" result under the ACME fields. */
.check-names { margin: 6px 0 14px; }
.check-names .table-wrap { margin-top: 8px; }
/* A list to pick rows from (linking zones): the whole row toggles. */
table.pick-list tr.pick { cursor: pointer; }
table.pick-list tr.pick td:first-child { width: 2em; }
table.pick-list tr.pick.selected { background: var(--accent-soft); }
/* Encrypted DNS: a protocol per line with its port. */
.enc-ports > div { margin: 2px 0; }
.enc-ports input[type=number] { display: inline-block; }
/* Statistics: views switched on and off. */
.filter-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.filter-rows .filter-row { margin-bottom: 0; }
.filter-row .chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip.toggle-chip { opacity: 0.55; font-size: 13px; padding: 3px 12px; }
.chip.toggle-chip.on { opacity: 1; text-decoration: none; background: var(--accent-soft); border-color: var(--accent); }
/* Drop-down menus in the top bar (Server, the user). */
details.menu { position: relative; }
details.menu > summary { list-style: none; cursor: pointer; padding: 6px 10px; border-radius: var(--radius); color: var(--text); white-space: nowrap; }
details.menu > summary::-webkit-details-marker { display: none; }
details.menu > summary:hover, details.menu[open] > summary { background: var(--info-soft); }
details.menu > summary.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
details.menu > summary .caret { margin-left: 4px; font-size: 0.8em; opacity: 0.7; }
details.menu > summary .user { color: inherit; }
details.menu .menu-list {
position: absolute; top: calc(100% + 4px); left: 0; z-index: 20; min-width: 190px;
display: flex; flex-direction: column; padding: 4px;
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
}
details.menu.right .menu-list { left: auto; right: 0; }
details.menu .menu-list a, details.menu .menu-list .menu-item {
display: block; padding: 7px 10px; border-radius: var(--radius); color: var(--text); text-align: left;
background: none; border: 0; font: inherit; cursor: pointer; white-space: nowrap;
}
details.menu .menu-list a:hover, details.menu .menu-list .menu-item:hover { background: var(--info-soft); text-decoration: none; }
details.menu .menu-list a.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
nav.main { align-items: center; }
header.top .header-right { display: flex; align-items: center; gap: 12px; margin-left: auto; }
@media (max-width: 1200px) {
header.top form.search input { width: 160px; }
header.top { gap: 10px; }
}
/* Encrypted DNS: the health of each listener. */
ul.probes { list-style: none; padding: 0; margin: 0 0 8px; }
ul.probes > li { padding: 6px 0; border-bottom: 1px solid var(--border); }
ul.probes > li .msg { margin: 6px 0 0; }
/* Comparing a zone across views. */
table.compare td { vertical-align: top; }
/* The same name and type columns in every section, so they line up. */
table.compare { table-layout: auto; width: 100%; }
table.compare tr > :nth-child(1) { width: 18ch; }
table.compare tr > :nth-child(2) { width: 8ch; }
table.compare td.compare-cell button { margin-top: 4px; }
/* One line per record: a long TXT record is cut, with the whole of it on hover. */
table.compare .oneline { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46ch; }
.compare-actions { margin-top: 4px; }
.compare-controls { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 12px; }
.compare-controls label { color: var(--muted); }
ul.compare-summary { margin: 0 0 12px; padding-left: 18px; }
details.compare-only { margin: 12px 0; }
details.compare-only summary { cursor: pointer; padding: 4px 0; }
table.compare tr.muted td { color: var(--muted); }
/* Text that wraps anywhere, e.g. long statements in the Added by Jarl table. */
pre.wrap { white-space: pre-wrap; word-break: break-all; }
/* New reverse zone: sizes and the networks the records use. */
.reverse-helper { margin-top: 6px; }
.form-grid > .grid-cell { min-width: 0; }
.size-chips { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; margin-bottom: 6px; }
.size-chips button.active { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
.cands { display: flex; flex-direction: column; gap: 3px; max-height: 260px; overflow-y: auto; }
.cands button.cand { flex-shrink: 0; text-align: left; font-weight: normal; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Encrypted DNS: per listen address, the views that get its records. */
td.addr-views { width: 100%; }
.view-chips { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; }
.view-chips label.chip { white-space: nowrap; font-size: 0.9em; }