Files

308 lines
14 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;
}
/* ---------- MapLibre map (dark) ---------- */
#map {
height: 360px;
border-radius: var(--tblr-border-radius);
border: 1px solid var(--tblr-border-color);
}
/* Painted behind the WebGL canvas, so it shows while tiles are still in flight
rather than a white flash on a dark page. */
.maplibregl-map { background: #14171c; }
/* Scoped under .maplibregl-popup so these beat MapLibre's own defaults, which load
after app.css (maplibre-gl.css is pulled in per-page in the map card body). */
.maplibregl-popup .maplibregl-popup-content {
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));
}
.maplibregl-popup-anchor-top .maplibregl-popup-tip { border-bottom-color: var(--tblr-bg-surface); }
.maplibregl-popup-anchor-bottom .maplibregl-popup-tip { border-top-color: var(--tblr-bg-surface); }
.maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: var(--tblr-bg-surface); }
.maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: var(--tblr-bg-surface); }
.maplibregl-popup-close-button { color: var(--tblr-body-color); }
/* Controls: the zoom buttons, plus our own two button groups (the Dark/Light
basemap switch and the region-editor draw tools, both built in JS — see
basemap.js and regionmap.js). MapLibre's default control is a white box, which
is wrong on every page we render.
Everything here is scoped under .maplibregl-map purely to win the cascade:
maplibre-gl.css is pulled in per-page in the map card body, so it loads *after*
app.css and an equally specific rule of ours loses to it. The tell when this is
missing is subtle — the buttons we style by state still look right, and only the
unpressed ones stay white. */
.maplibregl-map .maplibregl-ctrl-group {
background: var(--tblr-bg-surface);
box-shadow: var(--tblr-box-shadow, 0 1px 2px rgba(0, 0, 0, 0.4));
}
.maplibregl-map .maplibregl-ctrl-group button { color: var(--tblr-body-color); }
/* The zoom glyphs are background-image SVGs with a hard-coded #333 fill — fine on
MapLibre's white control, invisible on our dark one, and not restyleable because
the color lives inside the data: URI. Inverting the icon is the way to reach it. */
.maplibregl-map .maplibregl-ctrl-group button .maplibregl-ctrl-icon { filter: invert(1); }
.maplibregl-map .maplibregl-ctrl-group button + button { border-top-color: var(--tblr-border-color); }
.maplibregl-map .maplibregl-ctrl-group button:not(:disabled):hover { background: var(--tblr-bg-surface-secondary); }
/* MapLibre's icon buttons are square and unlabeled; ours carry text, so they need
width and horizontal padding or the label is clipped to a 29px box. */
.maplibregl-map .mesh-basemap-ctrl button,
.maplibregl-map .mesh-draw-ctrl button {
width: auto;
min-width: 4.5rem;
padding: 0 0.5rem;
font-size: 0.75rem;
line-height: 1.75rem;
}
/* aria-pressed is the state, so it is also what styles the active button — there
is no second class to keep in sync. */
.maplibregl-map .mesh-basemap-ctrl button[aria-pressed="true"],
.maplibregl-map .mesh-draw-ctrl button[aria-pressed="true"] {
background: var(--tblr-primary);
color: #fff;
}
.maplibregl-map .mesh-draw-ctrl button:disabled { opacity: 0.45; }
/* The attribution CARTO require stays visible; only its chrome is themed. It sits
over the map, not the page, so it is deliberately NOT painted in theme tokens:
the same strip has to stay legible over both the near-black Dark Matter basemap
and the near-white Voyager one, which a token that only knows the page theme
cannot do. A dark scrim with near-white text reads on both. */
.maplibregl-map .maplibregl-ctrl.maplibregl-ctrl-attrib {
/* Both class names, because upstream's own rule is .maplibregl-ctrl.maplibregl-
ctrl-attrib — matching only one of them ties on specificity and loses, which
shows up as white-on-white text rather than as nothing happening. */
background: rgba(0, 0, 0, 0.7);
color: #f4f6fa;
}
.maplibregl-map .maplibregl-ctrl-attrib a,
.maplibregl-map .maplibregl-ctrl-attrib a:hover { color: #f4f6fa; }
/* 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;
}
}