flowrs-engine/styles/kit.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

1372 lines
57 KiB
CSS

/* SPDX-License-Identifier: MIT OR Apache-2.0
flowRS's component kit: 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/kit.css); the root NOTICE says so. */
/* ============================================================================
The component kit (crates/flowrs-ui/src/kit): app blocks, charts, controls and
instruments, every value a token from styles/tokens.css, so the same markup wears
Instrument and Paper.
House rules, checked by e2e/kit-polish.spec.js and js-test/tokens.test.mjs:
- space from the 4 px scale (--fr-space-*), type from the ramp (12/14/18/28; 24 for
the display sizes on a phone), weights 400/500/600, three radii on a screen;
- colours only through var(--fr-*); motion only through the duration tokens, which
fall to zero under prefers-reduced-motion;
- nothing sets a width the content cannot shrink to: min-width: 0 on flex and grid
children, and wide content (tables, registers) scrolls inside itself.
============================================================================ */
/* ---- Utilities ------------------------------------------------------------ */
.fr-sr {
position: absolute !important;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.fr-mono {
font-family: var(--fr-font-mono);
font-variant-numeric: tabular-nums;
}
.fr-btn:focus-visible,
.fr-tab:focus-visible,
.fr-tabpanel:focus-visible,
.fr-seg:focus-visible,
.fr-toggle:focus-visible,
.fr-input:focus-visible,
.fr-number-step:focus-visible,
.fr-slider-input:focus-visible,
.fr-knob:focus-visible,
.fr-th-sort:focus-visible,
.fr-table-scroll:focus-visible,
.fr-reg-scroll:focus-visible,
.fr-chart-data > summary:focus-visible,
.fr-gallery a:focus-visible {
outline: 2px solid var(--fr-focus);
outline-offset: 2px;
box-shadow: none;
}
/* Fills and strokes by meaning (SVG). Used by the kit and by the Front Panel's
instruments (crate::instruments), which draw the same way. */
.fr-f-none { fill: none; }
.fr-s-none { stroke: none; }
.fr-f-ink { fill: var(--fr-text); }
.fr-s-ink { stroke: var(--fr-text); }
.fr-f-muted { fill: var(--fr-text-3); }
.fr-s-muted { stroke: var(--fr-text-3); }
.fr-f-accent { fill: var(--fr-accent); }
.fr-s-accent { stroke: var(--fr-accent); }
.fr-f-accent-2 { fill: var(--fr-accent-2); }
.fr-s-accent-2 { stroke: var(--fr-accent-2); }
.fr-f-good { fill: var(--fr-good); }
.fr-s-good { stroke: var(--fr-good); }
.fr-f-warn { fill: var(--fr-warn); }
.fr-s-warn { stroke: var(--fr-warn); }
.fr-f-crit { fill: var(--fr-crit); }
.fr-s-crit { stroke: var(--fr-crit); }
.fr-f-track { fill: var(--fr-track); }
.fr-s-track { stroke: var(--fr-track); }
.fr-f-needle { fill: var(--fr-needle); }
.fr-s-needle { stroke: var(--fr-needle); }
.fr-f-card { fill: var(--fr-surface-2); }
.fr-f-cat-1 { fill: var(--fr-cat-1); }
.fr-s-cat-1 { stroke: var(--fr-cat-1); }
.fr-f-cat-2 { fill: var(--fr-cat-2); }
.fr-s-cat-2 { stroke: var(--fr-cat-2); }
.fr-f-cat-3 { fill: var(--fr-cat-3); }
.fr-s-cat-3 { stroke: var(--fr-cat-3); }
.fr-f-cat-4 { fill: var(--fr-cat-4); }
.fr-s-cat-4 { stroke: var(--fr-cat-4); }
.fr-f-cat-5 { fill: var(--fr-cat-5); }
.fr-s-cat-5 { stroke: var(--fr-cat-5); }
.fr-f-cat-6 { fill: var(--fr-cat-6); }
.fr-s-cat-6 { stroke: var(--fr-cat-6); }
.fr-f-cat-other { fill: var(--fr-cat-other); }
.fr-s-cat-other { stroke: var(--fr-cat-other); }
.fr-f-seq-1 { fill: var(--fr-seq-1); }
.fr-f-seq-2 { fill: var(--fr-seq-2); }
.fr-f-seq-3 { fill: var(--fr-seq-3); }
.fr-f-seq-4 { fill: var(--fr-seq-4); }
.fr-f-seq-5 { fill: var(--fr-seq-5); }
.fr-f-seq-6 { fill: var(--fr-seq-6); }
.fr-f-seq-7 { fill: var(--fr-seq-7); }
.fr-f-seq-8 { fill: var(--fr-seq-8); }
.fr-f-seq-9 { fill: var(--fr-seq-9); }
.fr-dashed { stroke-dasharray: 4 4; }
/* Front Panel instrument parts (crate::instruments) */
.fr-stop-from { stop-color: var(--fr-accent-2); }
.fr-stop-to { stop-color: var(--fr-accent); }
.fr-stop-liquid-top { stop-color: color-mix(in srgb, var(--fr-accent-2) 70%, var(--fr-white)); }
.fr-stop-liquid { stop-color: var(--fr-accent-2); }
.fr-i-vessel { fill: var(--fr-surface-sunk); stroke: var(--fr-track); }
.fr-i-band-2 { fill: var(--fr-border-2); }
.fr-i-band-3 { fill: var(--fr-border-3); }
.fr-i-area { fill: color-mix(in srgb, var(--fr-accent-2) 30%, transparent); stroke: var(--fr-accent-2); }
.fr-i-up { fill: var(--fr-good); stroke: var(--fr-good); }
.fr-i-down { fill: var(--fr-crit); stroke: var(--fr-crit); }
.fr-i-cellgap { stroke: var(--fr-surface-2); }
.fr-i-geo-sea { fill: var(--fr-surface-sunk); }
.fr-i-geo-grid { stroke: var(--fr-border-2); }
.fr-i-geo-equator { stroke: var(--fr-border-3); }
.fr-i-geo-pt { fill: var(--fr-accent-2); fill-opacity: 0.85; }
/* ---- Text ----------------------------------------------------------------- */
.fr-heading-block { display: flex; flex-direction: column; gap: var(--fr-space-1); min-width: 0; }
.fr-heading {
margin: 0;
color: var(--fr-text);
font-weight: var(--fr-fw-strong);
overflow-wrap: break-word;
}
.fr-h1 {
font-size: var(--fr-fs-2xl);
line-height: var(--fr-lh-display);
letter-spacing: var(--fr-tracking-display);
}
.fr-h2 { font-size: var(--fr-fs-lg); line-height: var(--fr-lh-heading); letter-spacing: var(--fr-tracking); }
.fr-h3,
.fr-h4 { font-size: var(--fr-fs-sm); line-height: var(--fr-lh-heading); }
.fr-heading-sub {
margin: 0;
color: var(--fr-text-2);
font-size: var(--fr-fs-sm);
line-height: var(--fr-lh-body);
}
.fr-text {
margin: 0;
max-width: 68ch;
color: var(--fr-text);
font-size: var(--fr-fs-sm);
line-height: var(--fr-lh-body);
overflow-wrap: break-word;
}
.fr-text-sm { font-size: var(--fr-fs-xs); }
.fr-text-lead { line-height: 1.6; }
.fr-text-secondary { color: var(--fr-text-2); }
.fr-text-muted { color: var(--fr-text-3); }
.fr-badge {
display: inline-flex;
align-items: center;
gap: var(--fr-space-1);
min-height: 24px;
padding: var(--fr-space-half) var(--fr-space-2);
border-radius: var(--fr-radius-full);
font-size: var(--fr-fs-xs);
font-weight: var(--fr-fw-medium);
line-height: var(--fr-lh-heading);
white-space: nowrap;
background: var(--fr-layer-pressed);
color: var(--fr-text-2);
}
.fr-badge-dot { width: 8px; height: 8px; border-radius: var(--fr-radius-full); background: currentColor; flex: none; }
.fr-badge.fr-tone-good { background: var(--fr-good-soft); color: var(--fr-good-text); }
.fr-badge.fr-tone-warn { background: var(--fr-warn-soft); color: var(--fr-warn-text); }
.fr-badge.fr-tone-crit { background: var(--fr-crit-soft); color: var(--fr-crit-text); }
.fr-badge.fr-tone-info { background: var(--fr-info-soft); color: var(--fr-info-text); }
.fr-badge.fr-tone-accent { background: var(--fr-accent-soft); color: var(--fr-accent-text); }
.fr-tone-text-neutral { color: var(--fr-text-2); }
.fr-tone-text-accent { color: var(--fr-accent-text); }
.fr-tone-text-good { color: var(--fr-good-text); }
.fr-tone-text-warn { color: var(--fr-warn-text); }
.fr-tone-text-crit { color: var(--fr-crit-text); }
.fr-tone-text-info { color: var(--fr-info-text); }
.fr-callout {
display: flex;
align-items: flex-start;
gap: var(--fr-space-3);
padding: var(--fr-space-3) var(--fr-space-4);
border-radius: var(--fr-radius-control);
background: var(--fr-layer-hover);
}
.fr-callout-mark {
display: inline-grid;
place-items: center;
width: 24px;
height: 24px;
flex: none;
border-radius: var(--fr-radius-full);
font-size: var(--fr-fs-xs);
font-weight: var(--fr-fw-strong);
background: var(--fr-layer-pressed);
color: var(--fr-text);
}
.fr-callout-body { display: flex; flex-direction: column; gap: var(--fr-space-half); min-width: 0; }
.fr-callout-title { margin: 0; font-weight: var(--fr-fw-strong); font-size: var(--fr-fs-sm); color: var(--fr-text); }
.fr-callout-text { margin: 0; font-size: var(--fr-fs-sm); color: var(--fr-text-2); overflow-wrap: break-word; }
.fr-callout.fr-tone-good { background: var(--fr-good-soft); }
.fr-callout.fr-tone-good .fr-callout-mark { background: var(--fr-good-text); color: var(--fr-surface-2); }
.fr-callout.fr-tone-good .fr-callout-title { color: var(--fr-good-text); }
.fr-callout.fr-tone-warn { background: var(--fr-warn-soft); }
.fr-callout.fr-tone-warn .fr-callout-mark { background: var(--fr-warn-text); color: var(--fr-surface-2); }
.fr-callout.fr-tone-warn .fr-callout-title { color: var(--fr-warn-text); }
.fr-callout.fr-tone-crit { background: var(--fr-crit-soft); }
.fr-callout.fr-tone-crit .fr-callout-mark { background: var(--fr-crit-text); color: var(--fr-surface-2); }
.fr-callout.fr-tone-crit .fr-callout-title { color: var(--fr-crit-text); }
.fr-callout.fr-tone-info { background: var(--fr-info-soft); }
.fr-callout.fr-tone-info .fr-callout-mark { background: var(--fr-info-text); color: var(--fr-surface-2); }
.fr-callout.fr-tone-info .fr-callout-title { color: var(--fr-info-text); }
/* Text on a soft tint reads in the text colour, not the tint's own: keep body copy legible. */
.fr-callout.fr-tone-good .fr-callout-text,
.fr-callout.fr-tone-warn .fr-callout-text,
.fr-callout.fr-tone-crit .fr-callout-text,
.fr-callout.fr-tone-info .fr-callout-text { color: var(--fr-text-soft); }
.fr-kv { margin: 0; display: flex; flex-direction: column; }
.fr-kv-row {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--fr-space-4);
padding: var(--fr-space-2) 0;
border-bottom: 1px solid var(--fr-border);
}
.fr-kv-row:last-child { border-bottom: 0; }
.fr-kv-label { color: var(--fr-text-2); min-width: 0; }
.fr-kv-value {
margin: 0;
color: var(--fr-text);
font-variant-numeric: tabular-nums;
text-align: right;
overflow-wrap: anywhere;
}
.fr-kv-value.fr-tone-text-neutral { color: var(--fr-text); }
/* ---- States --------------------------------------------------------------- */
.fr-skeleton { display: flex; flex-direction: column; gap: var(--fr-space-2); padding: var(--fr-space-1) 0; }
.fr-skel {
display: block;
border-radius: var(--fr-space-1);
background: var(--fr-layer-pressed);
animation: fr-pulse 1.2s ease-in-out infinite;
}
.fr-skel-line { height: 12px; width: 100%; }
.fr-skel-short { width: 60%; }
.fr-skel-tiny { width: 32%; }
.fr-skel-figure { height: 32px; width: 48%; }
.fr-skel-block { width: 100%; border-radius: var(--fr-radius-control); }
.fr-skel-row { height: 40px; width: 100%; }
@keyframes fr-pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.85; }
}
.fr-empty {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: var(--fr-space-2);
padding: var(--fr-space-6) var(--fr-space-4);
}
.fr-empty-mark {
width: 32px;
height: 32px;
border-radius: var(--fr-radius-full);
border: 2px dashed var(--fr-border-3);
}
.fr-empty-title { margin: 0; font-weight: var(--fr-fw-strong); color: var(--fr-text); }
.fr-empty-reason { margin: 0; max-width: 40ch; color: var(--fr-text-2); }
.fr-error {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: var(--fr-space-3);
padding: var(--fr-space-4);
border-radius: var(--fr-radius-control);
background: var(--fr-crit-soft);
}
.fr-error-mark {
display: inline-grid;
place-items: center;
width: 24px;
height: 24px;
flex: none;
border-radius: var(--fr-radius-full);
background: var(--fr-crit-text);
color: var(--fr-surface-2);
font-weight: var(--fr-fw-strong);
font-size: var(--fr-fs-xs);
}
.fr-error-body { flex: 1 1 160px; min-width: 0; display: flex; flex-direction: column; gap: var(--fr-space-half); }
.fr-error-title { margin: 0; font-weight: var(--fr-fw-strong); color: var(--fr-crit-text); }
.fr-error-cause { margin: 0; color: var(--fr-text-soft); overflow-wrap: break-word; }
/* ---- Layout --------------------------------------------------------------- */
.fr-stack { display: flex; min-width: 0; }
.fr-stack-col { flex-direction: column; }
.fr-stack-row { flex-direction: row; flex-wrap: wrap; }
.fr-stack-row.fr-nowrap { flex-wrap: nowrap; }
.fr-stack > * { min-width: 0; }
.fr-gap-none { gap: 0; }
.fr-gap-inner { gap: var(--fr-space-2); }
.fr-gap-group { gap: var(--fr-gap-stack); }
.fr-gap-section { gap: var(--fr-space-6); }
.fr-gap-card { gap: var(--fr-gap-card); }
.fr-align-start { align-items: flex-start; }
.fr-align-center { align-items: center; }
.fr-align-end { align-items: flex-end; }
.fr-align-baseline { align-items: baseline; }
.fr-justify-center { justify-content: center; }
.fr-justify-end { justify-content: flex-end; }
.fr-justify-between { justify-content: space-between; }
.fr-grid { display: grid; min-width: 0; }
.fr-grid > * { min-width: 0; }
.fr-grid-narrow { grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); }
.fr-grid-medium { grid-template-columns: repeat(auto-fit, minmax(min(100%, 248px), 1fr)); }
.fr-grid-wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr)); }
.fr-span-all { grid-column: 1 / -1; min-width: 0; display: flex; flex-direction: column; }
.fr-span-all > * { flex: 1; }
.fr-box { min-width: 0; border-radius: var(--fr-radius-control); }
.fr-pad-none { padding: 0; }
.fr-pad-inner { padding: var(--fr-space-2); }
.fr-pad-group { padding: var(--fr-space-4); }
.fr-pad-section { padding: var(--fr-space-6); }
.fr-box.fr-raised { background: var(--fr-surface-2); border: 1px solid var(--fr-border); }
.fr-box.fr-sunk { background: var(--fr-layer-hover); }
.fr-card {
display: flex;
flex-direction: column;
gap: var(--fr-gap-stack);
min-width: 0;
padding: var(--fr-pad-card);
border-radius: var(--fr-radius-card);
}
.fr-card.fr-raised {
background: var(--fr-surface-2);
border: 1px solid var(--fr-border);
box-shadow: var(--fr-shadow-card);
}
.fr-card.fr-sunk { background: var(--fr-layer-hover); }
.fr-card.fr-clear { padding: 0; }
.fr-card-head { display: flex; flex-direction: column; gap: var(--fr-space-half); min-width: 0; }
.fr-card-title { margin: 0; font-size: var(--fr-fs-sm); font-weight: var(--fr-fw-strong); line-height: var(--fr-lh-heading); color: var(--fr-text); }
.fr-card-sub { margin: 0; font-size: var(--fr-fs-xs); color: var(--fr-text-2); }
.fr-card-body { display: flex; flex-direction: column; gap: var(--fr-gap-stack); min-width: 0; }
.fr-tabs { display: flex; flex-direction: column; gap: var(--fr-space-4); min-width: 0; }
.fr-tablist {
display: flex;
gap: var(--fr-space-4);
border-bottom: 1px solid var(--fr-border);
overflow-x: auto;
scrollbar-width: none;
}
.fr-tab {
appearance: none;
border: 0;
background: none;
padding: var(--fr-space-2) 0;
min-height: 40px;
font: inherit;
font-weight: var(--fr-fw-medium);
color: var(--fr-text-2);
border-bottom: 2px solid transparent;
margin-bottom: -1px;
cursor: pointer;
white-space: nowrap;
transition: color var(--fr-dur-fast) var(--fr-ease-out), border-color var(--fr-dur-fast) var(--fr-ease-out);
}
.fr-tab:hover { color: var(--fr-text); }
.fr-tab[aria-selected="true"] { color: var(--fr-text); border-bottom-color: var(--fr-accent); }
.fr-tabpanel { min-width: 0; border-radius: var(--fr-space-1); }
.fr-tabpanel[hidden] { display: none; }
.fr-divider { width: 100%; height: 1px; border: 0; margin: 0; background: var(--fr-border); }
/* ---- Data ----------------------------------------------------------------- */
.fr-stat { display: flex; flex-direction: column; gap: var(--fr-space-2); min-width: 0; }
.fr-stat-label { margin: 0; color: var(--fr-text-2); font-size: var(--fr-fs-sm); line-height: var(--fr-lh-heading); overflow-wrap: break-word; }
.fr-stat-value {
margin: 0;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: var(--fr-space-1);
min-width: 0;
}
.fr-stat-figure {
font-size: var(--fr-fs-2xl);
font-weight: var(--fr-fw-strong);
line-height: var(--fr-lh-display);
letter-spacing: var(--fr-tracking-display);
font-variant-numeric: tabular-nums;
color: var(--fr-text);
overflow-wrap: anywhere;
}
.fr-stat-unit { color: var(--fr-text-2); font-size: var(--fr-fs-sm); font-weight: var(--fr-fw-medium); }
.fr-stat-delta {
margin: 0;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: var(--fr-space-1) var(--fr-space-2);
font-size: var(--fr-fs-xs);
}
.fr-delta { display: inline-flex; align-items: baseline; gap: var(--fr-space-1); font-weight: var(--fr-fw-medium); font-variant-numeric: tabular-nums; }
.fr-delta-arrow { font-size: var(--fr-fs-xs); }
.fr-stat-cmp { color: var(--fr-text-3); }
/* The line stretches with the tile; its end marker sits over it in pixels, so it is round
at any width. */
.fr-spark-wrap { position: relative; display: block; width: 100%; min-width: 0; }
.fr-spark { display: block; width: 100%; height: 28px; overflow: visible; }
.fr-spark-end {
position: absolute;
width: 6px;
height: 6px;
border-radius: var(--fr-radius-full);
transform: translate(-50%, -50%);
background: var(--fr-accent);
pointer-events: none;
}
.fr-spark-line { fill: none; stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.fr-spark-area { stroke: none; opacity: 0.12; }
.fr-tone-mark-accent .fr-spark-line { stroke: var(--fr-accent); }
.fr-tone-mark-accent .fr-spark-area { fill: var(--fr-accent); }
.fr-tone-mark-accent .fr-spark-end { background: var(--fr-accent); }
.fr-spark-none { color: var(--fr-text-3); }
.fr-progress-block { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--fr-space-1) var(--fr-space-3); align-items: center; }
.fr-progress-label { grid-column: 1; color: var(--fr-text); font-size: var(--fr-fs-sm); }
.fr-progress-value { grid-column: 2; grid-row: 1; color: var(--fr-text-2); font-size: var(--fr-fs-xs); font-variant-numeric: tabular-nums; text-align: right; }
.fr-progress { grid-column: 1 / -1; }
.fr-progress-track {
display: block;
height: 8px;
border-radius: var(--fr-radius-full);
background: var(--fr-layer-track);
overflow: hidden;
}
.fr-progress-fill {
display: block;
height: 100%;
border-radius: var(--fr-radius-full);
background: var(--fr-accent);
transition: width var(--fr-dur-slow) var(--fr-ease-out);
}
.fr-tone-mark-good .fr-progress-fill { background: var(--fr-good); }
.fr-tone-mark-warn .fr-progress-fill { background: var(--fr-warn); }
.fr-tone-mark-crit .fr-progress-fill { background: var(--fr-crit); }
.fr-tone-mark-info .fr-progress-fill { background: var(--fr-info); }
.fr-table-block { display: flex; flex-direction: column; gap: var(--fr-space-3); min-width: 0; }
.fr-table-tools { display: flex; flex-direction: column; gap: var(--fr-space-1); max-width: 320px; }
.fr-table-scroll {
overflow: auto;
min-width: 0;
border: 1px solid var(--fr-border);
border-radius: var(--fr-radius-control);
}
.fr-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: var(--fr-fs-sm);
font-variant-numeric: tabular-nums;
}
.fr-table th,
.fr-table td {
padding: var(--fr-space-3) var(--fr-space-4);
text-align: left;
vertical-align: middle;
border-bottom: 1px solid var(--fr-border);
white-space: nowrap;
color: var(--fr-text);
}
.fr-table thead th {
position: sticky;
top: 0;
z-index: 1;
background: var(--fr-surface-2);
color: var(--fr-text-2);
font-size: var(--fr-fs-xs);
font-weight: var(--fr-fw-strong);
}
.fr-table tbody th { font-weight: var(--fr-fw-medium); }
.fr-table tbody tr:last-child > * { border-bottom: 0; }
.fr-table tbody tr:hover > * { background: var(--fr-layer-hover); }
.fr-table .fr-num { text-align: right; }
.fr-th-sort {
appearance: none;
border: 0;
background: none;
padding: 0;
font: inherit;
color: inherit;
display: inline-flex;
align-items: center;
gap: var(--fr-space-1);
cursor: pointer;
min-height: 24px;
border-radius: var(--fr-space-1);
}
.fr-num .fr-th-sort { flex-direction: row-reverse; }
.fr-th-sort:hover { color: var(--fr-text); }
.fr-sort-mark { color: var(--fr-text-3); font-size: var(--fr-fs-xs); }
.fr-table-nomatch td { white-space: normal; color: var(--fr-text-2); }
.fr-table-nomatch td > * + * { margin-left: var(--fr-space-3); }
.fr-table-count { margin: 0; color: var(--fr-text-3); font-size: var(--fr-fs-xs); }
/* ---- Charts --------------------------------------------------------------- */
.fr-chart { margin: 0; display: flex; flex-direction: column; gap: var(--fr-space-2); min-width: 0; }
.fr-chart-head { display: flex; flex-direction: column; gap: var(--fr-space-half); }
.fr-chart-title { margin: 0; font-weight: var(--fr-fw-strong); color: var(--fr-text); line-height: var(--fr-lh-heading); }
.fr-chart-sub { margin: 0; color: var(--fr-text-2); font-size: var(--fr-fs-xs); }
.fr-axis-caption { margin: 0; color: var(--fr-text-3); font-size: var(--fr-fs-xs); }
.fr-axis-caption-x { text-align: center; }
.fr-chart-plot { position: relative; width: 100%; min-width: 0; }
.fr-chart-svg { display: block; max-width: 100%; height: auto; overflow: visible; }
.fr-chart-fail { margin: 0; color: var(--fr-text-2); }
.fr-chart-gridline { stroke: var(--fr-chart-grid); stroke-width: 1; }
.fr-chart-baseline { stroke: var(--fr-chart-axis); stroke-width: 1; }
.fr-chart-tick {
fill: var(--fr-text-3);
font-family: var(--fr-font-ui);
font-size: var(--fr-fs-xs);
font-variant-numeric: tabular-nums;
}
.fr-chart-endlabel { fill: var(--fr-text-2); font-size: var(--fr-fs-xs); font-weight: var(--fr-fw-medium); }
.fr-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.fr-area { stroke: none; opacity: 0.22; }
.fr-line-dot { stroke: var(--fr-surface-2); stroke-width: 1.5; }
.fr-bar,
.fr-cell,
.fr-point { stroke: none; }
.fr-point { fill-opacity: 0.85; }
.fr-hit { fill: transparent; stroke: none; }
.fr-crosshair { stroke: var(--fr-border-3); stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; }
.fr-point-ring { fill: none; stroke: var(--fr-text); stroke-width: 1.5; pointer-events: none; }
.fr-end-dot { stroke: none; }
/* The categorical slots and the ramp, as stroke for lines and fill for everything else. */
.fr-line.fr-cat-1, .fr-scope-trace.fr-cat-1, .fr-wave-trace.fr-cat-1 { stroke: var(--fr-cat-1); }
.fr-line.fr-cat-2, .fr-scope-trace.fr-cat-2, .fr-wave-trace.fr-cat-2 { stroke: var(--fr-cat-2); }
.fr-line.fr-cat-3, .fr-scope-trace.fr-cat-3, .fr-wave-trace.fr-cat-3 { stroke: var(--fr-cat-3); }
.fr-line.fr-cat-4, .fr-scope-trace.fr-cat-4, .fr-wave-trace.fr-cat-4 { stroke: var(--fr-cat-4); }
.fr-line.fr-cat-5, .fr-scope-trace.fr-cat-5, .fr-wave-trace.fr-cat-5 { stroke: var(--fr-cat-5); }
.fr-line.fr-cat-6, .fr-scope-trace.fr-cat-6, .fr-wave-trace.fr-cat-6 { stroke: var(--fr-cat-6); }
.fr-line.fr-cat-other { stroke: var(--fr-cat-other); }
.fr-cat-1:not(.fr-line, .fr-scope-trace, .fr-wave-trace) { fill: var(--fr-cat-1); background: var(--fr-cat-1); }
.fr-cat-2:not(.fr-line, .fr-scope-trace, .fr-wave-trace) { fill: var(--fr-cat-2); background: var(--fr-cat-2); }
.fr-cat-3:not(.fr-line, .fr-scope-trace, .fr-wave-trace) { fill: var(--fr-cat-3); background: var(--fr-cat-3); }
.fr-cat-4:not(.fr-line, .fr-scope-trace, .fr-wave-trace) { fill: var(--fr-cat-4); background: var(--fr-cat-4); }
.fr-cat-5:not(.fr-line, .fr-scope-trace, .fr-wave-trace) { fill: var(--fr-cat-5); background: var(--fr-cat-5); }
.fr-cat-6:not(.fr-line, .fr-scope-trace, .fr-wave-trace) { fill: var(--fr-cat-6); background: var(--fr-cat-6); }
.fr-cat-other:not(.fr-line) { fill: var(--fr-cat-other); background: var(--fr-cat-other); }
.fr-seq-1 { fill: var(--fr-seq-1); background: var(--fr-seq-1); }
.fr-seq-2 { fill: var(--fr-seq-2); background: var(--fr-seq-2); }
.fr-seq-3 { fill: var(--fr-seq-3); background: var(--fr-seq-3); }
.fr-seq-4 { fill: var(--fr-seq-4); background: var(--fr-seq-4); }
.fr-seq-5 { fill: var(--fr-seq-5); background: var(--fr-seq-5); }
.fr-seq-6 { fill: var(--fr-seq-6); background: var(--fr-seq-6); }
.fr-seq-7 { fill: var(--fr-seq-7); background: var(--fr-seq-7); }
.fr-seq-8 { fill: var(--fr-seq-8); background: var(--fr-seq-8); }
.fr-seq-9 { fill: var(--fr-seq-9); background: var(--fr-seq-9); }
.fr-legend {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-wrap: wrap;
gap: var(--fr-space-1) var(--fr-space-4);
font-size: var(--fr-fs-xs);
color: var(--fr-text-2);
}
.fr-legend-item { display: inline-flex; align-items: center; gap: var(--fr-space-2); }
.fr-legend-swatch { width: 12px; height: 12px; border-radius: var(--fr-radius-mark); flex: none; }
.fr-tip {
position: absolute;
left: 0;
top: 0;
z-index: 3;
pointer-events: none;
transition: transform var(--fr-dur-fast) var(--fr-ease-out);
}
.fr-tip-card {
transform: translateY(-100%);
min-width: 160px;
max-width: 240px;
padding: var(--fr-space-2) var(--fr-space-3);
border-radius: var(--fr-radius-control);
background: var(--fr-surface-3);
border: 1px solid var(--fr-border-2);
box-shadow: var(--fr-shadow-2);
font-size: var(--fr-fs-xs);
color: var(--fr-text);
}
.fr-tip-flip .fr-tip-card { transform: translate(-100%, -100%); }
.fr-tip-head { margin: 0 0 var(--fr-space-1); font-weight: var(--fr-fw-strong); }
.fr-tip-rows { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--fr-space-1); }
.fr-tip-row { display: flex; align-items: center; gap: var(--fr-space-2); }
.fr-tip-name { color: var(--fr-text-2); flex: 1; min-width: 0; }
.fr-tip-value { font-variant-numeric: tabular-nums; font-weight: var(--fr-fw-medium); }
.fr-chart-data > summary {
display: inline-flex;
align-items: center;
gap: var(--fr-space-2);
list-style: none;
min-height: 24px;
color: var(--fr-text-2);
font-size: var(--fr-fs-xs);
cursor: pointer;
border-radius: var(--fr-space-1);
}
.fr-chart-data > summary::-webkit-details-marker { display: none; }
.fr-chart-data > summary::before {
content: "";
border-left: 5px solid currentColor;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
transition: transform var(--fr-dur-fast) var(--fr-ease-out);
}
.fr-chart-data[open] > summary::before { transform: rotate(90deg); }
.fr-chart-data > summary:hover { color: var(--fr-text); }
/* A heatmap's scale: its nine steps between the lowest and highest value. */
.fr-scale { display: flex; align-items: center; gap: var(--fr-space-2); font-size: var(--fr-fs-xs); color: var(--fr-text-2); font-variant-numeric: tabular-nums; }
.fr-scale-steps { display: flex; gap: var(--fr-space-half); }
.fr-scale-step { width: 16px; height: 12px; border-radius: var(--fr-radius-mark); }
.fr-chart-data[open] > summary { margin-bottom: var(--fr-space-2); }
.fr-donut { display: flex; flex-wrap: wrap; align-items: center; gap: var(--fr-space-6); }
.fr-donut-figure { position: relative; width: 180px; height: 180px; flex: none; }
.fr-donut-svg { display: block; width: 100%; height: 100%; }
.fr-slice { stroke: var(--fr-surface-2); stroke-width: 2; transition: opacity var(--fr-dur-fast) var(--fr-ease-out); }
.fr-slice-dim { opacity: 0.35; }
/* The hole is its own disc in the card's colour, so the total reads against the card and
never against a slice under its box. */
.fr-donut-centre {
position: absolute;
inset: 32px;
border-radius: var(--fr-radius-full);
background: var(--fr-surface-2);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
pointer-events: none;
padding: 0 var(--fr-space-2);
}
.fr-donut-centre-value { font-size: var(--fr-fs-lg); font-weight: var(--fr-fw-strong); font-variant-numeric: tabular-nums; color: var(--fr-text); line-height: var(--fr-lh-heading); }
.fr-donut-centre-label { font-size: var(--fr-fs-xs); color: var(--fr-text-2); line-height: var(--fr-lh-heading); }
.fr-donut-legend { flex: 1 1 200px; min-width: 0; margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
.fr-donut-item {
display: grid;
grid-template-columns: 12px minmax(0, 1fr) auto auto;
align-items: center;
gap: var(--fr-space-2);
padding: var(--fr-space-1) 0;
font-size: var(--fr-fs-xs);
color: var(--fr-text-2);
}
.fr-donut-name { color: var(--fr-text); overflow-wrap: anywhere; }
.fr-donut-val,
.fr-donut-share { font-variant-numeric: tabular-nums; text-align: right; }
.fr-donut-share { min-width: 48px; color: var(--fr-text); font-weight: var(--fr-fw-medium); }
.fr-donut-item-on .fr-donut-name { font-weight: var(--fr-fw-strong); }
/* ---- Controls ------------------------------------------------------------- */
.fr-btn {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--fr-space-2);
min-height: 40px;
padding: 0 var(--fr-space-4);
border-radius: var(--fr-radius-control);
border: 1px solid transparent;
font: inherit;
font-weight: var(--fr-fw-medium);
line-height: var(--fr-lh-heading);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
transition: background var(--fr-dur-press) var(--fr-ease-out), border-color var(--fr-dur-press) var(--fr-ease-out),
transform var(--fr-dur-press) var(--fr-ease-out);
}
.fr-btn:active:not(:disabled) { transform: translateY(1px); }
.fr-btn:disabled { cursor: not-allowed; opacity: 0.55; }
.fr-btn-sm { min-height: 32px; padding: 0 var(--fr-space-3); font-size: var(--fr-fs-xs); }
.fr-btn-primary { background: var(--fr-primary-fill); color: var(--fr-accent-ink); }
.fr-btn-primary:hover:not(:disabled) { filter: brightness(1.06); }
.fr-btn-secondary { background: var(--fr-surface-2); color: var(--fr-text); border-color: var(--fr-border-3); }
.fr-btn-secondary:hover:not(:disabled) { background: var(--fr-surface-3); }
.fr-btn-ghost { background: transparent; color: var(--fr-text-2); }
.fr-btn-ghost:hover:not(:disabled) { background: var(--fr-layer-hover); color: var(--fr-text); }
.fr-btn-danger { background: var(--fr-crit-soft); color: var(--fr-crit-text); border-color: color-mix(in srgb, var(--fr-crit) 45%, transparent); }
.fr-btn-danger:hover:not(:disabled) { background: color-mix(in srgb, var(--fr-crit) 22%, transparent); }
.fr-field { display: flex; flex-direction: column; gap: var(--fr-space-2); min-width: 0; }
/* A field reads at a field's width, not a page's: a number or a line of text in a wide card
stops at 480 px (P4's measure, for inputs). */
.fr-card-body > .fr-field,
.fr-section-body > .fr-field,
.fr-form > .fr-field,
.fr-grid > .fr-field { max-width: 480px; }
.fr-field-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--fr-space-3); }
.fr-field-label { color: var(--fr-text); font-weight: var(--fr-fw-medium); font-size: var(--fr-fs-sm); }
.fr-field-value { color: var(--fr-text-2); font-variant-numeric: tabular-nums; font-size: var(--fr-fs-sm); white-space: nowrap; }
.fr-field-hint { margin: 0; color: var(--fr-text-3); font-size: var(--fr-fs-xs); }
.fr-field-hint:empty { display: none; }
.fr-field-error { margin: 0; color: var(--fr-crit-text); font-size: var(--fr-fs-xs); font-weight: var(--fr-fw-medium); }
.fr-field-error[hidden] { display: none; }
.fr-field-invalid .fr-input { border-color: var(--fr-crit); }
.fr-input {
width: 100%;
min-width: 0;
height: 40px;
padding: 0 var(--fr-space-3);
border: 1px solid var(--fr-border-control);
border-radius: var(--fr-radius-control);
background: var(--fr-surface-sunk);
color: var(--fr-text);
font: inherit;
transition: border-color var(--fr-dur-fast) var(--fr-ease-out);
}
.fr-input::placeholder { color: var(--fr-text-3); }
.fr-input:hover { border-color: var(--fr-text-3); }
.fr-slider { position: relative; height: 24px; }
.fr-slider-track {
position: absolute;
left: 0;
right: 0;
top: 8px;
height: 8px;
border-radius: var(--fr-radius-full);
background: var(--fr-layer-track);
overflow: hidden;
}
.fr-slider-fill { display: block; height: 100%; background: var(--fr-accent); }
.fr-slider-input {
position: absolute;
inset: 0;
width: 100%;
margin: 0;
appearance: none;
background: transparent;
cursor: pointer;
border-radius: var(--fr-radius-full);
}
.fr-slider-input::-webkit-slider-runnable-track { height: 24px; background: transparent; }
.fr-slider-input::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
margin-top: 2px;
border-radius: var(--fr-radius-full);
background: var(--fr-surface-2);
border: 2px solid var(--fr-accent);
box-shadow: var(--fr-shadow-1);
}
.fr-slider-input::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: var(--fr-radius-full);
background: var(--fr-surface-2);
border: 2px solid var(--fr-accent);
}
.fr-slider-scale { display: flex; justify-content: space-between; color: var(--fr-text-3); font-size: var(--fr-fs-xs); font-variant-numeric: tabular-nums; }
.fr-number { display: flex; align-items: stretch; gap: var(--fr-space-2); }
.fr-number-input { flex: 1; text-align: right; font-variant-numeric: tabular-nums; }
.fr-number-unit { align-self: center; color: var(--fr-text-2); min-width: 24px; }
.fr-number-step {
appearance: none;
width: 40px;
flex: none;
border: 1px solid var(--fr-border-control);
border-radius: var(--fr-radius-control);
background: var(--fr-surface-2);
color: var(--fr-text);
font: inherit;
font-size: var(--fr-fs-lg);
cursor: pointer;
}
.fr-number-step:hover { background: var(--fr-surface-3); }
.fr-toggle-field { display: flex; align-items: center; justify-content: space-between; gap: var(--fr-space-4); }
.fr-toggle-text { display: flex; flex-direction: column; gap: var(--fr-space-half); min-width: 0; }
.fr-toggle-desc { color: var(--fr-text-2); font-size: var(--fr-fs-xs); }
.fr-toggle {
appearance: none;
display: inline-flex;
align-items: center;
gap: var(--fr-space-2);
padding: 0;
border: 0;
background: none;
color: var(--fr-text-2);
font: inherit;
font-size: var(--fr-fs-xs);
font-weight: var(--fr-fw-medium);
cursor: pointer;
flex: none;
border-radius: var(--fr-radius-full);
}
.fr-toggle-knob {
position: relative;
width: 44px;
height: 24px;
border-radius: var(--fr-radius-full);
background: var(--fr-layer-track);
border: 1px solid var(--fr-border-control);
transition: background var(--fr-dur-fast) var(--fr-ease-out), border-color var(--fr-dur-fast) var(--fr-ease-out);
}
.fr-toggle-knob::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
border-radius: var(--fr-radius-full);
background: var(--fr-text-2);
transition: transform var(--fr-dur-slow) var(--fr-ease-out), background var(--fr-dur-fast) var(--fr-ease-out);
}
.fr-toggle[aria-checked="true"] .fr-toggle-knob { background: var(--fr-accent); border-color: var(--fr-accent); }
.fr-toggle[aria-checked="true"] .fr-toggle-knob::after { transform: translateX(20px); background: var(--fr-accent-ink); }
.fr-toggle[aria-checked="true"] .fr-toggle-state { color: var(--fr-text); }
.fr-toggle-state { min-width: 24px; text-align: left; }
.fr-select { position: relative; }
.fr-select-input { appearance: none; padding-right: var(--fr-space-8); cursor: pointer; }
.fr-select-chevron {
position: absolute;
right: var(--fr-space-3);
top: 50%;
width: 8px;
height: 8px;
border-right: 2px solid var(--fr-text-2);
border-bottom: 2px solid var(--fr-text-2);
transform: translateY(-75%) rotate(45deg);
pointer-events: none;
}
.fr-seg-field { display: flex; flex-direction: column; align-items: flex-start; gap: var(--fr-space-2); min-width: 0; }
.fr-gallery-tools .fr-seg-field { flex-direction: row; align-items: center; }
.fr-segmented {
display: inline-flex;
flex-wrap: wrap;
gap: var(--fr-space-half);
padding: var(--fr-space-half);
border-radius: var(--fr-radius-control);
background: var(--fr-layer-pressed);
max-width: 100%;
}
.fr-seg {
appearance: none;
border: 0;
min-height: 32px;
padding: 0 var(--fr-space-3);
border-radius: var(--fr-space-2);
background: transparent;
color: var(--fr-text-2);
font: inherit;
font-weight: var(--fr-fw-medium);
cursor: pointer;
transition: background var(--fr-dur-fast) var(--fr-ease-out), color var(--fr-dur-fast) var(--fr-ease-out);
}
.fr-seg:hover { color: var(--fr-text); }
.fr-seg[aria-checked="true"] { background: var(--fr-surface-2); color: var(--fr-text); box-shadow: var(--fr-shadow-1); }
@media (pointer: coarse) {
.fr-btn, .fr-input, .fr-number-step { min-height: 44px; }
.fr-seg, .fr-tab { min-height: 44px; }
}
/* ---- Instruments ---------------------------------------------------------- */
.fr-instrument { display: flex; flex-direction: column; gap: var(--fr-space-3); min-width: 0; }
.fr-instrument-label { margin: 0; color: var(--fr-text); font-weight: var(--fr-fw-strong); }
.fr-gauge { display: flex; flex-direction: column; align-items: center; gap: var(--fr-space-1); }
.fr-gauge-svg { display: block; width: 100%; max-width: 200px; height: auto; overflow: visible; }
.fr-gauge-track { fill: none; stroke: var(--fr-track); stroke-width: 8; stroke-linecap: round; }
.fr-gauge-value { fill: none; stroke-width: 8; stroke-linecap: round; }
.fr-gauge-zone { fill: none; stroke-width: 3; stroke-linecap: butt; }
.fr-gauge-tick { stroke: var(--fr-text-3); stroke-width: 1.2; }
.fr-gauge-needle { stroke: var(--fr-needle); stroke-width: 2.5; stroke-linecap: round; }
.fr-gauge-hub { fill: var(--fr-needle); }
.fr-tone-stroke-accent { stroke: var(--fr-accent); }
.fr-tone-stroke-neutral { stroke: var(--fr-text-3); }
.fr-tone-stroke-good { stroke: var(--fr-good); }
.fr-tone-stroke-warn { stroke: var(--fr-warn); }
.fr-tone-stroke-crit { stroke: var(--fr-crit); }
.fr-tone-stroke-info { stroke: var(--fr-info); }
.fr-gauge-readout { margin: 0; display: flex; align-items: baseline; gap: var(--fr-space-1); }
.fr-gauge-figure { font-family: var(--fr-font-mono); font-size: var(--fr-fs-2xl); font-weight: var(--fr-fw-medium); font-variant-numeric: tabular-nums; line-height: var(--fr-lh-display); color: var(--fr-text); }
.fr-gauge-unit { color: var(--fr-text-2); font-weight: var(--fr-fw-medium); }
.fr-gauge-range { margin: 0; width: 100%; max-width: 200px; display: flex; justify-content: space-between; color: var(--fr-text-3); font-size: var(--fr-fs-xs); font-variant-numeric: tabular-nums; }
.fr-scope { display: flex; flex-direction: column; gap: var(--fr-space-3); }
.fr-scope-screen {
position: relative;
aspect-ratio: 5 / 3;
max-height: 320px;
border-radius: var(--fr-radius-control);
background: var(--fr-surface-sunk);
border: 1px solid var(--fr-border);
overflow: hidden;
}
.fr-scope-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.fr-scope-div { stroke: var(--fr-chart-grid); stroke-width: 1; vector-effect: non-scaling-stroke; }
.fr-scope-axis { stroke: var(--fr-border-3); }
.fr-scope-trace { fill: none; stroke-width: 2; stroke-linejoin: round; }
.fr-scope-scale,
.fr-scope-time {
position: absolute;
bottom: var(--fr-space-2);
padding: var(--fr-space-half) var(--fr-space-2);
border-radius: var(--fr-space-1);
background: var(--fr-surface-2);
color: var(--fr-text-2);
font-family: var(--fr-font-mono);
font-size: var(--fr-fs-xs);
}
.fr-scope-scale { left: var(--fr-space-2); }
.fr-scope-time { right: var(--fr-space-2); }
.fr-scope-chs { margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: var(--fr-space-2) var(--fr-space-6); }
.fr-scope-ch { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--fr-space-2); font-size: var(--fr-fs-xs); }
.fr-scope-ch-name { color: var(--fr-text); font-weight: var(--fr-fw-medium); }
.fr-scope-meas { color: var(--fr-text-2); font-family: var(--fr-font-mono); font-variant-numeric: tabular-nums; }
.fr-wave { display: flex; flex-direction: column; gap: var(--fr-space-1); }
.fr-wave-lane {
display: grid;
grid-template-columns: 48px minmax(0, 1fr) 48px;
align-items: center;
gap: var(--fr-space-3);
padding: var(--fr-space-1) var(--fr-space-2);
border-radius: var(--fr-space-2);
background: var(--fr-surface-sunk);
}
.fr-wave-name { font-family: var(--fr-font-mono); font-size: var(--fr-fs-xs); color: var(--fr-text); }
.fr-wave-svg { display: block; width: 100%; height: 24px; }
.fr-wave-trace { fill: none; stroke-width: 2; }
.fr-wave-level { font-size: var(--fr-fs-xs); color: var(--fr-text-2); text-align: right; }
.fr-wave-ruler {
margin: 0;
display: flex;
justify-content: space-between;
padding: 0 64px 0 72px;
color: var(--fr-text-3);
font-size: var(--fr-fs-xs);
font-variant-numeric: tabular-nums;
}
.fr-led {
display: grid;
grid-template-columns: 16px minmax(0, 1fr) auto;
align-items: center;
gap: var(--fr-space-3);
min-height: 32px;
}
.fr-led-lamp {
width: 16px;
height: 16px;
border-radius: var(--fr-radius-full);
border: 2px solid var(--fr-text-3);
background: transparent;
transition: background var(--fr-dur-base) var(--fr-ease-out), box-shadow var(--fr-dur-base) var(--fr-ease-out);
}
.fr-led-unknown .fr-led-lamp { border-style: dashed; }
.fr-led-on.fr-tone-mark-good .fr-led-lamp { background: var(--fr-good); border-color: var(--fr-good); box-shadow: 0 0 10px -1px var(--fr-good); }
.fr-led-on.fr-tone-mark-warn .fr-led-lamp { background: var(--fr-warn); border-color: var(--fr-warn); box-shadow: 0 0 10px -1px var(--fr-warn); }
.fr-led-on.fr-tone-mark-crit .fr-led-lamp { background: var(--fr-crit); border-color: var(--fr-crit); box-shadow: 0 0 10px -1px var(--fr-crit); }
.fr-led-on.fr-tone-mark-info .fr-led-lamp,
.fr-led-on.fr-tone-mark-accent .fr-led-lamp { background: var(--fr-info); border-color: var(--fr-info); box-shadow: 0 0 10px -1px var(--fr-info); }
.fr-led-label { color: var(--fr-text); }
.fr-led-word { color: var(--fr-text-2); font-size: var(--fr-fs-xs); font-weight: var(--fr-fw-medium); }
.fr-led-on .fr-led-word { color: var(--fr-text); }
/* A verdict says itself three ways: the lamp's colour, a mark and its word. A failure is
the loudest thing on the card: a filled --crit lamp with a glow and a --crit badge. */
.fr-led-verdict .fr-led-word { font-size: var(--fr-fs-sm); font-weight: var(--fr-fw-strong); }
.fr-led-verdict.fr-verdict-pass .fr-led-lamp { background: var(--fr-good); border-color: var(--fr-good); }
.fr-led-verdict.fr-verdict-fail .fr-led-lamp { background: var(--fr-crit); border-color: var(--fr-crit); box-shadow: 0 0 10px -1px var(--fr-crit); }
.fr-led-verdict.fr-verdict-warn .fr-led-lamp { background: var(--fr-warn); border-color: var(--fr-warn); }
.fr-led-verdict.fr-verdict-unknown .fr-led-lamp { border-style: dashed; }
.fr-verdict-mark { margin-right: var(--fr-space-1); }
/* A readout's verdict in its tone: the value's own colour would otherwise win. */
.fr-kv-value.fr-tone-text-crit { color: var(--fr-crit-text); font-weight: var(--fr-fw-strong); }
.fr-kv-value.fr-tone-text-good { color: var(--fr-good-text); font-weight: var(--fr-fw-strong); }
.fr-kv-value.fr-tone-text-warn { color: var(--fr-warn-text); font-weight: var(--fr-fw-strong); }
.fr-knob-field { display: flex; flex-direction: column; align-items: center; gap: var(--fr-space-2); }
.fr-knob {
width: 112px;
height: 112px;
border-radius: var(--fr-radius-full);
cursor: ns-resize;
touch-action: none;
}
.fr-knob-svg { display: block; width: 100%; height: 100%; }
.fr-knob-track { fill: none; stroke: var(--fr-track); stroke-width: 4; stroke-linecap: round; }
.fr-knob-fill { fill: none; stroke: var(--fr-accent); stroke-width: 4; stroke-linecap: round; }
.fr-knob-body {
fill: var(--fr-knob-lo);
stroke: var(--fr-knob-hi);
stroke-width: 2;
}
.fr-knob-pointer { stroke: var(--fr-accent); stroke-width: 3; stroke-linecap: round; }
.fr-knob-value { font-family: var(--fr-font-mono); font-size: var(--fr-fs-lg); font-variant-numeric: tabular-nums; color: var(--fr-text); }
.fr-reg { display: flex; flex-direction: column; gap: var(--fr-space-3); min-width: 0; }
.fr-reg-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--fr-space-2) var(--fr-space-4); }
.fr-reg-name { font-weight: var(--fr-fw-strong); color: var(--fr-text); }
.fr-reg-addr { font-family: var(--fr-font-mono); font-size: var(--fr-fs-xs); color: var(--fr-text-2); }
.fr-reg-value { margin-left: auto; font-family: var(--fr-font-mono); font-size: var(--fr-fs-lg); color: var(--fr-text); font-variant-numeric: tabular-nums; }
.fr-reg-scroll { overflow-x: auto; min-width: 0; padding-bottom: var(--fr-space-1); border-radius: var(--fr-space-1); }
.fr-reg-grid { display: grid; gap: var(--fr-space-half); }
.fr-reg-8 { grid-template-columns: repeat(8, minmax(40px, 1fr)); min-width: 336px; }
.fr-reg-16 { grid-template-columns: repeat(16, minmax(36px, 1fr)); min-width: 608px; }
.fr-reg-32 { grid-template-columns: repeat(32, minmax(32px, 1fr)); min-width: 1088px; }
.fr-reg-bit {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--fr-space-half);
padding: var(--fr-space-1) 0;
border-radius: var(--fr-space-1);
background: var(--fr-surface-sunk);
font-family: var(--fr-font-mono);
}
.fr-reg-bitno { font-size: var(--fr-fs-xs); color: var(--fr-text-3); }
.fr-reg-bitval { font-size: var(--fr-fs-sm); color: var(--fr-text-2); }
.fr-reg-set { background: var(--fr-accent-soft); }
.fr-reg-set .fr-reg-bitval { color: var(--fr-accent-text); font-weight: var(--fr-fw-strong); }
.fr-reg-field {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--fr-space-half);
min-width: 0;
padding: var(--fr-space-1);
border-top: 2px solid var(--fr-border-3);
font-size: var(--fr-fs-xs);
}
.fr-reg-field-name { color: var(--fr-text); font-weight: var(--fr-fw-medium); }
.fr-reg-field-val { color: var(--fr-text-2); font-family: var(--fr-font-mono); }
.fr-svg { display: block; width: 100%; height: auto; max-width: 480px; }
.fr-svg-text { font-family: var(--fr-font-ui); font-size: var(--fr-fs-sm); }
.fr-svg-text-sm { font-size: var(--fr-fs-xs); }
/* ---- The gallery page ----------------------------------------------------- */
.fr-gallery {
min-height: 100vh;
background: var(--fr-bg);
color: var(--fr-text);
font-family: var(--fr-font-ui);
font-size: var(--fr-fs-sm);
line-height: var(--fr-lh-body);
letter-spacing: var(--fr-tracking);
}
.fr-gallery-head {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--fr-space-3) var(--fr-space-6);
max-width: 1200px;
margin: 0 auto;
padding: var(--fr-space-6) var(--fr-space-6) 0;
}
.fr-gallery-brand { display: inline-flex; align-items: center; gap: var(--fr-space-2); }
.fr-gallery-mark { width: 16px; height: 16px; border-radius: var(--fr-space-1); background: var(--fr-flow); }
.fr-gallery-kicker { color: var(--fr-text-2); font-weight: var(--fr-fw-medium); }
.fr-gallery-tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--fr-space-3); }
.fr-gallery-main {
max-width: 1200px;
margin: 0 auto;
padding: var(--fr-space-8) var(--fr-space-6) var(--fr-space-12);
display: flex;
flex-direction: column;
gap: var(--fr-space-12);
}
.fr-gallery-section { display: flex; flex-direction: column; gap: var(--fr-space-6); min-width: 0; scroll-margin-top: var(--fr-space-6); }
.fr-gallery-hero { gap: var(--fr-space-4); }
.fr-gallery-said { margin: 0; color: var(--fr-text-2); min-height: 24px; }
/* ---- Section, code, text input, progress range (kit) ---------------------- */
.fr-section { display: flex; flex-direction: column; gap: var(--fr-space-4); min-width: 0; }
.fr-section-head { display: flex; flex-direction: column; gap: var(--fr-space-1); min-width: 0; }
.fr-section-title {
margin: 0;
color: var(--fr-text);
font-size: var(--fr-fs-lg);
font-weight: var(--fr-fw-strong);
line-height: var(--fr-lh-heading);
letter-spacing: var(--fr-tracking);
}
.fr-section-desc { margin: 0; color: var(--fr-text-2); font-size: var(--fr-fs-sm); }
.fr-section-body { display: flex; flex-direction: column; gap: var(--fr-gap-card); min-width: 0; }
.fr-code {
margin: 0;
max-height: 320px;
overflow: auto;
padding: var(--fr-space-3);
border: 1px solid var(--fr-border);
border-radius: var(--fr-radius-control);
background: var(--fr-surface-sunk);
color: var(--fr-text);
font-family: var(--fr-font-mono);
font-size: var(--fr-fs-xs);
line-height: var(--fr-lh-body);
white-space: pre;
}
.fr-code:focus-visible { outline: 2px solid var(--fr-focus); outline-offset: 2px; }
.fr-textarea { height: auto; min-height: 96px; padding: var(--fr-space-2) var(--fr-space-3); resize: vertical; line-height: var(--fr-lh-body); }
.fr-input-swatch { width: 64px; padding: var(--fr-space-1); }
.fr-progress-track { position: relative; }
.fr-progress-target {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
background: var(--fr-text);
}
.fr-progress-vertical { display: flex; justify-content: flex-start; }
.fr-progress-vertical .fr-progress-track { width: 16px; height: 120px; display: flex; flex-direction: column; justify-content: flex-end; }
.fr-progress-vertical .fr-progress-fill { width: 100%; transition: height var(--fr-dur-slow) var(--fr-ease-out); }
.fr-progress-vertical .fr-progress-target { top: auto; left: 0; right: 0; width: auto; height: 2px; }
/* ---- The app view --------------------------------------------------------- */
.fr-app {
background: var(--fr-bg);
color: var(--fr-text);
font-family: var(--fr-font-ui);
font-size: var(--fr-fs-sm);
line-height: var(--fr-lh-body);
letter-spacing: var(--fr-tracking);
padding: var(--fr-space-6);
display: flex;
flex-direction: column;
gap: var(--fr-space-4);
min-width: 0;
}
.fr-app-body { min-width: 0; max-width: 1280px; width: 100%; margin: 0 auto; }
/* A reading alone in its section is a tile, not a banner: on a wide page a KPI tile, a lamp
or a gauge keeps about the width it has in a row of four (OpenUI's KPI row), never the
page's. On a phone every card is the column's width, so their edges line up. */
@media (min-width: 600px) {
.fr-app .fr-section-body > .fr-card:only-child:has(.fr-stat, .fr-led, .fr-gauge) {
width: 100%;
max-width: 20rem;
}
.fr-app .fr-section-body > .fr-card:only-child:has(.fr-image) {
width: 100%;
max-width: 36rem;
}
}
/* An image in an app is a figure, not a wallpaper: its title above it, as a card's head,
and at most 32 rem wide however few pixels it has (a 64 px test pattern stays crisp). */
.fr-app .fr-image-img { width: 100%; max-width: 32rem; }
.fr-app .fr-image-caption {
order: -1;
color: var(--fr-text);
font-size: var(--fr-fs-sm);
font-weight: var(--fr-fw-strong);
line-height: var(--fr-lh-heading);
}
/* Everything an app can focus shows the kit's ring, the editor's own drawings (a test
report's controls) included. */
.fr-app :focus-visible,
.fr-runonly :focus-visible {
outline: 2px solid var(--fr-focus);
outline-offset: 2px;
box-shadow: none;
}
.fr-app-note { margin: 0; color: var(--fr-text-2); }
.fr-app-skeleton { display: flex; flex-direction: column; gap: var(--fr-space-2); min-width: 0; }
.fr-problems {
max-width: 1280px;
width: 100%;
margin: 0 auto;
padding: var(--fr-space-3) var(--fr-space-4);
border: 1px solid var(--fr-warn);
border-radius: var(--fr-radius-control);
background: var(--fr-warn-soft);
}
.fr-problems-title { margin: 0; color: var(--fr-warn-text); font-weight: var(--fr-fw-strong); }
.fr-problems-list { margin: var(--fr-space-2) 0 0; padding-left: var(--fr-space-5); color: var(--fr-text); }
.fr-problem-at { font-family: var(--fr-font-mono); color: var(--fr-text-soft); }
.fr-problem-hint { display: block; color: var(--fr-text-soft); }
.fr-error-hint { margin: 0; color: var(--fr-text-soft); font-size: var(--fr-fs-xs); overflow-wrap: break-word; }
.fr-figure,
.fr-image { margin: 0; display: flex; flex-direction: column; gap: var(--fr-space-2); min-width: 0; }
.fr-figure-title { margin: 0; color: var(--fr-text); font-weight: var(--fr-fw-medium); font-size: var(--fr-fs-sm); }
.fr-figure-body svg { width: 100%; height: 160px; display: block; }
.fr-image-img { max-width: 100%; height: auto; display: block; border-radius: var(--fr-radius-mark); image-rendering: pixelated; }
.fr-image-caption { color: var(--fr-text-2); font-size: var(--fr-fs-xs); }
.fr-kv-block { display: flex; flex-direction: column; gap: var(--fr-space-2); min-width: 0; }
.fr-kv-title { margin: 0; color: var(--fr-text); font-weight: var(--fr-fw-medium); }
.fr-gauge-block { display: flex; flex-direction: column; gap: var(--fr-space-2); min-width: 0; }
.fr-led-block.fr-lamp .fr-led-lamp { width: 24px; height: 24px; }
.fr-spacer { height: 0; }
.fr-spacer-inner { height: var(--fr-space-2); }
.fr-spacer-group { height: var(--fr-space-4); }
.fr-spacer-section { height: var(--fr-space-6); }
.fr-form { display: flex; flex-direction: column; gap: var(--fr-space-4); align-items: flex-start; min-width: 0; }
.fr-form > * { align-self: stretch; }
.fr-form > .fr-btn { align-self: flex-start; }
.fr-fieldset { margin: 0; padding: 0; border: 0; min-width: 0; display: flex; flex-direction: column; gap: var(--fr-space-2); }
.fr-range-pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--fr-space-3); }
.fr-multi { display: flex; flex-direction: column; gap: var(--fr-space-2); }
.fr-empty-block { display: flex; flex-direction: column; align-items: center; gap: var(--fr-space-3); }
.fr-app-toolbar {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: var(--fr-space-3) var(--fr-space-6);
padding: var(--fr-space-3) var(--fr-space-6);
border-bottom: 1px solid var(--fr-border);
background: var(--fr-surface-1);
color: var(--fr-text);
font-family: var(--fr-font-ui);
font-size: var(--fr-fs-sm);
}
.fr-app-toolbar .fr-seg-field,
.fr-app-toolbar .fr-toggle-field { flex-direction: row; align-items: center; gap: var(--fr-space-3); }
.fr-app-actions { display: flex; flex-wrap: wrap; gap: var(--fr-space-2); align-items: center; }
.fr-app-runonly { text-decoration: none; }
/* ---- The agent's app: its reply streaming in, and the host's confirmations ----- */
.fr-app-streaming {
margin: 0;
padding: var(--fr-space-2) var(--fr-space-6);
color: var(--fr-text-2);
font-family: var(--fr-font-ui);
font-size: var(--fr-fs-sm);
}
.fr-confirm {
max-width: 1280px;
width: 100%;
margin: 0 auto;
padding: var(--fr-space-3) var(--fr-space-4);
border: 1px solid var(--fr-warn);
border-radius: var(--fr-radius-control);
background: var(--fr-warn-soft);
display: flex;
flex-direction: column;
gap: var(--fr-space-2);
}
.fr-confirm-title { margin: 0; color: var(--fr-warn-text); font-weight: var(--fr-fw-strong); }
.fr-confirm-body { margin: 0; color: var(--fr-text); overflow-wrap: anywhere; }
.fr-confirm-list {
margin: 0;
padding-left: var(--fr-space-5);
color: var(--fr-text);
font-family: var(--fr-font-mono);
font-size: var(--fr-fs-xs);
overflow-wrap: anywhere;
}
.fr-confirm-actions { display: flex; flex-wrap: wrap; gap: var(--fr-space-2); }
.fr-share,
.fr-app-editor {
display: flex;
flex-direction: column;
gap: var(--fr-space-2);
padding: var(--fr-space-3) var(--fr-space-6);
border-bottom: 1px solid var(--fr-border);
background: var(--fr-surface-1);
color: var(--fr-text);
font-family: var(--fr-font-ui);
font-size: var(--fr-fs-sm);
}
.fr-share-note { margin: 0; color: var(--fr-text-2); }
.fr-app-passes .fr-field { max-width: 360px; }
.fr-share-note code { font-family: var(--fr-font-mono); font-size: var(--fr-fs-xs); font-variant-ligatures: none; color: var(--fr-text); overflow-wrap: anywhere; }
.fr-share-refused { margin: 0; color: var(--fr-crit-text); font-weight: var(--fr-fw-medium); white-space: pre-line; }
.fr-app-source { min-height: 200px; font-family: var(--fr-font-mono); font-size: var(--fr-fs-xs); }
/* ---- Why each component, the checks the app keeps, and the two layouts --------- */
.fr-app-why > summary { cursor: pointer; font-weight: var(--fr-fw-medium); color: var(--fr-text); }
.fr-why-list {
margin: var(--fr-space-2) 0 0;
padding-left: var(--fr-space-5);
display: flex;
flex-direction: column;
gap: var(--fr-space-1);
color: var(--fr-text-2);
}
.fr-why-list b { color: var(--fr-text); font-weight: var(--fr-fw-medium); }
.fr-why-off { color: var(--fr-warn-text); }
.fr-check-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--fr-space-2); }
.fr-check { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--fr-space-1) var(--fr-space-3); }
.fr-check code { font-family: var(--fr-font-mono); font-size: var(--fr-fs-xs); font-variant-ligatures: none; color: var(--fr-text); overflow-wrap: anywhere; }
.fr-check-why { color: var(--fr-text-2); }
.fr-check-broken code { color: var(--fr-crit-text); }
.fr-check-summary { margin: 0; font-weight: var(--fr-fw-medium); color: var(--fr-text); }
.fr-check-summary-broken { color: var(--fr-crit-text); }
.fr-app-ab { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--fr-space-4); }
.fr-app-ab-col {
display: flex;
flex-direction: column;
gap: var(--fr-space-2);
min-width: 0;
padding: var(--fr-space-3);
border: 1px solid var(--fr-border);
border-radius: var(--fr-radius-card);
background: var(--fr-surface-sunk);
}
.fr-app-ab-col .fr-app { padding: var(--fr-space-3); }
.fr-app-ab-name { margin: 0; font-weight: var(--fr-fw-strong); color: var(--fr-text); }
/* Run-only mode is the page: in flow, so the page (not a box in it) scrolls, and the
editor behind it is hidden rather than covered. */
.fr-runonly {
min-height: 100vh;
background: var(--fr-bg);
color: var(--fr-text);
font-family: var(--fr-font-ui);
}
.fr-runonly-bar {
display: flex;
justify-content: flex-end;
max-width: 1328px;
margin: 0 auto;
padding: var(--fr-space-3) var(--fr-space-6) 0;
}
.fr-runonly-bar .fr-btn { text-decoration: none; }
.fr-runonly > .fr-placeholder { max-width: 1280px; margin: var(--fr-space-4) auto 0; }
@media (max-width: 479px) {
.fr-grid-narrow,
.fr-grid-medium,
.fr-grid-wide { grid-template-columns: minmax(0, 1fr); }
.fr-h1,
.fr-stat-figure,
.fr-gauge-figure { font-size: var(--fr-fs-xl); }
.fr-gallery-head { padding: var(--fr-space-4) var(--fr-space-4) 0; }
.fr-gallery-main { padding: var(--fr-space-6) var(--fr-space-4) var(--fr-space-10); gap: var(--fr-space-10); }
.fr-card { padding: var(--fr-space-4); }
.fr-donut { justify-content: center; }
.fr-app { padding: var(--fr-space-4); }
.fr-app-toolbar,
.fr-share,
.fr-app-editor { padding-left: var(--fr-space-4); padding-right: var(--fr-space-4); }
.fr-runonly-bar { padding: var(--fr-space-3) var(--fr-space-4) 0; }
.fr-app-ab { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
.fr-skel { animation: none; opacity: 0.6; }
.fr-tip { transition: none; }
}