/* 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; } }