Files
jarl/internal/web/static/assets/style.css
T
adamandClaude Opus 5.5 7be0762f48 Server statistics, multi-view record commands, and rc.3 fixes
Added:
- Server statistics: named's memory and CPU (from /proc), cache size,
  sockets and client counts against their limits (statistics channel,
  rndc status), host memory and load, and free disk space. Memory and
  CPU are kept over time as gauges (sum and sample count, averaged in
  timelines). Statistics page Server section, jarl stats server,
  GET /stats/server.
- jarl record add|set|delete|disable|enable --view a,b | all: the same
  change in each view, applied as one changeset.
- Blocking can be paused until resumed (jarl block pause indefinitely,
  "Until I resume", {"indefinitely": true}).

Changed:
- List options (allow-transfer, forwarders, ...) get their braces added
  when given without them.
- The dashboard lists named-checkconf's warnings about the running
  configuration; the CLI prints them after an apply.
- Stats page: tile and card rows fill their width, and the queries and
  failed answers charts share margins so their time axes line up.
- Dashboard: options have their own tile.
- 24-hour times in the web UI.

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

473 lines
27 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.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; }
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; }
/* 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; }