/* ============================================================ * app.css — supplemental styles only * ------------------------------------------------------------ * The member-console design system is Bootstrap-first. This file * does NOT define a parallel token layer or utility system: it only * covers the small set of things Bootstrap 5 does not. * * Contract: * - Use Bootstrap utilities/components directly in templates. * - Add a rule here only when Bootstrap genuinely cannot express it * (e.g. the HTMX loading-indicator hook, fixed toast positioning, * cross-browser styling). * - Reference Bootstrap CSS variables (var(--bs-*)) instead of * hardcoded color values so this file stays in sync if Bootstrap's * defaults change. * * See docs/design-system.md for the full Bootstrap usage guide and the * custom-primitives reference. * ============================================================ */ /* HTMX loading indicators */ .htmx-indicator { display: none; } .htmx-request .htmx-indicator { display: inline-block; } .htmx-request.htmx-indicator { display: inline-block; } /* Dashboard quota bar */ .quota-bar { height: 8px; border-radius: 4px; } /* Global toast positioning */ .toast-container { position: fixed; top: 1rem; right: 1rem; z-index: 1100; } /* Entitlement quota meter — Bootstrap doesn't style consistently * across engines, so we normalize it to the Bootstrap primary color. */ meter { height: 0.75rem; border-radius: 0.25rem; overflow: hidden; } meter::-webkit-meter-bar { background: var(--bs-border-color); border: none; border-radius: 0.25rem; } meter::-webkit-meter-optimum-value, meter::-webkit-meter-suboptimum-value, meter::-webkit-meter-even-less-good-value { background: var(--bs-primary); border-radius: 0.25rem; } meter::-moz-meter-bar { background: var(--bs-primary); } /* Shared application sidebar — present on every top-level layout * (dashboard, products, operator). Gives the nav a stable minimum width on * lg+ screens so capability labels don't reflow, and a hard maximum so no * entry's content can ever widen the rail (maintainer 2026-08-23: the * sidebar never gets wide; long labels wrap instead). 15rem is sized so * the longest current entry, "Identity and Access" plus its external-link * icon, fits on one line. */ .app-sidebar { min-width: 15rem; max-width: 15rem; } /* External-destination cue (chrome-conventions: "the identity-provider * handoff is marked subtly") — a small monochrome inline SVG that inherits * the link color. An SVG, not a character: arrow/box glyphs like U+2197 * take emoji presentation in common font stacks, which reads as unserious. * Inline SVG is an atomic inline, so browsers allow a soft wrap before it * even with no whitespace — the icon must share a text-nowrap span with the * label's last word so it can never land on a line by itself. */ .external-link-icon { width: 0.75em; height: 0.75em; margin-left: 0.35em; vertical-align: -0.05em; opacity: 0.7; } /* Disclosure buttons show their open state (maintainer 2026-08-23: an * opened panel's trigger must not look unpressed). Bootstrap's collapse * plumbing maintains aria-expanded on the trigger; painting the button's * own active palette (the .btn variant's CSS variables) flips an outline * button to its filled look while the panel is open, for any variant. */ .btn[data-bs-toggle="collapse"][aria-expanded="true"] { color: var(--bs-btn-active-color); background-color: var(--bs-btn-active-bg); border-color: var(--bs-btn-active-border-color); } /* Field-help cue (form-conventions: "Dense form rows keep explanations * behind a help icon") — a small circled question mark following a label, * carrying its explanation in the wrapping span's title tooltip, so a * compact form row never holds a paragraph. Same rationale as * .external-link-icon for being an SVG rather than a character. */ .form-help-icon { width: 0.85em; height: 0.85em; margin-left: 0.3em; vertical-align: -0.1em; opacity: 0.6; cursor: help; } /* The main content region is a flex item beside the sidebar, and flex * items default to min-width:auto — they refuse to shrink below their * content's min-content width, so one wide table (e.g. the plan-topology * grid) silently widens the whole page past the viewport instead of * scrolling inside its own .table-responsive wrapper (maintainer * 2026-08-23: the body extended beyond the top navbar). min-width:0 lets * the region shrink to the available space; wide content then scrolls in * its own overflow container. Applies to the operator
and the * member layouts' .flex-fill content divs alike. */ .app-sidebar ~ main, .app-sidebar ~ .flex-fill { min-width: 0; } /* Active page indicator — Bootstrap's default .nav-link.active only changes * color, which is faint against bg-light; add weight + a tinted background * so the current page is unambiguous at a glance. */ .app-sidebar .nav-link.active { font-weight: 600; background-color: rgba(var(--bs-primary-rgb), 0.08); border-radius: 0.25rem; } /* Plan topology overview (operator-topology-legibility). The first column * (rank) stays pinned while the cross-ladder grid scrolls horizontally inside * its .table-responsive wrapper, so ranks stay readable when many ladders push * the grid wider than the viewport. Scoped to .topology-grid so no other * operator table is affected. */ .topology-grid th:first-child, .topology-grid td:first-child { position: sticky; left: 0; z-index: 1; background-color: var(--bs-body-bg); } /* Drag-to-reorder (operator) — shared by the topology ladder columns * (horizontal) and the ladder tier rows (vertical), both driven by SortableJS * (self-hosted for CSP) via sortable-reorder.js. Bootstrap-variable drag * feedback + grab affordance, no inline styles. Pinned items (e.g. the topology * rank column) carry no handle and are excluded via data-sortable-filter. */ .sortable-handle { cursor: grab; user-select: none; } .sortable-handle:active { cursor: grabbing; } .sortable-ghost { opacity: 0.5; } .sortable-ghost, .sortable-chosen { background-color: var(--bs-primary-bg-subtle) !important; } /* A table set flush against a card's edges (.card > .table-responsive — the * operator landing's Recent activity timeline) is a scroll container: it * clips its rows to its own square box, painting straight through the curve * the card border draws at each corner. Give the wrapper Bootstrap's own * inner-radius token so the clip follows the border; scoped to first/last * child (mirroring Bootstrap's .card > .list-group treatment) so a wrapper * sitting below a card header keeps its square top corners. */ .card > .table-responsive:first-child { border-top-left-radius: var(--bs-card-inner-border-radius); border-top-right-radius: var(--bs-card-inner-border-radius); } .card > .table-responsive:last-child { border-bottom-left-radius: var(--bs-card-inner-border-radius); border-bottom-right-radius: var(--bs-card-inner-border-radius); } /* Affected-population disclosure in preview panels (org-type default change, * tier reorder/removal): the bucket line carries only the count; org names * live behind a
and render one per row, scrolling past ~8 rows so * hundred-org previews stay compact. */ .org-disclosure-list { max-height: 10rem; overflow-y: auto; margin-top: .25rem; padding-left: .75rem; }