Files
astrolabe/styles/base.css
T

129 lines
4.1 KiB
CSS

@import './tokens.css';
/*
* IBM Plex, self-hosted via @fontsource — offline/PWA safe, never a CDN
* (docs/architecture/09 §3.1). Sans: 400 body, 600 emphasis/headings, 300 for
* large display only. Mono: 400 editor/code, 500 for emphasis. Keep weights
* minimal — add one only when a surface needs it.
*
* The family-level import pulls every script subset (latin, latin-ext, cyrillic,
* greek, vietnamese). We keep them all: user data — snippet names, dataset
* values, chart labels — can be in any language, and Plex must render it rather
* than fall back to system-ui. @font-face `unicode-range` means the browser only
* *downloads* the subset a glyph needs, so the unused subsets cost nothing at
* runtime; they only add to the offline precache, which is the price of working
* internationally offline. Vite bundles the woff2 and Workbox precaches them
* (woff2 is in the PWA globPatterns — see vite.config.ts).
*/
@import '@fontsource/ibm-plex-sans/300.css';
@import '@fontsource/ibm-plex-sans/400.css';
@import '@fontsource/ibm-plex-sans/600.css';
@import '@fontsource/ibm-plex-mono/400.css';
@import '@fontsource/ibm-plex-mono/500.css';
* {
box-sizing: border-box;
}
html,
body,
#app {
height: 100%;
margin: 0;
}
body {
font-family: var(--font-sans);
font-size: var(--font-size-base);
/* Plex "requires space to breathe" — keep body line-height generous (doc §3.1). */
line-height: 1.45;
color: var(--text);
background: var(--bg);
/* Repaint chrome on theme flip; neutralized under reduced-motion below. */
transition:
background var(--dur-moderate) var(--ease),
color var(--dur-moderate) var(--ease);
}
/*
* Shared keyboard focus ring — always visible, accessibility is non-negotiable
* (doc §4, principle 4). Components may override the offset (e.g. inset on
* fields) but should not remove it.
*/
:where(button, input, select, textarea, a, [tabindex]):focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
/*
* Field baseline (arch 09 §4) — the quiet field: a fill one step off its surface
* plus a bottom border only, no box. The fill comes from the contextual --field
* token (elevated surfaces set `--field: var(--field-02)` on their container,
* the same mechanism as --control-hover-fill); the underline is --border-strong,
* which meets 3:1 non-text contrast in both themes. Declared once here at zero
* specificity (:where) so modules add only their idiosyncrasies — width, mono
* font, padding — never a competing border recipe. Monaco's internal widgets
* (find/replace inputs) keep their own styling and are excluded.
*/
:where(
input:not([type]),
input[type='text'],
input[type='search'],
input[type='number'],
input[type='url'],
textarea
):where(:not(.monaco-editor *)) {
font: inherit;
color: var(--text);
background: var(--field);
border: none;
border-block-end: var(--border-width) solid var(--border-strong);
border-radius: var(--radius);
}
:where(input, textarea)::placeholder {
color: var(--text-placeholder);
}
/* On a field the ring hugs the box — an offset ring reads as a second border
where there is no longer a box to anchor it. */
:where(
input:not([type]),
input[type='text'],
input[type='search'],
input[type='number'],
input[type='url'],
textarea
):where(:not(.monaco-editor *)):focus-visible {
outline-offset: -2px;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/*
* Visually-hidden but available to assistive tech — the standard sr-only recipe.
* Used for polite `aria-live`/`role="status"` announcements whose visible
* confirmation lives elsewhere (e.g. Copy Reference shows "Copied" on the button
* for sighted users; this announces it to a screen reader). See
* docs/architecture/10 → Toast copy (the "toast only what can't be seen" rule).
*/
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
}