ACLs, views' match lists, options with address match lists and blocking client groups take ranges such as 10.0.10.101-10.0.10.199 or 2001:db8::100-2001:db8::1ff (negated, or with spaces around the dash, too). Jarl stores the range as written and writes the fewest prefixes that cover exactly that range to named.conf, with the range as a comment. New package internal/iprange; namedconf gets an AMRange element (a range used to parse as an ACL name), ExpandRanges and statement comments. Mistakes are refused when entered, validation sees what ranges cover, and IPv6 ranges in the IPv4-mapped space get a warning. The web UI shows what a range becomes while typing (GET /catalog/ranges). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
492 lines
28 KiB
CSS
492 lines
28 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; }
|