2026-08-14 09:40:03 +12:00
|
|
|
|
/* One vocabulary for the whole console.
|
|
|
|
|
|
Every screen is built from the components below — card, tile, field, tag, table, list.
|
|
|
|
|
|
A page that needs a look of its own is a missing component, not a licence for a style
|
|
|
|
|
|
attribute: there are no inline styles in the page components, on purpose, and no CSS
|
|
|
|
|
|
modules either. One stylesheet, read top to bottom, is what a strict no-dependency
|
|
|
|
|
|
console has instead of a design system nobody can see the whole of.
|
|
|
|
|
|
|
|
|
|
|
|
The palette takes its cues from GitHub's dark interface: near-black chrome, a cool
|
|
|
|
|
|
charcoal work area, restrained borders and one deliberate green for live or primary
|
|
|
|
|
|
actions. An operator can scan dense operational data without the interface competing
|
|
|
|
|
|
for attention.
|
|
|
|
|
|
|
|
|
|
|
|
The console is darker than the television app rather than lighter. What makes a card
|
|
|
|
|
|
hard to find is not the darkness of the page behind it but the absence of anything
|
|
|
|
|
|
*between* them — so the page drops to near-black and the separation is done properly,
|
|
|
|
|
|
by a card that sits a measurable step above it and a hairline visible on a bad monitor.
|
|
|
|
|
|
Four steps, and they are the whole scale: page, card, raised (a control), high (a
|
|
|
|
|
|
control under the pointer). A fifth value is a decision that has not been made yet.
|
|
|
|
|
|
|
|
|
|
|
|
The accent is spent only on what is live, selected or primary. Beside it are three
|
|
|
|
|
|
secondary tones with no verdict attached — info (blue), note (violet), data (teal) —
|
|
|
|
|
|
which is what lets a page distinguish one kind of thing from another without every
|
|
|
|
|
|
coloured element reading as a warning. Green means good, amber means look, red means
|
|
|
|
|
|
wrong; the other three mean nothing at all, which is the point. A tone is passed, never
|
2026-08-18 08:41:48 +12:00
|
|
|
|
derived: the library is teal, a person violet, a television blue, on every page.
|
|
|
|
|
|
|
|
|
|
|
|
Beside the accent is one quieter green, `idle`, which is not a seventh meaning but the
|
|
|
|
|
|
accent's meaning held at lower confidence: good, but not this minute. It exists so that
|
|
|
|
|
|
"fine and not connected right now" can stop borrowing amber, which is reserved for
|
|
|
|
|
|
something an operator should look at. Nothing derives it either. */
|
2026-08-14 09:40:03 +12:00
|
|
|
|
|
|
|
|
|
|
:root {
|
|
|
|
|
|
color-scheme: dark;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
--oled: #000;
|
|
|
|
|
|
--bg: #080b10;
|
|
|
|
|
|
--rail-bg: var(--oled);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
--top-bg: rgba(1, 4, 9, 0.94);
|
|
|
|
|
|
--surface: #0d1117;
|
|
|
|
|
|
--surface-lift: #161b22;
|
|
|
|
|
|
--surface-hi: #21262d;
|
|
|
|
|
|
--line: #30363d;
|
|
|
|
|
|
--line-soft: #21262d;
|
|
|
|
|
|
--text: #f0f6fc;
|
|
|
|
|
|
--muted: #b1bac4;
|
|
|
|
|
|
--quiet: #7d8590;
|
|
|
|
|
|
--accent: #2ea043;
|
|
|
|
|
|
--accent-ink: #56d364;
|
|
|
|
|
|
--accent-wash: rgba(46, 160, 67, 0.16);
|
2026-08-18 08:41:48 +12:00
|
|
|
|
/* The quiet green: fine, but not this minute. Same hue as the accent and deliberately
|
|
|
|
|
|
duller, because it is the accent's own meaning at lower confidence rather than a
|
|
|
|
|
|
verdict of its own — a television in ordinary use that happens to be switched off is
|
|
|
|
|
|
not something an operator has to do anything about, and amber said it was. */
|
|
|
|
|
|
--idle: #4a8f60;
|
|
|
|
|
|
--idle-ink: #86c79a;
|
|
|
|
|
|
--idle-wash: rgba(74, 143, 96, 0.13);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
--danger: #e5534b;
|
|
|
|
|
|
--danger-ink: #ff9b94;
|
|
|
|
|
|
--danger-wash: rgba(229, 83, 75, 0.13);
|
|
|
|
|
|
--warn: #e0ad4e;
|
|
|
|
|
|
--warn-ink: #f2cb78;
|
|
|
|
|
|
--warn-wash: rgba(239, 196, 107, 0.13);
|
|
|
|
|
|
--info: #4f9cd8;
|
|
|
|
|
|
--info-ink: #93cbef;
|
|
|
|
|
|
--info-wash: rgba(79, 156, 216, 0.14);
|
|
|
|
|
|
--note: #a07ce8;
|
|
|
|
|
|
--note-ink: #bda1f5;
|
|
|
|
|
|
--note-wash: rgba(160, 124, 232, 0.14);
|
|
|
|
|
|
--data: #3fc2b6;
|
|
|
|
|
|
--data-ink: #6fdcd0;
|
|
|
|
|
|
--data-wash: rgba(63, 194, 182, 0.13);
|
|
|
|
|
|
--rail: 236px;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
--safe-top: env(safe-area-inset-top, 0px);
|
|
|
|
|
|
--safe-right: env(safe-area-inset-right, 0px);
|
|
|
|
|
|
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
|
|
|
|
--safe-left: env(safe-area-inset-left, 0px);
|
|
|
|
|
|
--top: calc(58px + var(--safe-top));
|
2026-08-14 09:40:03 +12:00
|
|
|
|
--radius: 8px;
|
|
|
|
|
|
--radius-sm: 6px;
|
|
|
|
|
|
--radius-xs: 4px;
|
|
|
|
|
|
--sans: "Inter Variable", Inter, "Segoe UI", sans-serif;
|
|
|
|
|
|
--mono: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
* {
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
[hidden] {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
|
margin: 0;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
background:
|
|
|
|
|
|
radial-gradient(circle at 72% -20%, rgba(63, 194, 182, 0.045), transparent 38rem),
|
|
|
|
|
|
var(--bg);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
color: var(--text);
|
|
|
|
|
|
font: 16.5px/1.55 var(--sans);
|
|
|
|
|
|
font-weight: 450;
|
|
|
|
|
|
letter-spacing: 0;
|
|
|
|
|
|
-webkit-font-smoothing: antialiased;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
min-width: 320px;
|
|
|
|
|
|
min-height: 100dvh;
|
|
|
|
|
|
background: var(--bg);
|
|
|
|
|
|
-webkit-tap-highlight-color: transparent;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
a {
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Minimal animation, and none of it decorative. The only movement in the console is a
|
|
|
|
|
|
thing appearing (a toast, a panel) and the one spinner that says a request is in
|
|
|
|
|
|
flight. Anybody honouring the platform's reduce-motion setting gets neither. */
|
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
|
*,
|
|
|
|
|
|
*::before,
|
|
|
|
|
|
*::after {
|
|
|
|
|
|
animation-duration: 0.001ms !important;
|
|
|
|
|
|
animation-iteration-count: 1 !important;
|
|
|
|
|
|
transition-duration: 0.001ms !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.skip {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: -9999px;
|
|
|
|
|
|
top: 8px;
|
|
|
|
|
|
z-index: 60;
|
|
|
|
|
|
padding: 8px 14px;
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.skip:focus {
|
|
|
|
|
|
left: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Focus is visible everywhere. The console is driven from a keyboard as much as a mouse,
|
|
|
|
|
|
and a focus ring that only some controls have is worse than none. */
|
|
|
|
|
|
:focus-visible {
|
|
|
|
|
|
outline: 2px solid var(--accent);
|
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
|
border-radius: var(--radius-xs);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- top bar ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* The bar spans the whole width above both the rail and the page, and carries the things
|
|
|
|
|
|
that are true of the console rather than of any one screen: which gateway this is,
|
|
|
|
|
|
whether it is answering, the search, the notification bell and the way out. */
|
|
|
|
|
|
.topbar {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
inset: 0 0 auto 0;
|
|
|
|
|
|
z-index: 30;
|
|
|
|
|
|
height: var(--top);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
padding: var(--safe-top) max(clamp(14px, 2vw, 22px), var(--safe-right)) 0 var(--safe-left);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
background: var(--top-bg);
|
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
|
box-shadow: 0 1px 0 rgba(240, 246, 252, 0.02), 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
|
|
|
|
backdrop-filter: blur(14px);
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-brand {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 11px;
|
|
|
|
|
|
flex: 0 0 var(--rail);
|
|
|
|
|
|
/* 22px is the rail's own padding plus a link's, so the mark sits in the same column as
|
|
|
|
|
|
the navigation icons under it rather than a few pixels to their left. */
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
padding: 0 22px;
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* Round, because the wordmark beside it is the only other thing in this corner and two
|
|
|
|
|
|
rounded rectangles side by side read as a control rather than as a signature. */
|
|
|
|
|
|
.brand-mark {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
width: 30px;
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
flex: 0 0 30px;
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
letter-spacing: -0.06em;
|
|
|
|
|
|
}
|
|
|
|
|
|
.brand-word {
|
|
|
|
|
|
font-size: 17px;
|
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
|
letter-spacing: -0.01em;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-spacer {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-tools {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar .omni {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translateX(-50%);
|
2026-08-15 09:23:26 +12:00
|
|
|
|
width: min(820px, calc(100vw - var(--rail) - 380px));
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
/* The version is set in the console's monospace because it is read character by character
|
|
|
|
|
|
when somebody is working out which tree a container was deployed from. */
|
|
|
|
|
|
.topbar-version {
|
|
|
|
|
|
font: 11.5px/1 var(--mono);
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-status {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
width: 38px;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
flex: 0 0 38px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: 1px solid transparent;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
cursor: pointer;
|
2026-08-16 12:13:51 +12:00
|
|
|
|
transition: background .16s ease, border-color .16s ease, color .16s ease;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.topbar-status:hover:not(:disabled),
|
|
|
|
|
|
.topbar-status:focus-visible {
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-status:disabled {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
cursor: default;
|
|
|
|
|
|
opacity: 1;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.topbar-status .dot {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
width: 9px;
|
|
|
|
|
|
height: 9px;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--quiet);
|
2026-08-16 21:20:55 +12:00
|
|
|
|
box-shadow: 0 0 0 3px rgba(125, 133, 144, .12);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.topbar-status[data-tone="ok"] .dot {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
background: var(--accent-ink);
|
|
|
|
|
|
box-shadow: 0 0 0 3px var(--accent-wash), 0 0 9px rgba(86, 211, 100, .28);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.topbar-status[data-tone="bad"] .dot {
|
|
|
|
|
|
background: var(--danger);
|
2026-08-16 21:20:55 +12:00
|
|
|
|
box-shadow: 0 0 0 3px var(--danger-wash);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-16 12:13:51 +12:00
|
|
|
|
/* The account is an identity first and an exit second. Keeping sign-out in this menu
|
|
|
|
|
|
prevents an unexplained door icon from competing with the operational controls. */
|
|
|
|
|
|
.account-menu {
|
|
|
|
|
|
position: relative;
|
2026-08-16 21:20:55 +12:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
height: 38px;
|
2026-08-16 12:13:51 +12:00
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-trigger {
|
|
|
|
|
|
max-width: 190px;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
padding: 0 9px 0 5px;
|
|
|
|
|
|
border-color: var(--line);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-trigger:hover:not(:disabled),
|
|
|
|
|
|
.account-menu[data-open="true"] .account-trigger {
|
|
|
|
|
|
border-color: rgba(86, 211, 100, .5);
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-avatar {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
width: 27px;
|
|
|
|
|
|
height: 27px;
|
|
|
|
|
|
flex: 0 0 27px;
|
|
|
|
|
|
border: 1px solid rgba(86, 211, 100, .36);
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-weight: 750;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-name {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-caret {
|
|
|
|
|
|
width: 12px;
|
|
|
|
|
|
height: 12px;
|
|
|
|
|
|
transition: transform .16s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-menu[data-open="true"] .account-caret {
|
|
|
|
|
|
transform: rotate(180deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-panel {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: calc(100% + 8px);
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
width: min(280px, calc(100vw - 24px));
|
|
|
|
|
|
padding: 7px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
box-shadow: 0 18px 44px rgba(0, 0, 0, .55);
|
|
|
|
|
|
z-index: 40;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-identity {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 11px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
padding: 9px 10px 12px;
|
|
|
|
|
|
border-bottom: 1px solid var(--line-soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-avatar-large {
|
|
|
|
|
|
width: 34px;
|
|
|
|
|
|
height: 34px;
|
|
|
|
|
|
flex-basis: 34px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-identity span:last-child {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-identity small,
|
|
|
|
|
|
.account-identity b {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-identity small {
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-identity b {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
}
|
2026-08-17 19:09:17 +12:00
|
|
|
|
/* A link and a submit button sit in this menu side by side, and they have to be one row
|
|
|
|
|
|
shape: the operator reads them as two entries of the same list, not as a link above a
|
|
|
|
|
|
form. */
|
|
|
|
|
|
.account-panel > a,
|
|
|
|
|
|
.account-panel form button {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 9px;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-panel > a:hover {
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-panel > a {
|
2026-08-16 12:13:51 +12:00
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
}
|
2026-08-17 19:09:17 +12:00
|
|
|
|
.account-panel form {
|
|
|
|
|
|
margin-top: 2px;
|
|
|
|
|
|
}
|
2026-08-16 12:13:51 +12:00
|
|
|
|
.account-panel form button {
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
border-color: transparent;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-panel form button:hover:not(:disabled) {
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-14 09:40:03 +12:00
|
|
|
|
@media (max-width: 900px) {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
.topbar-version {
|
2026-08-14 09:40:03 +12:00
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- omni search ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.omni {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-input {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni[data-open="true"] .omni-input {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
}
|
2026-08-15 09:23:26 +12:00
|
|
|
|
.omni-input input[type="search"] {
|
2026-08-14 09:40:03 +12:00
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
min-width: 0;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
height: 100%;
|
|
|
|
|
|
padding: 0;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
border: 0;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
border-radius: 0;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
background: none;
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-input input::placeholder {
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-input .ico {
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
flex: 0 0 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* The shortcut is printed on the control because it is the only thing on the page saying
|
|
|
|
|
|
the console can be driven from the keyboard. */
|
|
|
|
|
|
.omni-key {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font: 10.5px/1.3 var(--mono);
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-panel {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: calc(100% + 6px);
|
2026-08-16 21:20:55 +12:00
|
|
|
|
right: auto;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-height: min(50vh, 420px);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
padding: 6px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
|
|
|
|
|
|
z-index: 40;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-item .ico {
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
flex: 0 0 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-item.on,
|
|
|
|
|
|
.omni-item:hover {
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-item b {
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-item small {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-item span {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-group {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 10.5px;
|
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- notification bell ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.bell {
|
|
|
|
|
|
position: relative;
|
2026-08-16 21:20:55 +12:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
height: 38px;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.bell-button {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
2026-08-16 21:20:55 +12:00
|
|
|
|
width: 38px;
|
|
|
|
|
|
height: 38px;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-button:hover {
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell[data-open="true"] .bell-button {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
/* The badge counts unread events. It is a count rather than a dot because "there is news"
|
|
|
|
|
|
and "there are fourteen things you have not read" are different amounts of urgency. */
|
|
|
|
|
|
.bell-badge {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -6px;
|
|
|
|
|
|
right: -6px;
|
|
|
|
|
|
min-width: 17px;
|
|
|
|
|
|
height: 17px;
|
|
|
|
|
|
padding: 0 4px;
|
|
|
|
|
|
border-radius: 9px;
|
|
|
|
|
|
background: var(--danger);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font: 700 10.5px/17px var(--sans);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-panel {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: calc(100% + 8px);
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
width: min(420px, 92vw);
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
|
|
|
|
|
|
z-index: 40;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 11px 14px;
|
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-head b {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-list {
|
|
|
|
|
|
max-height: min(58vh, 460px);
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 11px;
|
|
|
|
|
|
padding: 11px 14px;
|
|
|
|
|
|
border-bottom: 1px solid var(--line-soft);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-item:last-child {
|
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-item:hover {
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
/* An unread row wears a bar down its left edge rather than a different background: the
|
|
|
|
|
|
list is read by scanning, and a row that changes colour when it is read makes the whole
|
|
|
|
|
|
panel reflow visually the moment it is opened. */
|
|
|
|
|
|
.bell-item[data-unread="true"] {
|
|
|
|
|
|
box-shadow: inset 2px 0 0 var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-item .glyph {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-body {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-body b {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-body p {
|
|
|
|
|
|
margin: 2px 0 0;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-body time {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-top: 3px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-foot {
|
|
|
|
|
|
padding: 9px 14px;
|
|
|
|
|
|
border-top: 1px solid var(--line);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-foot a {
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- rail ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.rail {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: var(--top);
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: var(--rail);
|
2026-08-15 09:23:26 +12:00
|
|
|
|
padding: 12px 12px max(20px, var(--safe-bottom));
|
2026-08-14 09:40:03 +12:00
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
background: var(--rail-bg);
|
|
|
|
|
|
border-right: 1px solid var(--line);
|
|
|
|
|
|
z-index: 20;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail-group + .rail-group {
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
}
|
2026-08-15 09:23:26 +12:00
|
|
|
|
.rail-group:first-child {
|
|
|
|
|
|
margin-bottom: 9px;
|
|
|
|
|
|
padding-bottom: 9px;
|
|
|
|
|
|
border-bottom: 1px solid var(--line-soft);
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.rail-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 9px 10px 5px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font: 600 10.5px/1 var(--sans);
|
|
|
|
|
|
letter-spacing: 0.1em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail-head .caret {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
transition: transform 0.12s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail-head[aria-expanded="false"] .caret {
|
|
|
|
|
|
transform: rotate(-90deg);
|
|
|
|
|
|
}
|
2026-08-15 09:23:26 +12:00
|
|
|
|
.rail-head-static {
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.rail a {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 7px 10px;
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail a:hover {
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail a[aria-current="page"] {
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail a .ico {
|
|
|
|
|
|
flex: 0 0 17px;
|
|
|
|
|
|
opacity: 0.85;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail a[aria-current="page"] .ico {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* A rail item may carry the same badge the bell does, so news is visible without the
|
|
|
|
|
|
panel being open. */
|
|
|
|
|
|
.rail-badge {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
min-width: 18px;
|
|
|
|
|
|
padding: 0 5px;
|
|
|
|
|
|
border-radius: 9px;
|
|
|
|
|
|
background: var(--danger);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font: 700 10.5px/17px var(--sans);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- page ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.page {
|
|
|
|
|
|
width: min(calc(100vw - var(--rail)), 1920px);
|
2026-08-16 12:13:51 +12:00
|
|
|
|
min-width: 0;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
margin: var(--top) 0 0 calc(var(--rail) + max(0px, (100vw - var(--rail) - 1920px) / 2));
|
|
|
|
|
|
padding: clamp(24px, 2.4vw, 44px) clamp(22px, 3vw, 56px) 80px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head h1 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
|
letter-spacing: -0.02em;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head p {
|
|
|
|
|
|
margin: 5px 0 0;
|
|
|
|
|
|
max-width: 68ch;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
2026-08-15 09:23:26 +12:00
|
|
|
|
.page-head-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head-icon {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
flex: 0 0 40px;
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head-icon .ico {
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head-text {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.page-head-row .page-head-actions {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.crumbs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.crumbs a {
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.crumbs a:hover {
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-15 09:23:26 +12:00
|
|
|
|
@media (max-width: 1400px) {
|
2026-08-14 09:40:03 +12:00
|
|
|
|
:root {
|
|
|
|
|
|
--rail: 0px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail {
|
|
|
|
|
|
transform: translateX(-100%);
|
2026-08-15 09:23:26 +12:00
|
|
|
|
transition: transform 0.2s cubic-bezier(.22, 1, .36, 1);
|
|
|
|
|
|
width: min(320px, calc(100vw - 72px));
|
|
|
|
|
|
padding-right: 14px;
|
|
|
|
|
|
padding-left: max(14px, var(--safe-left));
|
|
|
|
|
|
background: rgba(8, 12, 18, .98);
|
|
|
|
|
|
box-shadow: none;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.rail[data-open="true"] {
|
|
|
|
|
|
transform: none;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
box-shadow: 24px 0 80px rgba(0, 0, 0, 0.58);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.topbar-brand {
|
|
|
|
|
|
flex: 0 0 auto;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
padding: 0 4px 0 12px;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.page {
|
|
|
|
|
|
margin-left: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-15 09:23:26 +12:00
|
|
|
|
.rail-scrim {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1400px) {
|
|
|
|
|
|
.rail-scrim {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
inset: var(--top) 0 0 0;
|
|
|
|
|
|
z-index: 19;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
background: rgba(1, 4, 9, .58);
|
|
|
|
|
|
backdrop-filter: blur(5px);
|
|
|
|
|
|
animation: rail-scrim-in .16s ease-out;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail-scrim:hover:not(:disabled) {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: rgba(1, 4, 9, .58);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes rail-scrim-in {
|
|
|
|
|
|
from { opacity: 0; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.rail-toggle {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
width: 34px;
|
|
|
|
|
|
height: 34px;
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
2026-08-15 09:23:26 +12:00
|
|
|
|
@media (max-width: 1400px) {
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.rail-toggle {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- error banner ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.banner {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
padding: 11px 14px;
|
|
|
|
|
|
border: 1px solid var(--danger);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--danger-wash);
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.banner button {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- cards ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.card {
|
|
|
|
|
|
padding: 16px 18px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
}
|
|
|
|
|
|
.card + .card,
|
|
|
|
|
|
.grid + .card,
|
|
|
|
|
|
.card + .grid,
|
|
|
|
|
|
.grid + .grid {
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 11px;
|
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head h2 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 14.5px;
|
|
|
|
|
|
font-weight: 620;
|
|
|
|
|
|
letter-spacing: -0.01em;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head p {
|
|
|
|
|
|
margin: 2px 0 0;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head-text {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head-actions {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-foot {
|
|
|
|
|
|
margin-top: 14px;
|
|
|
|
|
|
padding-top: 13px;
|
|
|
|
|
|
border-top: 1px solid var(--line-soft);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
.grid[data-cols="2"] {
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
.grid[data-cols="wide"] {
|
|
|
|
|
|
grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr);
|
|
|
|
|
|
}
|
|
|
|
|
|
@media (max-width: 1100px) {
|
|
|
|
|
|
.grid[data-cols="wide"] {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- tiles ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.tiles {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile {
|
|
|
|
|
|
padding: 13px 15px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile .glyph {
|
2026-08-14 11:47:32 +12:00
|
|
|
|
/* Summary cards share tile padding, so their marker always starts at the same top-left
|
|
|
|
|
|
inset. The SVG itself remains centred by .glyph's grid treatment. */
|
|
|
|
|
|
margin: 0 0 9px;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.tile b {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
|
letter-spacing: -0.03em;
|
|
|
|
|
|
line-height: 1.15;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile b.small {
|
|
|
|
|
|
font-size: 14.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
letter-spacing: -0.01em;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile span {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-top: 3px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* A tile that links somewhere behaves like one; a tile that does not must not look like
|
|
|
|
|
|
it does. */
|
|
|
|
|
|
a.tile {
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
a.tile:hover {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- glyphs, tags, chips ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.ico {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
width: 1em;
|
|
|
|
|
|
height: 1em;
|
|
|
|
|
|
font-size: 17px;
|
|
|
|
|
|
fill: none;
|
|
|
|
|
|
stroke: currentColor;
|
|
|
|
|
|
stroke-width: 1.7;
|
|
|
|
|
|
stroke-linecap: round;
|
|
|
|
|
|
stroke-linejoin: round;
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.glyph {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
width: 30px;
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
flex: 0 0 30px;
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile .glyph .ico { width: 18px; height: 18px; margin: auto; }
|
|
|
|
|
|
|
|
|
|
|
|
.loading-page { display: grid; gap: 20px; }
|
|
|
|
|
|
.loading-heading { display: grid; gap: 10px; max-width: 48rem; }
|
|
|
|
|
|
.loading-heading .skeleton:first-child { height: 34px; width: 28%; }
|
|
|
|
|
|
.loading-heading .skeleton:last-child { height: 18px; width: 72%; }
|
|
|
|
|
|
.loading-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
|
|
|
|
|
|
.loading-tiles .skeleton { height: 132px; }
|
|
|
|
|
|
.loading-card { display: grid; gap: 12px; min-height: 180px; padding: 22px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
|
|
|
|
|
|
.loading-card .skeleton:first-child { height: 20px; width: 32%; }
|
|
|
|
|
|
.loading-card .skeleton:not(:first-child) { height: 14px; }
|
|
|
|
|
|
@media (max-width: 820px) { .loading-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
|
|
|
|
.glyph .ico {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
.glyph[data-tone="ok"] {
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.glyph[data-tone="idle"] {
|
|
|
|
|
|
background: var(--idle-wash);
|
|
|
|
|
|
color: var(--idle-ink);
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.glyph[data-tone="warn"] {
|
|
|
|
|
|
background: var(--warn-wash);
|
|
|
|
|
|
color: var(--warn-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.glyph[data-tone="bad"] {
|
|
|
|
|
|
background: var(--danger-wash);
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.glyph[data-tone="info"] {
|
|
|
|
|
|
background: var(--info-wash);
|
|
|
|
|
|
color: var(--info-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.glyph[data-tone="note"] {
|
|
|
|
|
|
background: var(--note-wash);
|
|
|
|
|
|
color: var(--note-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.glyph[data-tone="data"] {
|
|
|
|
|
|
background: var(--data-wash);
|
|
|
|
|
|
color: var(--data-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tag {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* A verdict tag carries a dot in its own colour: tone alone is no signal to somebody who
|
|
|
|
|
|
cannot separate the green from the amber. */
|
|
|
|
|
|
.tag[data-tone]::before {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
width: 6px;
|
|
|
|
|
|
height: 6px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: currentColor;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tag[data-tone="ok"] {
|
|
|
|
|
|
border-color: rgba(82, 181, 75, 0.35);
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.tag[data-tone="idle"] {
|
|
|
|
|
|
border-color: rgba(74, 143, 96, 0.35);
|
|
|
|
|
|
background: var(--idle-wash);
|
|
|
|
|
|
color: var(--idle-ink);
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.tag[data-tone="warn"] {
|
|
|
|
|
|
border-color: rgba(239, 196, 107, 0.35);
|
|
|
|
|
|
background: var(--warn-wash);
|
|
|
|
|
|
color: var(--warn-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tag[data-tone="bad"] {
|
|
|
|
|
|
border-color: rgba(229, 83, 75, 0.35);
|
|
|
|
|
|
background: var(--danger-wash);
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tag[data-tone="info"] {
|
|
|
|
|
|
border-color: rgba(79, 156, 216, 0.35);
|
|
|
|
|
|
background: var(--info-wash);
|
|
|
|
|
|
color: var(--info-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tag[data-tone="note"] {
|
|
|
|
|
|
border-color: rgba(160, 124, 232, 0.35);
|
|
|
|
|
|
background: var(--note-wash);
|
|
|
|
|
|
color: var(--note-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tag[data-tone="data"] {
|
|
|
|
|
|
border-color: rgba(63, 194, 182, 0.35);
|
|
|
|
|
|
background: var(--data-wash);
|
|
|
|
|
|
color: var(--data-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chip {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
padding: 3px 9px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-xs);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font: 11.5px/1.5 var(--mono);
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chip[data-tone="ok"] {
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.chip[data-tone="idle"] {
|
|
|
|
|
|
color: var(--idle-ink);
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.chip[data-tone="warn"] {
|
|
|
|
|
|
color: var(--warn-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.chip[data-tone="bad"] {
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.chip[data-tone="info"] {
|
|
|
|
|
|
color: var(--info-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.chip[data-tone="note"] {
|
|
|
|
|
|
color: var(--note-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.chip[data-tone="data"] {
|
|
|
|
|
|
color: var(--data-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.chips {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- tables ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* Dense but readable, which is most of what this console is for. The wrapper is what
|
|
|
|
|
|
scrolls, so a wide table never makes the page scroll sideways. */
|
|
|
|
|
|
.table-wrap {
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
overscroll-behavior-inline: contain;
|
|
|
|
|
|
-webkit-overflow-scrolling: touch;
|
|
|
|
|
|
margin: 0 -18px -16px;
|
|
|
|
|
|
padding: 0 18px 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
table {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-width: max-content;
|
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-15 09:23:26 +12:00
|
|
|
|
/* Tablets get a drawer shell rather than a squeezed desktop rail. The controls remain in
|
|
|
|
|
|
one calm bar in both orientations; only the least useful labels disappear as space gets
|
|
|
|
|
|
tighter, while the live status itself always remains visible. */
|
|
|
|
|
|
@media (max-width: 1400px) {
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.topbar {
|
|
|
|
|
|
display: flex;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
gap: 10px;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
height: var(--top);
|
2026-08-15 09:23:26 +12:00
|
|
|
|
padding: var(--safe-top) max(16px, var(--safe-right)) 0 max(16px, var(--safe-left));
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.topbar-brand {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail-toggle {
|
|
|
|
|
|
margin-left: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-spacer {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar .omni {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
left: auto;
|
|
|
|
|
|
transform: none;
|
|
|
|
|
|
flex: 1 1 auto;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
width: auto;
|
|
|
|
|
|
max-width: 640px;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.omni-key,
|
2026-08-16 21:20:55 +12:00
|
|
|
|
.topbar-version {
|
2026-08-14 09:40:03 +12:00
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-tools {
|
|
|
|
|
|
display: contents;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-button,
|
|
|
|
|
|
.rail-toggle {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-status {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
width: 40px;
|
|
|
|
|
|
flex-basis: 40px;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
min-width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
background: var(--surface);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
2026-08-16 21:20:55 +12:00
|
|
|
|
.omni-input,
|
|
|
|
|
|
.bell,
|
|
|
|
|
|
.bell-button,
|
|
|
|
|
|
.account-menu,
|
|
|
|
|
|
.account-trigger {
|
|
|
|
|
|
height: 40px;
|
2026-08-16 12:13:51 +12:00
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.page {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: clamp(24px, 3vw, 34px) max(24px, calc(var(--safe-right) + 10px)) max(64px, calc(var(--safe-bottom) + 36px)) max(24px, calc(var(--safe-left) + 10px));
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
.table-wrap {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
margin: 0 -18px -16px;
|
|
|
|
|
|
padding: 0 18px 18px;
|
|
|
|
|
|
scrollbar-gutter: stable;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
|
|
|
|
|
table {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
th,
|
|
|
|
|
|
td {
|
|
|
|
|
|
padding-right: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-15 09:23:26 +12:00
|
|
|
|
|
2026-08-16 12:13:51 +12:00
|
|
|
|
@media (max-width: 1100px) {
|
|
|
|
|
|
.account-trigger {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-name,
|
|
|
|
|
|
.account-caret {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-15 09:23:26 +12:00
|
|
|
|
@media (max-width: 820px) {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
.brand-word {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar-status {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page {
|
|
|
|
|
|
padding: 22px max(16px, var(--safe-right)) max(56px, calc(var(--safe-bottom) + 28px)) max(16px, var(--safe-left));
|
|
|
|
|
|
}
|
|
|
|
|
|
.grid,
|
|
|
|
|
|
.grid[data-cols="2"],
|
|
|
|
|
|
.grid[data-cols="wide"],
|
|
|
|
|
|
.grid.two {
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tiles {
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
.table-wrap {
|
|
|
|
|
|
margin-right: -16px;
|
|
|
|
|
|
margin-left: -16px;
|
|
|
|
|
|
padding-right: 16px;
|
|
|
|
|
|
padding-left: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-16 12:13:51 +12:00
|
|
|
|
|
2026-08-16 21:20:55 +12:00
|
|
|
|
/* A phone keeps every global control on one row. Search is the only flexible item: the
|
|
|
|
|
|
navigation and identity controls retain useful targets while the field gives up width
|
|
|
|
|
|
before the header can wrap. Its relative wrapper still anchors the results directly
|
|
|
|
|
|
beneath the field at exactly the width available here. */
|
2026-08-16 12:13:51 +12:00
|
|
|
|
@media (max-width: 680px) {
|
|
|
|
|
|
:root {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
--top: calc(60px + var(--safe-top));
|
2026-08-16 12:13:51 +12:00
|
|
|
|
}
|
|
|
|
|
|
.topbar {
|
|
|
|
|
|
align-items: center;
|
2026-08-16 21:20:55 +12:00
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
|
gap: clamp(4px, 1.5vw, 8px);
|
|
|
|
|
|
padding: calc(var(--safe-top) + 8px) max(8px, var(--safe-right)) 8px max(8px, var(--safe-left));
|
2026-08-16 12:13:51 +12:00
|
|
|
|
}
|
|
|
|
|
|
.rail-toggle { order: 1; }
|
2026-08-16 21:20:55 +12:00
|
|
|
|
.topbar-brand {
|
|
|
|
|
|
order: 2;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
flex: 0 0 auto;
|
2026-08-16 12:13:51 +12:00
|
|
|
|
}
|
|
|
|
|
|
.topbar .omni {
|
2026-08-16 21:20:55 +12:00
|
|
|
|
order: 3;
|
|
|
|
|
|
flex: 1 1 96px;
|
|
|
|
|
|
width: 0;
|
|
|
|
|
|
min-width: 0;
|
2026-08-16 12:13:51 +12:00
|
|
|
|
max-width: none;
|
|
|
|
|
|
}
|
2026-08-16 21:20:55 +12:00
|
|
|
|
.topbar-status {
|
|
|
|
|
|
order: 4;
|
|
|
|
|
|
margin-left: 0;
|
2026-08-16 12:13:51 +12:00
|
|
|
|
}
|
2026-08-16 21:20:55 +12:00
|
|
|
|
.bell { order: 5; }
|
|
|
|
|
|
.account-menu { order: 6; }
|
|
|
|
|
|
.brand-mark {
|
|
|
|
|
|
width: 30px;
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
flex-basis: 30px;
|
2026-08-16 12:13:51 +12:00
|
|
|
|
}
|
|
|
|
|
|
.account-panel,
|
|
|
|
|
|
.bell-panel {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: calc(var(--top) + 8px);
|
|
|
|
|
|
}
|
|
|
|
|
|
.account-panel {
|
|
|
|
|
|
right: max(12px, var(--safe-right));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 370px) {
|
|
|
|
|
|
.tiles {
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
th {
|
|
|
|
|
|
padding: 0 12px 8px 0;
|
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
letter-spacing: 0.07em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
td {
|
|
|
|
|
|
padding: 9px 12px 9px 0;
|
|
|
|
|
|
border-bottom: 1px solid var(--line-soft);
|
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
|
}
|
|
|
|
|
|
tr:last-child td {
|
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
tbody tr:hover td {
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
th:last-child,
|
|
|
|
|
|
td:last-child {
|
|
|
|
|
|
padding-right: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
td.num,
|
|
|
|
|
|
th.num {
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
|
}
|
|
|
|
|
|
td.mono {
|
|
|
|
|
|
font: 12px/1.5 var(--mono);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
td.nowrap,
|
|
|
|
|
|
th.nowrap {
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
td.muted {
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
/* A sortable header is a button, so it is reachable from a keyboard. */
|
|
|
|
|
|
th button {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
letter-spacing: inherit;
|
|
|
|
|
|
text-transform: inherit;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
th button:hover {
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
th[aria-sort] button {
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.table-row-link {
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.table-row-link:hover {
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- lists ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 1px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding: 11px 0;
|
|
|
|
|
|
border-bottom: 1px solid var(--line-soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item:last-child {
|
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item .list-body {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item b {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item p {
|
|
|
|
|
|
margin: 2px 0 0;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item .list-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- controls ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
button,
|
|
|
|
|
|
.btn {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 7px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
padding: 0 13px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
font: 600 13px/1 var(--sans);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
button:hover:not(:disabled),
|
|
|
|
|
|
.btn:hover {
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
border-color: #2c3542;
|
|
|
|
|
|
}
|
|
|
|
|
|
button:disabled {
|
|
|
|
|
|
opacity: 0.45;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
button[data-variant="primary"] {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
|
color: #06240a;
|
|
|
|
|
|
}
|
|
|
|
|
|
button[data-variant="primary"]:hover:not(:disabled) {
|
|
|
|
|
|
background: var(--accent-ink);
|
|
|
|
|
|
border-color: var(--accent-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
button[data-variant="danger"] {
|
|
|
|
|
|
border-color: rgba(229, 83, 75, 0.4);
|
|
|
|
|
|
background: var(--danger-wash);
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
button[data-variant="danger"]:hover:not(:disabled) {
|
|
|
|
|
|
background: rgba(229, 83, 75, 0.22);
|
|
|
|
|
|
}
|
|
|
|
|
|
button[data-variant="quiet"] {
|
|
|
|
|
|
border-color: transparent;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
button[data-variant="quiet"]:hover:not(:disabled) {
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
button[data-size="sm"] {
|
|
|
|
|
|
height: 26px;
|
|
|
|
|
|
padding: 0 9px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.field {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field > span {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field > small {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-top: 5px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
input[type="text"],
|
|
|
|
|
|
input[type="password"],
|
|
|
|
|
|
input[type="search"],
|
|
|
|
|
|
input[type="number"],
|
|
|
|
|
|
input[type="date"],
|
2026-08-15 09:23:26 +12:00
|
|
|
|
input[type="time"],
|
|
|
|
|
|
input[type="datetime-local"],
|
2026-08-14 09:40:03 +12:00
|
|
|
|
input[type="url"],
|
|
|
|
|
|
select,
|
|
|
|
|
|
textarea {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
textarea {
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
min-height: 74px;
|
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
|
resize: vertical;
|
|
|
|
|
|
}
|
|
|
|
|
|
input:focus,
|
|
|
|
|
|
select:focus,
|
|
|
|
|
|
textarea:focus {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
input::placeholder,
|
|
|
|
|
|
textarea::placeholder {
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
select {
|
|
|
|
|
|
appearance: none;
|
|
|
|
|
|
padding-right: 26px;
|
|
|
|
|
|
background-image: linear-gradient(45deg, transparent 50%, var(--quiet) 50%),
|
|
|
|
|
|
linear-gradient(135deg, var(--quiet) 50%, transparent 50%);
|
|
|
|
|
|
background-position: calc(100% - 14px) 14px, calc(100% - 9px) 14px;
|
|
|
|
|
|
background-size: 5px 5px;
|
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.fields {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 13px;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
.field-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A switch, not a checkbox: the console is full of on/off policy and a control that says
|
|
|
|
|
|
which way it is pointing from across the room is worth the extra markup. */
|
|
|
|
|
|
.check {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 11px;
|
|
|
|
|
|
padding: 10px 0;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check input {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check .switch {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 34px;
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
flex: 0 0 34px;
|
|
|
|
|
|
margin-top: 1px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
transition: background 0.12s ease, border-color 0.12s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check .switch::after {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 2px;
|
|
|
|
|
|
left: 2px;
|
|
|
|
|
|
width: 14px;
|
|
|
|
|
|
height: 14px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--muted);
|
|
|
|
|
|
transition: transform 0.12s ease, background 0.12s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check input:checked + .switch {
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.check input:checked + .switch::after {
|
|
|
|
|
|
transform: translateX(14px);
|
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.check input:focus-visible + .switch {
|
|
|
|
|
|
outline: 2px solid var(--accent);
|
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check input:disabled + .switch {
|
|
|
|
|
|
opacity: 0.45;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check-body b {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check-body p {
|
|
|
|
|
|
margin: 2px 0 0;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A row of exclusive choices — a date window, a severity — where a select would hide the
|
|
|
|
|
|
options behind a click. */
|
|
|
|
|
|
.segments {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
padding: 2px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
}
|
|
|
|
|
|
.segments button {
|
|
|
|
|
|
height: 26px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.segments button[aria-pressed="true"] {
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- filter bar ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* Fast filtering is most of what makes a table useful, so the controls sit above it in a
|
|
|
|
|
|
row of their own rather than behind a "filters" disclosure nobody opens. */
|
|
|
|
|
|
.filters {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
|
padding: 13px 15px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
}
|
|
|
|
|
|
.filters .field {
|
|
|
|
|
|
flex: 1 1 160px;
|
|
|
|
|
|
max-width: 260px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.filters .field.grow {
|
|
|
|
|
|
flex: 2 1 240px;
|
|
|
|
|
|
max-width: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.filters .filter-actions {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.filter-summary {
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- charts ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* One bar chart, drawn with divs. A charting library would be the single largest
|
|
|
|
|
|
dependency in the console for the sake of four bar charts. */
|
|
|
|
|
|
.bars {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
gap: 3px;
|
|
|
|
|
|
height: 92px;
|
|
|
|
|
|
padding-top: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bar {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
flex: 1 1 0;
|
|
|
|
|
|
min-width: 3px;
|
|
|
|
|
|
border-radius: 2px 2px 0 0;
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
border-top: 2px solid var(--accent);
|
|
|
|
|
|
min-height: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bar[data-tone="bad"] {
|
|
|
|
|
|
background: var(--danger-wash);
|
|
|
|
|
|
border-top-color: var(--danger);
|
|
|
|
|
|
}
|
|
|
|
|
|
.bar[data-empty="true"] {
|
|
|
|
|
|
background: var(--line-soft);
|
|
|
|
|
|
border-top-color: var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.bars-axis {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A proportion bar, for one number against a total. */
|
|
|
|
|
|
.meter {
|
|
|
|
|
|
height: 6px;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
background: var(--surface-hi);
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
.meter > div {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.meter[data-tone="warn"] > div {
|
|
|
|
|
|
background: var(--warn);
|
|
|
|
|
|
}
|
|
|
|
|
|
.meter[data-tone="bad"] > div {
|
|
|
|
|
|
background: var(--danger);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- log / code ---------- */
|
|
|
|
|
|
|
2026-08-18 08:41:48 +12:00
|
|
|
|
/* The log table.
|
|
|
|
|
|
|
|
|
|
|
|
Six columns, in the order the questions are asked: when, how bad, which part of the
|
|
|
|
|
|
server, what happened, did it work, how long did it take. The columns are the whole
|
|
|
|
|
|
design — an operator learns where to look once and then reads down a column rather than
|
|
|
|
|
|
across a line, which is what makes thirty seconds of watching this page worth anything.
|
|
|
|
|
|
|
|
|
|
|
|
Monospace is spent deliberately rather than applied to the row. The time, the method,
|
|
|
|
|
|
the status and the duration are values to be compared down a column and are set in it;
|
|
|
|
|
|
the summary is a sentence and is set in the sans face, because a sentence in monospace
|
|
|
|
|
|
is slower to read and looks like a dump of something. */
|
|
|
|
|
|
|
|
|
|
|
|
.logbar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px 10px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logbar-filters {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1 1 520px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logbar-filters select {
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
padding: 0 26px 0 9px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* The actions are secondary to the filters and are marked so by weight, not by distance:
|
|
|
|
|
|
the previous bar gave Pause and Export the same prominence as the controls that decide
|
|
|
|
|
|
what the table contains. */
|
|
|
|
|
|
.logbar-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logsearch {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1 1 260px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
min-width: 200px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logsearch svg {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 9px;
|
|
|
|
|
|
width: 14px;
|
|
|
|
|
|
height: 14px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logsearch input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 30px;
|
|
|
|
|
|
padding-left: 28px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* One chip per narrowing in force. They are the only place a filter can be removed one at
|
|
|
|
|
|
a time, which is what makes drilling in by clicking a value in the table reversible. */
|
|
|
|
|
|
.logchips {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logchip {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font: 500 11px/1 var(--sans);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logchip svg {
|
|
|
|
|
|
width: 11px;
|
|
|
|
|
|
height: 11px;
|
|
|
|
|
|
opacity: .7;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logchip:hover:not(:disabled) {
|
|
|
|
|
|
border-color: var(--danger);
|
|
|
|
|
|
background: var(--danger-wash);
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logchip-clear {
|
|
|
|
|
|
border-style: dashed;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.logshell {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.logview {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
height: 62vh;
|
|
|
|
|
|
min-height: 320px;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
overflow: auto;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: #080a0e;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
font: 12px/1.5 var(--sans);
|
2026-08-15 09:23:26 +12:00
|
|
|
|
contain: layout paint style;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
2026-08-19 06:57:59 +12:00
|
|
|
|
/* Every track but the event is a fixed width, and that is the whole of why the columns
|
|
|
|
|
|
line up. A row is its own grid container — there are twenty thousand of them and no
|
|
|
|
|
|
shared table — so a `minmax()` track sized from its own content gave each row a slightly
|
|
|
|
|
|
different Service and Result column, and the Result column moved as the widest verdict
|
|
|
|
|
|
on screen changed. Fixed tracks cannot move; content inside them truncates instead.
|
|
|
|
|
|
|
|
|
|
|
|
Vertical rhythm is set here too: cells start at the top of the row rather than being
|
|
|
|
|
|
centred in it, so the Time, Level, Service and Result of a two-line event sit on the
|
|
|
|
|
|
same line as its summary instead of dropping half a line to the middle of the pair. */
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.loghead,
|
|
|
|
|
|
.logrow {
|
|
|
|
|
|
display: grid;
|
2026-08-19 06:57:59 +12:00
|
|
|
|
grid-template-columns: 92px 52px 190px minmax(240px, 1fr) 150px 68px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
}
|
2026-08-14 11:47:32 +12:00
|
|
|
|
.loghead {
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
top: 0;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
z-index: 2;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
height: 31px;
|
2026-08-14 11:47:32 +12:00
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
|
background: #10131a;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
letter-spacing: .08em;
|
|
|
|
|
|
text-transform: uppercase;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
min-width: 900px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.loghead > :last-child {
|
|
|
|
|
|
text-align: right;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
}
|
|
|
|
|
|
.logbody {
|
|
|
|
|
|
position: relative;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
min-width: 900px;
|
2026-08-14 11:47:32 +12:00
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
|
|
|
|
|
|
/* The date is a divider between days rather than a column repeated on every row. A log
|
|
|
|
|
|
watched live is almost always one day; printing the date 20,000 times to cover the
|
|
|
|
|
|
handful of rows where it changes is the trade the compact timestamp exists to avoid. */
|
|
|
|
|
|
.logday {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
left: 0;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
display: flex;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
align-items: center;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
height: 26px;
|
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
background: linear-gradient(to bottom, rgba(255, 255, 255, .03), transparent);
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font: 700 10px/1 var(--sans);
|
|
|
|
|
|
letter-spacing: .08em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logday span {
|
|
|
|
|
|
padding-right: 10px;
|
|
|
|
|
|
background: #080a0e;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logday::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 1px;
|
|
|
|
|
|
background: var(--line-soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 06:57:59 +12:00
|
|
|
|
/* The figures below are the twin of the row geometry in `pages/Logs.tsx`: 7px of padding,
|
|
|
|
|
|
a 16px first line, a 2px gap and a 15px second line, over a 1px hairline. The row's
|
|
|
|
|
|
height is set inline from those constants, so a cell drawn taller than its share here is
|
|
|
|
|
|
text sliced through the middle rather than a row that grows. Every first-line cell is
|
|
|
|
|
|
therefore given `line-height: 16px` explicitly, whatever its font size. */
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logrow {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
left: 0;
|
2026-08-19 06:57:59 +12:00
|
|
|
|
align-items: start;
|
|
|
|
|
|
padding-top: 7px;
|
|
|
|
|
|
padding-bottom: 7px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, .03);
|
2026-08-15 09:23:26 +12:00
|
|
|
|
contain: strict;
|
|
|
|
|
|
}
|
2026-08-19 06:57:59 +12:00
|
|
|
|
.logrow > *,
|
|
|
|
|
|
.logrow-line {
|
|
|
|
|
|
line-height: 16px;
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logrow:hover {
|
|
|
|
|
|
background: rgba(255, 255, 255, .035);
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logrow[data-selected] {
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
}
|
|
|
|
|
|
/* A failure is marked on the row and not only in a column, because the thing an operator
|
|
|
|
|
|
is scanning for is a row, and a hairline at the start of one is found faster than a word
|
|
|
|
|
|
two thirds of the way along it. INFO is deliberately unmarked: most of the table is INFO
|
|
|
|
|
|
and a mark every row carries is a mark that says nothing. */
|
|
|
|
|
|
.logrow[data-level="ERROR"] {
|
|
|
|
|
|
box-shadow: inset 2px 0 0 var(--danger);
|
|
|
|
|
|
background: rgba(229, 83, 75, .05);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow[data-level="WARN"] {
|
|
|
|
|
|
box-shadow: inset 2px 0 0 var(--warn);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.logrow-time {
|
2026-08-14 09:40:03 +12:00
|
|
|
|
color: var(--quiet);
|
2026-08-19 06:57:59 +12:00
|
|
|
|
font: 11px/16px var(--mono);
|
2026-08-18 08:41:48 +12:00
|
|
|
|
font-variant-numeric: tabular-nums;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
|
|
|
|
|
|
/* Every structured value in the table is also the control that filters to it. They are
|
|
|
|
|
|
buttons rather than links because nothing navigates: the table narrows in place. */
|
|
|
|
|
|
.logfacet {
|
|
|
|
|
|
height: auto;
|
2026-08-14 09:40:03 +12:00
|
|
|
|
min-width: 0;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
padding: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
border: 0;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
background: none;
|
|
|
|
|
|
color: inherit;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
font: inherit;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logfacet:hover:not(:disabled) {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
border: 0;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
background: none;
|
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
|
text-underline-offset: 3px;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logfacet:focus-visible {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
outline: 1px solid var(--accent);
|
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
|
|
|
|
|
|
.logrow-level {
|
|
|
|
|
|
color: var(--quiet);
|
2026-08-19 06:57:59 +12:00
|
|
|
|
font: 700 10px/16px var(--sans);
|
2026-08-18 08:41:48 +12:00
|
|
|
|
letter-spacing: .06em;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-level[data-level="ERROR"] { color: var(--danger-ink); }
|
|
|
|
|
|
.logrow-level[data-level="WARN"] { color: var(--warn-ink); }
|
|
|
|
|
|
.logrow-level[data-level="INFO"] { color: var(--muted); }
|
|
|
|
|
|
.logrow-level[data-level="DEBUG"],
|
|
|
|
|
|
.logrow-level[data-level="TRACE"] { color: var(--quiet); }
|
|
|
|
|
|
|
|
|
|
|
|
/* Service and component. The service is the recognisable half — one word, upper case,
|
|
|
|
|
|
always the same colour for the same subsystem — and the component sits beside it in the
|
|
|
|
|
|
ordinary text colour, so the pair reads as a place rather than as two tags. The tones
|
|
|
|
|
|
are the console's secondary palette only: green, amber and red mean good, look and wrong
|
|
|
|
|
|
on every other page and must not start meaning "playback" on this one. */
|
|
|
|
|
|
.logrow-place {
|
|
|
|
|
|
display: flex;
|
2026-08-19 06:57:59 +12:00
|
|
|
|
align-items: center;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
gap: 5px;
|
|
|
|
|
|
min-width: 0;
|
2026-08-19 06:57:59 +12:00
|
|
|
|
height: 16px;
|
|
|
|
|
|
overflow: hidden;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
}
|
|
|
|
|
|
.logrow-service {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
max-width: 96px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
color: var(--quiet);
|
2026-08-19 06:57:59 +12:00
|
|
|
|
font: 700 10px/16px var(--sans);
|
2026-08-18 08:41:48 +12:00
|
|
|
|
letter-spacing: .07em;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-service[data-tone="info"] { color: var(--info-ink); }
|
|
|
|
|
|
.logrow-service[data-tone="note"] { color: var(--note-ink); }
|
|
|
|
|
|
.logrow-service[data-tone="data"] { color: var(--data-ink); }
|
|
|
|
|
|
.logrow-service[data-tone="idle"] { color: var(--idle-ink); }
|
|
|
|
|
|
.logrow-service[data-tone="quiet"] { color: var(--muted); }
|
|
|
|
|
|
.logrow-sep {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
color: var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-component {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11.5px;
|
2026-08-19 06:57:59 +12:00
|
|
|
|
line-height: 16px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The summary is the row. It is a button because selecting the row is what opens the
|
|
|
|
|
|
record, and the whole width of the column should be the target. */
|
|
|
|
|
|
.logrow-summary {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 2px;
|
2026-08-19 06:57:59 +12:00
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
justify-content: flex-start;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-summary:hover:not(:disabled) {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-summary:focus-visible {
|
|
|
|
|
|
outline: 1px solid var(--accent);
|
|
|
|
|
|
outline-offset: -1px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-line {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
min-width: 0;
|
2026-08-19 06:57:59 +12:00
|
|
|
|
height: 16px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
}
|
|
|
|
|
|
.logrow-action {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
color: var(--muted);
|
2026-08-19 06:57:59 +12:00
|
|
|
|
font: 600 10.5px/16px var(--mono);
|
2026-08-18 08:41:48 +12:00
|
|
|
|
letter-spacing: .04em;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-action[data-method="POST"],
|
|
|
|
|
|
.logrow-action[data-method="PUT"],
|
|
|
|
|
|
.logrow-action[data-method="PATCH"] { color: var(--info-ink); }
|
|
|
|
|
|
.logrow-action[data-method="DELETE"] { color: var(--danger-ink); }
|
|
|
|
|
|
.logrow-text {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
2026-08-19 06:57:59 +12:00
|
|
|
|
/* Identity on a request row, kept on the summary's own line. It gives way before the path
|
|
|
|
|
|
does — knowing which route was called matters more than which television called it, and
|
|
|
|
|
|
the drawer holds both either way. */
|
|
|
|
|
|
.logrow-trail {
|
|
|
|
|
|
flex: 0 1 auto;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
max-width: 38%;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 06:57:59 +12:00
|
|
|
|
/* The second line: the reason a failure needs no drawer, or the person and position that
|
|
|
|
|
|
make an application event mean something. Exactly one line tall, 15px, which is the
|
|
|
|
|
|
figure the row was measured with — it can truncate but it can never wrap, because a wrap
|
|
|
|
|
|
is a row overflowing into the one below it. */
|
|
|
|
|
|
.logrow-second {
|
|
|
|
|
|
height: 15px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
line-height: 15px;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-second[data-tone="error"] {
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-18 08:41:48 +12:00
|
|
|
|
/* A result is a word, not a badge. A success is understated to the point of being ignorable
|
|
|
|
|
|
— which is the correct amount of attention for the four hundredth 200 in a row — and only
|
|
|
|
|
|
a failure is given a fill. */
|
|
|
|
|
|
.logrow-result {
|
|
|
|
|
|
min-width: 0;
|
2026-08-19 06:57:59 +12:00
|
|
|
|
height: 16px;
|
|
|
|
|
|
overflow: hidden;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
}
|
|
|
|
|
|
.logrow-verdict {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
color: var(--quiet);
|
2026-08-19 06:57:59 +12:00
|
|
|
|
font: 500 11px/14px var(--mono);
|
2026-08-18 08:41:48 +12:00
|
|
|
|
}
|
|
|
|
|
|
.logrow-verdict[data-tone="ok"] { color: var(--muted); }
|
|
|
|
|
|
.logrow-verdict[data-tone="info"] { color: var(--info-ink); }
|
|
|
|
|
|
.logrow-verdict[data-tone="data"] { color: var(--data-ink); }
|
2026-08-19 06:57:59 +12:00
|
|
|
|
/* A fill still has to fit the row's line box: 14px of text and 1px either side is the
|
|
|
|
|
|
16px every other cell on the line occupies. */
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logrow-verdict[data-tone="warn"],
|
|
|
|
|
|
.logrow-verdict[data-tone="bad"] {
|
|
|
|
|
|
padding: 1px 6px;
|
|
|
|
|
|
border-radius: var(--radius-xs);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-verdict[data-tone="warn"] {
|
|
|
|
|
|
background: var(--warn-wash);
|
|
|
|
|
|
color: var(--warn-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-verdict[data-tone="bad"] {
|
|
|
|
|
|
background: var(--danger-wash);
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.logrow-duration {
|
|
|
|
|
|
color: var(--quiet);
|
2026-08-19 06:57:59 +12:00
|
|
|
|
font: 11px/16px var(--mono);
|
2026-08-18 08:41:48 +12:00
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logrow-duration[data-tone="warn"] { color: var(--warn-ink); }
|
|
|
|
|
|
.logrow-duration[data-tone="bad"] {
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Offered only once the operator has left the tail, which is the only time following the
|
|
|
|
|
|
log automatically would be taking the page away from them. */
|
|
|
|
|
|
.logtail {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
right: 18px;
|
|
|
|
|
|
bottom: 14px;
|
|
|
|
|
|
z-index: 3;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
height: 28px;
|
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
border: 1px solid var(--accent);
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
font: 600 11px/1 var(--sans);
|
|
|
|
|
|
box-shadow: 0 6px 16px rgba(0, 0, 0, .5);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logtail svg {
|
|
|
|
|
|
width: 12px;
|
|
|
|
|
|
height: 12px;
|
|
|
|
|
|
transform: rotate(90deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logtail:hover:not(:disabled) {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The drawer answers "exactly how and why". Sections rather than one alphabetical list,
|
|
|
|
|
|
and a field with no value is omitted rather than printed as `unknown`. */
|
|
|
|
|
|
.logdrawer {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
margin-top: 12px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
padding: 14px;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logdrawer-head {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 16px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
2026-08-15 09:23:26 +12:00
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logdrawer-head > div:first-child {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logdrawer-head b {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
display: block;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
font-size: 14px;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logdrawer-place {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
margin: 0;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
color: var(--quiet);
|
2026-08-18 08:41:48 +12:00
|
|
|
|
font-size: 11px;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logdrawer-error {
|
|
|
|
|
|
margin: 6px 0 0;
|
|
|
|
|
|
color: var(--danger-ink);
|
|
|
|
|
|
font: 12px/1.5 var(--mono);
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdrawer-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdrawer-grid {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
display: grid;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
|
|
|
|
gap: 16px 24px;
|
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdrawer-section h4 {
|
|
|
|
|
|
margin: 0 0 6px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font: 700 10px/1 var(--sans);
|
|
|
|
|
|
letter-spacing: .08em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdrawer-section dl {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(88px, max-content) minmax(0, 1fr);
|
|
|
|
|
|
gap: 4px 12px;
|
|
|
|
|
|
margin: 0;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
font: 12px/1.5 var(--mono);
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logdrawer-section dt { color: var(--muted); }
|
|
|
|
|
|
.logdrawer-section dd { min-width: 0; margin: 0; overflow-wrap: anywhere; }
|
|
|
|
|
|
.logdrawer-raw {
|
|
|
|
|
|
margin-top: 14px;
|
|
|
|
|
|
padding-top: 12px;
|
|
|
|
|
|
border-top: 1px solid var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdrawer-raw summary {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font: 700 10px/1 var(--sans);
|
|
|
|
|
|
letter-spacing: .08em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdrawer-raw pre {
|
|
|
|
|
|
max-height: 320px;
|
|
|
|
|
|
margin: 10px 0 0;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-xs);
|
|
|
|
|
|
background: #080a0e;
|
|
|
|
|
|
font: 11px/1.6 var(--mono);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Columns are given up in the order they are least missed: the duration first, since it is
|
|
|
|
|
|
the only one of the six whose absence costs nothing to understanding what happened, and
|
|
|
|
|
|
the timestamp after it. Level, service and the summary are never dropped — a log that
|
|
|
|
|
|
cannot say how bad, where, or what is not a log. */
|
|
|
|
|
|
@media (max-width: 1180px) {
|
|
|
|
|
|
.loghead,
|
|
|
|
|
|
.logrow {
|
2026-08-19 06:57:59 +12:00
|
|
|
|
grid-template-columns: 84px 46px 170px minmax(200px, 1fr) 130px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.loghead > :last-child,
|
|
|
|
|
|
.logrow-duration {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.loghead,
|
|
|
|
|
|
.logbody {
|
2026-08-19 06:57:59 +12:00
|
|
|
|
min-width: 680px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
|
.loghead,
|
|
|
|
|
|
.logrow {
|
2026-08-19 06:57:59 +12:00
|
|
|
|
grid-template-columns: 46px 140px minmax(180px, 1fr) 110px;
|
2026-08-18 08:41:48 +12:00
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.loghead > :first-child,
|
|
|
|
|
|
.logrow-time {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.loghead,
|
|
|
|
|
|
.logbody {
|
|
|
|
|
|
min-width: 520px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-14 11:47:32 +12:00
|
|
|
|
.logdetails {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdetails summary {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdetails summary::-webkit-details-marker { display: none; }
|
|
|
|
|
|
.logdetails summary::before {
|
|
|
|
|
|
content: '›';
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
margin-right: 6px;
|
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdetails[open] summary::before { transform: rotate(90deg); }
|
|
|
|
|
|
.logdetails dl {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(130px, max-content) minmax(0, 1fr);
|
|
|
|
|
|
gap: 4px 12px;
|
|
|
|
|
|
margin: 7px 0 2px;
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: rgba(255, 255, 255, .025);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdetails dt { color: var(--muted); }
|
|
|
|
|
|
.logdetails dd { min-width: 0; margin: 0; color: var(--text); overflow-wrap: anywhere; }
|
2026-08-14 09:40:03 +12:00
|
|
|
|
|
|
|
|
|
|
pre.code {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 12px 14px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: #080a0e;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font: 12px/1.6 var(--mono);
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- misc ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.empty {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 22px 0;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.muted {
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.quiet {
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
.mono {
|
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.note {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 10px 13px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-left: 2px solid var(--info);
|
|
|
|
|
|
border-radius: var(--radius-xs);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.note[data-tone="warn"] {
|
|
|
|
|
|
border-left-color: var(--warn);
|
|
|
|
|
|
}
|
|
|
|
|
|
.note[data-tone="bad"] {
|
|
|
|
|
|
border-left-color: var(--danger);
|
|
|
|
|
|
}
|
|
|
|
|
|
.note[data-tone="ok"] {
|
|
|
|
|
|
border-left-color: var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.stack {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.row.tight {
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.spacer {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* One spinner, and it is the only thing in the console that moves on its own. */
|
|
|
|
|
|
.spinner {
|
|
|
|
|
|
width: 14px;
|
|
|
|
|
|
height: 14px;
|
|
|
|
|
|
border: 2px solid var(--line);
|
|
|
|
|
|
border-top-color: var(--accent);
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
animation: spin 0.7s linear infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
to {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The page's own loading state: a shape rather than a spinner, so the layout does not
|
|
|
|
|
|
jump when the data lands. */
|
|
|
|
|
|
.skeleton {
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: linear-gradient(90deg, var(--surface) 25%, var(--surface-lift) 50%, var(--surface) 75%);
|
|
|
|
|
|
background-size: 300% 100%;
|
|
|
|
|
|
animation: shimmer 1.3s ease-in-out infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
@keyframes shimmer {
|
|
|
|
|
|
to {
|
|
|
|
|
|
background-position: -300% 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- toasts ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* A mutation says what happened here rather than in the error banner, which is reserved
|
|
|
|
|
|
for the console failing to talk to the gateway at all. */
|
|
|
|
|
|
.toasts {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
right: 18px;
|
|
|
|
|
|
bottom: 18px;
|
|
|
|
|
|
z-index: 50;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
max-width: min(420px, 90vw);
|
|
|
|
|
|
}
|
|
|
|
|
|
.toast {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 11px 14px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-left: 3px solid var(--accent);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
animation: toast-in 0.14s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.toast[data-tone="bad"] {
|
|
|
|
|
|
border-left-color: var(--danger);
|
|
|
|
|
|
}
|
|
|
|
|
|
.toast[data-tone="warn"] {
|
|
|
|
|
|
border-left-color: var(--warn);
|
|
|
|
|
|
}
|
|
|
|
|
|
.toast button {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
@keyframes toast-in {
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateY(6px);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- dialog ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.scrim {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
z-index: 45;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: rgba(4, 6, 9, 0.7);
|
|
|
|
|
|
backdrop-filter: blur(2px);
|
|
|
|
|
|
}
|
|
|
|
|
|
.dialog {
|
|
|
|
|
|
width: min(520px, 100%);
|
|
|
|
|
|
max-height: 86vh;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
padding: 20px 22px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
|
|
|
|
|
|
}
|
|
|
|
|
|
.dialog h2 {
|
|
|
|
|
|
margin: 0 0 6px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dialog p {
|
|
|
|
|
|
margin: 0 0 16px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dialog-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- key/value rows ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* A label on the left, a verdict on the right. The console's densest way of stating a
|
|
|
|
|
|
handful of facts about one subject, and the shape the overview is built from. */
|
|
|
|
|
|
.kv {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
.kv-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding: 9px 0;
|
|
|
|
|
|
border-bottom: 1px solid var(--line-soft);
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.kv-row:last-child {
|
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.kv-row > span:first-child {
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.kv-row > span:last-child {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A tile strip with no card around it, for a group of numbers inside one. */
|
|
|
|
|
|
.tiles.plain {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
.tiles.plain .tile {
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tiles.plain .tile b {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hint {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A two-column card grid that does not collapse as eagerly as the default. */
|
|
|
|
|
|
.grid.two {
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
@media (max-width: 980px) {
|
|
|
|
|
|
.grid.two {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- linkable list rows ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* A row that is a destination. The whole row is the target rather than a link inside it,
|
|
|
|
|
|
because a list of people is read by scanning names and clicking one. */
|
|
|
|
|
|
.list-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding: 12px 18px;
|
|
|
|
|
|
border-bottom: 1px solid var(--line-soft);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-row:last-child {
|
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-row:hover {
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-main {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 7px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-meta {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-top: 2px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-row .list-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* A card that holds nothing but a list: the rows carry their own padding, so the card
|
|
|
|
|
|
must not add a second inset around them. */
|
|
|
|
|
|
.card.flush {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Initials behind a name. Emby has no avatar for most households, and a row of identical
|
|
|
|
|
|
grey circles says nothing — the letters are what makes one row distinguishable from the
|
|
|
|
|
|
next at a glance. */
|
|
|
|
|
|
.avatar {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
width: 34px;
|
|
|
|
|
|
height: 34px;
|
|
|
|
|
|
flex: 0 0 34px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--note-wash);
|
|
|
|
|
|
color: var(--note-ink);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
letter-spacing: 0.02em;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-18 08:41:48 +12:00
|
|
|
|
/* The presence dot: four-valued, and the tone is passed rather than derived. The two
|
|
|
|
|
|
greens are the point — one set is connected now and the other was an hour ago, and both
|
|
|
|
|
|
are fine, so they read as the same answer at two strengths rather than as two answers.
|
|
|
|
|
|
The dot is 7px and colour alone is never the whole signal here: every one of these
|
|
|
|
|
|
carries the wording in its title. */
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.dot-state {
|
|
|
|
|
|
width: 7px;
|
|
|
|
|
|
height: 7px;
|
|
|
|
|
|
flex: 0 0 7px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
.dot-state[data-tone="ok"] {
|
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.dot-state[data-tone="idle"] {
|
|
|
|
|
|
background: var(--idle);
|
|
|
|
|
|
}
|
2026-08-14 09:40:03 +12:00
|
|
|
|
.dot-state[data-tone="warn"] {
|
|
|
|
|
|
background: var(--warn);
|
|
|
|
|
|
}
|
|
|
|
|
|
.dot-state[data-tone="bad"] {
|
|
|
|
|
|
background: var(--danger);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A column of switches, for a list of people or of options. */
|
|
|
|
|
|
.checks.columns {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0 24px;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.crumb {
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.crumb:hover {
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A run of small chips inside a table cell — a device's build history. */
|
|
|
|
|
|
.versions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- journey visits ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* One visit read from its first step to its last. A timeline rather than a table because
|
|
|
|
|
|
the shape of a visit — how many steps, and how far apart — is most of what is being
|
|
|
|
|
|
read, and a table of five rows hides it. */
|
|
|
|
|
|
.visit {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.visit + .visit {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.visit header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.visit header b {
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.visit header span {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.visit header .tag {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-answers {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
gap: 1px;
|
|
|
|
|
|
margin: 0 0 15px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-xs);
|
|
|
|
|
|
background: var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-answer {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
padding: 11px 12px 11px 38px;
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-answer .ico {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 13px;
|
|
|
|
|
|
left: 12px;
|
|
|
|
|
|
width: 16px;
|
|
|
|
|
|
height: 16px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-answer[data-kind="entry"] .ico,
|
|
|
|
|
|
.journey-answer[data-kind="outcome"] .ico { color: var(--accent); }
|
|
|
|
|
|
.journey-answer[data-kind="selection"] .ico { color: var(--note-ink); }
|
|
|
|
|
|
.journey-answer span,
|
|
|
|
|
|
.journey-answer b {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-answer span {
|
|
|
|
|
|
margin-bottom: 3px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
|
letter-spacing: .04em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-answer b {
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-timeline {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0 0 0 14px;
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
border-left: 1px solid var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-timeline li {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 7px 0 7px 5px;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.timeline-dot {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: -18px;
|
|
|
|
|
|
top: 12px;
|
|
|
|
|
|
width: 7px;
|
|
|
|
|
|
height: 7px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.timeline-dot[data-action="select"],
|
|
|
|
|
|
.timeline-dot[data-action="open"] { background: var(--note-ink); }
|
|
|
|
|
|
.timeline-dot[data-action="close"] { background: var(--quiet); }
|
|
|
|
|
|
.journey-timeline li > div { min-width: 0; }
|
|
|
|
|
|
.journey-timeline li b {
|
|
|
|
|
|
display: inline;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-timeline li div span {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.journey-timeline li time {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
padding-left: 10px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.visits {
|
|
|
|
|
|
max-height: 60vh;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
@media (max-width: 640px) {
|
|
|
|
|
|
.journey-answers { grid-template-columns: 1fr; }
|
|
|
|
|
|
.journey-timeline li { align-items: flex-start; flex-wrap: wrap; }
|
|
|
|
|
|
.journey-timeline li time { width: 100%; margin: 2px 0 0; padding-left: 0; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A headline number over a proportion bar, for one figure that has a denominator. */
|
|
|
|
|
|
.summary {
|
|
|
|
|
|
padding: 13px 15px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.summary-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-bottom: 9px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.summary-head b {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.summary-head strong {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
|
letter-spacing: -0.03em;
|
|
|
|
|
|
}
|
|
|
|
|
|
.summary p {
|
|
|
|
|
|
margin: 9px 0 0;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.summary-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
|
|
|
|
margin-top: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.table-sub {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.route-arrow {
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
details summary {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
padding: 6px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- theme swatch ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* A palette rendered as its own three colours. The custom properties are the only inline
|
|
|
|
|
|
style in the console, and they are values rather than layout: what a colour scheme looks
|
|
|
|
|
|
like cannot be described in a class name. */
|
|
|
|
|
|
.swatch {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 34px;
|
|
|
|
|
|
height: 22px;
|
|
|
|
|
|
flex: 0 0 34px;
|
|
|
|
|
|
margin-top: 1px;
|
|
|
|
|
|
border-radius: var(--radius-xs);
|
|
|
|
|
|
background: var(--swatch-surface, var(--surface-hi));
|
|
|
|
|
|
border: 1px solid var(--swatch-hairline, var(--line));
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
.swatch i {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: auto 0 0 0;
|
|
|
|
|
|
height: 7px;
|
|
|
|
|
|
background: var(--swatch-accent, var(--accent));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A caption inside a card, above a group of controls that belong together. Quieter than a
|
|
|
|
|
|
heading, because the card already has one. */
|
|
|
|
|
|
.group-label {
|
|
|
|
|
|
margin: 4px 0 2px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
.group + .group {
|
|
|
|
|
|
margin-top: 14px;
|
|
|
|
|
|
padding-top: 14px;
|
|
|
|
|
|
border-top: 1px solid var(--line-soft);
|
|
|
|
|
|
}
|
2026-08-15 09:23:26 +12:00
|
|
|
|
|
|
|
|
|
|
/* ---------- hero scheduler ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.hero-pins {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-pin {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 30px minmax(0, 1fr) auto auto;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
min-height: 52px;
|
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
|
border: 1px solid var(--line-soft);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-pin-order {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
width: 26px;
|
|
|
|
|
|
height: 26px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--note-wash);
|
|
|
|
|
|
color: var(--note-ink);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 750;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-pin > span:nth-child(2) {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-pin b,
|
|
|
|
|
|
.hero-pin small {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-pin b { font-size: 13.5px; }
|
|
|
|
|
|
.hero-pin small { margin-top: 2px; color: var(--quiet); font-size: 11.5px; }
|
|
|
|
|
|
|
|
|
|
|
|
.hero-schedule-list {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 100px minmax(0, 1fr) auto;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 11px;
|
|
|
|
|
|
background: linear-gradient(135deg, var(--surface-lift), rgba(13, 17, 23, .74));
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule::before {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0 auto 0 0;
|
|
|
|
|
|
width: 3px;
|
|
|
|
|
|
background: var(--quiet);
|
|
|
|
|
|
opacity: .4;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule[data-enabled="true"]::before {
|
|
|
|
|
|
background: var(--info);
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-time {
|
|
|
|
|
|
align-self: stretch;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding-right: 14px;
|
|
|
|
|
|
border-right: 1px solid var(--line-soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-time b {
|
|
|
|
|
|
color: var(--info-ink);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
letter-spacing: .08em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-time span {
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font: 12px/1.4 var(--mono);
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-main { min-width: 0; }
|
|
|
|
|
|
.hero-schedule-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-title h3 {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-main > p {
|
|
|
|
|
|
margin: 4px 0 8px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero-schedule-dialog {
|
|
|
|
|
|
width: min(760px, 100%);
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-dialog-head {
|
|
|
|
|
|
padding: 24px 26px 20px;
|
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
|
background:
|
|
|
|
|
|
radial-gradient(circle at 90% -40%, var(--info-wash), transparent 240px),
|
|
|
|
|
|
var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-kicker {
|
|
|
|
|
|
color: var(--info-ink);
|
|
|
|
|
|
font-size: 10.5px;
|
|
|
|
|
|
font-weight: 750;
|
|
|
|
|
|
letter-spacing: .1em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-dialog-head h2 {
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
font-size: 21px;
|
|
|
|
|
|
letter-spacing: -.025em;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-dialog-head p { margin-bottom: 0; }
|
|
|
|
|
|
.hero-schedule-dialog > .fields,
|
|
|
|
|
|
.hero-schedule-dialog > .schedule-days,
|
|
|
|
|
|
.hero-schedule-dialog > .schedule-options,
|
|
|
|
|
|
.hero-schedule-dialog > .check {
|
|
|
|
|
|
margin-right: 26px;
|
|
|
|
|
|
margin-left: 26px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin: 20px 26px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency button {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
min-height: 68px;
|
|
|
|
|
|
padding: 11px 13px;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
white-space: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency button b,
|
|
|
|
|
|
.schedule-frequency button span {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency button span {
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency button[aria-pressed="true"] {
|
|
|
|
|
|
border-color: var(--info);
|
|
|
|
|
|
background: var(--info-wash);
|
|
|
|
|
|
color: var(--info-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency button[aria-pressed="true"] span { color: var(--muted); }
|
|
|
|
|
|
.schedule-days {
|
|
|
|
|
|
margin-top: 18px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border: 1px solid var(--line-soft);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-days-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-days-head > b { font-size: 12.5px; }
|
|
|
|
|
|
.schedule-days-head > div {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-days-head button {
|
|
|
|
|
|
height: 28px;
|
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
|
border-color: transparent;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-day-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-day-grid button {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-day-grid button[aria-pressed="true"] {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
background: var(--accent-wash);
|
|
|
|
|
|
color: var(--accent-ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-options {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 2fr) minmax(140px, 1fr);
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
gap: 22px;
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
padding-top: 18px;
|
|
|
|
|
|
border-top: 1px solid var(--line-soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-option-label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-bottom: 3px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-placement-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
|
gap: 6px 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-dialog > .check { margin-top: 8px; }
|
|
|
|
|
|
.hero-schedule-dialog > .dialog-actions {
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
padding: 18px 26px;
|
|
|
|
|
|
border-top: 1px solid var(--line);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 820px) {
|
|
|
|
|
|
.hero-pin {
|
|
|
|
|
|
grid-template-columns: 30px minmax(0, 1fr) auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-pin > button:last-child { grid-column: 3; }
|
|
|
|
|
|
.hero-schedule {
|
|
|
|
|
|
grid-template-columns: 78px minmax(0, 1fr);
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-actions {
|
|
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
|
|
padding-top: 10px;
|
|
|
|
|
|
border-top: 1px solid var(--line-soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
.hero-schedule-actions button { flex: 1 1 0; }
|
|
|
|
|
|
.hero-schedule-dialog-head { padding: 22px 20px 18px; }
|
|
|
|
|
|
.hero-schedule-dialog > .fields,
|
|
|
|
|
|
.hero-schedule-dialog > .schedule-days,
|
|
|
|
|
|
.hero-schedule-dialog > .schedule-options,
|
|
|
|
|
|
.hero-schedule-dialog > .check {
|
|
|
|
|
|
margin-right: 20px;
|
|
|
|
|
|
margin-left: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency {
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin: 16px 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency button {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
min-height: 62px;
|
|
|
|
|
|
padding: 9px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.schedule-frequency button span { display: none; }
|
|
|
|
|
|
.schedule-days-head { align-items: flex-start; }
|
|
|
|
|
|
.schedule-days-head > div { flex-wrap: wrap; justify-content: flex-end; }
|
|
|
|
|
|
.schedule-day-grid { gap: 4px; }
|
|
|
|
|
|
.schedule-day-grid button { min-width: 0; padding: 0 3px; font-size: 11px; }
|
|
|
|
|
|
.schedule-options { grid-template-columns: 1fr; }
|
|
|
|
|
|
.schedule-placement-grid { grid-template-columns: 1fr; }
|
|
|
|
|
|
.hero-schedule-dialog > .dialog-actions { padding: 16px 20px max(16px, var(--safe-bottom)); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- tablet and touch refinement ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
/* iPad is an operating surface, not a large phone. Cards gain enough separation to be
|
|
|
|
|
|
parsed at arm's length, but data remains dense and two-column where portrait width can
|
|
|
|
|
|
genuinely support it. */
|
2026-08-16 12:13:51 +12:00
|
|
|
|
@media (pointer: coarse) and (min-width: 821px) and (max-width: 1400px) {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
.page-head {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head h1 {
|
|
|
|
|
|
font-size: 26px;
|
|
|
|
|
|
line-height: 1.2;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head p {
|
|
|
|
|
|
max-width: 72ch;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card {
|
|
|
|
|
|
padding: 20px 22px;
|
|
|
|
|
|
border-color: rgba(99, 110, 123, .42);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
box-shadow: 0 1px 0 rgba(255, 255, 255, .025), 0 14px 40px rgba(0, 0, 0, .14);
|
|
|
|
|
|
}
|
|
|
|
|
|
.card + .card,
|
|
|
|
|
|
.grid + .card,
|
|
|
|
|
|
.card + .grid,
|
|
|
|
|
|
.grid + .grid {
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.grid {
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head {
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head h2 {
|
|
|
|
|
|
font-size: 15.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head p {
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-foot {
|
|
|
|
|
|
margin-top: 18px;
|
|
|
|
|
|
padding-top: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tiles {
|
|
|
|
|
|
gap: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile {
|
|
|
|
|
|
min-height: 112px;
|
|
|
|
|
|
padding: 16px 17px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: linear-gradient(145deg, var(--surface-lift), var(--surface));
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile b {
|
|
|
|
|
|
font-size: 27px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.filters {
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.filters .field {
|
|
|
|
|
|
flex: 1 1 calc(50% - 6px);
|
|
|
|
|
|
max-width: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.filters .field.grow {
|
|
|
|
|
|
flex-basis: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-row {
|
|
|
|
|
|
min-height: 64px;
|
|
|
|
|
|
padding: 14px 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item {
|
|
|
|
|
|
min-height: 60px;
|
|
|
|
|
|
padding: 13px 0;
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
/* The column collapse itself is width-driven and lives beside the table's own rules;
|
|
|
|
|
|
what a touch device needs on top of it is room to scroll and controls it can reach. */
|
2026-08-15 09:23:26 +12:00
|
|
|
|
.logview {
|
|
|
|
|
|
height: min(66dvh, 720px);
|
|
|
|
|
|
}
|
2026-08-18 08:41:48 +12:00
|
|
|
|
.logbar-actions {
|
|
|
|
|
|
margin-left: 0;
|
|
|
|
|
|
}
|
2026-08-15 09:23:26 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 820px) {
|
|
|
|
|
|
.page-head-row {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr);
|
|
|
|
|
|
gap: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head-row .page-head-actions {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
margin-left: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card {
|
|
|
|
|
|
padding: 17px 18px;
|
|
|
|
|
|
border-radius: 11px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head {
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head-actions {
|
|
|
|
|
|
width: 100%;
|
2026-08-16 12:13:51 +12:00
|
|
|
|
margin-left: 0;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
}
|
|
|
|
|
|
.card-foot > button,
|
|
|
|
|
|
.card-foot > .btn {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
min-height: 104px;
|
|
|
|
|
|
padding: 14px;
|
|
|
|
|
|
background: linear-gradient(145deg, var(--surface-lift), var(--surface));
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile b {
|
|
|
|
|
|
font-size: 23px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tile span {
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
}
|
|
|
|
|
|
.filters .field,
|
|
|
|
|
|
.filters .field.grow {
|
|
|
|
|
|
flex: 1 1 100%;
|
|
|
|
|
|
max-width: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.filters .filter-actions {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
margin-left: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item,
|
|
|
|
|
|
.list-row,
|
|
|
|
|
|
.list-main {
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item,
|
|
|
|
|
|
.list-row {
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.list-item .list-actions,
|
|
|
|
|
|
.list-row .list-actions {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
padding-left: 46px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.kv-row {
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dialog {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-height: min(88dvh, 720px);
|
|
|
|
|
|
padding: 22px;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.scrim {
|
|
|
|
|
|
align-items: end;
|
|
|
|
|
|
padding: 12px max(12px, var(--safe-right)) max(12px, var(--safe-bottom)) max(12px, var(--safe-left));
|
|
|
|
|
|
}
|
|
|
|
|
|
.dialog-actions > button {
|
|
|
|
|
|
flex: 1 1 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-panel {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: calc(var(--top) + 8px);
|
|
|
|
|
|
right: max(12px, var(--safe-right));
|
|
|
|
|
|
left: max(12px, var(--safe-left));
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
max-height: calc(100dvh - var(--top) - var(--safe-bottom) - 20px);
|
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.bell-list {
|
|
|
|
|
|
max-height: calc(100dvh - var(--top) - var(--safe-bottom) - 120px);
|
|
|
|
|
|
}
|
|
|
|
|
|
.toasts {
|
|
|
|
|
|
right: max(12px, var(--safe-right));
|
|
|
|
|
|
bottom: max(12px, var(--safe-bottom));
|
|
|
|
|
|
left: max(12px, var(--safe-left));
|
|
|
|
|
|
max-width: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Coarse pointers need a target, not merely larger text. This is deliberately scoped to
|
|
|
|
|
|
tablet widths so a desktop browser with touch support keeps the compact operations UI. */
|
|
|
|
|
|
@media (pointer: coarse) and (max-width: 1400px) {
|
|
|
|
|
|
button,
|
|
|
|
|
|
.btn {
|
|
|
|
|
|
min-width: 44px;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
padding-right: 15px;
|
|
|
|
|
|
padding-left: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
button[data-size="sm"] {
|
|
|
|
|
|
min-width: 38px;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
padding-right: 11px;
|
|
|
|
|
|
padding-left: 11px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar .rail-toggle,
|
|
|
|
|
|
.topbar .bell-button,
|
|
|
|
|
|
.topbar .topbar-status,
|
2026-08-16 12:13:51 +12:00
|
|
|
|
.topbar .account-trigger {
|
2026-08-15 09:23:26 +12:00
|
|
|
|
width: 44px;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
2026-08-16 21:20:55 +12:00
|
|
|
|
.topbar .bell,
|
|
|
|
|
|
.topbar .account-menu {
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.topbar .omni-input {
|
|
|
|
|
|
height: 44px;
|
2026-08-15 09:23:26 +12:00
|
|
|
|
}
|
|
|
|
|
|
.rail a {
|
|
|
|
|
|
min-height: 46px;
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
border-radius: 9px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.rail-head {
|
|
|
|
|
|
min-height: 42px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
input[type="text"],
|
|
|
|
|
|
input[type="password"],
|
|
|
|
|
|
input[type="search"],
|
|
|
|
|
|
input[type="number"],
|
|
|
|
|
|
input[type="date"],
|
|
|
|
|
|
input[type="time"],
|
|
|
|
|
|
input[type="datetime-local"],
|
|
|
|
|
|
input[type="url"],
|
|
|
|
|
|
select {
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
select {
|
|
|
|
|
|
background-position: calc(100% - 16px) 20px, calc(100% - 11px) 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
textarea {
|
|
|
|
|
|
min-height: 104px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check {
|
|
|
|
|
|
min-height: 52px;
|
|
|
|
|
|
padding-top: 14px;
|
|
|
|
|
|
padding-bottom: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check .switch {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
flex-basis: 40px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check .switch::after {
|
|
|
|
|
|
width: 18px;
|
|
|
|
|
|
height: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.check input:checked + .switch::after {
|
|
|
|
|
|
transform: translateX(16px);
|
|
|
|
|
|
}
|
|
|
|
|
|
.segments {
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.segments button {
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.crumb,
|
|
|
|
|
|
.crumbs a,
|
|
|
|
|
|
.table-row-link {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
min-height: 36px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-16 12:13:51 +12:00
|
|
|
|
/* Short laptop screens benefit from vertical density, while retaining the same readable
|
|
|
|
|
|
widths and full-size pointer targets. */
|
|
|
|
|
|
@media (pointer: fine) and (min-width: 821px) and (max-height: 800px) {
|
|
|
|
|
|
.page {
|
|
|
|
|
|
padding-top: 20px;
|
|
|
|
|
|
padding-bottom: 52px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.page-head {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card {
|
|
|
|
|
|
padding-top: 14px;
|
|
|
|
|
|
padding-bottom: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-head {
|
|
|
|
|
|
margin-bottom: 11px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-15 09:23:26 +12:00
|
|
|
|
@media (hover: none) {
|
|
|
|
|
|
tbody tr:hover td,
|
|
|
|
|
|
.list-row:hover,
|
|
|
|
|
|
.bell-item:hover,
|
|
|
|
|
|
a.tile:hover {
|
|
|
|
|
|
background: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-19 06:57:59 +12:00
|
|
|
|
|
|
|
|
|
|
/* The notification history's detail row.
|
|
|
|
|
|
*
|
|
|
|
|
|
* The drawer is a row inside the table rather than a panel beside it, so the record stays
|
|
|
|
|
|
* under the row it belongs to while an operator reads down a filtered list. It borrows the
|
|
|
|
|
|
* log viewer's drawer vocabulary wholesale — the two answer the same shape of question and
|
|
|
|
|
|
* a second look for it would be a second thing to keep in step. */
|
|
|
|
|
|
.detail-row > td {
|
|
|
|
|
|
padding: 0 0 12px;
|
|
|
|
|
|
background: var(--surface-sunken, transparent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.logdrawer-raw h4 {
|
|
|
|
|
|
margin: 0 0 6px;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
font: 700 10px/1 var(--sans);
|
|
|
|
|
|
letter-spacing: .08em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* The message is the one thing on the page rendered as prose rather than as a field: it is
|
|
|
|
|
|
the sentence a viewer actually read, and setting it in the mono field type would make it
|
|
|
|
|
|
look like a value rather than like the notification it is. */
|
|
|
|
|
|
.notification-body {
|
|
|
|
|
|
max-width: 70ch;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
font: 13px/1.6 var(--sans);
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
}
|
2026-08-19 14:25:44 +12:00
|
|
|
|
|
|
|
|
|
|
/* ---------- runtime ----------
|
|
|
|
|
|
|
|
|
|
|
|
The verdict block and the section headings the runtime pages are built from. The verdict
|
|
|
|
|
|
is deliberately not a `.banner`: a banner is something that has gone wrong and can be
|
|
|
|
|
|
dismissed, and this is a standing answer that is usually good news. */
|
|
|
|
|
|
|
|
|
|
|
|
.subhead {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin: 6px 0 0;
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
letter-spacing: 0.07em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.subhead small {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
letter-spacing: 0;
|
|
|
|
|
|
text-transform: none;
|
|
|
|
|
|
color: var(--quiet);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.verdict {
|
|
|
|
|
|
margin: 0 0 16px;
|
|
|
|
|
|
padding: 12px 14px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-left: 3px solid var(--accent);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict[data-tone="warn"] {
|
|
|
|
|
|
border-left-color: var(--warn);
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict[data-tone="bad"] {
|
|
|
|
|
|
border-left-color: var(--danger);
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 9px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict-head svg {
|
|
|
|
|
|
width: 17px;
|
|
|
|
|
|
height: 17px;
|
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict[data-tone="warn"] .verdict-head svg {
|
|
|
|
|
|
color: var(--warn);
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict[data-tone="bad"] .verdict-head svg {
|
|
|
|
|
|
color: var(--danger);
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict-head b {
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict-notes {
|
|
|
|
|
|
margin: 11px 0 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.verdict-notes li {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 9px;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
2026-08-22 08:26:10 +12:00
|
|
|
|
|
|
|
|
|
|
/* ---------- metadata hero ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.metadata-hero-layout {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(320px, .82fr) minmax(460px, 1.35fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-hero-order {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-hero-order-row {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 30px minmax(0, 1fr) auto;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
border: 1px solid var(--line-soft);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--surface-lift);
|
|
|
|
|
|
opacity: .62;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-hero-order-row[data-enabled] { opacity: 1; }
|
|
|
|
|
|
.metadata-hero-order-number {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
width: 26px;
|
|
|
|
|
|
height: 26px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-hero-order-actions { display: flex; gap: 5px; }
|
|
|
|
|
|
.metadata-hero-order-actions button { min-width: 34px; padding-inline: 8px; }
|
2026-08-22 11:32:50 +12:00
|
|
|
|
.metadata-hero-colour {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 7px;
|
|
|
|
|
|
margin-top: 18px;
|
|
|
|
|
|
padding-top: 16px;
|
|
|
|
|
|
border-top: 1px solid var(--line-soft);
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-hero-colour > b { font-size: 13.5px; }
|
|
|
|
|
|
.metadata-hero-colour > p { margin: 0; color: var(--muted); font-size: 12.5px; }
|
|
|
|
|
|
.metadata-hero-colour .segments { width: fit-content; }
|
2026-08-22 08:26:10 +12:00
|
|
|
|
.metadata-hero-preview {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
isolation: isolate;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
aspect-ratio: 16 / 7.2;
|
|
|
|
|
|
min-height: 280px;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
|
background: #080b0e;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-hero-preview::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
z-index: -1;
|
|
|
|
|
|
background: linear-gradient(90deg, #080b0e 0 34%, rgba(8,11,14,.9) 48%, rgba(8,11,14,.2) 76%, transparent);
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-hero-preview-art {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0 0 0 36%;
|
|
|
|
|
|
z-index: -2;
|
|
|
|
|
|
background:
|
|
|
|
|
|
radial-gradient(circle at 67% 24%, rgba(211,242,255,.8) 0 2%, transparent 3%),
|
|
|
|
|
|
radial-gradient(ellipse at 76% 78%, rgba(71,107,92,.8), transparent 45%),
|
|
|
|
|
|
linear-gradient(145deg, #17303d 0%, #456273 42%, #101a1f 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-hero-preview-copy {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
width: 54%;
|
|
|
|
|
|
min-height: 100%;
|
|
|
|
|
|
padding: 26px 0 24px 28px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-title {
|
|
|
|
|
|
max-width: 330px;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-family: Georgia, serif;
|
|
|
|
|
|
font-size: clamp(24px, 3vw, 42px);
|
|
|
|
|
|
line-height: .95;
|
|
|
|
|
|
letter-spacing: .03em;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-ratings { display: flex; gap: 8px; }
|
|
|
|
|
|
.metadata-preview-ratings b {
|
|
|
|
|
|
padding: 5px 8px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
background: rgba(255,255,255,.11);
|
|
|
|
|
|
color: #e8edf0;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-facts {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 7px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
color: #bdc7ce;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
line-height: 16px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-facts span {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-facts b {
|
|
|
|
|
|
flex: none;
|
|
|
|
|
|
padding: 1px 5px;
|
|
|
|
|
|
border: 1px solid rgba(255,255,255,.28);
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
background: rgba(0,0,0,.22);
|
|
|
|
|
|
color: #e8edf0;
|
|
|
|
|
|
font-size: inherit;
|
|
|
|
|
|
line-height: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-reason {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
line-height: 16px;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-progress {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
color: #bdc7ce;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
line-height: 16px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-progress > span {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
width: 112px;
|
|
|
|
|
|
height: 3px;
|
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
|
background: rgba(255,255,255,.22);
|
|
|
|
|
|
}
|
|
|
|
|
|
.metadata-preview-progress i {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
width: 59%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
|
}
|
2026-08-22 11:32:50 +12:00
|
|
|
|
.metadata-preview-progress b { color: var(--accent); font-size: inherit; line-height: inherit; }
|
|
|
|
|
|
.metadata-preview-progress[data-colour="white"] b { color: #fff; }
|
2026-08-22 08:26:10 +12:00
|
|
|
|
.metadata-preview-summary { margin: 0; color: #cbd2d7; font-size: 12px; line-height: 1.45; }
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 980px) {
|
|
|
|
|
|
.metadata-hero-layout { grid-template-columns: 1fr; }
|
|
|
|
|
|
}
|