The Alerts page's active list now says each alert in one line around the resource it watches, e.g. "cpu_pct on web-1 is 94% (> 90%), firing for 12m", with web-1 linking to its host, monitor or container page (or the plugin's page for Traefik, UniFi and SNMP). Values take their unit from the metric name through formatting.metric_value. A silent series reads "No cpu_pct from web-1 for 20m"; an up/down metric says the word. Actions follow the routes' roles: acknowledge, edit, toggle and New rule for operators and admins, Delete for admins only, nothing for viewers. The sidebar shows how many alerts need attention beside Alerts, from an async context processor that skips htmx fragments, logged-out pages and the database-down page, and renders without the count if the query fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
365 lines
21 KiB
CSS
365 lines
21 KiB
CSS
/* steward/static/steward.css — Steward's base stylesheet.
|
|
*
|
|
* Values come from theme.css, the stylesheet design system 6 ("Steward",
|
|
* inheriting FabledSword) generates. The short names below are Steward's
|
|
* aliases onto those tokens, kept so templates written against them still
|
|
* render; new work writes the --fs-* token. Contrast measured 2026-10-06
|
|
* (#5092): every text colour here is at least 4.5:1 on page, card, hover
|
|
* and its own 15-25% tint.
|
|
*/
|
|
:root {
|
|
--bg: var(--fs-surface-page);
|
|
--bg-card: var(--fs-surface-raised);
|
|
--bg-elevated: var(--fs-surface-hover);
|
|
--border: color-mix(in srgb, var(--fs-border-color) 70%, var(--fs-surface-raised));
|
|
--border-mid: var(--fs-border-color);
|
|
--text: var(--fs-text-primary);
|
|
--text-muted: var(--fs-text-secondary);
|
|
--text-dim: var(--fs-text-tertiary);
|
|
--gold: var(--fs-accent);
|
|
--gold-hover: color-mix(in srgb, var(--fs-accent) 85%, var(--fs-text-primary));
|
|
--gold-dim: var(--fs-accent-soft);
|
|
--accent: var(--fs-accent);
|
|
--accent-hover: var(--gold-hover);
|
|
/* Status: the -fg colour is the text and indicator; the -dim tint sits behind it. */
|
|
--green: var(--fs-success-fg);
|
|
--green-dim: color-mix(in srgb, var(--fs-success) 20%, transparent);
|
|
--yellow: var(--fs-warning-fg);
|
|
--yellow-dim: color-mix(in srgb, var(--fs-warning) 20%, transparent);
|
|
/* Between warning and error: the worse-than-warning band (slow, degraded). */
|
|
--orange: color-mix(in srgb, var(--fs-warning-fg) 50%, var(--fs-error-fg));
|
|
--orange-dim: color-mix(in srgb, var(--fs-warning) 20%, transparent);
|
|
--red: var(--fs-error-fg);
|
|
--red-dim: color-mix(in srgb, var(--fs-error) 20%, transparent);
|
|
/* Info (acknowledged): lighter than the house --fs-info-fg (50%), which falls
|
|
to 4.18:1 on its tint over a hovered row; 40% holds 5.09:1 there. */
|
|
--blue: color-mix(in srgb, var(--fs-info) 40%, var(--fs-text-primary));
|
|
--blue-dim: color-mix(in srgb, var(--fs-info) 20%, transparent);
|
|
--font-serif: var(--fs-font-display);
|
|
}
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: var(--fs-font-body); background: var(--bg); color: var(--text); line-height: var(--fs-leading-body); }
|
|
a { color: var(--accent); text-decoration: none; }
|
|
a:hover { color: var(--accent-hover); }
|
|
code { font-family: var(--fs-font-mono); font-size: 0.875em; background: var(--bg-elevated); padding: 0.1em 0.35em; border-radius: 3px; }
|
|
|
|
#candle-glow {
|
|
position: fixed;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
background:
|
|
radial-gradient(ellipse 80% 60% at 50% 40%, color-mix(in srgb, var(--fs-accent) 4.5%, transparent) 0%, transparent 70%),
|
|
radial-gradient(ellipse 50% 40% at 20% 80%, color-mix(in srgb, var(--fs-accent) 2.5%, transparent) 0%, transparent 65%);
|
|
}
|
|
|
|
/* ── App shell: persistent left sidebar + content column ───────────────────── */
|
|
.app-shell { display: flex; min-height: 100vh; position: relative; z-index: 1; }
|
|
.sidebar {
|
|
width: 220px; flex-shrink: 0; background: var(--bg-card);
|
|
border-right: 1px solid var(--border-mid);
|
|
display: flex; flex-direction: column;
|
|
position: sticky; top: 0; height: 100vh; z-index: 20;
|
|
}
|
|
.sidebar .brand {
|
|
font-family: var(--font-serif); font-weight: 500; font-size: 1.15rem;
|
|
letter-spacing: 0.01em; color: var(--gold); text-decoration: none;
|
|
display: flex; align-items: center; gap: 0.5rem;
|
|
padding: 0.85rem 1.25rem; border-bottom: 1px solid var(--border);
|
|
}
|
|
.sidebar .brand svg { flex-shrink: 0; }
|
|
/* Only the nav list scrolls when long — the brand and user/logout stay pinned. */
|
|
.side-nav { flex: 1; min-height: 0; overflow-y: auto; padding: 0.75rem 0; }
|
|
.nav-group { margin-bottom: 0.9rem; }
|
|
.nav-group-label {
|
|
font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em;
|
|
color: var(--text-dim); font-weight: 500; padding: 0 1.25rem; margin-bottom: 0.3rem;
|
|
}
|
|
.side-nav a {
|
|
display: flex; align-items: center; gap: 0.5rem;
|
|
padding: 0.4rem 1.25rem; font-size: 0.9rem; color: var(--text-muted);
|
|
border-left: 2px solid transparent; text-decoration: none;
|
|
transition: color .12s, background .12s, border-color .12s;
|
|
}
|
|
.side-nav a:hover { color: var(--text); background: var(--bg-elevated); }
|
|
.side-nav a.active {
|
|
color: var(--gold); border-left-color: var(--gold);
|
|
background: color-mix(in srgb, var(--gold) 8%, transparent);
|
|
}
|
|
/* How many alerts need attention, beside the Alerts link. */
|
|
.nav-count {
|
|
margin-left: auto; padding: 0 0.45em; border-radius: var(--fs-radius-sm);
|
|
background: var(--red-dim); color: var(--red); font-size: 0.75rem; font-weight: 600;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.side-user { border-top: 1px solid var(--border); padding: 0.7rem 1.25rem; }
|
|
.side-user form { margin: 0; }
|
|
.side-user button {
|
|
color: var(--text-muted); font-size: 0.84rem; text-decoration: none;
|
|
display: flex; align-items: center; gap: 0.4rem;
|
|
background: none; border: 0; padding: 0; font-family: inherit; cursor: pointer;
|
|
}
|
|
.side-user button:hover { color: var(--text); }
|
|
/* Keyboard focus: a clear inset ring on every interactive sidebar element. */
|
|
.sidebar .brand:focus-visible,
|
|
.side-nav a:focus-visible,
|
|
.side-user button:focus-visible {
|
|
outline: 2px solid var(--accent); outline-offset: -2px; color: var(--text);
|
|
}
|
|
.app-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
.topbar { display: none; } /* slim mobile bar; shown only under 900px */
|
|
.nav-scrim { display: none; }
|
|
|
|
/* Layout */
|
|
main { padding: 1.5rem 2rem; max-width: 1400px; margin: 0 auto; width: 100%; min-width: 0; box-sizing: border-box; position: relative; z-index: 1; }
|
|
|
|
/* Responsive: sidebar slides off-canvas under 900px, toggled by ☰ */
|
|
@media (max-width: 900px) {
|
|
.sidebar {
|
|
position: fixed; left: 0; top: 0; bottom: 0; height: 100%;
|
|
transform: translateX(-100%); transition: transform .2s ease;
|
|
box-shadow: var(--fs-shadow-3);
|
|
}
|
|
body.nav-open .sidebar { transform: translateX(0); }
|
|
.topbar {
|
|
display: flex; align-items: center; gap: 0.75rem; height: 48px; padding: 0 1rem;
|
|
background: var(--bg-card); border-bottom: 1px solid var(--border-mid);
|
|
position: sticky; top: 0; z-index: 15;
|
|
}
|
|
.sidebar-toggle {
|
|
background: none; border: none; color: var(--text-muted); font-size: 1.3rem;
|
|
cursor: pointer; line-height: 1; padding: 0.25rem; border-radius: 4px;
|
|
}
|
|
.sidebar-toggle:hover { color: var(--text); }
|
|
.sidebar-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
.topbar .brand-sm {
|
|
font-family: var(--font-serif); color: var(--gold); font-weight: 500;
|
|
font-size: 1.15rem; text-decoration: none;
|
|
}
|
|
body.nav-open .nav-scrim {
|
|
display: block; position: fixed; inset: 0; background: var(--fs-overlay); z-index: 18;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.sidebar { transition: none; }
|
|
.side-nav a { transition: none; }
|
|
}
|
|
|
|
/* Alerts */
|
|
.alert { padding: 0.75rem 1rem; border-radius: 6px; margin-bottom: 1rem; font-size: 0.9rem; }
|
|
.alert-error { background: var(--red-dim); border: 1px solid color-mix(in srgb, var(--fs-error) 45%, transparent); color: var(--red); }
|
|
.alert-success { background: var(--green-dim); border: 1px solid color-mix(in srgb, var(--fs-success) 60%, transparent); color: var(--green); }
|
|
|
|
/* Cards */
|
|
.card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; margin-bottom: 1rem; }
|
|
.card-flush { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow-x: auto; overflow-y: hidden; margin-bottom: 1rem; }
|
|
/* A table wider than the screen scrolls sideways inside its card instead of
|
|
widening the page at phone width. Any card holding a table does this, so no
|
|
page has to remember a wrapper; .table-wrap is for a table outside a card.
|
|
(Only cards with a table: a plain card can hold a popover that must not clip.) */
|
|
.card:has(table.table) { overflow-x: auto; }
|
|
.table-wrap { overflow-x: auto; }
|
|
|
|
/* Typography */
|
|
.page-title { font-family: var(--font-serif); font-size: 1.6rem; font-weight: 500; color: var(--text); line-height: var(--fs-leading-heading); margin-bottom: 1.5rem; }
|
|
/* The tiny micro-label: body face (display face only at 18px and up), caps, tracked. */
|
|
.section-title { font-family: var(--fs-font-body); font-size: var(--fs-size-tiny); font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: var(--fs-tracking-tiny); }
|
|
h1, h2 { font-family: var(--font-serif); font-weight: 500; }
|
|
h3, h4 { font-family: var(--fs-font-body); font-weight: 500; }
|
|
strong, b, th { font-weight: 500; }
|
|
|
|
/* Tables */
|
|
.table { width: 100%; border-collapse: collapse; }
|
|
.table th { text-align: left; padding: 0.6rem 1rem; color: var(--text-muted); font-weight: 500; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; border-bottom: 1px solid var(--border-mid); }
|
|
.table td { padding: 0.7rem 1rem; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
|
|
.table tbody tr:last-child td { border-bottom: none; }
|
|
.table tbody tr:hover td { background: var(--bg-elevated); }
|
|
.table .td-actions { text-align: right; white-space: nowrap; }
|
|
|
|
/* Forms */
|
|
.form-group { margin-bottom: 1rem; }
|
|
label { display: block; margin-bottom: 0.3rem; font-size: 0.85rem; color: var(--text-muted); font-family: var(--fs-font-body); }
|
|
input[type=text], input[type=email], input[type=password], input[type=number], input[type=url], select, textarea {
|
|
width: 100%; padding: 0.5rem 0.75rem; background: var(--bg); border: 1px solid var(--border-mid);
|
|
border-radius: 5px; color: var(--text); font-size: 0.9rem; font-family: inherit;
|
|
transition: border-color .15s;
|
|
}
|
|
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }
|
|
/* Keyboard focus is always visible: the design system's ring on anything
|
|
that takes focus. */
|
|
a:focus-visible, button:focus-visible, summary:focus-visible, [tabindex]:focus-visible,
|
|
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: none; box-shadow: var(--fs-focus-ring); }
|
|
select { cursor: pointer; }
|
|
textarea { resize: vertical; }
|
|
/* Form fields: .field wraps a .field-label, the .input and an optional .field-hint. */
|
|
.field { display: flex; flex-direction: column; gap: 0.2rem; margin-bottom: 0.9rem; }
|
|
.field-label { display: block; margin-bottom: 0; font-size: 0.75rem; color: var(--text-muted); }
|
|
.field-hint { font-size: 0.75rem; color: var(--text-dim); }
|
|
.field-error { font-size: 0.85rem; color: var(--red); margin-bottom: 0.75rem; }
|
|
.input { width: 100%; padding: 0.4rem 0.65rem; background: var(--bg); border: 1px solid var(--border-mid); border-radius: var(--fs-radius-sm); color: var(--text); font-size: 0.88rem; font-family: inherit; }
|
|
.input:focus { outline: none; border-color: var(--accent); }
|
|
.input:disabled { opacity: var(--fs-disabled-opacity); }
|
|
|
|
/* Buttons */
|
|
.btn { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.45rem 1rem; background: var(--accent); color: var(--fs-surface-page); border: none; border-radius: 5px; cursor: pointer; font-size: 0.875rem; font-family: inherit; font-weight: 500; transition: background .15s; text-decoration: none; }
|
|
.btn:hover { background: var(--accent-hover); color: var(--fs-surface-page); }
|
|
.btn-sm { padding: 0.3rem 0.65rem; font-size: 0.8rem; }
|
|
.btn-icon { padding: 0.3rem 0.5rem; line-height: 1; }
|
|
.btn-ghost { background: transparent; color: var(--text-muted); border: 1px solid var(--border-mid); }
|
|
.btn-ghost:hover { background: var(--bg-elevated); color: var(--text); }
|
|
.btn-danger { background: var(--fs-action-destructive); color: var(--fs-text-on-action); }
|
|
.btn-danger:hover { background: var(--fs-action-destructive-hover); color: var(--fs-text-on-action); }
|
|
.btn-warn { background: var(--yellow-dim); color: var(--yellow); border: 1px solid color-mix(in srgb, var(--fs-warning) 60%, transparent); }
|
|
.btn-warn:hover { background: color-mix(in srgb, var(--fs-warning) 32%, transparent); }
|
|
|
|
/* Badges / Status */
|
|
.badge { display: inline-block; padding: 0.2em 0.55em; border-radius: var(--fs-radius-sm); font-size: 0.75rem; font-weight: 500; letter-spacing: 0.02em; }
|
|
.badge-green { background: var(--green-dim); color: var(--green); }
|
|
.badge-red { background: var(--red-dim); color: var(--red); }
|
|
.badge-yellow { background: var(--yellow-dim); color: var(--yellow); }
|
|
.badge-orange { background: var(--orange-dim); color: var(--orange); }
|
|
.badge-gold { background: var(--gold-dim); color: var(--fs-accent-fg); }
|
|
.badge-dim { background: var(--bg-elevated); color: var(--text-muted); }
|
|
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
.dot-up { background: var(--green); box-shadow: 0 0 4px var(--green); }
|
|
.dot-down { background: var(--red); }
|
|
.dot-warn { background: var(--yellow); }
|
|
.dot-dim { background: var(--text-dim); }
|
|
/* Tones the status_badge / status_dot macros draw (formatting.STATES). */
|
|
.badge-ok { background: var(--green-dim); color: var(--green); }
|
|
.badge-warn { background: var(--yellow-dim); color: var(--yellow); }
|
|
.badge-crit { background: var(--red-dim); color: var(--red); }
|
|
.badge-info { background: var(--blue-dim); color: var(--blue); }
|
|
.dot-ok { background: var(--green); }
|
|
.dot-crit { background: var(--red); }
|
|
.dot-info { background: var(--blue); }
|
|
.dot-label { margin-left: 0.35rem; font-size: 0.85rem; }
|
|
/* Text in a tone: status_class() results ('ok' / 'warn' / 'crit' / 'unknown')
|
|
and the STATES tones. */
|
|
.tone-ok { color: var(--green); }
|
|
.tone-warn { color: var(--yellow); }
|
|
.tone-crit { color: var(--red); }
|
|
.tone-info { color: var(--blue); }
|
|
.tone-dim, .tone-unknown { color: var(--text-dim); }
|
|
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
|
|
/* Empty state */
|
|
.empty { color: var(--text-muted); font-size: 0.9rem; padding: 2rem; text-align: center; }
|
|
.empty-state { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; color: var(--text-muted); font-size: 0.9rem; padding: 2rem 1rem; text-align: center; }
|
|
|
|
/* Page header: title on the left, its actions on the right (page_header macro). */
|
|
.page-header { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; gap: 0.75rem 1rem; margin-bottom: 1.5rem; }
|
|
.page-header .page-title { margin-bottom: 0; }
|
|
.page-subtitle { margin-top: 0.3rem; color: var(--text-muted); font-size: 0.9rem; }
|
|
.page-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
|
|
|
|
/* Stat tile (stat_tile macro): a label over one headline number. */
|
|
.stat-tile { display: flex; flex-direction: column; gap: 0.25rem; padding: 0.9rem 1rem; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--fs-radius-md); color: var(--text); min-width: 8rem; }
|
|
a.stat-tile:hover { border-color: var(--border-mid); background: var(--bg-elevated); color: var(--text); }
|
|
.stat-tile-label { font-size: var(--fs-size-tiny); font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: var(--fs-tracking-tiny); }
|
|
.stat-tile-value { font-size: 1.6rem; font-weight: 500; line-height: 1.1; font-variant-numeric: tabular-nums; }
|
|
.stat-tile-hint { font-size: 0.78rem; color: var(--text-dim); }
|
|
.stat-ok .stat-tile-value { color: var(--green); }
|
|
.stat-warn .stat-tile-value { color: var(--yellow); }
|
|
.stat-crit .stat-tile-value { color: var(--red); }
|
|
.stat-info .stat-tile-value { color: var(--blue); }
|
|
.stat-dim .stat-tile-value { color: var(--text-dim); }
|
|
.stat-strip { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: 0.75rem; margin-bottom: 1rem; }
|
|
|
|
/* "Needs attention": what is wrong, first on a page. */
|
|
.problem-card { border-color: color-mix(in srgb, var(--fs-error) 45%, var(--border)); }
|
|
.problem-list { list-style: none; display: flex; flex-direction: column; gap: 0.45rem; }
|
|
.problem-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.6rem; overflow-wrap: anywhere; }
|
|
/* A label/value list: <dl class="kv"><dt>…</dt><dd>…</dd></dl>. */
|
|
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 0.35rem 1.25rem; font-size: 0.88rem; }
|
|
.kv dt { color: var(--text-muted); }
|
|
/* The host page's health header (_host_vitals.html). */
|
|
.host-health-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.75rem; margin-bottom: 0.75rem; }
|
|
.host-health-summary { font-size: 0.92rem; }
|
|
/* A filled bar for a percentage; the fill takes its grade from status_class. */
|
|
.meter { height: 8px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
|
|
.meter-fill { height: 100%; }
|
|
.meter-ok { background: var(--green); }
|
|
.meter-warn { background: var(--yellow); }
|
|
.meter-crit { background: var(--red); }
|
|
.meter-unknown { background: var(--text-dim); }
|
|
.problem-detail { color: var(--text-muted); font-size: 0.82rem; }
|
|
|
|
/* Breadcrumbs */
|
|
/* Breadcrumb renders as a kicker line tucked directly above the page header
|
|
(.page-title), so nested and top-level views share one consistent header. */
|
|
.breadcrumb { display:flex; align-items:center; flex-wrap:wrap; gap:0.35rem; font-size:0.78rem; color:var(--text-dim); margin:0 0 0.45rem; }
|
|
.breadcrumb a { color:var(--text-muted); }
|
|
.breadcrumb a:hover { color:var(--text); }
|
|
.breadcrumb-sep { color:var(--border-mid); }
|
|
.breadcrumb-cur { color:var(--text-muted); }
|
|
|
|
/* Stat cards (dashboard) */
|
|
.stat-val { font-size: 2rem; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
.stat-label { font-size: 0.8rem; color: var(--text-muted); margin-left: 0.3rem; }
|
|
|
|
/* Ping pills */
|
|
.ping-card { padding: 0.75rem 1.25rem; }
|
|
.ping-row { display:flex; align-items:center; gap:1rem; padding:0.45rem 0; border-bottom:1px solid var(--border); }
|
|
.ping-row:last-child { border-bottom:none; }
|
|
.ping-row-paused { opacity:0.7; }
|
|
.row-actions { display:flex; gap:0.35rem; flex-shrink:0; }
|
|
.ping-meta { min-width:120px; max-width:180px; flex-shrink:1; overflow:hidden; }
|
|
.ping-name { font-size:0.875rem; font-weight:500; display:block; overflow-wrap:anywhere; }
|
|
a.ping-name:hover { text-decoration:underline; }
|
|
/* Dashboard widget grid — Gridstack-rendered. Static (positioned but inert) in
|
|
view mode; the dashboard itself becomes the edit surface when Edit is toggled,
|
|
so drag-resize happens over live widget data. The bottom drawer is a fixed
|
|
overlay, so the dashboard width is identical entering/leaving edit mode. */
|
|
.grid-stack-item-content {
|
|
background:var(--bg-card); border:1px solid var(--border-mid); border-radius:8px;
|
|
padding:0.8rem 1rem; display:flex; flex-direction:column; overflow:hidden;
|
|
}
|
|
.grid-stack-item-content .dash-cell-body { flex:1; min-height:0; overflow:auto; }
|
|
/* The widget body is a query container, so fragments can restyle to their OWN
|
|
panel width (not the viewport) — widgets adapt as you resize them. */
|
|
.dash-cell-body { container-type:inline-size; }
|
|
/* Host-list widgets flow into columns as the panel widens: use the width, drop
|
|
the vertical deadspace; stay single-column (and untruncated) when narrow. */
|
|
.host-blocks { display:grid; grid-template-columns:1fr; gap:0 1.5rem; }
|
|
@container (min-width:520px) { .host-blocks { grid-template-columns:1fr 1fr; } }
|
|
@container (min-width:900px) { .host-blocks { grid-template-columns:1fr 1fr 1fr; } }
|
|
.panel-chrome { display:none; }
|
|
.panel-drag { cursor:grab; color:var(--text-dim); user-select:none; }
|
|
.panel-drag:active { cursor:grabbing; }
|
|
body.dash-editing .panel-chrome { display:inline-flex; align-items:center; }
|
|
body.dash-editing .grid-stack-item-content { outline:1px dashed var(--border-mid); outline-offset:-1px; }
|
|
body.dash-editing main { padding-bottom:46vh; }
|
|
.widget-drawer {
|
|
position:fixed; left:0; right:0; bottom:0; transform:translateY(100%);
|
|
transition:transform .25s ease; max-height:44vh; overflow-y:auto;
|
|
background:var(--bg-card); border-top:1px solid var(--border-mid);
|
|
box-shadow:var(--fs-shadow-3); z-index:60; padding:1rem 1.5rem 1.5rem;
|
|
}
|
|
body.dash-editing .widget-drawer { transform:translateY(0); }
|
|
.ping-addr { display:block; color:var(--text-muted); font-size:0.75rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
.ping-pills { display:flex; gap:2px; flex:1; min-width:0; overflow:hidden; align-items:center; }
|
|
.pill { display:inline-block; width:7px; height:22px; border-radius:2px; cursor:default; transition:opacity .1s; }
|
|
.pill:hover { opacity:.75; }
|
|
.pill-empty { background:var(--bg-elevated); }
|
|
.pill-up { background:var(--green); }
|
|
.pill-down { background:var(--red); }
|
|
.ping-cur { min-width:72px; text-align:right; font-size:0.85rem; font-variant-numeric:tabular-nums; }
|
|
.ping-cur-good { color:var(--green); }
|
|
.ping-cur-warn { color:var(--yellow); }
|
|
.ping-cur-bad { color:var(--orange); }
|
|
.ping-cur-down { color:var(--red); font-weight:500; }
|
|
.ping-cur-nd { color:var(--text-dim); }
|
|
|
|
/* Widget headers */
|
|
.widget-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:0.75rem; }
|
|
.widget-title { font-family:var(--fs-font-body); font-size:var(--fs-size-tiny); font-weight:500; color:var(--text-muted); text-transform:uppercase; letter-spacing:var(--fs-tracking-tiny); }
|
|
.widget-link { font-size:0.8rem; color:var(--text-muted); }
|
|
.widget-link:hover { color:var(--text); }
|
|
|
|
/* Time range selector */
|
|
.range-btn { background:none; border:1px solid var(--border-mid); border-radius:4px; color:var(--text-muted); padding:0.18rem 0.5rem; cursor:pointer; font-size:0.75rem; font-family:inherit; transition:background .1s,color .1s,border-color .1s; }
|
|
.range-btn:hover { background:var(--bg-elevated); color:var(--text); border-color:var(--accent); }
|
|
.range-btn.active { background:var(--accent); border-color:var(--accent); color:var(--fs-surface-page); }
|