/* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial */ /* SPDX-FileCopyrightText: 2025-2026 Christian Galo */ /* ============================================================ * 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, the linked card's hover shade). * - 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); } /* Inline code chip (design D16): renders in the body colour on a faint * background, keeping the mono font, instead of Bootstrap's default pink * colour — an identifier (a key, a route) is not an error, and the * unstyled pink read as one. */ code { color: var(--bs-body-color); background-color: var(--bs-tertiary-bg); padding: 0.1em 0.3em; border-radius: 0.25rem; } pre code { color: inherit; background-color: transparent; padding: 0; border-radius: 0; } /* White card headers (design D16): links and outline-primary buttons * inside a card header use --bs-link-hover-color (6.1:1 on white) rather * than Bootstrap's default tinted card-header background, which clears the * organization composite's residual contrast failure (4.24:1 on the tinted * header) without a one-off override there. */ .card-header { background-color: var(--bs-body-bg); } .card-header .nav-link, .card-header .btn-outline-primary { color: var(--bs-link-hover-color); } .card-header .btn-outline-primary { border-color: var(--bs-link-hover-color); } /* Application shell rail (docs/design-system.md "Application shell"; * chrome-conventions "One shell for both surfaces"). Present on every * top-level layout: the member rail (partials/shell_rail_member.html) and * the operator rail (operator.html). A fixed 15rem so labels never reflow * and no entry's content can ever widen it (maintainer 2026-08-23: the * sidebar never gets wide; long labels wrap instead). Below lg the same * element is a Bootstrap offcanvas drawer, where the fixed width also caps * the drawer. */ .app-sidebar { min-width: 15rem; max-width: 15rem; } /* At lg and up Bootstrap's responsive offcanvas (.offcanvas-lg) becomes a * static block, resets its background to transparent, and lays out * .offcanvas-body as a flex row. Restore the rail's surface and let the * entry list fill the rail's width so the active highlight spans it. */ @media (min-width: 992px) { .app-sidebar { background-color: var(--bs-light) !important; } .app-sidebar .offcanvas-body { flex-direction: column; } .app-sidebar .offcanvas-body > .nav { width: 100%; } } /* The surface switch, the rail's one non-section entry (member rail: * "Operator panel"; operator rail: "Member panel", design D25), sits last * among the entries and apart from the task entries, using the * border-top-plus-spacing grouping idiom the rails have always used. */ .app-rail-switch { margin-top: 0.5rem; padding-top: 0.5rem; border-top: var(--bs-border-width) solid var(--bs-border-color); } /* The account group, the drawer's second list below lg (mobile-shell-menu * D8). Two groups in one drawer are told apart by distance first and by ink * second (NN/g, the proximity principle: varying whitespace is what * communicates a grouping, and rules of equal weight say "peers"). So the * group is pushed to the drawer's foot by the auto margin, which also lands * it in the same place on both surfaces however many sections the rail has, * and it carries exactly one hairline — the boundary that survives when a * full drawer squeezes the whitespace out. The rail's own entries keep the * one they have for the switch; nothing else in the drawer draws a line. * * The drawer's padding moved from the entry list to .offcanvas-body when * this became two lists, so both sit at one inset; the flex column is what * gives margin-top:auto something to push against (Bootstrap already lays * the body out this way at lg and up, where the group is display:none). */ .app-sidebar .offcanvas-body { display: flex; flex-direction: column; } .app-rail-account { margin-top: auto; padding-top: 0.75rem; border-top: var(--bs-border-width) solid var(--bs-border-color); } /* The identity block names the group (aria-labelledby), so it is set as a * heading and hugs the entries it heads rather than floating between them * and the rule. It carries the entries' horizontal padding itself, being no * link; the email wraps rather than widening the rail (chrome-conventions * "The sidebar has a fixed width"). */ .app-rail-account-identity { margin-bottom: 0.25rem; padding: 0 1rem; } .app-rail-account-name { display: block; font-weight: 600; } .app-rail-account-email { display: block; font-size: 0.875em; color: var(--bs-secondary-color); overflow-wrap: break-word; } /* The rail toggler lives in an always-expanded navbar, and Bootstrap's * .navbar-expand hides .navbar-toggler at every width; show it below lg, * where the rail is a drawer (d-lg-none hides it again at lg and up). It is * the bar's only control there, at the right end, and it never shrinks. The * brand's 55vw cap keeps it off the account trigger, which does not render * below lg, so the deployment name takes the rest of the line; min-width:0 * and the ellipsis still stop it widening the bar (mobile-shell-menu D2). */ @media (max-width: 991.98px) { .app-topbar .navbar-toggler { display: inline-block; flex-shrink: 0; } .app-topbar .navbar-brand { max-width: none; } } /* Sign out is a