/* ============================================================================= YKSA UI kit. Ships with yksa-web-kit; see ecosystem/ui-kit.md. Requires Bootstrap >= 5.3: the state pills below are built on the --bs-*-bg-subtle / --bs-*-text-emphasis / --bs-*-border-subtle families, and text-body-secondary is a 5.3 utility. On 5.2.3 every rule here still parses and the page renders wrong -- muted text is not muted, tinted backgrounds are transparent. The bundled bootstrap.min.css is the version this is written for; do not point a service at its own older copy. Two rules govern the whole kit: 1. Typography carries meaning through weight, colour and size only. No small-caps, no thin/semibold, no letter-spacing, no .display-*. Weights are 400 and 700. 2. Colour is never the only signal. Every state renders an icon and a word alongside its hue. Anything Bootstrap already provides is used as-is. The classes below exist only where it has no equivalent. ============================================================================= */ :root { --navbar-height: 44px; /* One layer scale for the whole estate, because the alternative is each surface picking a number and a sticky bar eventually landing on top of an open menu -- which is exactly what ops's console header did to the navbar's timezone dropdown. Bootstrap's own scale starts at modal-backdrop (1040), so everything here stays below that. 1020 in-page sticky bars (ops's console header) 1030 page chrome: navbar, action bar, cookie banner 1035 anything transient the user just opened -- above all of it */ --yksa-z-sticky: 1020; --yksa-z-chrome: 1030; --yksa-z-floating: 1035; } /* Bootstrap declares --bs-dropdown-zindex on .dropdown-menu, not on :root, so it has to be overridden here -- a value set on :root is shadowed and does nothing. At Bootstrap's default of 1000 an open menu loses to every bar above. */ .dropdown-menu { --bs-dropdown-zindex: var(--yksa-z-floating); } /* ----------------------------------------------------------------------------- Navbar and footer -- layout-identical across every service -------------------------------------------------------------------------- */ .custom-navbar { height: var(--navbar-height); padding-top: 0; padding-bottom: 0; z-index: var(--yksa-z-chrome); border: none; background-color: var(--bs-body-bg) !important; } .navbar { z-index: var(--yksa-z-chrome); } .navbar-brand { margin-right: 1em; } /* Nav links fill the bar's height and butt against one another, so the active item reads as a selected tab rather than a highlighted word. */ .nav-full-height { display: flex; align-items: center; height: 100%; } /* The top padding is deliberately larger than the bottom: it is what sits the link text on the same optical line as the wordmark in the brand block. Do not symmetrise it -- the alignment is the reason it is here. */ .nav-full-height.nav-link { color: inherit; padding-left: 1rem !important; padding-right: 1rem !important; padding-top: 12px; background-color: var(--bs-body-bg); margin-right: -1px; } /* The locale item's only content is a flag image. Because the link is a flex container, an image child gives it no text line box to take its height from, so the item came out ~9px shorter than every neighbour. Reserve the height one line of body text would have occupied and letterbox the flag inside it: same box as its neighbours, flag unscaled and undistorted. Scoped to .nav-link so the brand block's wordmark is untouched. */ .nav-full-height.nav-link > img { min-height: calc(1em * var(--bs-body-line-height)); object-fit: contain; } .nav-full-height.nav-link:hover, .nav-full-height.nav-link.active { color: #fff !important; background-color: var(--bs-primary); } /* ----------------------------------------------------------------------------- State -- the sanctioned rendering of status (ui/_state.html) -------------------------------------------------------------------------- */ .yksa-state { display: inline-flex; align-items: center; gap: .25rem; font-size: .75rem; font-weight: 700; padding: .05rem .4rem; white-space: nowrap; border: 1px solid transparent; } /* health */ .yksa-state-ok, .yksa-state-complete { color: var(--bs-success-text-emphasis); background: var(--bs-success-bg-subtle); border-color: var(--bs-success-border-subtle); } .yksa-state-warning, .yksa-state-forced { color: var(--bs-warning-text-emphasis); background: var(--bs-warning-bg-subtle); border-color: var(--bs-warning-border-subtle); } .yksa-state-failed { color: var(--bs-danger-text-emphasis); background: var(--bs-danger-bg-subtle); border-color: var(--bs-danger-border-subtle); } .yksa-state-running, .yksa-state-in-progress { color: var(--bs-primary-text-emphasis); background: var(--bs-primary-bg-subtle); border-color: var(--bs-primary-border-subtle); } .yksa-state-skipped, .yksa-state-unknown, .yksa-state-not-started { color: var(--bs-secondary-color); background: var(--bs-secondary-bg-subtle); border-color: var(--bs-border-color); } /* Neutral metadata chip. Not a coloured badge, because it carries no status. */ .yksa-chip { display: inline-flex; align-items: center; gap: .25rem; font-size: .75rem; padding: 0 .35rem; white-space: nowrap; color: var(--bs-secondary-color); background: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); } /* ----------------------------------------------------------------------------- Typography helpers -------------------------------------------------------------------------- */ .yksa-label { font-size: .75rem; font-weight: 700; color: var(--bs-secondary-color); } .yksa-hint { font-size: .75rem; font-weight: 400; color: var(--bs-secondary-color); } /* Separator between items in a run of inline metadata: a hairline rule, not a character. A middot at .75rem is hard to tell from a decimal point or a full stop, it is announced by screen readers, and it lands in the clipboard when the line is copied. This is the inline-text counterpart to Bootstrap's `.vr`, which is built for flex rows and has no margins of its own. */ .yksa-sep { display: inline-block; width: 1px; height: 0.9em; margin-inline: 0.5rem; vertical-align: -0.1em; background-color: var(--bs-border-color); } /* Inside an already-tight run (a card header, a table cell), the default margin is too generous. */ .yksa-sep-tight { margin-inline: 0.3rem; } /* Tabular numerals in the body face. Monospace is reserved for verbatim machine text -- hex dumps, TLE lines, command mnemonics. */ .yksa-num { font-variant-numeric: tabular-nums; } .yksa-readout { font-variant-numeric: tabular-nums; font-weight: 700; line-height: 1; } .yksa-utc { font-variant-numeric: tabular-nums; } .yksa-utc .yksa-local { color: var(--bs-secondary-color); font-size: .8em; } /* ----------------------------------------------------------------------------- Layout -------------------------------------------------------------------------- */ /* A record in a list is a row with a left status marker, not a card. */ .yksa-row { display: flex; align-items: center; gap: .75rem; padding: .5rem; border-left: 4px solid var(--bs-border-color); } .yksa-row:hover { background: var(--bs-tertiary-bg); } .yksa-row.is-in-progress { border-left-color: var(--bs-primary); } .yksa-row.is-complete { border-left-color: var(--bs-success); } .yksa-row.is-pending { border-left-color: var(--bs-warning); } .yksa-row.is-failed { border-left-color: var(--bs-danger); } /* The one saturated element allowed on a surface: the active shift, the pass in contact, the source currently polling. Two on a page means one is wrong. */ .yksa-row.is-active { border-left-color: var(--bs-success); background: var(--bs-success-bg-subtle); } /* Empty state: a sentence plus, where there is one, the action that resolves it -- beside the sentence, not under it. Never a blank card. */ .yksa-empty { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; padding: 1.25rem; color: var(--bs-secondary-color); } /* Reading column for forms and reference pages that are worse for being wide. */ .yksa-narrow { max-width: 920px; } /* Save bar pinned to the viewport bottom, plus a spacer so it never covers the last row of the form it belongs to. */ .yksa-actionbar { position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--yksa-z-chrome); } .yksa-actionbar-spacer { height: 5rem; } .form-error-bar ul { margin-bottom: 0; } /* ----------------------------------------------------------------------------- Cookie banner -------------------------------------------------------------------------- */ .cookie-banner { position: sticky; bottom: 0; left: 0; right: 0; z-index: var(--yksa-z-chrome); background-color: #1f2937; color: #f8fafc; } .cookie-banner a { color: #bfdbfe; } /* ----------------------------------------------------------------------------- Responsive -------------------------------------------------------------------------- */ @media (max-width: 991px) { /* Collapsed, the nav is a stacked list; the full-height tab treatment would put a border around every row. */ .nav-full-height.nav-link { padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); margin-right: 0; border: none !important; } .custom-navbar { height: auto; padding-top: 0.5rem; padding-bottom: 0.5rem; } } /* ----------------------------------------------------------------------------- Chip filters (yksa/includes/chip_filters.html + src/chip-filters.js) -------------------------------------------------------------------------- */ .filter-chip { cursor: pointer; user-select: none; transition: background-color 0.12s ease; } .filter-chip:hover { filter: brightness(110%); } .filter-chip.active { background-color: var(--bs-primary) !important; color: #fff; } /* ----------------------------------------------------------------------------- Prose and tables Long-form text rendered from the database (policies, descriptions) and the table cells that hold it. Everything else that used to live in main.css was qsl-only and did not come across. -------------------------------------------------------------------------- */ .text-content { overflow: auto; hyphens: auto; } .text-content img { max-width: 100%; height: auto !important; } .text-content p:last-child, .text-content ul:last-child, .text-content ol:last-child, td p:last-child, td ul:last-child { margin-bottom: 0 !important; } /* Django's messages framework emits `error`; Bootstrap only ships `danger`. */ .alert-error { --bs-alert-color: #fff; --bs-alert-bg: #c42526; --bs-alert-border-color: #c42526; } .btn-outline-bordered { border-color: rgba(0, 0, 0, 0.066); }