Files
MeshTender/internal/web/static/app.css
T
2026-08-09 18:05:59 -04:00

268 lines
11 KiB
CSS

/* MeshTender app-specific styles layered on top of Tabler.
Everything generic (cards, forms, buttons, tables, badges, dropdowns) comes
from Tabler; this file only covers things Tabler has no component for: the
live console/event log, the Leaflet map, and the public-key code block. */
/* ---------- Text selection ----------
The dark theme's default selection highlight is nearly invisible inside form
controls; force a high-contrast highlight everywhere, inputs included. */
::selection {
background-color: var(--tblr-primary);
color: #fff;
}
input::selection,
textarea::selection {
background-color: var(--tblr-primary);
color: #fff;
}
/* ---------- Back links (icon + text, plain link — not a button) ----------
These are navigational "← All organizations" links. Styling them as a plain
link (rather than .btn .btn-link) avoids the button's inline-flex box being
icon-height tall but text-width wide, which made the focus/hover state show
asymmetric padding (flush left/right, padded top/bottom). */
.back-link {
display: inline-flex;
align-items: center;
color: var(--tblr-link-color);
text-decoration: none;
}
/* a.back-link beats Tabler's `a:hover:has(.icon){text-decoration:none}`. */
a.back-link:hover {
color: var(--tblr-link-hover-color);
text-decoration: underline;
}
.back-link .icon {
width: 1.25rem;
height: 1.25rem;
}
/* ---------- Public-key / monospace code blocks ---------- */
code.pk,
code.pubkey {
display: block;
word-break: break-all;
background: var(--tblr-bg-surface-secondary);
border: 1px solid var(--tblr-border-color);
border-radius: var(--tblr-border-radius);
padding: 0.5rem 0.625rem;
font-size: 0.8125rem;
/* gray-400, not --tblr-secondary (#6b7280): the latter is only 3.67:1 on the
near-black panel bg and fails WCAG AA for this key/data text. */
color: var(--tblr-gray-400);
}
/* ---------- Collapse toggle label (Show/Hide) ---------- */
.collapse-hide {
display: none;
}
[aria-expanded="true"] .collapse-show {
display: none;
}
[aria-expanded="true"] .collapse-hide {
display: inline;
}
/* A row's expanded panel (account page credential rows) lines up with the row's
heading rather than with the icon gutter to its left: one .icon (1.25rem) plus
one g-3 grid gutter (1rem). Hardcoded rather than read from --tblr-icon-size,
which Tabler redefines per component context. */
.row-panel {
padding-left: 2.25rem;
}
/* A chevron on a collapse toggle: points down when the panel is closed, up when
it's open. Bootstrap maintains aria-expanded on the toggle itself, so the same
attribute the Show/Hide labels key off drives this too — no JS of our own. */
.collapse-chevron {
transition: transform 0.2s ease;
}
[aria-expanded="true"] .collapse-chevron {
transform: rotate(180deg);
}
/* ---------- Live event / console log ---------- */
.evlog {
list-style: none;
margin: 0;
padding: 0.75rem;
background: var(--tblr-bg-surface-secondary);
border: 1px solid var(--tblr-border-color);
border-radius: var(--tblr-border-radius);
max-height: 240px;
overflow-y: auto;
font-family: var(--tblr-font-monospace);
font-size: 0.8125rem;
}
.evlog:empty { display: none; }
.evlog.console { max-height: 320px; }
.ev { padding: 0.15rem 0; border-bottom: 1px solid var(--tblr-border-color-translucent); }
.ev:last-child { border-bottom: none; }
/* Colors chosen for WCAG AA (≥4.5:1) on the near-black log background:
--tblr-secondary (#6b7280) and --tblr-danger (#d63939) only reach ~3.7:1,
so info uses the lighter gray-400 and errors the lighter danger emphasis. */
.ev-info { color: var(--tblr-gray-400); }
.ev-confirmed { color: var(--tblr-success); font-weight: 600; }
.ev-error, .ev-timeout { color: var(--tblr-danger-text-emphasis); }
.ev-warning, .ev-denied, .ev-noreply { color: var(--tblr-warning); }
.ev-debug { color: var(--tblr-info); word-break: break-all; }
.ev-sent { color: var(--tblr-azure); }
.ev-reply { color: var(--tblr-body-color); white-space: pre-wrap; }
/* ---------- Badge contrast (dark) ----------
Tabler's light "-lt" badges set their text to the full-strength brand color
over a 10%-opacity tint, which is too dark for this small status text on the dark
theme. The list below is now maintained by TestContrastMeetsWCAGAA rather than by
eye — an earlier by-hand pass asserted "azure/success/warning/yellow already pass",
and azure (4.15:1) and teal (4.13:1) in fact did not.
Where Tabler defines a dark-theme *-text-emphasis value, use it; the rest are mixed
toward white against their own palette variable, so they stay tied to the palette
instead of becoming hand-picked hexes. */
.badge.bg-purple-lt { color: #d98fe6 !important; }
.badge.bg-secondary-lt { color: var(--tblr-secondary-text-emphasis) !important; }
.badge.bg-blue-lt { color: var(--tblr-primary-text-emphasis) !important; }
.badge.bg-azure-lt { color: color-mix(in srgb, var(--tblr-azure) 80%, #fff) !important; }
.badge.bg-teal-lt { color: color-mix(in srgb, var(--tblr-teal) 80%, #fff) !important; }
/* Tabler's danger button pairs its off-white foreground (#f9fafb) with the danger red,
which measures 4.46:1 — just under the 4.5:1 floor. Pure white reaches 4.66:1 without
touching the button's color. */
.btn-danger {
color: #fff;
}
/* ---------- Contrast corrections (WCAG AA on the dark theme) ----------
Found by internal/e2e TestContrastMeetsWCAGAA, which measures rendered pixels. All
three point at Tabler's own dark-theme *-text-emphasis values rather than
hand-picked colors, so they stay tied to the palette.
Tabler's dark theme lightens --tblr-link-hover-color but NOT --tblr-link-color, which
leaves every link at 3.55:1 on the page background and 2.94:1 on a card — below the
4.5:1 AA floor for body text. Emphasis blue measures 7.07:1 and 5.85:1. .back-link
already reads --tblr-link-color, so it's fixed by this too. */
:root {
--tblr-link-color: var(--tblr-primary-text-emphasis);
}
/* Inactive tab labels use --tblr-muted (#6b7280 → 3.67:1). Emphasis gray is 7.65:1 and
still clearly dimmer than the active tab, which uses the full body color. */
.nav-tabs .nav-link {
color: var(--tblr-secondary-text-emphasis);
}
/* ---------- Members list filter (org members page) ----------
The members page filters rows by role + search in JS (text search can't be
pure CSS). Rows carry `.d-flex` (display:flex !important), so the JS toggles
this `!important` hide class rather than an inline style, which `.d-flex`
would otherwise win over. */
#member-list .member-hidden {
display: none !important;
}
/* ---------- Step indicator (.steps) ----------
Tabler ships two unrelated components under the same `.steps` class: the
horizontal step indicator (ul.steps > li.step-item) we use on the add/added
wizards, and a numbered documentation list (div.steps > h3). The latter's
rule sets border-left + a left margin/padding, which also lands on the step
indicator — drawing a stray vertical line and an empty gap before the first
step. Undo those just for the indicator usage. */
.steps:has(> .step-item) {
margin-left: 0;
padding-left: 0;
border-left: 0;
}
/* ---------- Leaflet map (dark) ---------- */
#map {
height: 360px;
border-radius: var(--tblr-border-radius);
border: 1px solid var(--tblr-border-color);
}
.leaflet-container { background: #14171c; }
/* Scoped under .leaflet-popup so these beat Leaflet's own defaults, which load
after app.css (leaflet.css is pulled in per-page in the map card body). */
.leaflet-popup .leaflet-popup-content-wrapper,
.leaflet-popup .leaflet-popup-tip {
background: var(--tblr-bg-surface);
color: var(--tblr-body-color);
box-shadow: var(--tblr-box-shadow, 0 1px 2px rgba(0, 0, 0, 0.4));
}
.leaflet-popup .leaflet-popup-content { color: var(--tblr-body-color); }
.leaflet-container .leaflet-bar a,
.leaflet-container .leaflet-control-zoom a {
background: var(--tblr-bg-surface);
color: var(--tblr-body-color);
border-bottom-color: var(--tblr-border-color);
}
.leaflet-container .leaflet-bar a:hover { background: var(--tblr-bg-surface-secondary); }
/* Base-layer (Dark/Light) switcher: match the dark UI so the native radios render
correctly. On the default white box + dark color-scheme, an unselected radio
shows as a black dot; a dark box fixes that, and accent-color tints the selected
one. Scoped under .leaflet-container to beat leaflet.css (loaded after app.css). */
.leaflet-container .leaflet-control-layers {
background: var(--tblr-bg-surface);
color: var(--tblr-body-color);
border-color: var(--tblr-border-color);
box-shadow: var(--tblr-box-shadow, 0 1px 2px rgba(0, 0, 0, 0.4));
}
.leaflet-container .leaflet-control-layers-list label { margin: 0; color: var(--tblr-body-color); }
.leaflet-container .leaflet-control-layers-selector { accent-color: var(--tblr-primary); }
/* Profile list on the org config page: a long list scrolls inside its card
instead of pushing the location-preview map off the page. Rows are compact —
Tabler's default list-group padding plus a full-size icon button makes a ~4.5rem
row, so only a couple fit before scrolling. Both are driven by CSS variables, so
turn those down here rather than overriding the padding itself. */
.profile-list {
max-height: 22rem;
overflow-y: auto;
}
.profile-list .list-group-item {
--tblr-list-group-item-padding-y: 0.375rem;
}
.profile-list .btn-icon {
--tblr-btn-padding-y: 0.25rem;
--tblr-btn-icon-size: 1.25rem;
}
/* Active region block in the config editor: highlight the block currently being
drawn on the shared region map. */
.region-block.region-active {
border-color: var(--tblr-primary) !important;
box-shadow: 0 0 0 1px var(--tblr-primary);
}
/* Footer on the centered auth layout (authbase). footerbody right-packs its two
columns, which suits a full-width page but reads as misaligned under a narrow
centered card, so center them here instead. */
.footer-centered .row {
justify-content: center;
}
/* Undo Tabler's scrollbar compensation, which shifts the whole page sideways.
@media (min-width:992px) it sets `:root { margin-left: calc(100vw - 100%) }` —
100vw includes the scrollbar, 100% doesn't, so a page long enough to scroll gets a
left margin the width of the scrollbar. The intent is to stop layout shifting when
a scrollbar appears, but the result is that every scrollable page sits ~15px right
of the window with an unpainted strip down the left edge, navbar included (it was
visible on the org Repeaters and Configuration tabs, and absent on the shorter Home
and Members tabs, which is what made it look page-specific).
scrollbar-gutter reserves the space on the correct side instead: no shift when the
scrollbar appears, no gutter on the left. Where it isn't supported the declaration
is ignored, which lands on the plain browser behavior rather than back on the
sideways shift. Overlay scrollbars (macOS, mobile) take no space at all, so the
gutter is a no-op there.
Overriding here rather than editing tabler.min.css keeps that file byte-identical
to the audited upstream artifact (internal/licenses pins its hash). */
:root {
scrollbar-gutter: stable;
}
@media (min-width: 992px) {
:root {
margin-left: 0;
}
}