/* 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; } }