Files
2026-10-10 11:56:38 +08:00

234 lines
12 KiB
CSS

@layer tokens {
/* PRIVATE --fs-* only; the EXPORT --*-color-* tier is derived elsewhere and unread here.
* docs/design-system.md "Two tiers, and the split is load-bearing". */
:root {
/* palette colours live in 03-palettes.css */
--fs-shadow-pop: 0 12px 34px rgba(0,0,0,.30); /* pop surfaces: menus, dropdown lists, the select picker */
/* which of the three a control takes: docs/design-system.md "The derived ladder" */
--fs-focus-ring: 0 0 0 3px var(--fs-accent-soft);
--fs-focus-ring-solo: 0 0 0 2px var(--fs-panel), 0 0 0 5px var(--fs-accent);
--fs-focus-ring-invalid: 0 0 0 3px var(--fs-danger-fill);
/* opaque, not a tint (4.21:1 in dark); pinned by tools/mirror.mjs
* (`selected-row/paint`), docs/css.md "@mirror" */
--fs-row-rail: inset 2px 0 0 var(--fs-accent);
/* every separator is this one line, stated once rather than in 69 longhand declarations */
--fs-hairline: 1px solid var(--fs-border);
/* ring and border-colour transition together, or the ring arrives before the edge does */
--fs-field-transition: border-color var(--fs-dur), box-shadow var(--fs-dur);
/* the theme's one colour literal on purpose. docs/design-system.md "The derived ladder". */
--fs-scrim: rgba(0, 0, 0, .7);
/* Role tint ladder, derived so it follows the palette. docs/design-system.md "The derived
* ladder". */
--fs-good-soft: color-mix(in srgb, var(--fs-good) 12%, transparent);
--fs-danger-soft: color-mix(in srgb, var(--fs-danger) 12%, transparent);
--fs-accent-fill: color-mix(in srgb, var(--fs-accent) 18%, transparent);
--fs-good-fill: color-mix(in srgb, var(--fs-good) 18%, transparent);
--fs-warn-fill: color-mix(in srgb, var(--fs-warn) 18%, transparent);
--fs-danger-fill: color-mix(in srgb, var(--fs-danger) 18%, transparent);
--fs-accent-line: color-mix(in srgb, var(--fs-accent) 40%, transparent);
--fs-good-line: color-mix(in srgb, var(--fs-good) 40%, transparent);
--fs-danger-line: color-mix(in srgb, var(--fs-danger) 40%, transparent);
--fs-accent-line-hi: color-mix(in srgb, var(--fs-accent) 55%, transparent);
--fs-good-line-hi: color-mix(in srgb, var(--fs-good) 55%, transparent);
--fs-danger-line-hi: color-mix(in srgb, var(--fs-danger) 55%, transparent);
/* ours, redrawn to stay so — not Lucide's. docs/design-system.md "Two glyphs drawn as
* tokens, not fetched". */
--fs-icon-refresh: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4.4 9.6A8.5 8.5 0 0 1 18.6 7.6'/%3E%3Cpath d='M19.6 14.4A8.5 8.5 0 0 1 5.4 16.4'/%3E%3Cpath fill='%23000' stroke='none' d='M21.6 10.6 15.4 8.4 20.4 4.2Z'/%3E%3Cpath fill='%23000' stroke='none' d='M2.4 13.4 8.6 15.6 3.6 19.8Z'/%3E%3C/svg%3E");
/* css-dup cannot police a glyph inlined once, sized at two call sites — keep in step by hand */
--fs-icon-refresh-sz: calc(18px * var(--fs-density-type));
/* direction flips by mode: light darkens, dark brightens (`filter` recolours the ink too) */
--fs-hover-lift: brightness(.90);
/* four durations, no easing token. docs/design-system.md "Scales" ("Motion") */
--fs-dur: .15s; /* a state change: colour, border, shadow, background, filter */
--fs-dur-move: .2s; /* something that moves or unfolds: transform, max-height */
--fs-dur-fade: .25s; /* a transient thing fading in or out: spinner, notification */
--fs-dur-fill: .4s; /* a progress bar growing to its value */
--fs-content-max: 1280px; /* content column cap */
/* also read by the bar's centering calc and the notification-width calc */
--fs-content-pad: calc(28px * var(--fs-density-space));
/* tokens, not literals — fs-chrome.js's fitShell() reads them back. docs/design-system.md
* "Scales" ("Shell geometry"). */
--fs-sidebar-w: calc(224px * var(--fs-density-box)); /* the expanded vertical sidebar's column */
--fs-rail-w: calc(68px * var(--fs-density-box)); /* ...and the same sidebar collapsed to its icon rail */
--fs-content-min: 500px; /* below this a content column stops being readable */
/* opaque on purpose. docs/design-system.md "The derived ladder". */
--fs-bar-bg: var(--fs-panel);
/* base/10-reset.css's scroll-margin must equal this or an in-page jump lands under the bar */
/* drawn, not fetched. docs/design-system.md "Two glyphs drawn as tokens, not fetched". */
--fs-select-chevron:
linear-gradient(-45deg, transparent 45%, var(--fs-dim) 45% 55%, transparent 55%),
linear-gradient(45deg, transparent 45%, var(--fs-dim) 45% 55%, transparent 55%);
--fs-bar-h: calc(46px * var(--fs-density-box));
/* logo box, .fs-themerow controls, Log out and the poll pill share this size */
--fs-btn-size: calc(34px * var(--fs-density-box));
/* one base (Appearance -> Rounding); three radii scale from it */
--fs-radius-base: 12px;
--fs-radius-lg: var(--fs-radius-base); /* cards, panels, modals, table frame, popovers */
--fs-radius: calc(var(--fs-radius-base) * 5 / 6); /* controls: inputs, buttons, dropdowns, tabs (10 @12) */
--fs-radius-sm: calc(var(--fs-radius-base) * 2 / 3); /* chips, code, small insets, rail toggle (8 @12) */
--fs-radius-pill: 999px; /* pills, toggles, badges — always round */
/* .cbi-section's own gutter; theme/45-misc.css reads it back to undo it (bleed to the card edge) */
--fs-card-pad: calc(16px * var(--fs-density-space));
/* vertical rhythm between cards: .cbi-section, tab groups, alerts, action bars */
--fs-card-gap: calc(16px * var(--fs-density-space));
--fs-font-sans: "Manrope", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--fs-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
/* five steps, ~1.25 apart */
/* three multipliers, one knob each for text/air/boxes */
--fs-density-type: 1;
--fs-density-space: 1;
--fs-density-box: 1;
/* chrome-control floors, not a field's height (--fs-control-h below) */
--fs-ctl-h-sm: calc(30px * var(--fs-density-box)); /* dense/small: .btn.small, table-row buttons */
--fs-ctl-h: calc(38px * var(--fs-density-box)); /* the default control */
--fs-ctl-h-lg: calc(44px * var(--fs-density-box)); /* prominent: .btn.large, the login submit */
/* rounded in @supports below (round() is younger than the floor) */
--fs-type-2xs: calc(10px * var(--fs-density-type));
--fs-type-xs: calc(11px * var(--fs-density-type)); /* eyebrow, badges, tooltips, dense metadata */
--fs-type: calc(13px * var(--fs-density-type)); /* body, tables, fields, menu items — the base */
--fs-type-lg: calc(16px * var(--fs-density-type)); /* section titles (h3/legend), alert headings */
--fs-type-xl: calc(20px * var(--fs-density-type)); /* page titles */
--fs-type-2xl: calc(26px * var(--fs-density-type)); /* h1 — only a third-party app emits a bare one */
/* one spelling per weight — `700`/`bold` and `400`/`normal` were both in use. */
--fs-weight-normal: 400; /* "not bold" — no face of its own; resolves onto the 600 */
--fs-weight: 600; /* the UI default: body, labels, controls */
--fs-weight-bold: 700; /* titles, table headers, the eyebrow */
--fs-leading: 1.5; /* unitless ON PURPOSE — see above (13 -> 19.5px) */
--fs-leading-tight: 1.2; /* headings and single-line plates, where 1.5 looks loose */
/* one microlabel idiom, named once */
--fs-eyebrow-tracking: .06em;
--fs-eyebrow-weight: 700;
--fs-eyebrow-color: var(--fs-faint);
/* pick the nearer step; do not add one for a single rule */
--fs-space-0-5: calc(2px * var(--fs-density-space));
--fs-space-1: calc(4px * var(--fs-density-space));
--fs-space-1-5: calc(6px * var(--fs-density-space));
--fs-space-2: calc(8px * var(--fs-density-space));
--fs-space-2-5: calc(10px * var(--fs-density-space));
--fs-space-3: calc(12px * var(--fs-density-space));
--fs-space-3-5: calc(14px * var(--fs-density-space));
--fs-space-4: calc(16px * var(--fs-density-space));
--fs-space-5: calc(20px * var(--fs-density-space));
--fs-space-6: calc(24px * var(--fs-density-space));
--fs-space-7: calc(28px * var(--fs-density-space));
/* sparse on purpose above -7 — gutters and insets, not rhythm */
--fs-space-8: calc(32px * var(--fs-density-space));
--fs-space-10: calc(40px * var(--fs-density-space));
/* every single-line field's height, derived not measured */
--fs-control-h: calc(var(--fs-type) * var(--fs-leading) + var(--fs-space-1) * 2 + 2px);
/* ceiling for an elastic `.cbi-value-field` (flex: 1) — a COLUMN width, off the 4px scale */
--fs-field-max: 440px;
/* every z-index comes from here */
--fs-z-raise: 2;
--fs-z-sticky: 50;
--fs-z-flyout: 70;
--fs-z-header: 800;
--fs-z-popover: 850;
--fs-z-overlay: 900;
--fs-z-tooltip: 1000;
--fs-z-dropdown: 1100;
/* outbound only (audit.py --strict fails a styles/ read of one). docs/design-system.md
* "The export tier is a RAMP" */
--background-color-high: var(--fs-panel);
--background-color-medium: color-mix(in srgb, var(--fs-panel) 55%, var(--fs-panel2));
--background-color-low: var(--fs-panel2);
--text-color-highest: var(--fs-text);
--text-color-high: var(--fs-text);
--text-color-medium: var(--fs-dim);
--text-color-low: var(--fs-faint);
/* borders carry no text, so -low may fade toward the surface — a hairline is meant to
* disappear into it */
--border-color-high: color-mix(in srgb, var(--fs-border) 60%, var(--fs-text));
--border-color-medium: var(--fs-border);
--border-color-low: color-mix(in srgb, var(--fs-border) 55%, var(--fs-panel));
/* tools/export-tier.mjs proves the whole tier across palette x mode */
--primary-color-high: var(--fs-accent);
--primary-color-medium: color-mix(in oklch, var(--fs-accent) 78%, var(--fs-dim));
--primary-color-low: color-mix(in oklch, var(--fs-accent) 45%, var(--fs-dim));
--on-primary-color: var(--fs-on-accent);
--error-color-high: var(--fs-danger);
--error-color-medium: color-mix(in oklch, var(--fs-danger) 78%, var(--fs-dim));
--error-color-low: color-mix(in oklch, var(--fs-danger) 45%, var(--fs-dim));
--on-error-color: var(--fs-on-danger);
--success-color-high: var(--fs-good);
--success-color-medium: color-mix(in oklch, var(--fs-good) 78%, var(--fs-dim));
--success-color-low: color-mix(in oklch, var(--fs-good) 45%, var(--fs-dim));
--on-success-color: var(--fs-on-good);
--warn-color-high: var(--fs-warn);
--warn-color-medium: color-mix(in oklch, var(--fs-warn) 78%, var(--fs-dim));
--warn-color-low: color-mix(in oklch, var(--fs-warn) 45%, var(--fs-dim));
--on-warn-color: var(--fs-on-warn);
--fs-disabled-opacity: .6;
color-scheme: light;
}
/* round(…) tested where a length is expected, not by a calc() probe */
@supports (width: round(1px, 1px)) {
:root {
--fs-type-2xs: calc(round(10px * var(--fs-density-type), 1px));
--fs-type-xs: calc(round(11px * var(--fs-density-type), 1px));
--fs-type: calc(round(13px * var(--fs-density-type), 1px));
--fs-type-lg: calc(round(16px * var(--fs-density-type), 1px));
--fs-type-xl: calc(round(20px * var(--fs-density-type), 1px));
--fs-type-2xl: calc(round(26px * var(--fs-density-type), 1px));
--fs-control-h: calc(round(up, calc(var(--fs-type) * var(--fs-leading) + var(--fs-space-1) * 2 + 2px), 4px));
}
}
/* bare :root is Normal; these multiply the LADDERS, not the rules */
:root[data-density="compact"] {
--fs-density-type: .9;
--fs-density-box: .85;
--fs-density-space: .65;
}
:root[data-density="large"] {
--fs-density-type: 1.15;
--fs-density-box: 1.15;
/* --fs-density-space stays at the bare :root default of 1 here — large density widens type
* and controls, not the space/gutter scale. */
}
/* No :root[data-content-width] block: fs-axes.js's propAxis writes --fs-content-max inline
* instead. docs/design-system.md "The appearance axes" ("Content width"). */
:root[data-darkmode="true"] {
/* palette colours are in 03-palettes.css; only mode-level tokens live here */
--fs-disabled-opacity: .45;
/* dark fill is light, so brightening (not darkening) moves it away from page AND ink */
--fs-hover-lift: brightness(1.15);
color-scheme: dark;
}
/* no component bridge (--*-hsl, --*-rgb): docs/conventions.md "Writing CSS" ("No colour
* literals"). The one surviving --*-rgb is luci-mod-network's own (base/95-luci.css), not ours. */
}