flowrs-engine/styles/tokens.css
flowRS fd958c0547 flowRS engine, cut from flowRS 3451b065
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 19:04:39 -05:00

399 lines
13 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* SPDX-License-Identifier: MIT OR Apache-2.0
flowRS's design tokens: flowRS's own work, under MIT OR Apache-2.0 (at your option), the licence
of the runtime crates. Every exported app ships a copy (ui/tokens.css); the root NOTICE says so. */
/* ============================================================================
flowRS design tokens: one schema (--fr-*), two skins.
This is the ONLY file in the front-end that may spell a colour as a hex literal
(crates/flowrs-ui/js-test/tokens.test.mjs fails on one anywhere else). Everything
else reads a token: CSS through var(--fr-…), the canvases through crate::kit::tokens,
which reads this file at compile time.
Skins override values only, never names. `Instrument` (dark) is the IDE's look and the
default; `Paper` (light) is the OpenUI-grade app skin: grey surfaces, one accent, 16 px
card corners, 24 px gaps, faint borders, 90–200 ms ease-out motion. A skin applies to
the element that carries `data-skin` and everything inside it, so a page can show both.
Every pair a reader depends on is measured, after alpha blending, by the tests:
text ≥ 4.5:1 on every surface it sits on (WCAG 1.4.3), marks and control edges ≥ 3:1
(1.4.11), the focus ring ≥ 3:1, and the state colours and the held wire ≥ ΔE 6 apart
for normal, protan and deutan vision (flowrs-critic's tokens test, flowrs-viz's metric).
Chart palettes are dpb-plot's validated slots and blue ramp (crates/dpb-plot/src/colour.rs).
Three layers:
1. the Instrument values (the default, and any [data-skin="instrument"] subtree);
2. the Paper values;
3. tokens derived from those, and the legacy names main.css and views.css were written
against. They are declared on every skin root so they resolve inside that skin:
a custom property's var() is resolved where it is declared, then inherited.
============================================================================ */
/* 1. Instrument (dark) ------------------------------------------------------ */
:root,
[data-skin="instrument"] {
color-scheme: dark;
/* Surfaces: elevation by lightness, with a faint blue bias */
--fr-bg: #090c12; /* the page */
--fr-surface-1: #0f131c; /* rails, panels */
--fr-surface-2: #161c28; /* cards, controls */
--fr-surface-3: #1f2836; /* raised, selected */
--fr-surface-sunk: #0b0f16; /* wells: inputs, code, readouts */
--fr-hi: rgba(255, 255, 255, 0.05); /* a card's inner top-highlight */
/* Ink layers: one ink at an alpha, so a layer stacks on any surface */
--fr-layer-hover: rgba(238, 242, 248, 0.05);
--fr-layer-pressed: rgba(238, 242, 248, 0.09);
--fr-layer-track: rgba(238, 242, 248, 0.08);
--fr-overlay: rgba(4, 7, 12, 0.62);
/* Hairlines */
--fr-border: #222b3a;
--fr-border-2: #2c3849;
--fr-border-3: #3a4658;
--fr-border-control: #67758a; /* an input's edge: 3:1 on every surface */
/* Text (4.5:1 or better on every surface and on a hovered one) */
--fr-text: #eef2f8;
--fr-text-soft: #d3dbe6;
--fr-text-2: #a9b4c6;
--fr-text-3: #8e9aad; /* muted: was #6a7688, 3.22:1 on surface-3; now 5.21:1 */
--fr-text-disabled: rgba(238, 242, 248, 0.38);
--fr-console-text: #b6f0c9;
/* Brand: the cyan-to-emerald flow; the accent is the signature teal */
--fr-accent: #35e0c4;
--fr-accent-2: #46a6ff;
--fr-accent-ink: #04140f; /* text on the accent */
--fr-accent-text: #35e0c4; /* the accent as text on a surface */
--fr-accent-soft: rgba(53, 224, 196, 0.12);
--fr-accent-glow: rgba(53, 224, 196, 0.35);
--fr-focus: #35e0c4;
--fr-focus-glow: rgba(53, 224, 196, 0.22);
--fr-white: #ffffff;
/* State: reserved for meaning, never a series. Each has a mark, a text and a soft tint. */
--fr-good: #4ade80; /* was #34d399, ΔE 5.8 from the accent for deutan vision; now 8.3 */
--fr-warn: #f4b740;
--fr-crit: #ff5d6c;
--fr-info: #46a6ff;
--fr-good-text: #6fe39a;
--fr-warn-text: #f7c75f;
--fr-crit-text: #ff8a95;
--fr-info-text: #8ec2ff;
--fr-good-soft: rgba(74, 222, 128, 0.14);
--fr-warn-soft: rgba(244, 183, 64, 0.14);
--fr-crit-soft: rgba(255, 93, 108, 0.14);
--fr-info-soft: rgba(70, 166, 255, 0.14);
/* A held wire carries the previous step. Lavender, lighter than every ownership
colour: was #c084fc, ΔE 2.6 from the borrow blue for deutan vision; now 8.2 (protan)
from --fr-accent-2 and 10.4 from the borrow wire. */
--fr-held: #d8b4fe;
/* Wires by ownership mode (the canvas) */
--fr-wire-move: #ff9f4a;
--fr-wire-borrow: #4a9eff;
--fr-wire-borrow-mut: #ff5252;
--fr-wire-clone: #4ade80;
--fr-wire-copy: #9aa4b2;
/* Chart: one hue for one series, dpb-plot's dark slots for categories (at most six,
the rest fold into "other"), its blue ramp with the anchor flipped so low values
recede into the dark surface. */
--fr-cat-1: #3987e5;
--fr-cat-2: #d95926;
--fr-cat-3: #199e70;
--fr-cat-4: #c98500;
--fr-cat-5: #d55181;
--fr-cat-6: #008300;
--fr-cat-other: #898781;
--fr-seq-1: #0d366b;
--fr-seq-2: #104281;
--fr-seq-3: #1c5cab;
--fr-seq-4: #2a78d6;
--fr-seq-5: #3987e5;
--fr-seq-6: #6da7ec;
--fr-seq-7: #9ec5f4;
--fr-seq-8: #b7d3f6;
--fr-seq-9: #cde2fb;
--fr-chart-grid: rgba(238, 242, 248, 0.07);
--fr-chart-axis: #3a4658;
/* Instruments */
--fr-track: #2b3440; /* a gauge's or bar's empty track */
--fr-needle: #e6edf3;
--fr-knob-hi: #2b3646;
--fr-knob-lo: #10151e;
--fr-led-off: #232b37;
/* Layout */
--fr-radius-card: 14px;
--fr-pad-card: 16px;
--fr-gap-card: 16px;
--fr-gap-stack: 12px;
/* Elevation */
--fr-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5);
--fr-shadow-2: 0 6px 18px rgba(0, 0, 0, 0.4);
--fr-shadow-3: 0 24px 60px rgba(0, 0, 0, 0.55);
--fr-shadow-card: 0 6px 18px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.05);
/* The one main action: the flow gradient */
--fr-primary-fill: linear-gradient(120deg, #46a6ff 0%, #35e0c4 100%);
/* Named colours a person's graph may ask for (a game sprite's "green") */
--fr-swatch-red: #ef4444;
--fr-swatch-green: #22c55e;
--fr-swatch-blue: #3b82f6;
--fr-swatch-yellow: #eab308;
--fr-swatch-orange: #f97316;
--fr-swatch-purple: #a855f7;
--fr-swatch-pink: #ec4899;
--fr-swatch-white: #f8fafc;
--fr-swatch-cyan: #38bdf8;
--fr-swatch-lime: #84cc16;
--fr-swatch-gold: #f59e0b;
--fr-swatch-gray: #9ca3af;
/* The block diagram's node families (the AST bridge's rir.* constructs) */
--fr-node-structure: #64748b;
--fr-node-item: #8b5cf6;
--fr-node-block: #334155;
--fr-node-binding: #0ea5e9;
--fr-node-control: #f59e0b;
--fr-node-call: #10b981;
--fr-node-closure: #ec4899;
--fr-node-operator: #3b82f6;
--fr-node-data: #14b8a6;
--fr-node-jump: #ef4444;
--fr-node-leaf: #4b5563;
--fr-node-unknown: #3a4048;
/* The status bar's dot */
--fr-status-idle: #8a8a8a;
--fr-status-busy: #ffb74a;
}
/* 2. Paper (light) ---------------------------------------------------------- */
[data-skin="paper"] {
color-scheme: light;
/* Surfaces: grey page, white cards (cards rise by being lighter) */
--fr-bg: #f3f4f6;
--fr-surface-1: #f9fafb;
--fr-surface-2: #ffffff;
--fr-surface-3: #eceef1;
--fr-surface-sunk: #f3f4f6;
--fr-hi: rgba(255, 255, 255, 0);
--fr-layer-hover: rgba(20, 23, 28, 0.04);
--fr-layer-pressed: rgba(20, 23, 28, 0.08);
--fr-layer-track: rgba(20, 23, 28, 0.07);
--fr-overlay: rgba(20, 23, 28, 0.4);
/* Faint hairlines: felt rather than seen */
--fr-border: rgba(20, 23, 28, 0.08);
--fr-border-2: rgba(20, 23, 28, 0.12);
--fr-border-3: rgba(20, 23, 28, 0.2);
--fr-border-control: #7a818c;
--fr-text: #14171c;
--fr-text-soft: #2b313b;
--fr-text-2: #454d5a;
--fr-text-3: #5b6370;
--fr-text-disabled: rgba(20, 23, 28, 0.38);
--fr-console-text: #14532d;
/* One accent: the brand teal, deepened to read on white */
--fr-accent: #0b7a69;
--fr-accent-2: #2563c9;
--fr-accent-ink: #ffffff;
--fr-accent-text: #08695a;
--fr-accent-soft: rgba(11, 122, 105, 0.1);
--fr-accent-glow: rgba(11, 122, 105, 0.18);
--fr-focus: #0b7a69;
--fr-focus-glow: rgba(11, 122, 105, 0.16);
/* State, spread in lightness so they stay apart for protan and deutan vision */
--fr-good: #0f6a31;
--fr-warn: #c16e2d;
--fr-crit: #bb0916;
--fr-info: #2563c9;
--fr-good-text: #0f6a31;
--fr-warn-text: #8a4a12;
--fr-crit-text: #a8121c;
--fr-info-text: #1d55ad;
--fr-good-soft: rgba(15, 106, 49, 0.1);
--fr-warn-soft: rgba(193, 110, 45, 0.12);
--fr-crit-soft: rgba(187, 9, 22, 0.08);
--fr-info-soft: rgba(37, 99, 201, 0.1);
--fr-held: #45257d;
--fr-wire-move: #b45309;
--fr-wire-borrow: #2563c9;
--fr-wire-borrow-mut: #bb0916;
--fr-wire-clone: #0f6a31;
--fr-wire-copy: #5b6370;
/* dpb-plot's light slots (each 3:1 on the card) and its blue ramp, low = light */
--fr-cat-1: #2a78d6;
--fr-cat-2: #e96632;
--fr-cat-3: #0ba370;
--fr-cat-4: #bd7f03;
--fr-cat-5: #d76c95;
--fr-cat-6: #008300;
--fr-cat-other: #898781;
--fr-seq-1: #cde2fb;
--fr-seq-2: #b7d3f6;
--fr-seq-3: #9ec5f4;
--fr-seq-4: #6da7ec;
--fr-seq-5: #3987e5;
--fr-seq-6: #2a78d6;
--fr-seq-7: #1c5cab;
--fr-seq-8: #104281;
--fr-seq-9: #0d366b;
--fr-chart-grid: rgba(20, 23, 28, 0.07);
--fr-chart-axis: rgba(20, 23, 28, 0.2);
--fr-track: #e3e6ea;
--fr-needle: #14171c;
--fr-knob-hi: #ffffff;
--fr-knob-lo: #dfe3e8;
--fr-led-off: #d5d9df;
--fr-radius-card: 16px;
--fr-pad-card: 20px;
--fr-gap-card: 24px;
--fr-gap-stack: 16px;
/* Two layers, negative spread, low alpha: soft lift, not an outline */
--fr-shadow-1: 0 1px 3px -2px rgba(0, 0, 0, 0.06), 0 2px 5px -2px rgba(0, 0, 0, 0.06);
--fr-shadow-2: 0 4px 8px -4px rgba(0, 0, 0, 0.08), 0 12px 24px -8px rgba(0, 0, 0, 0.08);
--fr-shadow-3: 0 16px 32px -8px rgba(0, 0, 0, 0.16), 0 32px 64px -12px rgba(0, 0, 0, 0.22);
/* A card is its fill and a faint border; no shadow on top of both (P16). */
--fr-shadow-card: none;
/* One accent, solid */
--fr-primary-fill: #0b7a69;
}
/* 3. Derived tokens and legacy names, resolved under each skin -------------- */
:root,
[data-skin] {
/* Brand gradient and focus */
--fr-flow: linear-gradient(120deg, var(--fr-accent-2) 0%, var(--fr-accent) 100%);
--fr-ring: 0 0 0 2px var(--fr-focus), 0 0 0 5px var(--fr-focus-glow);
--fr-wire-held: var(--fr-held);
--fr-status-ready: var(--fr-good);
--fr-status-error: var(--fr-crit);
/* Type: Geist and Geist Mono (styles/fonts.css). Ramp 12/14/16/18/24/28/32. */
--fr-font-ui: "Geist", ui-sans-serif, -apple-system, system-ui, "Segoe UI", sans-serif;
--fr-font-mono: "Geist Mono", ui-monospace, "SF Mono", "JetBrains Mono", monospace;
--fr-fs-xs: 12px;
--fr-fs-sm: 14px;
--fr-fs-md: 16px;
--fr-fs-lg: 18px;
--fr-fs-xl: 24px;
--fr-fs-2xl: 28px;
--fr-fs-3xl: 32px;
--fr-fw-regular: 400;
--fr-fw-medium: 500;
--fr-fw-strong: 600;
--fr-lh-body: 1.5;
--fr-lh-heading: 1.25;
--fr-lh-display: 1.1;
--fr-tracking: -0.01em;
--fr-tracking-display: -0.02em;
--fr-tracking-caps: 0.04em;
/* Space: a 4 px grid */
--fr-space-0: 0px;
--fr-space-half: 2px;
--fr-space-1: 4px;
--fr-space-2: 8px;
--fr-space-3: 12px;
--fr-space-4: 16px;
--fr-space-5: 20px;
--fr-space-6: 24px;
--fr-space-8: 32px;
--fr-space-10: 40px;
--fr-space-12: 48px;
/* Radius: three on a screen (card, control, pill) */
--fr-radius-control: 10px;
--fr-radius-full: 999px;
--fr-radius-mark: 3px;
/* Motion: 90 ms press, 120–160 ms fades, 200 ms moves, 300 ms panels; ease-out in,
ease-in out. Zero under prefers-reduced-motion (below). */
--fr-dur-press: 90ms;
--fr-dur-fast: 120ms;
--fr-dur-base: 160ms;
--fr-dur-slow: 200ms;
--fr-dur-panel: 300ms;
--fr-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--fr-ease-in: cubic-bezier(0.4, 0, 1, 1);
--fr-ease-standard: cubic-bezier(0.2, 0.7, 0.2, 1);
/* Legacy names: main.css and views.css were written against these */
--bg: var(--fr-bg);
--panel: var(--fr-surface-1);
--panel-2: var(--fr-surface-2);
--panel-3: var(--fr-surface-3);
--hi: var(--fr-hi);
--border: var(--fr-border);
--border-2: var(--fr-border-2);
--text: var(--fr-text);
--text-2: var(--fr-text-2);
--muted: var(--fr-text-3);
--accent: var(--fr-accent);
--accent-2: var(--fr-accent-2);
--accent-ink: var(--fr-accent-ink);
--accent-glow: var(--fr-accent-glow);
--flow: var(--fr-flow);
--good: var(--fr-good);
--warn: var(--fr-warn);
--crit: var(--fr-crit);
--held: var(--fr-held);
--origin-human: var(--fr-accent);
--origin-agent: var(--fr-accent-2);
--origin-mixed: var(--fr-warn);
--origin-imported: var(--fr-text-2);
--origin-unknown: var(--fr-text-3);
--r-sm: 7px;
--r-md: 10px;
--r-lg: 14px;
--r-xl: 20px;
--sh-1: var(--fr-shadow-1);
--sh-2: var(--fr-shadow-2);
--sh-3: var(--fr-shadow-3);
--ring: var(--fr-ring);
--ease: var(--fr-ease-standard);
--fast: var(--fr-dur-fast) var(--fr-ease-standard);
--med: var(--fr-dur-slow) var(--fr-ease-standard);
--font-ui: var(--fr-font-ui);
--font-mono: var(--fr-font-mono);
}
/* Reduced motion: every duration token goes to zero, and anything that still animates
(a transition or keyframe written with a literal time) is cut to a single frame. */
@media (prefers-reduced-motion: reduce) {
:root,
[data-skin] {
--fr-dur-press: 0ms;
--fr-dur-fast: 0ms;
--fr-dur-base: 0ms;
--fr-dur-slow: 0ms;
--fr-dur-panel: 0ms;
}
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}