@ksp-gonogo/ui-kit 1.0.0-rc.21 → 1.0.0-rc.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/{chunk-GGJES6XH.js → chunk-JNKL3HA3.js} +307 -263
- package/dist/chunk-JNKL3HA3.js.map +1 -0
- package/dist/{chunk-IZZF25H2.js → chunk-SPBHQUHI.js} +1 -1
- package/dist/chunk-SPBHQUHI.js.map +1 -0
- package/dist/gridUnits.d.ts +4 -4
- package/dist/gridUnits.js +1 -1
- package/dist/guards.d.ts +2 -1
- package/dist/guards.js.map +1 -1
- package/dist/index.d.ts +938 -328
- package/dist/index.js +1388 -798
- package/dist/index.js.map +1 -1
- package/dist/reads-index.json +1559 -0
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/dist/tokens.css +2 -0
- package/dist/{units-Cl-8ZdJc.d.ts → units-DZYIWBWP.d.ts} +32 -6
- package/package.json +5 -12
- package/dist/chunk-GGJES6XH.js.map +0 -1
- package/dist/chunk-IZZF25H2.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../theme/src/DefaultThemeProvider.tsx","../../theme/src/defaultDarkTheme.ts","../src/ActionMenu.tsx","../src/Combobox.tsx","../src/EmptyState.tsx","../src/AudioInputPicker.tsx","../src/StatusIndicator.tsx","../src/useAudioInput.ts","../src/audioCaptureSupport.ts","../src/Switch.tsx","../src/AugmentSettingsPanel.tsx","../src/AutoEmptyState.tsx","../src/anchoredPosition.ts","../src/Band.tsx","../src/Block.tsx","../src/SubjectHeading.tsx","../src/Box.tsx","../src/ButtonGroup.tsx","../src/Card.tsx","../src/CommandButton/CommandButton.tsx","../src/CommandDelay/commandOutcomes.ts","../src/FitLabelButton.tsx","../src/CommandButton/InFlightFace.tsx","../src/Spinner.tsx","../src/CommandDelay/CommandGroup.tsx","../src/CommandDelay/SignalDelayBadge.tsx","../src/CommandDelay/useRailEntry.ts","../src/ComposerBar.tsx","../src/ConsoleFrame.tsx","../src/Console.tsx","../src/ContainerBreak.tsx","../src/configEqual.ts","../src/DataKeyPicker.tsx","../src/DataLine.tsx","../src/DataTable.tsx","../src/inlineOverflow.tsx","../src/Dial.tsx","../src/Disclosure.tsx","../src/DivergingBar.tsx","../src/Divider.tsx","../src/ExpandableText.tsx","../src/ephemerisFigure.ts","../src/Fill.tsx","../src/FilterList.tsx","../src/Floating.tsx","../src/Grid.tsx","../src/HeldBadge.tsx","../src/HoverCard.tsx","../src/Inline.tsx","../src/JogWheel.tsx","../src/LineGraph.tsx","../src/reckoningBasisPhrase.ts","../src/LockMark.tsx","../src/MarkerIcons.tsx","../src/Meter.tsx","../src/BarTrack.tsx","../src/MissionDate.tsx","../src/MissionDateField.tsx","../src/Modal.tsx","../src/ModalSaveBar.tsx","../src/Notice.tsx","../src/ReadFrameControl.tsx","../src/ReadOnlyField.tsx","../src/Readout.tsx","../src/Row.tsx","../src/reckoningMarkDraw.tsx","../src/resourceColor.ts","../src/SelectableRow.tsx","../src/Slider.tsx","../src/Stat.tsx","../src/StatContributions.tsx","../src/Stepper.tsx","../src/StreamStatusBadge.tsx","../src/sizeDelta.ts","../src/Tabs.tsx","../src/Tape.tsx","../src/TextField.tsx","../src/TrajectoryWithheldNote.tsx","../src/Truncate.tsx","../src/UnitInput.tsx","../src/usePrefersReducedMotion.ts","../src/vesselMark.tsx","../src/WidgetMeters.tsx","../src/WidgetScope.tsx","../src/widgetDeclaredTopics.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { ThemeProvider } from \"styled-components\";\nimport { defaultDarkTheme } from \"./defaultDarkTheme\";\n\n/**\n * Props for {@link DefaultThemeProvider}.\n *\n * @category Theme\n */\nexport interface DefaultThemeProviderProps {\n children?: ReactNode;\n}\n\n/**\n * Mounts {@link defaultDarkTheme} in a styled-components `ThemeProvider`, the\n * same theme the app mounts.\n *\n * Kit components read `theme.colors` and throw without a theme in scope, so\n * wrap anything that renders them outside the app (a test, a story, a\n * standalone page) in this. The theme's values are CSS variables, which\n * `@ksp-gonogo/ui-kit/tokens.css` defines: import that sheet once, or every\n * colour and size is unset.\n *\n * @example\n * ```tsx\n * import \"@ksp-gonogo/ui-kit/tokens.css\";\n * import { value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { DefaultThemeProvider, Meter } from \"@ksp-gonogo/ui-kit\";\n * import { render } from \"@testing-library/react\";\n *\n * render(\n * <DefaultThemeProvider>\n * <Meter label=\"Charge\" value={value(\"ratio\", 0.4)} />\n * </DefaultThemeProvider>,\n * );\n * ```\n *\n * @category Theme\n */\nexport function DefaultThemeProvider({\n children,\n}: Readonly<DefaultThemeProviderProps>) {\n return <ThemeProvider theme={defaultDarkTheme}>{children}</ThemeProvider>;\n}\n","import type { UiKitTheme } from \"./theme\";\n\n/**\n * `default-dark`: the built-in mission-control theme, and the one the app\n * mounts. Every colour and size is a CSS variable string (such as\n * `var(--color-text-primary)`), so responsive overrides such as larger type on\n * a touch screen still apply when a component reads the theme. The variables\n * are defined by `@ksp-gonogo/ui-kit/tokens.css`, which has to be imported\n * once for any of them to have a value.\n *\n * @category Theme\n */\nexport const defaultDarkTheme: UiKitTheme = {\n colors: {\n text: {\n primary: \"var(--color-text-primary)\",\n muted: \"var(--color-text-muted)\",\n dim: \"var(--color-text-dim)\",\n faint: \"var(--color-text-faint)\",\n inverse: \"var(--color-text-inverse)\",\n },\n surface: {\n app: \"var(--color-surface-app)\",\n panel: \"var(--color-surface-panel)\",\n raised: \"var(--color-surface-raised)\",\n sunken: \"var(--color-surface-sunken)\",\n },\n border: {\n subtle: \"var(--color-border-subtle)\",\n strong: \"var(--color-border-strong)\",\n },\n accent: {\n fg: \"var(--color-accent-fg)\",\n bg: \"var(--color-accent-bg)\",\n },\n focus: \"var(--color-focus)\",\n },\n typography: {\n family: {\n mono: \"var(--font-family-mono)\",\n },\n size: {\n xs: \"var(--font-size-xs)\",\n sm: \"var(--font-size-sm)\",\n base: \"var(--font-size-base)\",\n lg: \"var(--font-size-lg)\",\n },\n weight: {\n regular: 400,\n bold: 700,\n },\n letterSpacing: {\n tight: \"0.05em\",\n label: \"0.1em\",\n wide: \"0.15em\",\n body: \"0\",\n },\n },\n borders: {\n subtle: \"1px solid var(--color-border-subtle)\",\n strong: \"1px solid var(--color-border-strong)\",\n },\n};\n","import type { CSSProperties, ReactNode } from \"react\";\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { groupComboboxOptions } from \"./Combobox\";\nimport { EmptyState } from \"./EmptyState\";\nimport { focusRingInset } from \"./focusRing\";\n\n/**\n * One action in an {@link ActionMenu}.\n *\n * @category Floating\n */\nexport interface ActionMenuItem {\n /** Stable id handed back to `onSelect`. */\n key: string;\n /** The item's visible text, and its accessible name unless `ariaLabel` is set. */\n label: string;\n /** Optional grouping header; ungrouped items collapse into one flat list. */\n group?: string;\n /** Present but not currently firable: rendered dimmed and inert, still reachable. */\n disabled?: boolean;\n /** Overrides the accessible name when `label` alone is ambiguous out of context. */\n ariaLabel?: string;\n}\n\n/**\n * Props for {@link ActionMenu}.\n *\n * @category Floating\n */\nexport interface ActionMenuProps {\n /** The actions, in order. Items sharing a `group` are listed under that heading. */\n items: readonly ActionMenuItem[];\n /** Fired with the item's `key`. A disabled item never fires. */\n onSelect: (key: string) => void;\n /** Escape, Tab, or an outside pointer press. The caller restores trigger focus. */\n onDismiss: () => void;\n /** Accessible name for the menu itself: `role=\"menu\"` needs one. */\n ariaLabel: string;\n /** Positioning (the caller owns anchoring); merged over the menu's own frame styles. */\n style?: CSSProperties;\n /** Rendered above the items: a title row, a pending indicator, a delay readout. */\n header?: ReactNode;\n /** Rendered below the items. */\n footer?: ReactNode;\n /** Shown in place of the items when there are none. Defaults to \"No actions\". */\n emptyLabel?: string;\n /** Heading for items with no `group` when other items have one. Defaults to \"Other\". */\n otherLabel?: string;\n}\n\n/**\n * A menu of actions to fire: the APG `menu` pattern\n * (https://www.w3.org/WAI/ARIA/apg/patterns/menu/).\n *\n * It differs from {@link ComboboxListbox}, a `listbox` for choosing a value:\n * this is a `menu` for invoking a command, so its items are real `<button>`s,\n * it owns keyboard focus while open, and selecting an item fires it.\n *\n * Keyboard: ArrowDown and ArrowUp walk the items, Home and End jump to the\n * ends, Enter or Space fires the focused item, and Escape or Tab dismisses. A\n * pointer press outside the menu dismisses it. Focus moves to the first item\n * on open; restore focus to the trigger in `onDismiss`. A disabled item stays\n * reachable by keyboard, so it reads as unavailable rather than missing.\n *\n * The menu is absolutely positioned and draws its own raised surface; place\n * it with `style`, or put it in a {@link Floating} with\n * `style={{ position: \"static\" }}`. Render it only while open.\n *\n * @example\n * ```tsx\n * {menuAnchor && (\n * <Floating anchor={menuAnchor}>\n * <ActionMenu\n * items={[\n * { key: \"deploy\", label: \"Deploy chute\" },\n * { key: \"cut\", label: \"Cut chute\", disabled: !deployed },\n * ]}\n * ariaLabel=\"Parachute actions\"\n * style={{ position: \"static\" }}\n * onSelect={(key) => {\n * fire(key);\n * close();\n * }}\n * onDismiss={close}\n * />\n * </Floating>\n * )}\n * ```\n *\n * @category Floating\n */\nexport function ActionMenu({\n items,\n onSelect,\n onDismiss,\n ariaLabel,\n style,\n header,\n footer,\n emptyLabel = \"No actions\",\n otherLabel = \"Other\",\n}: Readonly<ActionMenuProps>) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const menuId = useId();\n const [activeIndex, setActiveIndex] = useState(0);\n\n const groups = groupComboboxOptions(items, otherLabel);\n const flat = groups.flatMap(([, bucket]) => bucket);\n\n /*\n * Roving DOM focus, so the real buttons handle Enter/Space. `itemCount` is a\n * dependency because a menu can open before its items arrive; while empty the\n * container itself takes focus so Escape still has somewhere to bubble from.\n */\n const itemCount = flat.length;\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n if (itemCount === 0) {\n container.focus();\n return;\n }\n const buttons =\n container.querySelectorAll<HTMLButtonElement>('[role=\"menuitem\"]');\n buttons[activeIndex]?.focus();\n }, [activeIndex, itemCount]);\n\n // Bound on pointerdown, not click, so an outside press dismisses before it can activate anything else.\n useEffect(() => {\n const onPointerDown = (e: PointerEvent) => {\n const container = containerRef.current;\n if (\n container &&\n e.target instanceof Node &&\n !container.contains(e.target)\n ) {\n onDismiss();\n }\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n }, [onDismiss]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n onDismiss();\n return;\n }\n // A menu does not trap Tab: it dismisses and focus continues past the trigger.\n if (e.key === \"Tab\") {\n onDismiss();\n return;\n }\n if (flat.length === 0) return;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setActiveIndex((i) => Math.min(i + 1, flat.length - 1));\n break;\n case \"ArrowUp\":\n e.preventDefault();\n setActiveIndex((i) => Math.max(i - 1, 0));\n break;\n case \"Home\":\n e.preventDefault();\n setActiveIndex(0);\n break;\n case \"End\":\n e.preventDefault();\n setActiveIndex(flat.length - 1);\n break;\n }\n },\n [flat.length, onDismiss],\n );\n\n return (\n <Menu\n ref={containerRef}\n // A menu with no menuitem children is invalid ARIA, so the empty state is a plain labelled region.\n role={itemCount > 0 ? \"menu\" : undefined}\n aria-label={ariaLabel}\n onKeyDown={onKeyDown}\n // Programmatic focus only, for the empty state; never a tab stop.\n tabIndex={-1}\n style={style}\n >\n {header}\n {flat.length === 0 ? (\n <EmptyState layout=\"fill\">{emptyLabel}</EmptyState>\n ) : (\n groups.map(([group, bucket], groupIndex) => (\n <div\n key={group}\n {...(groups.length > 1\n ? {\n role: \"group\",\n \"aria-labelledby\": `${menuId}-group-${groupIndex}`,\n }\n : { role: \"none\" })}\n >\n {/* A single bucket renders flat, without a header. */}\n {groups.length > 1 ? (\n <GroupHeader\n id={`${menuId}-group-${groupIndex}`}\n aria-hidden=\"true\"\n >\n {group}\n </GroupHeader>\n ) : null}\n {bucket.map((item) => {\n const index = flat.indexOf(item);\n return (\n <MenuItem\n key={item.key}\n type=\"button\"\n role=\"menuitem\"\n aria-label={item.ariaLabel}\n // aria-disabled, not `disabled`: a natively disabled button cannot take focus and drops out of the arrow-key walk.\n aria-disabled={item.disabled}\n $disabled={item.disabled}\n tabIndex={index === activeIndex ? 0 : -1}\n onFocus={() => setActiveIndex(index)}\n onClick={() => {\n if (!item.disabled) onSelect(item.key);\n }}\n >\n {item.label}\n </MenuItem>\n );\n })}\n </div>\n ))\n )}\n {footer}\n </Menu>\n );\n}\n\nconst Menu = styled.div`\n position: absolute;\n min-width: 180px;\n max-width: 280px;\n max-height: 280px;\n overflow-y: auto;\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-strong);\n border-radius: var(--radius-regular);\n padding: var(--inset-menu-list);\n z-index: var(--z-dropdown);\n`;\n\nconst GroupHeader = styled.div`\n font-size: var(--font-size-caption);\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n padding: var(--inset-menu-group-label);\n`;\n\nconst MenuItem = styled.button<{ $disabled?: boolean }>`\n display: block;\n width: 100%;\n text-align: left;\n border: none;\n background: transparent;\n color: var(--color-text-primary);\n font: inherit;\n font-size: var(--font-size-compact);\n padding: var(--inset-menu-item);\n cursor: pointer;\n\n &:hover[aria-disabled=\"false\"],\n &:hover:not([aria-disabled]) {\n background: var(--color-border-subtle);\n }\n\n ${focusRingInset}\n /* The background marks the focused row where the ring meets the menu border. */\n &:focus-visible {\n background: var(--color-border-subtle);\n }\n\n &[aria-disabled=\"true\"] {\n color: var(--color-text-faint);\n cursor: default;\n }\n`;\n","import type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { EmptyState } from \"./EmptyState\";\nimport { statusFill } from \"./tone\";\n\n/*\n * The combobox/listbox primitive. The pure filter/group/navigate functions and\n * the presentational `ComboboxListbox` are decoupled from any input surface, so\n * a caller can drive them from a literal `<input role=\"combobox\">` or from raw\n * keystrokes.\n */\n\n/**\n * A single selectable item in a combobox or listbox dropdown. Extend it to carry more per option.\n *\n * @category Form\n */\nexport interface ComboboxOption {\n /** Unique within the list; what a selection reports. */\n key: string;\n /** What the operator reads. Defaults to the option's key. */\n label?: string;\n /** The heading the option is listed under. Options with none share one bucket. */\n group?: string;\n}\n\n/**\n * Case-insensitive substring match against an option's label (falling back\n * to its key): the default filter every combobox consumer starts from.\n *\n * @category Form\n */\nexport function comboboxOptionMatches(\n option: ComboboxOption,\n query: string,\n): boolean {\n if (!query) return true;\n const q = query.toLowerCase();\n const label = option.label ?? option.key;\n return (\n label.toLowerCase().includes(q) || option.key.toLowerCase().includes(q)\n );\n}\n\n/**\n * Filters `options` against `query` using `matches`, by default a case-insensitive substring match on the label or key.\n *\n * @category Form\n */\nexport function filterComboboxOptions<Option extends ComboboxOption>(\n options: readonly Option[],\n query: string,\n matches: (option: Option, query: string) => boolean = comboboxOptionMatches,\n): Option[] {\n return options.filter((o) => matches(o, query));\n}\n\n/**\n * Buckets `options` by `.group` (default bucket `otherLabel`), sorted by\n * group name: the grouped-listbox shape combobox consumers render from.\n * Ungrouped callers (every option has no `group`) collapse to a single\n * `otherLabel` bucket, which renders as one flat list.\n *\n * @category Form\n */\nexport function groupComboboxOptions<Option extends ComboboxOption>(\n options: readonly Option[],\n otherLabel = \"Other\",\n): Array<[string, Option[]]> {\n const groups = new Map<string, Option[]>();\n for (const o of options) {\n const g = o.group ?? otherLabel;\n let bucket = groups.get(g);\n if (!bucket) {\n bucket = [];\n groups.set(g, bucket);\n }\n bucket.push(o);\n }\n return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b));\n}\n\n/**\n * Flattens grouped options back into render and navigation order: the order ArrowUp and ArrowDown walk, and what `activeIndex` indexes into.\n *\n * @category Form\n */\nexport function flattenComboboxGroups<Option extends ComboboxOption>(\n groups: ReadonlyArray<[string, Option[]]>,\n): Option[] {\n return groups.flatMap(([, items]) => items);\n}\n\n/**\n * Steps the active (highlighted) index by `delta` for one ArrowUp/ArrowDown\n * keypress, clamped to `[0, length - 1]`; never wraps, never goes negative.\n * Returns `-1` when there is nothing to select (`length === 0`).\n *\n * @category Form\n */\nexport function moveComboboxActiveIndex(\n current: number,\n delta: number,\n length: number,\n): number {\n if (length === 0) return -1;\n return Math.max(0, Math.min(current + delta, length - 1));\n}\n\n/**\n * Props for {@link ComboboxListbox}.\n *\n * @category Form\n */\nexport interface ComboboxListboxProps<Option extends ComboboxOption> {\n /** DOM id for the listbox element: pair with the owning input's `aria-controls`. */\n id: string;\n /** The options to show, bucketed by {@link groupComboboxOptions}. */\n groups: ReadonlyArray<[string, Option[]]>;\n /** The same options in navigation order, from {@link flattenComboboxGroups}. */\n flatOptions: readonly Option[];\n /** Index into `flatOptions` of the currently highlighted item, or `-1` for none. */\n activeIndex: number;\n /** The currently committed value (distinct from `activeIndex`'s in-progress highlight), if any. */\n selectedKey?: string | null;\n /** The DOM id of an option, for the owning input's `aria-activedescendant`. */\n getOptionId: (key: string) => string;\n /** Called with a `flatOptions` index when the pointer moves over an option. */\n onHoverIndex: (index: number) => void;\n /** Called with an option's key when it is clicked or tapped. */\n onSelectKey: (key: string) => void;\n /** Custom item body, such as a label with a trailing unit. Defaults to the option's label, or its key when it has none. */\n renderItem?: (option: Option) => ReactNode;\n /** Shown when `flatOptions` is empty. Defaults to \"No matches\". */\n emptyLabel?: string;\n /**\n * Accessible name for the listbox itself. Pass one whenever the owning\n * control is not a literal `<input role=\"combobox\">`, since an unnamed\n * `role=\"listbox\"` is an accessibility violation.\n */\n ariaLabel?: string;\n /**\n * Which side of the control the list opens on. Defaults to `\"below\"`.\n * `\"above\"` is for a control at the foot of a clipping container, such as a\n * console composer, where a downward list would be clipped away.\n */\n placement?: \"below\" | \"above\";\n}\n\n/**\n * The presentational half of the combobox pattern: a `role=\"listbox\"`\n * dropdown of grouped, keyboard-navigable options. Owns no state:\n * `activeIndex`, `onHoverIndex` and `onSelectKey` are fully controlled by the\n * caller, who drives them with {@link filterComboboxOptions},\n * {@link groupComboboxOptions}, {@link flattenComboboxGroups} and\n * {@link moveComboboxActiveIndex}. The list is absolutely positioned, so put it\n * in a `position: relative` container beside the input. Pressing an option\n * does not take focus from the input.\n *\n * @example\n * ```tsx\n * import { useId, useState } from \"react\";\n * import {\n * type ComboboxOption,\n * ComboboxListbox,\n * filterComboboxOptions,\n * flattenComboboxGroups,\n * groupComboboxOptions,\n * Input,\n * moveComboboxActiveIndex,\n * } from \"@ksp-gonogo/ui-kit\";\n *\n * function BodyPicker(props: {\n * options: readonly ComboboxOption[];\n * selected: string | undefined;\n * onPick: (key: string) => void;\n * }) {\n * const [query, setQuery] = useState(\"\");\n * const [open, setOpen] = useState(false);\n * const [active, setActive] = useState(-1);\n * const listId = useId();\n * const optionId = (key: string) => `${listId}-${key}`;\n * const groups = groupComboboxOptions(filterComboboxOptions(props.options, query));\n * const flat = flattenComboboxGroups(groups);\n * // The list can shrink under the highlighted row, so it is looked up, never assumed.\n * const activeOption = flat[active];\n *\n * return (\n * <div style={{ position: \"relative\" }}>\n * <Input\n * role=\"combobox\"\n * aria-label=\"Body\"\n * aria-expanded={open}\n * aria-controls={listId}\n * aria-autocomplete=\"list\"\n * aria-activedescendant={activeOption ? optionId(activeOption.key) : undefined}\n * value={query}\n * onFocus={() => setOpen(true)}\n * onBlur={() => setOpen(false)}\n * onChange={(e) => {\n * setQuery(e.target.value);\n * setActive(-1);\n * }}\n * onKeyDown={(e) => {\n * if (e.key === \"ArrowDown\") setActive((i) => moveComboboxActiveIndex(i, 1, flat.length));\n * if (e.key === \"ArrowUp\") setActive((i) => moveComboboxActiveIndex(i, -1, flat.length));\n * if (e.key === \"Enter\" && activeOption) props.onPick(activeOption.key);\n * if (e.key === \"Escape\") setOpen(false);\n * }}\n * />\n * {open && (\n * <ComboboxListbox\n * id={listId}\n * groups={groups}\n * flatOptions={flat}\n * activeIndex={active}\n * selectedKey={props.selected}\n * getOptionId={optionId}\n * onHoverIndex={setActive}\n * onSelectKey={props.onPick}\n * />\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @category Form\n */\nexport function ComboboxListbox<Option extends ComboboxOption>({\n id,\n groups,\n flatOptions,\n activeIndex,\n selectedKey,\n getOptionId,\n onHoverIndex,\n onSelectKey,\n renderItem,\n emptyLabel = \"No matches\",\n ariaLabel,\n placement = \"below\",\n}: Readonly<ComboboxListboxProps<Option>>) {\n return (\n <Dropdown\n role=\"listbox\"\n id={id}\n aria-label={ariaLabel}\n $placement={placement}\n >\n {flatOptions.length === 0 ? (\n <EmptyState layout=\"fill\">{emptyLabel}</EmptyState>\n ) : (\n groups.map(([group, items], groupIndex) => (\n <DropdownGroup\n key={group}\n role=\"group\"\n aria-labelledby={`${id}-group-${groupIndex}`}\n >\n <GroupHeader id={`${id}-group-${groupIndex}`} aria-hidden=\"true\">\n {group}\n </GroupHeader>\n {items.map((opt) => {\n const globalIdx = flatOptions.indexOf(opt);\n const isActive = globalIdx === activeIndex;\n return (\n <DropdownItem\n key={opt.key}\n id={getOptionId(opt.key)}\n role=\"option\"\n aria-selected={isActive}\n $active={isActive}\n $selected={opt.key === selectedKey}\n onPointerDown={(e) => {\n // Prevent the input from losing focus (and triggering an outside-click dismiss) before the selection runs.\n e.preventDefault();\n onSelectKey(opt.key);\n }}\n onMouseEnter={() => onHoverIndex(globalIdx)}\n >\n {renderItem ? renderItem(opt) : (opt.label ?? opt.key)}\n </DropdownItem>\n );\n })}\n </DropdownGroup>\n ))\n )}\n </Dropdown>\n );\n}\n\nconst Dropdown = styled.div<{ $placement: \"below\" | \"above\" }>`\n position: absolute;\n ${({ $placement }) =>\n $placement === \"above\"\n ? \"bottom: calc(100% + 2px);\"\n : \"top: calc(100% + 2px);\"}\n left: 0;\n right: 0;\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-strong);\n border-radius: var(--radius-regular);\n max-height: 280px;\n overflow-y: auto;\n z-index: var(--z-dropdown);\n`;\n\nconst DropdownGroup = styled.div``;\n\nconst GroupHeader = styled.div`\n font-size: var(--font-size-caption);\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n padding: var(--inset-menu-group-label);\n position: sticky;\n top: 0;\n background: var(--color-surface-raised);\n`;\n\n/** The option under the keyboard or pointer sits on the panel ground beside the focus bar; the committed option, when not highlighted, sits on the go status fill. */\nfunction optionFill(active: boolean, selected: boolean): string {\n if (active) return \"background: var(--color-surface-panel);\";\n if (selected) return statusFill(\"go\");\n return \"background: transparent;\";\n}\n\nconst DropdownItem = styled.div<{ $active: boolean; $selected: boolean }>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--inset-menu-item);\n cursor: pointer;\n ${({ $active, $selected }) => optionFill($active, $selected)}\n /* Focus stays on the input, so the highlighted option carries the focus colour as an inset bar. */\n box-shadow: ${({ $active }) =>\n $active ? \"inset 3px 0 0 var(--color-focus)\" : \"none\"};\n\n &:hover {\n background: var(--color-surface-panel);\n }\n`;\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\n\n/**\n * Where an {@link EmptyState} sits: `inline` where it is mounted, `fill`\n * centred in the available space.\n *\n * @category EmptyState\n */\nexport type EmptyStateLayout = \"inline\" | \"fill\";\n\n/**\n * Props for {@link EmptyState}. Any other `div` attribute passes through.\n *\n * @category EmptyState\n */\nexport interface EmptyStateProps extends HTMLAttributes<HTMLDivElement> {\n /** The placeholder text. */\n children?: ReactNode;\n /** Defaults to `inline`. */\n layout?: EmptyStateLayout;\n}\n\n/**\n * Muted placeholder text shown when a panel or list has nothing to render.\n *\n * `inline` is the default: it adds no inset of its own and sits where it is\n * mounted, taking the padding of the body or section around it, so it lines up\n * with the content it stands in for. `fill` centres in the available space and\n * suits a panel's sole child.\n *\n * @example\n * ```tsx\n * function VesselsBody({ vessels }: { vessels: readonly string[] }) {\n * if (vessels.length === 0) {\n * return <EmptyState layout=\"fill\">No vessels in flight</EmptyState>;\n * }\n * return <VesselList vessels={vessels} />;\n * }\n * ```\n *\n * @category EmptyState\n * @categoryDescription EmptyState\n * What a widget shows in place of content: nothing to show, a notice, a spinner\n * while it waits, and the note for a trajectory that is withheld.\n */\nexport function EmptyState({\n children,\n layout = \"inline\",\n ...rest\n}: EmptyStateProps) {\n return (\n <EmptyState__Body $layout={layout} {...rest}>\n {children}\n </EmptyState__Body>\n );\n}\n\nconst LAYOUT_STYLES = {\n inline: css``,\n fill: css`\n width: 100%;\n height: 100%;\n flex: 1;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: var(--inset-empty-state);\n `,\n} as const;\n\nconst EmptyState__Body = styled.div<{ $layout: EmptyStateLayout }>`\n color: var(--color-text-muted);\n font-size: var(--font-size-compact);\n letter-spacing: 0.04em;\n\n ${({ $layout }) => LAYOUT_STYLES[$layout]}\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport { useId } from \"react\";\nimport { Button } from \"./Button\";\nimport { Field, FieldHint, FieldLabel, Select } from \"./Form\";\nimport { Stack } from \"./Stack\";\nimport { StatusIndicator } from \"./StatusIndicator\";\nimport {\n type AudioInputControls,\n type AudioInputState,\n describeAudioInput,\n type UseAudioInputOptions,\n useAudioInput,\n} from \"./useAudioInput\";\n\n/**\n * Props for {@link AudioInputPicker}: the {@link UseAudioInputOptions} plus a label.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputPickerProps extends UseAudioInputOptions {\n /** Visible name for the device picker. Defaults to `Audio input`. */\n label?: string;\n}\n\n/**\n * Microphone access and device choice, as one control: it asks for permission,\n * lists what the browser will admit to, opens the chosen input and hands the\n * stream back through `onStream`. It knows nothing about what the stream is for.\n * Built on {@link useAudioInput}; use the hook directly to draw your own.\n *\n * Every state it can be in is a distinguishable sentence in a live status line.\n * An insecure origin, a refused permission and an absent device all end with no\n * stream, but each has a different remedy. A refusal offers no retry button,\n * because the browser returns the same refusal without prompting while the site\n * setting stands. The stream is stopped when the picker unmounts.\n *\n * @example\n * ```tsx\n * <AudioInputPicker\n * label=\"Voice loop input\"\n * constraints={{ echoCancellation: true }}\n * onStream={(stream) => setStream(stream)}\n * />\n * ```\n *\n * @category AudioInputPicker\n * @categoryDescription AudioInputPicker\n * Choosing and opening a microphone: the picker an operator selects an input\n * with, the hook behind it, and whether this browser can capture audio at all.\n */\nexport function AudioInputPicker({\n label = \"Audio input\",\n ...options\n}: AudioInputPickerProps) {\n const input = useAudioInput(options);\n const selectId = useId();\n const unnamed = unnamedDevicesSentence(input);\n\n return (\n <Stack gap=\"related-dense\">\n <StatusIndicator tone={stateTone(input.status)} live>\n {stateSentence(input)}\n </StatusIndicator>\n\n {input.status === \"ready\" && input.devices.length > 0 ? (\n <Field>\n <FieldLabel htmlFor={selectId}>{label}</FieldLabel>\n <Select\n id={selectId}\n value={input.deviceId ?? \"\"}\n onChange={(event) => {\n void input.select(event.target.value);\n }}\n >\n {input.devices.map((device, index) => (\n <option key={device.deviceId} value={device.deviceId}>\n {describeAudioInput(device, index)}\n </option>\n ))}\n </Select>\n {input.failure ? (\n <FieldHint>\n {`The requested input did not open. The browser reported ${input.failure.name}, and the previous input is still running.`}\n </FieldHint>\n ) : null}\n </Field>\n ) : null}\n\n {canRequest(input.status) ? (\n <Button\n type=\"button\"\n onClick={() => {\n void input.request();\n }}\n disabled={input.status === \"requesting\"}\n aria-busy={input.status === \"requesting\"}\n >\n Request microphone access\n </Button>\n ) : null}\n\n {unnamed ? <FieldHint>{unnamed}</FieldHint> : null}\n </Stack>\n );\n}\n\nfunction canRequest(status: AudioInputControls[\"status\"]): boolean {\n return status === \"unasked\" || status === \"requesting\" || status === \"failed\";\n}\n\nconst TONES: Record<AudioInputControls[\"status\"], Tone> = {\n \"insecure-origin\": \"warn\",\n \"no-media-devices\": \"nogo\",\n unasked: \"neutral\",\n requesting: \"info\",\n refused: \"nogo\",\n \"no-device\": \"warn\",\n failed: \"nogo\",\n ready: \"go\",\n};\n\nfunction stateTone(status: AudioInputControls[\"status\"]): Tone {\n return TONES[status];\n}\n\n/**\n * The one sentence describing where capture stands. `unasked` splits on\n * whether any device label is populated, because a populated label is the\n * browser's evidence that access was already granted.\n */\nfunction stateSentence(state: AudioInputState): string {\n switch (state.status) {\n case \"insecure-origin\":\n return \"This page is not a secure origin. Microphone capture is available on https pages and on localhost.\";\n case \"no-media-devices\":\n return \"This browser exposes no media device interface, so no input can be opened here.\";\n case \"unasked\":\n return accessGranted(state)\n ? \"Microphone access is granted for this origin. No input is open.\"\n : \"Microphone access has not been requested for this origin.\";\n case \"requesting\":\n return \"A capture request is with the browser, which may be prompting for access.\";\n case \"refused\":\n return \"Microphone access was refused for this origin. The browser holds that answer in its site settings and returns it to any further request without prompting.\";\n case \"no-device\":\n return \"No audio input device is present.\";\n case \"failed\":\n return `The input did not open. The browser reported ${state.failure?.name ?? \"an unnamed error\"}.`;\n case \"ready\":\n return `Capturing from ${openDeviceName(state)}.`;\n }\n}\n\nfunction accessGranted(state: AudioInputState): boolean {\n return state.devices.some((device) => device.label !== \"\");\n}\n\nfunction openDeviceName(state: AudioInputState): string {\n const index = state.devices.findIndex(\n (device) => device.deviceId === state.deviceId,\n );\n const device = index === -1 ? undefined : state.devices[index];\n return device ? describeAudioInput(device, index) : \"an unnamed input\";\n}\n\n/**\n * What is known about the devices while their names are not. Shown only before\n * access is granted, where a count is the whole of what the browser will say.\n */\nfunction unnamedDevicesSentence(state: AudioInputState): string | null {\n if (state.status !== \"unasked\" || accessGranted(state)) return null;\n const count = state.devices.length;\n if (count === 0) return null;\n return count === 1\n ? \"One audio input is visible. Its name is withheld by the browser until access is granted.\"\n : `${count} audio inputs are visible. Their names are withheld by the browser until access is granted.`;\n}\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styled, { css, keyframes } from \"styled-components\";\nimport { TONE_MARK, toneEdge } from \"./tone\";\n\n/**\n * Props for {@link StatusIndicator}. Other `div` attributes pass through to the row.\n *\n * @category Badge\n */\nexport interface StatusIndicatorProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The state the dot shows, which sets its colour. */\n tone: Tone;\n /** The words beside the dot. */\n children: ReactNode;\n /**\n * When true, the indicator becomes a screen-reader live region.\n * Use for state that updates dynamically and the user benefits from\n * being told (connection going from probing to ok or fail). Default\n * false to keep it out of the accessibility tree for purely decorative\n * uses.\n */\n live?: boolean;\n /**\n * Pulse the dot to signal an active, changing state (a live or connecting\n * data source, for example). `\"slow\"` (2s) reads as steady-live, `\"fast\"` (1s) as\n * working/reconnecting. Omit for a static dot. Guarded by\n * `prefers-reduced-motion` (the dot holds still when the user opts out).\n */\n pulse?: \"slow\" | \"fast\";\n}\n\n/**\n * A boxed row with a coloured dot on the left and a sentence of status text on\n * the right, bordered in the tone's colour and one control high. Use it where\n * a sentence describes a state (connection status, a data source's health) and\n * the dot tells the operator at a glance whether to worry. For a compact\n * uppercase pill use {@link Badge} instead.\n *\n * @example\n * ```tsx\n * <StatusIndicator tone=\"caution\" pulse=\"fast\" live>\n * Reconnecting to the relay\n * </StatusIndicator>\n * ```\n *\n * @category Badge\n */\nexport function StatusIndicator({\n tone,\n children,\n live = false,\n pulse,\n ...rest\n}: StatusIndicatorProps) {\n const liveAttrs = live\n ? { role: \"status\" as const, \"aria-live\": \"polite\" as const }\n : {};\n return (\n <StatusIndicator__Row data-tone={tone} {...liveAttrs} {...rest}>\n <StatusIndicator__Dot\n data-tone={tone}\n $pulse={pulse}\n aria-hidden=\"true\"\n />\n <StatusIndicator__Text>{children}</StatusIndicator__Text>\n </StatusIndicator__Row>\n );\n}\n\nconst StatusIndicator__Row = styled.div<{ \"data-tone\": Tone }>`\n display: flex;\n align-items: center;\n gap: var(--gap-glyph-box);\n font-size: var(--font-size-compact);\n /* Small, so a wrapped sentence clears the border without pushing a single line past the control height. */\n padding: var(--inset-status-box);\n /* A boxed readout that sits in bars beside controls, so it takes the kit's one control height. */\n min-height: var(--control-height);\n background: var(--color-surface-raised);\n border: 1px solid;\n border-radius: var(--radius-regular);\n\n border-color: ${({ \"data-tone\": tone }) => toneEdge(tone)};\n`;\n\nconst statusPulse = keyframes`\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n`;\n\nconst StatusIndicator__Dot = styled.span<{\n \"data-tone\": Tone;\n $pulse?: \"slow\" | \"fast\";\n}>`\n width: 8px;\n height: 8px;\n border-radius: var(--radius-circle);\n flex-shrink: 0;\n\n background: ${({ \"data-tone\": tone }) => TONE_MARK[tone]};\n\n /* A looping pulse needs its own reduced-motion guard; the 1s/2s periods encode connection state. */\n ${({ $pulse }) =>\n $pulse\n ? css`\n @media (prefers-reduced-motion: no-preference) {\n animation: ${statusPulse} ${$pulse === \"fast\" ? \"1s\" : \"2s\"}\n var(--ease-emphasis) infinite;\n }\n `\n : \"\"}\n`;\n\nconst StatusIndicator__Text = styled.span`\n color: var(--color-text-primary);\n line-height: var(--line-height-body);\n`;\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { audioCaptureSupport } from \"./audioCaptureSupport\";\n\n/**\n * One selectable capture device.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputDevice {\n /** The browser's id for the device, what {@link AudioInputControls.select} takes. */\n deviceId: string;\n /**\n * The browser's name for the device. Empty until capture access is granted,\n * since browsers withhold device names from a page without access; an empty\n * label does not mean the device has no name.\n */\n label: string;\n}\n\n/**\n * Where the capture attempt stands. Each failure is its own status because\n * each has a different remedy:\n *\n * - `unasked`: nothing has been requested yet, the starting status\n * - `requesting`: a request is with the browser, which may be prompting\n * - `ready`: a capture is open\n * - `insecure-origin` and `no-media-devices`: capture cannot be attempted, per\n * {@link audioCaptureSupport}\n * - `refused`: the operator (or a standing site setting) said no. The browser\n * holds that refusal for the origin, so asking again returns it without\n * prompting\n * - `no-device`: access is not the problem, there is nothing to capture from\n * - `failed`: the device exists and is permitted, and opening it still did not\n * work (in use elsewhere, hardware error). `failure.name` carries the\n * browser's own word for it\n *\n * @category AudioInputPicker\n */\nexport type AudioInputStatus =\n | \"insecure-origin\"\n | \"no-media-devices\"\n | \"unasked\"\n | \"requesting\"\n | \"refused\"\n | \"no-device\"\n | \"failed\"\n | \"ready\";\n\n/**\n * An attempt to open an input that did not work.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputFailure {\n /** The `DOMException` name the browser reported, e.g. `NotReadableError`. */\n name: string;\n /** The device the attempt named, or null for an unpinned first request. */\n deviceId: string | null;\n}\n\n/**\n * Everything {@link useAudioInput} knows about capture at one moment.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputState {\n /** Where the capture attempt stands. */\n status: AudioInputStatus;\n /**\n * Every audio input the browser will admit to. Populated before access is\n * granted on the engines that allow it, with empty labels; entries with an\n * empty `deviceId` are dropped, since they name nothing that can be opened.\n */\n devices: readonly AudioInputDevice[];\n /** The open device, null whenever no capture is running. */\n deviceId: string | null;\n /** The live stream, null whenever no capture is running. */\n stream: MediaStream | null;\n /**\n * The last attempt that did not work. Set alongside `ready` when a SWITCH\n * failed and the previous capture is still open, which is the one case where\n * a failure is not the whole state.\n */\n failure: AudioInputFailure | null;\n}\n\n/**\n * What {@link useAudioInput} returns: the current state and the three actions.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputControls extends AudioInputState {\n /** Open the default input, prompting for access the first time. */\n request(): Promise<void>;\n /** Open a named input, keeping the current one if the new one will not open. */\n select(deviceId: string): Promise<void>;\n /** Stop the capture and hand back a null stream. Access stays granted. */\n release(): void;\n}\n\n/**\n * Options for {@link useAudioInput} and {@link AudioInputPicker}.\n *\n * @category AudioInputPicker\n */\nexport interface UseAudioInputOptions {\n /**\n * Track constraints merged into every request. A `deviceId` here is\n * overwritten by the selected device.\n */\n constraints?: MediaTrackConstraints;\n /** Called with each stream as it opens, and with null as it closes. */\n onStream?: (stream: MediaStream | null) => void;\n}\n\n/**\n * Option text for a device: its label, or `Input <n>, name withheld` (one-based)\n * while the browser withholds the name.\n *\n * @category AudioInputPicker\n */\nexport function describeAudioInput(\n device: AudioInputDevice,\n index: number,\n): string {\n return device.label !== \"\"\n ? device.label\n : `Input ${index + 1}, name withheld`;\n}\n\nconst REFUSED_ERRORS = new Set([\n \"NotAllowedError\",\n \"PermissionDeniedError\",\n \"SecurityError\",\n]);\n\nconst ABSENT_ERRORS = new Set([\n \"NotFoundError\",\n \"DevicesNotFoundError\",\n \"OverconstrainedError\",\n \"ConstraintNotSatisfiedError\",\n]);\n\nfunction errorName(err: unknown): string {\n if (err instanceof Error && err.name !== \"\") return err.name;\n if (typeof err === \"object\" && err !== null && \"name\" in err) {\n const name = (err as { name: unknown }).name;\n if (typeof name === \"string\" && name !== \"\") return name;\n }\n return \"UnknownError\";\n}\n\nfunction statusForError(name: string): AudioInputStatus {\n if (REFUSED_ERRORS.has(name)) return \"refused\";\n if (ABSENT_ERRORS.has(name)) return \"no-device\";\n return \"failed\";\n}\n\nfunction stopTracks(stream: MediaStream): void {\n for (const track of stream.getTracks()) track.stop();\n}\n\nfunction trackDeviceId(stream: MediaStream): string | null {\n const track = stream.getAudioTracks()[0];\n const settings = track?.getSettings?.();\n return settings?.deviceId ?? null;\n}\n\nasync function listInputs(): Promise<AudioInputDevice[]> {\n const media = navigator.mediaDevices;\n if (!media || typeof media.enumerateDevices !== \"function\") return [];\n try {\n const all = await media.enumerateDevices();\n return all\n .filter(\n (device) => device.kind === \"audioinput\" && device.deviceId !== \"\",\n )\n .map((device) => ({ deviceId: device.deviceId, label: device.label }));\n } catch {\n // A failed enumeration is not a device fact; the request itself decides whether capture works.\n return [];\n }\n}\n\nfunction initialState(): AudioInputState {\n const support = audioCaptureSupport();\n return {\n status: support.supported ? \"unasked\" : support.reason,\n devices: [],\n deviceId: null,\n stream: null,\n failure: null,\n };\n}\n\n/**\n * The permission-and-selection half of microphone capture, with no opinion\n * about how it is drawn. {@link AudioInputPicker} is the drawn form and the\n * usual entry point.\n *\n * Nothing is requested until `request()` or `select()` is called. Every outcome\n * is a named {@link AudioInputStatus} rather than a boolean plus an error: a\n * refused permission, an absent device and an insecure origin all end with no\n * stream, and each needs a different fix. The device list follows the\n * browser's device changes, a capture whose device disappears is closed, and\n * any open stream is stopped on unmount.\n *\n * @example\n * ```tsx\n * function MicGate({ onStream }: { onStream: (stream: MediaStream | null) => void }) {\n * const mic = useAudioInput({ onStream });\n * if (mic.status === \"unasked\") {\n * return <Button onClick={() => void mic.request()}>Enable microphone</Button>;\n * }\n * return <Text>{mic.status}</Text>;\n * }\n * ```\n *\n * @category AudioInputPicker\n */\nexport function useAudioInput(\n options: UseAudioInputOptions = {},\n): AudioInputControls {\n const [state, setState] = useState<AudioInputState>(initialState);\n const streamRef = useRef<MediaStream | null>(null);\n // Mirrored out of state so the devicechange handler's updater stays pure under StrictMode's double invocation.\n const openDeviceIdRef = useRef<string | null>(null);\n const mountedRef = useRef(true);\n const optionsRef = useRef(options);\n\n useEffect(() => {\n optionsRef.current = options;\n });\n\n const closeStream = useCallback(() => {\n const open = streamRef.current;\n if (!open) return;\n streamRef.current = null;\n openDeviceIdRef.current = null;\n stopTracks(open);\n optionsRef.current.onStream?.(null);\n }, []);\n\n // Losing the open device ends the capture; only an empty list means there is nothing left to choose.\n const applyDevices = useCallback(\n (devices: AudioInputDevice[]) => {\n const openId = openDeviceIdRef.current;\n const lost =\n streamRef.current !== null &&\n (openId === null\n ? devices.length === 0\n : !devices.some((device) => device.deviceId === openId));\n setState((prev) => {\n if (prev.status !== \"ready\" || !lost) return { ...prev, devices };\n return {\n ...prev,\n status: devices.length === 0 ? \"no-device\" : \"unasked\",\n devices,\n deviceId: null,\n stream: null,\n };\n });\n if (lost) closeStream();\n },\n [closeStream],\n );\n\n // Re-read on every devicechange: before access is granted this yields ids without labels, after it fills the labels in and notices an unplug.\n useEffect(() => {\n const media = navigator.mediaDevices;\n if (!media) return;\n\n let cancelled = false;\n const sync = () => {\n void listInputs().then((devices) => {\n if (!cancelled) applyDevices(devices);\n });\n };\n sync();\n media.addEventListener?.(\"devicechange\", sync);\n return () => {\n cancelled = true;\n media.removeEventListener?.(\"devicechange\", sync);\n };\n }, [applyDevices]);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n closeStream();\n };\n }, [closeStream]);\n\n const open = useCallback(async (deviceId: string | null) => {\n const media = navigator.mediaDevices;\n if (!media || typeof media.getUserMedia !== \"function\") return;\n\n setState((prev) => ({\n ...prev,\n status: prev.stream ? prev.status : \"requesting\",\n failure: null,\n }));\n\n const audio: MediaTrackConstraints = {\n ...optionsRef.current.constraints,\n ...(deviceId === null ? {} : { deviceId: { exact: deviceId } }),\n };\n\n try {\n const stream = await media.getUserMedia({ audio });\n if (!mountedRef.current) {\n stopTracks(stream);\n return;\n }\n const previous = streamRef.current;\n streamRef.current = stream;\n if (previous && previous !== stream) stopTracks(previous);\n\n const listed = await listInputs();\n const active = trackDeviceId(stream) ?? deviceId ?? listed[0]?.deviceId;\n const devices =\n listed.length > 0 ? listed : openDeviceOnly(stream, active);\n openDeviceIdRef.current = active ?? null;\n setState({\n status: \"ready\",\n devices,\n deviceId: active ?? null,\n stream,\n failure: null,\n });\n optionsRef.current.onStream?.(stream);\n } catch (err) {\n if (!mountedRef.current) return;\n const failure: AudioInputFailure = { name: errorName(err), deviceId };\n setState((prev) =>\n prev.stream\n ? { ...prev, failure }\n : {\n status: statusForError(failure.name),\n devices: prev.devices,\n deviceId: null,\n stream: null,\n failure,\n },\n );\n }\n }, []);\n\n const request = useCallback(() => open(null), [open]);\n const select = useCallback((deviceId: string) => open(deviceId), [open]);\n const release = useCallback(() => {\n closeStream();\n setState((prev) => ({\n ...prev,\n status: prev.status === \"ready\" ? \"unasked\" : prev.status,\n deviceId: null,\n stream: null,\n }));\n }, [closeStream]);\n\n return { ...state, request, select, release };\n}\n\n/**\n * The one device we can prove exists, for an engine that grants capture and\n * still enumerates nothing. Returns an empty list rather than a nameless,\n * idless row, since a row that selects nothing is worse than no row.\n */\nfunction openDeviceOnly(\n stream: MediaStream,\n deviceId: string | null | undefined,\n): AudioInputDevice[] {\n if (!deviceId) return [];\n return [{ deviceId, label: stream.getAudioTracks()[0]?.label ?? \"\" }];\n}\n","/*\n * The origin is checked FIRST: some engines withhold navigator.mediaDevices on\n * an insecure origin and some expose it and refuse at getUserMedia, so a\n * presence-only probe misreports both.\n */\n\n/**\n * Why microphone capture cannot run on this page: `insecure-origin` (plain\n * http, which works again over https or on localhost) or `no-media-devices`\n * (the browser has no media device interface at all).\n *\n * @category AudioInputPicker\n */\nexport type AudioCaptureUnsupportedReason =\n | \"insecure-origin\"\n | \"no-media-devices\";\n\n/**\n * What {@link audioCaptureSupport} reports: supported, or not with the reason.\n *\n * @category AudioInputPicker\n */\nexport type AudioCaptureSupport =\n | { supported: true }\n | { supported: false; reason: AudioCaptureUnsupportedReason };\n\n/**\n * Whether this page can open a microphone, probed synchronously before touching\n * a device. It reports a reason rather than a boolean because the two failures\n * need different copy.\n *\n * @category AudioInputPicker\n */\nexport function audioCaptureSupport(): AudioCaptureSupport {\n // Anything other than the boolean `true` a browser sets is treated as insecure.\n if (\n !(\"isSecureContext\" in globalThis) ||\n globalThis.isSecureContext !== true\n ) {\n return { supported: false, reason: \"insecure-origin\" };\n }\n\n const media =\n typeof navigator === \"undefined\" ? undefined : navigator.mediaDevices;\n if (!media || typeof media.getUserMedia !== \"function\") {\n return { supported: false, reason: \"no-media-devices\" };\n }\n return { supported: true };\n}\n","import styled from \"styled-components\";\n\ninterface SwitchBaseProps {\n checked: boolean;\n onChange: (value: boolean) => void;\n /** Renders dimmed and non-interactive; `onChange` never fires. For a toggle whose effective state is controlled elsewhere, such as a sub-setting inert while its parent setting is off. */\n disabled?: boolean;\n /** The checkbox's id, for a `<label htmlFor>` outside the switch. */\n id?: string;\n}\n\n/**\n * Props for {@link Switch}: `checked`, `onChange(next)`, optional `disabled`\n * and `id`, and exactly one name. A switch always has a name: a visible `label`\n * drawn beside it, or an `aria-label` where the row it sits in shows its own\n * label text.\n *\n * @category Form\n */\nexport type SwitchProps = SwitchBaseProps &\n (\n | { label: string; \"aria-label\"?: never }\n | { label?: never; \"aria-label\": string }\n );\n\n/**\n * An on/off toggle for a setting that takes effect at once: a native checkbox\n * drawn as a sliding track, keyboard-operable with Space and with the focus\n * ring drawn on the track.\n *\n * @category Form\n */\nexport function Switch({\n checked,\n onChange,\n label,\n disabled,\n id,\n \"aria-label\": ariaLabel,\n}: SwitchProps) {\n return (\n <SwitchLabel $disabled={disabled}>\n <SwitchInput\n type=\"checkbox\"\n id={id}\n checked={checked}\n disabled={disabled}\n onChange={(e) => onChange(e.target.checked)}\n aria-label={label ? undefined : ariaLabel}\n />\n <SwitchTrack $checked={checked} $disabled={disabled}>\n <SwitchThumb $checked={checked} $disabled={disabled} />\n </SwitchTrack>\n {label && <SwitchText>{label}</SwitchText>}\n </SwitchLabel>\n );\n}\n\nconst SwitchLabel = styled.label<{ $disabled?: boolean }>`\n display: inline-flex;\n align-items: center;\n gap: var(--gap-glyph-control);\n cursor: ${({ $disabled }) => ($disabled ? \"not-allowed\" : \"pointer\")};\n user-select: none;\n opacity: ${({ $disabled }) => ($disabled ? 0.5 : 1)};\n\n @media (pointer: coarse) {\n /* Expand tap target to 44px tall without enlarging the visual track. */\n min-height: 44px;\n padding: var(--inset-switch-touch);\n }\n`;\n\nconst SwitchTrack = styled.div<{ $checked: boolean; $disabled?: boolean }>`\n width: 28px;\n height: 14px;\n /* --radius-pill keeps the stadium shape through a height change. */\n border-radius: var(--radius-pill);\n background: ${({ $checked, $disabled }) => ($disabled ? \"var(--color-surface-raised)\" : $checked ? \"var(--color-go-mark)\" : \"var(--color-surface-raised)\")};\n border: 1px solid ${({ $checked, $disabled }) => ($disabled ? \"var(--color-border-strong)\" : $checked ? \"var(--color-go-mark)\" : \"var(--color-border-strong)\")};\n position: relative;\n flex-shrink: 0;\n transition: background var(--duration-base), border-color var(--duration-base);\n`;\n\n/* The input is invisible and sizeless, so its keyboard focus is drawn on the\n track it sits beside. */\nconst SwitchInput = styled.input`\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n\n &:focus-visible + ${SwitchTrack} {\n outline: 2px solid var(--color-focus);\n outline-offset: 2px;\n }\n`;\n\nconst SwitchThumb = styled.div<{ $checked: boolean; $disabled?: boolean }>`\n position: absolute;\n /* Measured inside the border: the track is border-box, so its 1px border leaves a 26 by 12 box, and 2px on every side centres the 8px thumb at either end. */\n top: 2px;\n left: ${({ $checked }) => ($checked ? \"16px\" : \"2px\")};\n width: 8px;\n height: 8px;\n border-radius: var(--radius-circle);\n background: ${({ $checked, $disabled }) => ($disabled ? \"var(--color-text-faint)\" : $checked ? \"var(--color-accent-fg)\" : \"var(--color-text-faint)\")};\n transition: left var(--duration-base), background var(--duration-base);\n`;\n\nconst SwitchText = styled.span`\n font-size: var(--font-size-caption);\n color: var(--color-text-muted);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n`;\n","import type { NamespacedAugmentSettings } from \"./augments\";\nimport { Field, FieldLabel, FieldRow, Input } from \"./Form\";\nimport { Switch } from \"./Switch\";\n\n/**\n * Props for {@link AugmentSettingsPanel}.\n *\n * @category Extensions\n */\nexport interface AugmentSettingsPanelProps {\n /** Every augment's settings block for the widget's slots, as {@link getAugmentSettings} returns them. */\n settings: readonly NamespacedAugmentSettings[];\n /** The widget's persisted per-augment values, keyed `[namespace][key]`. `undefined` when nothing has been saved yet; each field then shows its own `default`. */\n values: Record<string, Record<string, unknown>> | undefined;\n /** Called on every field edit with the namespace (augment id), the field key, and the new value. An emptied number field passes `undefined`. */\n onChange: (namespace: string, key: string, value: unknown) => void;\n}\n\n/**\n * Draws the settings augments add to a widget, one control per field: a\n * {@link Switch} for a `boolean` field, a text or number input otherwise.\n * Fields are namespaced by augment id, so two augments' settings with the same\n * key never collide. A widget with several slots joins the\n * {@link getAugmentSettings} result for each into one `settings` array. Renders\n * nothing when `settings` is empty.\n *\n * @example\n * ```tsx\n * <AugmentSettingsPanel\n * settings={getAugmentSettings(\"power-systems.sections\")}\n * values={draft.augmentSettings}\n * onChange={(augmentId, key, value) =>\n * setDraft((d) => ({\n * ...d,\n * augmentSettings: {\n * ...d.augmentSettings,\n * [augmentId]: { ...d.augmentSettings?.[augmentId], [key]: value },\n * },\n * }))\n * }\n * />\n * ```\n *\n * @category Extensions\n */\nexport function AugmentSettingsPanel({\n settings,\n values,\n onChange,\n}: Readonly<AugmentSettingsPanelProps>) {\n if (settings.length === 0) return null;\n\n return (\n <>\n {settings.flatMap((block) =>\n block.fields.map((field) => {\n const stored = values?.[block.namespace]?.[field.key];\n const current = stored ?? field.default;\n const label = field.label ?? field.key;\n const fieldId = `augment-setting-${block.namespace}-${field.key}`;\n\n if (field.type === \"boolean\") {\n return (\n <FieldRow key={`${block.namespace}.${field.key}`}>\n <Switch\n checked={Boolean(current)}\n onChange={(value) =>\n onChange(block.namespace, field.key, value)\n }\n label={label}\n />\n </FieldRow>\n );\n }\n\n return (\n <Field key={`${block.namespace}.${field.key}`}>\n <FieldLabel htmlFor={fieldId}>{label}</FieldLabel>\n <Input\n id={fieldId}\n type={field.type === \"number\" ? \"number\" : \"text\"}\n value={current === undefined ? \"\" : String(current)}\n onChange={(e) => {\n const raw = e.target.value;\n if (field.type !== \"number\") {\n onChange(block.namespace, field.key, raw);\n return;\n }\n // An emptied field stores nothing, so the default applies; a partial entry such as a lone \"-\" stores nothing at all.\n if (raw === \"\") {\n onChange(block.namespace, field.key, undefined);\n return;\n }\n const n = Number(raw);\n if (Number.isFinite(n))\n onChange(block.namespace, field.key, n);\n }}\n />\n </Field>\n );\n }),\n )}\n </>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Props for {@link AutoEmptyState}. Any other `div` attribute passes through\n * to the content region.\n *\n * @category EmptyState\n */\nexport interface AutoEmptyStateProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Shown while the content region renders no DOM nodes at all, for example\n * when every augment bound to a slot returned `null`. The switch is pure\n * CSS, so the caller never inspects what its children rendered.\n */\n fallback: ReactNode;\n /** Gap between rendered children. Defaults to `related-dense`. */\n gap?: GapToken;\n children?: ReactNode;\n}\n\n/**\n * A content region (a vertical flex column) followed by a fallback that hides\n * itself once the region has rendered anything. Use it where a frame owns the\n * fallback but its content comes from augments or other children it cannot\n * inspect. It never scrolls on its own; the panel body it sits in scrolls.\n *\n * @example\n * ```tsx\n * // Each SensorRow renders null for a sensor that is switched off.\n * <AutoEmptyState fallback={<EmptyState>No sensors active</EmptyState>}>\n * {sensors.map((s) => (\n * <SensorRow key={s.id} sensor={s} />\n * ))}\n * </AutoEmptyState>\n * ```\n *\n * @category EmptyState\n */\nexport function AutoEmptyState({\n fallback,\n gap = \"related-dense\",\n children,\n ...rest\n}: AutoEmptyStateProps) {\n return (\n <>\n <AutoEmptyState__Content $gap={gap} {...rest}>\n {children}\n </AutoEmptyState__Content>\n <AutoEmptyState__Fallback>{fallback}</AutoEmptyState__Fallback>\n </>\n );\n}\n\nconst AutoEmptyState__Content = styled.div<{ $gap: GapToken }>`\n display: flex;\n flex-direction: column;\n gap: ${({ $gap }) => GAP_VAR[$gap]};\n`;\n\nconst AutoEmptyState__Fallback = styled.div`\n ${AutoEmptyState__Content}:not(:empty) + & {\n display: none;\n }\n`;\n","/**\n * A point in viewport (client) coordinates.\n *\n * @category Floating\n */\nexport interface AnchorPoint {\n /** Pixels from the viewport's left edge. */\n x: number;\n /** Pixels from the viewport's top edge. */\n y: number;\n}\n\n/** A measured box, or the viewport itself. */\nexport interface BoxSize {\n w: number;\n h: number;\n}\n\n/**\n * Where an anchored layer's top-left corner goes, in viewport coordinates.\n *\n * @category Floating\n */\nexport interface AnchoredPosition {\n /** Pixels from the viewport's left edge. */\n left: number;\n /** Pixels from the viewport's top edge. */\n top: number;\n}\n\n/** Distance from the anchor point to the layer's near corner. */\nconst GAP = 12;\n/** Minimum distance kept between the layer and the viewport edge. */\nconst EDGE = 8;\n\n/**\n * Where to put a `layer`-sized box anchored at `anchor` inside `viewport`, as\n * the position {@link Floating} uses. It goes 12px below and right of the\n * anchor by preference; on an axis where that runs past the edge it flips to\n * the anchor's other side rather than sliding, so it still reads as attached.\n * If neither side fits, it is clamped to stay 8px inside the viewport, and the\n * layer's own scroll box must carry the rest.\n *\n * @category Floating\n */\nexport function anchoredPosition(\n anchor: AnchorPoint,\n layer: BoxSize,\n viewport: BoxSize,\n): AnchoredPosition {\n return {\n left: place(anchor.x, layer.w, viewport.w),\n top: place(anchor.y, layer.h, viewport.h),\n };\n}\n\n/** One axis of the placement: after the anchor, else before it, else clamped. */\nfunction place(anchor: number, extent: number, bound: number): number {\n const after = anchor + GAP;\n if (after + extent <= bound - EDGE) return after;\n\n const before = anchor - GAP - extent;\n if (before >= EDGE) return before;\n\n return Math.max(EDGE, bound - EDGE - extent);\n}\n","import type { Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { magnitudeOf } from \"./magnitude\";\nimport { NULL_DISPLAY } from \"./NullValue\";\nimport { Unit, type UnitProps } from \"./Unit\";\nimport {\n UnitSharedFormat,\n useReadsAsOneFigure,\n useSharedFormat,\n} from \"./UnitSharedFormat\";\nimport { VisuallyHidden } from \"./VisuallyHidden\";\n\n/**\n * The en dash {@link Band} draws between its two ends. It is `aria-hidden`,\n * since a screen reader is given the interval in words. Import it to match or\n * assert on the separator rather than writing the character again.\n *\n * @category Unit\n */\nexport const INTERVAL_DASH = \"–\";\n\n/**\n * Props for {@link Band}. `decimals`, `format` and `as` pin both ends at once.\n *\n * @category Unit\n */\nexport interface BandProps<UnitSymbol extends string = string>\n extends Pick<UnitProps<UnitSymbol>, \"decimals\" | \"format\" | \"as\"> {\n /** The low end of the interval. */\n min?: Value<UnitSymbol> | null;\n /** The high end. */\n max?: Value<UnitSymbol> | null;\n /**\n * The modulus of a circular quantity, in the value's own unit: 360 for an\n * angle in degrees. Supplying it is what lets the band say a quantity went all\n * the way round instead of printing a meaningless width.\n */\n wrapsAt?: number;\n className?: string;\n}\n\n/**\n * A closed interval, rendered as its two ENDS and never collapsed to a\n * midpoint, since the width is often the point (a mean orbital element's width\n * says whether the orbit is stable).\n *\n * - A half-absent band is absent: with either end missing it draws the null\n * token, since one end alone would read as a scalar\n * - The precision follows the width: digits widen until the ends read\n * differently, so 6 700 km to 6 710 km never prints as `6.7 Mm` twice\n * - Both ends are one {@link UnitSharedFormat} group, which settles the unit\n * and digit count across the pair, so an interval is never written in two\n * units (`999 m` to `1.0 km`)\n * - With `wrapsAt`, a band spanning half the turn or more renders as\n * precessing rather than as an interval like `0°` to `359°`\n *\n * @category Unit\n */\nexport function Band<UnitSymbol extends string = string>({\n min,\n max,\n wrapsAt,\n className,\n ...pins\n}: BandProps<UnitSymbol>) {\n const low = magnitudeOf(min);\n const high = magnitudeOf(max);\n\n if (min == null || max == null || low === null || high === null) {\n return <Band__Body className={className}>{NULL_DISPLAY}</Band__Body>;\n }\n\n if (wrapsAt !== undefined && Math.abs(high - low) >= wrapsAt / 2) {\n // The producer's own sentinel: the angle swept far enough that no midpoint or half-width exists.\n return <Band__Body className={className}>(precesses)</Band__Body>;\n }\n\n // `separate`: an interval promises a width, so its ends may not print the same figure. The caller's pins apply once, to the group.\n return (\n <UnitSharedFormat of={min.unit} separate {...pins}>\n <BandEnds min={min} max={max} className={className} />\n </UnitSharedFormat>\n );\n}\n\n/**\n * The ends, drawn once the group has settled how both are written.\n *\n * <p>Two ends that come out as the same text are drawn ONCE, marked\n * approximate (`~65.3 km`): the width is below what the display can show. The\n * rule is \"the display cannot tell them apart\", answered by the group, not a\n * tolerance.</p>\n *\n * <p>Both ends report to the group in every branch, so the group's answer\n * cannot depend on the branch chosen from it.</p>\n */\nfunction BandEnds<UnitSymbol extends string = string>({\n min,\n max,\n className,\n}: {\n min: Value<UnitSymbol>;\n max: Value<UnitSymbol>;\n className?: string;\n}) {\n // Reported bare: pins here would take each end out of the group.\n useSharedFormat(min);\n useSharedFormat(max);\n const oneFigure = useReadsAsOneFigure(min);\n\n if (oneFigure) {\n return (\n <Band__Body className={className}>\n <Band__Approximate>\n {/* The tilde is hidden; the spoken word beside it replaces it. */}\n <span aria-hidden=\"true\">~</span>\n <VisuallyHidden>approximately </VisuallyHidden>\n {/* Either end: they render identically in this branch. */}\n <Unit value={min} />\n </Band__Approximate>\n </Band__Body>\n );\n }\n\n return (\n <Band__Body className={className}>\n <Unit value={min} />\n <Band__Dash aria-hidden=\"true\">{INTERVAL_DASH}</Band__Dash>\n <VisuallyHidden> to </VisuallyHidden>\n <Unit value={max} />\n </Band__Body>\n );\n}\n\nconst Band__Body = styled.span`\n display: inline-flex;\n align-items: baseline;\n gap: var(--gap-figure-parts);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n`;\n\nconst Band__Dash = styled.span`\n color: var(--color-text-faint);\n`;\n\n// The tilde and the figure are one phrase with no gap between them: `~65.3 km`, never `~ 65.3 km`.\nconst Band__Approximate = styled.span`\n display: inline-flex;\n align-items: baseline;\n white-space: nowrap;\n`;\n","import type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { SubjectHeading } from \"./SubjectHeading\";\nimport type { StaticElement } from \"./staticElement\";\n\n/**\n * A record's anatomy, as props: a name, badges on the name's line, a body of\n * figures, asides and a footer of actions. Use the props where they fit; the\n * parts (`Block.Title` and the rest) are for a record that needs its own\n * arrangement with the same type and spacing.\n */\nexport interface BlockAnatomyProps {\n /**\n * The block's heading, naming what it is about: larger and heavier than the\n * body under it. Replaces the HTML `title` tooltip attribute.\n */\n title?: ReactNode;\n /** Tag for `title`. Defaults to `div`; pass a heading where the outline wants one. */\n titleAs?: StaticElement;\n /**\n * Drawn BEFORE the title, on the title's own line: a marker, a rank pip, an\n * index. Not a status, which reads after the thing it is a status of.\n */\n titleLeft?: ReactNode;\n /**\n * Drawn AFTER the title and pushed to the end of its line: the badges that\n * say how its subject is doing. Wraps under the title rather than squeezing\n * it. Distinct from `right`, which sits beside (or below) the whole body.\n */\n titleRight?: ReactNode;\n /**\n * Content beside the title and body, on the left. Reflows to a full-width row\n * ABOVE the title when there is no room for both.\n */\n left?: ReactNode;\n /** Content beside the title and body, on the right. Reflows BELOW the body when narrow. */\n right?: ReactNode;\n /** Content across the top, above the title. It stays there at every width. */\n top?: ReactNode;\n /** Content across the bottom, below the footer. It stays there at every width. */\n bottom?: ReactNode;\n /** The actions and tags that close the block, spread across one wrapping row. */\n footer?: ReactNode;\n /** The body, under the title. */\n children?: ReactNode;\n}\n\n/**\n * Props for {@link Block}: the record anatomy (`title`, `titleLeft`,\n * `titleRight`, `left`, `right`, `top`, `bottom`, `footer`) plus any `div`\n * attribute.\n *\n * @category Layout\n */\nexport interface BlockProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"title\">,\n BlockAnatomyProps {\n /**\n * Rendered tag. Defaults to `div`. A record in a list wants `li`, a\n * self-contained record `article`, a banner `section`.\n */\n as?: StaticElement;\n}\n\n/** The name, sized as one. Type belongs to the arrangement, so `Card.Title` is this same object. */\nconst Block__Title = styled.div`\n color: var(--color-text-primary);\n font-weight: 600;\n /* One rung above the body the arrangement sets; both halves are declared here, so a site cannot break the step. */\n font-size: var(--font-size-value);\n line-height: var(--line-height-tight);\n /* A content-sized basis, so a long title wraps instead of running into the badge beside it. */\n flex: 1 1 auto;\n min-width: 0;\n`;\n\n/** The lead-in and the name, as one subject for the heading row to push against. */\nconst Block__TitleLead = styled.div`\n display: flex;\n align-items: baseline;\n gap: var(--gap-record-lead);\n flex: 1 1 auto;\n min-width: 0;\n`;\n\n/**\n * Props for `Block.TitleRow` (and `Card.TitleRow`): `left` is drawn before the\n * name, `right` is pushed to the end of the name's line.\n *\n * @category Layout\n */\nexport interface BlockTitleRowProps {\n /** Drawn before the name, such as an icon or a mark. Absent, the name starts the line. */\n left?: ReactNode;\n /** Pushed to the end of the name's line, usually the subject's state. Absent, nothing follows the name. */\n right?: ReactNode;\n /** The name itself. */\n children?: ReactNode;\n}\n\n/** The name's line: an optional lead-in, the name, and the state pushed to the end, via `SubjectHeading`. */\nfunction Block__TitleRow({ left, right, children }: BlockTitleRowProps) {\n return (\n <SubjectHeading align=\"baseline\" status={right}>\n {left == null ? (\n children\n ) : (\n <Block__TitleLead>\n {left}\n {children}\n </Block__TitleLead>\n )}\n </SubjectHeading>\n );\n}\n\n/**\n * The row the side asides sit in. The wrap IS the collapse: a `left` aside with\n * nowhere to go lands above the body, a `right` aside below it. Nothing is ever\n * hidden, unlike `Panel`'s aside.\n */\nconst Block__Middle = styled.div`\n display: flex;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: var(--gap-related);\n`;\n\n/**\n * What the side asides sit beside: the title row and the body, as one column,\n * so an aside spans both. `--block-body-floor` is the width below which the\n * body stops sharing a line; a zero basis would shrink forever and never wrap.\n */\nconst Block__Body = styled.div`\n display: flex;\n flex-direction: column;\n gap: var(--gap-related);\n flex: 1 1 var(--block-body-floor, 9rem);\n min-width: 0;\n`;\n\n/** A block-level aside. The side variants step `--radius-display-frame` down, so a `FramedDisplay` in `left` gets a corner proportioned to its small box. */\nconst Block__Aside = styled.div<{ $side?: boolean }>`\n min-width: 0;\n ${({ $side }) =>\n $side\n ? `flex: 0 0 auto;\n --radius-display-frame: var(--radius-display-frame-aside);`\n : \"\"}\n`;\n\n/** The actions and tags that close the record. Wraps rather than overflowing. */\nconst Block__Footer = styled.div`\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--gap-related);\n flex-wrap: wrap;\n`;\n\n/** The arrangement with no surface. `Card` and `Notice` extend it; outside this package the door is `Block`. */\nexport const Block__Root = styled.div`\n display: flex;\n flex-direction: column;\n gap: var(--gap-related);\n min-width: 0;\n /* The compact body size, declared here so the title stays one rung above it. */\n font-size: var(--font-size-compact);\n`;\n\n/**\n * Renders the anatomy inside whichever root it is handed. The title row and\n * children are direct children of the root while there is no side aside, so a caller may restyle\n * the root's flex direction.\n */\nexport function renderBlockAnatomy(\n Root: ElementType,\n {\n title,\n titleAs,\n titleLeft,\n titleRight,\n left,\n right,\n top,\n bottom,\n footer,\n children,\n ...rest\n }: BlockAnatomyProps & Record<string, unknown>,\n): ReactNode {\n const hasTitleRow = title != null || titleLeft != null || titleRight != null;\n const hasSideAside = left != null || right != null;\n const heading = hasTitleRow && (\n <Block__TitleRow left={titleLeft} right={titleRight}>\n {title != null && <Block__Title as={titleAs}>{title}</Block__Title>}\n </Block__TitleRow>\n );\n return (\n <Root {...rest}>\n {top != null && <Block__Aside $side={false}>{top}</Block__Aside>}\n {hasSideAside ? (\n <Block__Middle>\n {left != null && <Block__Aside $side>{left}</Block__Aside>}\n <Block__Body>\n {heading}\n {children}\n </Block__Body>\n {right != null && <Block__Aside $side>{right}</Block__Aside>}\n </Block__Middle>\n ) : (\n <>\n {heading}\n {children}\n </>\n )}\n {footer != null && <Block__Footer>{footer}</Block__Footer>}\n {bottom != null && <Block__Aside $side={false}>{bottom}</Block__Aside>}\n </Root>\n );\n}\n\n/** The parts, hung off every surface that composes them: `Card.Title === Block.Title`. */\nexport const BLOCK_PARTS = {\n Title: Block__Title,\n TitleRow: Block__TitleRow,\n Body: Block__Body,\n Aside: Block__Aside,\n Footer: Block__Footer,\n} as const;\n\nfunction BlockRoot({ ...props }: BlockProps): ReactNode {\n return renderBlockAnatomy(Block__Root, props);\n}\n\n/**\n * The arrangement of a record with no surface: a name sized as a heading, a\n * body under it, asides on any of four sides, and a footer. {@link Card} is the\n * same arrangement on a sunken surface.\n *\n * `titleRight` is not `right`: `titleRight` holds badges on the name's line and\n * wraps under the name when narrow, while `right` sits beside the whole body\n * and drops below it when narrow. The parts (`Block.Title`, `Block.TitleRow`,\n * `Block.Body`, `Block.Aside`, `Block.Footer`) are there for a record that\n * needs its own composition but the same type and spacing.\n *\n * @example\n * ```tsx\n * <Block\n * as=\"li\"\n * title={contract.title}\n * titleRight={<Badge tone=\"warn\">Expires soon</Badge>}\n * footer={<Button onClick={onAccept}>Accept</Button>}\n * >\n * <Text level=\"muted\">{contract.synopsis}</Text>\n * </Block>\n * ```\n *\n * @category Layout\n */\nexport const Block = Object.assign(BlockRoot, BLOCK_PARTS);\n","import type { ReactNode } from \"react\";\nimport { Cluster, type ClusterAlign } from \"./Cluster\";\nimport type { GapToken } from \"./scales\";\n\n/**\n * Props for {@link SubjectHeading}.\n *\n * @category Typography\n */\nexport interface SubjectHeadingProps {\n /** What the line is about: a program's title, a course's name, a vessel. */\n children: ReactNode;\n /**\n * That subject's state, as a `Badge` or a short run of them. Drawn AFTER the\n * subject and pushed to the end of the line; absent when there is no state\n * worth showing, which leaves the subject alone on the line rather than\n * beside a gap.\n */\n status?: ReactNode;\n /** Gap between the subject and its state. Defaults to `related-packed`. */\n gap?: GapToken;\n /**\n * `align-items`, for a subject that wraps to two lines beside a one-line\n * badge: `start` keeps the badge level with the first line of the name\n * instead of floating in the middle of it.\n */\n align?: ClusterAlign;\n}\n\n/**\n * A subject and its state on one line, subject first and the state pushed to\n * the end. The order is fixed, so the state always reads after the thing it\n * describes, to an eye and to a screen reader.\n *\n * The line wraps: a long subject drops its badge onto a second line rather\n * than squeezing the name.\n *\n * @example\n * ```tsx\n * <SubjectHeading\n * status={\n * <Badge tone=\"go\">Active</Badge>\n * }\n * >\n * <Text weight=\"semibold\">{program.title}</Text>\n * </SubjectHeading>\n * ```\n *\n * @category Typography\n */\nexport function SubjectHeading({\n children,\n status,\n gap = \"related-packed\",\n align = \"center\",\n}: Readonly<SubjectHeadingProps>) {\n return (\n <Cluster align={align} gap={gap} justify=\"between\" wrap>\n {children}\n {status}\n </Cluster>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { INSET_NAME, type InsetToken, RADIUS_VAR } from \"./scales\";\n\n/**\n * The background tiers {@link Box} can paint: `app` is the page behind every\n * widget, `panel` a widget's own body, `raised` something lifted off a panel\n * (a card, a popover) and `sunken` a well set into one.\n *\n * @category Layout\n */\nexport type BoxSurface = \"app\" | \"panel\" | \"raised\" | \"sunken\";\n\n/**\n * The corner radii {@link Box} accepts: `regular` for ordinary controls, rows\n * and cards, `floating` for something that sits above the app (a dialog, a\n * menu), and `pill` for a fully rounded stadium. The display-frame corner is\n * reserved for {@link FramedDisplay}.\n *\n * @category Layout\n */\nexport type BoxRadius = \"regular\" | \"floating\" | \"pill\";\n\n/**\n * The padding names {@link Box}'s `pad` prop accepts, the same set as\n * {@link InsetToken}.\n *\n * @category Layout\n */\nexport type BoxPad = InsetToken;\n\n/**\n * Props for {@link Box}. Any other `div` attribute passes through.\n *\n * @category Layout\n */\nexport interface BoxProps extends HTMLAttributes<HTMLDivElement> {\n /** Background surface tier. Omit for a transparent box. */\n surface?: BoxSurface;\n /** Padding, as the name of a surface inset. */\n pad?: BoxPad;\n /** Adds a `1px solid` subtle border. Defaults to `false`. */\n bordered?: boolean;\n /** Corner radius. Omit for square corners. */\n radius?: BoxRadius;\n children?: ReactNode;\n}\n\nconst SURFACE_VAR: Record<BoxSurface, string> = {\n app: \"var(--color-surface-app)\",\n panel: \"var(--color-surface-panel)\",\n raised: \"var(--color-surface-raised)\",\n sunken: \"var(--color-surface-sunken)\",\n};\n\n/**\n * A `div` that paints a surface: background tier, padding, a subtle border and\n * a corner radius, each optional. With no props it is a plain `div`. Use it for\n * a well or an inset block; use {@link Card} for a titled item in a list.\n *\n * @category Layout\n */\nexport function Box({\n surface,\n pad,\n bordered = false,\n radius,\n children,\n ...rest\n}: BoxProps) {\n return (\n <Box__Root\n $surface={surface}\n $pad={pad}\n $bordered={bordered}\n $radius={radius}\n {...rest}\n >\n {children}\n </Box__Root>\n );\n}\n\nconst Box__Root = styled.div<{\n $surface?: BoxSurface;\n $pad?: BoxPad;\n $bordered: boolean;\n $radius?: BoxRadius;\n}>`\n ${({ $surface }) => $surface && `background: ${SURFACE_VAR[$surface]};`}\n ${({ $bordered }) => $bordered && `border: 1px solid var(--color-border-subtle);`}\n ${({ $radius }) => $radius && `border-radius: ${RADIUS_VAR[$radius]};`}\n ${({ $pad }) => $pad && `padding: var(${INSET_NAME[$pad]});`}\n`;\n","import { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Props for {@link ButtonGroup}. Any other `div` attribute passes through, and\n * the ref reaches the root `div`.\n *\n * @category Button\n */\nexport interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Give every button the width of the widest one. On by default, since a row of\n * actions that differ only in the length of their word reads as a mistake.\n * Turn it off for a group whose members are meant to size to their own content.\n */\n equalWidth?: boolean;\n /** Gap between buttons, one of the {@link GapToken} names. Omitted, it is the container's `related` gap. */\n gap?: GapToken;\n children?: ReactNode;\n}\n\n/**\n * A row of related buttons that share a size: the same height whatever each\n * button's own `size` or content, and by default the same width. It hugs its\n * buttons rather than filling the row, so place it with a layout primitive.\n *\n * Name the group with `aria-label` when it is a toolbar a screen reader should\n * announce as one.\n *\n * @example\n * ```tsx\n * <ButtonGroup>\n * <Button variant=\"primary\">Accept</Button>\n * <Button>Decline</Button>\n * </ButtonGroup>\n * ```\n *\n * @category Button\n */\nexport const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(\n function ButtonGroup({ equalWidth = true, gap, children, ...rest }, ref) {\n return (\n <ButtonGroup__Root\n ref={ref}\n $equalWidth={equalWidth}\n $gap={gap}\n {...rest}\n >\n {children}\n </ButtonGroup__Root>\n );\n },\n);\n\nconst ButtonGroup__Root = styled.div<{ $equalWidth: boolean; $gap?: GapToken }>`\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: ${({ $equalWidth }) => ($equalWidth ? \"1fr\" : \"auto\")};\n align-items: stretch;\n width: max-content;\n max-width: 100%;\n gap: ${({ $gap }) => ($gap ? GAP_VAR[$gap] : \"var(--gap-related)\")};\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport {\n BLOCK_PARTS,\n Block__Root,\n type BlockProps,\n renderBlockAnatomy,\n} from \"./Block\";\nimport { toneEdge } from \"./tone\";\n\n/**\n * Props for {@link Card}: everything {@link BlockProps} takes, plus the\n * surface's own marks.\n *\n * @category Layout\n */\nexport interface CardProps extends BlockProps {\n /**\n * The record's status, drawn as a 2px rule down the leading edge. For what\n * the record is, use `identityColor`.\n */\n tone?: Tone;\n /**\n * Dims the card to signal unavailable-but-still-listed: a crew member on a\n * mission, a part not yet unlocked. Its text drops to the muted tier, which\n * still reads at 4.5:1, and its `tone` rule and identity tab go half-strength.\n */\n dimmed?: boolean;\n /**\n * What the record is, drawn as a short tab centred on the top edge: a\n * resource's own colour, a category's hue. Not a status, which is `tone`;\n * a card can have both.\n *\n * A CSS colour, not a resource name: resolve a name first with\n * {@link resourceColor}, as in `identityColor={resourceColor(name)}`.\n */\n identityColor?: string;\n /**\n * The roomier inset for a card that is its screen rather than one record in a\n * list, such as a panel of device cards each holding its own controls. Only\n * this card's padding changes, not the spacing of what it holds.\n */\n standalone?: boolean;\n children?: ReactNode;\n}\n\n/** Wider than the 2px `tone` rule, so identity reads as a different mark from status. */\nconst STRIP_WIDTH = \"3px\";\n\n/**\n * `Block` plus a surface, as a styled extension of the same root.\n *\n * A card is the COMPACT density tier: it re-declares `--gap-related` and\n * `--gap-section` one rung down, so anything inside it written as\n * `gap: var(--gap-related)` tightens without a conditional, including a third\n * party's CSS. Only those two gap names step; insets do not, because a chip\n * inside a card is still a chip.\n */\nconst Card__Root = styled(Block__Root)<{\n $tone?: Tone;\n $dimmed?: boolean;\n $identityColor?: string;\n $standalone?: boolean;\n}>`\n --gap-related: var(--gap-related-compact);\n --gap-section: var(--gap-section-compact);\n --bleed-inline: 0px;\n\n position: relative;\n background: var(--color-surface-sunken);\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n ${({ $standalone }) =>\n $standalone\n ? \"padding: var(--inset-surface-standalone);\"\n : \"padding: var(--inset-surface);\"}\n ${({ $tone }) => ($tone ? `border-left: 2px solid ${toneEdge($tone)};` : \"\")}\n ${({ $identityColor }) =>\n $identityColor\n ? `\n &::before {\n content: \"\";\n position: absolute;\n top: -1px;\n left: 50%;\n transform: translateX(-50%);\n width: var(--size-mark);\n height: ${STRIP_WIDTH};\n background: ${$identityColor};\n border-radius: var(--radius-regular) var(--radius-regular) 0 0;\n }\n `\n : \"\"}\n ${({ $dimmed, $tone }) =>\n $dimmed\n ? `\n --color-text-primary: var(--color-text-muted);\n color: var(--color-text-muted);\n ${$tone ? `border-left-color: color-mix(in srgb, ${toneEdge($tone)} 50%, transparent);` : \"\"}\n &::before {\n opacity: 0.5;\n }\n `\n : \"\"}\n`;\n\nfunction CardRoot({\n tone,\n dimmed,\n identityColor,\n standalone,\n ...props\n}: CardProps): ReactNode {\n return renderBlockAnatomy(Card__Root, {\n ...props,\n $tone: tone,\n $dimmed: dimmed,\n $identityColor: identityColor,\n $standalone: standalone,\n });\n}\n\n/**\n * A record on a sunken surface: {@link Block}'s arrangement inside a bordered,\n * padded box. Its parts are `Block`'s own (`Card.Title === Block.Title`); use\n * `Block` for the grouping without the box.\n *\n * Inside a card the `related` and `section` gaps are one step smaller, so a\n * {@link Stack} or {@link Cluster} with no `gap` of its own is tighter there\n * without being told. Padding sizes do not change.\n *\n * @example\n * ```tsx\n * <Card\n * title={tank.resource}\n * titleRight={tank.low && <Badge tone=\"warn\">Low</Badge>}\n * tone={tank.low ? \"warn\" : undefined}\n * dimmed={tank.locked}\n * identityColor={resourceColor(tank.resource)}\n * >\n * <Unit value={tank.amount} />\n * </Card>\n * ```\n *\n * @category Layout\n */\nexport const Card = Object.assign(CardRoot, BLOCK_PARTS);\n","import { classifyCommandRejection } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport styled, { css, keyframes } from \"styled-components\";\nimport { Button__Body, type ButtonTone } from \"../Button\";\nimport type { CommandDelayHandle } from \"../CommandDelay/CommandDelay\";\nimport {\n type CommandFoundLike,\n commandFoundSentence,\n} from \"../CommandDelay/commandFoundSentence\";\nimport { commandLossSentence } from \"../CommandDelay/commandLossSentence\";\nimport { commandOutcomes } from \"../CommandDelay/commandOutcomes\";\nimport {\n type CommandRefusalLike,\n commandGateSentence,\n commandRefusalSentence,\n} from \"../CommandDelay/commandRefusalSentence\";\nimport { FitLabel } from \"../FitLabelButton\";\nimport { LiveRegion } from \"../LiveRegion\";\nimport { Tooltip } from \"../Tooltip\";\nimport { InFlightFace } from \"./InFlightFace\";\n\n/**\n * How long, in milliseconds, an armed {@link CommandButton} waits for its confirming press before it returns to rest.\n *\n * @category CommandButton\n */\nexport const ARM_TIMEOUT_MS = 4000;\n\n/**\n * How long a refusal stays on the control before it returns to rest: long\n * enough to read, not forever, because the situation the game refused on can\n * change.\n */\nexport const REFUSAL_TIMEOUT_MS = 8000;\n\n/**\n * The outer backstop on a pending dispatch. `send()`'s promise is guaranteed\n * to settle; this is a belt for a handle from some other source that makes\n * no such guarantee, never the primary clear.\n */\nexport const PENDING_BACKSTOP_MS = 30_000;\n\n/**\n * What the mod says about a command before it is pressed. Declared structurally; `useCommand`'s `gate` satisfies it.\n *\n * @category CommandButton\n */\nexport interface CommandGateLike extends CommandRefusalLike {\n /** The game EVALUATED this and said no. */\n blocked: boolean;\n /**\n * The mod could not evaluate this command's gates at all, as in a sandbox\n * save. The control is drawn as ordinary, not darkened, and carries\n * `data-gate` to say so.\n */\n undetermined?: boolean;\n}\n\n/**\n * What a command reply is known to be BEFORE you know which command produced\n * it: the result envelope, carrying the command's own value on `payload`. The\n * default {@link CommandButtonHandle} reply, so a reader cannot treat the\n * envelope as the payload; `payload` stays `unknown` until narrowed.\n *\n * @category CommandButton\n */\nexport interface CommandReplyLike {\n /** Whether the command ran. False pairs with the refusal the handle surfaces. */\n success: boolean;\n /** The command's own value, when it has one. `unknown` until the reader knows which command this is. */\n payload?: unknown;\n}\n\n/**\n * The command handle a {@link CommandButton} dispatches on: the delay-rail handle plus a\n * way to send. Declared structurally; {@link useCommand}'s return value satisfies it.\n *\n * @category CommandButton\n */\nexport interface CommandButtonHandle<Result = CommandReplyLike, Args = unknown>\n extends CommandDelayHandle {\n // A method rather than a function-valued property, so strictFunctionTypes does not check typed args contravariantly and reject every typed handle.\n /**\n * Dispatch. The promise resolves when the command is confirmed and rejects\n * when it is refused, lost, or the machinery failed, so the control clears its\n * own pending state with no per-command telemetry predicate.\n *\n * `Result` carries the reply's real type to\n * {@link CommandButtonProps.onConfirmed}; a handle from `useCommand(\"...\")`\n * supplies it from the generated command map.\n */\n send(args?: Args, opts?: { label?: string; topic?: string }): Promise<Result>;\n /**\n * The standing gate verdict, when the mod publishes one for this command.\n * Absent means nothing is known in advance.\n */\n gate?: CommandGateLike;\n /**\n * The standing verdict for a call with these arguments, when the command's\n * gate depends on which item they name. `useCommand` supplies it; a control\n * that sends arguments reads it in preference to {@link gate}.\n */\n gateFor?(args: Args): CommandGateLike | undefined;\n}\n\n/**\n * The standing verdict a control sending `args` draws itself from: the\n * per-item one when the handle can say, else the command's own.\n *\n * @category CommandButton\n */\nexport function standingGate<Args>(\n handle: Pick<CommandButtonHandle<unknown, Args>, \"gate\" | \"gateFor\">,\n args: Args | undefined,\n): CommandGateLike | undefined {\n return handle.gateFor ? handle.gateFor(args as Args) : handle.gate;\n}\n\n/**\n * Where a {@link CommandButton} is in the command lifecycle.\n *\n * - `idle`: at rest\n * - `armed`: the operator has asked, and is being asked to mean it. Only\n * reachable when the caller supplied a `confirmLabel`\n * - `pending`: dispatched, nothing back yet (the signal-delay window)\n * - `refused`: the game evaluated it and said no. A retry changes nothing until\n * the situation does\n * - `lost`: nothing came back. Not `idle`, which would look like a confirmed\n * command, and not `refused`, because the game decided nothing and the\n * command may have executed\n * - `found`: this control lost a command, and that command has since replied.\n * Not a confirmation: the operator may already have re-sent it\n * - `blocked`: the game will refuse this, and said so before anyone pressed.\n * The control is dark but NOT `disabled`: it carries `aria-disabled`, stays\n * focusable, and responds to a press by showing why. A gate verdict is\n * advice, sampled and possibly a beat behind, and the dispatch is evaluated\n * again by the game anyway\n *\n * @category CommandButton\n */\nexport type CommandButtonPhase =\n | \"idle\"\n | \"armed\"\n | \"pending\"\n | \"refused\"\n | \"lost\"\n | \"found\"\n | \"blocked\";\n\n/**\n * The tones a {@link CommandButton} can draw in; the same set as a plain `Button`.\n *\n * @category CommandButton\n */\nexport type CommandButtonTone = ButtonTone;\n\n/**\n * The two sizes of {@link CommandButton}.\n *\n * @category CommandButton\n */\nexport type CommandButtonSize = \"sm\" | \"md\";\n\n/**\n * Options for {@link useCommandButton}.\n *\n * @category CommandButton\n */\nexport interface UseCommandButtonOptions<\n Result = CommandReplyLike,\n Args = unknown,\n> {\n /** The command the control dispatches, usually the return of `useCommand`. */\n handle: CommandButtonHandle<Result, Args>;\n /** Args for the dispatch. See {@link CommandButtonProps.args}. */\n args?: NoInfer<Args>;\n /** The dispatch's operator-facing description, which a refusal or loss sentence is named after. */\n commandLabel?: string;\n /** Receives the dispatch's resolved result. See {@link CommandButtonProps.onConfirmed}. */\n onConfirmed?: (result: Result) => void;\n}\n\n/**\n * What {@link useCommandButton} returns: the current phase, a flag per phase,\n * the composed sentences to show, and `press`.\n *\n * @category CommandButton\n */\nexport interface CommandButtonState {\n /** The current phase. `blocked` whenever the gate refuses and no outcome or dispatch is standing. */\n phase: CommandButtonPhase;\n /** Dispatched, nothing back. */\n isPending: boolean;\n /** Asking the operator to mean it. */\n isArmed: boolean;\n /** The game said no, and `refusalText` says what it said. */\n isRefused: boolean;\n /** Nothing came back. Not a verdict, and not the same as at rest. */\n isLost: boolean;\n /** A command this control lost has since replied. `foundText` says what the reply was. */\n isFound: boolean;\n /**\n * The game will refuse this if it is pressed, and said so in advance.\n * `refusalText` carries the reason here too, so a caller renders one string\n * whichever side of the press it is on.\n */\n isBlocked: boolean;\n /**\n * The operator pressed a blocked control and is being told why: the reason\n * made visible on demand, for the sighted keyboard user a `title` never\n * reaches.\n */\n isShowingReason: boolean;\n /**\n * The composed sentence, or `null` when there is nothing to say: a refusal\n * once one has happened, else the standing gate reason.\n */\n refusalText: string | null;\n /**\n * What the recovered command turned out to have done, composed, or `null`\n * outside the `found` phase. Kept apart from `refusalText`, which is true at a\n * different moment.\n */\n foundText: string | null;\n /**\n * What is known about a command that got no reply, composed, or `null`\n * outside the `lost` phase: the sentence `CommandButton` speaks for it.\n */\n lossText: string | null;\n /** This handle has a dispatch with no reply (overdue or lost), for the `data-unconfirmed` tint. */\n hasUnconfirmed: boolean;\n /** This handle has a dispatch that never left, for the `data-failed` tint. */\n hasFailure: boolean;\n /**\n * The control was pressed. Advances the machine: arm, then dispatch; a press\n * while pending is ignored; a press while refused, lost or found clears that\n * outcome; a press while BLOCKED dispatches nothing and shows the reason\n * instead. `armable` says whether there is a confirm step, which only the\n * caller knows.\n */\n press: (armable: boolean) => void;\n}\n\n/**\n * The {@link CommandButton} lifecycle with no rendering attached, for a control\n * that looks different. The hook keeps the phase, the arm timeout and the\n * reply handling; the caller draws each phase and wires its own click to\n * `press`. `CommandButton` is this hook plus the default rendering.\n *\n * @example\n * ```tsx\n * function RecoverButton() {\n * const recover = useCommand(\"ksp.recover\");\n * const { isArmed, isPending, isRefused, refusalText, press } =\n * useCommandButton({ handle: recover, commandLabel: \"Recover\" });\n * const word = isPending\n * ? \"Recovering...\"\n * : isRefused\n * ? \"Refused\"\n * : isArmed\n * ? \"Confirm recover\"\n * : \"Recover\";\n * return (\n * <button\n * type=\"button\"\n * aria-busy={isPending || undefined}\n * title={refusalText ?? undefined}\n * onClick={() => press(true)}\n * >\n * {word}\n * </button>\n * );\n * }\n * ```\n *\n * @category CommandButton\n */\nexport function useCommandButton<Result = CommandReplyLike, Args = unknown>({\n handle,\n args,\n commandLabel,\n onConfirmed,\n}: UseCommandButtonOptions<Result, Args>): CommandButtonState {\n const [phase, setPhase] = useState<CommandButtonPhase>(\"idle\");\n const [refusal, setRefusal] = useState<CommandRefusalLike | null>(null);\n const [found, setFound] = useState<CommandFoundLike | null>(null);\n // A press on a blocked control shows its reason, cleared on the refusal window.\n const [reasonShown, setReasonShown] = useState(false);\n\n const gate = standingGate(handle, args);\n // An undetermined gate is NOT a refusal and must not darken anything.\n const gateBlocks = gate?.blocked === true;\n\n // A late reply must not set state after unmount or overwrite a newer dispatch's pending.\n const mountedRef = useRef(true);\n const dispatchSeqRef = useRef(0);\n useEffect(\n () => () => {\n mountedRef.current = false;\n },\n [],\n );\n\n /*\n * A command THIS control lost, turning up answered. One handle often serves\n * many rows, so the phase is gated on `awaitingFoundRef` (set when this\n * control's own dispatch settled lost). Watched off the handle, because the\n * dispatch promise already rejected as lost and never settles again.\n */\n const founds = handle.founds;\n const awaitingFoundRef = useRef(false);\n const foundsSeenRef = useRef(founds?.length ?? 0);\n useEffect(() => {\n const count = founds?.length ?? 0;\n const grew = count > foundsSeenRef.current;\n foundsSeenRef.current = count;\n const latest = founds?.[count - 1];\n if (!grew || !latest || !awaitingFoundRef.current) return;\n awaitingFoundRef.current = false;\n setFound(latest);\n setRefusal(null);\n setPhase(\"found\");\n }, [founds]);\n\n // Auto-disarm, so a forgotten arm does not sit live indefinitely.\n useEffect(() => {\n if (phase !== \"armed\") return;\n const id = setTimeout(() => setPhase(\"idle\"), ARM_TIMEOUT_MS);\n return () => clearTimeout(id);\n }, [phase]);\n\n // Let a refusal, a silence or a found be read, then return to rest.\n useEffect(() => {\n if (phase !== \"refused\" && phase !== \"lost\" && phase !== \"found\") return;\n const id = setTimeout(() => {\n setPhase(\"idle\");\n setRefusal(null);\n // The rail holds a found until dismissed; this is only the echo on the control.\n setFound(null);\n }, REFUSAL_TIMEOUT_MS);\n return () => clearTimeout(id);\n }, [phase]);\n\n useEffect(() => {\n if (!reasonShown) return;\n const id = setTimeout(() => setReasonShown(false), REFUSAL_TIMEOUT_MS);\n return () => clearTimeout(id);\n }, [reasonShown]);\n\n // The gate reopening takes the reason down with it.\n useEffect(() => {\n if (!gateBlocks) setReasonShown(false);\n }, [gateBlocks]);\n\n useEffect(() => {\n if (phase !== \"pending\") return;\n const id = setTimeout(() => setPhase(\"idle\"), PENDING_BACKSTOP_MS);\n return () => clearTimeout(id);\n }, [phase]);\n\n const dispatch = useCallback(() => {\n const seq = dispatchSeqRef.current + 1;\n dispatchSeqRef.current = seq;\n setPhase(\"pending\");\n setRefusal(null);\n const settle = (\n next: CommandButtonPhase,\n reason: CommandRefusalLike | null,\n ) => {\n if (!mountedRef.current || dispatchSeqRef.current !== seq) return;\n setRefusal(reason);\n setPhase(next);\n };\n handle.send(args, commandLabel ? { label: commandLabel } : undefined).then(\n (result: Result) => {\n settle(\"idle\", null);\n onConfirmed?.(result);\n },\n (err: unknown) => {\n const rejection = classifyCommandRejection(err);\n if (rejection.kind === \"lost\") {\n // From here on, a found landing on the handle is this control's.\n awaitingFoundRef.current = true;\n settle(\"lost\", null);\n return;\n }\n if (rejection.kind !== \"refused\") {\n // A machinery failure is the rail's to report, not this control's.\n settle(\"idle\", null);\n return;\n }\n settle(\"refused\", {\n errorCode: rejection.errorCode,\n reason: rejection.reason,\n command: rejection.command,\n args: rejection.args,\n label: rejection.label ?? commandLabel,\n breach: rejection.breach,\n // The game's own words, which `commandRefusalSentence` prefers over its general clause.\n detail: rejection.detail,\n });\n },\n );\n }, [handle, args, commandLabel, onConfirmed]);\n\n // The rail is the primary outcome surface; these tints only say WHICH control issued the command.\n const { hasUnconfirmed, hasFailure } = commandOutcomes(handle);\n\n const press = useCallback(\n (armable: boolean) => {\n if (phase === \"pending\") return;\n // The game already said it will refuse this, so the press shows why instead of spending a round trip.\n if (gateBlocks) {\n setReasonShown(true);\n return;\n }\n // A press on an outcome clears it and starts the handshake over, rather than dispatching straight back.\n if (phase === \"refused\" || phase === \"lost\" || phase === \"found\") {\n setRefusal(null);\n setFound(null);\n setPhase(\"idle\");\n return;\n }\n if (armable && phase !== \"armed\") {\n setPhase(\"armed\");\n return;\n }\n dispatch();\n },\n [phase, gateBlocks, dispatch],\n );\n\n // A real outcome outranks a standing gate, and a command already travelling is not stopped by a gate that shut behind it.\n const effectivePhase: CommandButtonPhase =\n phase === \"pending\" ||\n phase === \"refused\" ||\n phase === \"lost\" ||\n phase === \"found\"\n ? phase\n : gateBlocks\n ? \"blocked\"\n : phase;\n\n return {\n phase: effectivePhase,\n isPending: effectivePhase === \"pending\",\n isArmed: effectivePhase === \"armed\",\n isRefused: effectivePhase === \"refused\",\n isLost: effectivePhase === \"lost\",\n isFound: effectivePhase === \"found\",\n isBlocked: effectivePhase === \"blocked\",\n isShowingReason: effectivePhase === \"blocked\" && reasonShown,\n refusalText: refusal\n ? commandRefusalSentence(refusal)\n : effectivePhase === \"blocked\" && gate\n ? // The mod's gate is per command, so the caller's label says which row went dark.\n commandGateSentence({\n ...gate,\n label: gate.label ?? commandLabel,\n args: gate.args ?? args,\n })\n : null,\n foundText: found ? commandFoundSentence(found) : null,\n lossText:\n effectivePhase === \"lost\"\n ? commandLossSentence({ args, label: commandLabel })\n : null,\n hasUnconfirmed,\n hasFailure,\n press,\n };\n}\n\ntype NativeButtonProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"onClick\" | \"type\" | \"children\" | \"aria-pressed\" | \"aria-busy\" | \"title\"\n>;\n\n/**\n * Props for {@link CommandButton}. Any other native button attribute is passed through.\n *\n * @category CommandButton\n */\nexport interface CommandButtonProps<Result = CommandReplyLike, Args = unknown>\n extends NativeButtonProps {\n /**\n * The command this control dispatches. Its reply type is what\n * {@link CommandButtonProps.onConfirmed} receives, inferred.\n */\n handle: CommandButtonHandle<Result, Args>;\n /**\n * Args for the dispatch, passed straight to `handle.send` and checked\n * against what that command takes. `NoInfer`, so a wrong args object cannot\n * widen `Args` to its own shape.\n */\n args?: NoInfer<Args>;\n /**\n * The dispatch's operator-facing description, which a refusal is named\n * after (\"Hire Valentina Kerman refused: ...\").\n */\n commandLabel?: string;\n /**\n * What the control says on hover and keyboard focus while it is at rest or\n * armed, in the kit's tip. An outcome's own sentence replaces it while one\n * stands. It is not the accessible name: give `aria-label` for that.\n */\n title?: string;\n /** The resting label. */\n label: ReactNode;\n /**\n * The armed label. Supplying it makes this an arm-then-confirm control: the\n * first click puts it in the armed phase, the second dispatches, and an armed\n * control left alone returns to rest after {@link ARM_TIMEOUT_MS}. Omit it for\n * a control that dispatches on one click.\n *\n * Arm anything irreversible, and anything that spends career funds.\n */\n confirmLabel?: ReactNode;\n /**\n * The in-flight wording, spoken and shown on hover. The control itself draws\n * only a spinner while in flight, at its resting width. Defaults to\n * \"Working...\".\n */\n pendingLabel?: string;\n /** The refused label. Defaults to \"Refused\". */\n refusedLabel?: ReactNode;\n /**\n * The label for a dispatch that got no reply. Defaults to \"No reply\": not\n * \"failed\" and not \"refused\", neither of which is known.\n */\n lostLabel?: ReactNode;\n /**\n * The label for a command this control lost that has since replied.\n * Defaults to \"Found\", with the whole sentence on the accessible name and\n * title. Not \"Confirmed\": the operator was told to give up on it.\n */\n foundLabel?: ReactNode;\n /**\n * The blocked phase's accessible name, for a control whose gate reason is\n * already spelled out beside it. Omit it and the accessible name is the\n * composed gate sentence.\n */\n blockedAriaLabel?: string;\n /**\n * The armed phase's accessible name, for a control whose resting\n * `aria-label` says more than its visible word (\"Hire Desdin Kerman for\n * 30,000 funds\"). Omit it and the visible confirm wording is the accessible\n * name, never the resting label, since the next press means something\n * different.\n */\n confirmAriaLabel?: string;\n /** The in-flight phase's accessible name, for a control whose resting name says more than `pendingLabel`. Defaults to `pendingLabel`. */\n pendingAriaLabel?: string;\n /**\n * Whether this control's command is CURRENTLY IN EFFECT, for a control that\n * represents state as well as acting on it: a SAS toggle, an action group, an\n * activated strategy. Sets `aria-pressed` and the active fill. Leave it\n * undefined for a control that only acts.\n */\n active?: boolean;\n /**\n * How much the control asks to be pressed at rest. `ghost` (default) is the\n * quiet outline that fills only while active, armed or refused; `primary`\n * is filled in `tone` at rest, for the one verb of a group that commits.\n */\n variant?: \"ghost\" | \"primary\";\n /**\n * The tone of the fill while `active`, and of the resting fill for a\n * `primary` control. Defaults to `neutral`.\n */\n tone?: CommandButtonTone;\n /**\n * Shown in place of the word when the word does not fit the control's width,\n * measured against the control itself and following resizes, for a control in\n * a narrow cell. The word stays the accessible name in both states. Needs a\n * plain-text label and, when set, `confirmLabel`.\n */\n icon?: ReactNode;\n /** Shown in place of the confirm word when it does not fit. Defaults to {@link CommandButtonProps.icon}. */\n confirmIcon?: ReactNode;\n /**\n * Called with the control's press while a click on it would do something\n * (not while it is in flight) and with `null` when it would not, for a\n * caller that lets an input press the control from elsewhere. The press\n * takes whether the control has a confirm step.\n */\n onPressReady?: (press: ((armable: boolean) => void) | null) => void;\n /** Defaults to `md`. */\n size?: CommandButtonSize;\n /** Tone for the armed phase. Defaults to `go`: confirm reads as commit. */\n confirmTone?: CommandButtonTone;\n /**\n * Called once a dispatch is confirmed, for a caller with local state to\n * settle. The pending state itself needs nothing from you.\n *\n * Receives what the dispatch resolved with, typed off the handle. A confirmed\n * command did not necessarily do something: a mod that de-duplicates on\n * request id replies to a repeat with the receipt it stored the first time. The\n * value is the reply envelope; the command's own value is on `payload`.\n */\n onConfirmed?: (result: Result) => void;\n}\n\n/**\n * A button that dispatches a command and shows what became of it: at rest,\n * armed (with a `confirmLabel`), in flight, refused, lost (no reply came in\n * time), found (a lost command has since replied), or blocked. It is blocked\n * while the mod says in advance that the command would be refused, such as a\n * purchase the save cannot afford, and the button then says why. The panel's\n * delay rail shows that a command is in flight; this shows which control sent\n * it.\n *\n * Pending ends when the promise `send()` returned settles, whether or not any\n * telemetry changes as a result. Pending is per RENDERED CONTROL, not\n * per handle, so one handle serving a list gives each row its own pending\n * state. Outcomes are announced through a polite live region.\n *\n * @example\n * ```tsx\n * const transmitCmd = useCommand(\"science.experiment.transmit\");\n *\n * <CommandButton\n * size=\"sm\"\n * handle={transmitCmd}\n * args={{ partId }}\n * commandLabel=\"Transmit experiment\"\n * label=\"Transmit\"\n * confirmLabel=\"Confirm transmit\"\n * pendingLabel=\"Transmitting...\"\n * />\n * ```\n *\n * @category CommandButton\n * @categoryDescription CommandButton\n * A control that sends a command and shows what became of it: armed, in flight,\n * refused, lost, found again or blocked, with the outcome announced. Every control that\n * dispatches a command handle is one of these.\n */\nexport function CommandButton<Result = CommandReplyLike, Args = unknown>({\n handle,\n args,\n commandLabel,\n label,\n confirmLabel,\n pendingLabel = \"Working...\",\n refusedLabel = \"Refused\",\n lostLabel = \"No reply\",\n foundLabel = \"Found\",\n confirmAriaLabel,\n pendingAriaLabel,\n blockedAriaLabel,\n active,\n variant = \"ghost\",\n tone = \"neutral\",\n icon,\n confirmIcon,\n onPressReady,\n size = \"md\",\n confirmTone = \"go\",\n onConfirmed,\n disabled,\n title,\n \"aria-label\": ariaLabel,\n ...rest\n}: Readonly<CommandButtonProps<Result, Args>>) {\n const {\n phase,\n isPending,\n isArmed,\n isRefused,\n isLost,\n isFound,\n isBlocked,\n isShowingReason,\n refusalText,\n foundText,\n lossText,\n hasUnconfirmed,\n hasFailure,\n press,\n } = useCommandButton({ handle, args, commandLabel, onConfirmed });\n\n useEffect(() => {\n if (!onPressReady || isPending) return;\n onPressReady(press);\n return () => onPressReady(null);\n }, [onPressReady, isPending, press]);\n\n const resolveBody = (): ReactNode => {\n if (isPending)\n return (\n <InFlightFace\n holds={label}\n label={pendingLabel}\n spinnerSize={size === \"sm\" ? 10 : 12}\n />\n );\n if (isRefused) return refusedLabel;\n if (isLost) return lostLabel;\n if (isFound) return foundLabel;\n // The reason IN the control. It runs long with the numbers at the end, so it must wrap, never truncate.\n if (isShowingReason) return refusalText;\n if (isArmed) return confirmLabel;\n return label;\n };\n const resolved = resolveBody();\n const fitWord = icon !== undefined && typeof resolved === \"string\";\n const body = fitWord ? (\n <FitLabel label={resolved} icon={isArmed ? (confirmIcon ?? icon) : icon} />\n ) : (\n resolved\n );\n /*\n * An arm-then-confirm control keeps the width of its wider word in both\n * states, so arming it never reflows the row it sits in. Only plain-text\n * labels can be measured this way; a node label sizes itself as before.\n */\n const reserve =\n icon === undefined &&\n typeof label === \"string\" &&\n typeof confirmLabel === \"string\" &&\n !isRefused &&\n !isLost &&\n !isFound &&\n !isShowingReason;\n\n const outcome = isRefused\n ? refusalText\n : isLost\n ? lossText\n : isFound\n ? foundText\n : null;\n\n const filled = active === true || isArmed || isRefused;\n const drawnTone = commandTone({\n restTone: variant === \"primary\" ? tone : \"neutral\",\n filled,\n isRefused,\n isFound,\n isArmed,\n confirmTone,\n tone,\n });\n\n return (\n <>\n <Tooltip\n text={foundText ?? refusalText ?? (isPending ? pendingLabel : title)}\n >\n <CommandButton__Body\n type=\"button\"\n $tone={drawnTone}\n $variant={variant}\n $fit={fitWord}\n $size={size}\n $pressed={filled}\n $armed={isArmed}\n $blocked={isBlocked}\n aria-pressed={active}\n aria-busy={isPending || undefined}\n // aria-disabled, not disabled, so the control keeps focus while the outcome lands on it.\n aria-disabled={isBlocked || isPending || undefined}\n disabled={disabled}\n data-unconfirmed={hasUnconfirmed ? \"true\" : undefined}\n data-failed={hasFailure ? \"true\" : undefined}\n data-command-phase={phase}\n // A diagnostic hook only: an undetermined gate renders as an ordinary control.\n data-gate={\n isBlocked\n ? \"blocked\"\n : standingGate(handle, args)?.undetermined\n ? \"undetermined\"\n : undefined\n }\n /*\n * The accessible name tracks the phase: an outcome's full sentence while\n * it stands, and for armed and pending the visible wording rather than\n * the resting label, which describes a state the control has left.\n */\n aria-label={\n (isRefused\n ? (refusalText ?? undefined)\n : isLost\n ? (lossText ?? undefined)\n : isFound\n ? (foundText ?? ariaLabel)\n : isBlocked\n ? (blockedAriaLabel ?? refusalText ?? ariaLabel)\n : isPending\n ? (pendingAriaLabel ?? pendingLabel)\n : isArmed\n ? confirmAriaLabel\n : ariaLabel) ?? (fitWord ? resolved : undefined)\n }\n onClick={() => press(confirmLabel !== undefined)}\n data-rest-label={reserve ? label : undefined}\n data-armed-label={reserve ? confirmLabel : undefined}\n {...rest}\n >\n {reserve ? <CommandButton__Face>{body}</CommandButton__Face> : body}\n </CommandButton__Body>\n </Tooltip>\n {/* Mounted with the control, so an outcome lands in a region assistive tech is already watching. */}\n <LiveRegion visuallyHidden>{outcome}</LiveRegion>\n </>\n );\n}\n\n/** The tone a command draws in: plain at rest, then the tone of the phase it is filled for. */\nfunction commandTone(p: {\n restTone: CommandButtonTone;\n filled: boolean;\n isRefused: boolean;\n isFound: boolean;\n isArmed: boolean;\n confirmTone: CommandButtonTone;\n tone: CommandButtonTone;\n}): CommandButtonTone {\n if (!p.filled) return p.restTone;\n if (p.isRefused) return \"warn\";\n // `found` reverses a warning, so it does not wear the warning's colour.\n if (p.isFound) return \"neutral\";\n if (p.isArmed) return p.confirmTone;\n return p.tone;\n}\n\nconst armedPulse = keyframes`\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.65; }\n`;\n\nconst CommandButton__Face = styled.span`\n grid-area: 1 / 1;\n /* First in the cell's order, so the button's baseline is the face's and not an invisible sizer's. */\n order: -1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gap-glyph);\n`;\n\n/* A command at rest is the quiet ghost; in effect, armed or refused it takes the one pressed fill. */\nconst CommandButton__Body = styled(Button__Body)<{\n $armed: boolean;\n $blocked: boolean;\n $fit: boolean;\n}>`\n letter-spacing: 0.04em;\n\n /* Shrinks below its word so the word can be measured and give way to the icon. */\n ${({ $fit }) => ($fit ? \"min-width: 0;\" : \"\")}\n\n ${({ $armed }) =>\n $armed &&\n css`\n @media (prefers-reduced-motion: no-preference) {\n animation: ${armedPulse} 1s var(--ease-emphasis) infinite;\n }\n `}\n\n &:disabled {\n opacity: 0.5;\n }\n\n /* Dimmed toward the muted text token rather than faded, so the refusal reason stays readable; the warn border says the game refused. */\n ${({ $blocked }) =>\n $blocked &&\n css`\n background: transparent;\n border-style: dashed;\n border-color: var(--color-warn-mark);\n color: var(--color-text-muted);\n cursor: help;\n\n @media (hover: hover) {\n &:hover:not(:disabled) {\n border-color: var(--color-warn-mark);\n color: var(--color-warn-text);\n }\n }\n `}\n\n /* Both labels sit invisibly in the one grid cell with the face, so the box is as wide as the wider of them. */\n &[data-rest-label] {\n display: inline-grid;\n justify-items: center;\n }\n &[data-rest-label]::before,\n &[data-rest-label]::after {\n grid-area: 1 / 1;\n visibility: hidden;\n height: 0;\n }\n &[data-rest-label]::before {\n content: attr(data-rest-label);\n }\n &[data-rest-label]::after {\n content: attr(data-armed-label);\n }\n\n /* In flight, not unavailable: full strength with a spinner. */\n &[aria-busy=\"true\"] {\n opacity: 1;\n cursor: progress;\n }\n`;\n","import type { CommandDelayHandle } from \"./CommandDelay\";\nimport type { InFlightCommandLike } from \"./toInFlightListItems\";\n\n/**\n * What became of a handle's dispatches, as the control that issued them echoes it.\n *\n * @category CommandButton\n */\nexport interface CommandOutcomes {\n /**\n * This handle's own dispatches that went past their reply with no answer\n * (overdue or lost) and HAVE an in-flight row. They may still have run.\n */\n unconfirmed: InFlightCommandLike[];\n /** True when any dispatch is unconfirmed, including a loss that never had a row. For a control's `data-unconfirmed` tint. */\n hasUnconfirmed: boolean;\n /** True when a dispatch never left this machine, or the mod answered it with a fault. For a control's `data-failed` tint. */\n hasFailure: boolean;\n /**\n * Clear an entry, the SAME dismiss the widget-top queue uses, so clearing on\n * the control clears it in the queue too.\n */\n dismiss: (id: string) => void;\n}\n\n/**\n * A handle's unanswered and undelivered dispatches plus the shared `dismiss`,\n * so the control that issued the command can echo what became of it while the\n * Panel rail stays the primary surface. An unanswered command may still run,\n * so it is unconfirmed and never counted as a failure.\n *\n * @category CommandButton\n */\nexport function commandOutcomes(handle: CommandDelayHandle): CommandOutcomes {\n const unconfirmed = handle.inFlight.filter(\n (c) => c.predictedPhase === \"overdue\" || c.predictedPhase === \"lost\",\n );\n return {\n unconfirmed,\n hasUnconfirmed: unconfirmed.length > 0 || (handle.losses?.length ?? 0) > 0,\n hasFailure:\n (handle.undelivered?.length ?? 0) > 0 ||\n (handle.failures?.length ?? 0) > 0,\n dismiss: handle.dismiss ?? (() => {}),\n };\n}\n","import {\n type ComponentPropsWithoutRef,\n forwardRef,\n type ReactNode,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport styled from \"styled-components\";\n\n/**\n * Props for {@link FitLabelButton}. Any other `button` attribute passes\n * through; `children` is replaced by `label` and `icon`.\n *\n * @category Button\n */\nexport interface FitLabelButtonProps\n extends Omit<ComponentPropsWithoutRef<\"button\">, \"children\"> {\n /**\n * The button's word and its accessible name, in both states.\n */\n label: string;\n /** Shown in place of the label when the label does not fit. */\n icon: ReactNode;\n}\n\n/**\n * A `button` that shows its label while the label fits and its icon when it\n * does not. The switch is measured against the button's own width, not a\n * breakpoint, and follows resizes. It is an unstyled native button\n * (`type=\"button\"` by default), so style it or wrap it as the layout needs.\n *\n * The accessible name is always `label`, carried in `aria-label` in both\n * states. Put anything explaining a disabled state in `title`, since it is the\n * only explanation left once the word is gone.\n *\n * @example\n * ```tsx\n * import { FitLabelButton, SettingsIcon } from \"@ksp-gonogo/ui-kit\";\n *\n * function SettingsButton(props: { onOpen: () => void }) {\n * return <FitLabelButton label=\"Settings\" icon={<SettingsIcon />} onClick={props.onOpen} />;\n * }\n * ```\n *\n * @category Button\n */\nexport const FitLabelButton = forwardRef<\n HTMLButtonElement,\n FitLabelButtonProps\n>(function FitLabelButton({ label, icon, type, ...rest }, ref) {\n return (\n <button ref={ref} type={type ?? \"button\"} aria-label={label} {...rest}>\n <FitLabel label={label} icon={icon} />\n </button>\n );\n});\n\n/**\n * The label-or-icon content of a {@link FitLabelButton}, for another control\n * that draws its own button and wants the same collapse. It measures its\n * parent's content box, so the parent must be able to shrink below the label\n * (`min-width: 0`). Both states are `aria-hidden`: the parent carries the name.\n * Kit-internal, not on the barrel.\n */\nexport function FitLabel({ label, icon }: { label: string; icon: ReactNode }) {\n const contentRef = useRef<HTMLSpanElement | null>(null);\n const ghostRef = useRef<HTMLSpanElement | null>(null);\n const [fits, setFits] = useState(true);\n\n useLayoutEffect(() => {\n const content = contentRef.current;\n const ghost = ghostRef.current;\n if (!content || !ghost) return;\n const measure = () => {\n // The content span's clientWidth is the button's content box, padding excluded.\n setFits(ghost.scrollWidth <= content.clientWidth);\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(content);\n observer.observe(ghost);\n return () => observer.disconnect();\n }, []);\n\n return (\n <FitLabelButton__Content ref={contentRef}>\n <FitLabelButton__Ghost ref={ghostRef} aria-hidden=\"true\">\n {label}\n </FitLabelButton__Ghost>\n {fits ? (\n <FitLabelButton__Label aria-hidden=\"true\">\n {label}\n </FitLabelButton__Label>\n ) : (\n <FitLabelButton__Icon aria-hidden=\"true\" data-fit-label-icon=\"\">\n {icon}\n </FitLabelButton__Icon>\n )}\n </FitLabelButton__Content>\n );\n}\n\nconst FitLabelButton__Content = styled.span`\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n /* Shrinks to what the parent allows, so an overlong label is detected rather than overflowing its cell. */\n min-width: 0;\n width: 100%;\n`;\n\n/** Measured, never seen, never read out. */\nconst FitLabelButton__Ghost = styled.span`\n position: absolute;\n left: 0;\n top: 0;\n visibility: hidden;\n pointer-events: none;\n white-space: nowrap;\n`;\n\nconst FitLabelButton__Label = styled.span`\n /* nowrap, so a label that does not fit overflows and the measurement stays honest. */\n white-space: nowrap;\n`;\n\nconst FitLabelButton__Icon = styled.span`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n`;\n","import type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { Spinner } from \"../Spinner\";\n\n/**\n * The props of {@link InFlightFace}.\n *\n * @category CommandButton\n */\nexport interface InFlightFaceProps {\n /** The resting label, kept invisible so the control keeps its resting width. */\n holds: ReactNode;\n /** The in-flight wording, given to the spinner as its name. */\n label: string;\n /** The spinner's size in px. Defaults to 12. */\n spinnerSize?: number;\n}\n\n/**\n * What a command control draws while its dispatch is in flight: a spinner\n * alone, centred over its resting label held invisible, so the control does not\n * change width. The wording belongs on the control's accessible name and title.\n *\n * @category CommandButton\n */\nexport function InFlightFace({\n holds,\n label,\n spinnerSize = 12,\n}: Readonly<InFlightFaceProps>) {\n return (\n <InFlightFace__Root>\n <InFlightFace__Hold aria-hidden=\"true\">{holds}</InFlightFace__Hold>\n <InFlightFace__Spinner>\n <Spinner size={spinnerSize} ariaLabel={label} />\n </InFlightFace__Spinner>\n </InFlightFace__Root>\n );\n}\n\n/* One grid cell both children share, so the face sizes to the held label with no positioned ancestor. */\nconst InFlightFace__Root = styled.span`\n display: inline-grid;\n & > * {\n grid-area: 1 / 1;\n }\n`;\n\nconst InFlightFace__Hold = styled.span`\n visibility: hidden;\n`;\n\nconst InFlightFace__Spinner = styled.span`\n display: flex;\n align-items: center;\n justify-content: center;\n`;\n","import type { HTMLAttributes } from \"react\";\nimport styled, { keyframes } from \"styled-components\";\n\n/**\n * Props for {@link Spinner}. Any other `span` attribute passes through, except\n * `color` and `role`.\n *\n * @category EmptyState\n */\nexport interface SpinnerProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"role\"> {\n /** Outer diameter in pixels. Defaults to 12. */\n size?: number;\n /** Stroke width in pixels. Defaults to 2. */\n thickness?: number;\n /** Active arc colour. Defaults to the accent foreground. */\n color?: string;\n /** Accessible name for screen readers. Defaults to \"Loading\". */\n ariaLabel?: string;\n}\n\n/**\n * A small inline pending indicator: a ring with a coloured arc, sized to sit\n * beside a row label or value without reflowing the layout. It is\n * `role=\"status\"` with an accessible name. Under `prefers-reduced-motion` the\n * ring does not spin.\n *\n * @category EmptyState\n */\nexport function Spinner({\n size = 12,\n thickness = 2,\n color = \"var(--color-accent-fg)\",\n ariaLabel = \"Loading\",\n ...rest\n}: Readonly<SpinnerProps>) {\n return (\n <SpinnerEl\n {...rest}\n role=\"status\"\n aria-label={ariaLabel}\n $size={size}\n $thickness={thickness}\n $color={color}\n />\n );\n}\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`;\n\nconst SpinnerEl = styled.span<{\n $size: number;\n $thickness: number;\n $color: string;\n}>`\n display: inline-block;\n width: ${({ $size }) => `${$size}px`};\n height: ${({ $size }) => `${$size}px`};\n border-radius: var(--radius-circle);\n border: ${({ $thickness }) => `${$thickness}px`} solid\n var(--color-border-subtle);\n border-top-color: ${({ $color }) => $color};\n flex-shrink: 0;\n @media (prefers-reduced-motion: no-preference) {\n /* Off the motion scale: continuous rotation, not a UI transition. */\n animation: ${spin} 700ms linear infinite;\n }\n`;\n","import { type ReactNode, useEffect, useId } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { focusRing } from \"../focusRing\";\nimport { Tooltip } from \"../Tooltip\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\n\ninterface CommandGroupOwnProps<GroupValue extends Record<string, unknown>> {\n /** The group's current value, owned by the caller and written by the child inputs. */\n value: GroupValue;\n /** Fired exactly once, with the group's current `value`, on an explicit commit; never on a child input's own change. */\n onCommit: (v: GroupValue) => void;\n /** Marks the commit control unavailable, in an error tone, for example while there is no path to the craft. It stays focusable and says why (`gatedReason`); `onCommit` never fires while gated. */\n gated?: boolean;\n /** The group's inputs (wheels, sliders and so on), drawn as given. */\n children: ReactNode;\n /**\n * What the commit button draws. Defaults to \"Commit\". A string names the\n * button by itself; a node (an icon-only commit) MUST be paired with\n * `commitAriaLabel`, because every kit icon is `aria-hidden`, and omitting it\n * is a compile error. A node also squares the button's inset.\n */\n commitLabel?: ReactNode;\n /**\n * The commit button's accessible name, for when `commitLabel` cannot be one.\n * Always the ACTION, never the glyph (\"Commit framing\", not \"Return arrow\").\n */\n commitAriaLabel?: string;\n /** Why a gated commit is unavailable: the button's description, and its hover title. */\n gatedReason?: string;\n /**\n * Where the commit control sits relative to the inputs. `\"column\"` (default)\n * puts it on its own line under them; `\"row\"` puts it beside them, for a\n * corner-sized strip that cannot spare the height.\n */\n orientation?: \"column\" | \"row\";\n /**\n * Let the inputs break onto further lines. On by default, unlike the kit's\n * other rows. Turn it off for a strip that must stay one line.\n */\n wrap?: boolean;\n}\n\n/**\n * The commit control's two props, with the a11y pairing spelled as a type: a\n * `commitLabel` that is not a string has to come with a `commitAriaLabel`. The\n * component also warns in dev, for a caller who spreads a props bag past the\n * check.\n */\ntype CommandGroupCommitNaming =\n | { commitLabel?: string; commitAriaLabel?: string }\n | { commitLabel: ReactNode; commitAriaLabel: string };\n\n/**\n * Props for {@link CommandGroup}. A `commitLabel` that is not a string must come with a `commitAriaLabel`.\n *\n * @category CommandDelay\n */\nexport type CommandGroupProps<GroupValue extends Record<string, unknown>> =\n CommandGroupOwnProps<GroupValue> & CommandGroupCommitNaming;\n\n/**\n * Grouped-confirm (select-then-commit) primitive: child inputs write into the\n * caller's controlled `value`, and nothing dispatches until the explicit commit\n * fires `onCommit` once with the whole group's value. One delayed dispatch for\n * the group, not one per input. It has no data hooks and no dispatch of its\n * own.\n *\n * @category CommandDelay\n */\nexport function CommandGroup<GroupValue extends Record<string, unknown>>({\n value,\n onCommit,\n gated = false,\n children,\n commitLabel = \"Commit\",\n commitAriaLabel,\n gatedReason = \"No path: command dispatch is disabled\",\n orientation = \"column\",\n wrap = true,\n}: CommandGroupProps<GroupValue>) {\n // The types refuse a nameless glyph commit; this catches a spread props bag the checker could not see.\n const named =\n typeof commitLabel === \"string\" || typeof commitLabel === \"number\";\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n if (named || commitAriaLabel) return;\n console.warn(\n \"CommandGroup was given a non-text commitLabel and no commitAriaLabel, \" +\n \"so its commit button has no accessible name. Pass commitAriaLabel \" +\n \"naming the action (not the glyph).\",\n );\n }, [named, commitAriaLabel]);\n const reasonId = useId();\n\n return (\n <CommandGroup__Root data-gated={gated} $orientation={orientation}>\n <CommandGroup__Inputs $wrap={wrap}>{children}</CommandGroup__Inputs>\n {gated && <VisuallyHidden id={reasonId}>{gatedReason}</VisuallyHidden>}\n <Tooltip text={gated ? gatedReason : undefined} announce={false}>\n <CommandGroup__CommitButton\n type=\"button\"\n aria-disabled={gated || undefined}\n aria-describedby={gated ? reasonId : undefined}\n $gated={gated}\n $orientation={orientation}\n $icon={!named}\n aria-label={commitAriaLabel}\n onClick={() => {\n if (gated) return;\n onCommit(value);\n }}\n >\n {commitLabel}\n </CommandGroup__CommitButton>\n </Tooltip>\n </CommandGroup__Root>\n );\n}\n\nconst CommandGroup__Root = styled.div<{ $orientation: \"column\" | \"row\" }>`\n display: flex;\n flex-direction: ${({ $orientation }) => $orientation};\n ${({ $orientation }) => $orientation === \"row\" && \"align-items: center;\"}\n gap: var(--gap-control-row);\n`;\n\nconst CommandGroup__Inputs = styled.div<{ $wrap: boolean }>`\n display: flex;\n flex-wrap: ${({ $wrap }) => ($wrap ? \"wrap\" : \"nowrap\")};\n gap: var(--gap-control-row);\n align-items: center;\n`;\n\nconst CommandGroup__CommitButton = styled.button<{\n $gated: boolean;\n $orientation: \"column\" | \"row\";\n $icon: boolean;\n}>`\n align-self: ${({ $orientation }) =>\n $orientation === \"row\" ? \"center\" : \"flex-start\"};\n /* A commit control must not be squeezed to nothing by the inputs beside it. */\n ${({ $orientation }) => $orientation === \"row\" && \"flex-shrink: 0;\"}\n padding: var(--inset-commit-button);\n font-size: var(--font-size-compact);\n font-weight: 600;\n border-radius: var(--radius-regular);\n border: 1px solid var(--color-border-subtle);\n background: var(--color-surface-raised);\n color: var(--color-text-primary);\n cursor: pointer;\n\n ${({ $icon }) =>\n $icon &&\n css`\n /* The glyph centres in the box, and the inset is squared so the button is a square. */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: var(--inset-icon-button-compact);\n\n @media (pointer: coarse) {\n padding: var(--inset-icon-button);\n }\n `}\n\n ${focusRing}\n\n ${({ $gated }) =>\n $gated &&\n css`\n cursor: not-allowed;\n border-color: var(--color-nogo-status);\n background: var(--color-nogo-status);\n color: var(--color-nogo-on-status);\n opacity: 0.7;\n `}\n`;\n","import { type Reading, type Value, value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { Unit } from \"../Unit\";\nimport { withDelayCurrency } from \"./delayCurrency\";\n\n/**\n * Props for {@link SignalDelayBadge}.\n *\n * @category CommandDelay\n */\nexport interface SignalDelayBadgeProps {\n /** One-way separation in seconds. Rendered as-is; the caller decides IF. */\n oneWaySeconds: number;\n /** The delay as the reading it arrived in, so a quiet `comms.delay` draws the figure held. */\n delayReading?: Reading<Value<\"s\">> | null;\n /** So a caller can pin or re-tone it with `styled()`. */\n className?: string;\n}\n\n/**\n * How long it takes to REACH the other end, as one small chip (\"one-way ~4.2 s\").\n *\n * One-way, because the operator's question is when their words land. Draw it\n * beside a control that sends across the gap, so the light time is read before\n * the press rather than learned after it. Under a minute the figure keeps one\n * decimal.\n *\n * @category CommandDelay\n */\nexport function SignalDelayBadge({\n oneWaySeconds,\n delayReading,\n className,\n}: SignalDelayBadgeProps) {\n const oneWay = value(\"s\", oneWaySeconds);\n return (\n // Not a live region: the delay changes with every sample.\n <SignalDelayBadge__Chip\n className={className}\n role=\"group\"\n aria-label=\"Signal delay\"\n >\n one-way ~\n {/* A delay is a READOUT, not a countdown, so under a minute it keeps a decimal the ladder would truncate. */}\n <Unit\n value={withDelayCurrency(oneWay, delayReading)}\n {...(oneWay.lessThan(60)\n ? { scale: \"never\" as const, decimals: 1 }\n : {})}\n />\n </SignalDelayBadge__Chip>\n );\n}\n\nconst SignalDelayBadge__Chip = styled.div`\n /* Non-growing: it shares the console's corner row, and its text is short by construction. */\n flex: 0 0 auto;\n padding: var(--inset-chip-readout);\n font-family: var(--font-family-mono);\n font-size: var(--font-size-compact);\n white-space: nowrap;\n color: var(--color-text-muted);\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n`;\n","import { useEffect, useId } from \"react\";\nimport type { CommandDelayHandle } from \"./CommandDelay\";\nimport { useDelayRailStore } from \"./DelayRailContext\";\n\n/**\n * Something the rail draws that is not a command, such as an open microphone's\n * ribbon. A command handle is registered by `useCommand` itself, so one passed\n * here is a type error rather than a second entry.\n *\n * @category CommandDelay\n */\nexport type RailEntry = CommandDelayHandle & { send?: never };\n\n/**\n * Put a non-command entry on the nearest panel's delay rail for the life of the\n * calling component, updating it in place as it changes. `null` contributes\n * nothing, so an entry can come and go; outside a rail it is a no-op.\n *\n * @category CommandDelay\n */\nexport function useRailEntry(entry: RailEntry | null): void {\n const store = useDelayRailStore();\n const id = useId();\n const present = entry !== null;\n\n // Keyed on identity only, so a value change updates in place rather than re-registering.\n // biome-ignore lint/correctness/useExhaustiveDependencies: value changes are applied by the update effect, not by re-registering\n useEffect(() => {\n if (!store || !entry) return;\n return store.register({ id, ...entry });\n }, [store, id, present]);\n\n // The store's shallow-equal guard means an entry that did not actually move notifies nobody.\n useEffect(() => {\n if (!store || !entry) return;\n store.update(id, entry);\n }, [store, id, entry]);\n}\n","import { type ComponentPropsWithoutRef, type ReactNode, useId } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Button } from \"./Button\";\nimport { SendIcon } from \"./Icons\";\nimport { useTooltip } from \"./Tooltip\";\nimport { VisuallyHidden } from \"./VisuallyHidden\";\n\n/**\n * Props for {@link ComposerBar}. Any other `div` attribute is passed to the row.\n *\n * @category Console\n */\nexport interface ComposerBarProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * Input is not being accepted. Swaps the accent outline for the error one,\n * and tones `flag` to match.\n */\n blocked?: boolean;\n /**\n * A short chip straddling the LEFT end of the row's top border, saying WHY\n * the outline turned red. The right end belongs to the console's delay\n * reading. A few upper-case words, pinned so it never changes the row's\n * height.\n */\n flag?: string;\n /**\n * The glyph that says \"type here\", at the head of the row and in the\n * console's tone. Optional: a composer that CHOOSES rather than types has\n * nothing to prompt for.\n */\n prompt?: string;\n /**\n * Commit what is composed. Given, the row grows a send button at its far end;\n * omitted, the only send is whatever key the composer binds.\n *\n * The button is an ADDITION to that key, for a touch screen with no keyboard\n * up. Wire it to the same entry point the key handler calls, not a second\n * copy of the send path. It carries the verb and nothing else, so it never\n * reflows the composer.\n */\n onSend?: () => void;\n /**\n * Refuse the press. Distinct from `blocked`, which is about the row as a\n * whole: a composer with nothing typed in it is perfectly able to accept\n * input and still has nothing to send.\n */\n sendDisabled?: boolean;\n /**\n * The verb. Defaults to \"Send\". It is the button's ACCESSIBLE NAME whichever\n * way the button is drawn.\n */\n sendLabel?: string;\n /**\n * How to draw the verb: the glyph by default. `\"text\"` is for a composer\n * whose verb has no glyph, such as \"Open\", where a send arrow would claim the\n * row transmits something.\n */\n sendVariant?: \"icon\" | \"text\";\n /**\n * What the operator should know before pressing send, as a kit tooltip on\n * the button (pointer and keyboard focus) and as its accessible description. A\n * `\\n` starts a new line. It never disables the send: pressing it stays the\n * operator's choice.\n */\n sendTooltip?: string;\n children?: ReactNode;\n}\n\n/**\n * The input at the foot of a {@link Console}: a bordered, non-growing row whose\n * own border says whether input is being accepted. The border takes the\n * console's tone (`--console-tone-fg`), falling back to the primary accent, so\n * what is outlined is what the operator types into.\n *\n * The border turns error-toned when `blocked` (for example, no comms path), so\n * the operator sees it while typing rather than on pressing the key.\n *\n * It styles the row and nothing inside it except the send button (`onSend`)\n * and the prompt glyph (`prompt`); font and character pitch belong to the\n * caller. Whatever goes in sits flush on the row, with no outline of its own.\n *\n * @category Console\n */\nexport function ComposerBar({\n blocked = false,\n flag,\n prompt,\n onSend,\n sendDisabled = false,\n sendLabel = \"Send\",\n sendVariant = \"icon\",\n sendTooltip,\n children,\n ...rest\n}: ComposerBarProps) {\n const { anchor, tip } = useTooltip(sendTooltip);\n const described = useId();\n return (\n <ComposerBar__Row $blocked={blocked} {...rest}>\n {prompt !== undefined && (\n <ComposerBar__Prompt aria-hidden=\"true\">{prompt}</ComposerBar__Prompt>\n )}\n {children}\n {onSend !== undefined && (\n /* The anchor is the wrapper, not the button: a disabled button raises no pointer events. */\n <ComposerBar__SendAnchor {...anchor}>\n <ComposerBar__Send\n type=\"button\"\n disabled={sendDisabled}\n onClick={onSend}\n $icon={sendVariant === \"icon\"}\n {...(sendTooltip ? { \"aria-describedby\": described } : {})}\n {...(sendVariant === \"icon\" ? { \"aria-label\": sendLabel } : {})}\n >\n {sendVariant === \"icon\" ? <SendIcon size={16} /> : sendLabel}\n </ComposerBar__Send>\n {sendTooltip && (\n <VisuallyHidden id={described}>\n {sendTooltip.replace(\"\\n\", \". \")}\n </VisuallyHidden>\n )}\n {tip}\n </ComposerBar__SendAnchor>\n )}\n {flag !== undefined && (\n // `role=\"status\"`, never `alert`: a lost path is ambient, not an interruption.\n <ComposerBar__Flag $blocked={blocked} role=\"status\">\n {flag}\n </ComposerBar__Flag>\n )}\n </ComposerBar__Row>\n );\n}\n\nconst ComposerBar__Row = styled.div<{ $blocked: boolean }>`\n position: relative;\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: var(--gap-composer);\n padding: var(--inset-field);\n background: var(--color-surface-panel);\n border: 1px solid\n ${({ $blocked }) =>\n $blocked\n ? \"var(--color-nogo-text)\"\n : \"var(--console-tone-fg, var(--color-accent-fg))\"};\n border-radius: var(--radius-regular);\n`;\n\n// Its own margin rather than the row's gap, since a terminal's caret block must sit flush against the composed line.\nconst ComposerBar__Prompt = styled.span`\n flex: 0 0 auto;\n color: var(--console-tone-fg, var(--color-accent-fg));\n font-weight: bold;\n margin-right: var(--gap-prompt-glyph);\n`;\n\n/*\n * Pushed to the far end by its own auto margin, so it never moves with each\n * keystroke. `font-size` is stated, not inherited from a terminal's character\n * pitch on the row.\n */\nconst ComposerBar__SendAnchor = styled.span`\n display: inline-flex;\n\n /* A disabled button swallows pointer events, so they go to the anchor instead. */\n > button:disabled {\n pointer-events: none;\n }\n`;\n\nconst ComposerBar__Send = styled(Button)<{ $icon: boolean }>`\n flex: 0 0 auto;\n margin-left: auto;\n font-size: var(--font-size-compact);\n\n ${({ $icon }) =>\n $icon &&\n css`\n /* The glyph centres in the box, and the inset is squared so the button is a square. */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: var(--inset-icon-button);\n /* The row's own outline tone, so the control reads as belonging to the row. */\n color: var(--console-tone-fg, var(--color-accent-fg));\n\n @media (pointer: coarse) {\n padding: var(--inset-icon-button-touch);\n }\n `}\n`;\n\nconst ComposerBar__Flag = styled.div<{ $blocked: boolean }>`\n position: absolute;\n /* Straddles the top border by half its own height, which tracks the font size. */\n top: 0;\n transform: translateY(-50%);\n /* The left end of the border: a console's delay reading takes the right end, and the two can show together. */\n left: var(--inset-console-foot);\n /* Local ordering against its row only, so not on the --z-* ladder. */\n z-index: 1;\n /* Overlaps the prompt glyph, so it must not eat a press aimed at the control under it. */\n pointer-events: none;\n padding: var(--inset-chip);\n font-family: var(--font-family-mono);\n font-size: var(--font-size-caption);\n font-weight: bold;\n letter-spacing: 0.04em;\n border-radius: var(--radius-regular);\n\n ${({ $blocked }) =>\n $blocked\n ? css`\n color: var(--color-nogo-on-status);\n background: var(--color-nogo-status);\n border: 1px solid var(--color-nogo-on-status);\n `\n : css`\n color: var(--color-text-muted);\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-subtle);\n `}\n`;\n","import type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\n\n/*\n * The BOX a console is drawn in: a scrolling surface that takes the remaining\n * height of a panel body, with whatever the operator types into sitting INSIDE\n * it at the foot. Reached through `Console`, not exported bare.\n *\n * The nesting is exactly two deep: a quiet frame with a SUBTLE border, and one\n * bordered composer inside it that wears the console's accent. Never a third\n * box (an `<input>` with its own outline inside the composer).\n *\n * It is the positioning context, so a status badge pinned INSIDE the surface\n * costs no height on a widget at its `minSize`. Unlike `FramedDisplay` it takes\n * the remaining height; unlike `ScrollArea` it has a border and a positioning\n * context. It does not scroll: what goes inside scrolls itself.\n *\n * ## The standing reading sits at the FOOT, on the composer's TOP border\n *\n * `standing` is a readout of the LINK right now, drawn right-aligned over the\n * composer's top border, never over the scrollback's prose. It shares a column\n * with the queue's countdowns (the offset is the FOOT's inset, so the figures\n * line up exactly), and the two are never drawn together.\n *\n * `ComposerBar`'s own flag takes the LEFT end of the same border, since both\n * can be up at once: a terminal's separation and its refusal reveal on\n * different clocks. Neither knows about the other.\n *\n * With no composer there is no border to straddle, so the chip becomes an\n * ordinary right-aligned child at the foot, costing a band of height rather\n * than sitting over the prose.\n */\n/**\n * Which accent a {@link Console} is drawn in: `accent` (the primary accent) for\n * a terminal that dispatches to a craft, `info` for a message log.\n *\n * @category Console\n */\nexport type ConsoleTone = \"accent\" | \"info\";\n\nexport interface ConsoleFrameProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * Which accent this console answers in: the primary accent for a terminal\n * that dispatches to a craft, the informational one for a message log. The\n * frame paints nothing with it; it declares `--console-tone-fg` once for the\n * composer's border, prompt glyph and focus ring.\n */\n tone?: ConsoleTone;\n /**\n * What is still crossing, at the foot and immediately above the composer.\n * A slot of its own, so the frame knows whether there is a composer.\n */\n queue?: ReactNode;\n /**\n * The line the operator types on, at the foot, inside the frame and inset\n * from its edges. Given but falsy keeps the foot, so a mode switch does not\n * change the surface's height.\n */\n composer?: ReactNode;\n /**\n * A standing reading of the link, drawn over the TOP border of the composer\n * and right-aligned to the queue's column of times. Out of flow while there\n * IS a composer; an ordinary child at the foot when there is not.\n */\n standing?: ReactNode;\n children?: ReactNode;\n}\n\nexport function ConsoleFrame({\n tone = \"accent\",\n queue,\n composer,\n standing,\n children,\n ...rest\n}: ConsoleFrameProps) {\n // Any of the three grows a foot, so a standing reading never hangs over the log.\n const hasFoot =\n queue !== undefined || composer !== undefined || standing !== undefined;\n // A falsy `composer` keeps the foot but has no border, so the chip goes back into flow.\n const straddles = standing !== undefined && Boolean(composer);\n return (\n // A stable structural hook, since containment is invisible to a role query.\n <ConsoleFrame__Box data-console-frame=\"\" $tone={tone} {...rest}>\n <ConsoleFrame__Surface>{children}</ConsoleFrame__Surface>\n {hasFoot && (\n <ConsoleFrame__Foot $straddled={straddles}>\n {queue}\n {standing !== undefined && (\n /*\n A structural hook for the same reason. BEFORE the composer, so\n in the in-flow case the reading is announced before the control\n it sits above.\n */\n <ConsoleFrame__Standing\n data-console-standing=\"\"\n $straddle={straddles}\n >\n {standing}\n </ConsoleFrame__Standing>\n )}\n {composer}\n </ConsoleFrame__Foot>\n )}\n </ConsoleFrame__Box>\n );\n}\n\nconst ConsoleFrame__Box = styled.div<{ $tone: ConsoleTone }>`\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n background: var(--color-surface-panel);\n /* Subtle, not the tone: the accent belongs to the input's own border. */\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n overflow: hidden;\n\n ${({ $tone }) =>\n $tone === \"info\"\n ? css`\n --console-tone-fg: var(--color-info-text);\n `\n : css`\n --console-tone-fg: var(--color-accent-fg);\n `}\n`;\n\n// The scrollback half. `position: relative` is for the caller's own overlays; the frame pins nothing here.\nconst ConsoleFrame__Surface = styled.div`\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n display: flex;\n`;\n\n/*\n * A row, so a second standing reading sits beside the first in reading order.\n * `right` is the FOOT's inset, putting the chip's right edge on the\n * composer's, which lines its figure up with the queue's countdowns. `top` is\n * the same token as the foot's padding-top, so no hand-computed offset drifts\n * when the font size changes on a coarse pointer. That assumes the composer is\n * the foot's first box, which holds because the chip and the queue are never\n * drawn together. `z-index: 1` is local sibling ordering.\n */\nconst ConsoleFrame__Standing = styled.div<{ $straddle: boolean }>`\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: var(--gap-console-readings);\n /* Overlaps the composer's corner with nothing to click, so it must not take a press. */\n pointer-events: none;\n\n ${({ $straddle }) =>\n $straddle\n ? css`\n position: absolute;\n right: var(--inset-console-foot);\n top: var(--inset-console-foot-straddled);\n transform: translateY(-50%);\n z-index: 1;\n `\n : // No border to straddle, so it takes a line of its own and keeps only the column.\n css`\n align-self: flex-end;\n `}\n`;\n\n/*\n * Non-growing, so a filling queue or an opening picker never takes height from\n * the scrollback. Its own positioning context, so a composer's dropdown anchors\n * inside the console and the standing reading pins against the foot. The\n * padding makes the composer read as a control IN the console. `$straddled`\n * deepens the top inset so the half-chip above the border never lands on the\n * log's last line.\n */\nconst ConsoleFrame__Foot = styled.div<{ $straddled: boolean }>`\n position: relative;\n flex: 0 0 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--gap-console-foot);\n padding: var(--inset-console-foot);\n\n ${({ $straddled }) =>\n $straddled &&\n css`\n padding-top: var(--inset-console-foot-straddled);\n `}\n`;\n","import type { Reading, Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport {\n InFlightList,\n type InFlightListItem,\n signalDelayPresentation,\n} from \"./CommandDelay/InFlightList\";\nimport { SignalDelayBadge } from \"./CommandDelay/SignalDelayBadge\";\nimport { ConsoleFrame, type ConsoleTone } from \"./ConsoleFrame\";\n\nexport type { ConsoleTone };\n\n/**\n * Props for {@link Console}. Any other `div` attribute is passed to the frame.\n *\n * @category Console\n */\nexport interface ConsoleProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * Which accent this console is drawn in, declared once for the composer's\n * border, prompt glyph and focus ring.\n */\n tone?: ConsoleTone;\n /**\n * How long it takes to reach the other end, in seconds. `null` when there is\n * no measurable path. Neither `null` nor zero gets a chip.\n */\n oneWaySeconds?: number | null;\n /**\n * The same delay as the reading it arrived in (`useCommand`'s\n * `delayReading`), so the chip's figure draws held while `comms.delay` is\n * quiet. Omitted, it draws as current.\n */\n delayReading?: Reading<Value<\"s\">> | null;\n /**\n * Whether anything this console sends can sit in a queue. A read-only viewer\n * passes `false` and gets NEITHER reading at a long delay.\n */\n canQueue?: boolean;\n /**\n * Force the chip whatever the separation, for a terminal in CHARACTER mode,\n * which has no composed line to queue.\n */\n alwaysBadge?: boolean;\n /**\n * What is still crossing, one entry per thing out. Drawn between the\n * scrollback and the composer, never inside the scroll.\n */\n inFlight?: InFlightListItem[];\n /**\n * Set when each `inFlight` entry carries the delay it was sent under, so the\n * queue stays after the live delay reading is gone: words sent at four\n * light-minutes are still crossing after the path drops. The chip and the\n * queue are still never drawn together.\n */\n inFlightFrozenAtDispatch?: boolean;\n /**\n * The line the operator types on, at the foot and inside the frame. Omitted,\n * the console grows no foot. Given but currently absent (a terminal in\n * character mode), the foot stays, so the surface does not change height\n * with the mode. A standing chip grows a foot for itself either way.\n */\n composer?: ReactNode;\n /** The scrollback: an emulator screen, a log, a list of rows. */\n children?: ReactNode;\n}\n\n/** Every console's queue name; not a prop, so an operator never hears two. */\nconst QUEUE_LABEL = \"Uplink queue\";\n\n/**\n * A console: a scrolling surface, what is still crossing on the link, the line\n * the operator is typing, and one reading of how far away the other end is.\n *\n * The delay reading is chosen here, not by the widget: a standing\n * {@link SignalDelayBadge} when the other end is close enough that a countdown\n * would be over before it could be read, an {@link InFlightList} queue of what\n * is crossing when it is not, never both. Both sit at the foot. A widget says\n * only how far away the other end is (`oneWaySeconds`), whether it can queue\n * (`canQueue`) and what is queued (`inFlight`).\n *\n * The composer (usually a {@link ComposerBar}) and the scrollback are the\n * widget's; this places them and draws the frame around them.\n *\n * @category Console\n * @categoryDescription Console\n * A two-way text console over a delayed link: the scrollback, the line being\n * typed, and what is still crossing, framed with one reading of how far away\n * the other end is.\n */\nexport function Console({\n tone,\n oneWaySeconds = null,\n delayReading,\n canQueue = true,\n alwaysBadge = false,\n inFlight,\n inFlightFrozenAtDispatch = false,\n composer,\n children,\n ...rest\n}: ConsoleProps) {\n const presentation = signalDelayPresentation({\n oneWaySeconds,\n canQueue,\n alwaysBadge,\n });\n // \"badge\" implies a non-null separation, which the type cannot carry back here.\n const badgeSeconds = presentation === \"badge\" ? oneWaySeconds : null;\n const showStrip =\n inFlight !== undefined &&\n (presentation === \"strip\" ||\n (inFlightFrozenAtDispatch && presentation !== \"badge\"));\n return (\n // Two slots rather than one `footer`, so the frame knows whether there is a composer to place the reading against.\n <ConsoleFrame\n {...(tone !== undefined ? { tone } : {})}\n {...(badgeSeconds !== null\n ? {\n standing: (\n <SignalDelayBadge\n oneWaySeconds={badgeSeconds}\n delayReading={delayReading}\n />\n ),\n }\n : {})}\n {...(showStrip\n ? { queue: <InFlightList items={inFlight} ariaLabel={QUEUE_LABEL} /> }\n : {})}\n {...(composer !== undefined ? { composer } : {})}\n {...rest}\n >\n {children}\n </ConsoleFrame>\n );\n}\n","import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport styled, { type CSSObject } from \"styled-components\";\n\n/**\n * Props for {@link ContainerBreak}.\n *\n * @category Layout\n */\nexport interface ContainerBreakProps extends HTMLAttributes<HTMLDivElement> {\n /** Container width in px past which `wide` applies instead of `narrow`. */\n at: number;\n /** Styles applied while the container is narrower than `at`. */\n narrow?: CSSProperties;\n /** Styles applied once the container is at least `at` px wide, layered over `narrow`. */\n wide?: CSSProperties;\n children?: ReactNode;\n}\n\n/**\n * A `div` that switches between two sets of inline styles at a container-query\n * breakpoint, which a React `style` prop cannot express on its own. The\n * nearest ancestor with `container-type: inline-size` (a plain\n * `<div style={{ containerType: \"inline-size\" }}>` is enough) decides which\n * side of `at` this element is on. Both style sets are ordinary React\n * `CSSProperties`.\n *\n * @example\n * ```tsx\n * <div style={{ containerType: \"inline-size\" }}>\n * <ContainerBreak\n * at={360}\n * narrow={{ display: \"flex\", flexDirection: \"column\" }}\n * wide={{ flexDirection: \"row\" }}\n * >\n * {chart}\n * {legend}\n * </ContainerBreak>\n * </div>\n * ```\n *\n * @category Layout\n */\nexport function ContainerBreak({\n at,\n narrow,\n wide,\n children,\n ...rest\n}: ContainerBreakProps) {\n return (\n <ContainerBreak__Root $at={at} $narrow={narrow} $wide={wide} {...rest}>\n {children}\n </ContainerBreak__Root>\n );\n}\n\nconst ContainerBreak__Root = styled.div<{\n $at: number;\n $narrow?: CSSProperties;\n $wide?: CSSProperties;\n}>`\n ${({ $narrow }) => $narrow as CSSObject}\n @container (min-width: ${({ $at }) => $at}px) {\n ${({ $wide }) => $wide as CSSObject}\n }\n`;\n","/**\n * Value equality for widget config objects, as {@link useModalSaveBar} uses\n * it to decide whether a draft differs from the saved config.\n *\n * - `undefined` and a missing key are treated the same\n * - object key order is ignored\n * - arrays compare by index\n * - `Date` values compare by time, `Set` by membership (primitives and\n * deep-equal members) and `Map` by entries\n *\n * @category Modal\n */\nexport function configEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n return a.every((item, i) => configEqual(item, b[i]));\n }\n\n if (a instanceof Date || b instanceof Date) {\n return (\n a instanceof Date &&\n b instanceof Date &&\n Object.is(a.getTime(), b.getTime())\n );\n }\n\n if (a instanceof Set || b instanceof Set) {\n if (!(a instanceof Set) || !(b instanceof Set)) return false;\n if (a.size !== b.size) return false;\n const rest = [...b];\n // Greedy match is exact here: configEqual is an equivalence relation.\n return [...a].every((x) => {\n const i = rest.findIndex((y) => configEqual(x, y));\n if (i === -1) return false;\n rest.splice(i, 1);\n return true;\n });\n }\n\n if (a instanceof Map || b instanceof Map) {\n if (!(a instanceof Map) || !(b instanceof Map)) return false;\n if (a.size !== b.size) return false;\n for (const [k, v] of a) {\n if (!b.has(k) || !configEqual(v, b.get(k))) return false;\n }\n return true;\n }\n\n if (typeof a === \"object\" && typeof b === \"object\") {\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n // Union of keys whose value is not undefined on either side.\n const keys = new Set<string>();\n for (const k of Object.keys(ao)) if (ao[k] !== undefined) keys.add(k);\n for (const k of Object.keys(bo)) if (bo[k] !== undefined) keys.add(k);\n for (const k of keys) {\n if (!configEqual(ao[k], bo[k])) return false;\n }\n return true;\n }\n\n return false;\n}\n","import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport styled from \"styled-components\";\nimport {\n ComboboxListbox,\n type ComboboxOption,\n filterComboboxOptions,\n flattenComboboxGroups,\n groupComboboxOptions,\n moveComboboxActiveIndex,\n} from \"./Combobox\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * One key a {@link DataKeyPicker} offers.\n *\n * @category Form\n */\nexport interface KeyOption extends ComboboxOption {\n /** A unit symbol drawn at the end of the option's row. */\n unit?: string;\n}\n\n/**\n * Props for {@link DataKeyPicker}.\n *\n * @category Form\n */\nexport interface DataKeyPickerProps {\n /** The keys on offer. An empty list is treated as not yet loaded, so no saved key is flagged as unavailable against it. */\n keys: KeyOption[];\n /** The picked key, or null for none. */\n value: string | null;\n /** Called with the picked key, or with null when cleared. */\n onChange: (key: string | null) => void;\n /** Adds a clear control while a key is picked. Defaults to false. */\n clearable?: boolean;\n /** Shown while no key is picked. Defaults to \"Search...\". */\n placeholder?: string;\n /**\n * What to call the thing the picked key names, for the message shown when a\n * saved key is not among `keys` and for the clear control's name. Defaults\n * to \"value\".\n */\n subjectNoun?: string;\n /** Put on the input, so a `<label htmlFor>` elsewhere names it. */\n id?: string;\n /** Names the input when no label points at it. */\n \"aria-label\"?: string;\n}\n\n/**\n * Shown in place of a label when a saved key is not in `keys`: rendering the\n * raw key would read as a valid selection that silently reads nothing.\n */\nconst RETIRED_MESSAGE = \"is no longer available\";\n\n/**\n * A searchable picker over a list of keys: a combobox input that filters as\n * the operator types, with the options grouped under their `group` headings\n * and each option's unit drawn beside it. Enter with nothing highlighted picks\n * the first match; Escape or a click outside closes the list.\n *\n * A saved `value` that is not among a non-empty `keys` is flagged in place\n * (\"This value is no longer available. Pick another.\") rather than shown as a\n * valid selection.\n *\n * @category Form\n */\nexport function DataKeyPicker({\n keys,\n value,\n onChange,\n clearable = false,\n placeholder = \"Search...\",\n subjectNoun = \"value\",\n id,\n \"aria-label\": ariaLabel,\n}: DataKeyPickerProps) {\n const [query, setQuery] = useState(\"\");\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(-1);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const listboxId = useId();\n const optionIdPrefix = useId();\n const retiredId = useId();\n const optionId = (key: string) => `${optionIdPrefix}-${key}`;\n\n const selectedOption = keys.find((k) => k.key === value);\n // An empty `keys` is a catalogue that has not arrived, so nothing is judged retired against it.\n const retired =\n value !== null &&\n value !== \"\" &&\n selectedOption === undefined &&\n keys.length > 0;\n\n const filtered = useMemo(\n () => filterComboboxOptions(keys, query),\n [keys, query],\n );\n\n const sortedGroups = useMemo(\n () => groupComboboxOptions(filtered),\n [filtered],\n );\n\n const flatOptions = useMemo(\n () => flattenComboboxGroups(sortedGroups),\n [sortedGroups],\n );\n\n const openPicker = useCallback(() => {\n setOpen(true);\n setQuery(\"\");\n setActiveIndex(-1);\n }, []);\n\n const closePicker = useCallback(() => {\n setOpen(false);\n setQuery(\"\");\n setActiveIndex(-1);\n }, []);\n\n const selectOption = useCallback(\n (key: string) => {\n onChange(key);\n closePicker();\n },\n [onChange, closePicker],\n );\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (!open) {\n if (e.key === \"Enter\" || e.key === \"ArrowDown\") openPicker();\n return;\n }\n switch (e.key) {\n case \"Escape\":\n closePicker();\n break;\n case \"ArrowDown\":\n e.preventDefault();\n setActiveIndex((i) =>\n moveComboboxActiveIndex(i, 1, flatOptions.length),\n );\n break;\n case \"ArrowUp\":\n e.preventDefault();\n setActiveIndex((i) =>\n moveComboboxActiveIndex(i, -1, flatOptions.length),\n );\n break;\n case \"Enter\": {\n // Falls back to the first filtered result, so a partial label plus Enter works.\n const opt =\n activeIndex >= 0 ? flatOptions[activeIndex] : flatOptions[0];\n if (opt) selectOption(opt.key);\n break;\n }\n }\n };\n\n useEffect(() => {\n if (!open) return;\n const onOutside = (e: PointerEvent) => {\n if (!containerRef.current?.contains(e.target as Node)) closePicker();\n };\n document.addEventListener(\"pointerdown\", onOutside);\n return () => document.removeEventListener(\"pointerdown\", onOutside);\n }, [open, closePicker]);\n\n const displayValue = open ? query : (selectedOption?.label ?? value ?? \"\");\n const activeOption =\n open && activeIndex >= 0 ? flatOptions[activeIndex] : undefined;\n\n return (\n <Container ref={containerRef}>\n <PickerInput\n ref={inputRef}\n id={id}\n aria-label={ariaLabel}\n value={displayValue}\n placeholder={value ? undefined : placeholder}\n $hasValue={!!value && !open}\n $retired={retired && !open}\n aria-invalid={retired && !open ? true : undefined}\n aria-describedby={retired && !open ? retiredId : undefined}\n onFocus={openPicker}\n onBlur={closePicker}\n onChange={(e) => {\n setQuery(e.target.value);\n setActiveIndex(-1);\n }}\n onKeyDown={handleKeyDown}\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-autocomplete=\"list\"\n aria-activedescendant={\n activeOption ? optionId(activeOption.key) : undefined\n }\n />\n {retired && !open && (\n <RetiredNote id={retiredId}>\n {`This ${subjectNoun} ${RETIRED_MESSAGE}. Pick another.`}\n </RetiredNote>\n )}\n {clearable && value && !open && (\n <ClearButton\n type=\"button\"\n aria-label={`Clear ${subjectNoun}`}\n onClick={() => {\n onChange(null);\n closePicker();\n }}\n >\n ×\n </ClearButton>\n )}\n {open && (\n <ComboboxListbox\n id={listboxId}\n groups={sortedGroups}\n flatOptions={flatOptions}\n activeIndex={activeIndex}\n selectedKey={value}\n getOptionId={optionId}\n onHoverIndex={setActiveIndex}\n onSelectKey={selectOption}\n ariaLabel={ariaLabel ?? \"Data keys\"}\n renderItem={(opt) => (\n <>\n <ItemLabel>{opt.label ?? opt.key}</ItemLabel>\n {opt.unit && <ItemUnit>{opt.unit}</ItemUnit>}\n </>\n )}\n />\n )}\n </Container>\n );\n}\n\nconst Container = styled.div`\n position: relative;\n`;\n\nconst PickerInput = styled.input<{ $hasValue: boolean; $retired?: boolean }>`\n background: var(--color-surface-raised);\n border: 1px solid ${({ $retired }) => ($retired ? \"var(--color-nogo-text)\" : \"var(--color-border-strong)\")};\n border-radius: var(--radius-regular);\n color: ${({ $hasValue, $retired }) => {\n if ($retired) return \"var(--color-nogo-text)\";\n return $hasValue ? \"var(--color-text-primary)\" : \"var(--color-text-muted)\";\n }};\n font-size: var(--font-size-value);\n padding: var(--inset-field);\n width: 100%;\n\n &:focus {\n border-color: var(--color-text-faint);\n outline: none;\n }\n\n ${focusRing}\n\n &::placeholder {\n color: var(--color-text-faint);\n }\n`;\n\nconst ClearButton = styled.button`\n position: absolute;\n right: 6px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n color: var(--color-text-dim);\n cursor: pointer;\n font-size: var(--font-size-lg);\n line-height: var(--line-height-flush);\n padding: var(--inset-glyph-tight);\n\n &:hover {\n color: var(--color-text-primary);\n }\n\n ${focusRing}\n`;\n\n/**\n * Sits under the input rather than replacing its text, so the operator can\n * still read which key went missing.\n */\nconst RetiredNote = styled.div`\n color: var(--color-nogo-text);\n font-size: var(--font-size-compact);\n margin-top: var(--gap-caption);\n`;\n\nconst ItemLabel = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-primary);\n`;\n\nconst ItemUnit = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-muted);\n margin-left: var(--gap-trailing-mark);\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { TONE_TEXT } from \"./tone\";\n\n/**\n * Props for {@link DataLine}.\n *\n * @category Readout\n */\nexport interface DataLineProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * What the reading IS, in one or two words. Set in a quiet uppercase so the\n * eye skips it on a second pass and lands on the reading.\n */\n label: ReactNode;\n /**\n * A chip qualifying the reading: a `Badge` saying which of two states it is in.\n * Sits at the head of the reading, inside the reading's own column, so the two\n * wrap together.\n */\n lead?: ReactNode;\n /** The reading. Write a quantity through {@link Unit}. */\n children?: ReactNode;\n /** How alarming the reading is. Defaults to `neutral`. */\n tone?: Tone;\n /**\n * Give the label a fixed column so the readings on consecutive lines line up\n * down their left edge; a long reading wraps inside its own column. Off by\n * default.\n */\n aligned?: boolean;\n}\n\n/**\n * One labelled reading on a line: what it is, then what it says.\n *\n * The label is quiet uppercase; the reading takes the primary foreground with\n * tabular figures, so a run of lines never reads as one block of grey. `tone`\n * colours the reading and never the label.\n *\n * For a settings row, where the value sits at the row's right edge, use\n * {@link ReadOnlyField} instead.\n *\n * @category Readout\n */\nexport function DataLine({\n label,\n lead,\n children,\n tone = \"neutral\",\n aligned = false,\n ...rest\n}: DataLineProps) {\n return (\n <DataLine__Root $aligned={aligned} {...rest}>\n <DataLine__Label>{label}</DataLine__Label>\n <DataLine__Value $tone={tone}>\n {lead}\n {children}\n </DataLine__Value>\n </DataLine__Root>\n );\n}\n\n/**\n * The label column's width, in `ch` of the grid container's font: room for\n * about ten of the smaller label's characters.\n */\nconst LABEL_COLUMN = \"7ch\";\n\nconst DataLine__Root = styled.div<{ $aligned: boolean }>`\n display: ${({ $aligned }) => ($aligned ? \"grid\" : \"flex\")};\n align-items: baseline;\n gap: var(--gap-data-line);\n min-width: 0;\n ${({ $aligned }) =>\n $aligned\n ? css`\n grid-template-columns: ${LABEL_COLUMN} minmax(0, 1fr);\n `\n : css`\n flex-wrap: wrap;\n `}\n`;\n\nconst DataLine__Label = styled.span`\n font-size: var(--font-size-caption);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n`;\n\nconst DataLine__Value = styled.span<{ $tone: Tone }>`\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--gap-data-line);\n font-size: var(--font-size-value);\n font-variant-numeric: tabular-nums;\n min-width: 0;\n color: ${({ $tone }) => TONE_TEXT[$tone]};\n`;\n","import type { ReactNode } from \"react\";\nimport { Fragment, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRingInset } from \"./focusRing\";\nimport { InlineOverflowGlow, useInlineOverflow } from \"./inlineOverflow\";\nimport type { UnitValue } from \"./readingCurrency\";\nimport { Unit } from \"./Unit\";\nimport { useKeyboardScrollable } from \"./useScrollerMetric\";\n\ninterface DataTableColumnBase {\n /** Stable identity for the column, and its React key. */\n key: string;\n /** The column's heading, in the table head. */\n header: ReactNode;\n /**\n * This column names its row: its cells are row headers\n * (`<th scope=\"row\">`), so a screen reader says the row's name with every\n * other cell in it. One column per table, normally the first.\n */\n rowHeader?: boolean;\n /**\n * `end` right-aligns the cell and its header. Use it for every numeric\n * column, so the digits line up.\n */\n align?: \"start\" | \"end\";\n /**\n * CSS width for the column, e.g. `\"1fr\"` or `\"9ch\"`. Omitted columns size\n * to their content. A `ch` width on a numeric column keeps it from\n * twitching as values change magnitude.\n */\n width?: string;\n /**\n * Minimum width for the column, so a text column in a narrow panel scrolls\n * instead of wrapping to single words.\n */\n minWidth?: string;\n}\n\n/**\n * One column of a {@link DataTable}: what each cell draws, from a `render` of\n * the caller's own, or from a `value` the table draws through {@link Unit}.\n * `value` takes a whole `Reading`, so a held figure keeps its mark, and a\n * `null` or `undefined` draws the null token.\n *\n * @category DataTable\n */\nexport type DataTableColumn<Row> = DataTableColumnBase &\n (\n | { render: (row: Row) => ReactNode; value?: never }\n | { value: (row: Row) => UnitValue | null | undefined; render?: never }\n );\n\n/** A cell's content, from whichever of the two the column gives. */\nfunction cellContent<Row>(col: DataTableColumn<Row>, row: Row): ReactNode {\n if (col.value !== undefined) return <Unit value={col.value(row) ?? null} />;\n return col.render(row);\n}\n\n/**\n * A run of rows under a heading, for a table whose rows arrive already\n * grouped (by body, by vessel, by stage).\n *\n * @category DataTable\n */\nexport interface DataTableSection<Row> {\n /** Unique within the table; the section's React key. */\n id: string;\n /** The heading drawn across the table above the section's rows. */\n title: ReactNode;\n /** The rows under this heading. */\n rows: Row[];\n}\n\n/**\n * Props for {@link DataTable}.\n *\n * @category DataTable\n */\nexport interface DataTableProps<Row> {\n /** The columns, left to right. */\n columns: ReadonlyArray<DataTableColumn<Row>>;\n /** Flat rows. Ignored when `sections` is given. */\n rows?: ReadonlyArray<Row>;\n /** Grouped rows. Takes precedence over `rows`. */\n sections?: ReadonlyArray<DataTableSection<Row>>;\n /** A stable, unique key for each row. */\n rowKey: (row: Row) => string;\n /**\n * Describes the table to a screen reader. It is not shown: the visible\n * heading is the widget's own.\n */\n caption: string;\n /** Shown in place of the body when there is nothing to list. */\n empty?: ReactNode;\n /**\n * Extra content for a row, rendered as a full-width row directly beneath it:\n * where per-row controls and augment slots go, so the columns stay aligned.\n */\n rowDetail?: (row: Row) => ReactNode;\n className?: string;\n}\n\n/**\n * A real table for tabular readouts, so figures in a column can be compared\n * down the page.\n *\n * Semantic `<table>` throughout. Each section is a `<tbody>` of its own,\n * headed by a `<th scope=\"rowgroup\">` spanning the width, and a `rowHeader`\n * column makes each row's naming cell a `<th scope=\"row\">`. A table wider than\n * its slot scrolls sideways.\n *\n * @example\n * ```tsx\n * const columns: ReadonlyArray<DataTableColumn<Subject>> = [\n * { key: \"subject\", header: \"Subject\", rowHeader: true, width: \"1fr\", minWidth: \"22ch\", render: (s) => s.title },\n * { key: \"data\", header: \"Data\", align: \"end\", width: \"9ch\", value: (s) => value(\"Mit\", s.dataMits) },\n * { key: \"remaining\", header: \"Remaining\", align: \"end\", width: \"10ch\", value: (s) => value(\"science\", s.remaining) },\n * ];\n *\n * <DataTable\n * caption=\"Science aboard the active vessel, by subject\"\n * columns={columns}\n * rows={subjects}\n * rowKey={(s) => s.subjectId}\n * empty=\"No subject matches the filter.\"\n * />\n * ```\n *\n * @category DataTable\n * @categoryDescription DataTable\n * A table of rows under typed columns, with optional sections, for data an\n * operator scans across.\n */\nexport function DataTable<Row>({\n columns,\n rows,\n sections,\n rowKey,\n caption,\n empty,\n rowDetail,\n className,\n}: Readonly<DataTableProps<Row>>) {\n const groups: ReadonlyArray<DataTableSection<Row>> =\n sections ?? (rows ? [{ id: \"\", title: null, rows: [...rows] }] : []);\n const total = groups.reduce((n, g) => n + g.rows.length, 0);\n const [scroller, setScroller] = useState<HTMLDivElement | null>(null);\n const overflow = useInlineOverflow(scroller);\n const tabIndex = useKeyboardScrollable(scroller);\n\n return (\n <DataTable__Shell className={className}>\n <DataTable__Scroller ref={setScroller} tabIndex={tabIndex}>\n <DataTable__Table>\n <DataTable__Caption>{caption}</DataTable__Caption>\n <thead>\n <tr>\n {columns.map((col) => (\n <DataTable__HeaderCell\n key={col.key}\n scope=\"col\"\n $align={col.align ?? \"start\"}\n style={{\n width: col.width,\n minWidth: col.minWidth,\n }}\n >\n {col.header}\n </DataTable__HeaderCell>\n ))}\n </tr>\n </thead>\n {total === 0 && empty !== undefined && (\n <tbody>\n <tr>\n <DataTable__EmptyCell colSpan={columns.length}>\n {empty}\n </DataTable__EmptyCell>\n </tr>\n </tbody>\n )}\n {groups.map((group) => (\n <tbody key={group.id}>\n {group.title !== null && group.title !== undefined && (\n <tr>\n <DataTable__SectionCell\n scope=\"rowgroup\"\n colSpan={columns.length}\n >\n {group.title}\n </DataTable__SectionCell>\n </tr>\n )}\n {group.rows.map((row) => {\n const key = rowKey(row);\n const detail = rowDetail?.(row);\n const hasDetail =\n detail !== null &&\n detail !== undefined &&\n detail !== false &&\n detail !== \"\";\n return (\n <Fragment key={key}>\n <DataTable__Row $hasDetail={hasDetail}>\n {columns.map((col) => (\n <DataTable__Cell\n key={col.key}\n as={col.rowHeader ? \"th\" : undefined}\n scope={col.rowHeader ? \"row\" : undefined}\n $align={col.align ?? \"start\"}\n >\n {cellContent(col, row)}\n </DataTable__Cell>\n ))}\n </DataTable__Row>\n {hasDetail ? (\n <tr>\n <DataTable__DetailCell colSpan={columns.length}>\n {detail}\n </DataTable__DetailCell>\n </tr>\n ) : null}\n </Fragment>\n );\n })}\n </tbody>\n ))}\n </DataTable__Table>\n </DataTable__Scroller>\n <InlineOverflowGlow $position=\"left\" $visible={overflow.left} />\n <InlineOverflowGlow $position=\"right\" $visible={overflow.right} />\n </DataTable__Shell>\n );\n}\n\n/* Reaches out to the panel's edges, so a table scrolled sideways passes under a glow in the gutter rather than stopping at the padding. */\nconst DataTable__Shell = styled.div`\n position: relative;\n min-width: 0;\n margin-inline: calc(-1 * var(--bleed-inline));\n`;\n\n/*\n * Wide content scrolls inside the table rather than pushing the widget's\n * layout sideways. A flex box, so the end padding is part of what scrolls and\n * the last column comes to rest on the content column too.\n */\nconst DataTable__Scroller = styled.div`\n display: flex;\n padding-inline: var(--bleed-inline);\n overflow-x: auto;\n min-width: 0;\n ${focusRingInset}\n scrollbar-width: none;\n &::-webkit-scrollbar {\n display: none;\n }\n`;\n\nconst DataTable__Table = styled.table`\n width: 100%;\n border-collapse: collapse;\n font-size: var(--font-size-compact);\n`;\n\n/* Announced, not shown: the visible heading is the widget's own. */\nconst DataTable__Caption = styled.caption`\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n`;\n\nconst DataTable__HeaderCell = styled.th<{ $align: \"start\" | \"end\" }>`\n text-align: ${({ $align }) => $align};\n position: sticky;\n top: 0;\n /* Local sibling ordering over its own rows, not a rung on the app ladder. */\n z-index: 1;\n background: var(--color-surface-panel);\n color: var(--color-text-faint);\n font-size: var(--font-size-caption);\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n white-space: nowrap;\n padding: var(--inset-table-cell);\n border-bottom: 1px solid var(--color-border-subtle);\n`;\n\nconst DataTable__SectionCell = styled.th`\n text-align: start;\n background: var(--color-surface-raised);\n color: var(--color-text-muted);\n font-size: var(--font-size-caption);\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: var(--inset-table-section);\n border-bottom: 1px solid var(--color-border-subtle);\n`;\n\n/* A row and its detail are one record, so only the bottom of the pair is ruled. */\nconst DataTable__Row = styled.tr<{ $hasDetail: boolean }>`\n &:not(:last-child) > td,\n &:not(:last-child) > th {\n border-bottom: ${({ $hasDetail }) =>\n $hasDetail ? \"none\" : \"1px solid var(--color-border-subtle)\"};\n }\n`;\n\n/* A row header is the same cell to the eye; only its role differs. */\nconst DataTable__Cell = styled.td<{ $align: \"start\" | \"end\" }>`\n text-align: ${({ $align }) => $align};\n font-weight: inherit;\n color: var(--color-text-primary);\n padding: var(--inset-table-cell);\n font-variant-numeric: tabular-nums;\n vertical-align: baseline;\n`;\n\nconst DataTable__DetailCell = styled.td`\n padding: var(--inset-table-detail);\n border-bottom: 1px solid var(--color-border-subtle);\n`;\n\nconst DataTable__EmptyCell = styled.td`\n color: var(--color-text-faint);\n font-style: italic;\n padding: var(--inset-table-empty);\n`;\n","import { useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\n\nexport interface InlineOverflow {\n /** Content is scrolled past on the start edge. */\n left: boolean;\n /** Content remains beyond the end edge. */\n right: boolean;\n}\n\nconst NONE: InlineOverflow = { left: false, right: false };\n\n/**\n * Which inline edges of a horizontal scroller have content beyond them, kept\n * current across scrolling and across the scroller or its children resizing.\n */\nexport function useInlineOverflow(el: HTMLElement | null): InlineOverflow {\n const [overflow, setOverflow] = useState(NONE);\n // React's same-value bailout is not guaranteed with other work queued, so an unchanged measurement is filtered here.\n const last = useRef(NONE);\n\n useEffect(() => {\n if (!el) return;\n const update = () => {\n const left = el.scrollLeft > 1;\n const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;\n if (last.current.left === left && last.current.right === right) return;\n last.current = { left, right };\n setOverflow(last.current);\n };\n\n update();\n el.addEventListener(\"scroll\", update, { passive: true });\n const ro =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n ro?.observe(el);\n for (const child of Array.from(el.children)) ro?.observe(child);\n const mo = new MutationObserver(() => {\n for (const child of Array.from(el.children)) ro?.observe(child);\n update();\n });\n mo.observe(el, { childList: true });\n return () => {\n el.removeEventListener(\"scroll\", update);\n ro?.disconnect();\n mo.disconnect();\n };\n }, [el]);\n\n return overflow;\n}\n\n/**\n * The glow over a horizontal scroller's edge that says more content lies that\n * way. At least the panel gutter wide, so a strip reaching the panel's edge\n * fades out across the gutter instead of stopping hard at the padding. Render\n * it after the scroller: it paints over it by document order.\n */\nexport const InlineOverflowGlow = styled.div<{\n $position: \"left\" | \"right\";\n $visible: boolean;\n}>`\n position: absolute;\n top: 0;\n bottom: 0;\n ${({ $position }) => ($position === \"left\" ? \"left: 0;\" : \"right: 0;\")}\n width: max(28px, var(--bleed-inline));\n pointer-events: none;\n opacity: ${({ $visible }) => ($visible ? 1 : 0)};\n transition: opacity var(--duration-base) var(--ease-standard);\n background: linear-gradient(\n to ${({ $position }) => ($position === \"left\" ? \"right\" : \"left\")},\n rgba(255, 255, 255, 0.12),\n rgba(255, 255, 255, 0) 100%\n );\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n`;\n","import { bandIn, type Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport {\n heldNameMarker,\n InstrumentBound,\n InstrumentHeldMark,\n sayHeld,\n} from \"./instrumentCurrency\";\nimport { NULL_DISPLAY } from \"./NullValue\";\nimport {\n figureAttributes,\n resolveCurrency,\n type UnitValue,\n} from \"./readingCurrency\";\nimport { placedOnScale, standsApart } from \"./standsApart\";\nimport { useTooltip } from \"./Tooltip\";\nimport { type FormatsFor, speakQuantity, writeQuantity } from \"./units\";\n\n/**\n * A coloured segment of a {@link Dial}'s arc, in the dial's own unit.\n *\n * @category Gauge\n */\nexport interface DialZone<Unit extends string = string> {\n /** Lower bound of the coloured arc segment. */\n from: Value<Unit>;\n /** Upper bound of the coloured arc segment. */\n to: Value<Unit>;\n /** The segment's colour: any CSS colour, such as a theme `var()`. */\n color: string;\n}\n\n/**\n * A tick mark on a {@link Dial}'s face, with an optional label beside it.\n *\n * @category Gauge\n */\nexport interface DialTick<Unit extends string = string> {\n /** Value at which to draw the tick. */\n value: Value<Unit>;\n /** Optional short label (e.g. \"N\", \"E\"). */\n label?: string;\n}\n\n/**\n * Props for {@link Dial}.\n *\n * @category Gauge\n */\nexport interface DialProps<Unit extends string = string> {\n /** Current value: the needle position, or the whole reading it arrived in. */\n value: UnitValue<Unit>;\n /** The value at `startAngle`. */\n min: Value<Unit>;\n /** The value at `startAngle + sweep`. */\n max: Value<Unit>;\n /**\n * Drawn width in pixels. The SVG scales down to a narrower slot rather than\n * clipping.\n *\n * @defaultValue `120`\n */\n width?: number;\n /**\n * Drawn height in pixels.\n *\n * @defaultValue `120`\n */\n height?: number;\n /**\n * Degrees clockwise from up where `min` sits.\n *\n * @defaultValue `0`, the top\n */\n startAngle?: number;\n /**\n * Degrees swept from `min` to `max`.\n *\n * @defaultValue `360`, a full circle\n */\n sweep?: number;\n /**\n * Treat the value as wrapping (compass): value is taken modulo the range.\n * Without it a value outside `min..max` pins the needle at the nearer end.\n */\n wrap?: boolean;\n /** Coloured arc segments. */\n zones?: ReadonlyArray<DialZone<Unit>>;\n /** Tick marks around the face. */\n ticks?: ReadonlyArray<DialTick<Unit>>;\n /**\n * Pin the rung the centre readout is written at, for the cases where\n * convention beats magnitude. Absent, the value's own kind decides.\n */\n format?: FormatsFor<Unit>;\n /** Centre label override. Defaults to the value, written with its unit. */\n valueLabel?: string;\n /**\n * Draws a half-circle face with the figure under the hub, the instrument\n * layout for a gauge whose figure is the point: the arc is raised to the top\n * of the box and a strip is reserved under it for the figure. `regular` and\n * `large` set the figure's size. Absent, the figure sits in the centre of\n * the face. Honoured only for a face that sweeps 180° or less.\n */\n readout?: \"regular\" | \"large\";\n /** Needle colour. Defaults to `var(--color-text-primary)`. */\n needleColor?: string;\n /** Arc track colour. Defaults to `var(--color-border-subtle)`. */\n trackColor?: string;\n /** Accessible name. Defaults to `\"Dial\"`; the value is spoken as `aria-valuetext`. */\n ariaLabel?: string;\n}\n\nconst TRACK_THICKNESS = 6;\nconst HUB_RADIUS = 3;\n\n/** The half-circle layout's face: a thicker track and hub, and the figure's size with its baseline below the hub (the strip reserved under the arc). */\nconst RAISED = { track: 8, hub: 4, needle: 0.92 } as const;\nconst READOUT = {\n regular: { size: 16, drop: 18 },\n large: { size: 24, drop: 32 },\n} as const;\n\n/** Point on a circle of radius `r`, `angleDeg` clockwise from up (12 o'clock). */\nfunction pointAt(\n cx: number,\n cy: number,\n r: number,\n angleDeg: number,\n): { x: number; y: number } {\n const a = (angleDeg * Math.PI) / 180;\n return { x: cx + r * Math.sin(a), y: cy - r * Math.cos(a) };\n}\n\nfunction arcPath(\n cx: number,\n cy: number,\n r: number,\n a0: number,\n a1: number,\n): string {\n const p0 = pointAt(cx, cy, r, a0);\n const p1 = pointAt(cx, cy, r, a1);\n const delta = a1 - a0;\n const largeArc = Math.abs(delta) > 180 ? 1 : 0;\n const sweepFlag = delta >= 0 ? 1 : 0;\n return `M ${p0.x.toFixed(2)} ${p0.y.toFixed(2)} A ${r} ${r} 0 ${largeArc} ${sweepFlag} ${p1.x.toFixed(2)} ${p1.y.toFixed(2)}`;\n}\n\n/**\n * A round instrument whose needle sweeps a configurable arc (by default a full\n * 360° compass), so it can show a heading that wraps as well as a bounded\n * value. With `readout`, a half-circle face puts its figure under the hub.\n *\n * Angles are degrees clockwise from 12 o'clock (up is 0°), as on a compass.\n * `startAngle` places `min`; `sweep` is the span from `min` to `max`.\n *\n * The whole axis is one kind: `value`, `min`, `max`, every zone bound and\n * every tick are `Value<Unit>` of the same unit, and the centre readout writes\n * that unit itself. Handed a whole `Reading`, a held figure is marked as held\n * and a model's interval puts two bounds on the arc.\n *\n * The dial is a `role=\"meter\"` with the value as `aria-valuetext`. With no\n * value it draws no needle and is announced as an image naming the missing\n * value.\n *\n * @example A heading compass\n * ```tsx\n * import { useTelemetry, value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { Dial } from \"@ksp-gonogo/ui-kit\";\n *\n * function HeadingCompass() {\n * const attitude = useTelemetry(\"vessel.attitude\");\n * return (\n * <Dial\n * value={attitude.heading}\n * min={value(\"°\", 0)}\n * max={value(\"°\", 360)}\n * wrap\n * ticks={[\n * { value: value(\"°\", 0), label: \"N\" },\n * { value: value(\"°\", 90), label: \"E\" },\n * { value: value(\"°\", 180), label: \"S\" },\n * { value: value(\"°\", 270), label: \"W\" },\n * ]}\n * ariaLabel=\"Heading\"\n * />\n * );\n * }\n * ```\n *\n * @category Gauge\n * @categoryDescription Gauge\n * Instruments for a value against a scale: a dial and a tape, each with ticks\n * and coloured zones.\n */\nexport function Dial<Unit extends string = string>({\n value,\n min,\n max,\n width = 120,\n height = 120,\n startAngle = 0,\n sweep = 360,\n wrap = false,\n zones,\n ticks,\n valueLabel,\n readout,\n format,\n needleColor = \"var(--color-text-primary)\",\n trackColor = \"var(--color-border-subtle)\",\n ariaLabel,\n}: Readonly<DialProps<Unit>>) {\n const resolved = resolveCurrency(value);\n const { shown, held, caption, band } = resolved;\n const { anchor, tip } = useTooltip(caption);\n\n // The axis, unwrapped once into the face's angular geometry; every figure a reader sees goes back out through the unit layer.\n const current = shown?.magnitude ?? Number.NaN;\n const axisMin = min.magnitude;\n const axisMax = max.magnitude;\n const span = axisMax - axisMin;\n const safe = Number.isFinite(current) ? current : axisMin;\n const display =\n span > 0\n ? wrap\n ? axisMin + ((((safe - axisMin) % span) + span) % span)\n : Math.max(axisMin, Math.min(axisMax, safe))\n : axisMin;\n // No number, no needle; a present but non-finite number still falls back to the start of the scale.\n const hasFigure = shown != null;\n const face = shown == null ? null : { magnitude: display, unit: shown.unit };\n // An SVG `<text>` cannot contain a `<span>`, so this uses `writeQuantity`, with `speakQuantity` for `aria-valuetext`.\n const centreLabel =\n valueLabel ?? (face === null ? null : writeQuantity(face, { format }));\n const spoken = face === null ? NULL_DISPLAY : speakQuantity(face, { format });\n // The model's interval, narrowed to the figure's own unit.\n const interval =\n shown == null || band === null ? null : (bandIn(band, shown.unit) ?? null);\n\n const raised = readout !== undefined && sweep <= 180;\n const trackThickness = raised ? RAISED.track : TRACK_THICKNESS;\n const hubRadius = raised ? RAISED.hub : HUB_RADIUS;\n const figure = readout === undefined ? null : READOUT[readout];\n const cx = width / 2;\n // Raised: the hub sits on the arc's base line, with the figure's strip below it.\n const r =\n raised && figure !== null\n ? Math.min(\n (width - trackThickness) / 2,\n height - trackThickness - figure.drop,\n )\n : Math.min(width, height) / 2 - trackThickness - 2;\n const cy = raised ? r + trackThickness / 2 : height / 2;\n\n const angleOf = (v: number): number => {\n const t = span > 0 ? (v - axisMin) / span : 0;\n return startAngle + t * sweep;\n };\n\n /*\n * Onto the axis (clamped in the algebra, so another rung of the same kind\n * converts before it compares), then onto the face. Zone order and emptiness\n * are decided on quantities, since angles would flip under a negative sweep.\n */\n const onAxis = (q: Value<Unit>): Value<Unit> => q.max(min).min(max);\n const onFace = (q: Value<Unit>): number => angleOf(q.magnitude);\n\n // Compared as fractions of the sweep, from the angles the marks are drawn at.\n const onSweep = (angle: number): number =>\n sweep !== 0 ? (angle - startAngle) / sweep : 0;\n const drawnInterval =\n interval !== null &&\n standsApart(\n onSweep(angleOf(display)),\n [\n onSweep(onFace(onAxis(interval.lo))),\n onSweep(onFace(onAxis(interval.hi))),\n ],\n placedOnScale({ wraps: wrap }),\n )\n ? interval\n : null;\n\n const isFullCircle = sweep >= 360;\n const needle = pointAt(\n cx,\n cy,\n r * (raised ? RAISED.needle : 0.88),\n angleOf(display),\n );\n\n return (\n <Dial__Meter\n {...figureAttributes(resolved)}\n {...anchor}\n {...(hasFigure\n ? {\n role: \"meter\",\n \"aria-label\": sayHeld(ariaLabel ?? \"Dial\", caption),\n ...heldNameMarker(caption),\n \"aria-valuenow\": display,\n \"aria-valuemin\": axisMin,\n \"aria-valuemax\": axisMax,\n \"aria-valuetext\": spoken,\n }\n : {\n /* With no value to state, the face is an image rather than a meter at its minimum. */\n role: \"img\",\n \"aria-label\": sayHeld(`${ariaLabel ?? \"Dial\"}: ${spoken}`, caption),\n ...heldNameMarker(caption),\n })}\n >\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n aria-hidden=\"true\"\n style={{\n display: \"block\",\n fontFamily: \"var(--font-family-mono)\",\n maxWidth: \"100%\",\n height: \"auto\",\n }}\n >\n {r > 0 &&\n (isFullCircle ? (\n <circle\n cx={cx}\n cy={cy}\n r={r}\n fill=\"none\"\n stroke={trackColor}\n strokeWidth={trackThickness}\n />\n ) : (\n <path\n d={arcPath(cx, cy, r, startAngle, startAngle + sweep)}\n fill=\"none\"\n stroke={trackColor}\n strokeWidth={trackThickness}\n strokeLinecap=\"round\"\n />\n ))}\n\n {/* The track's rounded ends take the colour of the zone that reaches them, so a zoned arc has no grey cap. */}\n {r > 0 &&\n !isFullCircle &&\n zones?.flatMap((z) => {\n const lo = onAxis(z.from.min(z.to));\n const hi = onAxis(z.from.max(z.to));\n if (!hi.greaterThan(lo)) return [];\n return [\n // Clamped onto the axis already, so \"not past the end\" is \"at the end\".\n lo.greaterThan(min) ? null : startAngle,\n max.greaterThan(hi) ? null : startAngle + sweep,\n ].flatMap((angle) => {\n if (angle === null) return [];\n const end = pointAt(cx, cy, r, angle);\n return [\n <circle\n key={`cap-${z.color}-${angle}`}\n data-dial-cap=\"\"\n cx={end.x}\n cy={end.y}\n r={trackThickness / 2}\n fill={z.color}\n />,\n ];\n });\n })}\n\n {r > 0 &&\n zones?.map((z) => {\n const lo = onAxis(z.from.min(z.to));\n const hi = onAxis(z.from.max(z.to));\n if (!hi.greaterThan(lo)) return null;\n return (\n <path\n key={`zone-${z.color}-${onFace(lo)}-${onFace(hi)}`}\n d={arcPath(cx, cy, r, onFace(lo), onFace(hi))}\n fill=\"none\"\n stroke={z.color}\n strokeWidth={trackThickness}\n strokeLinecap=\"butt\"\n />\n );\n })}\n\n {r > 0 &&\n ticks?.map((tk) => {\n const at = tk.value.magnitude;\n const a = angleOf(at);\n const outer = pointAt(cx, cy, r, a);\n const inner = pointAt(cx, cy, r - trackThickness, a);\n const labelPt = pointAt(cx, cy, r - trackThickness - 8, a);\n return (\n <g key={`tick-${at}-${tk.label ?? \"\"}`}>\n <line\n x1={inner.x}\n y1={inner.y}\n x2={outer.x}\n y2={outer.y}\n stroke=\"var(--color-text-faint)\"\n strokeWidth={1}\n />\n {tk.label && (\n <text\n x={labelPt.x}\n y={labelPt.y}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n fontSize={9}\n fill=\"var(--color-text-faint)\"\n >\n {tk.label}\n </text>\n )}\n </g>\n );\n })}\n\n {r > 0 &&\n drawnInterval !== null &&\n ([\"lo\", \"hi\"] as const).map((end) => {\n const a = onFace(onAxis(drawnInterval[end]));\n const inner = pointAt(cx, cy, r - trackThickness / 2, a);\n const outer = pointAt(cx, cy, r + trackThickness / 2, a);\n return (\n <InstrumentBound\n key={end}\n end={end}\n x1={inner.x}\n y1={inner.y}\n x2={outer.x}\n y2={outer.y}\n />\n );\n })}\n\n {r > 0 && hasFigure && (\n <>\n <line\n x1={cx}\n y1={cy}\n x2={needle.x}\n y2={needle.y}\n stroke={needleColor}\n strokeWidth={2}\n strokeLinecap=\"round\"\n />\n <circle cx={cx} cy={cy} r={hubRadius} fill={needleColor} />\n </>\n )}\n\n <text\n x={cx}\n y={raised && figure !== null ? cy + figure.drop : cy + r * 0.55}\n textAnchor=\"middle\"\n fontSize={raised && figure !== null ? figure.size : 13}\n fontWeight={raised ? undefined : \"bold\"}\n fill={\n centreLabel === null\n ? \"var(--color-text-muted)\"\n : \"var(--color-text-primary)\"\n }\n >\n {centreLabel ?? NULL_DISPLAY}\n {held && centreLabel !== null && (\n <InstrumentHeldMark\n size={raised ? 7 : 6}\n figureSize={raised && figure !== null ? figure.size : 13}\n />\n )}\n </text>\n </svg>\n {tip}\n </Dial__Meter>\n );\n}\n\nconst Dial__Meter = styled.div`\n display: block;\n max-width: 100%;\n`;\n","import { type ReactNode, useId, useRef, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Button, type ButtonSize } from \"./Button\";\nimport { focusRing, focusRingInset } from \"./focusRing\";\nimport { ChevronRightIcon } from \"./Icons\";\nimport { useKeyboardScrollable } from \"./useScrollerMetric\";\n\n/**\n * Props for {@link Disclosure}.\n *\n * @category Disclosure\n */\nexport interface DisclosureProps {\n /**\n * The always-visible trigger content (text, a glyph, a badge). Pass a\n * function of `open` when the label itself should read differently once\n * expanded (\"Show detail\" to \"Hide detail\", for example).\n */\n label: ReactNode | ((open: boolean) => ReactNode);\n /** The panel content, revealed when open. */\n children: ReactNode;\n /**\n * Accessible name for the trigger. Required when `label` is a non-text node\n * (an icon or glyph) so the button is not unlabelled to a screen reader.\n */\n ariaLabel?: string;\n /** A class for the outer element, so a styled-component can extend it. */\n className?: string;\n /**\n * Where the panel opens. `\"popover\"`, the default, pops it out over whatever\n * follows, sized to its content, for a compact hint beside a tight trigger.\n * `\"inline\"` expands it in flow below the trigger, full width, as an\n * accordion, and the trigger grows a rotating chevron unless\n * `chevron={false}`.\n */\n variant?: \"popover\" | \"inline\";\n /**\n * How tall an `inline` panel may grow. `\"cap\"` (default) stops at a fixed\n * height and scrolls. `\"auto\"` grows in flow, for a panel already inside a\n * scrolling body, where a second scroller would hide content.\n */\n panelHeight?: \"cap\" | \"auto\";\n /**\n * Whether the rotating chevron renders for `variant=\"inline\"`. Defaults to\n * `true`. Set `false` when `label` already reads as the affordance, such as\n * \"Show detail\"; the label then right-aligns where the chevron would sit.\n */\n chevron?: boolean;\n /**\n * Renders the trigger as the kit's ghost `Button` (bordered, padded chrome)\n * instead of a plain unstyled one, sized to its content and right-aligned.\n * Pair with `chevron={false}` and a worded `label`: a chevron-less label with\n * no chrome reads as plain text.\n */\n asButton?: boolean;\n /**\n * Size of the `asButton` trigger, the `Button` size of the same name.\n */\n buttonSize?: ButtonSize;\n /**\n * Whether the panel starts expanded. Defaults to `false`.\n *\n * Pass `true` where the panel is the primary content and the trigger lets it\n * be folded away in a short tile. It is read once, at mount; to reset it,\n * give the Disclosure a React key that changes with the condition.\n */\n defaultOpen?: boolean;\n}\n\n/**\n * A button that shows and hides a panel of detail. The trigger is a real\n * `<button aria-expanded>`, so Enter and Space toggle it, and Escape closes\n * the panel and returns focus to the trigger. Use it for detail on demand\n * wherever a hover-only reveal would be unreachable without a mouse. By\n * default the panel pops over what follows; `variant=\"inline\"` expands it in\n * flow as an accordion.\n *\n * @example\n * ```tsx\n * <Disclosure\n * variant=\"inline\"\n * chevron={false}\n * label={(open) => (open ? \"Hide detail\" : \"Show detail\")}\n * >\n * <StageBreakdown stages={stages} />\n * </Disclosure>\n * ```\n *\n * @category Disclosure\n * @categoryDescription Disclosure\n * Detail on demand: a button that shows and hides a panel, reachable from the\n * keyboard where a hover reveal would not be.\n */\nexport function Disclosure({\n label,\n children,\n ariaLabel,\n className,\n variant = \"popover\",\n panelHeight = \"cap\",\n chevron = true,\n asButton = false,\n buttonSize = \"md\",\n defaultOpen = false,\n}: DisclosureProps) {\n const [open, setOpen] = useState(defaultOpen);\n const panelId = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const [panel, setPanel] = useState<HTMLDivElement | null>(null);\n const panelTabIndex = useKeyboardScrollable(panel);\n const showChevron = variant === \"inline\" && chevron;\n const resolvedLabel = typeof label === \"function\" ? label(open) : label;\n const triggerProps = {\n ref: triggerRef,\n type: \"button\" as const,\n \"aria-expanded\": open,\n \"aria-controls\": panelId,\n \"aria-label\": ariaLabel,\n onClick: () => setOpen((v) => !v),\n };\n const triggerBody = (\n <>\n {resolvedLabel}\n {showChevron && (\n <Disclosure__Chevron $open={open}>\n <ChevronRightIcon size=\"var(--icon-size-control)\" />\n </Disclosure__Chevron>\n )}\n </>\n );\n\n return (\n <Disclosure__Root\n className={className}\n $variant={variant}\n onKeyDown={(e) => {\n if (e.key === \"Escape\" && open) {\n setOpen(false);\n triggerRef.current?.focus();\n }\n }}\n >\n {asButton ? (\n <Disclosure__ButtonTrigger\n {...triggerProps}\n variant=\"ghost\"\n size={buttonSize}\n $inline={variant === \"inline\"}\n >\n {triggerBody}\n </Disclosure__ButtonTrigger>\n ) : (\n <Disclosure__Trigger\n {...triggerProps}\n $variant={variant}\n $align={variant === \"inline\" && !chevron ? \"end\" : \"between\"}\n >\n {triggerBody}\n </Disclosure__Trigger>\n )}\n {open && (\n <Disclosure__Panel\n ref={setPanel}\n id={panelId}\n role=\"group\"\n tabIndex={panelTabIndex}\n $variant={variant}\n $panelHeight={panelHeight}\n >\n {children}\n </Disclosure__Panel>\n )}\n </Disclosure__Root>\n );\n}\n\nconst Disclosure__Root = styled.div<{ $variant: \"popover\" | \"inline\" }>`\n position: relative;\n display: ${({ $variant }) => ($variant === \"inline\" ? \"flex\" : \"inline-flex\")};\n flex-direction: column;\n width: ${({ $variant }) => ($variant === \"inline\" ? \"100%\" : \"auto\")};\n`;\n\nconst Disclosure__Trigger = styled.button<{\n $variant: \"popover\" | \"inline\";\n $align: \"between\" | \"end\";\n}>`\n display: inline-flex;\n align-items: center;\n gap: var(--gap-glyph);\n padding: var(--inset-glyph-button);\n background: none;\n border: none;\n color: inherit;\n cursor: pointer;\n ${({ $variant, $align }) =>\n $variant === \"inline\" &&\n css`\n justify-content: ${$align === \"end\" ? \"flex-end\" : \"space-between\"};\n width: 100%;\n border-radius: var(--radius-regular);\n &:hover {\n background: var(--color-surface-sunken);\n }\n `}\n ${focusRing}\n`;\n\n/** The `asButton` trigger: the kit's ghost `Button`, pinned to the row's trailing edge when inline. */\nconst Disclosure__ButtonTrigger = styled(Button)<{ $inline: boolean }>`\n ${({ $inline }) =>\n $inline &&\n css`\n align-self: flex-end;\n `}\n`;\n\nconst Disclosure__Chevron = styled.span<{ $open: boolean }>`\n display: inline-flex;\n flex-shrink: 0;\n @media (prefers-reduced-motion: no-preference) {\n transition: transform var(--duration-base) var(--ease-standard);\n }\n transform: rotate(${({ $open }) => ($open ? 90 : 0)}deg);\n`;\n\nconst Disclosure__Panel = styled.div<{\n $variant: \"popover\" | \"inline\";\n $panelHeight: \"cap\" | \"auto\";\n}>`\n ${({ $variant }) =>\n $variant === \"popover\"\n ? css`\n position: absolute;\n top: 100%;\n right: 0;\n /* Local sibling ordering: lifts the popped panel above following rows, off the app z ladder. */\n z-index: 1;\n margin-top: var(--gap-disclosure);\n `\n : css`\n position: static;\n width: 100%;\n margin-top: var(--gap-disclosure);\n `}\n /* An accordion body grows in flow up to a cap, then scrolls, never spilling past the row below. */\n ${({ $variant, $panelHeight }) =>\n $variant === \"inline\" && $panelHeight === \"cap\"\n ? css`\n max-height: 16rem;\n overflow-y: auto;\n ${focusRingInset}\n `\n : \"\"}\n padding: var(--inset-surface);\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n`;\n","import type { Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled, { css } from \"styled-components\";\nimport {\n figureAttributes,\n resolveCurrency,\n type UnitValue,\n} from \"./readingCurrency\";\n\n/**\n * Props for {@link DivergingBar}.\n *\n * @category Meter\n */\nexport interface DivergingBarProps<Unit extends string = string> {\n /**\n * The signed quantity, or the whole reading it arrived in. Its sign picks the\n * direction: `>= 0` grows the fill rightward from the centre zero line in the\n * \"go\" green, `< 0` grows it leftward in the \"nogo\" red.\n */\n value: UnitValue<Unit>;\n /**\n * The largest `|value|` among the set this bar is compared against (e.g.\n * every term in a rate ledger), in the same unit. The fill reaches the\n * track's half-width when `|value| === maxAbs`; a non-positive scale renders\n * an empty track.\n */\n maxAbs: Value<Unit>;\n className?: string;\n}\n\n/**\n * A small bar centred on zero, for a signed quantity whose DIRECTION matters\n * as much as its size (a ledger term that produces or consumes). Decorative\n * and `aria-hidden`: pair it with the number, which carries the reading.\n *\n * A held figure fades the bar and nothing more; the number beside it says how\n * current it is.\n *\n * Hides itself when the nearest `inline-size` container (such as `Panel`) is\n * narrower than 300px, where the number alone is the reading that matters.\n *\n * @category Meter\n */\nexport function DivergingBar<Unit extends string = string>({\n value,\n maxAbs,\n className,\n}: DivergingBarProps<Unit>) {\n const resolved = resolveCurrency(value);\n const { shown, held } = resolved;\n // `dividedBy` checks the two are the same kind, so the quotient is dimensionless before it becomes a CSS width.\n const pct =\n shown != null && maxAbs.isPositive()\n ? Math.min(50, shown.abs().dividedBy(maxAbs).magnitude * 50)\n : 0;\n return (\n <DivergingBar__Track\n aria-hidden=\"true\"\n data-testid=\"diverging-bar\"\n {...figureAttributes(resolved)}\n className={className}\n >\n <DivergingBar__Zero />\n {/* No fill without a number: a zero-width bar would read as producing and consuming nothing. */}\n {shown != null && (\n <DivergingBar__Fill\n $positive={!shown.isNegative()}\n $held={held}\n style={{ width: `${pct}%` }}\n />\n )}\n </DivergingBar__Track>\n );\n}\n\n/** Below this container width a label, a bar and a number do not fit on one line. */\nconst DIVERGING_BAR_MIN_CONTAINER = \"300px\";\n\nconst DivergingBar__Track = styled.div`\n position: relative;\n width: 3.5rem;\n height: 4px;\n flex: 0 0 auto;\n border-radius: var(--radius-pill);\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-subtle);\n display: none;\n\n @container (min-width: ${DIVERGING_BAR_MIN_CONTAINER}) {\n display: block;\n }\n`;\n\nconst DivergingBar__Zero = styled.div`\n position: absolute;\n left: 50%;\n top: 0;\n bottom: 0;\n width: 1px;\n background: var(--color-border-strong);\n`;\n\nconst DivergingBar__Fill = styled.div<{\n $positive: boolean;\n $held: boolean;\n}>`\n position: absolute;\n top: 0;\n bottom: 0;\n border-radius: var(--radius-pill);\n /* Faint rather than a second hue: the fill's colour already carries the direction. */\n ${({ $held }) => ($held ? \"opacity: 0.45;\" : \"\")}\n ${({ $positive }) =>\n $positive\n ? css`\n left: 50%;\n background: var(--color-go-mark);\n `\n : css`\n right: 50%;\n background: var(--color-nogo-mark);\n `}\n`;\n","import type { HTMLAttributes } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Props for {@link Divider}. Any other `hr` attribute passes through.\n *\n * @category Layout\n */\nexport interface DividerProps extends HTMLAttributes<HTMLHRElement> {\n /**\n * Vertical space above and below the rule, one of the {@link GapToken}\n * names. Omit it for a flush rule, spaced by its surroundings.\n */\n space?: GapToken;\n}\n\n/**\n * A full-width 1px horizontal rule in the subtle border colour, for separating\n * stacked sections. It renders a real `<hr>`, so it carries separator\n * semantics. Flush by default; `space` adds room above and below.\n *\n * @category Layout\n */\nexport function Divider({ space, ...rest }: DividerProps) {\n return <Divider__Root $space={space} {...rest} />;\n}\n\nconst Divider__Root = styled.hr<{ $space?: GapToken }>`\n border: 0;\n border-top: 1px solid var(--color-border-subtle);\n width: 100%;\n margin: ${({ $space }) => ($space ? `${GAP_VAR[$space]} 0` : \"0\")};\n`;\n","import { useId, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { TextButton } from \"./Button\";\n\n/**\n * How much prose stands unprompted, in characters: roughly two lines at the\n * kit's body size in a dashboard column.\n */\nconst DEFAULT_LIMIT = 160;\n\n/**\n * Under this much text past the limit the \"Show more\" button costs more room\n * than the tail it would reveal, so the text stands whole.\n */\nconst WORTH_CUTTING = 24;\n\n/**\n * Three periods rather than the ellipsis character, which the design system\n * does not allow.\n */\nconst CUT_MARK = \"...\";\n\n/**\n * Props for {@link ExpandableText}.\n *\n * @category Typography\n */\nexport interface ExpandableTextProps {\n /** The authored text, rendered verbatim whether cut or whole. */\n children: string;\n /** Characters shown before the cut. Defaults to 160. */\n limit?: number;\n /**\n * What the prose describes (\"Objectives\", \"Description\"), naming the control\n * to a screen reader as \"Show more of Objectives\".\n */\n subject?: string;\n /** A class for the wrapping element, so a styled-component can extend it. */\n className?: string;\n}\n\n/**\n * Game-authored prose, cut to a readable length with the rest behind a\n * \"Show more\" button.\n *\n * The cut is a prefix of the string ending on a whole word, followed by\n * `...`; text with no space before `limit` is cut hard at `limit`. It is a character count, not\n * a CSS line clamp. Text no more than 24 characters past `limit` is shown\n * whole with no button, as a bare text node. The reveal is instantaneous.\n *\n * @category Typography\n */\nexport function ExpandableText({\n children,\n limit = DEFAULT_LIMIT,\n subject,\n className,\n}: ExpandableTextProps) {\n const [expanded, setExpanded] = useState(false);\n const proseId = useId();\n\n const head = cut(children, limit);\n\n // Prose short enough to stand whole renders as the caller's own text node.\n if (head === undefined) return <>{children}</>;\n\n const verb = expanded ? \"Show less\" : \"Show more\";\n\n return (\n <ExpandableText__Root className={className}>\n <span id={proseId}>{expanded ? children : `${head}${CUT_MARK}`}</span>{\" \"}\n <ExpandableText__Toggle\n type=\"button\"\n /* A stable hook for a render scene, rather than a generated class name. */\n data-expandable-toggle=\"\"\n aria-controls={proseId}\n aria-expanded={expanded}\n aria-label={subject === undefined ? undefined : `${verb} of ${subject}`}\n onClick={() => setExpanded((open) => !open)}\n >\n {verb}\n </ExpandableText__Toggle>\n </ExpandableText__Root>\n );\n}\n\n/**\n * The prefix to show, or `undefined` when the text is short enough to stand\n * whole. Cuts on the last space at or before `limit`; a run-on with no space\n * is cut hard at `limit`.\n */\nfunction cut(text: string, limit: number): string | undefined {\n if (text.length <= limit + WORTH_CUTTING) return undefined;\n\n const boundary = text.lastIndexOf(\" \", limit);\n return boundary <= 0 ? text.slice(0, limit) : text.slice(0, boundary);\n}\n\nconst ExpandableText__Root = styled.span`\n /* Long prose may carry a word wider than its column, and a broken word beats a sideways scroll. */\n overflow-wrap: break-word;\n`;\n\nconst ExpandableText__Toggle = styled(TextButton)`\n /* Sits on the text's baseline as the paragraph's last word, so the cut and its undo read as one. */\n white-space: nowrap;\n`;\n","import {\n asDeterministic,\n type CelestialBody,\n type CelestialFacts,\n type Reading,\n type Value,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport type { UnitValue } from \"./readingCurrency\";\n\n/**\n * Turns a quantity computed from the body catalogue into the figure a readout\n * draws, carrying how exact and how current it is. Made by\n * {@link ephemerisFigureOf}; hand the result to `Unit`'s `value`.\n *\n * @category Unit\n */\nexport type EphemerisFigure = <Unit extends string>(\n figure: Value<Unit>,\n) => UnitValue<Unit>;\n\n/**\n * The figure-maker for quantities computed from `bodies`' orbits and the clock\n * alone: a phase angle between two planets, a transfer window between them, a\n * pair's separation. Pass the catalogue reading and the bodies the figure rests\n * on.\n *\n * Where every one of those bodies is deterministic (a fixed conic, which is\n * every body of a stock install) the figure is stamped deterministic and takes\n * no mark, however long ago the catalogue last arrived: it is exact at the\n * instant it is drawn for. Where any is not (an n-body install, whose bodies\n * drift like craft) the figure is only as current as the catalogue, and a held\n * catalogue hands it over as held.\n *\n * A figure that also rests on the craft does not come through here: give it\n * the craft's mark with {@link derivedMarking}, since one inexact input makes\n * the result inexact.\n *\n * @category Unit\n */\nexport function ephemerisFigureOf(\n catalogue: Reading<CelestialFacts> | undefined,\n bodies: readonly (CelestialBody | null | undefined)[],\n): EphemerisFigure {\n const exact =\n bodies.length > 0 && bodies.every((body) => body?.deterministic === true);\n return (figure) => {\n const drawn = exact ? asDeterministic(figure) : figure;\n if (catalogue?.state !== \"held\") return drawn;\n return {\n state: \"held\",\n value: drawn,\n asOfUt: catalogue.asOfUt,\n grade: catalogue.grade,\n reckoning: { status: \"none\" },\n };\n };\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\n\n/**\n * Props for {@link Fill}.\n *\n * @category Layout\n */\nexport interface FillProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Grow to fill a flex parent (`flex: 1 1 auto`) instead of taking full\n * height. Use for a slot nested in a column (e.g. a graph slot sitting\n * above a sibling notice row); omit for the outermost container that\n * fills the dashboard grid cell.\n */\n grow?: boolean;\n children?: ReactNode;\n}\n\n/**\n * An overlay anchor that fills its parent: a `position: relative` flex column\n * that occupies exactly the space its parent gives it, so an absolutely\n * positioned overlay (e.g. a corner notice pill over a graph) has something to\n * sit on. It draws nothing itself. Full width and height by default; `grow`\n * for a slot nested in another flex column.\n *\n * @category Layout\n */\nexport function Fill({ grow = false, children, ...rest }: FillProps) {\n return (\n <Fill__Root $grow={grow} {...rest}>\n {children}\n </Fill__Root>\n );\n}\n\nconst Fill__Root = styled.div<{ $grow: boolean }>`\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n\n ${({ $grow }) =>\n $grow\n ? `flex: 1 1 auto;`\n : `\n height: 100%;\n width: 100%;\n `}\n`;\n","import type { ReactNode } from \"react\";\nimport { EmptyState } from \"./EmptyState\";\nimport { Stack } from \"./Stack\";\nimport { FilterRegion, type TermSegment, useRowFilter } from \"./useRowFilter\";\n\n/*\n * A filterable list over pre-processed rows: each carries the text it is\n * searchable by and its rendered node, so FilterList only matches strings.\n *\n * Providers add search terms, shown as toggles, through the universal\n * `filters` contribution segment, completed from the mounting widget's\n * context. Outside a widget the term list is empty and every row passes. The\n * filter model is `useRowFilter`, shared with hosts that render their own rows.\n */\n\n/**\n * One row of a {@link FilterList}: its key, the text it is matched against,\n * and the node to draw.\n *\n * @category FilterList\n */\nexport interface FilterRow {\n /** Stable React key for the row. */\n id: string;\n /** The text this row is matched against, built by the widget from its own fields. */\n searchText: string;\n /** The already-rendered row. */\n node: ReactNode;\n}\n\n/**\n * Props for {@link FilterList}.\n *\n * @category FilterList\n */\nexport interface FilterListProps {\n /** Every row, in display order; the list shows the ones that match. */\n rows: readonly FilterRow[];\n /** The contribution segment to pull toggle terms from. Defaults to the universal `filters`. */\n segment?: TermSegment;\n /** Shown when no row matches. Defaults to \"Nothing matches the filter\". */\n emptyLabel?: ReactNode;\n}\n\n/**\n * A filterable list of pre-rendered rows, with its filter control (toggle\n * chips for contributed terms, then a search box) above it. Each row carries\n * the text it is searchable by, so the list only matches strings: a row shows\n * while its `searchText` contains every selected term and the typed text,\n * case-insensitively.\n *\n * The toggle terms come from the `filters` contribution segment of the widget\n * the list is mounted in; outside a widget there are none and only the search\n * box shows. For rows the host renders itself (a table), use\n * {@link useRowFilter} with {@link FilterRegion} instead.\n *\n * @example\n * ```tsx\n * <FilterList\n * rows={contracts.map((c) => ({\n * id: c.id,\n * searchText: `${c.title} ${c.agency}`,\n * node: <Card title={c.title}>{c.agency}</Card>,\n * }))}\n * emptyLabel=\"No contracts match\"\n * />\n * ```\n *\n * @category FilterList\n * @categoryDescription FilterList\n * Lists an operator can narrow: rows filtered by typed text and contributed\n * filter chips, selectable rows, and the hook for filtering rows a widget draws\n * itself.\n */\nexport function FilterList({\n rows,\n segment = \"filters\",\n emptyLabel = \"Nothing matches the filter\",\n}: FilterListProps) {\n const filter = useRowFilter({ segment });\n const shown = rows.filter((row) => filter.matches(row.searchText));\n\n return (\n <FilterRegion filter={filter}>\n {shown.length > 0 ? (\n <Stack gap=\"related\">\n {shown.map((row) => (\n <div key={row.id}>{row.node}</div>\n ))}\n </Stack>\n ) : (\n <EmptyState>{emptyLabel}</EmptyState>\n )}\n </FilterRegion>\n );\n}\n","import {\n type HTMLAttributes,\n type ReactNode,\n useCallback,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styled from \"styled-components\";\nimport {\n type AnchoredPosition,\n type AnchorPoint,\n anchoredPosition,\n} from \"./anchoredPosition\";\n\n/**\n * The props of {@link Floating}.\n *\n * @category Floating\n */\nexport interface FloatingProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * The viewport point the layer attaches to, or a function returning it. A\n * function is re-read whenever the layer re-places (on resize and on any\n * scroll), so a layer anchored to an element follows it.\n */\n anchor: AnchorPoint | (() => AnchorPoint | null);\n /** What the layer shows. It has no surface of its own, so style it here. */\n children?: ReactNode;\n}\n\n/**\n * A layer drawn over the whole page rather than inside its widget: a hover\n * card, a tooltip, an anchored menu. It is portalled to the document body, so\n * no panel or scroll box clips it, and placed against the viewport by\n * {@link anchoredPosition}. It is measured before paint (and stays hidden\n * until then), and re-placed on every render, on resize and on any scroll.\n * The layer has no surface of its own: style what you put inside it. Any\n * other `div` attribute passes through to the layer.\n *\n * React events still bubble through the component tree, not the DOM, so a\n * pointer handler on an ancestor sees events from inside the layer.\n *\n * @example\n * ```tsx\n * function PartMenu({ details }: { details: ReactNode }) {\n * const [anchor, setAnchor] = useState<AnchorPoint | null>(null);\n * return (\n * <>\n * <Button onClick={(e) => setAnchor({ x: e.clientX, y: e.clientY })}>\n * Details\n * </Button>\n * {anchor && (\n * <Floating anchor={anchor} onMouseLeave={() => setAnchor(null)}>\n * <Box surface=\"raised\" pad=\"popover\" radius=\"floating\" bordered>\n * {details}\n * </Box>\n * </Floating>\n * )}\n * </>\n * );\n * }\n * ```\n *\n * @category Floating\n * @categoryDescription Floating\n * Layers drawn over the page instead of inside a widget: tooltips, hover cards\n * and anchored menus, placed against the viewport so no panel clips them.\n */\nexport function Floating({ anchor, style, children, ...rest }: FloatingProps) {\n const [host, setHost] = useState<HTMLDivElement | null>(null);\n const [pos, setPos] = useState<AnchoredPosition | null>(null);\n const anchorRef = useRef(anchor);\n anchorRef.current = anchor;\n\n const place = useCallback(() => {\n if (!host) return;\n const current = anchorRef.current;\n const at = typeof current === \"function\" ? current() : current;\n if (!at) return;\n const box = host.getBoundingClientRect();\n const next = anchoredPosition(\n at,\n { w: box.width, h: box.height },\n { w: window.innerWidth, h: window.innerHeight },\n );\n setPos((prev) =>\n prev && prev.left === next.left && prev.top === next.top ? prev : next,\n );\n }, [host]);\n\n // Every render re-places, since the anchor may have moved with it; an unchanged position is not a state change.\n useLayoutEffect(() => {\n place();\n });\n\n useLayoutEffect(() => {\n if (!host) return;\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(place);\n observer?.observe(host);\n window.addEventListener(\"resize\", place);\n // Captured: the dashboard scrolls inner containers, whose scroll events do not bubble.\n window.addEventListener(\"scroll\", place, true);\n return () => {\n observer?.disconnect();\n window.removeEventListener(\"resize\", place);\n window.removeEventListener(\"scroll\", place, true);\n };\n }, [host, place]);\n\n if (typeof document === \"undefined\") return null;\n return createPortal(\n <Floating__Root\n ref={setHost}\n // Hidden until measured, so the unplaced first frame never shows.\n style={{\n ...style,\n left: pos?.left ?? 0,\n top: pos?.top ?? 0,\n visibility: pos === null ? \"hidden\" : style?.visibility,\n }}\n {...rest}\n >\n {children}\n </Floating__Root>,\n document.body,\n );\n}\n\nconst Floating__Root = styled.div`\n position: fixed;\n z-index: var(--z-dropdown);\n`;\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Vertical alignment of the cells in a {@link Grid} row.\n *\n * @category Layout\n */\nexport type GridAlign = \"center\" | \"start\" | \"baseline\" | \"stretch\";\n\n/**\n * Props for {@link Grid}. Any other `div` attribute passes through.\n *\n * @category Layout\n */\nexport interface GridProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * `align-items` shorthand. Defaults to `center`; `baseline` is what a\n * label/value grid wants, so a caption and a larger value share a baseline;\n * `stretch` makes every cell in a row as tall as the tallest.\n */\n align?: GridAlign;\n /**\n * Fixed column template (e.g. `\"120px 1fr 60px\"`). Takes precedence over\n * `minColWidth` when both are set.\n */\n cols?: string;\n /**\n * Responsive columns: as many equal columns of at least this width as fit\n * (`repeat(auto-fill, minmax(min(minColWidth, 100%), 1fr))`). A container\n * narrower than `minColWidth` gets one column of its own width rather than\n * overflowing. Ignored when `cols` is set.\n */\n minColWidth?: string;\n /**\n * Auto-fit rather than auto-fill the `minColWidth` columns: a row with fewer\n * cells than columns stretches them across the row instead of leaving empty\n * tracks beside them. Only meaningful with `minColWidth`.\n */\n fit?: boolean;\n /** Gap between cells, one of the {@link GapToken} names. Defaults to `related-dense`. */\n gap?: GapToken;\n /**\n * Row gap, when it differs from the column gap: a label/value grid usually\n * wants its rows tighter than its columns.\n */\n rowGap?: GapToken;\n children?: ReactNode;\n}\n\nconst ALIGN_ITEMS: Record<GridAlign, string> = {\n center: \"center\",\n start: \"start\",\n baseline: \"baseline\",\n stretch: \"stretch\",\n};\n\n/**\n * A CSS grid, either with a fixed column template (`cols`) or with as many\n * equal columns of at least `minColWidth` as fit. With neither it is a single\n * column.\n *\n * @category Layout\n */\nexport function Grid({\n cols,\n minColWidth,\n fit = false,\n gap = \"related-dense\",\n rowGap,\n align = \"center\",\n children,\n ...rest\n}: GridProps) {\n return (\n <Grid__Root\n $cols={cols}\n $minColWidth={minColWidth}\n $fit={fit}\n $gap={gap}\n $rowGap={rowGap}\n $align={align}\n {...rest}\n >\n {children}\n </Grid__Root>\n );\n}\n\nconst Grid__Root = styled.div<{\n $cols?: string;\n $minColWidth?: string;\n $fit: boolean;\n $gap: GapToken;\n $rowGap?: GapToken;\n $align: GridAlign;\n}>`\n display: grid;\n align-items: ${({ $align }) => ALIGN_ITEMS[$align]};\n gap: ${({ $gap, $rowGap }) =>\n $rowGap ? `${GAP_VAR[$rowGap]} ${GAP_VAR[$gap]}` : GAP_VAR[$gap]};\n grid-template-columns: ${({ $cols, $minColWidth, $fit }) => {\n if ($cols) return $cols;\n if ($minColWidth)\n return `repeat(${$fit ? \"auto-fit\" : \"auto-fill\"}, minmax(min(${$minColWidth}, 100%), 1fr))`;\n return \"1fr\";\n }};\n`;\n","import type { HeldGrade } from \"@ksp-gonogo/sitrep-sdk\";\nimport { Badge, type BadgeSize } from \"./Badge\";\nimport { severityFromStreamStatus } from \"./status/severity\";\nimport { heldWord } from \"./status/streamStatusWord\";\nimport { Tooltip } from \"./Tooltip\";\n\n/**\n * Props for {@link HeldBadge}.\n *\n * @category Badge\n */\nexport interface HeldBadgeProps {\n /** Why the reading behind this part of the widget stopped updating. */\n grade: HeldGrade;\n /** What is held, in the operator's terms, for the hover text: \"Contract board\", a part's title. */\n subject?: string;\n /** Text size, `sm` or `md`. Absent, `md`. */\n size?: BadgeSize;\n}\n\n/**\n * The chip a widget draws beside one part of itself whose reading is held: a\n * row, a card, a totals cell. It prints the grade's word and severity, so it\n * always agrees with the panel's own badge.\n *\n * It is not a live region, unlike {@link StreamStatusBadge}: the panel's own\n * badge announces the change once.\n *\n * @category Badge\n */\nexport function HeldBadge({ grade, subject, size }: HeldBadgeProps) {\n const word = heldWord(grade);\n return (\n <Tooltip\n text={subject === undefined ? undefined : `${subject}: ${word}`}\n focusable\n >\n <Badge tone={severityFromStreamStatus(grade)} size={size}>\n {word}\n </Badge>\n </Tooltip>\n );\n}\n","import {\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\";\nimport styled from \"styled-components\";\nimport { Floating } from \"./Floating\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * The props of {@link HoverCard}.\n *\n * @category Floating\n */\nexport interface HoverCardProps {\n /** The always-visible trigger content: a glyph, a tag, a short label. */\n trigger: ReactNode;\n /** Accessible name for the trigger, required since the trigger is usually a glyph. */\n ariaLabel: string;\n /** What the card says. Read-only facts: a hover card holds no controls. */\n children: ReactNode;\n}\n\n/** How long the card waits after the pointer leaves, so it can cross the gap onto the card. */\nconst CLOSE_DELAY_MS = 120;\n\n/**\n * A card of facts about a trigger, shown while the trigger is hovered or\n * focused and gone when neither is, drawn over the page in a {@link Floating}\n * so no panel clips it. The trigger renders as an unstyled `button`. Escape\n * dismisses the card; the pointer can move onto the card without closing it.\n * The card is the trigger's description, so a screen reader hears it on focus.\n *\n * @example\n * ```tsx\n * <HoverCard trigger={<InfoIcon />} ariaLabel=\"Signal path\">\n * <Text>Relayed through {relayName}</Text>\n * </HoverCard>\n * ```\n *\n * @category Floating\n */\nexport function HoverCard({ trigger, ariaLabel, children }: HoverCardProps) {\n const [open, setOpen] = useState(false);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cardId = useId();\n\n const cancelClose = useCallback(() => {\n if (closeTimer.current === null) return;\n clearTimeout(closeTimer.current);\n closeTimer.current = null;\n }, []);\n const show = () => {\n cancelClose();\n setOpen(true);\n };\n const hideSoon = () => {\n cancelClose();\n closeTimer.current = setTimeout(() => setOpen(false), CLOSE_DELAY_MS);\n };\n const hide = useCallback(() => {\n cancelClose();\n setOpen(false);\n }, [cancelClose]);\n\n useEffect(() => cancelClose, [cancelClose]);\n\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") hide();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, hide]);\n\n const anchor = () => {\n const rect = triggerRef.current?.getBoundingClientRect();\n return rect ? { x: rect.left, y: rect.bottom } : null;\n };\n\n return (\n <>\n <HoverCard__Trigger\n ref={triggerRef}\n type=\"button\"\n aria-label={ariaLabel}\n aria-describedby={open ? cardId : undefined}\n onPointerEnter={show}\n onPointerLeave={hideSoon}\n onFocus={show}\n onBlur={hide}\n >\n {trigger}\n </HoverCard__Trigger>\n {open && (\n <Floating\n anchor={anchor}\n onPointerEnter={cancelClose}\n onPointerLeave={hideSoon}\n >\n <HoverCard__Card id={cardId} role=\"tooltip\">\n {children}\n </HoverCard__Card>\n </Floating>\n )}\n </>\n );\n}\n\nconst HoverCard__Trigger = styled.button`\n display: inline-flex;\n align-items: center;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n cursor: help;\n\n ${focusRing}\n`;\n\nconst HoverCard__Card = styled.div`\n padding: var(--inset-popover);\n border: 1px solid var(--color-border-strong);\n border-radius: var(--radius-regular);\n background: var(--color-surface-raised);\n color: var(--color-text-primary);\n`;\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Props for {@link Inline}. Any other `span` attribute passes through.\n *\n * @category Layout\n */\nexport interface InlineProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Gap between children, one of the {@link GapToken} names. Omitted, it is\n * the `related` gap of whatever container the run sits in, so it matches a\n * panel's or a card's spacing. Set it only where this run decides its own\n * spacing.\n */\n gap?: GapToken;\n /**\n * Adds `margin-left: 6px` so this run sits apart from a preceding sibling\n * run (e.g. a badge row followed by an action-button row).\n */\n inset?: boolean;\n /**\n * Lets the run break onto further lines and shrink with its container.\n * Without it the run stays on one line at its full width.\n *\n * Turn this on wherever the number of children comes from data, since a run\n * that cannot break overflows a narrow column.\n */\n wrap?: boolean;\n children?: ReactNode;\n}\n\n/**\n * A compact inline-flex `span` for a run of badges or action buttons. It does\n * not shrink (`flex-shrink: 0`), so a truncating sibling gives up space before\n * it does; set `wrap` when the number of children depends on data.\n *\n * @category Layout\n */\nexport function Inline({\n gap,\n inset = false,\n wrap = false,\n children,\n ...rest\n}: InlineProps) {\n return (\n <Inline__Root $gap={gap} $inset={inset} $wrap={wrap} {...rest}>\n {children}\n </Inline__Root>\n );\n}\n\nconst Inline__Root = styled.span<{\n $gap?: GapToken;\n $inset: boolean;\n $wrap: boolean;\n}>`\n display: inline-flex;\n gap: ${({ $gap }) => ($gap ? GAP_VAR[$gap] : \"var(--gap-related)\")};\n flex-shrink: ${({ $wrap }) => ($wrap ? 1 : 0)};\n ${({ $wrap }) => $wrap && `flex-wrap: wrap; min-width: 0;`}\n ${({ $inset }) => $inset && `margin-left: var(--gap-inline-cluster);`}\n`;\n","import type { KeyboardEvent, PointerEvent } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * What displacement MEANS.\n *\n * - `offset`: where the handle sits is the value. Needs bounds\n * - `rate`: where the handle sits is the speed the value changes at, and it\n * springs back to centre on release. Needs no bounds, for a value such as an\n * instant years out\n *\n * @category Form\n */\nexport type JogWheelMode = \"offset\" | \"rate\";\n\ninterface JogWheelCommon {\n value: number;\n step: number;\n /** Drag/keyboard axis. Default \"horizontal\". */\n orientation?: \"horizontal\" | \"vertical\";\n onChange: (next: number) => void;\n /** Caret label formatter, also used as `aria-valuetext`. Default `String(Math.round(v))`; for a quantity, write it with {@link writeQuantity}. */\n format?: (v: number) => string;\n ariaLabel: string;\n /**\n * The name drawn above the wheel. Defaults to `ariaLabel`; `false` draws\n * none, for a wheel the caller already names beside it.\n */\n label?: string | false;\n disabled?: boolean;\n /**\n * Box width in CSS px. Defaults to 56 horizontal / 24 vertical. Clamped up\n * to {@link JOG_WHEEL_MIN_TARGET_PX}. A `format` longer than a few\n * characters needs more than the default, as the caret label is clipped to\n * the box.\n */\n width?: number;\n /**\n * Box height in CSS px. Defaults to 24 horizontal / 56 vertical. Clamped up\n * to {@link JOG_WHEEL_MIN_TARGET_PX}.\n */\n height?: number;\n}\n\n/**\n * Props for {@link JogWheel}. In the default `offset` mode `min` and `max` are\n * required; in `rate` mode they are optional.\n *\n * @category Form\n */\nexport type JogWheelProps =\n | (JogWheelCommon & {\n mode?: \"offset\";\n min: number;\n max: number;\n })\n | (JogWheelCommon & {\n mode: \"rate\";\n /** Optional here: a rate control does not need somewhere to stop. */\n min?: number;\n max?: number;\n /**\n * How many `step`s per second at FULL displacement. The travel between\n * centre and full is what gives the fine end of the range, so this sets\n * the coarse end.\n */\n stepsPerSecond?: number;\n });\n\n/** Pixels of pointer travel per `step` of value. */\nconst SENSITIVITY_PX_PER_STEP = 4;\n\n/** Pointer travel from centre to FULL rate, in rate mode. */\nconst RATE_TRAVEL_PX = 80;\n\n/** How often a held rate control emits, in ms. */\nconst RATE_TICK_MS = 60;\n\n/** Default steps per second at full displacement. */\nconst DEFAULT_STEPS_PER_SECOND = 30;\n\n/**\n * The smallest either side of the wheel's box can be, in CSS px: WCAG 2.2 SC\n * 2.5.8 (Target Size, Minimum) at AA. Drag range is unaffected by the box, since the wheel captures\n * the pointer.\n *\n * @category Form\n */\nexport const JOG_WHEEL_MIN_TARGET_PX = 24;\n\n/**\n * Below this on the cross axis the 4px inset would clip the caret label.\n */\nconst COMPACT_CROSS_AXIS_PX = 32;\n\n/** Default box, per orientation: the long axis first. */\nconst DEFAULT_LONG_PX = 56;\nconst DEFAULT_SHORT_PX = JOG_WHEEL_MIN_TARGET_PX;\n\n/**\n * Pure clamp + quantise: move `value` by `deltaSteps` of `step` (fractional\n * deltaSteps welcome, for pointer drag), clamp to `[min,max]`, and snap to the\n * step grid.\n *\n * The grid is anchored at `min`. With an infinite `min` (an unbounded control),\n * the value moves by exactly the delta asked for and snaps to nothing.\n *\n * @category Form\n */\nexport function applyDelta(\n value: number,\n bounds: { min: number; max: number; step: number },\n deltaSteps: number,\n): number {\n const { min, max, step } = bounds;\n const raw = value + deltaSteps * step;\n const clamped = Math.min(max, Math.max(min, raw));\n if (!Number.isFinite(min)) return clamped;\n const snapped = min + Math.round((clamped - min) / step) * step;\n return Math.min(max, Math.max(min, snapped));\n}\n\n/**\n * A fine-grain tape scrubber against a fixed centre caret, for dialling a\n * number by small increments by pointer drag along its axis or by keyboard\n * (arrow keys move one `step`, Home and End go to `min` and `max`). A\n * focusable `role=\"slider\"`; nothing is emitted while `disabled`. Sized with\n * `width` and `height` in CSS px, as {@link Dial} and {@link Tape} are.\n *\n * Two limits:\n *\n * - The caret label and `aria-valuetext` are the same string, so a screen\n * reader cannot be given a fuller value description than the face shows\n * - The drag axis is viewport-relative, so a wheel rotated with a CSS\n * transform cannot be dragged along its drawn axis. Do not rotate it\n *\n * @example\n * ```tsx\n * <JogWheel\n * ariaLabel=\"Gain\"\n * value={gain}\n * min={0}\n * max={100}\n * step={1}\n * onChange={setGain}\n * />\n * ```\n *\n * @category Form\n */\nexport function JogWheel(props: JogWheelProps): JSX.Element {\n const {\n value,\n min,\n max,\n step,\n orientation = \"horizontal\",\n onChange,\n format,\n ariaLabel,\n label: caption = ariaLabel,\n disabled = false,\n width,\n height,\n } = props;\n const vertical = orientation === \"vertical\";\n const boxWidth = Math.max(\n JOG_WHEEL_MIN_TARGET_PX,\n width ?? (vertical ? DEFAULT_SHORT_PX : DEFAULT_LONG_PX),\n );\n const boxHeight = Math.max(\n JOG_WHEEL_MIN_TARGET_PX,\n height ?? (vertical ? DEFAULT_LONG_PX : DEFAULT_SHORT_PX),\n );\n const rate = props.mode === \"rate\";\n // A rate control has no ends, so its arithmetic runs unbounded rather than clamping to an invented pair.\n const lo = min ?? Number.NEGATIVE_INFINITY;\n const hi = max ?? Number.POSITIVE_INFINITY;\n const bounds = { min: lo, max: hi, step };\n const drag = useRef<{ start: number; startValue: number } | null>(null);\n // The latest value, so the interval does not close over the value the drag began at.\n const latest = useRef(value);\n latest.current = value;\n const [displacement, setDisplacement] = useState(0);\n\n const label = format ? format(value) : String(Math.round(value));\n const fraction =\n hi > lo && Number.isFinite(hi - lo) ? (value - lo) / (hi - lo) : 0.5;\n\n const emit = (next: number): void => {\n if (disabled) return;\n if (next !== value) onChange(next);\n };\n\n /**\n * While the handle is off centre, move the value at a speed set by how far.\n *\n * Cleaned up on release and on unmount, so it never drives a value nobody is\n * holding.\n */\n useEffect(() => {\n if (!rate || displacement === 0 || disabled) return;\n const perSecond =\n (props.mode === \"rate\" ? props.stepsPerSecond : undefined) ??\n DEFAULT_STEPS_PER_SECOND;\n const timer = setInterval(() => {\n const moved =\n latest.current +\n displacement * perSecond * step * (RATE_TICK_MS / 1000);\n onChange(moved);\n }, RATE_TICK_MS);\n return () => clearInterval(timer);\n }, [rate, displacement, disabled, step, onChange, props]);\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (disabled) return;\n let next: number | null = null;\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = applyDelta(value, bounds, 1);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = applyDelta(value, bounds, -1);\n break;\n case \"Home\":\n // Nowhere to go in a mode with no ends, so the key does nothing rather than jumping to an infinity.\n if (!Number.isFinite(lo)) return;\n next = lo;\n break;\n case \"End\":\n if (!Number.isFinite(hi)) return;\n next = hi;\n break;\n default:\n return;\n }\n e.preventDefault();\n emit(next);\n };\n\n const axisPos = (e: PointerEvent<HTMLDivElement>): number =>\n orientation === \"vertical\" ? e.clientY : e.clientX;\n\n const handlePointerDown = (e: PointerEvent<HTMLDivElement>): void => {\n if (disabled) return;\n drag.current = { start: axisPos(e), startValue: value };\n e.currentTarget.setPointerCapture(e.pointerId);\n };\n\n const handlePointerMove = (e: PointerEvent<HTMLDivElement>): void => {\n if (disabled || !drag.current) return;\n // Vertical: dragging UP (clientY decreases) should INCREASE the value.\n const travel =\n orientation === \"vertical\"\n ? drag.current.start - axisPos(e)\n : axisPos(e) - drag.current.start;\n if (rate) {\n // Displacement sets the speed, so nothing is emitted here: the ticking effect does the moving.\n const fractionOfFull = travel / RATE_TRAVEL_PX;\n setDisplacement(Math.max(-1, Math.min(1, fractionOfFull)));\n return;\n }\n const deltaSteps = travel / SENSITIVITY_PX_PER_STEP;\n emit(applyDelta(drag.current.startValue, bounds, deltaSteps));\n };\n\n const endDrag = (e: PointerEvent<HTMLDivElement>): void => {\n if (!drag.current) return;\n drag.current = null;\n // Springs back, so the value stops moving once the operator lets go.\n setDisplacement(0);\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n };\n\n return (\n <JogWheel__Frame>\n {caption !== false && (\n <JogWheel__Caption aria-hidden=\"true\">{caption}</JogWheel__Caption>\n )}\n <JogWheel__Root\n role=\"slider\"\n aria-label={ariaLabel}\n aria-orientation={orientation}\n aria-valuenow={Number.isFinite(value) ? value : undefined}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuetext={label}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n $orientation={orientation}\n $disabled={disabled}\n $width={boxWidth}\n $height={boxHeight}\n $compact={(vertical ? boxWidth : boxHeight) < COMPACT_CROSS_AXIS_PX}\n onKeyDown={handleKeyDown}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n >\n <JogWheel__Tape\n $orientation={orientation}\n style={\n orientation === \"vertical\"\n ? { transform: `translateY(${(0.5 - fraction) * 100}%)` }\n : { transform: `translateX(${(0.5 - fraction) * 100}%)` }\n }\n aria-hidden=\"true\"\n />\n <JogWheel__Caret $orientation={orientation} aria-hidden=\"true\" />\n <JogWheel__Label aria-hidden=\"true\">{label}</JogWheel__Label>\n </JogWheel__Root>\n </JogWheel__Frame>\n );\n}\n\nconst JogWheel__Frame = styled.div`\n display: inline-flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--gap-related-packed);\n`;\n\nconst JogWheel__Caption = styled.span`\n font-size: var(--font-size-caption);\n color: var(--color-text-muted);\n white-space: nowrap;\n`;\n\nconst JogWheel__Root = styled.div<{\n $orientation: \"horizontal\" | \"vertical\";\n $disabled: boolean;\n $width: number;\n $height: number;\n $compact: boolean;\n}>`\n position: relative;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n width: ${(p) => p.$width}px;\n height: ${(p) => p.$height}px;\n padding: ${(p) =>\n p.$compact ? \"var(--inset-jog-wheel-compact)\" : \"var(--inset-jog-wheel)\"};\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n background: var(--color-surface-raised);\n color: var(--color-text-primary);\n cursor: ${(p) =>\n p.$disabled\n ? \"not-allowed\"\n : p.$orientation === \"vertical\"\n ? \"ns-resize\"\n : \"ew-resize\"};\n touch-action: none;\n user-select: none;\n opacity: ${(p) => (p.$disabled ? 0.5 : 1)};\n\n ${focusRing}\n`;\n\nconst JogWheel__Tape = styled.div<{ $orientation: \"horizontal\" | \"vertical\" }>`\n position: absolute;\n inset: 0;\n background-image: repeating-linear-gradient(\n ${(p) => (p.$orientation === \"vertical\" ? \"0deg\" : \"90deg\")},\n var(--color-border-subtle) 0 1px,\n transparent 1px ${(p) => (p.$orientation === \"vertical\" ? \"8px\" : \"10px\")}\n );\n opacity: 0.6;\n pointer-events: none;\n`;\n\nconst JogWheel__Caret = styled.div<{ $orientation: \"horizontal\" | \"vertical\" }>`\n position: absolute;\n background: var(--color-accent-fg);\n pointer-events: none;\n ${(p) =>\n p.$orientation === \"vertical\"\n ? \"left: 0; right: 0; height: 2px; top: 50%;\"\n : \"top: 0; bottom: 0; width: 2px; left: 50%;\"}\n`;\n\nconst JogWheel__Label = styled.span`\n /* Last DOM sibling, so it paints over the absolute tape and caret without a z-index. */\n position: relative;\n font-family: var(--font-family-mono);\n font-size: var(--font-size-compact);\n color: var(--color-text-primary);\n background: var(--color-surface-raised);\n padding: var(--inset-jog-wheel-label);\n pointer-events: none;\n`;\n","import type {\n ReckoningBasis,\n SeriesReckonedSpan,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { bandClaim } from \"./bandClaim\";\nimport { reckoningBasisPhrase } from \"./reckoningBasisPhrase\";\n\n/**\n * One line on a {@link LineGraph}.\n *\n * @category LineGraph\n */\nexport interface LineGraphSeries {\n /** Unique within the graph; used as the line's React key. */\n id: string;\n /** Names this line in the chart's accessible name when part of it is reckoned. Not drawn; falls back to `id`. */\n label?: string;\n /** CSS colour for the stroke, e.g. `var(--color-nogo-mark)`. */\n color: string;\n /**\n * The line's points, ascending by `x`. Fewer than two renders no line for\n * this series. `x` and `y` are bare numbers in whatever unit the caller\n * chose, the same for every series: the chart scales each axis linearly\n * across the range it is given and labels neither. For a trend over time\n * pass seconds as `x`, a UT or seconds ago.\n */\n points: ReadonlyArray<{ x: number; y: number }>;\n /**\n * Indices into `points` that open a known hole: no data between the previous\n * point and that one (a comms blackout, a recording that overran). The stroke\n * and the sparkline fill break there, since a line through a span with no\n * readings would look like data.\n */\n breaks?: readonly number[];\n /**\n * Runs of `points` that a model carried forward rather than measured, by\n * index (`from` and `to` inclusive). Each is drawn muted and dashed in the\n * series' own colour, never recoloured, and the chart's accessible name says\n * how it was reckoned. A run carrying `bandLo`, `bandHi` and `bandKind` also\n * shades the region between its bounds, with no edge line, and the y domain\n * grows to include the bounds.\n */\n reckoned?: readonly SeriesReckonedSpan[];\n}\n\n/**\n * A horizontal reference level on a {@link LineGraph}, such as a safe limit.\n *\n * @category LineGraph\n */\nexport interface LineGraphThreshold {\n /** Unique within the graph; used as the threshold's React key. */\n id: string;\n /** Name of the level, for the caller's own bookkeeping. Not rendered; `valueText` is the drawn annotation. */\n label?: string;\n /** The y value the threshold is drawn at, in the same terms as the series' `y`. */\n value: number;\n /** Defaults to a muted warning colour, distinct from the data series. */\n color?: string;\n /**\n * Short text drawn beside a `\"marker\"`-style threshold (e.g. `\"0.5\"`),\n * naming the level the tick sits at. Without it the marker is a bare tick.\n * Ignored by the `\"full\"` rule, which is its own annotation.\n */\n valueText?: string;\n}\n\n/**\n * How a {@link LineGraph} draws its thresholds: `\"full\"` is a dashed rule\n * across the frame, `\"marker\"` a short tick at the left edge.\n *\n * @category LineGraph\n */\nexport type LineGraphThresholdStyle = \"full\" | \"marker\";\n\n/**\n * Props for {@link LineGraph}.\n *\n * @category LineGraph\n */\nexport interface LineGraphProps {\n /** The lines to draw, all against one shared x and y domain. */\n series: readonly LineGraphSeries[];\n /** Horizontal reference levels. */\n thresholds?: readonly LineGraphThreshold[];\n /** Pins the Y domain; otherwise derived from every series' + threshold's values. */\n yDomain?: readonly [number, number];\n /** Chart height in pixels. Width always fills the parent. */\n height?: number;\n /**\n * Accessible name for the whole chart (`role=\"img\"`). Omit when the trend is\n * decorative beside a readout that already carries the reading, and the chart\n * renders `aria-hidden`.\n */\n ariaLabel?: string;\n /** Placed on the chart's root element, for a caller's own sizing or layout. */\n className?: string;\n /**\n * `\"chart\"` (default): quarter gridlines and bare strokes. `\"sparkline\"`: no\n * gridlines, and each series area-shaded down to the frame's bottom edge, for\n * a compact glance trend.\n */\n variant?: \"chart\" | \"sparkline\";\n /**\n * How a threshold draws at its y-height. `\"full\"` (default) is a dashed rule\n * spanning the frame. `\"marker\"` is a fixed ~24px tick at the left edge with\n * the threshold's `valueText` beside it, for a threshold that is context\n * rather than the subject of the chart.\n */\n thresholdStyle?: LineGraphThresholdStyle;\n}\n\nconst VIEW_W = 100;\nconst VIEW_H = 40;\n\nfunction computeDomain(\n series: readonly LineGraphSeries[],\n thresholds: readonly LineGraphThreshold[],\n): [number, number] {\n const ys: number[] = [];\n for (const s of series)\n for (const p of s.points) if (Number.isFinite(p.y)) ys.push(p.y);\n for (const s of series)\n for (const run of s.reckoned ?? [])\n for (const y of [...(run.bandLo ?? []), ...(run.bandHi ?? [])])\n if (Number.isFinite(y)) ys.push(y);\n for (const t of thresholds) if (Number.isFinite(t.value)) ys.push(t.value);\n if (ys.length === 0) return [0, 1];\n let min = ys[0];\n let max = ys[0];\n for (const y of ys) {\n if (y < min) min = y;\n if (y > max) max = y;\n }\n if (min === max) {\n // A flat/single-value read still needs headroom to draw a visible line rather than one hugging an edge.\n const pad = min === 0 ? 1 : Math.abs(min) * 0.5;\n return [min - pad, max + pad];\n }\n // 8% headroom top and bottom so a peak/trough never touches the frame.\n const span = max - min;\n return [min - span * 0.08, max + span * 0.08];\n}\n\n/** A reckoned run is muted, never recoloured; 0.6 keeps series colours above 3:1 against the surface. */\nconst RECKONED_STROKE_OPACITY = 0.6;\nconst RECKONED_DASHARRAY = \"5 3\";\n/** A band takes its series' colour on a chart, as an uncertainty region does on `LineChart`. */\nconst BAND_OPACITY = 0.15;\n/** The sparkline's own area fill is the series colour at 0.12, so a band in that colour would vanish into it: a neutral token reads apart. */\nconst BAND_FILL_ON_AREA = \"var(--color-text-primary)\";\nconst BAND_OPACITY_ON_AREA = 0.22;\n\ntype TaggedPoint = { x: number; y: number; basis?: ReckoningBasis };\n\n/** The run's band as a closed polygon's corner list, else null: all three band fields must be present, and a point with no finite x or bound ends the region short of it. */\nfunction bandCorners(\n s: LineGraphSeries,\n run: SeriesReckonedSpan,\n toX: (x: number) => number,\n toY: (y: number) => number,\n): string | null {\n const { bandLo, bandHi, bandKind } = run;\n if (!bandLo || !bandHi || bandKind === undefined) return null;\n const upper: string[] = [];\n const lower: string[] = [];\n for (let i = run.from; i <= run.to; i++) {\n const p = s.points[i];\n const lo = bandLo[i - run.from];\n const hi = bandHi[i - run.from];\n if (\n !p ||\n !Number.isFinite(p.x) ||\n !Number.isFinite(lo) ||\n !Number.isFinite(hi)\n )\n break;\n upper.push(`${toX(p.x)},${toY(hi)}`);\n lower.unshift(`${toX(p.x)},${toY(lo)}`);\n }\n return upper.length < 2 ? null : [...upper, ...lower].join(\" \");\n}\n\n/** Each point's reckoning basis, absent for a measured one. Runs may name indices a trimmed window no longer has, so they are clamped. */\nfunction tagPoints(s: LineGraphSeries): TaggedPoint[] {\n const basisAt: Array<ReckoningBasis | undefined> = new Array(s.points.length);\n for (const run of s.reckoned ?? []) {\n const last = Math.min(s.points.length - 1, run.to);\n for (let i = Math.max(0, run.from); i <= last; i++) basisAt[i] = run.basis;\n }\n return s.points.map((p, i) => ({ x: p.x, y: p.y, basis: basisAt[i] }));\n}\n\n/**\n * One unbroken run cut again where the reckoning basis changes, each piece\n * taking the previous piece's last point so the stroke stays joined.\n */\nfunction splitAtBasis(\n run: ReadonlyArray<TaggedPoint>,\n): Array<{ basis?: ReckoningBasis; points: ReadonlyArray<TaggedPoint> }> {\n const out: Array<{\n basis?: ReckoningBasis;\n points: ReadonlyArray<TaggedPoint>;\n }> = [];\n let start = 0;\n for (let i = 1; i <= run.length; i++) {\n if (i === run.length || run[i].basis !== run[start].basis) {\n const from = start > 0 ? start - 1 : start;\n out.push({ basis: run[start].basis, points: run.slice(from, i) });\n start = i;\n }\n }\n return out;\n}\n\n/**\n * One series' points cut into unbroken runs at its `breaks` indices.\n */\nfunction splitAtBreaks<Point extends { x: number; y: number }>(\n points: ReadonlyArray<Point>,\n breaks: readonly number[] | undefined,\n): Array<ReadonlyArray<Point>> {\n // A sample that is not a finite number is a hole, the same as a stated break.\n const holes = points.flatMap((p, i) =>\n Number.isFinite(p.x) && Number.isFinite(p.y) ? [] : [i],\n );\n if (holes.length > 0) {\n const finite = points.filter(\n (p) => Number.isFinite(p.x) && Number.isFinite(p.y),\n );\n const shifted = [...(breaks ?? []), ...holes]\n .map((cut) => cut - holes.filter((h) => h < cut).length)\n .filter((cut, i, all) => all.indexOf(cut) === i);\n return splitAtBreaks(finite, shifted);\n }\n if (!breaks || breaks.length === 0) return [points];\n const runs: Array<ReadonlyArray<Point>> = [];\n const cuts = [...new Set(breaks)]\n .filter((i) => i > 0 && i < points.length)\n .sort((a, b) => a - b);\n let start = 0;\n for (const cut of cuts) {\n runs.push(points.slice(start, cut));\n start = cut;\n }\n runs.push(points.slice(start));\n return runs;\n}\n\nfunction computeXDomain(series: readonly LineGraphSeries[]): [number, number] {\n const xs: number[] = [];\n for (const s of series)\n for (const p of s.points) if (Number.isFinite(p.x)) xs.push(p.x);\n if (xs.length === 0) return [0, 1];\n let min = xs[0];\n let max = xs[0];\n for (const x of xs) {\n if (x < min) min = x;\n if (x > max) max = x;\n }\n return min === max ? [min - 1, max + 1] : [min, max];\n}\n\n/**\n * A minimal multi-series time-trend chart: coloured lines against a shared\n * domain, plus optional reference lines. For readings that need a trend rather\n * than a precise value; pair it with a {@link Unit} readout for the value.\n *\n * No axis ticks, no legend, no interaction. Gridlines are fixed quarter-marks.\n * Width fills the parent and strokes keep a constant width however the chart\n * is stretched. The y domain is derived from every series and threshold, with\n * headroom, unless `yDomain` pins it.\n *\n * @example A compact dose-rate trend with a safe limit marked\n * ```tsx\n * <LineGraph\n * variant=\"sparkline\"\n * height={96}\n * ariaLabel=\"Radiation dose rate, last 10 minutes\"\n * series={[\n * {\n * id: \"ambient\",\n * label: \"Ambient\",\n * color: \"var(--color-nogo-mark)\",\n * points: samples.map((s) => ({ x: s.ut, y: s.radPerHour })),\n * },\n * ]}\n * thresholds={[{ id: \"safe\", label: \"Safe threshold\", value: 0.5 }]}\n * thresholdStyle=\"marker\"\n * />\n * ```\n *\n * @category LineGraph\n * @categoryDescription LineGraph\n * A value over time: a line graph with series and thresholds, and the notice a\n * graph shows when it has nothing, or not enough, to draw.\n */\nexport function LineGraph({\n series,\n thresholds = [],\n yDomain,\n height = 120,\n ariaLabel,\n className,\n variant = \"chart\",\n thresholdStyle = \"full\",\n}: LineGraphProps) {\n const [yMin, yMax] = yDomain ?? computeDomain(series, thresholds);\n const [xMin, xMax] = computeXDomain(series);\n const ySpan = yMax - yMin || 1;\n const xSpan = xMax - xMin || 1;\n const isSparkline = variant === \"sparkline\";\n const tagged = series.map(tagPoints);\n\n const toX = (x: number) => ((x - xMin) / xSpan) * VIEW_W;\n const toY = (y: number) => VIEW_H - ((y - yMin) / ySpan) * VIEW_H;\n\n const chartLabel = ariaLabel\n ? [\n ariaLabel,\n ...series.flatMap((s) => {\n const name = s.label ?? s.id;\n const runs = s.reckoned ?? [];\n const kinds = new Set(\n runs\n .map((run) =>\n run.bandLo && run.bandHi ? run.bandKind : undefined,\n )\n .filter((kind) => kind !== undefined),\n );\n return [\n ...[...new Set(runs.map((run) => run.basis))].map(\n (basis) =>\n `${name}: part of this trace is reckoned, ${reckoningBasisPhrase(basis)}, not measured`,\n ),\n ...[...kinds].map(\n (kind) =>\n `${name}: ${bandClaim(kind, \"the value is inside the shaded region\")}`,\n ),\n ];\n }),\n ].join(\"; \")\n : undefined;\n\n return (\n <LineGraph__Root className={className} style={{ height }}>\n <svg\n viewBox={`0 0 ${VIEW_W} ${VIEW_H}`}\n preserveAspectRatio=\"none\"\n width=\"100%\"\n height=\"100%\"\n role={ariaLabel ? \"img\" : undefined}\n aria-label={chartLabel}\n aria-hidden={ariaLabel ? undefined : \"true\"}\n >\n {!isSparkline &&\n [0.25, 0.5, 0.75].map((f) => (\n <line\n key={f}\n x1={0}\n x2={VIEW_W}\n y1={VIEW_H * f}\n y2={VIEW_H * f}\n stroke=\"var(--color-border-subtle)\"\n strokeWidth={0.4}\n vectorEffect=\"non-scaling-stroke\"\n />\n ))}\n\n {isSparkline &&\n series.map((s, si) => {\n // Fewer than two points draws nothing, as for the stroke.\n if (s.points.length < 2) return null;\n // One polygon per unbroken run, so a hole leaves unshaded ground.\n return splitAtBreaks(tagged[si], s.breaks).map((run) => {\n if (run.length < 2) return null;\n const first = run[0];\n const last = run[run.length - 1];\n const areaPoints = [\n ...run.map((p) => `${toX(p.x)},${toY(p.y)}`),\n `${toX(last.x)},${VIEW_H}`,\n `${toX(first.x)},${VIEW_H}`,\n ].join(\" \");\n return (\n <polygon\n key={`${s.id}-area-${first.x}`}\n points={areaPoints}\n fill={s.color}\n fillOpacity={0.12}\n stroke=\"none\"\n />\n );\n });\n })}\n\n {series.flatMap((s) =>\n (s.reckoned ?? []).map((run, ri) => {\n const corners = bandCorners(s, run, toX, toY);\n if (corners === null) return null;\n return (\n <polygon\n // biome-ignore lint/suspicious/noArrayIndexKey: a band has no identity beyond its run's position\n key={`${s.id}-band-${ri}`}\n points={corners}\n data-band-kind={run.bandKind}\n fill={isSparkline ? BAND_FILL_ON_AREA : s.color}\n fillOpacity={isSparkline ? BAND_OPACITY_ON_AREA : BAND_OPACITY}\n stroke=\"none\"\n />\n );\n }),\n )}\n\n {thresholdStyle === \"full\" &&\n thresholds.map((t) => (\n <line\n key={t.id}\n x1={0}\n x2={VIEW_W}\n y1={toY(t.value)}\n y2={toY(t.value)}\n stroke={t.color ?? \"var(--color-warn-text)\"}\n strokeWidth={0.6}\n strokeDasharray=\"2 1.5\"\n vectorEffect=\"non-scaling-stroke\"\n />\n ))}\n\n {series.map((s, si) => {\n if (s.points.length < 2) return null;\n /*\n * One polyline per unbroken run and per basis within it, keyed on its\n * own first x, since run boundaries move as data slides through the window.\n */\n return splitAtBreaks(tagged[si], s.breaks).flatMap((run) =>\n splitAtBasis(run).map((piece) => {\n if (piece.points.length < 2) return null;\n const points = piece.points\n .map((p) => `${toX(p.x)},${toY(p.y)}`)\n .join(\" \");\n return (\n <polyline\n key={`${s.id}-${piece.points[0].x}`}\n points={points}\n data-reckoning-basis={piece.basis}\n fill=\"none\"\n stroke={s.color}\n strokeWidth={isSparkline ? 1 : 1.4}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeOpacity={\n piece.basis !== undefined\n ? RECKONED_STROKE_OPACITY\n : undefined\n }\n strokeDasharray={\n piece.basis !== undefined ? RECKONED_DASHARRAY : undefined\n }\n vectorEffect=\"non-scaling-stroke\"\n />\n );\n }),\n );\n })}\n </svg>\n\n {/* \"marker\" thresholds are an HTML overlay outside the stretched viewBox, so their size stays fixed on screen. */}\n {thresholdStyle === \"marker\" &&\n thresholds.map((t) => {\n const topPct = ((yMax - t.value) / ySpan) * 100;\n // A threshold outside the pinned domain is skipped rather than pinned to an edge it is not at.\n if (topPct < 0 || topPct > 100) return null;\n return (\n <LineGraph__ThresholdMarker\n key={t.id}\n aria-hidden=\"true\"\n data-threshold-marker={t.id}\n style={{\n top: `${topPct}%`,\n color: t.color ?? \"var(--color-warn-text)\",\n }}\n >\n <LineGraph__ThresholdTick />\n {t.valueText !== undefined && <span>{t.valueText}</span>}\n </LineGraph__ThresholdMarker>\n );\n })}\n </LineGraph__Root>\n );\n}\n\nconst LineGraph__ThresholdMarker = styled.div`\n position: absolute;\n left: 0;\n transform: translateY(-50%);\n display: inline-flex;\n align-items: center;\n gap: var(--gap-glyph);\n pointer-events: none;\n font-size: var(--font-size-caption);\n font-variant-numeric: tabular-nums;\n /* Single-glyph chrome text centring against a 2px tick: the flush rung. */\n line-height: var(--line-height-flush);\n`;\n\n/** The fixed ~24px tick, the length `Card`'s identity tab uses for a mark rather than a rule. */\nconst LineGraph__ThresholdTick = styled.span`\n display: inline-block;\n width: var(--size-mark);\n height: 2px;\n border-radius: var(--radius-pill);\n background: currentColor;\n`;\n\nconst LineGraph__Root = styled.div`\n position: relative;\n width: 100%;\n min-height: 0;\n\n svg {\n display: block;\n }\n`;\n","import type { ReckoningBasis } from \"@ksp-gonogo/sitrep-sdk\";\n\nconst PHRASE: Record<ReckoningBasis, string> = {\n // A combination joins readings of one moment; it never advances a value through time.\n combination: \"computed from several readings of the same moment\",\n \"kepler-propagation\": \"propagated forward on two-body motion\",\n \"linear-dead-reckoning\": \"carried forward at the last observed velocity\",\n \"powered-integration\": \"integrated forward through the burn\",\n \"rate-integration\": \"integrated forward at the last observed rate\",\n};\n\n/**\n * How a value was reckoned, in words, for a chart's accessible name: dash and\n * mutedness are not channels a screen reader has.\n *\n * @category LineGraph\n */\nexport function reckoningBasisPhrase(basis: ReckoningBasis): string {\n return PHRASE[basis];\n}\n","import type { LockedValue } from \"@ksp-gonogo/sitrep-sdk\";\nimport { isLocked } from \"@ksp-gonogo/sitrep-sdk\";\nimport { lockSentence } from \"@ksp-gonogo/sitrep-sdk/spine\";\nimport styled from \"styled-components\";\nimport { LockIcon } from \"./Icons\";\nimport type { LockSummary } from \"./LockScope\";\nimport { writeLockQuantity } from \"./lockQuantity\";\nimport { Tooltip } from \"./Tooltip\";\n\n/**\n * Props for {@link LockMark}.\n *\n * @category Panel\n */\nexport interface LockMarkProps {\n /** A locked scope's summary, or a gated field that arrived as a `LockedValue`. */\n lock: LockSummary | LockedValue;\n}\n\n/**\n * The compact form of a lock: a padlock and the name of what is missing, for\n * a tile too small for the full notice, or inline where a gated field would\n * be read. The whole sentence is its tooltip and its accessible name.\n *\n * @category Panel\n */\nexport function LockMark({ lock: given }: LockMarkProps) {\n const lock = isLocked(given) ? summaryOf(given) : given;\n const sentence =\n lock.hint === undefined ? lock.reason : `${lock.reason}. ${lock.hint}`;\n return (\n <Tooltip text={sentence} focusable>\n <LockMark__Body role=\"status\" aria-label={sentence}>\n <LockIcon />\n <LockMark__Name>{missingNames(lock)}</LockMark__Name>\n </LockMark__Body>\n </Tooltip>\n );\n}\n\n/** A gated field's lock as the same summary a locked scope hands its fallback. */\nfunction summaryOf(value: LockedValue): LockSummary {\n const locks = [\n { capability: { kind: \"topic\" as const, id: \"\" }, missing: value.locked },\n ];\n return { ...lockSentence(locks, writeLockQuantity), locks };\n}\n\n/** The unlocks' own names, which is the part of the sentence worth the space in a tiny tile. */\nfunction missingNames(lock: LockSummary): string {\n const names = [\n ...new Set(lock.locks.flatMap((l) => l.missing.map((m) => m.name))),\n ];\n return names.length > 0 ? names.join(\", \") : lock.reason;\n}\n\nconst LockMark__Body = styled.span`\n display: inline-flex;\n align-items: center;\n gap: var(--gap-figure-parts);\n max-width: 100%;\n min-width: 0;\n color: var(--color-text-muted);\n font-size: var(--font-size-caption);\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n`;\n\nconst LockMark__Name = styled.span`\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n`;\n","import { forwardRef, type SVGProps } from \"react\";\n\n/*\n * An original rendering of the standard spaceflight notation, not traced from\n * the game's artwork; only the hues follow KSP, through the --color-marker-*\n * tokens.\n */\n\n/**\n * Props for the navball marker icons ({@link ProgradeIcon} and the rest).\n *\n * The markers follow the standard spaceflight notation, with hues matching\n * KSP's navball through the `--color-marker-*` tokens. Shape carries the\n * meaning and hue only confirms it: every \"towards\" marker has a centre dot\n * and every \"away\" marker a centre cross (⊙ and ⊗), so the two halves of a\n * pair, which share a hue, stay distinct in greyscale. Each marker is drawn\n * first as a wider keyline in `currentColor` (the text colour, which\n * contrasts with the surface) and then as the coloured stroke on top. The\n * drawing is on a 24-unit viewBox. Any other SVG attribute passes through, and\n * the ref reaches the `svg` element.\n *\n * @category Icons\n */\nexport interface MarkerIconProps\n extends Omit<SVGProps<SVGSVGElement>, \"children\"> {\n /**\n * Rendered width and height, as pixels or any CSS length. Defaults to the\n * kit's standalone icon size, which grows a step on a coarse pointer.\n */\n size?: number | string;\n /** Width of the coloured stroke, in viewBox units. Defaults to 1.8. */\n strokeWidth?: number;\n /**\n * The icon's accessible name. With one, the icon renders as `role=\"img\"`\n * named by it; without one it is decorative and hidden from assistive\n * technology.\n */\n label?: string;\n}\n\n/** Extra width of the keyline beyond the coloured stroke, split across both sides. */\nconst KEYLINE_EXTRA = 1.4;\nconst DOT_RADIUS = 1.7;\n/** As in `Icons.tsx`: the token reaches the glyph through the presentation attribute, so it steps on a coarse pointer. */\nconst DEFAULT_SIZE = \"var(--icon-size-standalone)\";\nconst DEFAULT_STROKE_WIDTH = 1.8;\n\ntype MarkerColour = \"prograde\" | \"normal\" | \"radial\" | \"maneuver\" | \"target\";\n\ninterface MarkerShape {\n /** Stroked open paths; each string is one `d` attribute. */\n paths: string[];\n /** A filled centre dot, the ⊙ half of the notation. */\n dot?: [cx: number, cy: number];\n colour: MarkerColour;\n}\n\nconst RING = \"M12 6a6 6 0 1 0 0 12a6 6 0 1 0 0-12\";\n/** A tighter ring for the radial pair, leaving clear space for the chevrons outside it. */\nconst SMALL_RING = \"M12 7a5 5 0 1 0 0 10a5 5 0 1 0 0-10\";\n/**\n * The target square's four corner rays, carried onto the velocity ring: the\n * relative-velocity pair is prograde and retrograde in the target's frame.\n * Diagonal, so it reads as neither prograde nor retrograde.\n */\nconst RELATIVE_RAYS =\n \"M7.76 7.76L4.2 4.2M16.24 7.76L19.8 4.2M16.24 16.24L19.8 19.8M7.76 16.24L4.2 19.8\";\n/**\n * Two rails, the ∥ notation for parallel: along the target's own facing. An\n * open outline, so it is mistaken for neither the ring nor the square.\n */\nconst RAILS = \"M7 4.5V19.5M17 4.5V19.5\";\n/** The ⊗ half of the notation, centred on (cx, cy). */\nconst cross = (cx: number, cy: number): string =>\n `M${cx - 2.5} ${cy - 2.5}l5 5M${cx + 2.5} ${cy - 2.5}l-5 5`;\n\nconst SHAPES = {\n prograde: {\n colour: \"prograde\",\n paths: [RING, \"M12 6V2.5M6 12H2.5M18 12H21.5\"],\n dot: [12, 12],\n },\n retrograde: {\n colour: \"prograde\",\n paths: [\n RING,\n cross(12, 12),\n \"M7.76 7.76L5.3 5.3M16.24 7.76L18.7 5.3M12 18V21.5\",\n ],\n },\n normal: {\n colour: \"normal\",\n paths: [\"M12 4.5L19 17H5Z\"],\n dot: [12, 13],\n },\n antiNormal: {\n colour: \"normal\",\n paths: [\"M12 19.5L5 7H19Z\", cross(12, 11)],\n },\n radialOut: {\n colour: \"radial\",\n paths: [\n SMALL_RING,\n \"M10 4.5L12 2.5L14 4.5M19.5 10L21.5 12L19.5 14M10 19.5L12 21.5L14 19.5M4.5 10L2.5 12L4.5 14\",\n ],\n dot: [12, 12],\n },\n radialIn: {\n colour: \"radial\",\n paths: [\n SMALL_RING,\n \"M10 2.5L12 4.5L14 2.5M21.5 10L19.5 12L21.5 14M10 21.5L12 19.5L14 21.5M2.5 10L4.5 12L2.5 14\",\n cross(12, 12),\n ],\n },\n maneuver: {\n colour: \"maneuver\",\n paths: [\"M12 5L19 12L12 19L5 12Z\", \"M12 5V2\"],\n dot: [12, 12],\n },\n target: {\n colour: \"target\",\n paths: [\n \"M6 6H18V18H6Z\",\n \"M6 6L3.5 3.5M18 6L20.5 3.5M18 18L20.5 20.5M6 18L3.5 20.5\",\n ],\n dot: [12, 12],\n },\n antiTarget: {\n colour: \"target\",\n paths: [\n \"M6 6H18V18H6Z\",\n \"M6 6L3.5 3.5M18 6L20.5 3.5M18 18L20.5 20.5M6 18L3.5 20.5\",\n cross(12, 12),\n ],\n },\n relativePlus: {\n colour: \"target\",\n paths: [RING, RELATIVE_RAYS],\n dot: [12, 12],\n },\n relativeMinus: {\n colour: \"target\",\n paths: [RING, RELATIVE_RAYS, cross(12, 12)],\n },\n parallelPlus: {\n colour: \"target\",\n paths: [RAILS],\n dot: [12, 12],\n },\n parallelMinus: {\n colour: \"target\",\n paths: [RAILS, cross(12, 12)],\n },\n} satisfies Record<string, MarkerShape>;\n\n/**\n * The id of a navball marker: `prograde`, `retrograde`, `normal`,\n * `antiNormal`, `radialOut`, `radialIn`, `maneuver`, `target`, `antiTarget`,\n * `relativePlus`, `relativeMinus`, `parallelPlus` or `parallelMinus`. The\n * rendered `svg` carries it as `data-marker`.\n *\n * @category Icons\n */\nexport type MarkerId = keyof typeof SHAPES;\n\n/**\n * Every {@link MarkerId}, in the order a legend or contact sheet should show\n * them.\n *\n * @category Icons\n */\nexport const MARKER_IDS = Object.keys(SHAPES) as MarkerId[];\n\nfunction Layer({\n shape,\n colour,\n strokeWidth,\n dotRadius,\n}: {\n shape: MarkerShape;\n colour: string;\n strokeWidth: number;\n dotRadius: number;\n}) {\n return (\n <g\n fill=\"none\"\n stroke={colour}\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n {shape.paths.map((d) => (\n <path key={d} d={d} />\n ))}\n {shape.dot && (\n <circle\n cx={shape.dot[0]}\n cy={shape.dot[1]}\n r={dotRadius}\n fill={colour}\n stroke=\"none\"\n />\n )}\n </g>\n );\n}\n\nfunction makeMarker(id: MarkerId, displayName: string) {\n const shape: MarkerShape = SHAPES[id];\n const colour = `var(--color-marker-${shape.colour})`;\n const Marker = forwardRef<SVGSVGElement, MarkerIconProps>(\n (\n {\n size = DEFAULT_SIZE,\n strokeWidth = DEFAULT_STROKE_WIDTH,\n label,\n ...rest\n },\n ref,\n ) => {\n const frame = {\n ref,\n xmlns: \"http://www.w3.org/2000/svg\",\n width: size,\n height: size,\n viewBox: \"0 0 24 24\",\n \"data-marker\": id,\n };\n const layers = (\n <>\n <Layer\n shape={shape}\n colour=\"currentColor\"\n strokeWidth={strokeWidth + KEYLINE_EXTRA}\n dotRadius={DOT_RADIUS + KEYLINE_EXTRA / 2}\n />\n <Layer\n shape={shape}\n colour={colour}\n strokeWidth={strokeWidth}\n dotRadius={DOT_RADIUS}\n />\n </>\n );\n return label ? (\n <svg {...frame} role=\"img\" aria-label={label} {...rest}>\n {layers}\n </svg>\n ) : (\n <svg {...frame} aria-hidden=\"true\" {...rest}>\n {layers}\n </svg>\n );\n },\n );\n Marker.displayName = displayName;\n return Marker;\n}\n\n/**\n * The navball prograde marker: a ring with a centre dot and three ticks (top, left, right), in the prograde hue.\n *\n * @category Icons\n */\nexport const ProgradeIcon = makeMarker(\"prograde\", \"ProgradeIcon\");\n/**\n * The navball retrograde marker: a ring with a centre cross and three ticks (two diagonal above, one below), in the prograde hue.\n *\n * @category Icons\n */\nexport const RetrogradeIcon = makeMarker(\"retrograde\", \"RetrogradeIcon\");\n/**\n * The navball normal marker: an upward triangle with a centre dot, in the normal hue.\n *\n * @category Icons\n */\nexport const NormalIcon = makeMarker(\"normal\", \"NormalIcon\");\n/**\n * The navball anti-normal marker: a downward triangle with a centre cross, in the normal hue.\n *\n * @category Icons\n */\nexport const AntiNormalIcon = makeMarker(\"antiNormal\", \"AntiNormalIcon\");\n/**\n * The navball radial-out marker: a small ring with a centre dot and four chevrons pointing outward, in the radial hue.\n *\n * @category Icons\n */\nexport const RadialOutIcon = makeMarker(\"radialOut\", \"RadialOutIcon\");\n/**\n * The navball radial-in marker: a small ring with a centre cross and four chevrons pointing inward, in the radial hue.\n *\n * @category Icons\n */\nexport const RadialInIcon = makeMarker(\"radialIn\", \"RadialInIcon\");\n/**\n * The navball manoeuvre-node marker: a diamond with a centre dot and a tick at the top, in the manoeuvre hue.\n *\n * @category Icons\n */\nexport const ManeuverIcon = makeMarker(\"maneuver\", \"ManeuverIcon\");\n/**\n * The navball target marker: a square with corner rays and a centre dot, in the target hue.\n *\n * @category Icons\n */\nexport const TargetIcon = makeMarker(\"target\", \"TargetIcon\");\n/**\n * The navball anti-target marker: a square with corner rays and a centre cross, in the target hue.\n *\n * @category Icons\n */\nexport const AntiTargetIcon = makeMarker(\"antiTarget\", \"AntiTargetIcon\");\n/**\n * The relative-velocity prograde marker (prograde in the target's frame): a ring with diagonal corner rays and a centre dot, in the target hue.\n *\n * @category Icons\n */\nexport const RelativePlusIcon = makeMarker(\"relativePlus\", \"RelativePlusIcon\");\n/**\n * The relative-velocity retrograde marker (retrograde in the target's frame): a ring with diagonal corner rays and a centre cross, in the target hue.\n *\n * @category Icons\n */\nexport const RelativeMinusIcon = makeMarker(\n \"relativeMinus\",\n \"RelativeMinusIcon\",\n);\n/**\n * The parallel marker (along the target's own facing): two vertical rails with a centre dot, in the target hue.\n *\n * @category Icons\n */\nexport const ParallelPlusIcon = makeMarker(\"parallelPlus\", \"ParallelPlusIcon\");\n/**\n * The anti-parallel marker (against the target's own facing): two vertical rails with a centre cross, in the target hue.\n *\n * @category Icons\n */\nexport const ParallelMinusIcon = makeMarker(\n \"parallelMinus\",\n \"ParallelMinusIcon\",\n);\n\n/**\n * The tangent direction of the Frenet frame that n-body flight plans are\n * expressed in. The same component as {@link ProgradeIcon}.\n *\n * @category Icons\n */\nexport const TangentIcon = ProgradeIcon;\n\n/**\n * The normal direction of the Frenet frame, pointing at the centre of\n * curvature. The same component as {@link RadialInIcon}.\n *\n * @category Icons\n */\nexport const FrenetNormalIcon = RadialInIcon;\n\n/**\n * The binormal direction of the Frenet frame, which is the orbit normal. The\n * same component as {@link NormalIcon}.\n *\n * @category Icons\n */\nexport const BinormalIcon = NormalIcon;\n\n/**\n * The marker component for each {@link MarkerId}, for callers driven by data\n * rather than JSX.\n *\n * @example\n * ```tsx\n * function MarkerFor({ id, name }: { id: MarkerId; name: string }) {\n * const Marker = MARKER_ICONS[id];\n * return <Marker label={name} size={16} />;\n * }\n * ```\n *\n * @category Icons\n */\nexport const MARKER_ICONS: Record<MarkerId, typeof ProgradeIcon> = {\n prograde: ProgradeIcon,\n retrograde: RetrogradeIcon,\n normal: NormalIcon,\n antiNormal: AntiNormalIcon,\n radialOut: RadialOutIcon,\n radialIn: RadialInIcon,\n maneuver: ManeuverIcon,\n target: TargetIcon,\n antiTarget: AntiTargetIcon,\n relativePlus: RelativePlusIcon,\n relativeMinus: RelativeMinusIcon,\n parallelPlus: ParallelPlusIcon,\n parallelMinus: ParallelMinusIcon,\n};\n","import {\n type BandKind,\n bandIn,\n isStaticValue,\n type Reading,\n type Tone,\n type Value,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport {\n type HTMLAttributes,\n type ReactNode,\n useLayoutEffect,\n useRef,\n} from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { BarTrack } from \"./BarTrack\";\nimport { bandClaim } from \"./bandClaim\";\nimport { HeldFigure, HeldHost, HeldMark } from \"./HeldMark\";\nimport { magnitudeOr } from \"./magnitude\";\nimport { NullValue } from \"./NullValue\";\nimport { resolveCurrency, type UnitValue } from \"./readingCurrency\";\nimport { placedOnScale, standsApart } from \"./standsApart\";\nimport { Unit } from \"./Unit\";\nimport { UnitSharedFormat, useSharedFormat } from \"./UnitSharedFormat\";\nimport {\n type FormatQuantityOptions,\n type FormatsFor,\n speakQuantity,\n} from \"./units\";\n\n/**\n * Where a meter's label and figure sit relative to its bar.\n *\n * - `stacked`: the label and the figure on a line, the bar beneath them. The\n * default, for a meter that is a readout in its own right\n * - `row`: label, bar and figure on one line, for a list of meters read down a\n * column. Where the line runs out of room the figure, then the bar, wraps\n * rather than being clipped. Inside a `MeterStack` every row lines up\n *\n * @category Meter\n */\nexport type MeterLayout = \"stacked\" | \"row\";\n\ninterface MeterCommonProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Short label shown above the bar and used as the meter's accessible name. */\n label: string;\n /**\n * Semantic colour of the fill. Ignored when `fillColor` is set.\n *\n * @defaultValue `\"neutral\"`\n */\n tone?: Tone;\n /**\n * Arbitrary CSS colour for the fill, such as a resource's own from\n * {@link resourceColor}, for meters whose fill carries an identity rather than\n * a status. Wins over `tone` for the fill colour only.\n */\n fillColor?: string;\n /**\n * Text shown on the right of the header (e.g. \"5.0 rad/h\"). Defaults to the\n * figure, drawn through `<Unit>`. Also the `aria-valuetext`, so it stays a\n * plain string; pass `valueLabelNode` alongside it for visible markup.\n */\n valueLabel?: string;\n /**\n * Visual override for the header's value display. `aria-valuetext` still\n * reads `valueLabel`, so pass both: this for the eye, `valueLabel` for the\n * accessibility tree.\n */\n valueLabelNode?: ReactNode;\n /**\n * Where the label and figure sit relative to the bar. See {@link MeterLayout}.\n *\n * @defaultValue `\"stacked\"`\n */\n layout?: MeterLayout;\n /**\n * Draw the bar alone: no label or figure above it, as for a bar under a\n * heading that already says what it is. The label stays as the accessible\n * name and the figure as the spoken value. With no figure to draw the meter\n * renders nothing at all. Add `statement` to keep the figure, under the bar.\n */\n hideLabel?: boolean;\n /**\n * Keep the figure, `120 / 400 kg`, on a line under a bar drawn alone with\n * `hideLabel`. A capacity can change (a tank is staged away, a queue grows),\n * and the statement says how much of how much where the bar alone says only\n * how full. A headed meter already writes it in its head, so this changes\n * nothing there.\n */\n statement?: boolean;\n}\n\n/**\n * The props of {@link Meter}.\n *\n * @category Meter\n */\nexport interface MeterProps<UnitSymbol extends string = string>\n extends MeterCommonProps {\n /**\n * How much there is. Without a `capacity` it must already be a fraction, in\n * `ratio`. A {@link Reading} is accepted.\n *\n * `null`, or a reading with no value, draws a dash and an empty track, so\n * there is no need to check for a missing value first.\n */\n value: UnitValue<UnitSymbol> | null;\n /**\n * The whole that `value` is a fraction of, in the same unit. A\n * {@link Reading} is accepted. `null` is a capacity that could not be read,\n * and draws a dash.\n */\n capacity?: UnitValue<UnitSymbol> | null;\n /**\n * Fixes the unit `value` and `capacity` are written in. Rarely needed: by\n * default the two share one unit, chosen by magnitude.\n */\n format?: FormatsFor<UnitSymbol>;\n}\n\n/**\n * A labelled bar showing how full something is: a dose, a resource level, a\n * charge. The value is always written beside the bar, so colour never\n * carries meaning alone.\n *\n * Pass `value` alone when it is already a fraction, in `ratio`, or `value` and\n * `capacity` in the same unit for an amount out of a whole.\n *\n * ## Missing and held values\n *\n * With no number to show, the meter draws a dash and an empty track, never a\n * 0% bar. Given a {@link Reading}, a held value is marked as held, and a held\n * capacity hatches the unfilled part of the track.\n *\n * ## Bands\n *\n * Where a reading carries an uncertainty band, the meter draws a tick at each\n * end of it on the track: two marks, never a shaded interval. The bar still\n * shows the observed value, and the ticks sit where the forward model puts the\n * value now. A band on the capacity is ticked near the track's end, as a\n * fraction of the capacity drawn. A band in a different unit from the value or\n * capacity it belongs to draws nothing.\n *\n * @example A fraction on its own, which must be a `ratio`\n * ```tsx\n * import { value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { Meter } from \"@ksp-gonogo/ui-kit\";\n *\n * <Meter label=\"Shielding\" value={value(\"ratio\", 0.72)} tone=\"go\" />\n * ```\n *\n * @example An amount out of a capacity in the same unit\n * ```tsx\n * <Meter label=\"Ore\" value={value(\"kg\", 120)} capacity={value(\"kg\", 400)} />\n * ```\n *\n * @example A bar alone with its statement under it, as work advances toward a total that can move\n * ```tsx\n * <Meter\n * hideLabel\n * statement\n * label=\"Nodes researched\"\n * value={value(\"count\", 3)}\n * capacity={value(\"count\", 8)}\n * />\n * ```\n *\n * @example Row meters in a stack, sharing columns so every bar lines up\n * ```tsx\n * <MeterStack>\n * <Meter label=\"LF\" layout=\"row\" value={value(\"units\", 320)} capacity={value(\"units\", 400)} />\n * <Meter label=\"Ox\" layout=\"row\" value={value(\"units\", 391)} capacity={value(\"units\", 489)} />\n * </MeterStack>\n * ```\n *\n * @category Meter\n * @categoryDescription Meter\n * Bars for a value against its range: meters and stacks of them, level bars,\n * diverging bars, and the meters contributed to a widget.\n */\nexport function Meter<UnitSymbol extends string = string>({\n label,\n value,\n capacity,\n format,\n tone = \"neutral\",\n fillColor,\n valueLabel,\n valueLabelNode,\n layout = \"stacked\",\n hideLabel = false,\n statement = false,\n ...rest\n}: MeterProps<UnitSymbol>) {\n const shown = unwrap(value);\n const held = unwrap(capacity);\n const fraction = fillFraction(\n shown.figure,\n capacity === undefined ? undefined : held.figure,\n );\n if (fraction === null || !Number.isFinite(fraction)) {\n // Absence, not a zero bar.\n if (hideLabel) return null;\n return (\n <MeterFrame\n layout={layout}\n label={label}\n display={<NullValue />}\n {...rest}\n >\n {/* Decorative: the label and placeholder are the whole accessible content. */}\n <BarTrack percent={null} tone={tone} aria-hidden=\"true\" />\n </MeterFrame>\n );\n }\n const clamped = Math.min(1, Math.max(0, fraction));\n const bar = {\n label,\n layout,\n hideLabel,\n statement,\n pct: Math.round(clamped * 100),\n tone,\n fillColor,\n // A held capacity marks the track: the axis aged, not the reading on it.\n trackHeld: held.reading?.state === \"held\" && !isStaticValue(held.figure),\n fillHeld: shown.reading?.state === \"held\" && !isStaticValue(shown.figure),\n ...rest,\n };\n const endBounds = apartFrom(\n 1,\n boundsOn(held.reading, held.figure, held.figure),\n );\n if (capacity !== undefined) {\n // The scope encloses the whole bar, since `aria-valuetext` on the track is written at the settled rung.\n return (\n <UnitSharedFormat of={shown.figure?.unit} format={format}>\n <MeterPairBar\n {...bar}\n value={value}\n capacity={capacity}\n shown={shown}\n held={held}\n endBounds={endBounds}\n at={clamped}\n valueLabel={valueLabel}\n valueLabelNode={valueLabelNode}\n />\n </UnitSharedFormat>\n );\n }\n /*\n * No capacity, so the figure is the fraction and `<Unit>` draws it as it\n * arrived. The visible form is a node; `aria-valuetext` is a string from\n * `speakQuantity`, so it never announces \"72 percent-sign\".\n */\n const bounds = apartFrom(\n clamped,\n boundsOn(shown.reading, shown.figure, null),\n );\n return (\n <MeterBar\n {...bar}\n bounds={bounds}\n endBounds={endBounds}\n display={\n valueLabelNode ??\n (valueLabel === undefined ? (\n <Unit value={value} format={format} />\n ) : (\n <HeldLabel caption={heldCaption(value)}>{valueLabel}</HeldLabel>\n ))\n }\n spoken={withBands(\n valueLabel === undefined\n ? speakQuantity(shown.figure, { format })\n : sayCaption(valueLabel, heldCaption(value)),\n bounds,\n endBounds,\n )}\n />\n );\n}\n\n/** What a held reading's mark means in words, or null where the figure is current. */\nfunction heldCaption<UnitSymbol extends string>(\n input: UnitValue<UnitSymbol> | null | undefined,\n): string | null {\n const { held, caption } = resolveCurrency(input ?? null);\n return held ? caption : null;\n}\n\n/** A caller's own words for the figure, with the held reading's words after them. */\nfunction sayCaption(label: string, caption: string | null): string {\n return caption === null ? label : `${label}, ${caption}`;\n}\n\n/**\n * A caller's own string label for the figure, carrying the same held mark and\n * words `<Unit>` would have drawn, so a held meter always looks held. A\n * `valueLabelNode` is drawn as given: it holds its own `<Unit>`s, which mark\n * themselves.\n */\nfunction HeldLabel({\n caption,\n children,\n}: {\n caption: string | null;\n children: ReactNode;\n}) {\n if (caption === null) return <>{children}</>;\n return <HeldFigure caption={caption}>{children}</HeldFigure>;\n}\n\n/** One half of a meter, split into the figure and the reading it came in. */\ninterface Half<UnitSymbol extends string> {\n figure: Value<UnitSymbol> | null;\n reading: Reading<Value<UnitSymbol>> | null;\n}\n\n/**\n * The figure to draw, and the reading it came in where it came in one. The\n * discriminator is guarded on the runtime shape, since `in` throws on a\n * primitive and untyped JavaScript can pass one.\n */\nfunction unwrap<UnitSymbol extends string>(\n input: UnitValue<UnitSymbol> | null | undefined,\n): Half<UnitSymbol> {\n if (typeof input !== \"object\" || input === null || !(\"state\" in input)) {\n return { figure: input ?? null, reading: null };\n }\n return { figure: input.value ?? null, reading: input };\n}\n\n/**\n * The bar's fill, as the 0..1 a track is drawn from. `dividedBy` makes the two\n * halves the same kind, so the quotient is dimensionless. A capacity of zero is\n * no tank at all, so it yields `null`.\n */\nfunction fillFraction<UnitSymbol extends string>(\n figure: Value<UnitSymbol> | null,\n capacity: Value<UnitSymbol> | null | undefined,\n): number | null {\n if (figure === null || capacity === null) return null;\n if (capacity !== undefined && !capacity.isPositive()) return null;\n const drawn = capacity === undefined ? figure : figure.dividedBy(capacity);\n return drawn.magnitude;\n}\n\n/**\n * Where a band's two ends sit on the 0..1 track, and what they claim. The ends\n * are also kept as the bare quantities they arrived as, because the spoken\n * sentence names them in the value's own unit.\n */\ninterface MeterBounds {\n lo: number;\n hi: number;\n kind: BandKind;\n /** The low end as it arrived, for the sentence. */\n loSaid: { magnitude: number; unit: string };\n /** The high end as it arrived. */\n hiSaid: { magnitude: number; unit: string };\n}\n\n/**\n * The band this reading offers about its own figure, placed on the track.\n * `over` is what the two ends are divided by; `null` says the figure is already\n * a fraction. A divisor of zero places nothing.\n */\nfunction boundsOn<UnitSymbol extends string>(\n reading: Reading<Value<UnitSymbol>> | null,\n figure: Value<UnitSymbol> | null,\n over: Value<UnitSymbol> | null,\n): MeterBounds | null {\n if (reading === null || reading.reckoning.status !== \"available\") return null;\n if (figure === null) return null;\n const band = bandIn(reading.reckoning.band, figure.unit);\n if (!band) return null;\n const said = { loSaid: band.lo, hiSaid: band.hi, kind: band.kind };\n if (over === null) {\n return {\n lo: magnitudeOr(band.lo, 0),\n hi: magnitudeOr(band.hi, 0),\n ...said,\n };\n }\n if (!over.isPositive()) return null;\n return {\n lo: magnitudeOr(band.lo.dividedBy(over), 0),\n hi: magnitudeOr(band.hi.dividedBy(over), 0),\n ...said,\n };\n}\n\n/**\n * The bounds, or `null` where every one of them sits on the observation, so\n * they are neither marked nor spoken. `at` is the observation on the 0..1\n * track: the fill's end for the value's band, the track's end for the capacity's.\n */\nfunction apartFrom(at: number, bounds: MeterBounds | null): MeterBounds | null {\n if (bounds === null) return null;\n return standsApart(at, [bounds.lo, bounds.hi], placedOnScale())\n ? bounds\n : null;\n}\n\n/**\n * The intervals appended to what the track already announces, since the marks\n * are shapes with no reading of their own. What each pair claims is\n * `bandClaim`'s to say; the capacity's interval is its own clause.\n */\nfunction withBands(\n spoken: string,\n bounds: MeterBounds | null,\n endBounds: MeterBounds | null,\n shared?: FormatQuantityOptions,\n): string {\n const said = (bound: MeterBounds, lead: string): string => {\n const lo = speakQuantity(bound.loSaid, shared);\n const hi = speakQuantity(bound.hiSaid, shared);\n // The connector tells a listener which two of three numbers are the band.\n return bandClaim(bound.kind, `${lead}with bands at ${lo} and ${hi}`);\n };\n const clauses = [\n bounds === null ? null : said(bounds, \"\"),\n endBounds === null ? null : said(endBounds, \"capacity \"),\n ].filter((clause): clause is string => clause !== null);\n return clauses.length === 0 ? spoken : `${spoken}, ${clauses.join(\", \")}`;\n}\n\n/**\n * A bound's place along the track, as a percentage. Two decimals, since a band\n * half a percent wide is a real claim; clamped, since a model fitted near a\n * limit routinely bounds past it.\n */\nfunction boundPct(fraction: number): number {\n if (!Number.isFinite(fraction)) return 0;\n const clamped = Math.min(1, Math.max(0, fraction));\n return Math.round(clamped * 10_000) / 100;\n}\n\n/**\n * A mark's position. It straddles its bound, except at the two ends, where it\n * tucks fully inside the track so it is not clipped.\n */\nfunction markAt(fraction: number): {\n style: { left: string; transform: string };\n} {\n const pct = boundPct(fraction);\n const shift = pct <= 0 ? 0 : pct >= 100 ? -2 : -1;\n return { style: { left: `${pct}%`, transform: `translateX(${shift}px)` } };\n}\n\ninterface MeterBarProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n label: string;\n layout: MeterLayout;\n hideLabel: boolean;\n /** Whether the figure is kept on a line under a bar drawn alone. */\n statement: boolean;\n /** The fill, as the whole percent `aria-valuenow` and the track both take. */\n pct: number;\n tone: Tone;\n fillColor?: string;\n /** Whether the AXIS has stopped being current. See `BarTrack`. */\n trackHeld: boolean;\n /**\n * Whether the figure has stopped being current: one treatment for the whole\n * meter rather than a mark per entry.\n */\n fillHeld: boolean;\n /** The value for the eye, as markup. */\n display: ReactNode;\n /** The same value for the ear, as the string an attribute can hold. */\n spoken: string;\n /** Where the model's two bounds sit, or `null` where it offers none. */\n bounds: MeterBounds | null;\n /** Where an uncertain capacity puts the track's end, or `null`. */\n endBounds: MeterBounds | null;\n}\n\n/**\n * The meter as it is drawn, given a fill fraction and the two forms of the\n * value that goes with it.\n */\nfunction MeterBar({\n label,\n layout,\n hideLabel,\n statement,\n pct,\n tone,\n fillColor,\n trackHeld,\n fillHeld,\n display,\n spoken,\n bounds,\n endBounds,\n ...rest\n}: MeterBarProps) {\n return (\n <MeterFrame\n layout={layout}\n label={label}\n display={display}\n hideHead={hideLabel}\n statement={statement}\n {...rest}\n >\n <BarTrack\n percent={pct}\n tone={tone}\n fillColor={fillColor}\n trackHeld={trackHeld}\n fillHeld={fillHeld}\n role=\"meter\"\n aria-label={label}\n aria-valuenow={pct}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuetext={spoken}\n />\n {/* Decorative: what the marks say is already in `aria-valuetext`. */}\n {(bounds !== null || endBounds !== null) && (\n <Meter__Marks aria-hidden=\"true\">\n {bounds !== null && (\n <>\n <Meter__Bound data-bound=\"lo\" {...markAt(bounds.lo)} />\n <Meter__Bound data-bound=\"hi\" {...markAt(bounds.hi)} />\n </>\n )}\n {endBounds !== null && (\n <>\n <Meter__Bound data-end-bound=\"lo\" {...markAt(endBounds.lo)} />\n <Meter__Bound data-end-bound=\"hi\" {...markAt(endBounds.hi)} />\n </>\n )}\n </Meter__Marks>\n )}\n </MeterFrame>\n );\n}\n\n/**\n * The label, the figure and the bar, placed by `layout`.\n *\n * `children` is what goes in the bar: the track, and the marks over it. In the\n * stacked form the label and figure share a head line above it; in the row form\n * all three are siblings on one line, the bar between the two words.\n */\nfunction MeterFrame({\n layout,\n label,\n display,\n hideHead = false,\n statement = false,\n children,\n ...rest\n}: Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n layout: MeterLayout;\n label: string;\n display: ReactNode;\n /** Draw the bar alone; the track carries the name and the value. */\n hideHead?: boolean;\n /** With `hideHead`, keep the figure on a line under the bar. */\n statement?: boolean;\n children: ReactNode;\n}) {\n if (hideHead) {\n return (\n <Meter__Root $row={false} {...rest}>\n <Meter__Bar $row={false}>{children}</Meter__Bar>\n {statement && (\n <Meter__Statement data-meter-part=\"statement\">\n {display}\n </Meter__Statement>\n )}\n </Meter__Root>\n );\n }\n if (layout === \"row\") {\n return (\n <Meter__Root $row data-meter-row=\"\" {...rest}>\n <Meter__Label data-meter-part=\"label\">{label}</Meter__Label>\n <Meter__Bar $row data-meter-part=\"bar\">\n {children}\n </Meter__Bar>\n <Meter__Value $row data-meter-part=\"figure\">\n {display}\n </Meter__Value>\n </Meter__Root>\n );\n }\n return (\n <Meter__Root $row={false} {...rest}>\n <Meter__Head>\n <Meter__Label>{label}</Meter__Label>\n <Meter__Value $row={false}>{display}</Meter__Value>\n </Meter__Head>\n <Meter__Bar $row={false}>{children}</Meter__Bar>\n </Meter__Root>\n );\n}\n\n/**\n * The two halves, written and spoken at one rung, so a tank never reads\n * `999 m / 1.0 km`. The `<Unit>`s report into the enclosing\n * `<UnitSharedFormat>`; the spoken string cannot report by rendering, so this\n * component reports on its behalf and writes it at the settled rung.\n */\nfunction MeterPairBar<UnitSymbol extends string = string>({\n value,\n capacity,\n shown,\n held,\n at,\n valueLabel,\n valueLabelNode,\n ...bar\n}: Omit<MeterBarProps, \"display\" | \"spoken\" | \"bounds\"> &\n Pick<MeterProps<UnitSymbol>, \"valueLabel\" | \"valueLabelNode\"> & {\n value: UnitValue<UnitSymbol> | null;\n capacity: UnitValue<UnitSymbol> | null;\n shown: Half<UnitSymbol>;\n held: Half<UnitSymbol>;\n /** Where the fill ends, as the 0..1 the bounds are compared against. */\n at: number;\n }) {\n // A figure overridden in both forms is neither drawn nor spoken, so it sits out of the group.\n const reporting = valueLabel === undefined;\n const fromValue = useSharedFormat(reporting ? shown.figure : undefined);\n const fromCapacity = useSharedFormat(reporting ? held.figure : undefined);\n // One group, so both halves hear the same answer; either serves, and on the first pass neither has one yet.\n const shared = fromValue ?? fromCapacity ?? {};\n /*\n * A row writes the symbol once, after the capacity (`960 / 1,000 units`), and\n * the pair carries one held mark at its end. Which half aged is still\n * drawn on the bar.\n */\n const caption = heldCaption(value) ?? heldCaption(capacity);\n const display =\n valueLabelNode ??\n (valueLabel !== undefined ? (\n <HeldLabel caption={caption}>{valueLabel}</HeldLabel>\n ) : bar.layout === \"row\" ? (\n <Meter__Pair $marked={bar.fillHeld || bar.trackHeld}>\n <Unit value={value} hideUnitInGroup />\n {\" / \"}\n <Unit value={capacity} />\n {(bar.fillHeld || bar.trackHeld) && (\n <HeldMark aria-hidden=\"true\" data-held-mark=\"\" />\n )}\n </Meter__Pair>\n ) : (\n <>\n <Unit value={value} />\n {\" / \"}\n <Unit value={capacity} />\n </>\n ));\n const spoken =\n valueLabel === undefined\n ? `${speakQuantity(shown.figure, shared)} of ${speakQuantity(held.figure, shared)}`\n : sayCaption(valueLabel, caption);\n // The band's ends are written at the group's rung but never reported into it, since they are only spoken.\n const bounds = apartFrom(\n at,\n boundsOn(shown.reading, shown.figure, held.figure),\n );\n return (\n <MeterBar\n {...bar}\n display={display}\n spoken={withBands(spoken, bounds, bar.endBounds, shared)}\n bounds={bounds}\n />\n );\n}\n\n/**\n * The narrowest a row meter's bar is drawn in a `MeterStack`, in pixels. A\n * number, since the stack does arithmetic with it.\n */\nconst ROW_BAR_FLOOR_PX = 48;\n\n/**\n * A vertical list of meters. Row meters in it share columns, so every label,\n * bar and figure lines up. Where the figures do not fit beside the bars, every\n * figure moves under its bar.\n *\n * @category Meter\n */\nexport function MeterStack({\n children,\n ...rest\n}: HTMLAttributes<HTMLDivElement>) {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const stack = ref.current;\n if (!stack || typeof ResizeObserver === \"undefined\") return;\n const place = () => {\n // A stack that has not been laid out has no width to decide against.\n if (stack.clientWidth === 0) return;\n stack.toggleAttribute(\"data-figures-below\", !figuresFitBeside(stack));\n };\n // Every label and figure is observed too, since a figure growing a digit can push the list over the line.\n const resize = new ResizeObserver(place);\n const watch = () => {\n resize.disconnect();\n resize.observe(stack);\n for (const part of rowParts(stack)) resize.observe(part);\n };\n const rows = new MutationObserver(watch);\n rows.observe(stack, { childList: true, subtree: true });\n watch();\n place();\n return () => {\n resize.disconnect();\n rows.disconnect();\n };\n }, []);\n return (\n <Meter__Stack ref={ref} {...rest}>\n {children}\n </Meter__Stack>\n );\n}\n\n/**\n * Keeps a row meter on its `MeterStack`'s columns while it shares a slot with\n * other lines, such as a caption under the bar.\n *\n * @category Meter\n */\nexport const MeterRowGroup = styled.div.attrs({\n \"data-meter-row-group\": \"\",\n} as HTMLAttributes<HTMLDivElement>)`\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: subgrid;\n row-gap: var(--gap-meter-rows);\n min-width: 0;\n\n & > * {\n grid-column: 1 / -1;\n min-width: 0;\n }\n`;\n\n/** The labels and figures of the row meters the stack lines up. */\nfunction rowParts(stack: HTMLElement): HTMLElement[] {\n return Array.from(\n stack.querySelectorAll<HTMLElement>(\n ':scope > [data-meter-row] > [data-meter-part=\"label\"], ' +\n ':scope > [data-meter-row] > [data-meter-part=\"figure\"], ' +\n ':scope > [data-meter-row-group] > [data-meter-row] > [data-meter-part=\"label\"], ' +\n ':scope > [data-meter-row-group] > [data-meter-row] > [data-meter-part=\"figure\"]',\n ),\n );\n}\n\n/**\n * Whether the widest label, a bar at its floor and the widest figure fit on\n * one line of the stack.\n *\n * Measured from the parts' own content widths, which do not change with the\n * decision, so the answer does not flip-flop.\n */\nfunction figuresFitBeside(stack: HTMLElement): boolean {\n const parts = rowParts(stack);\n if (parts.length === 0) return true;\n let label = 0;\n let figure = 0;\n for (const part of parts) {\n if (part.dataset.meterPart === \"label\") {\n label = Math.max(label, unwrappedWidth(part));\n } else {\n figure = Math.max(figure, part.scrollWidth);\n }\n }\n const style = getComputedStyle(stack);\n const px = (v: string) => Number.parseFloat(v) || 0;\n const gap = px(style.columnGap);\n const room =\n stack.clientWidth - px(style.paddingLeft) - px(style.paddingRight);\n return label + gap + ROW_BAR_FLOOR_PX + gap + figure <= room;\n}\n\n/**\n * A label's width on one line, read with wrapping briefly turned off, since a\n * wrapped label takes the column's width rather than its own.\n */\nfunction unwrappedWidth(label: HTMLElement): number {\n const before = label.style.whiteSpace;\n label.style.whiteSpace = \"nowrap\";\n const width = label.scrollWidth;\n label.style.whiteSpace = before;\n return width;\n}\n\n/*\n * Three columns: label, bar, figure, or two with the figures below. A label\n * wraps rather than truncating, since a truncated name reads as a different meter.\n */\nconst Meter__Stack = styled.div`\n display: grid;\n grid-template-columns:\n minmax(min-content, max-content)\n minmax(${ROW_BAR_FLOOR_PX}px, 1fr)\n max-content;\n gap: var(--gap-meter-columns);\n width: 100%;\n\n & > * {\n grid-column: 1 / -1;\n min-width: 0;\n }\n\n & > [data-meter-row],\n & > [data-meter-row-group] > [data-meter-row] {\n display: grid;\n grid-template-columns: subgrid;\n column-gap: normal;\n }\n\n & > [data-meter-row] > [data-meter-part=\"label\"],\n & > [data-meter-row-group] > [data-meter-row] > [data-meter-part=\"label\"] {\n white-space: normal;\n }\n\n &[data-figures-below] {\n grid-template-columns:\n minmax(min-content, max-content)\n minmax(${ROW_BAR_FLOOR_PX}px, 1fr);\n }\n\n &[data-figures-below] > [data-meter-row] > [data-meter-part=\"bar\"],\n &[data-figures-below]\n > [data-meter-row-group]\n > [data-meter-row]\n > [data-meter-part=\"bar\"] {\n grid-column: 2 / -1;\n }\n\n &[data-figures-below] > [data-meter-row] > [data-meter-part=\"figure\"],\n &[data-figures-below]\n > [data-meter-row-group]\n > [data-meter-row]\n > [data-meter-part=\"figure\"] {\n grid-column: 1 / -1;\n }\n`;\n\n/** A mark sits one pixel inside each edge of the track, in a layer over it. */\nconst MARK_INSET = \"1px\";\n\n/* In the row form the three parts wrap rather than shrink. */\nconst Meter__Root = styled.div<{ $row: boolean }>`\n display: flex;\n width: 100%;\n min-width: 0;\n ${({ $row }) =>\n $row\n ? css`\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n column-gap: var(--gap-meter-columns);\n row-gap: var(--gap-caption);\n `\n : css`\n flex-direction: column;\n gap: var(--gap-caption);\n `}\n`;\n\nconst Meter__Head = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--gap-label-value);\n min-width: 0;\n /* The value may drop to its own line at narrow widths rather than crushing the label. */\n flex-wrap: wrap;\n`;\n\nconst Meter__Label = styled.span`\n font-size: var(--font-size-caption);\n color: var(--color-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n /* Never shrinks below the name: overflow hidden zeroes a flex item's automatic minimum size. */\n flex: 0 0 auto;\n max-width: 100%;\n`;\n\n/* The row form writes its figure a step smaller, so a dense list keeps one line per meter. */\nconst Meter__Value = styled.span<{ $row: boolean }>`\n font-size: ${({ $row }) =>\n $row ? \"var(--font-size-compact)\" : \"var(--font-size-value)\"};\n color: var(--color-text-primary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n flex: 0 0 auto;\n /* Pins the value to the trailing edge on the shared line and when it wraps to its own. */\n margin-left: auto;\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n /* Room for the held mark Unit draws outside this box, reserved unconditionally so a quiet channel causes no reflow. */\n padding-right: max(0.44em, var(--inset-meter-mark));\n`;\n\n/* The figure under a bar drawn alone: the row form's smaller step, since it annotates the bar rather than heading it. */\nconst Meter__Statement = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-primary);\n font-variant-numeric: tabular-nums;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n`;\n\n/* The layer the marks are drawn in, over the track rather than inside its clip. */\nconst Meter__Marks = styled.div`\n position: absolute;\n inset: 0;\n pointer-events: none;\n`;\n\n/* The track and the marks over it, in one box the marks are positioned against. */\nconst Meter__Bar = styled.div<{ $row: boolean }>`\n position: relative;\n ${({ $row }) =>\n $row\n ? css`\n flex: 1 1 28px;\n min-width: 28px;\n `\n : \"\"}\n`;\n\n/* One nowrap box for the row form's pair, so its single held mark hangs off the end of the phrase. */\nconst Meter__Pair = styled(HeldHost)<{ $marked: boolean }>`\n /* The pair's own mark reserves the room; the figures inside it carry none. */\n &::after {\n content: ${({ $marked }) => ($marked ? '\"\"' : \"none\")};\n }\n & > span > [data-held-mark] {\n display: none;\n }\n & > span::after {\n content: none;\n }\n`;\n\n/**\n * One end of the model's interval: a 2px tick across the track. Neutral and\n * two-toned (dimmed white with a dark hairline) so it reads over the empty\n * track and over any fill colour.\n */\nconst Meter__Bound = styled.div`\n position: absolute;\n top: ${MARK_INSET};\n bottom: ${MARK_INSET};\n width: 2px;\n background: var(--color-text-primary);\n /* Inset and outset, so the mark keeps an edge against a light or saturated fill on either side of its end. */\n box-shadow:\n 0 0 0 1px rgb(0 0 0 / 0.55),\n inset 0 0 0 0.5px rgb(0 0 0 / 0.35);\n opacity: 0.62;\n pointer-events: none;\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { HTMLAttributes } from \"react\";\nimport styled from \"styled-components\";\nimport { TONE_MARK } from \"./tone\";\n\n/**\n * The one track height a bar has. A mark drawn over it sits one pixel inside\n * each edge, in a layer of its own, since the track's `overflow: hidden`\n * rounds the fill.\n */\nconst TRACK_HEIGHT = \"8px\";\n\nexport interface BarTrackProps extends HTMLAttributes<HTMLDivElement> {\n /** The filled share, 0-100, already clamped; `null` draws the empty track alone. */\n percent: number | null;\n tone: Tone;\n /** An arbitrary CSS colour that wins outright over the tone fill. */\n fillColor?: string;\n /** The capacity has stopped being current: the unfilled part hatches. */\n trackHeld?: boolean;\n /** The reading has stopped being current: the fill dims. */\n fillHeld?: boolean;\n}\n\n/**\n * The track and fill a `Meter` draws. The role and its aria attributes arrive\n * through `rest`, since a decorative track carries none.\n */\nexport function BarTrack({\n percent,\n tone,\n fillColor,\n trackHeld = false,\n fillHeld = false,\n children,\n ...rest\n}: BarTrackProps) {\n return (\n <BarTrack__Track data-track-held={trackHeld ? \"\" : undefined} {...rest}>\n {percent !== null && (\n <BarTrack__Fill\n $tone={tone}\n $fillColor={fillColor}\n $held={fillHeld}\n data-fill-held={fillHeld ? \"\" : undefined}\n style={{ width: `${percent}%` }}\n />\n )}\n {trackHeld && (\n <BarTrack__Hatch\n data-track-hatch=\"\"\n style={{ left: `${percent ?? 0}%` }}\n />\n )}\n {children}\n </BarTrack__Track>\n );\n}\n\n/* The edge stays solid whether or not the capacity is current: a held capacity is drawn by BarTrack__Hatch inside it. */\nconst BarTrack__Track = styled.div`\n width: 100%;\n border-radius: var(--radius-pill);\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-subtle);\n overflow: hidden;\n /* Positioned for the fill only: this overflow rounds the fill's ends and would clip any mark drawn over it. */\n position: relative;\n height: ${TRACK_HEIGHT};\n`;\n\n/**\n * The unfilled part of a track whose capacity has stopped being current,\n * hatched thinly in the held mark's hue: the extent is what aged, so the\n * figure's fill is left clear of it. Static, so there is no motion to reduce.\n */\nconst BarTrack__Hatch = styled.div`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n background-image: repeating-linear-gradient(\n -45deg,\n ${TONE_MARK.warn} 0 1px,\n transparent 1px 4px\n );\n opacity: 0.55;\n pointer-events: none;\n`;\n\nconst BarTrack__Fill = styled.div<{\n $tone: Tone;\n $fillColor?: string;\n $held: boolean;\n}>`\n height: 100%;\n border-radius: var(--radius-pill);\n transition: width var(--duration-slow) var(--ease-standard);\n /* A held reading dims the fill, not the hue and not the whole bar, so a label beside it stays readable. */\n ${({ $held }) => ($held ? \"opacity: 0.55;\" : \"\")}\n background: ${({ $tone, $fillColor }) => $fillColor ?? TONE_MARK[$tone]};\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n`;\n","import type { Reading, Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { formatKspDate } from \"./formatKspDate\";\nimport { HeldFigure } from \"./HeldMark\";\nimport { figureAttributes, resolveCurrency } from \"./readingCurrency\";\nimport { Tooltip } from \"./Tooltip\";\nimport { VisuallyHidden } from \"./VisuallyHidden\";\n\n/**\n * Which clock an instant is on. Under signal delay they differ for the same\n * event: SCET is when it happens at the craft, and the received clock is when\n * the telemetry showing it reaches a command centre, one light-time later.\n *\n * The qualifier belongs to each reading, not to the screen, since two vessels\n * have different light-times. It is drawn as `SCET` or `AT <vantage>`. A\n * space-centre channel describes no craft, so it takes no context.\n *\n * @category MissionDate\n */\nexport type TimeContext =\n /** The craft's own clock: when the event happens, or happened, out there. */\n | { frame: \"scet\" }\n /**\n * The arrival clock at the observing vantage: when the frame showing it\n * reaches, or reached, the operator. `vantage` names that command centre; it\n * is optional because a frame can arrive before the roster naming it does.\n */\n | { frame: \"received\"; vantage?: string };\n\n/**\n * The qualifier as rendered: token for the eye, phrase for the accessibility\n * tree, and a title spelling out what the reading means.\n */\nfunction describeContext(context: TimeContext): {\n token: string;\n spoken: string;\n title: string;\n} {\n if (context.frame === \"scet\") {\n return {\n token: \"SCET\",\n spoken: \"spacecraft event time\",\n title: \"Spacecraft event time: when this happens at the craft\",\n };\n }\n const { vantage } = context;\n return vantage === undefined\n ? {\n token: \"RECEIVED\",\n spoken: \"received\",\n title: \"When the telemetry showing this arrives\",\n }\n : {\n token: `AT ${vantage}`,\n spoken: `received at ${vantage}`,\n title: `When the telemetry showing this arrives at ${vantage}`,\n };\n}\n\n/*\n * Sized and dimmed relative to the number it qualifies, as `<Unit>`'s symbol\n * is. `text-transform` is pinned so a lowercasing parent cannot turn SCET into prose.\n */\nconst MissionDate__Context = styled.span`\n font-size: max(0.72em, 10px);\n opacity: 0.72;\n white-space: nowrap;\n text-transform: none;\n`;\n\n/**\n * Props for {@link MissionDate}.\n *\n * @category MissionDate\n */\nexport interface MissionDateProps {\n /**\n * Universal time: seconds since the game's epoch, not a duration. A plain\n * number is accepted for the client-interpolated view clock. A `Value<\"s\">`\n * is accepted too and is read the same way, as seconds since the epoch: it\n * is never drawn as a length of time here. For a length of time use `Unit`.\n */\n value:\n | Value<\"ut\">\n | Value<\"s\">\n | Reading<Value<\"ut\">>\n | Reading<Value<\"s\">>\n | number\n | null\n | undefined;\n /**\n * Which clock the instant is on, shown after it as `SCET` or `AT KSC`.\n *\n * Leave it `undefined` where the two clocks do not differ (a LAN session, a\n * vantage that is the craft itself), since a qualifier that is always present\n * is one nobody reads.\n */\n context?: TimeContext;\n}\n\n/**\n * A universal time, rendered as a date on the calendar the game is running:\n * `Y1 D5 03:22:37` under stock, `14 Mar 1957 03:22:37` under a real calendar.\n *\n * An instant rather than a quantity: {@link Unit} draws lengths of time, and\n * this draws points in it, since 9,201,600 seconds after the epoch is Year 2\n * Day 1, not \"106 days\". The calendar is the one the game reports on\n * `time.calendar`. A missing or non-finite value renders {@link NULL_DISPLAY},\n * not the epoch. A held `Reading` keeps its date and takes the held mark.\n *\n * @example\n * ```tsx\n * import { MissionDate } from \"@ksp-gonogo/ui-kit\";\n *\n * // A UT field off a payload, in seconds since the epoch.\n * <MissionDate value={course.completesAtUt} />\n *\n * // An event time under signal delay, qualified with its clock.\n * <MissionDate value={nodeUt} context={{ frame: \"scet\" }} />\n * <MissionDate value={nodeUt} context={{ frame: \"received\", vantage: \"KSC\" }} />\n * ```\n *\n * @category MissionDate\n * @categoryDescription MissionDate\n * Instants on the game's own calendar: a date for a point in time, and a\n * countdown to one. A length of time is a {@link Unit}.\n */\nexport function MissionDate({ value, context }: MissionDateProps) {\n // A held instant stays true, so a held reading adds only the mark, its grade and its last-valid instant.\n const carried = typeof value === \"number\" ? undefined : value;\n const currency = resolveCurrency(carried);\n const { shown, held, caption } = currency;\n const ut = typeof value === \"number\" ? value : shown?.magnitude;\n const figure = figureAttributes({ ...currency, shown: ut });\n const qualifier = context && describeContext(context);\n const date = formatKspDate(ut ?? Number.NaN);\n return (\n <>\n {held ? (\n /* The component otherwise renders bare text, so the mark needs a box to hang off. */\n <HeldFigure {...figure} caption={caption}>\n {date}\n </HeldFigure>\n ) : (\n <span data-figure={figure[\"data-figure\"]}>{date}</span>\n )}\n {qualifier && (\n <>\n {\" \"}\n <Tooltip text={qualifier.title} focusable>\n <MissionDate__Context>\n {/* The phrase replaces the token in the accessibility tree, as `<Unit>`'s word does. */}\n <span aria-hidden=\"true\">{qualifier.token}</span>\n <VisuallyHidden>{qualifier.spoken}</VisuallyHidden>\n </MissionDate__Context>\n </Tooltip>\n </>\n )}\n </>\n );\n}\n","import { value } from \"@ksp-gonogo/sitrep-sdk\";\nimport { useId, useState } from \"react\";\nimport { Button } from \"./Button\";\nimport { Cluster } from \"./Cluster\";\nimport { FieldLabel, Input } from \"./Form\";\nimport { kspCalendar } from \"./kspTime\";\nimport { NULL_DISPLAY } from \"./NullValue\";\nimport { Stack } from \"./Stack\";\nimport { Text } from \"./Text\";\nimport { writeQuantity } from \"./units\";\n\n/**\n * Props for {@link MissionDateField}.\n *\n * @category Form\n */\nexport interface MissionDateFieldProps {\n /**\n * The instant being edited, in seconds since the game's epoch, or `null` when\n * there is none: a reading that did not arrive, a field the producer withheld.\n *\n * A non-finite number is read as `null` too, rather than clamped to the epoch.\n */\n value: number | null;\n\n /** Called with the new instant, in whole seconds since the game's epoch. */\n onChange: (ut: number) => void;\n\n /** Names the whole group for a screen reader: \"Ignition\", \"Plan end\". */\n label: string;\n\n disabled?: boolean;\n\n /**\n * The coarse steps offered, in seconds, smallest first. Rendered as a minus\n * button and a plus button per entry. Defaults to a minute, ten minutes, an\n * hour and a day of the LIVE calendar.\n *\n * An empty list removes the row entirely, for a caller with another nudge\n * control beside this one.\n */\n steps?: number[];\n}\n\n/**\n * The five components of an instant on the game's own calendar.\n *\n * @category Form\n */\nexport interface MissionDateParts {\n /** The year, one-based: UT zero is Year 1. */\n year: number;\n /** The day of the year, one-based: UT zero is Day 1. */\n day: number;\n /** The hour of the day, from zero. */\n hour: number;\n /** The minute of the hour, from zero. */\n minute: number;\n /** The second of the minute, from zero. */\n second: number;\n}\n\n/**\n * Splits a UT (seconds since the game's epoch) into calendar components, with years and days ONE-BASED to match\n * every other date this kit renders: UT zero is Year 1 Day 1, not Year 0 Day 0.\n *\n * A non-finite or negative UT gives the epoch. Check for an absent instant\n * before calling this rather than showing it as the epoch.\n *\n * @category Form\n */\nexport function partsOfUt(ut: number): MissionDateParts {\n const { year: YEAR, day: DAY, hour: HOUR, minute: MINUTE } = kspCalendar();\n const clamped = Number.isFinite(ut) ? Math.max(0, ut) : 0;\n\n const year = Math.floor(clamped / YEAR) + 1;\n const inYear = clamped % YEAR;\n const day = Math.floor(inYear / DAY) + 1;\n const inDay = inYear % DAY;\n\n return {\n year,\n day,\n hour: Math.floor(inDay / HOUR),\n minute: Math.floor((inDay % HOUR) / MINUTE),\n second: Math.floor(inDay % MINUTE),\n };\n}\n\n/** What an instant typed into an empty field is built on top of. */\nconst EPOCH_PARTS: MissionDateParts = {\n year: 1,\n day: 1,\n hour: 0,\n minute: 0,\n second: 0,\n};\n\n/**\n * Recombines calendar components into a UT.\n *\n * Does not clamp an out-of-range component: an hour of 30 rolls into the next\n * day, so the operator never does the carry.\n *\n * @category Form\n */\nexport function utOfParts(parts: MissionDateParts): number {\n const { year: YEAR, day: DAY, hour: HOUR, minute: MINUTE } = kspCalendar();\n return (\n (parts.year - 1) * YEAR +\n (parts.day - 1) * DAY +\n parts.hour * HOUR +\n parts.minute * MINUTE +\n parts.second\n );\n}\n\n/**\n * A coarse step's label, written by the kit's duration formatter on the live\n * calendar's tiers, so a day's worth of seconds reads as a day.\n */\nfunction stepLabel(seconds: number): string {\n return writeQuantity(value(\"s\", seconds));\n}\n\n/**\n * A universal time ENTERED as a calendar instant: year, day, hour, minute,\n * second, each its own field, plus coarse steps. The fields are for an instant\n * you know; the steps are the tuning loop for one you are looking for.\n *\n * Calendar lengths come from the running game's calendar, so day 300 means\n * what the game's own clock calls day 300. Edits round to the second, so the\n * field never shows one instant and holds another.\n *\n * An instant nobody stated is not the epoch: `null` comes up empty with the\n * absent token, and nothing is committed until a component is typed.\n *\n * {@link UnitInput} with a `ut` unit renders this for you and emits a `Value`;\n * use this directly when you hold the instant as a bare number of seconds.\n *\n * @category Form\n */\nexport function MissionDateField({\n value,\n onChange,\n label,\n disabled,\n steps,\n}: MissionDateFieldProps) {\n const groupId = useId();\n const absentId = `${groupId}-absent`;\n // A non-finite number is the same absence as a null, reached by a different route.\n const instant = value !== null && Number.isFinite(value) ? value : null;\n const parts = instant === null ? null : partsOfUt(instant);\n const calendar = kspCalendar();\n const coarse = steps ?? [\n calendar.minute,\n 10 * calendar.minute,\n calendar.hour,\n calendar.day,\n ];\n\n /*\n * An in-progress edit is held as text for the one field being typed in, so a\n * cleared field stays empty instead of snapping back. Dropped on blur, so the\n * field never disagrees with the instant.\n */\n const [draft, setDraft] = useState<{\n key: keyof MissionDateParts;\n text: string;\n } | null>(null);\n\n const field = (\n key: keyof MissionDateParts,\n text: string,\n min: number,\n width: string,\n ) => (\n <Stack gap=\"caption\" key={key}>\n <FieldLabel htmlFor={`${groupId}-${key}`}>{text}</FieldLabel>\n <Input\n id={`${groupId}-${key}`}\n type=\"number\"\n inputMode=\"numeric\"\n // Named for the group as well as the column, so two of these on one panel are distinguishable.\n aria-label={`${label} ${text}`}\n // On every field, so the absence is spoken on focus.\n aria-describedby={parts === null ? absentId : undefined}\n min={min}\n step={1}\n style={{ width }}\n disabled={disabled}\n placeholder={parts === null ? NULL_DISPLAY : undefined}\n value={\n draft?.key === key\n ? draft.text\n : parts === null\n ? \"\"\n : String(parts[key])\n }\n onBlur={() => setDraft(null)}\n onChange={(event) => {\n const typed = event.target.value;\n setDraft({ key, text: typed });\n if (typed.trim() === \"\") return;\n const next = Number(typed);\n if (!Number.isFinite(next)) return;\n // The epoch is the base only once the operator has typed something.\n onChange(utOfParts({ ...(parts ?? EPOCH_PARTS), [key]: next }));\n }}\n />\n </Stack>\n );\n\n return (\n <Stack gap=\"related-dense\" role=\"group\" aria-label={label}>\n <Cluster gap=\"related-dense\" wrap justify=\"start\">\n {field(\"year\", \"YEAR\", 1, \"5rem\")}\n {field(\"day\", \"DAY\", 1, \"5rem\")}\n {field(\"hour\", \"HR\", 0, \"4rem\")}\n {field(\"minute\", \"MIN\", 0, \"4rem\")}\n {field(\"second\", \"SEC\", 0, \"4rem\")}\n </Cluster>\n {/* Words as well as the token, since the dash carries the absence only to an eye. */}\n {parts === null && (\n <Text id={absentId} level=\"muted\" size=\"sm\">\n {`${NULL_DISPLAY} no ${label.toLowerCase()} to show. Type one to state it.`}\n </Text>\n )}\n {coarse.length === 0 ? null : (\n <Cluster gap=\"related-packed\" wrap justify=\"start\">\n <Text level=\"faint\" size=\"sm\">\n NUDGE\n </Text>\n {/* Disabled over an absent instant: a step is relative, and there is nothing to step from. */}\n {coarse.map((step) => (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n key={`minus-${step}`}\n disabled={disabled || instant === null}\n aria-label={`${label} earlier by ${stepLabel(step)}`}\n onClick={() => instant !== null && onChange(instant - step)}\n >\n {`-${stepLabel(step)}`}\n </Button>\n ))}\n {coarse.map((step) => (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n key={`plus-${step}`}\n disabled={disabled || instant === null}\n aria-label={`${label} later by ${stepLabel(step)}`}\n onClick={() => instant !== null && onChange(instant + step)}\n >\n {`+${stepLabel(step)}`}\n </Button>\n ))}\n </Cluster>\n )}\n </Stack>\n );\n}\n","import type { ReactNode } from \"react\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styled from \"styled-components\";\nimport { Button } from \"./Button\";\nimport { focusRingInset } from \"./focusRing\";\nimport { CloseIcon } from \"./Icons\";\nimport { ModalChromeContext, type ModalChromeValue } from \"./ModalSaveBar\";\nimport { titleText } from \"./titleText\";\nimport { useKeyboardScrollable } from \"./useScrollerMetric\";\n\ninterface ModalEntry {\n id: string;\n title?: string;\n ariaLabel?: string;\n width?: string;\n content: ReactNode;\n}\n\ninterface ModalOpenOptions {\n title?: string;\n /** Names a dialog that has no visible `title`. Ignored when `title` is given. */\n ariaLabel?: string;\n /** CSS length for the dialog max-width. Defaults to 560px. */\n width?: string;\n}\n\ninterface ModalContextValue {\n /** Opens a dialog showing `content` on top of any already open, and returns its id. */\n open: (content: ReactNode, options?: ModalOpenOptions) => string;\n /** Closes the dialog with this id, whether or not it is on top. Does nothing for an id already closed. */\n close: (id: string) => void;\n}\n\nconst ModalContext = createContext<ModalContextValue | null>(null);\n\n/** The id `open()` hands back is only ever compared against this module's own stack, so a counter is sufficient. */\nlet modalSeq = 0;\n\n/**\n * Holds the stack of open modal dialogs and renders them into `document.body`.\n * The dashboard mounts one at its root, so a widget on the dashboard calls\n * {@link useModal} without mounting its own. Anywhere else, such as a test or\n * a page of your own, wrap the tree in one: `useModal` needs it above it.\n *\n * Each dialog has a title bar with a close button, a scrolling body, and an\n * optional sticky footer set with {@link useModalSaveBar} or\n * {@link useModalChrome}. Escape, the close button and a click on the backdrop\n * close only the topmost dialog; when its content has reported unsaved\n * changes, they show a \"Discard unsaved changes?\" prompt instead. Focus is\n * trapped inside the dialog and returns to the opener when it closes.\n *\n * @category Modal\n * @categoryDescription Modal\n * Dialogs over the dashboard: opening one, its save bar and footer, and telling\n * whether its content has unsaved changes.\n */\nexport function ModalProvider({ children }: Readonly<{ children: ReactNode }>) {\n const [modals, setModals] = useState<ModalEntry[]>([]);\n\n const open = useCallback(\n (content: ReactNode, options?: ModalOpenOptions): string => {\n const id = `modal-${++modalSeq}`;\n setModals((prev) => [\n ...prev,\n {\n id,\n title: options?.title,\n ariaLabel: options?.ariaLabel,\n width: options?.width,\n content,\n },\n ]);\n return id;\n },\n [],\n );\n\n const close = useCallback((id: string) => {\n setModals((prev) => prev.filter((m) => m.id !== id));\n }, []);\n\n const value = useMemo(() => ({ open, close }), [open, close]);\n\n return (\n <ModalContext.Provider value={value}>\n {children}\n {modals.map((m, i) => (\n <ModalDialog\n key={m.id}\n entry={m}\n isTop={i === modals.length - 1}\n onClose={() => close(m.id)}\n />\n ))}\n </ModalContext.Provider>\n );\n}\n\n/**\n * Opens and closes modal dialogs. `open(content, options)` pushes a dialog\n * showing `content` and returns its id; `options` takes a `title`, an\n * `ariaLabel` for a dialog with no visible title, and a CSS `width` for the\n * dialog's max-width (560px by default). `close(id)` removes that dialog.\n * Throws when called outside a {@link ModalProvider}.\n *\n * @example\n * ```tsx\n * const { open } = useModal();\n * <Button onClick={() => open(<FlightDetails />, { title: \"Flight History\" })}>\n * History\n * </Button>\n * ```\n *\n * @category Modal\n */\nexport function useModal(): ModalContextValue {\n const ctx = useContext(ModalContext);\n if (!ctx) throw new Error(\"useModal must be used inside <ModalProvider>\");\n return ctx;\n}\n\ninterface ModalDialogProps {\n entry: ModalEntry;\n /** Only the topmost of a stack answers the keyboard. */\n isTop: boolean;\n onClose: () => void;\n}\n\nconst FOCUSABLE =\n 'button:not(:disabled), [href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex=\"-1\"])';\n\nfunction ModalDialog({ entry, isTop, onClose }: Readonly<ModalDialogProps>) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const isTopRef = useRef(isTop);\n isTopRef.current = isTop;\n\n // Focus goes back to whatever opened the dialog once it closes.\n useEffect(() => {\n const opener =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n return () => {\n if (opener?.isConnected) opener.focus();\n };\n }, []);\n const titleId = useId();\n // Dismiss only when both press and release land on the backdrop, so a text selection dragged out of the dialog does not close it.\n const downOnBackdropRef = useRef(false);\n\n // Footer and dirty state registered by content via useModalChrome.\n const [footer, setFooter] = useState<ReactNode>(null);\n const [dirty, setDirty] = useState(false);\n const [confirming, setConfirming] = useState(false);\n const confirmRef = useRef<HTMLButtonElement>(null);\n const dirtyRef = useRef(dirty);\n dirtyRef.current = dirty;\n const confirmingRef = useRef(confirming);\n confirmingRef.current = confirming;\n\n const chrome = useMemo<ModalChromeValue>(() => ({ setFooter, setDirty }), []);\n const [body, setBody] = useState<HTMLDivElement | null>(null);\n const bodyTabIndex = useKeyboardScrollable(body);\n\n // Every close path funnels here, so unsaved changes show the discard confirmation instead.\n const requestClose = useCallback(() => {\n if (dirtyRef.current) {\n setConfirming(true);\n return;\n }\n onClose();\n }, [onClose]);\n\n // While the discard confirmation is open, Escape cancels it rather than closing the modal.\n useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n if (e.key !== \"Escape\" || !isTopRef.current) return;\n if (confirmingRef.current) {\n e.stopPropagation();\n setConfirming(false);\n return;\n }\n requestClose();\n }\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [requestClose]);\n\n // Move focus into the confirmation when it appears.\n useEffect(() => {\n if (confirming) confirmRef.current?.focus();\n }, [confirming]);\n\n /*\n * Trap focus inside the dialog. The focusable set is read on every Tab, so a\n * control that renders late or becomes disabled is judged as it is now.\n * Re-runs when the form and confirmation views swap, to move focus into\n * whichever is shown.\n */\n // biome-ignore lint/correctness/useExhaustiveDependencies: `confirming` and `footer` are intentional triggers, they change the set of focusable elements.\n useEffect(() => {\n const el = dialogRef.current;\n if (!el) return;\n\n function handleTab(e: KeyboardEvent) {\n if (e.key !== \"Tab\" || !isTopRef.current || !el) return;\n const focusable = el.querySelectorAll<HTMLElement>(FOCUSABLE);\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last?.focus();\n }\n return;\n }\n if (document.activeElement === last) {\n e.preventDefault();\n first?.focus();\n }\n }\n document.addEventListener(\"keydown\", handleTab);\n if (!confirming) el.querySelector<HTMLElement>(FOCUSABLE)?.focus();\n return () => document.removeEventListener(\"keydown\", handleTab);\n }, [confirming, footer]);\n\n return (\n <>\n {createPortal(\n // Interactive, so it cannot also be role=\"presentation\"; keyboard users close with Escape.\n <Backdrop\n onMouseDown={(e) => {\n downOnBackdropRef.current = e.target === e.currentTarget;\n }}\n onMouseUp={(e) => {\n // Through requestClose, so the dirty guard can intercept.\n if (downOnBackdropRef.current && e.target === e.currentTarget) {\n requestClose();\n }\n downOnBackdropRef.current = false;\n }}\n >\n <Dialog\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={entry.title ? titleId : undefined}\n aria-label={entry.title ? undefined : entry.ariaLabel}\n $width={entry.width}\n >\n <DialogHeader>\n {entry.title && (\n <DialogTitle id={titleId}>{entry.title}</DialogTitle>\n )}\n <CloseButton onClick={requestClose} aria-label=\"Close\">\n <CloseIcon size={16} />\n </CloseButton>\n </DialogHeader>\n <DialogBody ref={setBody} tabIndex={bodyTabIndex}>\n <ModalChromeContext.Provider value={chrome}>\n {entry.content}\n </ModalChromeContext.Provider>\n </DialogBody>\n {footer && !confirming && <DialogFooter>{footer}</DialogFooter>}\n {confirming && (\n <DialogFooter\n role=\"alertdialog\"\n aria-label=\"Discard unsaved changes?\"\n >\n <DiscardPrompt>Discard unsaved changes?</DiscardPrompt>\n <Button\n variant=\"ghost\"\n type=\"button\"\n onClick={() => setConfirming(false)}\n >\n Keep editing\n </Button>\n <Button\n variant=\"primary\"\n ref={confirmRef}\n type=\"button\"\n onClick={() => {\n setConfirming(false);\n onClose();\n }}\n >\n Discard\n </Button>\n </DialogFooter>\n )}\n </Dialog>\n </Backdrop>,\n document.body,\n )}\n </>\n );\n}\n\nconst Backdrop = styled.div`\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.72);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: var(--z-modal);\n`;\n\nconst Dialog = styled.div<{ $width?: string }>`\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-strong);\n border-radius: var(--radius-floating);\n min-width: min(320px, 100vw - 16px);\n max-width: ${({ $width }) => $width ?? \"560px\"};\n width: 90vw;\n max-height: 80vh;\n display: flex;\n flex-direction: column;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);\n`;\n\nconst DialogHeader = styled.div`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--inset-modal-bar);\n border-bottom: 1px solid var(--color-border-subtle);\n flex-shrink: 0;\n`;\n\nconst DialogTitle = styled.h2`\n margin: 0;\n ${titleText}\n`;\n\nconst CloseButton = styled.button`\n background: none;\n border: none;\n color: var(--color-text-faint);\n cursor: pointer;\n line-height: var(--line-height-flush);\n padding: var(--inset-glyph-button);\n\n @media (hover: hover) {\n &:hover {\n color: var(--color-text-primary);\n }\n }\n @media (pointer: coarse) {\n min-width: 44px;\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n`;\n\nconst DialogBody = styled.div`\n padding: var(--inset-modal-body);\n overflow-y: auto;\n ${focusRingInset}\n -webkit-overflow-scrolling: touch;\n flex: 1;\n`;\n\n/* Sticky action region. Lives outside DialogBody so it never scrolls away. */\nconst DialogFooter = styled.div`\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--gap-control-row);\n padding: var(--inset-modal-bar);\n border-top: 1px solid var(--color-border-subtle);\n flex-shrink: 0;\n`;\n\nconst DiscardPrompt = styled.span`\n margin-right: auto;\n font-size: var(--font-size-compact);\n color: var(--color-text-primary);\n`;\n","import type { ReactNode } from \"react\";\nimport { createContext, useContext, useEffect, useMemo, useRef } from \"react\";\nimport { Button } from \"./Button\";\nimport { configEqual } from \"./configEqual\";\n\n/**\n * What a modal dialog offers the content inside it: a sticky footer and an\n * unsaved-changes flag. Read it through {@link useModalChrome} or\n * {@link useModalSaveBar} rather than directly.\n *\n * @category Modal\n */\nexport interface ModalChromeValue {\n /** Sets or replaces the sticky footer node. Pass null to clear it. */\n setFooter: (node: ReactNode) => void;\n /** Reports whether the current content has unsaved changes. */\n setDirty: (dirty: boolean) => void;\n}\n\n/**\n * The context each open modal dialog provides to its content, `null` outside\n * a modal. Content uses {@link useModalChrome} or {@link useModalSaveBar}\n * rather than reading it.\n *\n * @category Modal\n */\nexport const ModalChromeContext = createContext<ModalChromeValue | null>(null);\n\n/**\n * Shows `footer` as the enclosing modal's sticky footer, below the scrolling\n * body so it never scrolls out of view, and reports `dirty` so the modal asks\n * before discarding unsaved changes. The footer is cleared and the flag reset\n * on unmount. Does nothing outside a modal. For a plain Save button use\n * {@link useModalSaveBar}, which works out `dirty` for you.\n *\n * @category Modal\n */\nexport function useModalChrome(footer: ReactNode, dirty: boolean): void {\n const ctx = useContext(ModalChromeContext);\n const setFooter = ctx?.setFooter;\n const setDirty = ctx?.setDirty;\n\n useEffect(() => {\n setFooter?.(footer);\n return () => setFooter?.(null);\n }, [setFooter, footer]);\n\n useEffect(() => {\n setDirty?.(dirty);\n return () => setDirty?.(false);\n }, [setDirty, dirty]);\n}\n\n/**\n * Options for {@link useModalSaveBar}.\n *\n * @category Modal\n */\nexport interface ModalSaveBarOptions<Draft> {\n /** Called when the operator presses Save. */\n onSave: () => void;\n /**\n * The working draft the form would persist on Save, as the full config\n * object. Compared with its value when the modal opened and with `saved` to\n * decide whether there are unsaved changes.\n */\n value: Draft;\n /**\n * The config as currently persisted (the form's `config` prop). A draft\n * equal to it counts as clean even when it differs from the value at open.\n */\n saved: Draft;\n /** Save button label. Defaults to \"Save\". */\n saveLabel?: ReactNode;\n /** Extra buttons drawn to the left of Save, such as Cancel. */\n extra?: ReactNode;\n /**\n * Disables the Save button. Save is not disabled automatically when the form\n * has no changes, so the operator can always save again.\n */\n disabled?: boolean;\n}\n\n/**\n * Puts a primary Save button (and any `extra` buttons) in the enclosing\n * modal's sticky footer, and marks the modal dirty so it asks before\n * discarding unsaved edits. Use it in a widget's config form in place of a\n * Save button at the bottom of the form.\n *\n * The form counts as dirty only when `value` differs, by {@link configEqual},\n * from both its value when the modal opened and `saved`. The hook renders\n * nothing where it is called and does nothing outside a modal.\n *\n * @example\n * ```tsx\n * import { useMemo, useState } from \"react\";\n * import type { ConfigComponentProps } from \"@ksp-gonogo/sitrep-sdk\";\n * import { ConfigForm, Switch, useModalSaveBar } from \"@ksp-gonogo/ui-kit\";\n *\n * interface NavballConfig {\n * controlMode?: boolean;\n * }\n *\n * function NavballConfigForm({ config, onSave }: ConfigComponentProps<NavballConfig>) {\n * const [controlMode, setControlMode] = useState(config.controlMode === true);\n * const candidate = useMemo(() => ({ controlMode }), [controlMode]);\n * useModalSaveBar({\n * onSave: () => onSave(candidate),\n * value: candidate,\n * saved: config,\n * });\n * return (\n * <ConfigForm>\n * <Switch label=\"Control mode\" checked={controlMode} onChange={setControlMode} />\n * </ConfigForm>\n * );\n * }\n * ```\n *\n * @category Modal\n */\nexport function useModalSaveBar<Draft>(\n options: Readonly<ModalSaveBarOptions<Draft>>,\n): void {\n const { onSave, value, saved, saveLabel = \"Save\", extra, disabled } = options;\n\n // Wrapped in an object, so a falsy first value still counts as captured.\n const baselineRef = useRef<{ v: Draft } | null>(null);\n if (baselineRef.current === null) baselineRef.current = { v: value };\n\n const dirty =\n !configEqual(value, baselineRef.current.v) && !configEqual(value, saved);\n\n const footer = useMemo(\n () => (\n <>\n {extra}\n <Button\n variant=\"primary\"\n type=\"button\"\n onClick={onSave}\n disabled={disabled}\n >\n {saveLabel}\n </Button>\n </>\n ),\n [extra, onSave, disabled, saveLabel],\n );\n useModalChrome(footer, dirty);\n}\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { Block__Root, type BlockProps, renderBlockAnatomy } from \"./Block\";\nimport { LiveRegion } from \"./LiveRegion\";\nimport { toneEdge } from \"./tone\";\n\n/**\n * Props for {@link Notice}: everything {@link BlockProps} takes, plus the\n * banner's tone and how loudly it is announced.\n *\n * @category EmptyState\n */\nexport interface NoticeProps extends BlockProps {\n /**\n * What kind of statement this is, drawn as the banner's whole border.\n * Defaults to `warn`.\n */\n tone?: Tone;\n /**\n * Interrupt rather than announce politely: the banner becomes\n * `role=\"alert\"` with `aria-live=\"assertive\"`. Keep it for an abort and\n * nothing softer.\n */\n assertive?: boolean;\n /** What the banner says. */\n children?: ReactNode;\n}\n\nconst Notice__Root = styled(Block__Root)<{ $tone: Tone }>`\n --gap-related: var(--gap-related-comfortable);\n --bleed-inline: 0px;\n\n background: var(--color-surface-sunken);\n border: 1px solid ${({ $tone }) => toneEdge($tone)};\n border-radius: var(--radius-regular);\n /* A banner is its own strip of the screen, so it takes the roomier inset. */\n padding: var(--inset-surface-standalone);\n`;\n\n/**\n * A banner stating something about the widget rather than a record inside it,\n * such as what is limiting the ship. It takes {@link Block}'s anatomy (title,\n * asides, footer) on a sunken surface bordered in its tone.\n *\n * Unlike a toned {@link Card}, a notice is announced to screen readers. By\n * default it is `role=\"note\"` and its text is read out politely when it\n * appears and whenever its words change; with `assertive` it is\n * `role=\"alert\"` and interrupts. A `role` passed by the caller replaces the\n * kit's and turns the announcement off.\n *\n * @example\n * ```tsx\n * <Notice tone=\"nogo\" title=\"Thrust limited\">\n * Two engines are flamed out; available thrust is 48%.\n * </Notice>\n * ```\n *\n * @category EmptyState\n */\nexport function Notice({\n tone = \"warn\",\n assertive = false,\n role,\n children,\n ...props\n}: NoticeProps): ReactNode {\n const rootRef = useRef<HTMLElement>(null);\n const [said, setSaid] = useState(\"\");\n useEffect(() => {\n const root = rootRef.current;\n if (assertive || role !== undefined || root === null) return;\n const text = spokenText(root);\n setSaid((previous) => (previous === text ? previous : text));\n });\n\n if (assertive) {\n return renderBlockAnatomy(Notice__Root, {\n ...props,\n children,\n role: role ?? \"alert\",\n \"aria-live\": role === undefined ? \"assertive\" : undefined,\n $tone: tone,\n });\n }\n const block = renderBlockAnatomy(Notice__Root, {\n ...props,\n children,\n ref: rootRef,\n // A note, so a caller's `aria-label` names something that may carry a name.\n role: role ?? \"note\",\n $tone: tone,\n });\n if (role !== undefined) return block;\n return (\n <>\n {block}\n <LiveRegion visuallyHidden>{said}</LiveRegion>\n </>\n );\n}\n\n/**\n * The notice's words. Text runs are joined with a space, so a title and the\n * body under it do not read as one word.\n */\nfunction spokenText(root: HTMLElement): string {\n const runs: string[] = [];\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {\n runs.push(node.textContent ?? \"\");\n }\n return runs.join(\" \").replace(/\\s+/g, \" \").trim();\n}\n","import {\n type ReadFrameChoice,\n readFrameChoicesEqual,\n} from \"@ksp-gonogo/sitrep-sdk/frames\";\nimport { Field, FieldHint, FieldLabel, Select } from \"./Form\";\n\n/**\n * One frame a {@link ReadFrameControl} can offer, and what an operator calls it.\n *\n * @category Form\n */\nexport interface ReadFrameOption {\n choice: ReadFrameChoice;\n label: string;\n}\n\n/**\n * Props for {@link ReadFrameControl}.\n *\n * @category Form\n */\nexport interface ReadFrameControlProps {\n /** Put on the `<select>`, so `label` (as `htmlFor`) names it. */\n id: string;\n label: string;\n /** The widget's current read frame. Shown as a blank, disabled placeholder when it matches nothing on `options`, e.g. before the widget's catalogue has arrived. */\n value: ReadFrameChoice;\n options: readonly ReadFrameOption[];\n onChange: (choice: ReadFrameChoice) => void;\n hint?: string;\n}\n\n/**\n * The control a frame-dependent widget picks its read frame with: a labelled,\n * keyboard-operable `<select>` over `options`, with an optional hint below.\n * The selected option is found by frame equality with `value`\n * (`readFrameChoicesEqual` from `@ksp-gonogo/sitrep-sdk/frames`), so options\n * need no ids of their own.\n *\n * The caller decides what belongs on `options`: which named frames apply to\n * what it is drawing, and whether \"follow the Control Frame\" is one of them.\n * Offer that entry only when the live Control Frame would draw something the\n * other entries do not already draw; `readFrameChoicesEqual` is the check.\n *\n * @category Form\n */\nexport function ReadFrameControl({\n id,\n label,\n value,\n options,\n onChange,\n hint,\n}: Readonly<ReadFrameControlProps>) {\n const selectedIndex = options.findIndex((option) =>\n readFrameChoicesEqual(option.choice, value),\n );\n\n return (\n <Field>\n <FieldLabel htmlFor={id}>{label}</FieldLabel>\n <Select\n id={id}\n value={selectedIndex === -1 ? \"\" : String(selectedIndex)}\n onChange={(event) => {\n const option = options[Number(event.target.value)];\n if (option !== undefined) onChange(option.choice);\n }}\n >\n {selectedIndex === -1 && <option value=\"\" disabled />}\n {options.map((option, index) => (\n <option\n key={`${option.choice.kind}-${option.choice.bodyIndex ?? \"none\"}`}\n value={index}\n >\n {option.label}\n </option>\n ))}\n </Select>\n {hint !== undefined && <FieldHint>{hint}</FieldHint>}\n </Field>\n );\n}\n","import type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { NullValue } from \"./NullValue\";\nimport type { UnitValue } from \"./readingCurrency\";\nimport { Unit } from \"./Unit\";\n\n/**\n * What a read-only field can be handed. `null`/`undefined` show a placeholder.\n *\n * A quantity, or the whole `Reading` it arrived in, goes through {@link Unit},\n * so a held reading keeps its held mark. Not a bare number, which has lost the\n * unit that says how to write it: hand over `value(\"m\", 1)`,\n * `value(\"count\", 3)`, or `value(\"1\", x)` for a dimensionless reading.\n *\n * @category Form\n */\nexport type ReadOnlyFieldValue =\n | boolean\n | string\n | UnitValue\n | null\n | undefined;\n\n/**\n * Props for {@link ReadOnlyField}.\n *\n * @category Form\n */\nexport interface ReadOnlyFieldProps {\n /** What the value IS. Read first, and read every time. */\n label: ReactNode;\n /** Why it matters, or where it comes from. Announced with the label. */\n description?: ReactNode;\n value: ReadOnlyFieldValue;\n className?: string;\n}\n\n/**\n * A labelled value the reader cannot change: data, not a disabled control,\n * which some screen readers skip and which promises it would work otherwise.\n *\n * Renders a description list, one `<dl>` per field so a lone field stays valid,\n * so a reader gets the label and value as one unit. A quantity goes through\n * {@link Unit}.\n *\n * @category Form\n */\nexport function ReadOnlyField({\n label,\n description,\n value,\n className,\n}: ReadOnlyFieldProps) {\n return (\n <ReadOnlyField__List className={className}>\n <ReadOnlyField__Term>\n <ReadOnlyField__Label>{label}</ReadOnlyField__Label>\n {description !== undefined && description !== null && (\n <ReadOnlyField__Description>{description}</ReadOnlyField__Description>\n )}\n </ReadOnlyField__Term>\n <ReadOnlyField__Value $prose={typeof value === \"string\"}>\n <ReadOnlyFieldContent value={value} />\n </ReadOnlyField__Value>\n </ReadOnlyField__List>\n );\n}\n\n/**\n * The value half on its own, for a caller that already draws its own label,\n * drawn exactly as {@link ReadOnlyField} draws it.\n *\n * @category Form\n */\nexport function ReadOnlyFieldContent({\n value,\n}: {\n value: ReadOnlyFieldValue;\n}): ReactNode {\n if (value === null || value === undefined) return <NullValue />;\n if (typeof value === \"object\") return <Unit value={value} />;\n // \"On\"/\"Off\", as the game's own settings windows say.\n if (typeof value === \"boolean\") return value ? \"On\" : \"Off\";\n return value;\n}\n\nconst ReadOnlyField__List = styled.dl`\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--gap-field-term);\n margin: 0;\n`;\n\nconst ReadOnlyField__Term = styled.dt`\n display: flex;\n flex-direction: column;\n gap: var(--gap-caption);\n min-width: 0;\n /* Grows into the space a short value leaves and yields to a long one. */\n flex: 1 1 auto;\n`;\n\n/* The same rungs a writable row's label and description take, so a mixed column reads as one list. */\nconst ReadOnlyField__Label = styled.span`\n font-size: var(--font-size-value);\n color: var(--color-text-primary);\n`;\n\nconst ReadOnlyField__Description = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-dim);\n max-width: 32em;\n`;\n\nconst ReadOnlyField__Value = styled.dd<{ $prose?: boolean }>`\n margin: 0;\n min-width: 0;\n text-align: right;\n font-size: var(--font-size-value);\n font-variant-numeric: tabular-nums;\n color: var(--color-text-primary);\n\n /* A quantity never breaks from its symbol; a sentence wraps and takes at most half the row. */\n ${({ $prose }) =>\n $prose\n ? `\n white-space: normal;\n max-width: 50%;\n text-align: left;\n `\n : \"white-space: nowrap;\"}\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { TONE_TEXT } from \"./tone\";\n\n/**\n * Where a {@link Readout} sits: `hero` fills the remaining panel space and centres, `inline` sits baseline-aligned beside other content.\n *\n * @category Readout\n */\nexport type ReadoutSize = \"hero\" | \"inline\";\n\nconst Readout__Box = styled.div<{ $size: ReadoutSize; $tone: Tone }>`\n display: ${({ $size }) => ($size === \"hero\" ? \"flex\" : \"inline-flex\")};\n font-weight: 700;\n letter-spacing: 0.04em;\n color: ${({ $tone }) => TONE_TEXT[$tone]};\n ${({ $size }) =>\n $size === \"hero\"\n ? css`\n flex: 1;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--gap-tiny-content);\n text-align: center;\n /* A fluid size off the type scale, with a line height tuned to it so descenders do not clip. */\n font-size: clamp(20px, 6vw, 38px);\n line-height: 1.05;\n min-width: 0;\n `\n : css`\n align-items: baseline;\n gap: var(--gap-value-tag);\n /* Display tier: the type scale stops at lg, so this stays literal. */\n font-size: 22px;\n `}\n`;\n\n/**\n * The display-tier figure: bold, tracked, coloured by tone.\n *\n * `size=\"hero\"` is the dominant value of a widget's tiny form (ΔV, time to\n * impact, warp rate): it fills the remaining panel space and centres. The\n * default `size=\"inline\"` is the same treatment smaller, sitting beside other\n * content.\n *\n * Pair with {@link ReadoutCaption} for an optional sub-label.\n *\n * @category Readout\n * @categoryDescription Readout\n * Figures that stand on their own: the bold display readout, a labelled stat, a\n * data line, and the stats contributed to a widget.\n */\nexport function Readout({\n size = \"inline\",\n tone = \"neutral\",\n ...rest\n}: ComponentPropsWithoutRef<\"div\"> & {\n /** Where it sits. Defaults to `inline`. */\n size?: ReadoutSize;\n /** Colour-codes the figure for state-driven widgets. Defaults to `neutral`. */\n tone?: Tone;\n}) {\n return <Readout__Box $size={size} $tone={tone} {...rest} />;\n}\n\n/**\n * Muted uppercase secondary line for a {@link Readout}\n * (e.g. a mode tag).\n *\n * @category Readout\n */\nexport const ReadoutCaption = styled.span`\n font-size: var(--font-size-caption);\n font-weight: 400;\n letter-spacing: 0.05em;\n color: var(--color-text-muted);\n text-transform: uppercase;\n`;\n","import type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { focusRingInset } from \"./focusRing\";\nimport { statusFill } from \"./tone\";\n\n/**\n * Props for {@link Row}. Any other HTML attribute passes through to the\n * rendered element.\n *\n * @category Layout\n */\nexport interface RowProps extends HTMLAttributes<HTMLElement> {\n /** Rendered tag. Defaults to `li` (a `Row` typically sits in a plain `<ul>`). */\n as?: ElementType;\n /**\n * Makes the row a control: pointer cursor, a hover background, and a real\n * `:focus-visible` ring. Pair it with `as=\"button\"`, which is what a\n * selectable row must be so it is reachable by keyboard.\n */\n interactive?: boolean;\n /** Current selection, for an `interactive` row. */\n selected?: boolean;\n /**\n * Button type, for `as=\"button\"`: a bare `<button>` inside a form defaults to\n * `submit`.\n */\n type?: \"button\" | \"submit\" | \"reset\";\n /** Disabled, for `as=\"button\"`. */\n disabled?: boolean;\n /**\n * Lets the badges and actions drop to a second line when they cannot share\n * one with a readable name. The {@link RowName} keeps at least 12 characters\n * of width (or the whole row, if narrower) and the rest wraps. Off by\n * default, so a list never turns ragged without asking.\n */\n wrap?: boolean;\n /**\n * Marks the row as subordinate to the one above it (an \"of which\" line under\n * a total, a child under a tree node) by indenting it on the left. There is\n * one level of nesting.\n */\n nested?: boolean;\n children?: ReactNode;\n}\n\n/**\n * A single spaced-between list row: name on the left, badges and actions on\n * the right. The truncating name child is `RowName` (also `Row.Name`).\n */\nfunction RowBase({\n as,\n interactive = false,\n selected = false,\n wrap = false,\n nested = false,\n type,\n children,\n ...rest\n}: RowProps) {\n return (\n <Row__Root\n as={as ?? \"li\"}\n type={type ?? (as === \"button\" ? \"button\" : undefined)}\n $interactive={interactive}\n $selected={selected}\n $wrap={wrap}\n $nested={nested}\n {...rest}\n >\n {children}\n </Row__Root>\n );\n}\n\n/**\n * The name inside a {@link Row}: fills the free width and truncates with an\n * ellipsis. Also reachable as `Row.Name`.\n *\n * @category Layout\n */\nexport const RowName = styled.span`\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n min-width: 0;\n color: var(--color-text-primary);\n`;\n\n/**\n * How much of the name a wrapping row refuses to give up; the `min()` keeps it\n * from overflowing a narrower row.\n */\nconst WRAPPED_NAME_FLOOR = \"min(12ch, 100%)\";\n\nconst Row__Root = styled.li<{\n $interactive: boolean;\n $selected: boolean;\n $wrap: boolean;\n $nested: boolean;\n}>`\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: var(--gap-row);\n font-size: var(--font-size-compact);\n padding: var(--inset-row);\n ${({ $wrap }) =>\n $wrap\n ? `\n flex-wrap: wrap;\n row-gap: var(--gap-row-wrap);\n\n & > ${RowName} {\n min-width: ${WRAPPED_NAME_FLOOR};\n }\n`\n : \"\"}\n ${({ $interactive }) =>\n $interactive\n ? css`\n width: 100%;\n border: none;\n background: transparent;\n color: var(--color-text-primary);\n padding: var(--inset-row-pressable);\n border-radius: var(--radius-regular);\n cursor: pointer;\n text-align: left;\n font-family: inherit;\n\n &:hover {\n background: var(--color-surface-panel);\n }\n\n ${focusRingInset}\n`\n : \"\"}\n ${({ $interactive, $selected }) =>\n $interactive && $selected\n ? `\n ${statusFill(\"go\")}\n\n &:hover {\n background: var(--color-go-status);\n }\n`\n : \"\"}\n /* After the interactive block, whose padding shorthand would reset it. */\n ${({ $nested }) => ($nested ? \"padding-left: var(--indent-row);\" : \"\")}\n`;\n\n/**\n * One list row with its children spread between the edges: the name on the\n * left in a {@link RowName}, badges and actions on the right. Renders an `li`\n * by default, so it belongs in a plain `ul`. Set `interactive` with\n * `as=\"button\"` for a selectable row.\n *\n * @example\n * ```tsx\n * <ul>\n * {vessels.map((v) => (\n * <Row key={v.id}>\n * <Row.Name>{v.name}</Row.Name>\n * <Inline>\n * <Badge tone=\"go\">{v.situation}</Badge>\n * </Inline>\n * </Row>\n * ))}\n * </ul>\n * ```\n *\n * @category Layout\n */\nexport const Row = Object.assign(RowBase, { Name: RowName });\n","/**\n * The SVG and canvas faces of the reckoning mark, drawn from the same spec as\n * the DOM marks in `HeldMark.tsx`.\n */\nimport { RECKONING_MARK, type ReckoningKind } from \"./reckoningMarkSpec\";\n\n/** Half the held square's side in units of the mark's radius, which gives it the area of a dot of that radius, so the square weighs what the triangle beside it does. */\nconst HELD_SQUARE_HALF = 0.89;\n\n/** The opacity of a held position drawn beside a modelled one. */\nconst GHOST_OPACITY = 0.45;\n\n/**\n * The mark as an SVG shape centred on a point: a square for held, a triangle\n * pointing up for modelled.\n *\n * @category Unit\n */\nexport function ReckoningMarkSvg({\n kind,\n x,\n y,\n scale = 1,\n ghost = false,\n}: {\n kind: ReckoningKind;\n x: number;\n y: number;\n /** Multiplies the canvas-size radius, for a diagram drawn at other than 1x. */\n scale?: number;\n /** Drawn faint: a held position shown beside a modelled one. */\n ghost?: boolean;\n}) {\n const spec = RECKONING_MARK[kind];\n const r = spec.canvasRadius * scale;\n const common = {\n fill: spec.color,\n opacity: ghost ? GHOST_OPACITY : undefined,\n \"data-reckoning-mark\": kind,\n \"aria-hidden\": true as const,\n };\n if (kind === \"held\") {\n const half = r * HELD_SQUARE_HALF;\n return (\n <rect\n x={x - half}\n y={y - half}\n width={2 * half}\n height={2 * half}\n {...common}\n />\n );\n }\n const points = `${x},${y - r} ${x + r},${y + r * 0.8} ${x - r},${y + r * 0.8}`;\n return <polygon points={points} {...common} />;\n}\n\n/** A CSS custom property as the canvas needs it: a resolved colour, since a canvas cannot read `var()`. */\nfunction resolvedColor(\n canvas: HTMLCanvasElement,\n cssVar: string,\n fallback: string,\n): string {\n const v = getComputedStyle(canvas).getPropertyValue(cssVar).trim();\n return v || fallback;\n}\n\n/**\n * Options for {@link paintReckoningMark}.\n *\n * @category Unit\n */\nexport interface PaintReckoningMarkOptions {\n /** Drawn faint: a held position shown beside a modelled one. */\n ghost?: boolean;\n /** Multiplies the mark's size, for a canvas drawn at other than 1x. */\n scale?: number;\n}\n\n/**\n * Paints the mark on a canvas, centred on a point in canvas pixels: a square for\n * held, a triangle pointing up for modelled, in the hue the spec names.\n *\n * @intent The single-mark canvas face for a third-party canvas that draws one\n * held or modelled point; {@link paintReckonedPosition} composes it for a pair.\n * @category Unit\n */\nexport function paintReckoningMark(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D,\n kind: ReckoningKind,\n x: number,\n y: number,\n { ghost = false, scale = 1 }: PaintReckoningMarkOptions = {},\n): void {\n const spec = RECKONING_MARK[kind];\n const r = spec.canvasRadius * scale;\n ctx.save();\n ctx.globalAlpha = ghost ? GHOST_OPACITY : 1;\n ctx.fillStyle = resolvedColor(canvas, spec.cssVar, spec.fallback);\n ctx.beginPath();\n if (kind === \"held\") {\n const half = r * HELD_SQUARE_HALF;\n ctx.rect(x - half, y - half, 2 * half, 2 * half);\n } else {\n ctx.moveTo(x, y - r);\n ctx.lineTo(x + r, y + r * 0.8);\n ctx.lineTo(x - r, y + r * 0.8);\n ctx.closePath();\n }\n ctx.fill();\n ctx.restore();\n}\n\n/**\n * Where a craft was last observed and where a model puts it now, in canvas pixels.\n *\n * @category Unit\n */\nexport interface ReckonedPosition {\n /** The last observed position; drawn as the held mark. */\n held?: { x: number; y: number };\n /** Where the model puts the craft now; drawn as the modelled mark. */\n modelled?: { x: number; y: number };\n}\n\n/**\n * Paints a reckoned position: the held mark at the last observation, and the\n * modelled mark where the model puts the craft now. With both, the held mark\n * is drawn faint, so the pair reads as \"was here, modelled to be there\", joined\n * by a faint dashed track.\n *\n * @category Unit\n */\nexport function paintReckonedPosition(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D,\n { held, modelled }: ReckonedPosition,\n options: Omit<PaintReckoningMarkOptions, \"ghost\"> = {},\n): void {\n if (held !== undefined && modelled !== undefined) {\n ctx.save();\n ctx.globalAlpha = GHOST_OPACITY;\n ctx.strokeStyle = resolvedColor(\n canvas,\n RECKONING_MARK.modelled.cssVar,\n RECKONING_MARK.modelled.fallback,\n );\n ctx.lineWidth = 1;\n ctx.setLineDash([2, 3]);\n ctx.beginPath();\n ctx.moveTo(held.x, held.y);\n ctx.lineTo(modelled.x, modelled.y);\n ctx.stroke();\n ctx.restore();\n }\n if (held !== undefined) {\n paintReckoningMark(canvas, ctx, \"held\", held.x, held.y, {\n ...options,\n ghost: modelled !== undefined,\n });\n }\n if (modelled !== undefined) {\n paintReckoningMark(\n canvas,\n ctx,\n \"modelled\",\n modelled.x,\n modelled.y,\n options,\n );\n }\n}\n","/*\n * Resource-identity colour: a deterministic name -> colour mapping, so the same\n * resource renders the same colour everywhere. Identity, never severity.\n *\n * Two tiers, normalised into the same saturation and lightness range:\n * 1. A curated substring map for known resource kinds, ordered most specific\n * first. Each family owns one hue; members of a family are told apart by\n * a lightness hashed from their full name\n * 2. A golden-angle hash fallback for anything unrecognised, steered out of\n * every curated family's hue neighbourhood\n */\n\n/** Fixed saturation shared by both tiers, tuned by eye for the dark canvas. */\nconst SATURATION_PCT = 65;\n\n/**\n * Legible lightness range every curated member and Tier 2 fallback is mapped\n * into: below the floor a fill is hard to read on the dark canvas, above the\n * ceiling it washes out against highlight chrome.\n */\nconst LIGHTNESS_MIN_PCT = 38;\nconst LIGHTNESS_MAX_PCT = 72;\nconst LIGHTNESS_RANGE_PCT = LIGHTNESS_MAX_PCT - LIGHTNESS_MIN_PCT;\n\n/** Tier 2's fixed lightness (it gets its distinctness from hue), and a single-alias family's, which has no sibling to spread from. */\nconst LIGHTNESS_MID_PCT = 55;\n\n/** Golden angle in degrees: stepping a hash by this amount spreads hues\n * maximally without ever having to track which hues are already in use. */\nconst GOLDEN_ANGLE_DEG = 137.508;\n\n/** Default half-width, in degrees, of a curated family's Tier 2 reserved zone, before it is clamped to the gap to its nearest neighbour. */\nconst RESERVED_ZONE_DEFAULT_DEG = 10;\n\n/** Minimum gap, in degrees, kept between two adjacent families' reserved zones. */\nconst RESERVED_ZONE_MARGIN_DEG = 1.5;\n\n/** Hard stop on the reserved-zone rotation loop, so a pathological `CURATED` table cannot hang it. */\nconst MAX_ROTATIONS = 64;\n\n/**\n * The reserved-zone escape step is derived from the name and mapped into this\n * range, so two unknowns escaping the same zone rotate by different amounts\n * and diverge.\n */\nconst ESCAPE_STEP_MIN_DEG = 60;\nconst ESCAPE_STEP_RANGE_DEG = 241;\n\n/**\n * Fractional dither added to the escape step so it is never a whole number: a\n * whole-number step can orbit a small set of hues that all sit in reserved\n * zones.\n */\nconst ESCAPE_STEP_DITHER = GOLDEN_ANGLE_DEG - Math.floor(GOLDEN_ANGLE_DEG);\n\n/**\n * One curated family: every alias in `aliases` maps into the same `hue`.\n */\ninterface CuratedFamily {\n aliases: readonly string[];\n hue: number;\n}\n\n/**\n * Tier 1: curated substring matches against a lower-cased name. The first\n * match wins, so families are ordered most specific first: a family whose\n * alias contains another family's alias must come before it. Extend it rather\n * than widen an existing alias.\n */\nconst CURATED: readonly CuratedFamily[] = [\n { aliases: [\"liquidfuel\"], hue: 40 }, // amber\n { aliases: [\"lqdhydrogen\", \"hydrogen\"], hue: 205 }, // pale blue\n { aliases: [\"electriccharge\", \"ec\"], hue: 50 }, // yellow\n { aliases: [\"carbondioxide\", \"co2\", \"waste\"], hue: 65 }, // olive\n { aliases: [\"monopropellant\", \"monoprop\"], hue: 95 }, // yellow-green\n { aliases: [\"oxidizer\"], hue: 15 }, // red-orange\n { aliases: [\"oxygen\", \"air\"], hue: 190 }, // cyan\n { aliases: [\"water\"], hue: 215 }, // blue\n { aliases: [\"ore\"], hue: 28 }, // tan / brown\n { aliases: [\"food\"], hue: 130 }, // green\n { aliases: [\"xenon\"], hue: 275 }, // violet\n { aliases: [\"ablator\"], hue: 5 }, // grey-orange\n { aliases: [\"nitrogen\", \"ammonia\"], hue: 175 }, // teal\n];\n\n/** Shortest angular distance between two hues on the 0..360 wheel. */\nfunction hueDistance(a: number, b: number): number {\n const diff = Math.abs(a - b) % 360;\n return diff > 180 ? 360 - diff : diff;\n}\n\n/**\n * Each family's effective Tier 2 reserved-zone radius: the default, clamped to\n * half the gap to its nearest neighbour less the margin, so zones never overlap.\n */\nconst RESERVED_RADII_DEG: readonly number[] = CURATED.map((family, index) => {\n let nearestGap = Infinity;\n for (let other = 0; other < CURATED.length; other++) {\n if (other === index) continue;\n nearestGap = Math.min(\n nearestGap,\n hueDistance(family.hue, CURATED[other].hue),\n );\n }\n const halfGap = nearestGap / 2;\n return Math.max(\n 0,\n Math.min(RESERVED_ZONE_DEFAULT_DEG, halfGap - RESERVED_ZONE_MARGIN_DEG),\n );\n});\n\n/**\n * Every curated family's resolved Tier 2 reserved zone (centre and effective\n * radius).\n */\nexport const CURATED_RESERVED_ZONES: ReadonlyArray<{\n aliases: readonly string[];\n centre: number;\n radius: number;\n}> = CURATED.map((family, index) => ({\n aliases: family.aliases,\n centre: family.hue,\n radius: RESERVED_RADII_DEG[index],\n}));\n\n/**\n * Tier 1 family lookup: `key` must already be lower-cased. First alias match\n * wins; `undefined` when no curated family matches.\n */\nfunction matchCuratedFamily(\n key: string,\n table: readonly CuratedFamily[] = CURATED,\n): CuratedFamily | undefined {\n return table.find((candidate) =>\n candidate.aliases.some((alias) => key.includes(alias)),\n );\n}\n\n/**\n * Tier 1 hue lookup against a table (not exported from the package index).\n * `key` must already be lower-cased.\n */\nexport function matchCuratedHue(\n key: string,\n table: readonly CuratedFamily[] = CURATED,\n): number | undefined {\n return matchCuratedFamily(key, table)?.hue;\n}\n\n/**\n * FNV-1a, 32-bit: a fixed arithmetic definition, so colours stay deterministic\n * across engines and runs.\n */\nfunction fnv1a(str: string): number {\n let hash = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return hash >>> 0;\n}\n\n/** Positive modulo: JS `%` keeps the sign of the dividend. */\nfunction positiveMod(value: number, modulus: number): number {\n return ((value % modulus) + modulus) % modulus;\n}\n\n/**\n * Deterministic member lightness for a full resource name, hashed with a\n * namespaced key so it is independent of Tier 2's hue.\n */\nexport function memberLightness(key: string): number {\n const hash = fnv1a(`light:${key}`);\n const fraction = hash / 0xffffffff;\n return LIGHTNESS_MIN_PCT + fraction * LIGHTNESS_RANGE_PCT;\n}\n\n/**\n * Tier 1 hue and lightness for a full resource name, or `undefined` when no\n * curated family matches. A single-alias family sits at the mid lightness; a\n * multi-alias family spreads each member to its own hashed lightness.\n */\nexport function placedColor(\n key: string,\n): { hue: number; lightness: number } | undefined {\n const family = matchCuratedFamily(key);\n if (!family) return undefined;\n const lightness =\n family.aliases.length === 1 ? LIGHTNESS_MID_PCT : memberLightness(key);\n return { hue: family.hue, lightness };\n}\n\n/**\n * Whether a hue falls inside any curated family's reserved zone.\n */\nfunction isWithinReservedZone(hue: number): boolean {\n return CURATED_RESERVED_ZONES.some(\n ({ centre, radius }) => hueDistance(hue, centre) < radius,\n );\n}\n\n/**\n * Tier 2: `hue = (stableHash(name) * goldenAngle) mod 360`, rotated by a\n * name-derived step until it clears every reserved zone.\n */\nexport function hashHue(key: string): number {\n const hash = fnv1a(key);\n let hue = positiveMod(hash * GOLDEN_ANGLE_DEG, 360);\n // Upper bits of the hash, decoupled from the initial hue.\n const escapeStep =\n ESCAPE_STEP_MIN_DEG +\n ((hash >>> 8) % ESCAPE_STEP_RANGE_DEG) +\n ESCAPE_STEP_DITHER;\n let rotations = 0;\n while (isWithinReservedZone(hue) && rotations < MAX_ROTATIONS) {\n hue = positiveMod(hue + escapeStep, 360);\n rotations++;\n }\n return hue;\n}\n\n/**\n * Resolve a resource name to a stable, legible fill colour, as an `hsl()`\n * string. The same name always gets the same colour, case and surrounding\n * whitespace ignored. Known resources share a hue per family (fuels, for\n * instance) with a lightness that tells members apart; unrecognised names get\n * a hashed hue clear of every known family. The colour is an identity, never\n * a severity: use a tone for state.\n *\n * @example\n * ```tsx\n * <Meter label=\"LiquidFuel\" fillColor={resourceColor(\"LiquidFuel\")} value={amount} capacity={max} />\n * ```\n *\n * @category Theme\n * @categoryDescription Theme\n * The theme a widget is drawn with: its colours, borders and type, the colour\n * for each tone and resource, and whether the operator has asked for reduced\n * motion. Take colours from here, never write one by hand.\n */\nexport function resourceColor(name: string): string {\n const key = name.trim().toLowerCase();\n const placed = placedColor(key);\n const hue = placed?.hue ?? hashHue(key);\n const lightness = placed?.lightness ?? LIGHTNESS_MID_PCT;\n return `hsl(${Math.round(hue)}deg ${SATURATION_PCT}% ${Math.round(lightness)}%)`;\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\nimport { GAP_VAR, type GapToken, RADIUS_VAR } from \"./scales\";\nimport { statusFill } from \"./tone\";\n\n/**\n * Props for {@link SelectableRow}. Any other `button` attribute passes\n * through, except `type`, which is always `button`.\n *\n * @category FilterList\n */\nexport interface SelectableRowProps\n extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"type\"> {\n /**\n * Highlights the row as the active pick in a select-one list (a tracked\n * rotor, a targeted servo). Drives the go-toned fill and defaults\n * `aria-pressed`, so a caller sets the boolean once.\n */\n selected: boolean;\n /**\n * Gap between the stacked lines of content (a name line over a muted meta\n * line). Defaults to `caption`.\n */\n gap?: GapToken;\n /**\n * How the children sit. `stack` (default) is a column of lines. `split` is\n * one line whose first child takes the free width and whose other children\n * keep their own, for a name with a figure or tag trailing it.\n */\n layout?: \"stack\" | \"split\";\n /**\n * How `selected` reads. `fill` (default) floods the row in the go tone.\n * `outline` keeps the row's content on a raised ground edged in the accent,\n * for a row whose text should stay readable as it is.\n */\n selectedLook?: \"fill\" | \"outline\";\n /** What the row shows. */\n children?: ReactNode;\n}\n\n/**\n * A real `<button>` drawn as a full-width, left-aligned list row, for one\n * option in a pick-one list. It stacks its children vertically (typically a\n * name line over a muted meta line) and fills in the go tone while\n * `selected`. Text inherits the row's colour, so plain spans inside pick up\n * the selected tint. `aria-pressed` follows `selected`.\n *\n * @example\n * ```tsx\n * <Stack gap=\"rows\">\n * {servos.map((s) => (\n * <SelectableRow\n * key={s.id}\n * selected={s.id === selectedId}\n * onClick={() => select(s.id)}\n * >\n * <span>{s.name}</span>\n * <Text level=\"muted\" size=\"xs\">{s.group}</Text>\n * </SelectableRow>\n * ))}\n * </Stack>\n * ```\n *\n * @category FilterList\n */\nexport function SelectableRow({\n selected,\n gap = \"caption\",\n layout = \"stack\",\n selectedLook = \"fill\",\n children,\n ...rest\n}: SelectableRowProps) {\n return (\n <SelectableRow__Root\n type=\"button\"\n aria-pressed={rest[\"aria-expanded\"] === undefined ? selected : undefined}\n $selected={selected}\n $gap={gap}\n $layout={layout}\n $look={selectedLook}\n {...rest}\n >\n {children}\n </SelectableRow__Root>\n );\n}\n\nconst SelectableRow__Root = styled.button<{\n $selected: boolean;\n $gap: GapToken;\n $layout: \"stack\" | \"split\";\n $look: \"fill\" | \"outline\";\n}>`\n display: flex;\n ${({ $layout }) =>\n $layout === \"split\"\n ? css`\n justify-content: space-between;\n align-items: baseline;\n > :first-child {\n flex: 1;\n min-width: 0;\n }\n `\n : css`\n flex-direction: column;\n `}\n gap: ${({ $gap }) => GAP_VAR[$gap]};\n width: 100%;\n text-align: left;\n padding: var(--inset-selectable-row);\n border-radius: ${RADIUS_VAR.regular};\n border: 1px solid\n ${({ $selected, $look }) =>\n !$selected\n ? \"var(--color-border-subtle)\"\n : $look === \"outline\"\n ? \"var(--color-accent-fg)\"\n : \"transparent\"};\n ${({ $selected, $look }) =>\n !$selected\n ? \"background: transparent; color: inherit;\"\n : $look === \"outline\"\n ? \"background: var(--color-surface-raised); color: inherit;\"\n : statusFill(\"go\")}\n cursor: pointer;\n font-family: inherit;\n\n ${({ $selected }) =>\n $selected\n ? \"\"\n : css`\n @media (hover: hover) {\n &:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n border-color: var(--color-border-strong);\n }\n }\n `}\n\n &:disabled,\n &[aria-disabled=\"true\"] {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n ${focusRing}\n`;\n","import { forwardRef, type InputHTMLAttributes } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * A native range input's attributes, less the `type`, which a slider always\n * is.\n *\n * @category Form\n */\nexport type SliderProps = Omit<InputHTMLAttributes<HTMLInputElement>, \"type\">;\n\n/**\n * A native range slider in the kit's accent, for a value set by feel along a\n * fixed span (a throttle, a gain). It fills the width it is given; the value\n * it stands for is the caller's to show beside it.\n *\n * @category Form\n */\nexport const Slider = forwardRef<HTMLInputElement, SliderProps>(\n function Slider(props, ref) {\n return <Slider__Input ref={ref} type=\"range\" {...props} />;\n },\n);\n\nconst Slider__Input = styled.input`\n width: 100%;\n min-width: 0;\n margin: 0;\n background: none;\n accent-color: var(--color-accent-fg);\n cursor: pointer;\n\n ${focusRing}\n\n &:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { TONE_TEXT } from \"./tone\";\n\n/**\n * Props for {@link Stat}.\n *\n * @category Readout\n */\nexport interface StatProps\n extends Omit<HTMLAttributes<HTMLDListElement>, \"title\"> {\n /** The heading over the figure. Also the figure's accessible label. */\n label: ReactNode;\n /** The figure. Write a quantity through {@link Unit}. */\n children?: ReactNode;\n /** One line under the figure, qualifying it: a rate, a horizon, a count. */\n detail?: ReactNode;\n /** How alarming the figure is. Defaults to `neutral`. */\n tone?: Tone;\n}\n\n/**\n * One cell of a core-stat strip (a {@link Grid} of `minColWidth=\"7rem\" fit align=\"stretch\"`): a label, a figure, and at most one line under\n * it. A `<dl>` per cell, so the label is associated with the figure\n * programmatically and a cell stays valid wherever it is dropped.\n *\n * A strip of stats is a `Grid` with `minColWidth=\"7rem\" fit align=\"stretch\"\n * gap=\"related-compact\"`, so every stat gets the same room and the row reflows\n * to fewer columns as the tile narrows. The strip is not a live region: add\n * `role=\"status\"` yourself only when its figures change rarely, since figures\n * that move every frame would flood a screen reader.\n *\n * @example\n * ```tsx\n * <Grid minColWidth=\"7rem\" fit align=\"stretch\" gap=\"related-compact\">\n * <Stat label=\"Next hire\" tone={affordable ? \"neutral\" : \"nogo\"}>\n * <Unit value={complex.nextHireCost} />\n * </Stat>\n * <Stat label=\"Science\" detail=\"this flight\">\n * <Unit value={career.science} />\n * </Stat>\n * </Grid>\n * ```\n *\n * @category Readout\n */\nexport function Stat({\n label,\n children,\n detail,\n tone = \"neutral\",\n ...rest\n}: StatProps) {\n return (\n <Stat__Root {...rest}>\n <Stat__Label>{label}</Stat__Label>\n <Stat__Figure $tone={tone}>{children}</Stat__Figure>\n {detail !== undefined && detail !== null && (\n <Stat__Detail>{detail}</Stat__Detail>\n )}\n </Stat__Root>\n );\n}\n\nconst Stat__Root = styled.dl`\n display: flex;\n flex-direction: column;\n gap: var(--gap-caption);\n margin: 0;\n min-width: 0;\n padding: var(--inset-surface);\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n`;\n\nconst Stat__Label = styled.dt`\n font-size: var(--font-size-caption);\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n`;\n\nconst Stat__Figure = styled.dd<{ $tone: Tone }>`\n margin: 0;\n min-width: 0;\n /* Bottom-aligned, so figures line up across the strip even where one label wraps. */\n margin-top: auto;\n display: flex;\n align-items: baseline;\n flex-wrap: wrap;\n gap: var(--gap-figure-parts);\n font-size: var(--font-size-figure);\n font-weight: 700;\n line-height: var(--line-height-tight);\n font-variant-numeric: tabular-nums;\n color: ${({ $tone }) => TONE_TEXT[$tone]};\n`;\n\nconst Stat__Detail = styled.dd`\n margin: 0;\n min-width: 0;\n font-size: var(--font-size-compact);\n color: var(--color-text-muted);\n`;\n","import type { StatEntry } from \"@ksp-gonogo/sitrep-sdk\";\nimport { useContributionsBySlotId } from \"./contributionsRead\";\nimport { NullValue } from \"./NullValue\";\nimport { Stat } from \"./Stat\";\nimport { Unit } from \"./Unit\";\n\n/**\n * Props for {@link StatContributions}.\n *\n * @category Readout\n */\nexport interface StatContributionsProps {\n /**\n * The full slot id the host widget declared (`\"astronaut-complex.readouts\"`),\n * not a bare segment.\n */\n slot: string;\n}\n\n/**\n * Every stat contributed to `slot`, drawn through the kit's own {@link Stat}.\n *\n * A fragment, not a wrapper: the cells land in the host's own `Grid` of stats\n * as siblings of its built-in ones. Renders nothing when\n * nothing is contributed, so a host can place it unconditionally. An entry's\n * quantity is drawn through {@link Unit}, else its text, else the null token.\n *\n * @example\n * ```tsx\n * <Grid minColWidth=\"7rem\" fit align=\"stretch\" gap=\"related-compact\">\n * <Stat label=\"Funds\"><Unit value={funds} /></Stat>\n * <StatContributions slot=\"astronaut-complex.readouts\" />\n * </Grid>\n * ```\n *\n * @category Readout\n */\nexport function StatContributions({ slot }: StatContributionsProps) {\n // The kit cannot name a host widget's slot id, so the entry type is asserted against the contract shape.\n const entries = useContributionsBySlotId(slot) as readonly StatEntry[];\n if (entries.length === 0) return null;\n\n return (\n <>\n {entries.map((entry) => (\n <Stat\n key={entry.id}\n label={entry.label}\n detail={entry.detail}\n tone={entry.tone ?? \"neutral\"}\n >\n <StatFigure entry={entry} />\n </Stat>\n ))}\n </>\n );\n}\n\n/**\n * The figure: a quantity through `Unit`, else the entry's own text. An entry\n * carrying neither draws the null token rather than a blank cell.\n */\nfunction StatFigure({ entry }: { entry: StatEntry }) {\n // A `null` value still goes through `Unit`, which draws the null token itself.\n if (entry.value !== undefined) return <Unit value={entry.value} />;\n if (entry.text !== undefined) return <>{entry.text}</>;\n return <NullValue />;\n}\n","import { useCallback, useId } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * Props for {@link Stepper}.\n *\n * @category Form\n */\nexport interface StepperProps<Option> {\n /**\n * The values this control may hold, in the order it steps through them.\n * Ascending is the convention, because the increment control is drawn on the\n * right and reads as \"more\".\n */\n options: readonly Option[];\n /** The value now held. One not present in `options` reads as off the set:\n * both step controls stay live and stepping lands on the nearest end. */\n value: Option;\n /** Called with the option a step lands on. */\n onChange: (value: Option) => void;\n /** Names the quantity for a screen reader, and both step controls take their\n * own names from it. */\n label: string;\n /** How a value reads. Defaults to `String`, which suits a plain integer; for\n * a member carrying a unit, write it with {@link writeQuantity}. */\n format?: (value: Option) => string;\n /** Shows the value with both step controls off. */\n disabled?: boolean;\n /** Sits under the value, for the sentence that explains what the setting\n * costs. */\n children?: React.ReactNode;\n /** A test id for the element holding the value and both step controls. */\n \"data-testid\"?: string;\n}\n\n/**\n * A stepper over a FIXED ORDERED SET: eight step counts, four tolerances, five\n * quality levels. For a free quantity with a unit use {@link UnitInput}; for\n * one tuned by feel use {@link JogWheel}.\n *\n * Rendered as the ARIA spinbutton pattern. `aria-valuenow` carries the INDEX\n * rather than the value, because the members need not be evenly spaced;\n * `aria-valuetext` carries the formatted member, which is what gets announced.\n * Up and right step forward, down and left step back, Home and End go to the\n * ends.\n *\n * @category Form\n */\nexport function Stepper<Option>({\n options,\n value,\n onChange,\n label,\n format = String,\n disabled = false,\n children,\n \"data-testid\": testId,\n}: StepperProps<Option>) {\n const describedBy = useId();\n const index = options.indexOf(value);\n const atStart = index <= 0;\n const atEnd = index >= options.length - 1;\n\n const step = useCallback(\n (to: number) => {\n if (disabled || options.length === 0) {\n return;\n }\n const clamped = Math.min(Math.max(to, 0), options.length - 1);\n const next = options[clamped];\n if (next !== value) {\n onChange(next);\n }\n },\n [disabled, onChange, options, value],\n );\n\n // A held value outside the set steps to an end, the only answer that needs no guess at the neighbour.\n const back = () => step(index < 0 ? 0 : index - 1);\n const forward = () => step(index < 0 ? options.length - 1 : index + 1);\n\n return (\n <Stepper__Body data-testid={testId}>\n <Stepper__Step\n type=\"button\"\n aria-label={`Decrease ${label}`}\n onClick={back}\n disabled={disabled || (index >= 0 && atStart)}\n >\n −\n </Stepper__Step>\n <Stepper__Value\n role=\"spinbutton\"\n tabIndex={disabled ? -1 : 0}\n aria-label={label}\n aria-valuenow={index < 0 ? undefined : index}\n aria-valuemin={0}\n aria-valuemax={Math.max(options.length - 1, 0)}\n aria-valuetext={format(value)}\n aria-disabled={disabled || undefined}\n aria-describedby={children == null ? undefined : describedBy}\n onKeyDown={(event) => {\n switch (event.key) {\n case \"ArrowUp\":\n case \"ArrowRight\":\n event.preventDefault();\n forward();\n break;\n case \"ArrowDown\":\n case \"ArrowLeft\":\n event.preventDefault();\n back();\n break;\n case \"Home\":\n event.preventDefault();\n step(0);\n break;\n case \"End\":\n event.preventDefault();\n step(options.length - 1);\n break;\n default:\n break;\n }\n }}\n >\n {format(value)}\n </Stepper__Value>\n <Stepper__Step\n type=\"button\"\n aria-label={`Increase ${label}`}\n onClick={forward}\n disabled={disabled || (index >= 0 && atEnd)}\n >\n +\n </Stepper__Step>\n {children != null && (\n <Stepper__Note id={describedBy}>{children}</Stepper__Note>\n )}\n </Stepper__Body>\n );\n}\n\nconst Stepper__Body = styled.div`\n display: inline-grid;\n grid-template-columns: auto 1fr auto;\n align-items: center;\n gap: var(--gap-stepper);\n`;\n\nconst Stepper__Step = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--size-stepper-button);\n font-family: inherit;\n font-size: var(--font-size-compact);\n font-weight: 600;\n line-height: var(--line-height-flush);\n padding: var(--inset-stepper);\n border-radius: var(--radius-regular);\n cursor: pointer;\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-subtle);\n color: var(--color-text-muted);\n\n @media (hover: hover) {\n &:hover:not(:disabled) {\n border-color: var(--color-text-faint);\n color: var(--color-text-primary);\n }\n }\n\n ${focusRing}\n\n &:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n @media (pointer: coarse) {\n min-height: 44px;\n min-width: 44px;\n }\n`;\n\nconst Stepper__Value = styled.div`\n text-align: center;\n font-family: var(--font-family-mono);\n font-size: var(--font-size-value);\n font-variant-numeric: tabular-nums;\n color: var(--color-text-primary);\n padding: var(--inset-stepper);\n border-radius: var(--radius-regular);\n border: 1px solid var(--color-border-subtle);\n background: var(--color-surface-sunken);\n\n ${focusRing}\n\n &[aria-disabled=\"true\"] {\n opacity: 0.4;\n }\n`;\n\nconst Stepper__Note = styled.div`\n grid-column: 1 / -1;\n font-size: var(--font-size-compact);\n color: var(--color-text-muted);\n`;\n","import type { StreamStatusValue } from \"@ksp-gonogo/sitrep-sdk\";\nimport { Badge } from \"./Badge\";\nimport { LiveRegion } from \"./LiveRegion\";\nimport { severityFromStreamStatus } from \"./status/severity\";\nimport { formatStreamStatus } from \"./status/streamStatusWord\";\n\n/**\n * Props for {@link StreamStatusBadge}.\n *\n * @category Badge\n */\nexport interface StreamStatusBadgeProps {\n /** The stream status to show. */\n status: StreamStatusValue;\n}\n\n/**\n * A small `sm` {@link Badge} for a stream status: the status's word, as\n * {@link formatStreamStatus} writes it, in its severity, or nothing while the\n * status is `live`. It sits inside a polite live region that stays\n * mounted while empty, so the first change away from `live` is announced.\n *\n * The dashboard sets `recorded` and `last-before-blackout` itself from a\n * widget's declared channels and shows them in the panel header, so a widget\n * does not draw this for those. Use it for a status that is not the\n * panel's own (a sub-region reading a different Topic), or pass a status to\n * {@link Panel}'s `panelStatus` prop.\n *\n * @category Badge\n */\nexport function StreamStatusBadge({ status }: StreamStatusBadgeProps) {\n const label = formatStreamStatus(status);\n return (\n <LiveRegion>\n {label !== null && (\n <Badge tone={severityFromStreamStatus(status)} size=\"sm\">\n {label}\n </Badge>\n )}\n </LiveRegion>\n );\n}\n","import type {\n AugmentDefinition,\n ComponentDefinition,\n ContributionDefinition,\n SizeDelta,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport {\n getContributionsForSlot,\n onContributionsChange,\n} from \"@ksp-gonogo/sitrep-sdk/spine\";\nimport { useCallback, useEffect, useReducer } from \"react\";\nimport { getAugmentsForSlot, onAugmentsChange } from \"./augments\";\nimport { COMPONENT_SLOT_SEGMENTS } from \"./contributionsRead\";\nimport { useDomainAvailabilityStore } from \"./domainAvailability\";\n\n/** What a widget's definition says about its slots, which is all {@link sizeDeltaFor} reads. */\ntype SizedWidget = Pick<\n ComponentDefinition,\n \"id\" | \"augmentSlots\" | \"contributionSlots\"\n>;\n\n/**\n * The room, in grid units, that the extensions rendering in a widget ask of it:\n * the `sizeDelta` of every augment bound to a slot the widget declares in\n * `augmentSlots`, and of every contribution in the winning priority band of a\n * slot it declares in `contributionSlots` or carries as `badges`, `filters` or\n * `meters`, summed.\n *\n * An extension counts only while it renders, so one whose `requires` Domain is\n * not present adds nothing. A contribution in an absent Domain still holds its\n * band, as it does when rendering, so a lower band stays out. Per-instance\n * augment settings are not read: the delta is a property of the widget.\n *\n * @category Extensions\n */\nexport function sizeDeltaFor(\n def: SizedWidget,\n isDomainPresent: (domain: string) => boolean,\n): { w: number; h: number } {\n const total = { w: 0, h: 0 };\n const add = (delta: SizeDelta | undefined) => {\n total.w += delta?.w ?? 0;\n total.h += delta?.h ?? 0;\n };\n const renders = (e: Pick<AugmentDefinition, \"requires\">) =>\n e.requires === undefined || isDomainPresent(e.requires);\n\n for (const slot of new Set(def.augmentSlots ?? [])) {\n for (const augment of getAugmentsForSlot(slot)) {\n if (renders(augment)) add(augment.sizeDelta);\n }\n }\n const contributionSlots = new Set<string>([\n ...(def.contributionSlots ?? []),\n ...COMPONENT_SLOT_SEGMENTS.map((segment) => `${def.id}.${segment}`),\n ]);\n for (const slot of contributionSlots) {\n for (const contribution of getContributionsForSlot(slot)) {\n if (renders(contribution as Pick<ContributionDefinition, \"requires\">)) {\n add(contribution.sizeDelta);\n }\n }\n }\n return total;\n}\n\n/**\n * The definition with its `minSize` and `defaultSize` grown by `delta`: a\n * missing `minSize` counts from 1x1 and a missing `defaultSize` from the\n * dashboard's 3x3. The definition itself, unchanged, for a zero delta, so a\n * widget nothing extends keeps one identity.\n *\n * @category Extensions\n */\nexport function withSizeDelta<\n Def extends Pick<ComponentDefinition, \"minSize\" | \"defaultSize\">,\n>(def: Def, delta: { w: number; h: number }): Def {\n if (delta.w === 0 && delta.h === 0) return def;\n const grow = (base: { w: number; h: number }) => ({\n w: base.w + delta.w,\n h: base.h + delta.h,\n });\n return {\n ...def,\n minSize: grow(def.minSize ?? { w: 1, h: 1 }),\n defaultSize: grow(def.defaultSize ?? { w: 3, h: 3 }),\n };\n}\n\n/**\n * A function reading {@link sizeDeltaFor} against the live Domain presence of\n * the nearest store, whose identity changes whenever an augment or a\n * contribution registers or a Domain's presence changes, so a memo keyed on it\n * recomputes exactly then. Outside a store every Domain reads as absent.\n *\n * @category Extensions\n */\nexport function useSizeDeltaFor(): (def: SizedWidget) => {\n w: number;\n h: number;\n} {\n const store = useDomainAvailabilityStore();\n const [version, bump] = useReducer((n: number) => n + 1, 0);\n useEffect(() => {\n const stops = [\n onAugmentsChange(bump),\n onContributionsChange(bump),\n store?.subscribe(bump),\n ];\n return () => {\n for (const stop of stops) stop?.();\n };\n }, [store]);\n // `version` is the dependency that makes this a new function on a change.\n // biome-ignore lint/correctness/useExhaustiveDependencies: see above\n return useCallback(\n (def: SizedWidget) =>\n sizeDeltaFor(def, (domain) => store?.isAvailable(domain) ?? false),\n [store, version],\n );\n}\n","import type { KeyboardEvent, ReactNode } from \"react\";\nimport {\n useCallback,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport styled from \"styled-components\";\nimport { focusRingInset } from \"./focusRing\";\nimport { Grid } from \"./Grid\";\nimport { InlineOverflowGlow, useInlineOverflow } from \"./inlineOverflow\";\nimport { Section, SectionTitle } from \"./Section\";\nimport { useElementSize } from \"./useElementSize\";\nimport { VisuallyHidden } from \"./VisuallyHidden\";\n\n/**\n * One tab passed to {@link Tabs}: its label and the content shown when it is selected.\n *\n * @category Tabs\n */\nexport interface TabDescriptor {\n /** Stable identity for the tab. Falls back to the tab's position in the array. */\n id?: string;\n /** The tab's name in the strip, and its panel's accessible name. */\n label: string;\n /** What the tab's panel shows while it is selected. */\n content: ReactNode;\n /** Shows an attention dot beside the label, for a tab whose subsystem needs attention. */\n indicator?: boolean;\n /**\n * The tab's subsystem does not apply right now (nothing flying, no target).\n * It cannot be selected, roving navigation steps over it, and an active tab\n * that turns off falls through to the first tab that still applies. Prefer it\n * to an empty or dimmed panel.\n */\n disabled?: boolean;\n}\n\n/**\n * Props for {@link Tabs}.\n *\n * @category Tabs\n */\nexport interface TabsProps {\n /** The tabs, in strip order. */\n tabs: TabDescriptor[];\n /** The selected tab's id, for controlled selection. Omit it to let the component hold the selection, starting on the first tab. */\n activeId?: string;\n /** Called with the id of the tab the operator selects. */\n onChange?: (id: string) => void;\n /**\n * Lay every panel out side by side, each under its own label, once the\n * container is wide enough for a legible column each; below that, a tablist\n * and one panel. Opt-in, default `false`.\n */\n expandWhenRoomy?: boolean;\n /**\n * Accessible name for the tab strip itself, needed once a screen has more\n * than one. Ignored by the side-by-side layout, which has no strip.\n */\n \"aria-label\"?: string;\n /** As `aria-label`, when the name is already on screen as an element. */\n \"aria-labelledby\"?: string;\n /** A class for the outer element, so a styled-component can extend it. */\n className?: string;\n}\n\n/**\n * The minimum width in pixels each panel needs before {@link Tabs} with `expandWhenRoomy` lays them side by side.\n *\n * @category Tabs\n */\nexport const TABS_PANEL_MIN_WIDTH = 240;\n\n/** Gap between side-by-side panels: `Grid`'s `md` token as a literal, so the width check needs no theme. */\nconst TABS_PANEL_GAP = 8;\n\n/**\n * True when a container `containerWidth` pixels wide fits `panelCount` panels side by side at {@link TABS_PANEL_MIN_WIDTH} each, with the gaps between them. Always false for fewer than two panels.\n *\n * @category Tabs\n */\nexport function shouldExpandTabs(\n containerWidth: number,\n panelCount: number,\n): boolean {\n if (containerWidth <= 0 || panelCount < 2) return false;\n const needed =\n panelCount * TABS_PANEL_MIN_WIDTH + (panelCount - 1) * TABS_PANEL_GAP;\n return containerWidth >= needed;\n}\n\n/**\n * A tab strip with one panel shown below it, following the APG tabs pattern:\n * arrow keys move between tabs, Home and End jump to the ends, and disabled\n * tabs are skipped. The strip tightens its spacing, then scrolls, when the\n * labels do not fit. With `expandWhenRoomy`, a container wide enough for every\n * enabled tab drops the strip and shows each panel side by side under its own\n * heading.\n *\n * @example\n * ```tsx\n * <Tabs\n * aria-label=\"Vessel systems\"\n * tabs={[\n * { id: \"power\", label: \"Power\", content: <PowerView /> },\n * { id: \"comms\", label: \"Comms\", content: <CommsView />, indicator: commsAlarm },\n * { id: \"target\", label: \"Target\", content: <TargetView />, disabled: !hasTarget },\n * ]}\n * />\n * ```\n *\n * @category Tabs\n * @categoryDescription Tabs\n * Tabs that switch a widget's body between views.\n */\nexport function Tabs({\n tabs,\n activeId,\n onChange,\n expandWhenRoomy = false,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n className,\n}: Readonly<TabsProps>) {\n const uid = useId();\n const resolved = useMemo(\n () => tabs.map((t, i) => ({ ...t, id: t.id ?? `tab-${i}` })),\n [tabs],\n );\n\n const isControlled = activeId !== undefined;\n const [internalActiveId, setInternalActiveId] = useState(\n () => activeId ?? resolved[0]?.id ?? \"\",\n );\n const currentId = isControlled ? (activeId as string) : internalActiveId;\n // A disabled tab is never the one on screen, even when named; if every tab is disabled, the caller's choice stands.\n const named = resolved.find((t) => t.id === currentId);\n const active =\n named && !named.disabled\n ? named\n : (resolved.find((t) => !t.disabled) ?? named ?? resolved[0]);\n\n const select = useCallback(\n (id: string) => {\n if (!isControlled) setInternalActiveId(id);\n onChange?.(id);\n },\n [isControlled, onChange],\n );\n\n // Side-by-side mode measures and renders only the tabs that apply.\n const selectable = useMemo(\n () => resolved.filter((t) => !t.disabled),\n [resolved],\n );\n\n const { ref: sizeRef, size } = useElementSize<HTMLDivElement>({ w: 0, h: 0 });\n const expanded =\n expandWhenRoomy && shouldExpandTabs(size.w, selectable.length);\n\n const buttonRefs = useRef<Map<string, HTMLButtonElement>>(new Map());\n const barRef = useRef<HTMLDivElement | null>(null);\n /*\n * Tighter padding and tracking once the tabs stop fitting, decided against the\n * row's uncompacted width. It is cached, and only written while uncompacted,\n * because re-reading it while compacted would oscillate.\n */\n const naturalWidthRef = useRef<number | null>(null);\n const [compact, setCompact] = useState(false);\n // State as well as a ref: `expanded` swaps the strip in and out of the tree, and the overflow watch must follow it.\n const [scroller, setScroller] = useState<HTMLDivElement | null>(null);\n /*\n * Memoized so its identity is stable across renders. An inline ref callback\n * is a new function every render, and React detaches then reattaches a ref\n * whose identity changed, so an inline callback that calls a state setter\n * fires that setter on every render it causes, which is the render it just\n * caused: an infinite loop, React error #185.\n */\n const scrollerRef = useCallback((el: HTMLDivElement | null) => {\n barRef.current = el;\n setScroller(el);\n }, []);\n const overflow = useInlineOverflow(scroller);\n // Measured, since labels differ in width and the blob must land exactly on the active one.\n const [blob, setBlob] = useState<{ left: number; width: number } | null>(\n null,\n );\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-measure when the tab SET changes, since that is what changes the natural width.\n useLayoutEffect(() => {\n const bar = barRef.current;\n if (!bar) return;\n const measure = () => {\n // Recorded on every uncompacted pass, so a font swap is picked up too.\n setCompact((wasCompact) => {\n if (!wasCompact) naturalWidthRef.current = bar.scrollWidth;\n const natural = naturalWidthRef.current;\n return natural == null ? false : natural > bar.clientWidth;\n });\n };\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(bar);\n return () => ro.disconnect();\n }, [expanded, resolved.length]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-measure when the active tab or the tab set changes, which is exactly what moves the blob.\n useLayoutEffect(() => {\n const bar = barRef.current;\n const button = active ? buttonRefs.current.get(active.id) : undefined;\n if (!bar || !button) {\n setBlob(null);\n return;\n }\n const measure = () => {\n const el = buttonRefs.current.get(active?.id ?? \"\");\n if (!el) return;\n // offsetLeft is relative to the track, which scrolls with the tabs, so the blob does too.\n setBlob((prev) =>\n prev && prev.left === el.offsetLeft && prev.width === el.offsetWidth\n ? prev\n : { left: el.offsetLeft, width: el.offsetWidth },\n );\n };\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(bar);\n ro.observe(button);\n return () => ro.disconnect();\n }, [active?.id, resolved.length, expanded]);\n\n /**\n * Move `step` tabs from `from`, wrapping, and keep going while the landing\n * tab is disabled. Bounded by the tab count, so a set with nothing\n * selectable does not move.\n */\n const activateByIndex = useCallback(\n (from: number, step: number) => {\n const n = resolved.length;\n for (let i = 1; i <= n; i++) {\n const next = resolved[(((from + step * i) % n) + n) % n];\n if (!next || next.disabled) continue;\n select(next.id);\n buttonRefs.current.get(next.id)?.focus();\n return;\n }\n },\n [resolved, select],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLButtonElement>) => {\n const currentIdx = resolved.findIndex((t) => t.id === active?.id);\n if (currentIdx < 0) return;\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowDown\":\n e.preventDefault();\n activateByIndex(currentIdx, 1);\n break;\n case \"ArrowLeft\":\n case \"ArrowUp\":\n e.preventDefault();\n activateByIndex(currentIdx, -1);\n break;\n // Home/End start one step outside the strip, so the search lands on the end tab and walks inward past disabled ones.\n case \"Home\":\n e.preventDefault();\n activateByIndex(-1, 1);\n break;\n case \"End\":\n e.preventDefault();\n activateByIndex(resolved.length, -1);\n break;\n }\n },\n [resolved, active?.id, activateByIndex],\n );\n\n if (expanded) {\n return (\n <Tabs__Root ref={sizeRef} data-tabs-root=\"\" className={className}>\n <Grid\n minColWidth={`${TABS_PANEL_MIN_WIDTH}px`}\n align=\"start\"\n gap=\"related-comfortable\"\n >\n {selectable.map((tab) => (\n <Section key={tab.id}>\n <SectionTitle as=\"h3\" $rule>\n {tab.label}\n {tab.indicator && (\n <>\n <Tabs__Dot aria-hidden=\"true\" />\n <VisuallyHidden>, needs attention</VisuallyHidden>\n </>\n )}\n </SectionTitle>\n {tab.content}\n </Section>\n ))}\n </Grid>\n </Tabs__Root>\n );\n }\n\n return (\n <Tabs__Root ref={sizeRef} data-tabs-root=\"\" className={className}>\n <Tabs__BarShell>\n <Tabs__Scroller ref={scrollerRef}>\n <Tabs__Bar\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n >\n {blob && (\n <Tabs__Blob\n aria-hidden=\"true\"\n style={{ left: blob.left, width: blob.width }}\n />\n )}\n {resolved.map((tab) => {\n const isActive = tab.id === active?.id;\n return (\n <Tabs__Button\n key={tab.id}\n ref={(el) => {\n if (el) buttonRefs.current.set(tab.id, el);\n else buttonRefs.current.delete(tab.id);\n }}\n role=\"tab\"\n type=\"button\"\n id={`${uid}${tab.id}-tab`}\n aria-selected={isActive}\n aria-controls={`${uid}${tab.id}-panel`}\n aria-describedby={\n tab.indicator ? `${uid}${tab.id}-attention` : undefined\n }\n tabIndex={isActive ? 0 : -1}\n disabled={tab.disabled}\n $active={isActive}\n onClick={() => select(tab.id)}\n onKeyDown={handleKeyDown}\n $compact={compact}\n >\n {tab.label}\n {tab.indicator && (\n <>\n <Tabs__Dot aria-hidden=\"true\" />\n <span id={`${uid}${tab.id}-attention`} hidden>\n Needs attention\n </span>\n </>\n )}\n </Tabs__Button>\n );\n })}\n </Tabs__Bar>\n </Tabs__Scroller>\n <InlineOverflowGlow $position=\"left\" $visible={overflow.left} />\n <InlineOverflowGlow $position=\"right\" $visible={overflow.right} />\n </Tabs__BarShell>\n {active && (\n <Tabs__Panel\n role=\"tabpanel\"\n id={`${uid}${active.id}-panel`}\n aria-labelledby={`${uid}${active.id}-tab`}\n >\n {active.content}\n </Tabs__Panel>\n )}\n </Tabs__Root>\n );\n}\n\nconst Tabs__Root = styled.div`\n display: flex;\n flex-direction: column;\n gap: var(--gap-tabs-panel);\n /* Fills the panel and constrains children, so a flex:1 tab body can scroll. */\n flex: 1;\n min-height: 0;\n`;\n\n/*\n * Positioned wrapper so the overflow glows can sit over the strip's edges. It\n * reaches out to the panel's edges, so tabs scrolled out of view pass under a\n * glow sitting in the panel's gutter rather than being cut off at its padding.\n */\nconst Tabs__BarShell = styled.div`\n position: relative;\n margin-inline: calc(-1 * var(--bleed-inline));\n`;\n\n/* The scrolling strip. Its inline padding puts the track back on the content column while it rests. */\nconst Tabs__Scroller = styled.div`\n display: flex;\n padding-inline: var(--bleed-inline);\n overflow-x: auto;\n overflow-y: hidden;\n /* Native scrollbar hidden: the edge glows show scroll state. */\n scrollbar-width: none;\n -ms-overflow-style: none;\n &::-webkit-scrollbar {\n width: 0;\n height: 0;\n display: none;\n }\n`;\n\n// The track holding every tab; the blob measures and travels inside it.\nconst Tabs__Bar = styled.div`\n position: relative;\n flex: 1 0 auto;\n display: flex;\n gap: var(--gap-tab);\n background: var(--color-surface-sunken);\n border-radius: var(--radius-pill);\n padding: var(--inset-tab-track);\n flex-wrap: nowrap;\n`;\n\n// The selection blob. The buttons are `position: relative`, so DOM order alone puts the labels over it.\nconst Tabs__Blob = styled.span`\n position: absolute;\n top: var(--inset-tab-track);\n bottom: var(--inset-tab-track);\n border-radius: var(--radius-pill);\n background: var(--color-accent-bg);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);\n transition:\n left var(--duration-base) var(--ease-standard),\n width var(--duration-base) var(--ease-standard);\n\n /* With motion damped the blob simply appears on the new tab. */\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n`;\n\nconst Tabs__Dot = styled.span`\n display: inline-block;\n width: 7px;\n height: 7px;\n margin-left: var(--gap-trailing-mark);\n vertical-align: middle;\n border-radius: var(--radius-circle);\n background: var(--color-warn-mark);\n`;\n\nconst Tabs__Button = styled.button<{\n $active: boolean;\n $compact?: boolean;\n}>`\n /* Transparent: the blob behind it is the selected background. */\n background: transparent;\n border: none;\n /* Lifts the label over the blob by DOM order, no z-index involved. */\n position: relative;\n /* Tabs never shrink: a short label losing its word is worse than a long one continuing offscreen behind the overflow glow. */\n flex: 0 0 auto;\n white-space: nowrap;\n /* Inverts on the accent blob, where primary text fails contrast. */\n color: ${({ $active }) =>\n $active ? \"var(--color-text-inverse)\" : \"var(--color-text-faint)\"};\n cursor: pointer;\n font-size: var(--font-size-compact);\n font-weight: 700;\n text-transform: uppercase;\n border-radius: var(--radius-pill);\n /* Compact gives back the inset and tracking, never the label. Plain concatenation: a nested template literal breaks the parse. */\n ${({ $compact }) =>\n $compact\n ? \"padding: var(--inset-tab-compact);\" + \"letter-spacing: 0.04em;\"\n : \"padding: var(--inset-control);\" + \"letter-spacing: 0.12em;\"}\n /* The label gives under the press, the only feedback a tab gets on touch. */\n transition: transform var(--duration-fast) var(--ease-standard);\n\n &:active:not(:disabled) {\n transform: scale(0.96);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n &:active:not(:disabled) {\n transform: none;\n }\n }\n\n @media (hover: hover) {\n &:hover:not(:disabled) {\n color: var(--color-text-primary);\n }\n }\n\n /* Present but inert: legible enough to say what is missing, and the cursor says it will not respond. */\n &:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n\n ${focusRingInset}\n\n @media (pointer: coarse) {\n min-height: 44px;\n /* Compact still applies on touch: a tab scrolled half out of view is a smaller target, not a bigger one. */\n ${({ $compact }) =>\n $compact\n ? \"padding: var(--inset-tab-compact-touch);\"\n : \"padding: var(--inset-control-touch);\"}\n }\n`;\n\nconst Tabs__Panel = styled.div`\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n`;\n","import { bandIn, type Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport { useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport {\n heldNameMarker,\n InstrumentBound,\n InstrumentHeldMark,\n InstrumentNoFigure,\n sayHeld,\n} from \"./instrumentCurrency\";\nimport { NULL_DISPLAY } from \"./NullValue\";\nimport {\n figureAttributes,\n resolveCurrency,\n type UnitValue,\n} from \"./readingCurrency\";\nimport { placedOnScale, standsApart } from \"./standsApart\";\nimport { useTooltip } from \"./Tooltip\";\nimport { type FormatsFor, quantityScale, speakQuantity } from \"./units\";\n\n/**\n * A shaded band on a {@link Tape}'s scale, such as an ignition band.\n *\n * @category Gauge\n */\nexport interface TapeZone<Unit extends string = string> {\n /** Lower bound of the band. */\n from: Value<Unit>;\n /** Upper bound of the band. */\n to: Value<Unit>;\n /** Fill colour. Defaults to a faint warning tint. */\n color?: string;\n /** Names the band in the meter's spoken value. Never drawn: the band's colour and place say what it is. */\n label?: string;\n}\n\n/**\n * A point marker on a {@link Tape}'s scale, such as a gear-deploy altitude.\n *\n * @category Gauge\n */\nexport interface TapeMarker<Unit extends string = string> {\n /** Value at which to draw the marker. */\n value: Value<Unit>;\n /** Marker colour. Defaults to the accent foreground. */\n color?: string;\n /** Names the marker in the meter's spoken value. Never drawn, so a marker never competes with the scale for room. */\n label?: string;\n /** The model's interval around `value`, drawn as the same two bounds a {@link Meter} draws. */\n bounds?: { lo: Value<Unit>; hi: Value<Unit> };\n}\n\n/**\n * Props for {@link Tape}.\n *\n * @category Gauge\n */\nexport interface TapeProps<Unit extends string = string> {\n /**\n * Which side of the track the scale labels sit on. Default \"left\" (track\n * against the right edge). \"right\" mirrors it, for a tape down a widget's left\n * edge whose numbers should face the content.\n */\n labelSide?: \"left\" | \"right\";\n /** Current value: the pointer position, or the whole reading it arrived in. */\n value: UnitValue<Unit>;\n /** Bottom of the scale. */\n min: Value<Unit>;\n /** Top of the scale. */\n max: Value<Unit>;\n /**\n * Drawn width in pixels.\n *\n * @defaultValue `92`\n */\n width?: number;\n /**\n * Drawn height in pixels.\n *\n * @defaultValue `220`\n */\n height?: number;\n /**\n * Fill the parent's height instead of using a fixed `height`, as a\n * full-height rail beside the main content. `height` is the fallback until\n * the first measurement.\n */\n fillHeight?: boolean;\n /** Interior tick spacing. Omit for no interior ticks. */\n tickStep?: Value<Unit>;\n /** Shaded bands on the scale. */\n zones?: ReadonlyArray<TapeZone<Unit>>;\n /** Point markers on the scale. */\n markers?: ReadonlyArray<TapeMarker<Unit>>;\n /** Draw a distinct ground line at this value (e.g. 0). Always drawn: off the scale it is pinned to the edge it lies beyond, with a chevron pointing out. */\n groundLine?: Value<Unit>;\n /** Draw a sea-level line at this value, a dashed twin of the ground line with the same edge-pinning. */\n seaLevel?: Value<Unit>;\n /**\n * Pin the rung the whole scale is written at. Absent, the rung is taken from\n * `max` and held for every tick, the pointer flag and the unit header, so one\n * strip never mixes \"500 m\" and \"1.0 km\".\n */\n format?: FormatsFor<Unit>;\n /** Accessible label naming what the scale measures (e.g. \"Altitude above terrain\"). */\n ariaLabel: string;\n}\n\nconst PAD_TOP = 12;\nconst PAD_BOTTOM = 12;\n// Left gutter wide enough for a 5-digit unit-less label (e.g. \"10000\").\nconst TRACK_X = 52;\nconst TRACK_W = 10;\n\n/**\n * A vertical linear scale with a moving pointer, the altimeter or airspeed\n * strip instrument. A ruler of values with a pointer at the current `value`,\n * marked `zones` (e.g. an ignition band), point `markers` (e.g. a gear-deploy\n * altitude) and an optional `groundLine`.\n *\n * With no value it draws no pointer. Handed a whole `Reading`, a held figure\n * is marked as held and a model's interval puts two bounds on the scale.\n *\n * Renders as a `role=\"meter\"` on the current value (`aria-valuenow` clamped\n * into range, `aria-valuetext` carrying the written value). The SVG scale is\n * `aria-hidden`, so the widget using it provides a text summary of any zones or\n * markers an operator needs to hear.\n *\n * @category Gauge\n */\nexport function Tape<Unit extends string = string>({\n labelSide = \"left\",\n value,\n min,\n max,\n width = 92,\n height = 220,\n fillHeight = false,\n tickStep,\n zones,\n markers,\n groundLine,\n seaLevel,\n format,\n ariaLabel,\n}: Readonly<TapeProps<Unit>>) {\n // The wrapper's height is parent-driven, so measuring it has no feedback loop with the SVG sized from it.\n const wrapRef = useRef<HTMLDivElement>(null);\n const [measured, setMeasured] = useState(height);\n useEffect(() => {\n if (!fillHeight) return;\n const el = wrapRef.current;\n if (!el) return;\n const update = () => {\n const h = el.clientHeight;\n if (h > 0) setMeasured(h);\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, [fillHeight]);\n const h = fillHeight ? measured : height;\n\n const resolved = resolveCurrency(value);\n const { shown, held, caption, band } = resolved;\n const { anchor, tip } = useTooltip(caption);\n\n // Unwrapped once into pixel geometry; every figure a reader sees goes back out through the unit layer.\n const current = shown?.magnitude ?? Number.NaN;\n const axisMin = min.magnitude;\n const axisMax = max.magnitude;\n const span = axisMax - axisMin;\n const safe = Number.isFinite(current) ? current : axisMin;\n const clamped =\n span > 0 ? Math.max(axisMin, Math.min(axisMax, safe)) : axisMin;\n // No figure, no pointer: one resting at the foot of the rail would say the vessel is there.\n const hasFigure = shown != null;\n\n // One rung for the whole strip, so every mark and the head symbol agree.\n const scale = quantityScale(max, { format });\n const spoken =\n shown == null\n ? NULL_DISPLAY\n : speakQuantity(\n { magnitude: safe, unit: shown.unit },\n { format: scale.rung },\n );\n // Each mark picks its own rung, so a band's two ends are not rounded onto the same figure by the scale's.\n const said = (q: Value<Unit>): string => speakQuantity(q);\n const spokenMarks = [\n ...(markers ?? []).flatMap((m) =>\n m.label === undefined\n ? []\n : [\n m.bounds === undefined\n ? `${m.label} ${said(m.value)}`\n : `${m.label} ${said(m.value)}, between ${said(m.bounds.lo)} and ${said(m.bounds.hi)}`,\n ],\n ),\n ...(zones ?? []).flatMap((z) =>\n z.label === undefined\n ? []\n : [\n `${z.label} zone ${said(z.from.min(z.to))} to ${said(z.from.max(z.to))}`,\n ],\n ),\n ...(groundLine === undefined ? [] : [`ground ${said(groundLine)}`]),\n ...(seaLevel === undefined ? [] : [`sea level ${said(seaLevel)}`]),\n ];\n // The model's interval, narrowed to the figure's own unit: one in another kind is about something else.\n const interval =\n shown == null || band === null ? null : (bandIn(band, shown.unit) ?? null);\n\n // Floored: a squeezed `fillHeight` rail can measure under the padding, and SVG rejects a negative height.\n const usable = Math.max(0, h - PAD_TOP - PAD_BOTTOM);\n // value -> y: max at the top (y = PAD_TOP), min at the bottom.\n const yOf = (v: number): number => {\n if (!(span > 0)) return PAD_TOP + usable;\n const t = Math.max(0, Math.min(1, (v - axisMin) / span));\n return PAD_TOP + (1 - t) * usable;\n };\n\n // Zone ends are ordered in the value algebra (`min`/`max` convert first) before this unwraps them.\n const onRail = (q: Value<Unit>): number => yOf(q.magnitude);\n\n const trackTop = PAD_TOP;\n const trackBottom = PAD_TOP + usable;\n // Mirror the whole scale about the track when the labels read inboard.\n const mirrored = labelSide === \"right\";\n const trackX = mirrored ? width - TRACK_X - TRACK_W : TRACK_X;\n // The side the numeric scale is drawn on.\n const labelX = mirrored ? trackX + TRACK_W + 6 : trackX - 6;\n const labelAnchor = mirrored ? \"start\" : \"end\";\n\n // A level beyond either end sits on that edge with a chevron pointing out, so it is never lost off the rail.\n const drawLevel = (at: Value<Unit>, dashed: boolean) => {\n const y = onRail(at);\n const beyond = at.greaterThan(max) ? -1 : at.lessThan(min) ? 1 : 0;\n const x1 = trackX - 4;\n const x2 = trackX + TRACK_W + 4;\n const mid = trackX + TRACK_W / 2;\n return (\n <g data-level={dashed ? \"sea\" : \"ground\"}>\n <line\n x1={x1}\n y1={y}\n x2={x2}\n y2={y}\n stroke={\n dashed ? \"var(--color-info-mark)\" : \"var(--color-text-primary)\"\n }\n strokeWidth={dashed ? 1.5 : 2}\n strokeDasharray={dashed ? \"3 2\" : undefined}\n />\n {beyond !== 0 && (\n <polygon\n data-off-scale=\"true\"\n points={`${mid - 4},${y - beyond * 2} ${mid + 4},${y - beyond * 2} ${mid},${y + beyond * 4}`}\n fill={\n dashed ? \"var(--color-info-mark)\" : \"var(--color-text-primary)\"\n }\n />\n )}\n </g>\n );\n };\n const ticks: number[] = [];\n const step = tickStep?.magnitude ?? 0;\n if (step > 0 && span > 0) {\n const first = Math.ceil(axisMin / step) * step;\n for (let t = first; t <= axisMax + 1e-9; t += step) ticks.push(t);\n }\n\n const pointerY = yOf(clamped);\n // Compared as fractions of the rail, from the heights the marks are drawn at.\n const onScale = (y: number): number =>\n usable > 0 ? (PAD_TOP + usable - y) / usable : 0;\n const drawnInterval =\n interval !== null &&\n standsApart(\n onScale(pointerY),\n [onScale(onRail(interval.lo)), onScale(onRail(interval.hi))],\n placedOnScale(),\n )\n ? interval\n : null;\n\n return (\n <Tape__Meter\n ref={wrapRef}\n {...figureAttributes(resolved)}\n {...anchor}\n {...(hasFigure\n ? {\n role: \"meter\",\n \"aria-label\": sayHeld(ariaLabel, caption),\n ...heldNameMarker(caption),\n \"aria-valuenow\": clamped,\n \"aria-valuemin\": axisMin,\n \"aria-valuemax\": axisMax,\n \"aria-valuetext\":\n spokenMarks.length === 0\n ? spoken\n : `${spoken}; ${spokenMarks.join(\"; \")}`,\n }\n : {\n /* A meter must state a value, so a figureless rail is an image instead. */\n role: \"img\",\n \"aria-label\": sayHeld(`${ariaLabel}: ${spoken}`, caption),\n ...heldNameMarker(caption),\n })}\n style={fillHeight ? { height: \"100%\" } : undefined}\n >\n <svg\n width={width}\n height={h}\n viewBox={`0 0 ${width} ${h}`}\n aria-hidden=\"true\"\n style={\n fillHeight\n ? { display: \"block\", fontFamily: \"var(--font-family-mono)\" }\n : {\n display: \"block\",\n fontFamily: \"var(--font-family-mono)\",\n maxWidth: \"100%\",\n height: \"auto\",\n }\n }\n >\n {/* Track */}\n <rect\n x={trackX}\n y={trackTop}\n width={TRACK_W}\n height={usable}\n rx={2}\n fill=\"var(--color-surface-raised)\"\n />\n\n {/* Zones */}\n {zones?.map((z) => {\n const yHi = onRail(z.from.max(z.to));\n const yLo = onRail(z.from.min(z.to));\n const h = Math.max(0, yLo - yHi);\n return (\n <g key={`zone-${yLo}-${yHi}-${z.label ?? \"\"}`}>\n <rect\n x={trackX}\n y={yHi}\n width={TRACK_W}\n height={h}\n fill={z.color ?? \"var(--color-warn-mark)\"}\n opacity={0.55}\n />\n </g>\n );\n })}\n\n {/* Sea level under ground, so where they coincide the heavier ground line reads on top */}\n {seaLevel !== undefined && span > 0 && drawLevel(seaLevel, true)}\n {groundLine !== undefined && span > 0 && drawLevel(groundLine, false)}\n\n {/* Interior ticks and labels */}\n {ticks.map((t) => {\n const y = yOf(t);\n return (\n <g key={`tick-${t}`}>\n <line\n x1={mirrored ? trackX + TRACK_W + 4 : trackX - 4}\n y1={y}\n x2={mirrored ? trackX + TRACK_W : trackX}\n y2={y}\n stroke=\"var(--color-border-subtle)\"\n strokeWidth={1}\n />\n <text\n x={labelX}\n y={y}\n textAnchor={labelAnchor}\n dominantBaseline=\"middle\"\n fontSize={8}\n fill=\"var(--color-text-faint)\"\n >\n {scale.mark(t)}\n </text>\n </g>\n );\n })}\n\n {/* Markers */}\n {markers?.map((m) => {\n const { bounds } = m;\n const y = onRail(m.value);\n const color = m.color ?? \"var(--color-accent-fg)\";\n return (\n <g key={`marker-${y}-${m.label ?? \"\"}`} data-marker={m.label}>\n {/* Dashed and hollow, where the observed pointer is a solid bar, so a model's figure is never mistaken for a measurement */}\n <line\n x1={trackX}\n y1={y}\n x2={trackX + TRACK_W}\n y2={y}\n stroke={color}\n strokeWidth={2}\n strokeDasharray=\"2 2\"\n />\n <polygon\n points={\n mirrored\n ? `${trackX},${y} ${trackX - 5},${y - 3} ${trackX - 5},${y + 3}`\n : `${trackX + TRACK_W},${y} ${trackX + TRACK_W + 5},${y - 3} ${trackX + TRACK_W + 5},${y + 3}`\n }\n fill=\"none\"\n stroke={color}\n strokeWidth={1.5}\n />\n {bounds !== undefined &&\n ([\"lo\", \"hi\"] as const).map((end) => {\n const by = onRail(bounds[end]);\n return (\n <InstrumentBound\n key={end}\n end={end}\n x1={trackX}\n y1={by}\n x2={trackX + TRACK_W}\n y2={by}\n />\n );\n })}\n </g>\n );\n })}\n\n {/* The model's two bounds, across the rail */}\n {drawnInterval !== null &&\n ([\"lo\", \"hi\"] as const).map((end) => {\n const y = onRail(drawnInterval[end]);\n return (\n <InstrumentBound\n key={end}\n end={end}\n x1={trackX}\n y1={y}\n x2={trackX + TRACK_W}\n y2={y}\n />\n );\n })}\n\n {hasFigure && (\n <>\n <line\n x1={trackX - 6}\n y1={pointerY}\n x2={trackX + TRACK_W + 6}\n y2={pointerY}\n stroke=\"var(--color-accent-fg)\"\n strokeWidth={2}\n />\n <text\n x={mirrored ? labelX + 2 : labelX - 2}\n y={Math.max(trackTop + 4, Math.min(trackBottom - 4, pointerY))}\n textAnchor={labelAnchor}\n dominantBaseline=\"middle\"\n fontSize={11}\n fontWeight=\"bold\"\n fill=\"var(--color-accent-fg)\"\n >\n {scale.mark(safe)}\n {held && <InstrumentHeldMark size={5} />}\n </text>\n </>\n )}\n {!hasFigure && (\n <InstrumentNoFigure\n x={mirrored ? labelX + 8 : labelX - 8}\n y={trackTop + usable / 2}\n size={11}\n >\n {NULL_DISPLAY}\n </InstrumentNoFigure>\n )}\n\n {/* The scale's symbol, shown once; ticks and the flag stay symbol-less to fit the strip. */}\n {scale.symbol !== \"\" && (\n <text\n x={trackX + TRACK_W / 2}\n y={trackTop - 3}\n textAnchor=\"middle\"\n fontSize={8}\n fill=\"var(--color-text-faint)\"\n >\n {scale.symbol}\n </text>\n )}\n </svg>\n {tip}\n </Tape__Meter>\n );\n}\n\nconst Tape__Meter = styled.div`\n display: block;\n max-width: 100%;\n`;\n","import { useId } from \"react\";\nimport { Field, FieldHint, FieldLabel, Input } from \"./Form\";\nimport { Text } from \"./Text\";\n\n/**\n * Props for {@link TextField}.\n *\n * @category Form\n */\nexport interface TextFieldProps {\n /** Shown above the field and tied to it, so the field is never unlabelled. */\n label: string;\n /** The text now in the field. */\n value: string;\n /** Called with the whole new text on every edit. */\n onChange: (next: string) => void;\n /**\n * The message saying why the current text is refused, shown under the field;\n * setting it marks the field invalid. The field validates nothing itself, so\n * the caller decides what is refused (a duplicate name, a mod's naming rule).\n */\n invalid?: string;\n /** Shown faint in the empty field. Absent, the empty field shows nothing. */\n placeholder?: string;\n /** The most characters the field takes. Absent, no limit. */\n maxLength?: number;\n /** Shows the text but takes no input. */\n disabled?: boolean;\n /** A test id for the input element. */\n \"data-testid\"?: string;\n}\n\n/**\n * A single line of free text: a name somebody types.\n *\n * `TextField`, {@link UnitInput} or {@link Stepper}? The value decides:\n *\n * - `TextField` holds text whose content is the point and which nothing can\n * validate arithmetically, such as a name\n * - {@link UnitInput} holds a quantity with a unit, where any number in range\n * is legal\n * - {@link Stepper} holds one member of a small closed set, where the set is\n * the point\n *\n * The invalid message is `aria-describedby`-linked and `aria-invalid` is set\n * with it, so a screen reader hears the refusal on the field rather than only\n * seeing it beside it.\n *\n * @category Form\n */\nexport function TextField({\n label,\n value,\n onChange,\n invalid,\n placeholder,\n maxLength,\n disabled,\n \"data-testid\": testId,\n}: TextFieldProps) {\n const id = useId();\n const errorId = `${id}-invalid`;\n return (\n <Field>\n <FieldLabel htmlFor={id}>{label}</FieldLabel>\n <Input\n aria-describedby={invalid == null ? undefined : errorId}\n aria-invalid={invalid == null ? undefined : true}\n data-testid={testId}\n disabled={disabled}\n id={id}\n maxLength={maxLength}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n type=\"text\"\n value={value}\n />\n {invalid != null && (\n <FieldHint id={errorId}>\n <Text size=\"xs\" tone=\"warn\">\n {invalid}\n </Text>\n </FieldHint>\n )}\n </Field>\n );\n}\n","import {\n type OrbitTrajectory,\n TrajectoryKindLike,\n} from \"@ksp-gonogo/sitrep-sdk/spine\";\nimport { Stack } from \"./Stack\";\nimport { Text } from \"./Text\";\nimport { Tooltip } from \"./Tooltip\";\n\n/**\n * An orbit path that may not be drawn, and the reason: no horizon was stated\n * for it, the instant on screen is past its horizon, its shape was not stated,\n * the frame asked for cannot be built, or it could not be sampled. It is the\n * `withheld` member of `OrbitTrajectory`, which `useOrbitTrajectory` from\n * `@ksp-gonogo/sitrep-sdk/frames` returns.\n *\n * @category EmptyState\n */\nexport type WithheldTrajectory = Extract<\n OrbitTrajectory,\n { shape: \"withheld\" }\n>;\n\n/**\n * The heading and sentence for a withheld path, for a widget that lays the\n * words out itself. Each reason has its own wording, saying what would let the\n * path be drawn. Use this rather than your own words, so every widget\n * describes a withheld path the same way.\n *\n * @category EmptyState\n */\nexport function trajectoryWithheldCopy(withheld: WithheldTrajectory): {\n heading: string;\n detail: string;\n} {\n switch (withheld.reason) {\n case \"no-horizon-stated\":\n return {\n heading: \"NO HORIZON STATED\",\n detail: \"Nothing has said how far these elements answer for.\",\n };\n case \"past-horizon\":\n return withheld.trajectoryKind === TrajectoryKindLike.Integrated\n ? {\n heading: \"BEYOND INTEGRATION\",\n detail: \"The trajectory is not computed this far ahead yet.\",\n }\n : {\n heading: \"PAST HORIZON\",\n detail: \"These elements do not answer for the instant on screen.\",\n };\n case \"shape-not-stated\":\n return {\n heading: \"SHAPE NOT STATED\",\n detail: \"Nothing has said whether this trajectory is a conic.\",\n };\n case \"frame-unavailable\":\n return {\n heading: \"FRAME UNAVAILABLE\",\n detail:\n \"The trajectory is fine; the frame it was asked for cannot be built from the bodies known here. Pick another frame.\",\n };\n default:\n return {\n heading: \"NO PATH AVAILABLE\",\n detail: \"The integrated trajectory could not be sampled.\",\n };\n }\n}\n\n/**\n * Drawn in place of a path that is withheld: a heading and a sentence saying\n * why. It is announced as a status, not an alert, since a withheld path is\n * normal behaviour rather than a fault. `compact` shows the heading only, with\n * the sentence on hover, for a strip beside a readout or a caption.\n *\n * @category EmptyState\n */\nexport function TrajectoryWithheldNote({\n withheld,\n compact = false,\n}: Readonly<{ withheld: WithheldTrajectory; compact?: boolean }>) {\n const { heading, detail } = trajectoryWithheldCopy(withheld);\n return (\n <Tooltip text={compact ? detail : undefined} focusable>\n <Stack role=\"status\">\n <Text size=\"xs\">{heading}</Text>\n {!compact && (\n <Text level=\"muted\" size=\"xs\">\n {detail}\n </Text>\n )}\n </Stack>\n </Tooltip>\n );\n}\n","import styled from \"styled-components\";\n\n/**\n * A `span` that truncates to one line with an ellipsis, as a flex child that\n * fills the free width. The same behaviour as {@link RowName}, for a label\n * outside a {@link Row} (a grid cell, a card title). Its text is the neutral\n * text colour.\n *\n * @category Typography\n */\nexport const Truncate = styled.span`\n /* Its own theme colour rather than whatever the nearest element gives it, so no ancestor's default can reach the words. */\n color: var(--color-neutral-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n min-width: 0;\n`;\n","import {\n affineVectorUnitFor,\n type PointUnit,\n type Value,\n value,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport { useId, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\nimport { HeldFigure } from \"./HeldMark\";\nimport { JogWheel } from \"./JogWheel\";\nimport { MissionDateField, partsOfUt } from \"./MissionDateField\";\nimport { resolveCurrency, type UnitValue } from \"./readingCurrency\";\nimport { Slider } from \"./Slider\";\nimport { Stack } from \"./Stack\";\nimport { Text } from \"./Text\";\nimport type { FormatsFor } from \"./units\";\n\n/** Wide enough for `D6 04:17:36` in the caret label. */\nconst INSTANT_WHEEL_PX = 112;\n\n/**\n * Bounds for a POSITION slider, refused on a point-like unit: an instant can be\n * years out, and no range wide enough to reach it leaves useful precision. Use\n * {@link RateControl} for instants.\n *\n * @category Form\n */\nexport type SlidableRange<Unit extends string> = Unit extends PointUnit\n ? never\n : { min: number; max: number; step?: number };\n\n/**\n * A RATE wheel beside the field, on any unit. The handle's displacement is the\n * speed the value changes at, and it springs back to centre on release, so it\n * needs no bounds.\n *\n * @category Form\n */\nexport interface RateControl {\n /** One notch, in the unit a value of this kind MOVES BY: seconds for an instant, the field's own unit otherwise. */\n step: number;\n /** Notches per second at full displacement. The wheel's own default otherwise. */\n stepsPerSecond?: number;\n}\n\n/**\n * Props for {@link UnitInput}.\n *\n * @category Form\n */\nexport interface UnitInputProps<Unit extends string = string> {\n /**\n * The quantity being edited, or the whole `Reading` it arrived in. It\n * carries its own unit, exactly as {@link Unit}'s does; a held reading marks\n * the field's name and says so in it. Null or undefined shows an empty box.\n */\n value: UnitValue<NoInfer<Unit>> | null | undefined;\n /** The unit an emitted value carries, needed because there may be no value yet. */\n unit: Unit;\n /** Always a `Value`, never a number. */\n onChange: (next: Value<Unit>) => void;\n /** The control's visible name. Required: an `aria-label` alone leaves sighted operators guessing. */\n label: string;\n /**\n * Which rungs of this kind's ladder to type the value across, largest first,\n * one field per rung combining into a single value: `[\"h\", \"min\", \"s\"]` gives\n * hours, minutes and seconds that add up. Omit it for one field in `unit`.\n */\n rungs?: readonly FormatsFor<Unit>[];\n /** Supplying bounds adds a slider beside the field. See {@link SlidableRange}. */\n range?: SlidableRange<Unit>;\n /** Supplying a notch size adds a rate wheel beside the field. See {@link RateControl}. */\n rate?: RateControl;\n disabled?: boolean;\n}\n\n/** How many of `unit` one `symbol` is worth, via the registry: the ladder tables do not carry time. */\nfunction worth(symbol: string, unit: string): number {\n try {\n return (value as (u: string, n: number) => Value)(symbol, 1).in(unit)\n .magnitude;\n } catch {\n // NaN rather than 1: a wrong scale would silently add a number in the wrong unit to the total.\n return Number.NaN;\n }\n}\n\n/**\n * A total broken across rung sizes, largest first. Every rung but the last\n * takes a whole number and the last takes the remainder, fraction included, so\n * the fields add back up exactly.\n */\nfunction splitAcross(total: number, sizes: readonly number[]): number[] {\n let rest = total;\n return sizes.map((size, index) => {\n if (!Number.isFinite(size) || size === 0) return 0;\n const whole =\n index === sizes.length - 1 ? rest / size : Math.trunc(rest / size);\n rest -= whole * size;\n return whole;\n });\n}\n\n/**\n * A quantity, typed: the input counterpart of {@link Unit}, built from the same\n * `Value<Unit>`, {@link FormatsFor} and unit conversions. It emits a `Value`,\n * never a number, so the unit stays attached from the keystroke to the wire\n * boundary. An unfinished edit (an emptied box, a lone minus sign) is shown but\n * never emitted.\n *\n * - One number field in `unit` by default\n * - `rungs` splits the value across several fields that add up\n * - `range` adds a slider beside the field; `rate` adds a rate wheel\n * - A point-like unit such as `ut` is entered as a game-calendar date through\n * {@link MissionDateField}, and `rungs` is ignored\n *\n * @example\n * ```tsx\n * import { value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { Stack, UnitInput } from \"@ksp-gonogo/ui-kit\";\n * import { useState } from \"react\";\n *\n * function NodeEditor() {\n * const [prograde, setPrograde] = useState(value(\"m/s\", 0));\n * const [burn, setBurn] = useState(value(\"s\", 0));\n * const [nodeUt, setNodeUt] = useState(value(\"ut\", 0));\n * return (\n * <Stack>\n * <UnitInput\n * label=\"Prograde\"\n * unit=\"m/s\"\n * value={prograde}\n * onChange={setPrograde}\n * />\n * <UnitInput\n * // A duration typed as hours, minutes and seconds, emitted in seconds.\n * label=\"Burn time\"\n * unit=\"s\"\n * rungs={[\"h\", \"min\", \"s\"]}\n * value={burn}\n * onChange={setBurn}\n * />\n * <UnitInput\n * // A node time on the game calendar, nudged with a rate wheel.\n * label=\"Node UT\"\n * unit=\"ut\"\n * value={nodeUt}\n * rate={{ step: 60 }}\n * onChange={setNodeUt}\n * />\n * </Stack>\n * );\n * }\n * ```\n *\n * @category Form\n */\nexport function UnitInput<Unit extends string>({\n value: input,\n unit,\n onChange,\n label,\n rungs,\n range,\n rate,\n disabled,\n}: Readonly<UnitInputProps<Unit>>) {\n const id = useId();\n const { shown: current, held, caption } = resolveCurrency<Unit>(input);\n const name = held ? (\n <HeldFigure caption={caption}>{label}</HeldFigure>\n ) : (\n label\n );\n const bounds = range as\n | { min: number; max: number; step?: number }\n | undefined;\n // NaN, not zero, when nothing has been read: an absent value renders as an empty box.\n const magnitude = current ? current.magnitude : Number.NaN;\n // Keyed by field index (0 for the single field, the rung index in a rung row).\n const [typing, setTyping] = useState<Readonly<Record<number, Typing>>>({});\n const typed = (index: number): Typing | null => typing[index] ?? null;\n const type = (index: number, next: Typing) =>\n setTyping((held) => ({ ...held, [index]: next }));\n\n const wheel = rate ? (\n <Stack gap=\"related-packed\">\n <JogWheel\n mode=\"rate\"\n ariaLabel={`${label} rate`}\n value={magnitude}\n step={rate.step}\n stepsPerSecond={rate.stepsPerSecond}\n // A wheel against a value never read would dial away from an instant nobody stated.\n disabled={disabled || !Number.isFinite(magnitude)}\n format={isInstant(unit) ? caretDate : undefined}\n // A calendar instant is eleven characters, which the default box would clip.\n width={isInstant(unit) ? INSTANT_WHEEL_PX : undefined}\n onChange={(next) => onChange(value(unit, next))}\n />\n {/* A notch on an instant is an interval, so the notch unit is stated rather than inferred. */}\n <Text\n level=\"faint\"\n size=\"sm\"\n >{`${rate.step} ${movesBy(unit)} / notch`}</Text>\n </Stack>\n ) : null;\n\n if (isInstant(unit)) {\n // An instant is entered on the game calendar, which is its own rungs, so `rungs` is ignored here.\n return (\n <Control>\n {/* The date fields' group carries the name for assistive tech, so the visible one is not read twice. */}\n <GroupName aria-hidden=\"true\">{name}</GroupName>\n <Stack gap=\"related-dense\">\n <MissionDateField\n label={held && caption !== null ? `${label}, ${caption}` : label}\n value={Number.isFinite(magnitude) ? magnitude : null}\n disabled={disabled}\n // The rate wheel replaces the coarse nudge steps where there is one.\n steps={rate ? [] : undefined}\n onChange={(ut) => onChange(value(unit, ut))}\n />\n {wheel}\n </Stack>\n </Control>\n );\n }\n\n if (rungs && rungs.length > 0) {\n const sizes = rungs.map((symbol) => worth(String(symbol), unit));\n const parts = splitAcross(magnitude, sizes);\n const emit = (index: number, text: string) => {\n const amount = readNumber(text);\n if (amount === undefined) {\n // An unfinished edit is shown but never committed: an emptied box is not zero.\n type(index, { text, against: parts[index] });\n return;\n }\n const next = parts.slice();\n next[index] = amount;\n const total = next.reduce(\n (sum, each, i) =>\n Number.isFinite(sizes[i]) ? sum + each * sizes[i] : sum,\n 0,\n );\n type(index, { text, against: amount });\n onChange(value(unit, total));\n };\n\n return (\n <Control>\n <GroupName id={`${id}-label`}>{name}</GroupName>\n <RungRow role=\"group\" aria-labelledby={`${id}-label`}>\n {rungs.map((symbol, index) => (\n <RungCell key={String(symbol)}>\n <RungField\n type=\"number\"\n disabled={disabled}\n aria-label={`${label} ${String(symbol)}`}\n value={fieldText(typed(index), parts[index])}\n onChange={(event) => emit(index, event.target.value)}\n />\n <UnitSymbol aria-hidden=\"true\">{String(symbol)}</UnitSymbol>\n </RungCell>\n ))}\n </RungRow>\n {wheel}\n </Control>\n );\n }\n\n const emit = (text: string) => {\n const amount = readNumber(text);\n type(0, { text, against: amount ?? magnitude });\n if (amount !== undefined) {\n onChange(value(unit, amount));\n }\n };\n\n return (\n <Control>\n <FieldName htmlFor={id}>{name}</FieldName>\n <ValueRow>\n <SingleField\n id={id}\n type=\"number\"\n disabled={disabled}\n min={bounds?.min}\n max={bounds?.max}\n step={bounds?.step}\n value={fieldText(typed(0), magnitude)}\n onChange={(event) => emit(event.target.value)}\n />\n <UnitSymbol aria-hidden=\"true\">{unit}</UnitSymbol>\n </ValueRow>\n {bounds ? (\n <Slider\n disabled={disabled}\n aria-label={`${label} slider`}\n min={bounds.min}\n max={bounds.max}\n step={bounds.step ?? (bounds.max - bounds.min) / 100}\n // Parked at the low end while nothing has been read, rather than showing a handle at a position no value put it at.\n value={Number.isFinite(magnitude) ? magnitude : bounds.min}\n // A slider is never mid-edit, so it bypasses the typing buffer.\n onChange={(event) =>\n onChange(value(unit, readNumber(event.target.value) ?? bounds.min))\n }\n />\n ) : null}\n {wheel}\n </Control>\n );\n}\n\n/**\n * A field's in-progress text, and the magnitude it was typed against. Once the\n * value moves from anywhere else it no longer matches `against`, and the field\n * goes back to showing the value.\n */\ninterface Typing {\n text: string;\n against: number;\n}\n\n/** What a field shows: the text being typed, else the value it holds, else nothing. */\nfunction fieldText(typing: Typing | null, magnitude: number): string {\n if (typing !== null && Object.is(typing.against, magnitude)) {\n return typing.text;\n }\n return Number.isFinite(magnitude) ? String(round(magnitude)) : \"\";\n}\n\n/** The unit a value of this kind is moved by: its affine companion vector, else itself. */\nfunction movesBy(unit: string): string {\n return affineVectorUnitFor(unit) ?? unit;\n}\n\n/** True when this kind names an instant rather than an amount. */\nfunction isInstant(unit: string): boolean {\n return affineVectorUnitFor(unit) === \"s\";\n}\n\n/** An instant on the wheel's caret: day and clock only, since the fields beside it carry the year. */\nfunction caretDate(ut: number): string {\n if (!Number.isFinite(ut)) return \"\";\n const { day, hour, minute, second } = partsOfUt(ut);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return `D${day} ${pad(hour)}:${pad(minute)}:${pad(second)}`;\n}\n\n/** A typed field's number, or undefined while it holds no digit yet (empty, a lone minus). */\nfunction readNumber(text: string): number | undefined {\n const parsed = Number.parseFloat(text);\n return Number.isFinite(parsed) && /\\d/.test(text) ? parsed : undefined;\n}\n\n/** Trims float dust so a value that is rendered and typed back does not grow digits. */\nfunction round(n: number): number {\n return Number.isFinite(n) ? Math.round(n * 1e6) / 1e6 : 0;\n}\n\nconst Control = styled.div`\n display: flex;\n flex-direction: column;\n gap: var(--gap-field-label-compact);\n`;\n\nconst nameStyles = `\n font-size: var(--font-size-caption);\n color: var(--color-text-muted);\n letter-spacing: 0.06em;\n text-transform: uppercase;\n`;\n\nconst FieldName = styled.label`\n ${nameStyles}\n`;\n\nconst GroupName = styled.span`\n ${nameStyles}\n`;\n\nconst ValueRow = styled.div`\n display: grid;\n grid-template-columns: 1fr auto;\n align-items: center;\n gap: var(--gap-value-tag);\n`;\n\nconst SingleField = styled.input`\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-subtle);\n color: var(--color-text-primary);\n font-size: var(--font-size-value);\n padding: var(--inset-field-compact);\n border-radius: var(--radius-regular);\n text-align: right;\n font-variant-numeric: tabular-nums;\n min-width: 0;\n\n ${focusRing}\n`;\n\nconst UnitSymbol = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-faint);\n`;\n\nconst RungRow = styled.div`\n display: flex;\n gap: var(--gap-unit-parts);\n`;\n\nconst RungCell = styled.div`\n display: grid;\n grid-template-columns: 1fr auto;\n align-items: center;\n gap: var(--gap-unit-suffix);\n`;\n\nconst RungField = styled(SingleField)`\n width: 4.5em;\n`;\n","import { useEffect, useState } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\n/**\n * Whether the viewer has asked the OS to reduce motion, tracked live. For a\n * decision a stylesheet cannot reach (whether to replay a one-shot pulse, or\n * render a transition at all); a plain transition guards itself with a CSS\n * media query.\n *\n * Returns `false` when `matchMedia` is unavailable, so motion is suppressed\n * only on an explicit opt-out.\n *\n * @category Theme\n */\nexport function usePrefersReducedMotion(): boolean {\n const [reduced, setReduced] = useState(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return false;\n return window.matchMedia(QUERY).matches;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return;\n const mql = window.matchMedia(QUERY);\n const onChange = () => setReduced(mql.matches);\n onChange();\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return reduced;\n}\n","/**\n * The vessel as it is drawn on an orbit or a map. Shape and fill say how its\n * position is known, and a green outline says it is the vessel. One\n * definition, read by the SVG face and the canvas painter alike.\n */\nimport { RECKONING_MARK } from \"./reckoningMarkSpec\";\n\n/**\n * How the position a vessel is drawn at is known: `current` is a reading of\n * now, `held` is the last observation kept past its time, `modelled` is where a\n * model carries the craft to now, and `lost` is the last place of a craft that\n * has been given up on.\n *\n * @category Unit\n */\nexport type VesselMarkState = \"current\" | \"held\" | \"modelled\" | \"lost\";\n\n/** Half the held square's side, measured to the middle of its outline. */\nconst SQUARE_HALF = 0.86;\n/** The modelled triangle's corners, point up, as `[x, y]` pairs with y down. */\nconst TRIANGLE = [\n [0, -1.15],\n [1.05, 0.8],\n [-1.05, 0.8],\n] as const;\n/** The width of the outline round a held, modelled or lost shape. */\nconst OUTLINE_WIDTH = 0.3;\n\n/**\n * The vessel mark's geometry, in units of the radius it is drawn at, and its\n * hues. A current vessel is a circle in the vessel's green. A held one is a\n * square in the held hue and a modelled one a triangle, point up, in the\n * modelled hue, each outlined in the vessel's green, so the shape is never the\n * only thing that says which craft it is and the hue never the only thing that\n * says how it is known. A lost one is the held square emptied: the outline\n * alone, in the no-go hue, for a last place no longer known to be right. All\n * of them fill the same footprint.\n *\n * @category Unit\n */\nexport const VESSEL_MARK = {\n /** The vessel's green, as the theme token's name, for a canvas painter that resolves it itself. */\n cssVar: \"--color-accent-fg\",\n /** The vessel's green, as a CSS `var()` of the theme token. */\n color: \"var(--color-accent-fg)\",\n /** The vessel's green where the token cannot be read. */\n fallback: \"rgb(0 255 136)\",\n /** The hue of a lost vessel's outline. */\n lost: {\n /** The token's name. */\n cssVar: \"--color-nogo-mark\",\n /** The hue as a CSS `var()` of the token. */\n color: \"var(--color-nogo-mark)\",\n /** The hue where the token cannot be read. */\n fallback: \"rgb(255 77 77)\",\n },\n /** Half the held square's side, measured to the middle of its outline. */\n squareHalf: SQUARE_HALF,\n /** The modelled triangle's corners, point up, as `[x, y]` pairs with y down. */\n triangle: TRIANGLE,\n /** The width of the outline round a held, modelled or lost shape. */\n outlineWidth: OUTLINE_WIDTH,\n /**\n * The width of each of the keyline's two rings. A dark ring hugs the mark and\n * a light ring hugs the dark one, so the mark's edge always meets the dark\n * ring, whatever it is drawn on, and one ring or the other stands clear of\n * any ground: the light one on a dark ground, the dark one on a light ground.\n */\n keylineWidth: 0.2,\n /**\n * How far from its centre the largest shape reaches, outline included: its\n * farthest corner and half the outline round it. Two marks nearer than twice\n * this overlap.\n */\n reach:\n Math.max(\n Math.hypot(SQUARE_HALF, SQUARE_HALF),\n ...TRIANGLE.map(([x, y]) => Math.hypot(x, y)),\n ) +\n OUTLINE_WIDTH / 2,\n} as const;\n\n/** The mark is a drawing: the widget that places it says in words what the position is. */\nconst HIDDEN = { \"aria-hidden\": true } as const;\n\n/** The square's corners or the modelled triangle's, about a centre, at radius `r`. */\nfunction cornersOf(\n state: \"held\" | \"modelled\" | \"lost\",\n x: number,\n y: number,\n r: number,\n): [number, number][] {\n const half = VESSEL_MARK.squareHalf;\n const unit: readonly (readonly [number, number])[] =\n state === \"modelled\"\n ? VESSEL_MARK.triangle\n : [\n [-half, -half],\n [half, -half],\n [half, half],\n [-half, half],\n ];\n return unit.map(([px, py]) => [x + px * r, y + py * r]);\n}\n\n/** The keyline's two rings, outermost first, which is the order they are drawn in: each later one covers the inside of the one before. */\nconst KEYLINE_RINGS = [\n { ring: \"light\", color: \"rgb(250 250 250)\", widths: 2 },\n { ring: \"dark\", color: \"rgb(5 5 5)\", widths: 1 },\n] as const;\n\n/** The stroke that reaches `widths` ring widths beyond a mark's edge, half of any stroke lying inside the shape and under what is drawn over it. */\nfunction keylineStroke(\n state: VesselMarkState,\n r: number,\n widths: number,\n): number {\n const outline = state === \"current\" ? 0 : VESSEL_MARK.outlineWidth;\n return r * (outline + 2 * widths * VESSEL_MARK.keylineWidth);\n}\n\n/**\n * The props of {@link VesselMarkSvg}.\n *\n * @category Unit\n */\nexport interface VesselMarkSvgProps {\n /** The centre's x, in the diagram's own units. */\n x: number;\n /** The centre's y, in the diagram's own units, y down. */\n y: number;\n /** The radius of the current vessel's circle, in the diagram's own units. The held and modelled shapes fill the same footprint. */\n r: number;\n /** How the position is known. Absent, `\"current\"`: the green circle. */\n state?: VesselMarkState;\n /**\n * Draw the keyline round the mark: a dark ring and a light ring, so the mark\n * has an edge on any ground. For a mark that sits on a picture (a map, an\n * image) rather than a flat ground; on a flat ground the mark's own hues\n * already stand clear.\n */\n keyline?: boolean;\n}\n\n/**\n * The vessel mark as SVG shapes centred on a point, the triangle always point\n * up. Draw it outside any rotated group, so the shape does not turn with the\n * orbit. The group carries the point as its `translate`.\n *\n * @category Unit\n */\nexport function VesselMarkSvg({\n x,\n y,\n r,\n state = \"current\",\n keyline = false,\n}: Readonly<VesselMarkSvgProps>) {\n const corners =\n state === \"current\"\n ? \"\"\n : cornersOf(state, 0, 0, r)\n .map(([px, py]) => `${px},${py}`)\n .join(\" \");\n return (\n <g data-vessel-mark={state} transform={`translate(${x} ${y})`} {...HIDDEN}>\n {keyline &&\n KEYLINE_RINGS.map(({ ring, color, widths }) => {\n const border = {\n \"data-vessel-keyline\": ring,\n fill: \"none\",\n stroke: color,\n strokeWidth: keylineStroke(state, r, widths),\n strokeLinejoin: \"round\" as const,\n };\n return state === \"current\" ? (\n <circle key={ring} r={r} {...border} />\n ) : (\n <polygon key={ring} points={corners} {...border} />\n );\n })}\n {state === \"current\" ? (\n <circle r={r} fill={VESSEL_MARK.color} />\n ) : (\n <polygon\n points={corners}\n fill={state === \"lost\" ? \"none\" : RECKONING_MARK[state].color}\n stroke={state === \"lost\" ? VESSEL_MARK.lost.color : VESSEL_MARK.color}\n strokeWidth={r * VESSEL_MARK.outlineWidth}\n strokeLinejoin=\"round\"\n />\n )}\n </g>\n );\n}\n\n/** A CSS custom property as the canvas needs it: a resolved colour, since a canvas cannot read `var()`. */\nfunction resolvedColor(\n canvas: HTMLCanvasElement,\n cssVar: string,\n fallback: string,\n): string {\n const v = getComputedStyle(canvas).getPropertyValue(cssVar).trim();\n return v || fallback;\n}\n\n/** Traces a mark's silhouette as the current path. */\nfunction traceMark(\n ctx: CanvasRenderingContext2D,\n state: VesselMarkState,\n x: number,\n y: number,\n radius: number,\n): void {\n ctx.beginPath();\n if (state === \"current\") {\n ctx.arc(x, y, radius, 0, Math.PI * 2);\n return;\n }\n cornersOf(state, x, y, radius).forEach(([px, py], i) => {\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n });\n ctx.closePath();\n}\n\n/** Paints the keyline's two rings under where the mark is about to be painted. */\nfunction paintKeyline(\n ctx: CanvasRenderingContext2D,\n state: VesselMarkState,\n x: number,\n y: number,\n radius: number,\n): void {\n ctx.save();\n ctx.lineJoin = \"round\";\n for (const { color, widths } of KEYLINE_RINGS) {\n traceMark(ctx, state, x, y, radius);\n ctx.strokeStyle = color;\n ctx.lineWidth = keylineStroke(state, radius, widths);\n ctx.stroke();\n }\n ctx.restore();\n}\n\n/**\n * Options for {@link paintVesselMark} and {@link paintVesselPositions}.\n *\n * @category Unit\n */\nexport interface PaintVesselMarkOptions {\n /** Draw the keyline round the mark, a dark ring and a light ring, so it has an edge on any ground: see `VesselMarkSvgProps.keyline`. */\n keyline?: boolean;\n}\n\n/**\n * Paints the vessel mark on a canvas, centred on a point in canvas pixels, the\n * triangle always point up. `radius` is the current vessel's circle, 4 px\n * unless given.\n *\n * @category Unit\n */\nexport function paintVesselMark(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D,\n state: VesselMarkState,\n x: number,\n y: number,\n radius = 4,\n { keyline = false }: PaintVesselMarkOptions = {},\n): void {\n if (keyline) paintKeyline(ctx, state, x, y, radius);\n const green = resolvedColor(canvas, VESSEL_MARK.cssVar, VESSEL_MARK.fallback);\n ctx.save();\n traceMark(ctx, state, x, y, radius);\n if (state === \"current\") {\n ctx.fillStyle = green;\n ctx.fill();\n } else {\n if (state !== \"lost\") {\n const spec = RECKONING_MARK[state];\n ctx.fillStyle = resolvedColor(canvas, spec.cssVar, spec.fallback);\n ctx.fill();\n }\n ctx.strokeStyle =\n state === \"lost\"\n ? resolvedColor(\n canvas,\n VESSEL_MARK.lost.cssVar,\n VESSEL_MARK.lost.fallback,\n )\n : green;\n ctx.lineWidth = radius * VESSEL_MARK.outlineWidth;\n ctx.lineJoin = \"round\";\n ctx.stroke();\n }\n ctx.restore();\n}\n\n/**\n * Where a vessel is drawn, in canvas pixels, one point for each way its\n * position is known.\n *\n * @category Unit\n */\nexport type VesselPositions = Partial<\n Record<VesselMarkState, { x: number; y: number }>\n>;\n\n/**\n * Paints every position given with its own vessel mark, the one nearest to now\n * on top. A held position and a modelled one are joined by a faint dashed\n * track, so the pair reads as \"was here, modelled to be there\".\n *\n * @category Unit\n */\nexport function paintVesselPositions(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D,\n positions: VesselPositions,\n radius = 4,\n options: PaintVesselMarkOptions = {},\n): void {\n const { held, modelled } = positions;\n if (held !== undefined && modelled !== undefined) {\n ctx.save();\n ctx.globalAlpha = 0.45;\n ctx.strokeStyle = resolvedColor(\n canvas,\n RECKONING_MARK.modelled.cssVar,\n RECKONING_MARK.modelled.fallback,\n );\n ctx.lineWidth = 1;\n ctx.setLineDash([2, 3]);\n ctx.beginPath();\n ctx.moveTo(held.x, held.y);\n ctx.lineTo(modelled.x, modelled.y);\n ctx.stroke();\n ctx.restore();\n }\n for (const state of [\"lost\", \"held\", \"current\", \"modelled\"] as const) {\n const at = positions[state];\n if (at !== undefined)\n paintVesselMark(canvas, ctx, state, at.x, at.y, radius, options);\n }\n}\n","import type { MeterEntry } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { CSSProperties } from \"react\";\nimport { useContributions } from \"./contributionsRead\";\nimport { Meter, MeterStack } from \"./Meter\";\n\n/**\n * Props for {@link WidgetMeters}.\n *\n * @category Meter\n */\nexport interface WidgetMetersProps {\n /**\n * Render only the meters addressed at this row (a kerbal's name, a part id).\n * Omit in a whole-widget stack, which then shows only entries carrying no\n * `row` of their own. A row-addressed meter never appears in a stack that\n * names no row.\n */\n row?: string;\n /**\n * Inline style for the stack (an indent under a row, a max width). It sits\n * on the stack itself, so a row with no meters leaves no padding behind.\n */\n style?: CSSProperties;\n}\n\n/**\n * Every meter contributed to the mounting widget's `${componentId}.meters`\n * slot, drawn as a {@link MeterStack} of {@link Meter}s.\n *\n * Renders nothing when nothing is contributed, so a host can place it\n * unconditionally, one per row.\n *\n * An entry whose `value` is a whole `Reading` is passed to {@link Meter} as\n * is, so its band and held state are drawn the same whichever Uplink sent the\n * entry.\n *\n * @example Under each crew row, the meters Uplinks addressed to that kerbal\n * ```tsx\n * <li key={name}>\n * {name}\n * <WidgetMeters row={name} />\n * </li>\n * ```\n *\n * @category Meter\n */\nexport function WidgetMeters({ row, style }: WidgetMetersProps) {\n const entries = useContributions(\"meters\") as readonly MeterEntry[];\n const mine = entries.filter((entry) => entry.row === row);\n if (mine.length === 0) return null;\n\n return (\n <MeterStack style={style} role=\"group\" aria-label=\"meters\">\n {mine.map((entry) => (\n <Meter\n key={entry.id}\n label={entry.label}\n value={entry.value}\n tone={entry.tone}\n valueLabel={entry.valueLabel}\n />\n ))}\n </MeterStack>\n );\n}\n","import type { WidgetScope } from \"@ksp-gonogo/sitrep-sdk\";\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\n// Re-exported from the sdk so a widget merging into core and an Uplink merging into the sdk reach the one interface.\nexport type { WidgetScope, WidgetScopeRegistry } from \"@ksp-gonogo/sitrep-sdk\";\n\nconst WidgetScopeContext = createContext<{\n widget: string;\n scope: unknown;\n} | null>(null);\n\n/**\n * Publishes what this widget instance is currently focused on (the selected\n * resource, the followed body) to augments rendered in any of its slots, which\n * read it with {@link useWidgetScope}. It carries one scope value; several\n * unrelated fields belong in a slot's own props type instead.\n *\n * `widget` is the publishing widget's registered component id. It types\n * `scope` against that widget's `WidgetScopeRegistry` entry, and a reader only\n * receives the scope when it names the same widget.\n *\n * @category Extensions\n */\nexport function WidgetScopeProvider<Widget extends string>({\n widget,\n scope,\n children,\n}: {\n widget: Widget;\n scope: WidgetScope<Widget>;\n children?: ReactNode;\n}) {\n const value = useMemo(() => ({ widget, scope }), [widget, scope]);\n return (\n <WidgetScopeContext.Provider value={value}>\n {children}\n </WidgetScopeContext.Provider>\n );\n}\n\n/**\n * The current scope of the widget named by `componentId`, typed from its\n * `WidgetScopeRegistry` entry. `undefined` when that widget publishes none, or\n * when the augment is rendered outside it (in a test, say), so an augment must\n * handle `undefined`.\n *\n * @example\n * ```tsx\n * function StorageSection() {\n * const scope = useWidgetScope(\"power-systems\");\n * if (scope === undefined) return null;\n * return <ResourceDetail resource={scope.resource} />;\n * }\n * ```\n *\n * @category Extensions\n */\nexport function useWidgetScope<Widget extends string>(\n componentId: Widget,\n): WidgetScope<Widget> | undefined {\n const published = useContext(WidgetScopeContext);\n if (!published || published.widget !== componentId) return undefined;\n return published.scope as WidgetScope<Widget>;\n}\n","/**\n * The Topic-declaring fields of a widget definition that\n * {@link widgetDeclaredTopics} and {@link widgetDrawnFields} read. Any\n * `ComponentDefinition` satisfies it.\n *\n * @category Extensions\n */\nexport interface WidgetTopicDeclaration {\n channels?: readonly string[];\n optionalChannels?: readonly string[];\n fields?: readonly string[];\n dataRequirements?: readonly string[];\n}\n\n/**\n * Every channel a widget declares it mounts on: its `channels`,\n * `optionalChannels` and `dataRequirements` joined, without duplicates. The\n * dashboard derives a widget's blackout badge from these. Empty for an\n * undefined definition.\n *\n * @category Extensions\n */\nexport function widgetDeclaredTopics(\n def: WidgetTopicDeclaration | undefined,\n): readonly string[] {\n if (!def) return [];\n return dedupe([\n ...(def.channels ?? []),\n ...(def.optionalChannels ?? []),\n ...(def.dataRequirements ?? []),\n ]);\n}\n\n/**\n * Every field a widget declares it draws, as opposed to the channels it needs\n * live to render at all: its `fields` without duplicates, or, when it\n * declares none, its {@link widgetDeclaredTopics}. The dashboard matches alarms\n * to widgets against this list.\n *\n * @category Extensions\n */\nexport function widgetDrawnFields(\n def: WidgetTopicDeclaration | undefined,\n): readonly string[] {\n if (!def) return [];\n if (def.fields && def.fields.length > 0) return dedupe(def.fields);\n return widgetDeclaredTopics(def);\n}\n\nfunction dedupe(values: readonly string[]): readonly string[] {\n return [...new Set(values)];\n}\n"],"mappings":"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","names":["styled","styled","jsx","styled","jsx","jsxs","GroupHeader","styled","useId","styled","css","jsx","jsxs","styled","css","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","open","jsx","jsxs","useId","device","styled","jsx","jsxs","jsx","jsxs","value","styled","Fragment","jsx","jsxs","styled","styled","jsx","jsxs","styled","styled","jsxs","Fragment","jsx","jsxs","styled","styled","jsx","styled","styled","jsx","ButtonGroup","styled","styled","styled","useCallback","useEffect","useRef","useState","styled","css","keyframes","forwardRef","useRef","useState","styled","jsx","jsxs","FitLabelButton","styled","styled","keyframes","jsx","jsx","jsxs","styled","Fragment","jsx","jsxs","useState","useRef","useEffect","useCallback","keyframes","styled","css","useEffect","useId","styled","css","jsx","jsxs","value","useEffect","useId","styled","css","styled","jsx","jsxs","styled","useEffect","useId","useId","useEffect","useId","styled","css","jsx","jsxs","useId","styled","css","styled","css","jsx","jsxs","jsx","styled","jsx","useCallback","useEffect","useId","useRef","useState","styled","Fragment","jsx","jsxs","value","useState","useRef","useId","useCallback","useEffect","styled","styled","css","jsx","jsxs","styled","css","Fragment","useState","styled","useEffect","useRef","useState","styled","jsx","jsxs","useState","Fragment","styled","styled","Fragment","jsx","jsxs","value","styled","useId","useRef","useState","styled","css","Fragment","jsx","jsxs","useState","useId","useRef","styled","css","styled","css","jsx","jsxs","value","styled","css","styled","jsx","styled","useId","useState","styled","Fragment","jsx","jsxs","useState","useId","styled","styled","jsx","jsx","useCallback","useLayoutEffect","useRef","useState","styled","jsx","useState","useRef","place","useCallback","useLayoutEffect","styled","styled","jsx","styled","jsx","useCallback","useEffect","useId","useRef","useState","styled","Fragment","jsx","jsxs","useState","useRef","useId","useCallback","useEffect","styled","styled","jsx","styled","useEffect","useRef","useState","styled","jsx","jsxs","value","useRef","useState","useEffect","styled","styled","jsx","jsxs","cut","styled","styled","jsx","jsxs","value","styled","forwardRef","Fragment","jsx","jsxs","bandIn","useLayoutEffect","useRef","styled","css","styled","jsx","jsxs","styled","Fragment","jsx","jsxs","value","bandIn","useRef","useLayoutEffect","place","styled","css","styled","Fragment","jsx","jsxs","styled","value","value","useId","useState","jsx","jsxs","value","useId","useState","createContext","useCallback","useContext","useEffect","useId","useMemo","useRef","useState","createPortal","styled","useEffect","useMemo","useRef","Fragment","jsx","jsxs","useEffect","value","useRef","useMemo","Fragment","jsx","jsxs","createContext","useState","useCallback","value","useMemo","useContext","useRef","useEffect","useId","createPortal","styled","useEffect","useRef","useState","styled","Fragment","jsx","jsxs","styled","useRef","useState","useEffect","jsx","jsxs","value","styled","jsx","jsxs","value","styled","styled","css","jsx","styled","css","styled","css","jsx","styled","css","jsx","value","styled","css","jsx","styled","css","forwardRef","styled","jsx","forwardRef","Slider","styled","styled","jsx","jsxs","styled","Fragment","jsx","useCallback","useId","styled","jsx","jsxs","value","useId","useCallback","styled","jsx","useCallback","useEffect","useEffect","useCallback","useCallback","useId","useLayoutEffect","useMemo","useRef","useState","styled","Fragment","jsx","jsxs","useId","useMemo","useState","useCallback","useRef","useLayoutEffect","styled","bandIn","useEffect","useRef","useState","styled","Fragment","jsx","jsxs","value","useRef","useState","useEffect","h","bandIn","styled","useId","jsx","jsxs","value","useId","jsx","jsxs","styled","value","useId","useState","styled","jsx","jsxs","value","useId","useState","held","emit","styled","useEffect","useState","jsx","jsxs","resolvedColor","jsx","createContext","useContext","useMemo","jsx","value"]}
|
|
1
|
+
{"version":3,"sources":["../../theme/src/DefaultThemeProvider.tsx","../../theme/src/defaultDarkTheme.ts","../src/ActionMenu.tsx","../src/Combobox.tsx","../src/EmptyState.tsx","../src/AudioInputPicker.tsx","../src/StatusIndicator.tsx","../src/useAudioInput.ts","../src/audioCaptureSupport.ts","../src/Switch.tsx","../src/AugmentSettingsPanel.tsx","../src/AutoEmptyState.tsx","../src/AvatarStack.tsx","../src/anchoredPosition.ts","../src/Band.tsx","../src/Block.tsx","../src/SubjectHeading.tsx","../src/Box.tsx","../src/ButtonGroup.tsx","../src/Card.tsx","../src/CommandButton/CommandButton.tsx","../src/CommandDelay/commandOutcomes.ts","../src/FitLabelButton.tsx","../src/CommandButton/InFlightFace.tsx","../src/Spinner.tsx","../src/CommandDelay/CommandGroup.tsx","../src/CommandDelay/SignalDelayBadge.tsx","../src/CommandDelay/useRailEntry.ts","../src/ComposerBar.tsx","../src/ConsoleFrame.tsx","../src/Console.tsx","../src/ContainerBreak.tsx","../src/configEqual.ts","../src/DataKeyPicker.tsx","../src/DataLine.tsx","../src/DataTable.tsx","../src/inlineOverflow.tsx","../src/Dial.tsx","../src/Disclosure.tsx","../src/DivergingBar.tsx","../src/Divider.tsx","../src/ExpandableText.tsx","../src/ephemerisFigure.ts","../src/Fill.tsx","../src/FilterList.tsx","../src/Floating.tsx","../src/Grid.tsx","../src/HeldBadge.tsx","../src/HoverCard.tsx","../src/Inline.tsx","../src/JogWheel.tsx","../src/LineGraph.tsx","../src/reckoningBasisPhrase.ts","../src/LockMark.tsx","../src/MarkerIcons.tsx","../src/Meter.tsx","../src/BarTrack.tsx","../src/MissionDate.tsx","../src/MissionDateField.tsx","../src/Modal.tsx","../src/ModalSaveBar.tsx","../src/Notice.tsx","../src/reckoningMarkDraw.tsx","../src/PlotCrosshair.tsx","../src/plotReadoutPlacement.ts","../src/ReadFrameControl.tsx","../src/ReadOnlyField.tsx","../src/Readout.tsx","../src/Row.tsx","../src/resourceColor.ts","../src/SelectableRow.tsx","../src/Slider.tsx","../src/Stat.tsx","../src/StatContributions.tsx","../src/Stepper.tsx","../src/StreamStatusBadge.tsx","../src/sizeDelta.ts","../src/Tabs.tsx","../src/Tape.tsx","../src/TextField.tsx","../src/TrajectoryWithheldNote.tsx","../src/Truncate.tsx","../src/UnitInput.tsx","../src/usePrefersReducedMotion.ts","../src/vesselMark.tsx","../src/WidgetMeters.tsx","../src/WidgetScope.tsx","../src/widgetDeclaredTopics.ts","../src/wrappedMap.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { ThemeProvider } from \"styled-components\";\nimport { defaultDarkTheme } from \"./defaultDarkTheme\";\n\n/**\n * Props for {@link DefaultThemeProvider}.\n *\n * @category Theme\n */\nexport interface DefaultThemeProviderProps {\n children?: ReactNode;\n}\n\n/**\n * Mounts {@link defaultDarkTheme} in a styled-components `ThemeProvider`, the\n * same theme the app mounts.\n *\n * Kit components read `theme.colors` and throw without a theme in scope, so\n * wrap anything that renders them outside the app (a test, a story, a\n * standalone page) in this. The theme's values are CSS variables, which\n * `@ksp-gonogo/ui-kit/tokens.css` defines: import that sheet once, or every\n * colour and size is unset.\n *\n * @example\n * ```tsx\n * import \"@ksp-gonogo/ui-kit/tokens.css\";\n * import { value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { DefaultThemeProvider, Meter } from \"@ksp-gonogo/ui-kit\";\n * import { render } from \"@testing-library/react\";\n *\n * render(\n * <DefaultThemeProvider>\n * <Meter label=\"Charge\" value={value(\"ratio\", 0.4)} />\n * </DefaultThemeProvider>,\n * );\n * ```\n *\n * @category Theme\n */\nexport function DefaultThemeProvider({\n children,\n}: Readonly<DefaultThemeProviderProps>) {\n return <ThemeProvider theme={defaultDarkTheme}>{children}</ThemeProvider>;\n}\n","import type { UiKitTheme } from \"./theme\";\n\n/**\n * `default-dark`: the built-in mission-control theme, and the one the app\n * mounts. Every colour and size is a CSS variable string (such as\n * `var(--color-text-primary)`), so responsive overrides such as larger type on\n * a touch screen still apply when a component reads the theme. The variables\n * are defined by `@ksp-gonogo/ui-kit/tokens.css`, which has to be imported\n * once for any of them to have a value.\n *\n * @category Theme\n */\nexport const defaultDarkTheme: UiKitTheme = {\n colors: {\n text: {\n primary: \"var(--color-text-primary)\",\n muted: \"var(--color-text-muted)\",\n dim: \"var(--color-text-dim)\",\n faint: \"var(--color-text-faint)\",\n inverse: \"var(--color-text-inverse)\",\n },\n surface: {\n app: \"var(--color-surface-app)\",\n panel: \"var(--color-surface-panel)\",\n raised: \"var(--color-surface-raised)\",\n sunken: \"var(--color-surface-sunken)\",\n },\n border: {\n subtle: \"var(--color-border-subtle)\",\n strong: \"var(--color-border-strong)\",\n },\n accent: {\n fg: \"var(--color-accent-fg)\",\n bg: \"var(--color-accent-bg)\",\n },\n focus: \"var(--color-focus)\",\n },\n typography: {\n family: {\n mono: \"var(--font-family-mono)\",\n },\n size: {\n xs: \"var(--font-size-xs)\",\n sm: \"var(--font-size-sm)\",\n base: \"var(--font-size-base)\",\n lg: \"var(--font-size-lg)\",\n },\n weight: {\n regular: 400,\n bold: 700,\n },\n letterSpacing: {\n tight: \"0.05em\",\n label: \"0.1em\",\n wide: \"0.15em\",\n body: \"0\",\n },\n },\n borders: {\n subtle: \"1px solid var(--color-border-subtle)\",\n strong: \"1px solid var(--color-border-strong)\",\n },\n};\n","import type { CSSProperties, ReactNode } from \"react\";\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { groupComboboxOptions } from \"./Combobox\";\nimport { EmptyState } from \"./EmptyState\";\nimport { focusRingInset } from \"./focusRing\";\n\n/**\n * One action in an {@link ActionMenu}.\n *\n * @category Floating\n */\nexport interface ActionMenuItem {\n /** Stable id handed back to `onSelect`. */\n key: string;\n /** The item's visible text, and its accessible name unless `aria-label` is set. */\n label: string;\n /** Optional grouping header; ungrouped items collapse into one flat list. */\n group?: string;\n /** Present but not currently firable: rendered dimmed and inert, still reachable. */\n disabled?: boolean;\n /** Overrides the accessible name when `label` alone is ambiguous out of context. */\n \"aria-label\"?: string;\n}\n\n/**\n * Props for {@link ActionMenu}.\n *\n * @category Floating\n */\nexport interface ActionMenuProps {\n /** The actions, in order. Items sharing a `group` are listed under that heading. */\n items: readonly ActionMenuItem[];\n /** Fired with the item's `key`. A disabled item never fires. */\n onSelect: (key: string) => void;\n /** Escape, Tab, or an outside pointer press. The caller restores trigger focus. */\n onDismiss: () => void;\n /** Accessible name for the menu itself: `role=\"menu\"` needs one. */\n \"aria-label\": string;\n /** Positioning (the caller owns anchoring); merged over the menu's own frame styles. */\n style?: CSSProperties;\n /** Rendered above the items: a title row, a pending indicator, a delay readout. */\n header?: ReactNode;\n /** Rendered below the items. */\n footer?: ReactNode;\n /** Shown in place of the items when there are none. Defaults to \"No actions\". */\n emptyLabel?: string;\n /** Heading for items with no `group` when other items have one. Defaults to \"Other\". */\n otherLabel?: string;\n}\n\n/**\n * A menu of actions to fire: the APG `menu` pattern\n * (https://www.w3.org/WAI/ARIA/apg/patterns/menu/).\n *\n * It differs from {@link ComboboxListbox}, a `listbox` for choosing a value:\n * this is a `menu` for invoking a command, so its items are real `<button>`s,\n * it owns keyboard focus while open, and selecting an item fires it.\n *\n * Keyboard: ArrowDown and ArrowUp walk the items, Home and End jump to the\n * ends, Enter or Space fires the focused item, and Escape or Tab dismisses. A\n * pointer press outside the menu dismisses it. Focus moves to the first item\n * on open; restore focus to the trigger in `onDismiss`. A disabled item stays\n * reachable by keyboard, so it reads as unavailable rather than missing.\n *\n * The menu is absolutely positioned and draws its own raised surface; place\n * it with `style`, or put it in a {@link Floating} with\n * `style={{ position: \"static\" }}`. Render it only while open.\n *\n * @example\n * ```tsx\n * {menuAnchor && (\n * <Floating anchor={menuAnchor}>\n * <ActionMenu\n * items={[\n * { key: \"deploy\", label: \"Deploy chute\" },\n * { key: \"cut\", label: \"Cut chute\", disabled: !deployed },\n * ]}\n * aria-label=\"Parachute actions\"\n * style={{ position: \"static\" }}\n * onSelect={(key) => {\n * fire(key);\n * close();\n * }}\n * onDismiss={close}\n * />\n * </Floating>\n * )}\n * ```\n *\n * @category Floating\n */\nexport function ActionMenu({\n items,\n onSelect,\n onDismiss,\n \"aria-label\": ariaLabel,\n style,\n header,\n footer,\n emptyLabel = \"No actions\",\n otherLabel = \"Other\",\n}: Readonly<ActionMenuProps>) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const menuId = useId();\n const [activeIndex, setActiveIndex] = useState(0);\n\n const groups = groupComboboxOptions(items, otherLabel);\n const flat = groups.flatMap(([, bucket]) => bucket);\n\n /*\n * Roving DOM focus, so the real buttons handle Enter/Space. `itemCount` is a\n * dependency because a menu can open before its items arrive; while empty the\n * container itself takes focus so Escape still has somewhere to bubble from.\n */\n const itemCount = flat.length;\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n if (itemCount === 0) {\n container.focus();\n return;\n }\n const buttons =\n container.querySelectorAll<HTMLButtonElement>('[role=\"menuitem\"]');\n buttons[activeIndex]?.focus();\n }, [activeIndex, itemCount]);\n\n // Bound on pointerdown, not click, so an outside press dismisses before it can activate anything else.\n useEffect(() => {\n const onPointerDown = (e: PointerEvent) => {\n const container = containerRef.current;\n if (\n container &&\n e.target instanceof Node &&\n !container.contains(e.target)\n ) {\n onDismiss();\n }\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n }, [onDismiss]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n onDismiss();\n return;\n }\n // A menu does not trap Tab: it dismisses and focus continues past the trigger.\n if (e.key === \"Tab\") {\n onDismiss();\n return;\n }\n if (flat.length === 0) return;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setActiveIndex((i) => Math.min(i + 1, flat.length - 1));\n break;\n case \"ArrowUp\":\n e.preventDefault();\n setActiveIndex((i) => Math.max(i - 1, 0));\n break;\n case \"Home\":\n e.preventDefault();\n setActiveIndex(0);\n break;\n case \"End\":\n e.preventDefault();\n setActiveIndex(flat.length - 1);\n break;\n }\n },\n [flat.length, onDismiss],\n );\n\n return (\n <Menu\n ref={containerRef}\n // A menu with no menuitem children is invalid ARIA, so the empty state is a plain labelled region.\n role={itemCount > 0 ? \"menu\" : undefined}\n aria-label={ariaLabel}\n onKeyDown={onKeyDown}\n // Programmatic focus only, for the empty state; never a tab stop.\n tabIndex={-1}\n style={style}\n >\n {header}\n {flat.length === 0 ? (\n <EmptyState layout=\"fill\">{emptyLabel}</EmptyState>\n ) : (\n groups.map(([group, bucket], groupIndex) => (\n <div\n key={group}\n {...(groups.length > 1\n ? {\n role: \"group\",\n \"aria-labelledby\": `${menuId}-group-${groupIndex}`,\n }\n : { role: \"none\" })}\n >\n {/* A single bucket renders flat, without a header. */}\n {groups.length > 1 ? (\n <GroupHeader\n id={`${menuId}-group-${groupIndex}`}\n aria-hidden=\"true\"\n >\n {group}\n </GroupHeader>\n ) : null}\n {bucket.map((item) => {\n const index = flat.indexOf(item);\n return (\n <MenuItem\n key={item.key}\n type=\"button\"\n role=\"menuitem\"\n aria-label={item[\"aria-label\"]}\n // aria-disabled, not `disabled`: a natively disabled button cannot take focus and drops out of the arrow-key walk.\n aria-disabled={item.disabled}\n $disabled={item.disabled}\n tabIndex={index === activeIndex ? 0 : -1}\n onFocus={() => setActiveIndex(index)}\n onClick={() => {\n if (!item.disabled) onSelect(item.key);\n }}\n >\n {item.label}\n </MenuItem>\n );\n })}\n </div>\n ))\n )}\n {footer}\n </Menu>\n );\n}\n\nconst Menu = styled.div`\n position: absolute;\n min-width: 180px;\n max-width: 280px;\n max-height: 280px;\n overflow-y: auto;\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-strong);\n border-radius: var(--radius-regular);\n padding: var(--inset-menu-list);\n z-index: var(--z-dropdown);\n`;\n\nconst GroupHeader = styled.div`\n font-size: var(--font-size-caption);\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n padding: var(--inset-menu-group-label);\n`;\n\nconst MenuItem = styled.button<{ $disabled?: boolean }>`\n display: block;\n width: 100%;\n text-align: left;\n border: none;\n background: transparent;\n color: var(--color-text-primary);\n font: inherit;\n font-size: var(--font-size-compact);\n padding: var(--inset-menu-item);\n cursor: pointer;\n\n &:hover[aria-disabled=\"false\"],\n &:hover:not([aria-disabled]) {\n background: var(--color-border-subtle);\n }\n\n ${focusRingInset}\n /* The background marks the focused row where the ring meets the menu border. */\n &:focus-visible {\n background: var(--color-border-subtle);\n }\n\n &[aria-disabled=\"true\"] {\n color: var(--color-text-faint);\n cursor: default;\n }\n`;\n","import type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { EmptyState } from \"./EmptyState\";\nimport { statusFill } from \"./tone\";\n\n/*\n * The combobox/listbox primitive. The pure filter/group/navigate functions and\n * the presentational `ComboboxListbox` are decoupled from any input surface, so\n * a caller can drive them from a literal `<input role=\"combobox\">` or from raw\n * keystrokes.\n */\n\n/**\n * A single selectable item in a combobox or listbox dropdown. Extend it to carry more per option.\n *\n * @category Form\n */\nexport interface ComboboxOption {\n /** Unique within the list; what a selection reports. */\n key: string;\n /** What the operator reads. Defaults to the option's key. */\n label?: string;\n /** The heading the option is listed under. Options with none share one bucket. */\n group?: string;\n}\n\n/**\n * Case-insensitive substring match against an option's label (falling back\n * to its key): the default filter every combobox consumer starts from.\n *\n * @category Form\n */\nexport function comboboxOptionMatches(\n option: ComboboxOption,\n query: string,\n): boolean {\n if (!query) return true;\n const q = query.toLowerCase();\n const label = option.label ?? option.key;\n return (\n label.toLowerCase().includes(q) || option.key.toLowerCase().includes(q)\n );\n}\n\n/**\n * Filters `options` against `query` using `matches`, by default a case-insensitive substring match on the label or key.\n *\n * @category Form\n */\nexport function filterComboboxOptions<Option extends ComboboxOption>(\n options: readonly Option[],\n query: string,\n matches: (option: Option, query: string) => boolean = comboboxOptionMatches,\n): Option[] {\n return options.filter((o) => matches(o, query));\n}\n\n/**\n * Buckets `options` by `.group` (default bucket `otherLabel`), sorted by\n * group name: the grouped-listbox shape combobox consumers render from.\n * Ungrouped callers (every option has no `group`) collapse to a single\n * `otherLabel` bucket, which renders as one flat list.\n *\n * @category Form\n */\nexport function groupComboboxOptions<Option extends ComboboxOption>(\n options: readonly Option[],\n otherLabel = \"Other\",\n): Array<[string, Option[]]> {\n const groups = new Map<string, Option[]>();\n for (const o of options) {\n const g = o.group ?? otherLabel;\n let bucket = groups.get(g);\n if (!bucket) {\n bucket = [];\n groups.set(g, bucket);\n }\n bucket.push(o);\n }\n return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b));\n}\n\n/**\n * Flattens grouped options back into render and navigation order: the order ArrowUp and ArrowDown walk, and what `activeIndex` indexes into.\n *\n * @category Form\n */\nexport function flattenComboboxGroups<Option extends ComboboxOption>(\n groups: ReadonlyArray<[string, Option[]]>,\n): Option[] {\n return groups.flatMap(([, items]) => items);\n}\n\n/**\n * Steps the active (highlighted) index by `delta` for one ArrowUp/ArrowDown\n * keypress, clamped to `[0, length - 1]`; never wraps, never goes negative.\n * Returns `-1` when there is nothing to select (`length === 0`).\n *\n * @category Form\n */\nexport function moveComboboxActiveIndex(\n current: number,\n delta: number,\n length: number,\n): number {\n if (length === 0) return -1;\n return Math.max(0, Math.min(current + delta, length - 1));\n}\n\n/**\n * Props for {@link ComboboxListbox}.\n *\n * @category Form\n */\nexport interface ComboboxListboxProps<Option extends ComboboxOption> {\n /** DOM id for the listbox element: pair with the owning input's `aria-controls`. */\n id: string;\n /** The options to show, bucketed by {@link groupComboboxOptions}. */\n groups: ReadonlyArray<[string, Option[]]>;\n /** The same options in navigation order, from {@link flattenComboboxGroups}. */\n flatOptions: readonly Option[];\n /** Index into `flatOptions` of the currently highlighted item, or `-1` for none. */\n activeIndex: number;\n /** The currently committed value (distinct from `activeIndex`'s in-progress highlight), if any. */\n selectedKey?: string | null;\n /** The DOM id of an option, for the owning input's `aria-activedescendant`. */\n getOptionId: (key: string) => string;\n /** Called with a `flatOptions` index when the pointer moves over an option. */\n onHoverIndex: (index: number) => void;\n /** Called with an option's key when it is clicked or tapped. */\n onSelectKey: (key: string) => void;\n /** Custom item body, such as a label with a trailing unit. Defaults to the option's label, or its key when it has none. */\n renderItem?: (option: Option) => ReactNode;\n /** Shown when `flatOptions` is empty. Defaults to \"No matches\". */\n emptyLabel?: string;\n /**\n * Accessible name for the listbox itself. Pass one whenever the owning\n * control is not a literal `<input role=\"combobox\">`, since an unnamed\n * `role=\"listbox\"` is an accessibility violation.\n */\n \"aria-label\"?: string;\n /**\n * Which side of the control the list opens on. Defaults to `\"below\"`.\n * `\"above\"` is for a control at the foot of a clipping container, such as a\n * console composer, where a downward list would be clipped away.\n */\n placement?: \"below\" | \"above\";\n}\n\n/**\n * The presentational half of the combobox pattern: a `role=\"listbox\"`\n * dropdown of grouped, keyboard-navigable options. Owns no state:\n * `activeIndex`, `onHoverIndex` and `onSelectKey` are fully controlled by the\n * caller, who drives them with {@link filterComboboxOptions},\n * {@link groupComboboxOptions}, {@link flattenComboboxGroups} and\n * {@link moveComboboxActiveIndex}. The list is absolutely positioned, so put it\n * in a `position: relative` container beside the input. Pressing an option\n * does not take focus from the input.\n *\n * @example\n * ```tsx\n * import { useId, useState } from \"react\";\n * import {\n * type ComboboxOption,\n * ComboboxListbox,\n * filterComboboxOptions,\n * flattenComboboxGroups,\n * groupComboboxOptions,\n * Input,\n * moveComboboxActiveIndex,\n * } from \"@ksp-gonogo/ui-kit\";\n *\n * function BodyPicker(props: {\n * options: readonly ComboboxOption[];\n * selected: string | undefined;\n * onPick: (key: string) => void;\n * }) {\n * const [query, setQuery] = useState(\"\");\n * const [open, setOpen] = useState(false);\n * const [active, setActive] = useState(-1);\n * const listId = useId();\n * const optionId = (key: string) => `${listId}-${key}`;\n * const groups = groupComboboxOptions(filterComboboxOptions(props.options, query));\n * const flat = flattenComboboxGroups(groups);\n * // The list can shrink under the highlighted row, so it is looked up, never assumed.\n * const activeOption = flat[active];\n *\n * return (\n * <div style={{ position: \"relative\" }}>\n * <Input\n * role=\"combobox\"\n * aria-label=\"Body\"\n * aria-expanded={open}\n * aria-controls={listId}\n * aria-autocomplete=\"list\"\n * aria-activedescendant={activeOption ? optionId(activeOption.key) : undefined}\n * value={query}\n * onFocus={() => setOpen(true)}\n * // Safe to close on blur: pressing an option does not take focus from the input.\n * onBlur={() => setOpen(false)}\n * onChange={(e) => {\n * setQuery(e.target.value);\n * setActive(-1);\n * }}\n * onKeyDown={(e) => {\n * if (e.key === \"ArrowDown\") setActive((i) => moveComboboxActiveIndex(i, 1, flat.length));\n * if (e.key === \"ArrowUp\") setActive((i) => moveComboboxActiveIndex(i, -1, flat.length));\n * if (e.key === \"Enter\" && activeOption) props.onPick(activeOption.key);\n * if (e.key === \"Escape\") setOpen(false);\n * }}\n * />\n * {open && (\n * <ComboboxListbox\n * id={listId}\n * groups={groups}\n * flatOptions={flat}\n * activeIndex={active}\n * selectedKey={props.selected}\n * getOptionId={optionId}\n * onHoverIndex={setActive}\n * onSelectKey={props.onPick}\n * />\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @category Form\n */\nexport function ComboboxListbox<Option extends ComboboxOption>({\n id,\n groups,\n flatOptions,\n activeIndex,\n selectedKey,\n getOptionId,\n onHoverIndex,\n onSelectKey,\n renderItem,\n emptyLabel = \"No matches\",\n \"aria-label\": ariaLabel,\n placement = \"below\",\n}: Readonly<ComboboxListboxProps<Option>>) {\n return (\n <Dropdown\n role=\"listbox\"\n id={id}\n aria-label={ariaLabel}\n $placement={placement}\n >\n {flatOptions.length === 0 ? (\n <EmptyState layout=\"fill\">{emptyLabel}</EmptyState>\n ) : (\n groups.map(([group, items], groupIndex) => (\n <DropdownGroup\n key={group}\n role=\"group\"\n aria-labelledby={`${id}-group-${groupIndex}`}\n >\n <GroupHeader id={`${id}-group-${groupIndex}`} aria-hidden=\"true\">\n {group}\n </GroupHeader>\n {items.map((opt) => {\n const globalIdx = flatOptions.indexOf(opt);\n const isActive = globalIdx === activeIndex;\n return (\n <DropdownItem\n key={opt.key}\n id={getOptionId(opt.key)}\n role=\"option\"\n aria-selected={isActive}\n $active={isActive}\n $selected={opt.key === selectedKey}\n onPointerDown={(e) => {\n // Prevent the input from losing focus (and triggering an outside-click dismiss) before the selection runs.\n e.preventDefault();\n onSelectKey(opt.key);\n }}\n onMouseEnter={() => onHoverIndex(globalIdx)}\n >\n {renderItem ? renderItem(opt) : (opt.label ?? opt.key)}\n </DropdownItem>\n );\n })}\n </DropdownGroup>\n ))\n )}\n </Dropdown>\n );\n}\n\nconst Dropdown = styled.div<{ $placement: \"below\" | \"above\" }>`\n position: absolute;\n ${({ $placement }) =>\n $placement === \"above\"\n ? \"bottom: calc(100% + 2px);\"\n : \"top: calc(100% + 2px);\"}\n left: 0;\n right: 0;\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-strong);\n border-radius: var(--radius-regular);\n max-height: 280px;\n overflow-y: auto;\n z-index: var(--z-dropdown);\n`;\n\nconst DropdownGroup = styled.div``;\n\nconst GroupHeader = styled.div`\n font-size: var(--font-size-caption);\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n padding: var(--inset-menu-group-label);\n position: sticky;\n top: 0;\n background: var(--color-surface-raised);\n`;\n\n/** The option under the keyboard or pointer sits on the panel ground beside the focus bar; the committed option, when not highlighted, sits on the go status fill. */\nfunction optionFill(active: boolean, selected: boolean): string {\n if (active) return \"background: var(--color-surface-panel);\";\n if (selected) return statusFill(\"go\");\n return \"background: transparent;\";\n}\n\nconst DropdownItem = styled.div<{ $active: boolean; $selected: boolean }>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--inset-menu-item);\n cursor: pointer;\n ${({ $active, $selected }) => optionFill($active, $selected)}\n /* Focus stays on the input, so the highlighted option carries the focus colour as an inset bar. */\n box-shadow: ${({ $active }) =>\n $active ? \"inset 3px 0 0 var(--color-focus)\" : \"none\"};\n\n &:hover {\n background: var(--color-surface-panel);\n }\n`;\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\n\n/**\n * Where an {@link EmptyState} sits: `inline` where it is mounted, `fill`\n * centred in the available space.\n *\n * @category EmptyState\n */\nexport type EmptyStateLayout = \"inline\" | \"fill\";\n\n/**\n * Props for {@link EmptyState}. Any other `div` attribute passes through.\n *\n * @category EmptyState\n */\nexport interface EmptyStateProps extends HTMLAttributes<HTMLDivElement> {\n /** The placeholder text. */\n children?: ReactNode;\n /**\n * `inline`, the default, sits where it is mounted with no inset of its own;\n * `fill` centres the text in all the space its container gives it.\n */\n layout?: EmptyStateLayout;\n}\n\n/**\n * Muted placeholder text shown when a panel or list has nothing to render.\n *\n * `inline` is the default: it adds no inset of its own and sits where it is\n * mounted, taking the padding of the body or section around it, so it lines up\n * with the content it stands in for. `fill` centres in the available space and\n * suits a panel's sole child.\n *\n * @category EmptyState\n * @categoryDescription EmptyState\n * What a widget shows where there is no content to draw: a placeholder for an\n * empty panel or list, a notice, a spinner while it waits, and the note for a\n * trajectory that is withheld.\n */\nexport function EmptyState({\n children,\n layout = \"inline\",\n ...rest\n}: EmptyStateProps) {\n return (\n <EmptyState__Body $layout={layout} {...rest}>\n {children}\n </EmptyState__Body>\n );\n}\n\nconst LAYOUT_STYLES = {\n inline: css``,\n fill: css`\n width: 100%;\n height: 100%;\n flex: 1;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: var(--inset-empty-state);\n `,\n} as const;\n\nconst EmptyState__Body = styled.div<{ $layout: EmptyStateLayout }>`\n color: var(--color-text-muted);\n font-size: var(--font-size-compact);\n letter-spacing: 0.04em;\n\n ${({ $layout }) => LAYOUT_STYLES[$layout]}\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport { useId } from \"react\";\nimport { Button } from \"./Button\";\nimport { Field, FieldHint, FieldLabel, Select } from \"./Form\";\nimport { Stack } from \"./Stack\";\nimport { StatusIndicator } from \"./StatusIndicator\";\nimport {\n type AudioInputControls,\n type AudioInputState,\n describeAudioInput,\n type UseAudioInputOptions,\n useAudioInput,\n} from \"./useAudioInput\";\n\n/**\n * Props for {@link AudioInputPicker}: the {@link UseAudioInputOptions} plus a label.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputPickerProps extends UseAudioInputOptions {\n /** Visible name for the device picker. Defaults to `Audio input`. */\n label?: string;\n}\n\n/**\n * Microphone access and device choice, as one control: it asks for permission,\n * lists what the browser will admit to, opens the chosen input and hands the\n * stream back through `onStream`. It knows nothing about what the stream is for.\n * Built on {@link useAudioInput}; use the hook directly to draw your own.\n *\n * Every state it can be in is a distinguishable sentence in a live status line.\n * An insecure origin, a refused permission and an absent device all end with no\n * stream, but each has a different remedy. A refusal offers no retry button,\n * because the browser returns the same refusal without prompting while the site\n * setting stands. The stream is stopped when the picker unmounts.\n *\n * @example\n * ```tsx\n * <AudioInputPicker\n * label=\"Voice loop input\"\n * constraints={{ echoCancellation: true }}\n * onStream={(stream) => setStream(stream)}\n * />\n * ```\n *\n * @category AudioInputPicker\n * @categoryDescription AudioInputPicker\n * Choosing and opening a microphone: the picker an operator selects an input\n * with, the hook behind it, and whether this browser can capture audio at all.\n */\nexport function AudioInputPicker({\n label = \"Audio input\",\n ...options\n}: AudioInputPickerProps) {\n const input = useAudioInput(options);\n const selectId = useId();\n const unnamed = unnamedDevicesSentence(input);\n\n return (\n <Stack gap=\"related-dense\">\n <StatusIndicator tone={stateTone(input.status)} live>\n {stateSentence(input)}\n </StatusIndicator>\n\n {input.status === \"ready\" && input.devices.length > 0 ? (\n <Field>\n <FieldLabel htmlFor={selectId}>{label}</FieldLabel>\n <Select\n id={selectId}\n value={input.deviceId ?? \"\"}\n onChange={(event) => {\n void input.select(event.target.value);\n }}\n >\n {input.devices.map((device, index) => (\n <option key={device.deviceId} value={device.deviceId}>\n {describeAudioInput(device, index)}\n </option>\n ))}\n </Select>\n {input.failure ? (\n <FieldHint>\n {`The requested input did not open. The browser reported ${input.failure.name}, and the previous input is still running.`}\n </FieldHint>\n ) : null}\n </Field>\n ) : null}\n\n {canRequest(input.status) ? (\n <Button\n type=\"button\"\n onClick={() => {\n void input.request();\n }}\n disabled={input.status === \"requesting\"}\n aria-busy={input.status === \"requesting\"}\n >\n Request microphone access\n </Button>\n ) : null}\n\n {unnamed ? <FieldHint>{unnamed}</FieldHint> : null}\n </Stack>\n );\n}\n\nfunction canRequest(status: AudioInputControls[\"status\"]): boolean {\n return status === \"unasked\" || status === \"requesting\" || status === \"failed\";\n}\n\nconst TONES: Record<AudioInputControls[\"status\"], Tone> = {\n \"insecure-origin\": \"warn\",\n \"no-media-devices\": \"nogo\",\n unasked: \"neutral\",\n requesting: \"info\",\n refused: \"nogo\",\n \"no-device\": \"warn\",\n failed: \"nogo\",\n ready: \"go\",\n};\n\nfunction stateTone(status: AudioInputControls[\"status\"]): Tone {\n return TONES[status];\n}\n\n/**\n * The one sentence describing where capture stands. `unasked` splits on\n * whether any device label is populated, because a populated label is the\n * browser's evidence that access was already granted.\n */\nfunction stateSentence(state: AudioInputState): string {\n switch (state.status) {\n case \"insecure-origin\":\n return \"This page is not a secure origin. Microphone capture is available on https pages and on localhost.\";\n case \"no-media-devices\":\n return \"This browser exposes no media device interface, so no input can be opened here.\";\n case \"unasked\":\n return accessGranted(state)\n ? \"Microphone access is granted for this origin. No input is open.\"\n : \"Microphone access has not been requested for this origin.\";\n case \"requesting\":\n return \"A capture request is with the browser, which may be prompting for access.\";\n case \"refused\":\n return \"Microphone access was refused for this origin. The browser holds that answer in its site settings and returns it to any further request without prompting.\";\n case \"no-device\":\n return \"No audio input device is present.\";\n case \"failed\":\n return `The input did not open. The browser reported ${state.failure?.name ?? \"an unnamed error\"}.`;\n case \"ready\":\n return `Capturing from ${openDeviceName(state)}.`;\n }\n}\n\nfunction accessGranted(state: AudioInputState): boolean {\n return state.devices.some((device) => device.label !== \"\");\n}\n\nfunction openDeviceName(state: AudioInputState): string {\n const index = state.devices.findIndex(\n (device) => device.deviceId === state.deviceId,\n );\n const device = index === -1 ? undefined : state.devices[index];\n return device ? describeAudioInput(device, index) : \"an unnamed input\";\n}\n\n/**\n * What is known about the devices while their names are not. Shown only before\n * access is granted, where a count is the whole of what the browser will say.\n */\nfunction unnamedDevicesSentence(state: AudioInputState): string | null {\n if (state.status !== \"unasked\" || accessGranted(state)) return null;\n const count = state.devices.length;\n if (count === 0) return null;\n return count === 1\n ? \"One audio input is visible. Its name is withheld by the browser until access is granted.\"\n : `${count} audio inputs are visible. Their names are withheld by the browser until access is granted.`;\n}\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styled, { css, keyframes } from \"styled-components\";\nimport { TONE_MARK, toneEdge } from \"./tone\";\n\n/**\n * Props for {@link StatusIndicator}. Other `div` attributes pass through to the row.\n *\n * @category Badge\n */\nexport interface StatusIndicatorProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The state the dot shows, which sets its colour. */\n tone: Tone;\n /** The words beside the dot. */\n children: ReactNode;\n /**\n * When true, the indicator becomes a screen-reader live region.\n * Use for state that updates dynamically and the user benefits from\n * being told (connection going from probing to ok or fail). Default\n * false to keep it out of the accessibility tree for purely decorative\n * uses.\n */\n live?: boolean;\n /**\n * Pulse the dot to signal an active, changing state (a live or connecting\n * data source, for example). `\"slow\"` (2s) reads as steady-live, `\"fast\"` (1s) as\n * working/reconnecting. Omit for a static dot. Guarded by\n * `prefers-reduced-motion` (the dot holds still when the user opts out).\n */\n pulse?: \"slow\" | \"fast\";\n}\n\n/**\n * A boxed row with a coloured dot on the left and a sentence of status text on\n * the right, bordered in the tone's colour and one control high. Use it where\n * a sentence describes a state (connection status, a data source's health) and\n * the dot tells the operator at a glance whether to worry. For a compact\n * uppercase pill use {@link Badge} instead.\n *\n * @example\n * ```tsx\n * <StatusIndicator tone=\"caution\" pulse=\"fast\" live>\n * Reconnecting to the relay\n * </StatusIndicator>\n * ```\n *\n * @category Badge\n */\nexport function StatusIndicator({\n tone,\n children,\n live = false,\n pulse,\n ...rest\n}: StatusIndicatorProps) {\n const liveAttrs = live\n ? { role: \"status\" as const, \"aria-live\": \"polite\" as const }\n : {};\n return (\n <StatusIndicator__Row data-tone={tone} {...liveAttrs} {...rest}>\n <StatusIndicator__Dot\n data-tone={tone}\n $pulse={pulse}\n aria-hidden=\"true\"\n />\n <StatusIndicator__Text>{children}</StatusIndicator__Text>\n </StatusIndicator__Row>\n );\n}\n\nconst StatusIndicator__Row = styled.div<{ \"data-tone\": Tone }>`\n display: flex;\n align-items: center;\n gap: var(--gap-glyph-box);\n font-size: var(--font-size-compact);\n /* Small, so a wrapped sentence clears the border without pushing a single line past the control height. */\n padding: var(--inset-status-box);\n /* A boxed readout that sits in bars beside controls, so it takes the kit's one control height. */\n min-height: var(--control-height);\n background: var(--color-surface-raised);\n border: 1px solid;\n border-radius: var(--radius-regular);\n\n border-color: ${({ \"data-tone\": tone }) => toneEdge(tone)};\n`;\n\nconst statusPulse = keyframes`\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n`;\n\nconst StatusIndicator__Dot = styled.span<{\n \"data-tone\": Tone;\n $pulse?: \"slow\" | \"fast\";\n}>`\n width: 8px;\n height: 8px;\n border-radius: var(--radius-circle);\n flex-shrink: 0;\n\n background: ${({ \"data-tone\": tone }) => TONE_MARK[tone]};\n\n /* A looping pulse needs its own reduced-motion guard; the 1s/2s periods encode connection state. */\n ${({ $pulse }) =>\n $pulse\n ? css`\n @media (prefers-reduced-motion: no-preference) {\n animation: ${statusPulse} ${$pulse === \"fast\" ? \"1s\" : \"2s\"}\n var(--ease-emphasis) infinite;\n }\n `\n : \"\"}\n`;\n\nconst StatusIndicator__Text = styled.span`\n color: var(--color-text-primary);\n line-height: var(--line-height-body);\n`;\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { audioCaptureSupport } from \"./audioCaptureSupport\";\n\n/**\n * One selectable capture device.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputDevice {\n /** The browser's id for the device, what {@link AudioInputControls.select} takes. */\n deviceId: string;\n /**\n * The browser's name for the device. Empty until capture access is granted,\n * since browsers withhold device names from a page without access; an empty\n * label does not mean the device has no name.\n */\n label: string;\n}\n\n/**\n * Where the capture attempt stands. Each failure is its own status because\n * each has a different remedy:\n *\n * - `unasked`: nothing has been requested yet, the starting status\n * - `requesting`: a request is with the browser, which may be prompting\n * - `ready`: a capture is open\n * - `insecure-origin` and `no-media-devices`: capture cannot be attempted, per\n * {@link audioCaptureSupport}\n * - `refused`: the operator (or a standing site setting) said no. The browser\n * holds that refusal for the origin, so asking again returns it without\n * prompting\n * - `no-device`: access is not the problem, there is nothing to capture from\n * - `failed`: the device exists and is permitted, and opening it still did not\n * work (in use elsewhere, hardware error). `failure.name` carries the\n * browser's own word for it\n *\n * @category AudioInputPicker\n */\nexport type AudioInputStatus =\n | \"insecure-origin\"\n | \"no-media-devices\"\n | \"unasked\"\n | \"requesting\"\n | \"refused\"\n | \"no-device\"\n | \"failed\"\n | \"ready\";\n\n/**\n * An attempt to open an input that did not work.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputFailure {\n /** The `DOMException` name the browser reported, e.g. `NotReadableError`. */\n name: string;\n /** The device the attempt named, or null for an unpinned first request. */\n deviceId: string | null;\n}\n\n/**\n * Everything {@link useAudioInput} knows about capture at one moment.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputState {\n /** Where the capture attempt stands. */\n status: AudioInputStatus;\n /**\n * Every audio input the browser will admit to. Populated before access is\n * granted on the engines that allow it, with empty labels; entries with an\n * empty `deviceId` are dropped, since they name nothing that can be opened.\n */\n devices: readonly AudioInputDevice[];\n /** The open device, null whenever no capture is running. */\n deviceId: string | null;\n /** The live stream, null whenever no capture is running. */\n stream: MediaStream | null;\n /**\n * The last attempt that did not work. Set alongside `ready` when a SWITCH\n * failed and the previous capture is still open, which is the one case where\n * a failure is not the whole state.\n */\n failure: AudioInputFailure | null;\n}\n\n/**\n * What {@link useAudioInput} returns: the current state and the three actions.\n *\n * @category AudioInputPicker\n */\nexport interface AudioInputControls extends AudioInputState {\n /** Open the default input, prompting for access the first time. */\n request(): Promise<void>;\n /** Open a named input, keeping the current one if the new one will not open. */\n select(deviceId: string): Promise<void>;\n /** Stop the capture and hand back a null stream. Access stays granted. */\n release(): void;\n}\n\n/**\n * Options for {@link useAudioInput} and {@link AudioInputPicker}.\n *\n * @category AudioInputPicker\n */\nexport interface UseAudioInputOptions {\n /**\n * Track constraints merged into every request. A `deviceId` here is\n * overwritten by the selected device.\n */\n constraints?: MediaTrackConstraints;\n /** Called with each stream as it opens, and with null as it closes. */\n onStream?: (stream: MediaStream | null) => void;\n}\n\n/**\n * Option text for a device: its label, or `Input <n>, name withheld` (one-based)\n * while the browser withholds the name.\n *\n * @category AudioInputPicker\n */\nexport function describeAudioInput(\n device: AudioInputDevice,\n index: number,\n): string {\n return device.label !== \"\"\n ? device.label\n : `Input ${index + 1}, name withheld`;\n}\n\nconst REFUSED_ERRORS = new Set([\n \"NotAllowedError\",\n \"PermissionDeniedError\",\n \"SecurityError\",\n]);\n\nconst ABSENT_ERRORS = new Set([\n \"NotFoundError\",\n \"DevicesNotFoundError\",\n \"OverconstrainedError\",\n \"ConstraintNotSatisfiedError\",\n]);\n\nfunction errorName(err: unknown): string {\n if (err instanceof Error && err.name !== \"\") return err.name;\n if (typeof err === \"object\" && err !== null && \"name\" in err) {\n const name = (err as { name: unknown }).name;\n if (typeof name === \"string\" && name !== \"\") return name;\n }\n return \"UnknownError\";\n}\n\nfunction statusForError(name: string): AudioInputStatus {\n if (REFUSED_ERRORS.has(name)) return \"refused\";\n if (ABSENT_ERRORS.has(name)) return \"no-device\";\n return \"failed\";\n}\n\nfunction stopTracks(stream: MediaStream): void {\n for (const track of stream.getTracks()) track.stop();\n}\n\nfunction trackDeviceId(stream: MediaStream): string | null {\n const track = stream.getAudioTracks()[0];\n const settings = track?.getSettings?.();\n return settings?.deviceId ?? null;\n}\n\nasync function listInputs(): Promise<AudioInputDevice[]> {\n const media = navigator.mediaDevices;\n if (!media || typeof media.enumerateDevices !== \"function\") return [];\n try {\n const all = await media.enumerateDevices();\n return all\n .filter(\n (device) => device.kind === \"audioinput\" && device.deviceId !== \"\",\n )\n .map((device) => ({ deviceId: device.deviceId, label: device.label }));\n } catch {\n // A failed enumeration is not a device fact; the request itself decides whether capture works.\n return [];\n }\n}\n\nfunction initialState(): AudioInputState {\n const support = audioCaptureSupport();\n return {\n status: support.supported ? \"unasked\" : support.reason,\n devices: [],\n deviceId: null,\n stream: null,\n failure: null,\n };\n}\n\n/**\n * The permission-and-selection half of microphone capture, with no opinion\n * about how it is drawn. {@link AudioInputPicker} is the drawn form and the\n * usual entry point.\n *\n * Nothing is requested until `request()` or `select()` is called. Every outcome\n * is a named {@link AudioInputStatus} rather than a boolean plus an error: a\n * refused permission, an absent device and an insecure origin all end with no\n * stream, and each needs a different fix. The device list follows the\n * browser's device changes, a capture whose device disappears is closed, and\n * any open stream is stopped on unmount.\n *\n * @example\n * ```tsx\n * function MicGate({ onStream }: { onStream: (stream: MediaStream | null) => void }) {\n * const mic = useAudioInput({ onStream });\n * if (mic.status === \"unasked\") {\n * return <Button onClick={() => void mic.request()}>Enable microphone</Button>;\n * }\n * return <Text>{mic.status}</Text>;\n * }\n * ```\n *\n * @category AudioInputPicker\n */\nexport function useAudioInput(\n options: UseAudioInputOptions = {},\n): AudioInputControls {\n const [state, setState] = useState<AudioInputState>(initialState);\n const streamRef = useRef<MediaStream | null>(null);\n // Mirrored out of state so the devicechange handler's updater stays pure under StrictMode's double invocation.\n const openDeviceIdRef = useRef<string | null>(null);\n const mountedRef = useRef(true);\n const optionsRef = useRef(options);\n\n useEffect(() => {\n optionsRef.current = options;\n });\n\n const closeStream = useCallback(() => {\n const open = streamRef.current;\n if (!open) return;\n streamRef.current = null;\n openDeviceIdRef.current = null;\n stopTracks(open);\n optionsRef.current.onStream?.(null);\n }, []);\n\n // Losing the open device ends the capture; only an empty list means there is nothing left to choose.\n const applyDevices = useCallback(\n (devices: AudioInputDevice[]) => {\n const openId = openDeviceIdRef.current;\n const lost =\n streamRef.current !== null &&\n (openId === null\n ? devices.length === 0\n : !devices.some((device) => device.deviceId === openId));\n setState((prev) => {\n if (prev.status !== \"ready\" || !lost) return { ...prev, devices };\n return {\n ...prev,\n status: devices.length === 0 ? \"no-device\" : \"unasked\",\n devices,\n deviceId: null,\n stream: null,\n };\n });\n if (lost) closeStream();\n },\n [closeStream],\n );\n\n // Re-read on every devicechange: before access is granted this yields ids without labels, after it fills the labels in and notices an unplug.\n useEffect(() => {\n const media = navigator.mediaDevices;\n if (!media) return;\n\n let cancelled = false;\n const sync = () => {\n void listInputs().then((devices) => {\n if (!cancelled) applyDevices(devices);\n });\n };\n sync();\n media.addEventListener?.(\"devicechange\", sync);\n return () => {\n cancelled = true;\n media.removeEventListener?.(\"devicechange\", sync);\n };\n }, [applyDevices]);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n closeStream();\n };\n }, [closeStream]);\n\n const open = useCallback(async (deviceId: string | null) => {\n const media = navigator.mediaDevices;\n if (!media || typeof media.getUserMedia !== \"function\") return;\n\n setState((prev) => ({\n ...prev,\n status: prev.stream ? prev.status : \"requesting\",\n failure: null,\n }));\n\n const audio: MediaTrackConstraints = {\n ...optionsRef.current.constraints,\n ...(deviceId === null ? {} : { deviceId: { exact: deviceId } }),\n };\n\n try {\n const stream = await media.getUserMedia({ audio });\n if (!mountedRef.current) {\n stopTracks(stream);\n return;\n }\n const previous = streamRef.current;\n streamRef.current = stream;\n if (previous && previous !== stream) stopTracks(previous);\n\n const listed = await listInputs();\n const active = trackDeviceId(stream) ?? deviceId ?? listed[0]?.deviceId;\n const devices =\n listed.length > 0 ? listed : openDeviceOnly(stream, active);\n openDeviceIdRef.current = active ?? null;\n setState({\n status: \"ready\",\n devices,\n deviceId: active ?? null,\n stream,\n failure: null,\n });\n optionsRef.current.onStream?.(stream);\n } catch (err) {\n if (!mountedRef.current) return;\n const failure: AudioInputFailure = { name: errorName(err), deviceId };\n setState((prev) =>\n prev.stream\n ? { ...prev, failure }\n : {\n status: statusForError(failure.name),\n devices: prev.devices,\n deviceId: null,\n stream: null,\n failure,\n },\n );\n }\n }, []);\n\n const request = useCallback(() => open(null), [open]);\n const select = useCallback((deviceId: string) => open(deviceId), [open]);\n const release = useCallback(() => {\n closeStream();\n setState((prev) => ({\n ...prev,\n status: prev.status === \"ready\" ? \"unasked\" : prev.status,\n deviceId: null,\n stream: null,\n }));\n }, [closeStream]);\n\n return { ...state, request, select, release };\n}\n\n/**\n * The one device we can prove exists, for an engine that grants capture and\n * still enumerates nothing. Returns an empty list rather than a nameless,\n * idless row, since a row that selects nothing is worse than no row.\n */\nfunction openDeviceOnly(\n stream: MediaStream,\n deviceId: string | null | undefined,\n): AudioInputDevice[] {\n if (!deviceId) return [];\n return [{ deviceId, label: stream.getAudioTracks()[0]?.label ?? \"\" }];\n}\n","/*\n * The origin is checked FIRST: some engines withhold navigator.mediaDevices on\n * an insecure origin and some expose it and refuse at getUserMedia, so a\n * presence-only probe misreports both.\n */\n\n/**\n * Why microphone capture cannot run on this page: `insecure-origin` (plain\n * http, which works again over https or on localhost) or `no-media-devices`\n * (the browser has no media device interface at all).\n *\n * @category AudioInputPicker\n */\nexport type AudioCaptureUnsupportedReason =\n | \"insecure-origin\"\n | \"no-media-devices\";\n\n/**\n * What {@link audioCaptureSupport} reports: supported, or not with the reason.\n *\n * @category AudioInputPicker\n */\nexport type AudioCaptureSupport =\n | { supported: true }\n | { supported: false; reason: AudioCaptureUnsupportedReason };\n\n/**\n * Whether this page can open a microphone, probed synchronously before touching\n * a device. It reports a reason rather than a boolean because the two failures\n * need different copy.\n *\n * @category AudioInputPicker\n */\nexport function audioCaptureSupport(): AudioCaptureSupport {\n // Anything other than the boolean `true` a browser sets is treated as insecure.\n if (\n !(\"isSecureContext\" in globalThis) ||\n globalThis.isSecureContext !== true\n ) {\n return { supported: false, reason: \"insecure-origin\" };\n }\n\n const media =\n typeof navigator === \"undefined\" ? undefined : navigator.mediaDevices;\n if (!media || typeof media.getUserMedia !== \"function\") {\n return { supported: false, reason: \"no-media-devices\" };\n }\n return { supported: true };\n}\n","import styled from \"styled-components\";\n\n/**\n * The props every {@link Switch} takes.\n *\n * @category Form\n */\nexport interface SwitchBaseProps {\n /** Whether the switch is on. The switch is controlled: it shows this value and changes only when the caller changes it. */\n checked: boolean;\n /** Called with the new state when the operator flips the switch. */\n onChange: (value: boolean) => void;\n /** Renders dimmed and non-interactive; `onChange` never fires. For a toggle whose effective state is controlled elsewhere, such as a sub-setting inert while its parent setting is off. */\n disabled?: boolean;\n /** The checkbox's id, for a `<label htmlFor>` outside the switch. */\n id?: string;\n}\n\n/**\n * Props for {@link Switch}: `checked`, `onChange(next)`, optional `disabled`\n * and `id`, and exactly one name. A switch always has a name: a visible `label`\n * drawn beside it, or an `aria-label` where the row it sits in shows its own\n * label text.\n *\n * @category Form\n */\nexport type SwitchProps = SwitchBaseProps &\n (\n | {\n /** The visible name, drawn beside the track. Give this or `aria-label`, never both. */\n label: string;\n \"aria-label\"?: never;\n }\n | {\n label?: never;\n /** The accessible name, for a switch whose row already shows its label text. Give this or `label`, never both. */\n \"aria-label\": string;\n }\n );\n\n/**\n * An on/off toggle for one setting, which takes effect as it is flipped; inside\n * a form with a save bar the change waits for Save like any other field. It is\n * a native checkbox drawn as a sliding track, keyboard-operable with Space and\n * with the focus ring drawn on the track.\n *\n * @category Form\n */\nexport function Switch({\n checked,\n onChange,\n label,\n disabled,\n id,\n \"aria-label\": ariaLabel,\n}: SwitchProps) {\n return (\n <SwitchLabel $disabled={disabled}>\n <SwitchInput\n type=\"checkbox\"\n id={id}\n checked={checked}\n disabled={disabled}\n onChange={(e) => onChange(e.target.checked)}\n aria-label={label ? undefined : ariaLabel}\n />\n <SwitchTrack $checked={checked} $disabled={disabled}>\n <SwitchThumb $checked={checked} $disabled={disabled} />\n </SwitchTrack>\n {label && <SwitchText>{label}</SwitchText>}\n </SwitchLabel>\n );\n}\n\nconst SwitchLabel = styled.label<{ $disabled?: boolean }>`\n display: inline-flex;\n align-items: center;\n gap: var(--gap-glyph-control);\n cursor: ${({ $disabled }) => ($disabled ? \"not-allowed\" : \"pointer\")};\n user-select: none;\n opacity: ${({ $disabled }) => ($disabled ? 0.5 : 1)};\n\n @media (pointer: coarse) {\n /* Expand tap target to 44px tall without enlarging the visual track. */\n min-height: 44px;\n padding: var(--inset-switch-touch);\n }\n`;\n\nconst SwitchTrack = styled.div<{ $checked: boolean; $disabled?: boolean }>`\n width: 28px;\n height: 14px;\n /* --radius-pill keeps the stadium shape through a height change. */\n border-radius: var(--radius-pill);\n background: ${({ $checked, $disabled }) => ($disabled ? \"var(--color-surface-raised)\" : $checked ? \"var(--color-go-mark)\" : \"var(--color-surface-raised)\")};\n border: 1px solid ${({ $checked, $disabled }) => ($disabled ? \"var(--color-border-strong)\" : $checked ? \"var(--color-go-mark)\" : \"var(--color-border-strong)\")};\n position: relative;\n flex-shrink: 0;\n transition: background var(--duration-base), border-color var(--duration-base);\n`;\n\n/* The input is invisible and sizeless, so its keyboard focus is drawn on the\n track it sits beside. */\nconst SwitchInput = styled.input`\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n\n &:focus-visible + ${SwitchTrack} {\n outline: 2px solid var(--color-focus);\n outline-offset: 2px;\n }\n`;\n\nconst SwitchThumb = styled.div<{ $checked: boolean; $disabled?: boolean }>`\n position: absolute;\n /* Measured inside the border: the track is border-box, so its 1px border leaves a 26 by 12 box, and 2px on every side centres the 8px thumb at either end. */\n top: 2px;\n left: ${({ $checked }) => ($checked ? \"16px\" : \"2px\")};\n width: 8px;\n height: 8px;\n border-radius: var(--radius-circle);\n background: ${({ $checked, $disabled }) => ($disabled ? \"var(--color-text-faint)\" : $checked ? \"var(--color-accent-fg)\" : \"var(--color-text-faint)\")};\n transition: left var(--duration-base), background var(--duration-base);\n`;\n\nconst SwitchText = styled.span`\n font-size: var(--font-size-caption);\n color: var(--color-text-muted);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n`;\n","import type { NamespacedAugmentSettings } from \"./augments\";\nimport { Field, FieldLabel, FieldRow, Input } from \"./Form\";\nimport { Switch } from \"./Switch\";\n\n/**\n * Props for {@link AugmentSettingsPanel}.\n *\n * @category Extensions\n */\nexport interface AugmentSettingsPanelProps {\n /** Every augment's settings block for the widget's slots, as {@link getAugmentSettings} returns them. */\n settings: readonly NamespacedAugmentSettings[];\n /** The widget's persisted per-augment values, keyed `[namespace][key]`. `undefined` when nothing has been saved yet; each field then shows its own `default`. */\n values: Record<string, Record<string, unknown>> | undefined;\n /** Called on every field edit with the namespace (augment id), the field key, and the new value. An emptied number field passes `undefined`. */\n onChange: (namespace: string, key: string, value: unknown) => void;\n}\n\n/**\n * Draws the settings augments add to a widget, one control per field: a\n * {@link Switch} for a `boolean` field, a text or number input otherwise.\n * Fields are namespaced by augment id, so two augments' settings with the same\n * key never collide. A widget with several slots joins the\n * {@link getAugmentSettings} result for each into one `settings` array. Renders\n * nothing when `settings` is empty.\n *\n * @example\n * ```tsx\n * <AugmentSettingsPanel\n * settings={getAugmentSettings(\"power-systems.sections\")}\n * values={draft.augmentSettings}\n * onChange={(augmentId, key, value) =>\n * setDraft((d) => ({\n * ...d,\n * augmentSettings: {\n * ...d.augmentSettings,\n * [augmentId]: { ...d.augmentSettings?.[augmentId], [key]: value },\n * },\n * }))\n * }\n * />\n * ```\n *\n * @category Extensions\n */\nexport function AugmentSettingsPanel({\n settings,\n values,\n onChange,\n}: Readonly<AugmentSettingsPanelProps>) {\n if (settings.length === 0) return null;\n\n return (\n <>\n {settings.flatMap((block) =>\n block.fields.map((field) => {\n const stored = values?.[block.namespace]?.[field.key];\n const current = stored ?? field.default;\n const label = field.label ?? field.key;\n const fieldId = `augment-setting-${block.namespace}-${field.key}`;\n\n if (field.type === \"boolean\") {\n return (\n <FieldRow key={`${block.namespace}.${field.key}`}>\n <Switch\n checked={Boolean(current)}\n onChange={(value) =>\n onChange(block.namespace, field.key, value)\n }\n label={label}\n />\n </FieldRow>\n );\n }\n\n return (\n <Field key={`${block.namespace}.${field.key}`}>\n <FieldLabel htmlFor={fieldId}>{label}</FieldLabel>\n <Input\n id={fieldId}\n type={field.type === \"number\" ? \"number\" : \"text\"}\n value={current === undefined ? \"\" : String(current)}\n onChange={(e) => {\n const raw = e.target.value;\n if (field.type !== \"number\") {\n onChange(block.namespace, field.key, raw);\n return;\n }\n // An emptied field stores nothing, so the default applies; a partial entry such as a lone \"-\" stores nothing at all.\n if (raw === \"\") {\n onChange(block.namespace, field.key, undefined);\n return;\n }\n const n = Number(raw);\n if (Number.isFinite(n))\n onChange(block.namespace, field.key, n);\n }}\n />\n </Field>\n );\n }),\n )}\n </>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Props for {@link AutoEmptyState}. Any other `div` attribute passes through\n * to the content region.\n *\n * @category EmptyState\n */\nexport interface AutoEmptyStateProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Shown while the content region renders no DOM nodes at all, for example\n * when every augment bound to a slot returned `null`. The switch is pure\n * CSS, so the caller never inspects what its children rendered.\n */\n fallback: ReactNode;\n /** Gap between rendered children. Defaults to `related-dense`. */\n gap?: GapToken;\n /** The content that may render nothing; when every child renders nothing, `fallback` shows instead. */\n children?: ReactNode;\n}\n\n/**\n * A content region (a vertical flex column) followed by a fallback that hides\n * itself once the region has rendered anything. Use it where a frame owns the\n * fallback but its content comes from augments or other children it cannot\n * inspect. It never scrolls on its own; the panel body it sits in scrolls.\n *\n * @example\n * ```tsx\n * import { AutoEmptyState, EmptyState, Text } from \"@ksp-gonogo/ui-kit\";\n *\n * interface Sensor {\n * id: string;\n * name: string;\n * on: boolean;\n * }\n *\n * // Renders nothing for a sensor that is switched off.\n * function SensorRow({ sensor }: { sensor: Sensor }) {\n * return sensor.on ? <Text>{sensor.name}</Text> : null;\n * }\n *\n * function Sensors({ sensors }: { sensors: readonly Sensor[] }) {\n * return (\n * <AutoEmptyState fallback={<EmptyState>No sensors active</EmptyState>}>\n * {sensors.map((s) => (\n * <SensorRow key={s.id} sensor={s} />\n * ))}\n * </AutoEmptyState>\n * );\n * }\n * ```\n *\n * @category EmptyState\n */\nexport function AutoEmptyState({\n fallback,\n gap = \"related-dense\",\n children,\n ...rest\n}: AutoEmptyStateProps) {\n return (\n <>\n <AutoEmptyState__Content $gap={gap} {...rest}>\n {children}\n </AutoEmptyState__Content>\n <AutoEmptyState__Fallback>{fallback}</AutoEmptyState__Fallback>\n </>\n );\n}\n\nconst AutoEmptyState__Content = styled.div<{ $gap: GapToken }>`\n display: flex;\n flex-direction: column;\n gap: ${({ $gap }) => GAP_VAR[$gap]};\n`;\n\nconst AutoEmptyState__Fallback = styled.div`\n ${AutoEmptyState__Content}:not(:empty) + & {\n display: none;\n }\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\nimport { Tooltip } from \"./Tooltip\";\nimport { TONE_MARK, TONE_TEXT } from \"./tone\";\n\n/**\n * One person in an {@link AvatarStack}.\n *\n * @category Badge\n */\nexport interface AvatarStackItem {\n /** Stable key. */\n id: string;\n /** The full name: the avatar's tooltip and accessible name. The initials are drawn from it. */\n name: string;\n /** Colours the avatar's edge and initials. Absent, neutral. */\n tone?: Tone;\n /** Makes this avatar a button that calls this when pressed. Absent, the avatar is not a control. */\n onSelect?: () => void;\n}\n\n/**\n * Props for {@link AvatarStack}.\n *\n * @category Badge\n */\nexport interface AvatarStackProps {\n /** Everyone to show, in order. The first ones are drawn and the rest fold into the overflow count. */\n items: readonly AvatarStackItem[];\n /**\n * How many places the stack has, the overflow count included. The stack\n * reserves this many places whatever `items` holds, so it never changes size.\n * Absent, 3.\n */\n max?: number;\n /** Names the group for assistive technology, when it is not already labelled by its surroundings. */\n label?: string;\n}\n\n/**\n * Up to two letters standing for a name: the first letters of its first two\n * words, or the first two letters of a single word.\n *\n * @category Badge\n */\nexport function initialsOf(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"?\";\n const letters =\n parts.length === 1\n ? Array.from(parts[0] ?? \"\").slice(0, 2)\n : parts.slice(0, 2).map((word) => Array.from(word)[0] ?? \"\");\n return letters.join(\"\").toUpperCase();\n}\n\nconst DEFAULT_MAX = 3;\n\n/**\n * A row of small round initials badges, overlapped, for showing who is\n * involved in something without room for their names. Each avatar's tooltip\n * and accessible name is the full name.\n *\n * Its footprint is fixed by `max` and never by the number of items: with more\n * people than places, the last place becomes a \"+k\" count whose tooltip lists\n * the names it hides, and with fewer the unused places stay empty.\n *\n * @example\n * ```tsx\n * <AvatarStack\n * label=\"Speaking\"\n * items={[{ id: \"a\", name: \"Ares 4\" }, { id: \"w\", name: \"Woomera Range\" }]}\n * />\n * ```\n *\n * @category Badge\n */\nexport function AvatarStack({\n items,\n max = DEFAULT_MAX,\n label,\n}: AvatarStackProps) {\n const places = Math.max(1, Math.floor(max));\n const overflowing = items.length > places;\n const shown = overflowing ? items.slice(0, places - 1) : items;\n const hidden = overflowing ? items.slice(places - 1) : [];\n return (\n <AvatarStack__Row\n $places={places}\n role=\"group\"\n {...(label === undefined ? {} : { \"aria-label\": label })}\n >\n {shown.map((item) => (\n <Tooltip\n key={item.id}\n text={item.name}\n announce={false}\n focusable={item.onSelect === undefined}\n >\n {item.onSelect === undefined ? (\n <AvatarStack__Face\n $tone={item.tone}\n role=\"img\"\n aria-label={item.name}\n >\n {initialsOf(item.name)}\n </AvatarStack__Face>\n ) : (\n <AvatarStack__Face\n as=\"button\"\n type=\"button\"\n $tone={item.tone}\n aria-label={item.name}\n onClick={item.onSelect}\n >\n {initialsOf(item.name)}\n </AvatarStack__Face>\n )}\n </Tooltip>\n ))}\n {hidden.length > 0 && (\n <Tooltip\n text={hidden.map((item) => item.name).join(\", \")}\n announce={false}\n focusable\n >\n <AvatarStack__Face\n $tone={undefined}\n role=\"img\"\n aria-label={`${hidden.length} more: ${hidden.map((item) => item.name).join(\", \")}`}\n >\n +{hidden.length}\n </AvatarStack__Face>\n </Tooltip>\n )}\n </AvatarStack__Row>\n );\n}\n\nconst AvatarStack__Row = styled.div<{ $places: number }>`\n /* Two insets short of a control, so a stack in a bordered control of that height always fits inside it. */\n --avatar-size: calc(var(--control-height) - 2 * var(--inset-avatar));\n /*\n * Initials are centred, so the later badge must leave the earlier one's\n * letters clear: the step is nearly the whole badge and only a sliver of\n * edge is covered.\n */\n --avatar-step: calc(var(--avatar-size) * 0.92);\n display: flex;\n align-items: center;\n flex: none;\n block-size: var(--avatar-size);\n /* One place is a whole avatar and every further one only its uncovered part. */\n inline-size: calc(\n var(--avatar-size) + (${({ $places }) => $places} - 1) * var(--avatar-step)\n );\n`;\n\nconst AvatarStack__Face = styled.span<{ $tone: Tone | undefined }>`\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: var(--avatar-size);\n block-size: var(--avatar-size);\n margin: 0;\n padding: 0;\n border: 1px solid ${({ $tone }) => TONE_MARK[$tone ?? \"neutral\"]};\n border-radius: var(--radius-circle);\n background: var(--color-surface-raised);\n color: ${({ $tone }) => TONE_TEXT[$tone ?? \"neutral\"]};\n font: inherit;\n font-size: var(--font-size-caption);\n font-weight: 600;\n line-height: var(--line-height-flush);\n white-space: nowrap;\n /* Each avatar overlaps the one before it, which stays on top. */\n &:not(:first-child) {\n margin-inline-start: calc(var(--avatar-step) - var(--avatar-size));\n }\n &:is(button) {\n cursor: pointer;\n }\n ${focusRing}\n &:focus-visible {\n position: relative;\n }\n`;\n","/**\n * A point in viewport (client) coordinates.\n *\n * @category Floating\n */\nexport interface AnchorPoint {\n /** Pixels from the viewport's left edge. */\n x: number;\n /** Pixels from the viewport's top edge. */\n y: number;\n}\n\n/** A measured box, or the viewport itself. */\nexport interface BoxSize {\n w: number;\n h: number;\n}\n\n/**\n * Where an anchored layer's top-left corner goes, in viewport coordinates.\n *\n * @category Floating\n */\nexport interface AnchoredPosition {\n /** Pixels from the viewport's left edge. */\n left: number;\n /** Pixels from the viewport's top edge. */\n top: number;\n}\n\n/** Distance from the anchor point to the layer's near corner. */\nconst GAP = 12;\n/** Minimum distance kept between the layer and the viewport edge. */\nconst EDGE = 8;\n\n/**\n * Where to put a `layer`-sized box anchored at `anchor` inside `viewport`, as\n * the position {@link Floating} uses. It goes 12px below and right of the\n * anchor by preference; on an axis where that runs past the edge it flips to\n * the anchor's other side rather than sliding, so it still reads as attached.\n * If neither side fits, it is clamped to stay 8px inside the viewport, and the\n * layer's own scroll box must carry the rest.\n *\n * @category Floating\n */\nexport function anchoredPosition(\n anchor: AnchorPoint,\n layer: BoxSize,\n viewport: BoxSize,\n): AnchoredPosition {\n return {\n left: place(anchor.x, layer.w, viewport.w),\n top: place(anchor.y, layer.h, viewport.h),\n };\n}\n\n/** One axis of the placement: after the anchor, else before it, else clamped. */\nfunction place(anchor: number, extent: number, bound: number): number {\n const after = anchor + GAP;\n if (after + extent <= bound - EDGE) return after;\n\n const before = anchor - GAP - extent;\n if (before >= EDGE) return before;\n\n return Math.max(EDGE, bound - EDGE - extent);\n}\n","import type { Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { magnitudeOf } from \"./magnitude\";\nimport { NULL_DISPLAY } from \"./NullValue\";\nimport { Unit, type UnitProps } from \"./Unit\";\nimport {\n UnitSharedFormat,\n useReadsAsOneFigure,\n useSharedFormat,\n} from \"./UnitSharedFormat\";\nimport { VisuallyHidden } from \"./VisuallyHidden\";\n\n/**\n * The en dash {@link Band} draws between its two ends. It is `aria-hidden`,\n * since a screen reader is given the interval in words. Import it to match or\n * assert on the separator rather than writing the character again.\n *\n * @category Unit\n */\nexport const INTERVAL_DASH = \"–\";\n\n/**\n * Props for {@link Band}. `decimals`, `format` and `as` pin both ends at once.\n *\n * @category Unit\n */\nexport interface BandProps<UnitSymbol extends string = string>\n extends Pick<UnitProps<UnitSymbol>, \"decimals\" | \"format\" | \"as\"> {\n /** The low end of the interval. */\n min?: Value<UnitSymbol> | null;\n /** The high end. */\n max?: Value<UnitSymbol> | null;\n /**\n * The modulus of a circular quantity, in the value's own unit: 360 for an\n * angle in degrees. Supplying it is what lets the band say a quantity went all\n * the way round instead of printing a meaningless width.\n */\n wrapsAt?: number;\n /** A class for the outer element, so a styled-component can extend it. */\n className?: string;\n}\n\n/**\n * A closed interval, rendered as its two ENDS and never collapsed to a\n * midpoint, since the width is often the point (a mean orbital element's width\n * says whether the orbit is stable).\n *\n * - A half-absent band is absent: with either end missing it draws the null\n * token, since one end alone would read as a scalar\n * - The precision follows the width: digits widen until the ends read\n * differently, so 6 700 km to 6 710 km never prints as `6.7 Mm` twice\n * - Both ends are one {@link UnitSharedFormat} group, which settles the unit\n * and digit count across the pair, so an interval is never written in two\n * units (`999 m` to `1.0 km`)\n * - With `wrapsAt`, a band spanning half the turn or more renders as\n * precessing rather than as an interval like `0°` to `359°`\n *\n * @category Unit\n */\nexport function Band<UnitSymbol extends string = string>({\n min,\n max,\n wrapsAt,\n className,\n ...pins\n}: BandProps<UnitSymbol>) {\n const low = magnitudeOf(min);\n const high = magnitudeOf(max);\n\n if (min == null || max == null || low === null || high === null) {\n return <Band__Body className={className}>{NULL_DISPLAY}</Band__Body>;\n }\n\n if (wrapsAt !== undefined && Math.abs(high - low) >= wrapsAt / 2) {\n // The producer's own sentinel: the angle swept far enough that no midpoint or half-width exists.\n return <Band__Body className={className}>(precesses)</Band__Body>;\n }\n\n // `separate`: an interval promises a width, so its ends may not print the same figure. The caller's pins apply once, to the group.\n return (\n <UnitSharedFormat of={min.unit} separate {...pins}>\n <BandEnds min={min} max={max} className={className} />\n </UnitSharedFormat>\n );\n}\n\n/**\n * The ends, drawn once the group has settled how both are written.\n *\n * Two ends that come out as the same text are drawn ONCE, marked\n * approximate (`~65.3 km`): the width is below what the display can show. The\n * rule is \"the display cannot tell them apart\", answered by the group, not a\n * tolerance.\n *\n * Both ends report to the group in every branch, so the group's answer\n * cannot depend on the branch chosen from it.\n */\nfunction BandEnds<UnitSymbol extends string = string>({\n min,\n max,\n className,\n}: {\n min: Value<UnitSymbol>;\n max: Value<UnitSymbol>;\n className?: string;\n}) {\n // Reported bare: pins here would take each end out of the group.\n useSharedFormat(min);\n useSharedFormat(max);\n const oneFigure = useReadsAsOneFigure(min);\n\n if (oneFigure) {\n return (\n <Band__Body className={className}>\n <Band__Approximate>\n {/* The tilde is hidden; the spoken word beside it replaces it. */}\n <span aria-hidden=\"true\">~</span>\n <VisuallyHidden>approximately </VisuallyHidden>\n {/* Either end: they render identically in this branch. */}\n <Unit value={min} />\n </Band__Approximate>\n </Band__Body>\n );\n }\n\n return (\n <Band__Body className={className}>\n <Unit value={min} />\n <Band__Dash aria-hidden=\"true\">{INTERVAL_DASH}</Band__Dash>\n <VisuallyHidden> to </VisuallyHidden>\n <Unit value={max} />\n </Band__Body>\n );\n}\n\nconst Band__Body = styled.span`\n display: inline-flex;\n align-items: baseline;\n gap: var(--gap-figure-parts);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n`;\n\nconst Band__Dash = styled.span`\n color: var(--color-text-faint);\n`;\n\n// The tilde and the figure are one phrase with no gap between them: `~65.3 km`, never `~ 65.3 km`.\nconst Band__Approximate = styled.span`\n display: inline-flex;\n align-items: baseline;\n white-space: nowrap;\n`;\n","import type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { SubjectHeading } from \"./SubjectHeading\";\nimport type { StaticElement } from \"./staticElement\";\n\n/**\n * A record's anatomy, as props: a name, badges on the name's line, a body of\n * figures, asides and a footer of actions. Use the props where they fit; the\n * parts (`Block.Title` and the rest) are for a record that needs its own\n * arrangement with the same type and spacing.\n */\nexport interface BlockAnatomyProps {\n /**\n * The block's heading, naming what it is about: larger and heavier than the\n * body under it. Replaces the HTML `title` tooltip attribute.\n */\n title?: ReactNode;\n /** Tag for `title`. Defaults to `div`; pass a heading where the outline wants one. */\n titleAs?: StaticElement;\n /**\n * Drawn BEFORE the title, on the title's own line: a marker, a rank pip, an\n * index. Not a status, which reads after the thing it is a status of.\n */\n titleLeft?: ReactNode;\n /**\n * Drawn AFTER the title and pushed to the end of its line: the badges that\n * say how its subject is doing. Wraps under the title rather than squeezing\n * it. Distinct from `right`, which sits beside (or below) the whole body.\n */\n titleRight?: ReactNode;\n /**\n * Content beside the title and body, on the left. Reflows to a full-width row\n * ABOVE the title when there is no room for both.\n */\n left?: ReactNode;\n /** Content beside the title and body, on the right. Reflows BELOW the body when narrow. */\n right?: ReactNode;\n /** Content across the top, above the title. It stays there at every width. */\n top?: ReactNode;\n /** Content across the bottom, below the footer. It stays there at every width. */\n bottom?: ReactNode;\n /** The actions and tags that close the block, spread across one wrapping row. */\n footer?: ReactNode;\n /** The body, under the title. */\n children?: ReactNode;\n}\n\n/**\n * Props for {@link Block}: the record anatomy (`title`, `titleLeft`,\n * `titleRight`, `left`, `right`, `top`, `bottom`, `footer`) plus any `div`\n * attribute.\n *\n * @category Layout\n */\nexport interface BlockProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"title\">,\n BlockAnatomyProps {\n /**\n * Rendered tag. Defaults to `div`. A record in a list wants `li`, a\n * self-contained record `article`, a banner `section`.\n */\n as?: StaticElement;\n}\n\n/** The name, sized as one. Type belongs to the arrangement, so `Card.Title` is this same object. */\nconst Block__Title = styled.div`\n color: var(--color-text-primary);\n font-weight: 600;\n /* One rung above the body the arrangement sets; both halves are declared here, so a site cannot break the step. */\n font-size: var(--font-size-value);\n line-height: var(--line-height-tight);\n /* A content-sized basis, so a long title wraps instead of running into the badge beside it. */\n flex: 1 1 auto;\n min-width: 0;\n`;\n\n/** The lead-in and the name, as one subject for the heading row to push against. */\nconst Block__TitleLead = styled.div`\n display: flex;\n align-items: baseline;\n gap: var(--gap-record-lead);\n flex: 1 1 auto;\n min-width: 0;\n`;\n\n/**\n * Props for `Block.TitleRow` (and `Card.TitleRow`): `left` is drawn before the\n * name, `right` is pushed to the end of the name's line.\n *\n * @category Layout\n */\nexport interface BlockTitleRowProps {\n /** Drawn before the name, such as an icon or a mark. Absent, the name starts the line. */\n left?: ReactNode;\n /** Pushed to the end of the name's line, usually the subject's state. Absent, nothing follows the name. */\n right?: ReactNode;\n /** The name itself. */\n children?: ReactNode;\n}\n\n/** The name's line: an optional lead-in, the name, and the state pushed to the end, via `SubjectHeading`. */\nfunction Block__TitleRow({ left, right, children }: BlockTitleRowProps) {\n return (\n <SubjectHeading align=\"baseline\" status={right}>\n {left == null ? (\n children\n ) : (\n <Block__TitleLead>\n {left}\n {children}\n </Block__TitleLead>\n )}\n </SubjectHeading>\n );\n}\n\n/**\n * The row the side asides sit in. The wrap IS the collapse: a `left` aside with\n * nowhere to go lands above the body, a `right` aside below it. Nothing is ever\n * hidden, unlike `Panel`'s aside.\n */\nconst Block__Middle = styled.div`\n display: flex;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: var(--gap-related);\n`;\n\n/**\n * What the side asides sit beside: the title row and the body, as one column,\n * so an aside spans both. `--block-body-floor` is the width below which the\n * body stops sharing a line; a zero basis would shrink forever and never wrap.\n */\nconst Block__Body = styled.div`\n display: flex;\n flex-direction: column;\n gap: var(--gap-related);\n flex: 1 1 var(--block-body-floor, 9rem);\n min-width: 0;\n`;\n\n/** A block-level aside. The side variants step `--radius-display-frame` down, so a `FramedDisplay` in `left` gets a corner proportioned to its small box. */\nconst Block__Aside = styled.div<{ $side?: boolean }>`\n min-width: 0;\n ${({ $side }) =>\n $side\n ? `flex: 0 0 auto;\n --radius-display-frame: var(--radius-display-frame-aside);`\n : \"\"}\n`;\n\n/** The actions and tags that close the record. Wraps rather than overflowing. */\nconst Block__Footer = styled.div`\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--gap-related);\n flex-wrap: wrap;\n`;\n\n/** The arrangement with no surface. `Card` and `Notice` extend it; outside this package the door is `Block`. */\nexport const Block__Root = styled.div`\n display: flex;\n flex-direction: column;\n gap: var(--gap-related);\n min-width: 0;\n /* The compact body size, declared here so the title stays one rung above it. */\n font-size: var(--font-size-compact);\n`;\n\n/**\n * Renders the anatomy inside whichever root it is handed. The title row and\n * children are direct children of the root while there is no side aside, so a caller may restyle\n * the root's flex direction.\n */\nexport function renderBlockAnatomy(\n Root: ElementType,\n {\n title,\n titleAs,\n titleLeft,\n titleRight,\n left,\n right,\n top,\n bottom,\n footer,\n children,\n ...rest\n }: BlockAnatomyProps & Record<string, unknown>,\n): ReactNode {\n const hasTitleRow = title != null || titleLeft != null || titleRight != null;\n const hasSideAside = left != null || right != null;\n const heading = hasTitleRow && (\n <Block__TitleRow left={titleLeft} right={titleRight}>\n {title != null && <Block__Title as={titleAs}>{title}</Block__Title>}\n </Block__TitleRow>\n );\n return (\n <Root {...rest}>\n {top != null && <Block__Aside $side={false}>{top}</Block__Aside>}\n {hasSideAside ? (\n <Block__Middle>\n {left != null && <Block__Aside $side>{left}</Block__Aside>}\n <Block__Body>\n {heading}\n {children}\n </Block__Body>\n {right != null && <Block__Aside $side>{right}</Block__Aside>}\n </Block__Middle>\n ) : (\n <>\n {heading}\n {children}\n </>\n )}\n {footer != null && <Block__Footer>{footer}</Block__Footer>}\n {bottom != null && <Block__Aside $side={false}>{bottom}</Block__Aside>}\n </Root>\n );\n}\n\n/** The parts, hung off every surface that composes them: `Card.Title === Block.Title`. */\nexport const BLOCK_PARTS = {\n Title: Block__Title,\n TitleRow: Block__TitleRow,\n Body: Block__Body,\n Aside: Block__Aside,\n Footer: Block__Footer,\n} as const;\n\nfunction BlockRoot({ ...props }: BlockProps): ReactNode {\n return renderBlockAnatomy(Block__Root, props);\n}\n\n/**\n * The arrangement of a record with no surface: a name sized as a heading, a\n * body under it, asides on any of four sides, and a footer. {@link Card} is the\n * same arrangement on a sunken surface.\n *\n * `titleRight` is not `right`: `titleRight` holds badges on the name's line and\n * wraps under the name when narrow, while `right` sits beside the whole body\n * and drops below it when narrow. The parts are there for a record that needs\n * its own composition but the same type and spacing:\n *\n * - `Block.Title`: the heading text, sized and weighted as the `title` prop is\n * - `Block.TitleRow`: the title's line, holding `left`, the title and `right` badges\n * - `Block.Body`: the figures under the title\n * - `Block.Aside`: content beside or below the body (the `left`, `right`, `top` and `bottom` props)\n * - `Block.Footer`: the line of actions at the foot\n *\n * @example\n * ```tsx\n * <Block\n * as=\"li\"\n * title={contract.title}\n * titleRight={<Badge tone=\"warn\">Expires soon</Badge>}\n * footer={<Button onClick={onAccept}>Accept</Button>}\n * >\n * <Text level=\"muted\">{contract.synopsis}</Text>\n * </Block>\n * ```\n *\n * @category Layout\n */\nexport const Block = Object.assign(BlockRoot, BLOCK_PARTS);\n","import type { ReactNode } from \"react\";\nimport { Cluster, type ClusterAlign } from \"./Cluster\";\nimport type { GapToken } from \"./scales\";\n\n/**\n * Props for {@link SubjectHeading}.\n *\n * @category Typography\n */\nexport interface SubjectHeadingProps {\n /** What the line is about: a program's title, a course's name, a vessel. */\n children: ReactNode;\n /**\n * That subject's state, as a `Badge` or a short run of them. Drawn AFTER the\n * subject and pushed to the end of the line; absent when there is no state\n * worth showing, which leaves the subject alone on the line rather than\n * beside a gap.\n */\n status?: ReactNode;\n /** Gap between the subject and its state. Defaults to `related-packed`. */\n gap?: GapToken;\n /**\n * `align-items`, for a subject that wraps to two lines beside a one-line\n * badge: `start` keeps the badge level with the first line of the name\n * instead of floating in the middle of it.\n */\n align?: ClusterAlign;\n}\n\n/**\n * A subject and its state on one line, subject first and the state pushed to\n * the end. The order is fixed, so the state always reads after the thing it\n * describes, to an eye and to a screen reader.\n *\n * The line wraps: a long subject drops its badge onto a second line rather\n * than squeezing the name.\n *\n * @example\n * ```tsx\n * import { Badge, SubjectHeading, Text } from \"@ksp-gonogo/ui-kit\";\n *\n * <SubjectHeading\n * status={\n * <Badge tone=\"go\">Active</Badge>\n * }\n * >\n * <Text weight=\"semibold\">Kerbin Science Program</Text>\n * </SubjectHeading>\n * ```\n *\n * @category Typography\n */\nexport function SubjectHeading({\n children,\n status,\n gap = \"related-packed\",\n align = \"center\",\n}: Readonly<SubjectHeadingProps>) {\n return (\n <Cluster align={align} gap={gap} justify=\"between\" wrap>\n {children}\n {status}\n </Cluster>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { INSET_NAME, type InsetToken, RADIUS_VAR } from \"./scales\";\n\n/**\n * The background tiers {@link Box} can paint: `app` is the page behind every\n * widget, `panel` a widget's own body, `raised` something lifted off a panel\n * (a card, a popover) and `sunken` a well set into one.\n *\n * @category Layout\n */\nexport type BoxSurface = \"app\" | \"panel\" | \"raised\" | \"sunken\";\n\n/**\n * The corner radii {@link Box} accepts: `regular` for ordinary controls, rows\n * and cards, `floating` for something that sits above the app (a dialog, a\n * menu), and `pill` for a fully rounded stadium. {@link FramedDisplay} has a corner\n * of its own that a `Box` cannot take.\n *\n * @category Layout\n */\nexport type BoxRadius = \"regular\" | \"floating\" | \"pill\";\n\n/**\n * The padding names {@link Box}'s `pad` prop accepts, the same set as\n * {@link InsetToken}.\n *\n * @category Layout\n */\nexport type BoxPad = InsetToken;\n\n/**\n * Props for {@link Box}. Any other `div` attribute passes through.\n *\n * @category Layout\n */\nexport interface BoxProps extends HTMLAttributes<HTMLDivElement> {\n /** Background surface tier. Omit for a transparent box. */\n surface?: BoxSurface;\n /** Padding, as the name of a surface inset. */\n pad?: BoxPad;\n /** Adds a `1px solid` subtle border. Defaults to `false`. */\n bordered?: boolean;\n /** Corner radius. Omit for square corners. */\n radius?: BoxRadius;\n /** The content inside the padding and border. */\n children?: ReactNode;\n}\n\nconst SURFACE_VAR: Record<BoxSurface, string> = {\n app: \"var(--color-surface-app)\",\n panel: \"var(--color-surface-panel)\",\n raised: \"var(--color-surface-raised)\",\n sunken: \"var(--color-surface-sunken)\",\n};\n\n/**\n * A `div` that paints a surface: background tier, padding, a subtle border and\n * a corner radius, each optional. With no props it is a plain `div`. Use it for\n * a well or an inset block; use {@link Card} for a titled item in a list.\n *\n * @category Layout\n */\nexport function Box({\n surface,\n pad,\n bordered = false,\n radius,\n children,\n ...rest\n}: BoxProps) {\n return (\n <Box__Root\n $surface={surface}\n $pad={pad}\n $bordered={bordered}\n $radius={radius}\n {...rest}\n >\n {children}\n </Box__Root>\n );\n}\n\nconst Box__Root = styled.div<{\n $surface?: BoxSurface;\n $pad?: BoxPad;\n $bordered: boolean;\n $radius?: BoxRadius;\n}>`\n ${({ $surface }) => $surface && `background: ${SURFACE_VAR[$surface]};`}\n ${({ $bordered }) => $bordered && `border: 1px solid var(--color-border-subtle);`}\n ${({ $radius }) => $radius && `border-radius: ${RADIUS_VAR[$radius]};`}\n ${({ $pad }) => $pad && `padding: var(${INSET_NAME[$pad]});`}\n`;\n","import { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Props for {@link ButtonGroup}. Any other `div` attribute passes through, and\n * the ref reaches the root `div`.\n *\n * @category Button\n */\nexport interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Give every button the width of the widest one. On by default, since a row of\n * actions that differ only in the length of their word reads as a mistake.\n * Turn it off for a group whose members are meant to size to their own content.\n */\n equalWidth?: boolean;\n /** Gap between buttons, one of the {@link GapToken} names. Omitted, it is the container's `related` gap. */\n gap?: GapToken;\n /** The buttons to lay out together. */\n children?: ReactNode;\n}\n\n/**\n * A row of related buttons that share a size: the same height whatever each\n * button's own `size` or content, and by default the same width. It hugs its\n * buttons rather than filling the row, so place it with a layout primitive.\n *\n * Name the group with `aria-label` when it is a toolbar a screen reader should\n * announce as one.\n *\n * @example\n * ```tsx\n * <ButtonGroup>\n * <Button variant=\"primary\">Accept</Button>\n * <Button>Decline</Button>\n * </ButtonGroup>\n * ```\n *\n * @category Button\n */\nexport const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(\n function ButtonGroup({ equalWidth = true, gap, children, ...rest }, ref) {\n return (\n <ButtonGroup__Root\n ref={ref}\n $equalWidth={equalWidth}\n $gap={gap}\n {...rest}\n >\n {children}\n </ButtonGroup__Root>\n );\n },\n);\n\nconst ButtonGroup__Root = styled.div<{ $equalWidth: boolean; $gap?: GapToken }>`\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: ${({ $equalWidth }) => ($equalWidth ? \"1fr\" : \"auto\")};\n align-items: stretch;\n width: max-content;\n max-width: 100%;\n gap: ${({ $gap }) => ($gap ? GAP_VAR[$gap] : \"var(--gap-related)\")};\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport {\n BLOCK_PARTS,\n Block__Root,\n type BlockProps,\n renderBlockAnatomy,\n} from \"./Block\";\nimport { toneEdge } from \"./tone\";\n\n/**\n * Props for {@link Card}: everything {@link BlockProps} takes, plus the\n * surface's own marks.\n *\n * @category Layout\n */\nexport interface CardProps extends BlockProps {\n /**\n * The record's status, drawn as a 2px rule down the leading edge. For what\n * the record is, use `identityColor`.\n */\n tone?: Tone;\n /**\n * Dims the card to signal unavailable-but-still-listed: a crew member on a\n * mission, a part not yet unlocked. Its text drops to the muted tier, which\n * still reads at 4.5:1, and its `tone` rule and identity tab go half-strength.\n */\n dimmed?: boolean;\n /**\n * What the record is, drawn as a short tab centred on the top edge: a\n * resource's own colour, a category's hue. Not a status, which is `tone`;\n * a card can have both.\n *\n * A CSS colour, not a resource name: resolve a name first with\n * {@link resourceColor}, as in `identityColor={resourceColor(name)}`.\n */\n identityColor?: string;\n /**\n * The roomier inset for a card that is its screen rather than one record in a\n * list, such as a panel of device cards each holding its own controls. Only\n * this card's padding changes, not the spacing of what it holds.\n */\n standalone?: boolean;\n /** What the card holds. */\n children?: ReactNode;\n}\n\n/** Wider than the 2px `tone` rule, so identity reads as a different mark from status. */\nconst STRIP_WIDTH = \"3px\";\n\n/**\n * `Block` plus a surface, as a styled extension of the same root.\n *\n * A card is the COMPACT density tier: it re-declares `--gap-related` and\n * `--gap-section` one rung down, so anything inside it written as\n * `gap: var(--gap-related)` tightens without a conditional, including a third\n * party's CSS. Only those two gap names step; insets do not, because a chip\n * inside a card is still a chip.\n */\nconst Card__Root = styled(Block__Root)<{\n $tone?: Tone;\n $dimmed?: boolean;\n $identityColor?: string;\n $standalone?: boolean;\n}>`\n --gap-related: var(--gap-related-compact);\n --gap-section: var(--gap-section-compact);\n --bleed-inline: 0px;\n\n position: relative;\n background: var(--color-surface-sunken);\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n ${({ $standalone }) =>\n $standalone\n ? \"padding: var(--inset-surface-standalone);\"\n : \"padding: var(--inset-surface);\"}\n ${({ $tone }) => ($tone ? `border-left: 2px solid ${toneEdge($tone)};` : \"\")}\n ${({ $identityColor }) =>\n $identityColor\n ? `\n &::before {\n content: \"\";\n position: absolute;\n top: -1px;\n left: 50%;\n transform: translateX(-50%);\n width: var(--size-mark);\n height: ${STRIP_WIDTH};\n background: ${$identityColor};\n border-radius: var(--radius-regular) var(--radius-regular) 0 0;\n }\n `\n : \"\"}\n ${({ $dimmed, $tone }) =>\n $dimmed\n ? `\n --color-text-primary: var(--color-text-muted);\n color: var(--color-text-muted);\n ${$tone ? `border-left-color: color-mix(in srgb, ${toneEdge($tone)} 50%, transparent);` : \"\"}\n &::before {\n opacity: 0.5;\n }\n `\n : \"\"}\n`;\n\nfunction CardRoot({\n tone,\n dimmed,\n identityColor,\n standalone,\n ...props\n}: CardProps): ReactNode {\n return renderBlockAnatomy(Card__Root, {\n ...props,\n $tone: tone,\n $dimmed: dimmed,\n $identityColor: identityColor,\n $standalone: standalone,\n });\n}\n\n/**\n * A record on a sunken surface: {@link Block}'s arrangement inside a bordered,\n * padded box. Its parts are `Block`'s own (`Card.Title === Block.Title`); use\n * `Block` for the grouping without the box.\n *\n * Inside a card the `related` and `section` gaps are one step smaller, so a\n * {@link Stack} or {@link Cluster} with no `gap` of its own is tighter there\n * without being told. Padding sizes do not change.\n *\n * @example\n * ```tsx\n * <Card\n * title={tank.resource}\n * titleRight={tank.low && <Badge tone=\"warn\">Low</Badge>}\n * tone={tank.low ? \"warn\" : undefined}\n * dimmed={tank.locked}\n * identityColor={resourceColor(tank.resource)}\n * >\n * <Unit value={tank.amount} />\n * </Card>\n * ```\n *\n * @category Layout\n */\nexport const Card = Object.assign(CardRoot, BLOCK_PARTS);\n","import { classifyCommandRejection } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport styled, { css, keyframes } from \"styled-components\";\nimport { Button__Body, type ButtonTone } from \"../Button\";\nimport type { CommandDelayHandle } from \"../CommandDelay/CommandDelay\";\nimport {\n type CommandFoundLike,\n commandFoundSentence,\n} from \"../CommandDelay/commandFoundSentence\";\nimport { commandLossSentence } from \"../CommandDelay/commandLossSentence\";\nimport { commandOutcomes } from \"../CommandDelay/commandOutcomes\";\nimport {\n type CommandRefusalLike,\n commandGateSentence,\n commandRefusalSentence,\n} from \"../CommandDelay/commandRefusalSentence\";\nimport { FitLabel } from \"../FitLabelButton\";\nimport { LiveRegion } from \"../LiveRegion\";\nimport { Tooltip } from \"../Tooltip\";\nimport { InFlightFace } from \"./InFlightFace\";\n\n/**\n * How long, in milliseconds, an armed {@link CommandButton} waits for its confirming press before it returns to rest.\n *\n * @category CommandButton\n */\nexport const ARM_TIMEOUT_MS = 4000;\n\n/**\n * How long a refusal stays on the control before it returns to rest: long\n * enough to read, not forever, because the situation the game refused on can\n * change.\n */\nexport const REFUSAL_TIMEOUT_MS = 8000;\n\n/**\n * The outer backstop on a pending dispatch. `send()`'s promise is guaranteed\n * to settle; this is a belt for a handle from some other source that makes\n * no such guarantee, never the primary clear.\n */\nexport const PENDING_BACKSTOP_MS = 30_000;\n\n/**\n * What the mod says about a command before it is pressed. Declared structurally; `useCommand`'s `gate` satisfies it.\n *\n * @category CommandButton\n */\nexport interface CommandGateLike extends CommandRefusalLike {\n /** The game EVALUATED this and said no. */\n blocked: boolean;\n /**\n * The mod could not evaluate this command's gates (the checks it runs\n * before a command is pressed) at all, as in a sandbox save, which has no\n * career to check against. The control is drawn as ordinary, not darkened, and carries\n * `data-gate` to say so.\n */\n undetermined?: boolean;\n}\n\n/**\n * What a command reply is known to be BEFORE you know which command produced\n * it: the result envelope, carrying the command's own value on `payload`. The\n * default {@link CommandButtonHandle} reply, so a reader cannot treat the\n * envelope as the payload; `payload` stays `unknown` until narrowed.\n *\n * @category CommandButton\n */\nexport interface CommandReplyLike {\n /** Whether the command ran. False pairs with the refusal the handle surfaces. */\n success: boolean;\n /** The command's own value, when it has one. `unknown` until the reader knows which command this is. */\n payload?: unknown;\n}\n\n/**\n * The command handle a {@link CommandButton} dispatches on: the delay-rail handle plus a\n * way to send. Declared structurally; {@link useCommand}'s return value satisfies it.\n *\n * @category CommandButton\n */\nexport interface CommandButtonHandle<Result = CommandReplyLike, Args = unknown>\n extends CommandDelayHandle {\n // A method rather than a function-valued property, so strictFunctionTypes does not check typed args contravariantly and reject every typed handle.\n /**\n * Dispatch. The promise resolves when the command is confirmed and rejects\n * when it is refused, lost, or the machinery failed, so the control clears its\n * own pending state with no per-command telemetry predicate. A refusal\n * rejects with an error carrying its `CommandErrorCode`: read it with\n * `classifyCommandRejection` from `@ksp-gonogo/sitrep-sdk`.\n *\n * `Result` carries the reply's real type to\n * {@link CommandButtonProps.onConfirmed}; a handle from `useCommand(\"...\")`\n * supplies it from the generated command map.\n */\n send(args?: Args, opts?: { label?: string; topic?: string }): Promise<Result>;\n /**\n * The standing gate verdict, when the mod publishes one for this command.\n * Absent means nothing is known in advance.\n */\n gate?: CommandGateLike;\n /**\n * The standing verdict for a call with these arguments, when the command's\n * gate depends on which item they name. `useCommand` supplies it; a control\n * that sends arguments reads it in preference to {@link gate}.\n */\n gateFor?(args: Args): CommandGateLike | undefined;\n}\n\n/**\n * The standing verdict a control sending `args` draws itself from: the\n * per-item one when the handle can say, else the command's own.\n *\n * @category CommandButton\n */\nexport function standingGate<Args>(\n handle: Pick<CommandButtonHandle<unknown, Args>, \"gate\" | \"gateFor\">,\n args: Args | undefined,\n): CommandGateLike | undefined {\n return handle.gateFor ? handle.gateFor(args as Args) : handle.gate;\n}\n\n/**\n * Where a {@link CommandButton} is in the command lifecycle.\n *\n * - `idle`: at rest\n * - `armed`: the operator has asked, and is being asked to mean it. Only\n * reachable when the caller supplied a `confirmLabel`\n * - `pending`: dispatched, nothing back yet (the signal-delay window)\n * - `refused`: the game evaluated it and said no. A retry changes nothing until\n * the situation does\n * - `lost`: nothing came back. Not `idle`, which would look like a confirmed\n * command, and not `refused`, because the game decided nothing and the\n * command may have executed\n * - `found`: this control lost a command, and that command has since replied.\n * Not a confirmation: the operator may already have re-sent it\n * - `blocked`: the game will refuse this, and said so before anyone pressed.\n * The control is dark but NOT `disabled`: it carries `aria-disabled`, stays\n * focusable, and responds to a press by showing why. A gate verdict is\n * advice, sampled and possibly a beat behind, and the dispatch is evaluated\n * again by the game anyway\n *\n * @category CommandButton\n */\nexport type CommandButtonPhase =\n | \"idle\"\n | \"armed\"\n | \"pending\"\n | \"refused\"\n | \"lost\"\n | \"found\"\n | \"blocked\";\n\n/**\n * The tones a {@link CommandButton} can draw in; the same set as a plain `Button`.\n *\n * @category CommandButton\n */\nexport type CommandButtonTone = ButtonTone;\n\n/**\n * The two sizes of {@link CommandButton}.\n *\n * @category CommandButton\n */\nexport type CommandButtonSize = \"sm\" | \"md\";\n\n/**\n * Options for {@link useCommandButton}.\n *\n * @category CommandButton\n */\nexport interface UseCommandButtonOptions<\n Result = CommandReplyLike,\n Args = unknown,\n> {\n /** The command the control dispatches, usually the return of `useCommand`. */\n handle: CommandButtonHandle<Result, Args>;\n /** Args for the dispatch. See {@link CommandButtonProps.args}. */\n args?: NoInfer<Args>;\n /** The dispatch's operator-facing description, which a refusal or loss sentence is named after. */\n commandLabel?: string;\n /** Receives the dispatch's resolved result. See {@link CommandButtonProps.onConfirmed}. */\n onConfirmed?: (result: Result) => void;\n}\n\n/**\n * What {@link useCommandButton} returns: the current phase, a flag per phase,\n * the composed sentences to show, and `press`.\n *\n * @category CommandButton\n */\nexport interface CommandButtonState {\n /** The current phase. `blocked` whenever the gate refuses and no outcome or dispatch is standing. */\n phase: CommandButtonPhase;\n /** Dispatched, nothing back. */\n isPending: boolean;\n /** Asking the operator to mean it. */\n isArmed: boolean;\n /** The game said no, and `refusalText` says what it said. */\n isRefused: boolean;\n /** Nothing came back. Not a verdict, and not the same as at rest. */\n isLost: boolean;\n /** A command this control lost has since replied. `foundText` says what the reply was. */\n isFound: boolean;\n /**\n * The game will refuse this if it is pressed, and said so in advance.\n * `refusalText` carries the reason here too, so a caller renders one string\n * whichever side of the press it is on.\n */\n isBlocked: boolean;\n /**\n * The operator pressed a blocked control and is being told why: the reason\n * made visible on demand, for the sighted keyboard user a `title` never\n * reaches.\n */\n isShowingReason: boolean;\n /**\n * The composed sentence, or `null` when there is nothing to say: a refusal\n * once one has happened, else the standing gate reason.\n */\n refusalText: string | null;\n /**\n * What the recovered command turned out to have done, composed, or `null`\n * outside the `found` phase. Kept apart from `refusalText`, which is true at a\n * different moment.\n */\n foundText: string | null;\n /**\n * What is known about a command that got no reply, composed, or `null`\n * outside the `lost` phase: the sentence `CommandButton` speaks for it.\n */\n lossText: string | null;\n /** This handle has a dispatch with no reply (overdue or lost), for the `data-unconfirmed` tint. */\n hasUnconfirmed: boolean;\n /** This handle has a dispatch that never left, for the `data-failed` tint. */\n hasFailure: boolean;\n /**\n * The control was pressed. Advances the machine: arm, then dispatch; a press\n * while pending is ignored; a press while refused, lost or found clears that\n * outcome; a press while BLOCKED dispatches nothing and shows the reason\n * instead. `armable` says whether there is a confirm step, which only the\n * caller knows.\n */\n press: (armable: boolean) => void;\n}\n\n/**\n * The {@link CommandButton} lifecycle with no rendering attached, for a control\n * that looks different. The hook keeps the phase, the arm timeout and the\n * reply handling; the caller draws each phase and wires its own click to\n * `press`. `CommandButton` is this hook plus the default rendering.\n *\n * @example\n * ```tsx\n * import { useCommand } from \"@ksp-gonogo/sitrep-sdk\";\n * import { useCommandButton } from \"@ksp-gonogo/ui-kit\";\n *\n * function RecoverButton() {\n * const recover = useCommand(\"ksp.recover\");\n * const { isArmed, isPending, isRefused, refusalText, press } =\n * useCommandButton({ handle: recover, commandLabel: \"Recover\" });\n * const word = isPending\n * ? \"Recovering...\"\n * : isRefused\n * ? \"Refused\"\n * : isArmed\n * ? \"Confirm recover\"\n * : \"Recover\";\n * return (\n * <button\n * type=\"button\"\n * aria-busy={isPending || undefined}\n * title={refusalText ?? undefined}\n * // `true` asks for a confirm step: the first press arms, the second sends.\n * onClick={() => press(true)}\n * >\n * {word}\n * </button>\n * );\n * }\n * ```\n *\n * @category CommandButton\n */\nexport function useCommandButton<Result = CommandReplyLike, Args = unknown>({\n handle,\n args,\n commandLabel,\n onConfirmed,\n}: UseCommandButtonOptions<Result, Args>): CommandButtonState {\n const [phase, setPhase] = useState<CommandButtonPhase>(\"idle\");\n const [refusal, setRefusal] = useState<CommandRefusalLike | null>(null);\n const [found, setFound] = useState<CommandFoundLike | null>(null);\n // A press on a blocked control shows its reason, cleared on the refusal window.\n const [reasonShown, setReasonShown] = useState(false);\n\n const gate = standingGate(handle, args);\n // An undetermined gate is NOT a refusal and must not darken anything.\n const gateBlocks = gate?.blocked === true;\n\n // A late reply must not set state after unmount or overwrite a newer dispatch's pending.\n const mountedRef = useRef(true);\n const dispatchSeqRef = useRef(0);\n useEffect(\n () => () => {\n mountedRef.current = false;\n },\n [],\n );\n\n /*\n * A command THIS control lost, turning up answered. One handle often serves\n * many rows, so the phase is gated on `awaitingFoundRef` (set when this\n * control's own dispatch settled lost). Watched off the handle, because the\n * dispatch promise already rejected as lost and never settles again.\n */\n const founds = handle.founds;\n const awaitingFoundRef = useRef(false);\n const foundsSeenRef = useRef(founds?.length ?? 0);\n useEffect(() => {\n const count = founds?.length ?? 0;\n const grew = count > foundsSeenRef.current;\n foundsSeenRef.current = count;\n const latest = founds?.[count - 1];\n if (!grew || !latest || !awaitingFoundRef.current) return;\n awaitingFoundRef.current = false;\n setFound(latest);\n setRefusal(null);\n setPhase(\"found\");\n }, [founds]);\n\n // Auto-disarm, so a forgotten arm does not sit live indefinitely.\n useEffect(() => {\n if (phase !== \"armed\") return;\n const id = setTimeout(() => setPhase(\"idle\"), ARM_TIMEOUT_MS);\n return () => clearTimeout(id);\n }, [phase]);\n\n // Let a refusal, a silence or a found be read, then return to rest.\n useEffect(() => {\n if (phase !== \"refused\" && phase !== \"lost\" && phase !== \"found\") return;\n const id = setTimeout(() => {\n setPhase(\"idle\");\n setRefusal(null);\n // The rail holds a found until dismissed; this is only the echo on the control.\n setFound(null);\n }, REFUSAL_TIMEOUT_MS);\n return () => clearTimeout(id);\n }, [phase]);\n\n useEffect(() => {\n if (!reasonShown) return;\n const id = setTimeout(() => setReasonShown(false), REFUSAL_TIMEOUT_MS);\n return () => clearTimeout(id);\n }, [reasonShown]);\n\n // The gate reopening takes the reason down with it.\n useEffect(() => {\n if (!gateBlocks) setReasonShown(false);\n }, [gateBlocks]);\n\n useEffect(() => {\n if (phase !== \"pending\") return;\n const id = setTimeout(() => setPhase(\"idle\"), PENDING_BACKSTOP_MS);\n return () => clearTimeout(id);\n }, [phase]);\n\n const dispatch = useCallback(() => {\n const seq = dispatchSeqRef.current + 1;\n dispatchSeqRef.current = seq;\n setPhase(\"pending\");\n setRefusal(null);\n const settle = (\n next: CommandButtonPhase,\n reason: CommandRefusalLike | null,\n ) => {\n if (!mountedRef.current || dispatchSeqRef.current !== seq) return;\n setRefusal(reason);\n setPhase(next);\n };\n handle.send(args, commandLabel ? { label: commandLabel } : undefined).then(\n (result: Result) => {\n settle(\"idle\", null);\n onConfirmed?.(result);\n },\n (err: unknown) => {\n const rejection = classifyCommandRejection(err);\n if (rejection.kind === \"lost\") {\n // From here on, a found landing on the handle is this control's.\n awaitingFoundRef.current = true;\n settle(\"lost\", null);\n return;\n }\n if (rejection.kind !== \"refused\") {\n // A machinery failure is the rail's to report, not this control's.\n settle(\"idle\", null);\n return;\n }\n settle(\"refused\", {\n errorCode: rejection.errorCode,\n reason: rejection.reason,\n command: rejection.command,\n args: rejection.args,\n label: rejection.label ?? commandLabel,\n breach: rejection.breach,\n // The game's own words, which `commandRefusalSentence` prefers over its general clause.\n detail: rejection.detail,\n });\n },\n );\n }, [handle, args, commandLabel, onConfirmed]);\n\n // The rail is the primary outcome surface; these tints only say WHICH control issued the command.\n const { hasUnconfirmed, hasFailure } = commandOutcomes(handle);\n\n const press = useCallback(\n (armable: boolean) => {\n if (phase === \"pending\") return;\n // The game already said it will refuse this, so the press shows why instead of spending a round trip.\n if (gateBlocks) {\n setReasonShown(true);\n return;\n }\n // A press on an outcome clears it and starts the handshake over, rather than dispatching straight back.\n if (phase === \"refused\" || phase === \"lost\" || phase === \"found\") {\n setRefusal(null);\n setFound(null);\n setPhase(\"idle\");\n return;\n }\n if (armable && phase !== \"armed\") {\n setPhase(\"armed\");\n return;\n }\n dispatch();\n },\n [phase, gateBlocks, dispatch],\n );\n\n // A real outcome outranks a standing gate, and a command already travelling is not stopped by a gate that shut behind it.\n const effectivePhase: CommandButtonPhase =\n phase === \"pending\" ||\n phase === \"refused\" ||\n phase === \"lost\" ||\n phase === \"found\"\n ? phase\n : gateBlocks\n ? \"blocked\"\n : phase;\n\n return {\n phase: effectivePhase,\n isPending: effectivePhase === \"pending\",\n isArmed: effectivePhase === \"armed\",\n isRefused: effectivePhase === \"refused\",\n isLost: effectivePhase === \"lost\",\n isFound: effectivePhase === \"found\",\n isBlocked: effectivePhase === \"blocked\",\n isShowingReason: effectivePhase === \"blocked\" && reasonShown,\n refusalText: refusal\n ? commandRefusalSentence(refusal)\n : effectivePhase === \"blocked\" && gate\n ? // The mod's gate is per command, so the caller's label says which row went dark.\n commandGateSentence({\n ...gate,\n label: gate.label ?? commandLabel,\n args: gate.args ?? args,\n })\n : null,\n foundText: found ? commandFoundSentence(found) : null,\n lossText:\n effectivePhase === \"lost\"\n ? commandLossSentence({ args, label: commandLabel })\n : null,\n hasUnconfirmed,\n hasFailure,\n press,\n };\n}\n\ntype NativeButtonProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"onClick\" | \"type\" | \"children\" | \"aria-pressed\" | \"aria-busy\" | \"title\"\n>;\n\n/**\n * Props for {@link CommandButton}. Any other native button attribute is passed through.\n *\n * @category CommandButton\n */\nexport interface CommandButtonProps<Result = CommandReplyLike, Args = unknown>\n extends NativeButtonProps {\n /**\n * The command this control dispatches. Its reply type is what\n * {@link CommandButtonProps.onConfirmed} receives, inferred.\n */\n handle: CommandButtonHandle<Result, Args>;\n /**\n * Args for the dispatch, passed straight to `handle.send` and checked\n * against what that command takes. `NoInfer`, so a wrong args object cannot\n * widen `Args` to its own shape.\n */\n args?: NoInfer<Args>;\n /**\n * The dispatch's operator-facing description, which a refusal is named\n * after (\"Hire Valentina Kerman refused: ...\").\n */\n commandLabel?: string;\n /**\n * What the control says on hover and keyboard focus while it is at rest or\n * armed, in the kit's tip. An outcome's own sentence replaces it while one\n * stands. It is not the accessible name: give `aria-label` for that.\n */\n title?: string;\n /** The resting label. */\n label: ReactNode;\n /**\n * The armed label. Supplying it makes this an arm-then-confirm control: the\n * first click puts it in the armed phase, the second dispatches, and an armed\n * control left alone returns to rest after {@link ARM_TIMEOUT_MS}. Omit it for\n * a control that dispatches on one click.\n *\n * Arm anything irreversible, and anything that spends career funds.\n */\n confirmLabel?: ReactNode;\n /**\n * The in-flight wording, spoken and shown on hover. The control itself draws\n * only a spinner while in flight, at its resting width. Defaults to\n * \"Working...\".\n */\n pendingLabel?: string;\n /** The refused label. Defaults to \"Refused\". */\n refusedLabel?: ReactNode;\n /**\n * The label for a dispatch that got no reply. Defaults to \"No reply\": not\n * \"failed\" and not \"refused\", neither of which is known.\n */\n lostLabel?: ReactNode;\n /**\n * The label for a command this control lost that has since replied.\n * Defaults to \"Found\", with the whole sentence on the accessible name and\n * title. Not \"Confirmed\": the operator was told to give up on it.\n */\n foundLabel?: ReactNode;\n /**\n * The blocked phase's accessible name, for a control whose gate reason is\n * already spelled out beside it. Omit it and the accessible name is the\n * composed gate sentence.\n */\n blockedAriaLabel?: string;\n /**\n * The armed phase's accessible name, for a control whose resting\n * `aria-label` says more than its visible word (\"Hire Desdin Kerman for\n * 30,000 funds\"). Omit it and the visible confirm wording is the accessible\n * name, never the resting label, since the next press means something\n * different.\n */\n confirmAriaLabel?: string;\n /** The in-flight phase's accessible name, for a control whose resting name says more than `pendingLabel`. Defaults to `pendingLabel`. */\n pendingAriaLabel?: string;\n /**\n * Whether this control's command is CURRENTLY IN EFFECT, for a control that\n * represents state as well as acting on it: a SAS toggle, an action group, an\n * activated strategy. Sets `aria-pressed` and the active fill. Leave it\n * undefined for a control that only acts.\n */\n active?: boolean;\n /**\n * How much the control asks to be pressed at rest. `ghost` (default) is the\n * quiet outline that fills only while active, armed or refused; `primary`\n * is filled in `tone` at rest, for the one verb of a group that commits.\n */\n variant?: \"ghost\" | \"primary\";\n /**\n * The tone of the fill while `active`, and of the resting fill for a\n * `primary` control. Defaults to `neutral`.\n */\n tone?: CommandButtonTone;\n /**\n * Shown in place of the word when the word does not fit the control's width,\n * measured against the control itself and following resizes, for a control in\n * a narrow cell. The word stays the accessible name in both states. Needs a\n * plain-text label and, when set, `confirmLabel`.\n */\n icon?: ReactNode;\n /** Shown in place of the confirm word when it does not fit. Defaults to {@link CommandButtonProps.icon}. */\n confirmIcon?: ReactNode;\n /**\n * Called with the control's press while a click on it would do something\n * (not while it is in flight) and with `null` when it would not, for a\n * caller that lets an input (a key, a gamepad button or a serial device\n * bound to this control) press it from elsewhere. The press\n * takes whether the control has a confirm step.\n */\n onPressReady?: (press: ((armable: boolean) => void) | null) => void;\n /** Defaults to `md`. */\n size?: CommandButtonSize;\n /** Tone for the armed phase. Defaults to `go`: confirm reads as commit. */\n confirmTone?: CommandButtonTone;\n /**\n * Called once a dispatch is confirmed, for a caller with local state to\n * settle. The pending state itself needs nothing from you.\n *\n * Receives what the dispatch resolved with, typed off the handle. A confirmed\n * command did not necessarily do something: a mod that de-duplicates on\n * request id (the id each dispatch carries) replies to a repeat of the same\n * dispatch with the receipt it stored the first time. The\n * value is the reply envelope; the command's own value is on `payload`.\n */\n onConfirmed?: (result: Result) => void;\n}\n\n/**\n * A button that dispatches a command and shows what became of it: at rest,\n * armed (with a `confirmLabel`), in flight, refused, lost (no reply came in\n * time), found (a lost command has since replied), or blocked. It is blocked\n * while the mod says in advance that the command would be refused, such as a\n * purchase the save cannot afford, and the button then says why. The panel's\n * delay rail shows that a command is in flight; this shows which control sent\n * it.\n *\n * Pending ends when the promise `send()` returned settles, whether or not any\n * telemetry changes as a result. Pending is per RENDERED CONTROL, not\n * per handle, so one handle serving a list gives each row its own pending\n * state. Outcomes are announced through a polite live region.\n *\n * @example\n * ```tsx\n * const transmitCmd = useCommand(\"science.experiment.transmit\");\n *\n * <CommandButton\n * size=\"sm\"\n * handle={transmitCmd}\n * args={{ partId }}\n * commandLabel=\"Transmit experiment\"\n * label=\"Transmit\"\n * confirmLabel=\"Confirm transmit\"\n * pendingLabel=\"Transmitting...\"\n * />\n * ```\n *\n * @category CommandButton\n * @categoryDescription CommandButton\n * A control that sends a command and shows what became of it: armed, in flight,\n * refused, lost, found again or blocked, with the outcome announced. Every control that\n * dispatches a command handle is one of these.\n */\nexport function CommandButton<Result = CommandReplyLike, Args = unknown>({\n handle,\n args,\n commandLabel,\n label,\n confirmLabel,\n pendingLabel = \"Working...\",\n refusedLabel = \"Refused\",\n lostLabel = \"No reply\",\n foundLabel = \"Found\",\n confirmAriaLabel,\n pendingAriaLabel,\n blockedAriaLabel,\n active,\n variant = \"ghost\",\n tone = \"neutral\",\n icon,\n confirmIcon,\n onPressReady,\n size = \"md\",\n confirmTone = \"go\",\n onConfirmed,\n disabled,\n title,\n \"aria-label\": ariaLabel,\n ...rest\n}: Readonly<CommandButtonProps<Result, Args>>) {\n const {\n phase,\n isPending,\n isArmed,\n isRefused,\n isLost,\n isFound,\n isBlocked,\n isShowingReason,\n refusalText,\n foundText,\n lossText,\n hasUnconfirmed,\n hasFailure,\n press,\n } = useCommandButton({ handle, args, commandLabel, onConfirmed });\n\n useEffect(() => {\n if (!onPressReady || isPending) return;\n onPressReady(press);\n return () => onPressReady(null);\n }, [onPressReady, isPending, press]);\n\n const resolveBody = (): ReactNode => {\n if (isPending)\n return (\n <InFlightFace\n holds={label}\n label={pendingLabel}\n spinnerSize={size === \"sm\" ? 10 : 12}\n />\n );\n if (isRefused) return refusedLabel;\n if (isLost) return lostLabel;\n if (isFound) return foundLabel;\n // The reason IN the control. It runs long with the numbers at the end, so it must wrap, never truncate.\n if (isShowingReason) return refusalText;\n if (isArmed) return confirmLabel;\n return label;\n };\n const resolved = resolveBody();\n const fitWord = icon !== undefined && typeof resolved === \"string\";\n const body = fitWord ? (\n <FitLabel label={resolved} icon={isArmed ? (confirmIcon ?? icon) : icon} />\n ) : (\n resolved\n );\n /*\n * An arm-then-confirm control keeps the width of its wider word in both\n * states, so arming it never reflows the row it sits in. Only plain-text\n * labels can be measured this way; a node label sizes itself as before.\n */\n const reserve =\n icon === undefined &&\n typeof label === \"string\" &&\n typeof confirmLabel === \"string\" &&\n !isRefused &&\n !isLost &&\n !isFound &&\n !isShowingReason;\n\n const outcome = isRefused\n ? refusalText\n : isLost\n ? lossText\n : isFound\n ? foundText\n : null;\n\n const filled = active === true || isArmed || isRefused;\n const drawnTone = commandTone({\n restTone: variant === \"primary\" ? tone : \"neutral\",\n filled,\n isRefused,\n isFound,\n isArmed,\n confirmTone,\n tone,\n });\n\n return (\n <>\n <Tooltip\n text={foundText ?? refusalText ?? (isPending ? pendingLabel : title)}\n >\n <CommandButton__Body\n type=\"button\"\n $tone={drawnTone}\n $variant={variant}\n $fit={fitWord}\n $size={size}\n $pressed={filled}\n $armed={isArmed}\n $blocked={isBlocked}\n aria-pressed={active}\n aria-busy={isPending || undefined}\n // aria-disabled, not disabled, so the control keeps focus while the outcome lands on it.\n aria-disabled={isBlocked || isPending || undefined}\n disabled={disabled}\n data-unconfirmed={hasUnconfirmed ? \"true\" : undefined}\n data-failed={hasFailure ? \"true\" : undefined}\n data-command-phase={phase}\n // A diagnostic hook only: an undetermined gate renders as an ordinary control.\n data-gate={\n isBlocked\n ? \"blocked\"\n : standingGate(handle, args)?.undetermined\n ? \"undetermined\"\n : undefined\n }\n /*\n * The accessible name tracks the phase: an outcome's full sentence while\n * it stands, and for armed and pending the visible wording rather than\n * the resting label, which describes a state the control has left.\n */\n aria-label={\n (isRefused\n ? (refusalText ?? undefined)\n : isLost\n ? (lossText ?? undefined)\n : isFound\n ? (foundText ?? ariaLabel)\n : isBlocked\n ? (blockedAriaLabel ?? refusalText ?? ariaLabel)\n : isPending\n ? (pendingAriaLabel ?? pendingLabel)\n : isArmed\n ? confirmAriaLabel\n : ariaLabel) ?? (fitWord ? resolved : undefined)\n }\n onClick={() => press(confirmLabel !== undefined)}\n data-rest-label={reserve ? label : undefined}\n data-armed-label={reserve ? confirmLabel : undefined}\n {...rest}\n >\n {reserve ? <CommandButton__Face>{body}</CommandButton__Face> : body}\n </CommandButton__Body>\n </Tooltip>\n {/* Mounted with the control, so an outcome lands in a region assistive tech is already watching. */}\n <LiveRegion visuallyHidden>{outcome}</LiveRegion>\n </>\n );\n}\n\n/** The tone a command draws in: plain at rest, then the tone of the phase it is filled for. */\nfunction commandTone(p: {\n restTone: CommandButtonTone;\n filled: boolean;\n isRefused: boolean;\n isFound: boolean;\n isArmed: boolean;\n confirmTone: CommandButtonTone;\n tone: CommandButtonTone;\n}): CommandButtonTone {\n if (!p.filled) return p.restTone;\n if (p.isRefused) return \"warn\";\n // `found` reverses a warning, so it does not wear the warning's colour.\n if (p.isFound) return \"neutral\";\n if (p.isArmed) return p.confirmTone;\n return p.tone;\n}\n\nconst armedPulse = keyframes`\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.65; }\n`;\n\nconst CommandButton__Face = styled.span`\n grid-area: 1 / 1;\n /* First in the cell's order, so the button's baseline is the face's and not an invisible sizer's. */\n order: -1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gap-glyph);\n`;\n\n/* A command at rest is the quiet ghost; in effect, armed or refused it takes the one pressed fill. */\nconst CommandButton__Body = styled(Button__Body)<{\n $armed: boolean;\n $blocked: boolean;\n $fit: boolean;\n}>`\n letter-spacing: 0.04em;\n\n /* Shrinks below its word so the word can be measured and give way to the icon. */\n ${({ $fit }) => ($fit ? \"min-width: 0;\" : \"\")}\n\n ${({ $armed }) =>\n $armed &&\n css`\n @media (prefers-reduced-motion: no-preference) {\n animation: ${armedPulse} 1s var(--ease-emphasis) infinite;\n }\n `}\n\n &:disabled {\n opacity: 0.5;\n }\n\n /* Dimmed toward the muted text token rather than faded, so the refusal reason stays readable; the warn border says the game refused. */\n ${({ $blocked }) =>\n $blocked &&\n css`\n background: transparent;\n border-style: dashed;\n border-color: var(--color-warn-mark);\n color: var(--color-text-muted);\n cursor: help;\n\n @media (hover: hover) {\n &:hover:not(:disabled) {\n border-color: var(--color-warn-mark);\n color: var(--color-warn-text);\n }\n }\n `}\n\n /* Both labels sit invisibly in the one grid cell with the face, so the box is as wide as the wider of them. */\n &[data-rest-label] {\n display: inline-grid;\n justify-items: center;\n }\n &[data-rest-label]::before,\n &[data-rest-label]::after {\n grid-area: 1 / 1;\n visibility: hidden;\n height: 0;\n }\n &[data-rest-label]::before {\n content: attr(data-rest-label);\n }\n &[data-rest-label]::after {\n content: attr(data-armed-label);\n }\n\n /* In flight, not unavailable: full strength with a spinner. */\n &[aria-busy=\"true\"] {\n opacity: 1;\n cursor: progress;\n }\n`;\n","import type { CommandDelayHandle } from \"./CommandDelay\";\nimport type { InFlightCommandLike } from \"./toInFlightListItems\";\n\n/**\n * What became of a handle's dispatches, as the control that issued them echoes it.\n *\n * @category CommandButton\n */\nexport interface CommandOutcomes {\n /**\n * This handle's own dispatches that went past their reply with no answer\n * (overdue or lost) and HAVE an in-flight row. They may still have run.\n */\n unconfirmed: InFlightCommandLike[];\n /** True when any dispatch is unconfirmed, including a loss that never had a row. For a control's `data-unconfirmed` tint. */\n hasUnconfirmed: boolean;\n /** True when a dispatch never left this machine, or the mod answered it with a fault. For a control's `data-failed` tint. */\n hasFailure: boolean;\n /**\n * Clear an entry, the SAME dismiss the widget-top queue uses, so clearing on\n * the control clears it in the queue too.\n */\n dismiss: (id: string) => void;\n}\n\n/**\n * A handle's unanswered and undelivered dispatches plus the shared `dismiss`,\n * so the control that issued the command can echo what became of it while the\n * Panel rail stays the primary surface. An unanswered command may still run,\n * so it is unconfirmed and never counted as a failure.\n *\n * @category CommandButton\n */\nexport function commandOutcomes(handle: CommandDelayHandle): CommandOutcomes {\n const unconfirmed = handle.inFlight.filter(\n (c) => c.predictedPhase === \"overdue\" || c.predictedPhase === \"lost\",\n );\n return {\n unconfirmed,\n hasUnconfirmed: unconfirmed.length > 0 || (handle.losses?.length ?? 0) > 0,\n hasFailure:\n (handle.undelivered?.length ?? 0) > 0 ||\n (handle.failures?.length ?? 0) > 0,\n dismiss: handle.dismiss ?? (() => {}),\n };\n}\n","import {\n type ComponentPropsWithoutRef,\n forwardRef,\n type ReactNode,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport styled from \"styled-components\";\n\n/**\n * Props for {@link FitLabelButton}. Any other `button` attribute passes\n * through; `children` is replaced by `label` and `icon`.\n *\n * @category Button\n */\nexport interface FitLabelButtonProps\n extends Omit<ComponentPropsWithoutRef<\"button\">, \"children\"> {\n /**\n * The button's word and its accessible name, in both states.\n */\n label: string;\n /** Shown in place of the label when the label does not fit. */\n icon: ReactNode;\n}\n\n/**\n * A `button` that shows its label while the label fits and its icon when it\n * does not. The switch is measured against the button's own width, not a\n * breakpoint, and follows resizes. It is an unstyled native button\n * (`type=\"button\"` by default), so it has no border, fill or padding until you give it some: style it with\n * `styled(FitLabelButton)` or wrap it as the layout needs.\n *\n * The accessible name is always `label`, carried in `aria-label` in both\n * states. Put anything explaining a disabled state in `title`, since it is the\n * only explanation left once the word is gone.\n *\n * @example\n * ```tsx\n * import { FitLabelButton, SettingsIcon } from \"@ksp-gonogo/ui-kit\";\n *\n * function SettingsButton(props: { onOpen: () => void }) {\n * return <FitLabelButton label=\"Settings\" icon={<SettingsIcon />} onClick={props.onOpen} />;\n * }\n * ```\n *\n * @category Button\n */\nexport const FitLabelButton = forwardRef<\n HTMLButtonElement,\n FitLabelButtonProps\n>(function FitLabelButton({ label, icon, type, ...rest }, ref) {\n return (\n <button ref={ref} type={type ?? \"button\"} aria-label={label} {...rest}>\n <FitLabel label={label} icon={icon} />\n </button>\n );\n});\n\n/**\n * The label-or-icon content of a {@link FitLabelButton}, for another control\n * that draws its own button and wants the same collapse. It measures its\n * parent's content box, so the parent must be able to shrink below the label\n * (`min-width: 0`). Both states are `aria-hidden`: the parent carries the name.\n * Kit-internal, not on the barrel.\n */\nexport function FitLabel({ label, icon }: { label: string; icon: ReactNode }) {\n const contentRef = useRef<HTMLSpanElement | null>(null);\n const ghostRef = useRef<HTMLSpanElement | null>(null);\n const [fits, setFits] = useState(true);\n\n useLayoutEffect(() => {\n const content = contentRef.current;\n const ghost = ghostRef.current;\n if (!content || !ghost) return;\n const measure = () => {\n // The content span's clientWidth is the button's content box, padding excluded.\n setFits(ghost.scrollWidth <= content.clientWidth);\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(content);\n observer.observe(ghost);\n return () => observer.disconnect();\n }, []);\n\n return (\n <FitLabelButton__Content ref={contentRef}>\n <FitLabelButton__Ghost ref={ghostRef} aria-hidden=\"true\">\n {label}\n </FitLabelButton__Ghost>\n {fits ? (\n <FitLabelButton__Label aria-hidden=\"true\">\n {label}\n </FitLabelButton__Label>\n ) : (\n <FitLabelButton__Icon aria-hidden=\"true\" data-fit-label-icon=\"\">\n {icon}\n </FitLabelButton__Icon>\n )}\n </FitLabelButton__Content>\n );\n}\n\nconst FitLabelButton__Content = styled.span`\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n /* Shrinks to what the parent allows, so an overlong label is detected rather than overflowing its cell. */\n min-width: 0;\n width: 100%;\n`;\n\n/** Measured, never seen, never read out. */\nconst FitLabelButton__Ghost = styled.span`\n position: absolute;\n left: 0;\n top: 0;\n visibility: hidden;\n pointer-events: none;\n white-space: nowrap;\n`;\n\nconst FitLabelButton__Label = styled.span`\n /* nowrap, so a label that does not fit overflows and the measurement stays honest. */\n white-space: nowrap;\n`;\n\nconst FitLabelButton__Icon = styled.span`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n`;\n","import type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { Spinner } from \"../Spinner\";\n\n/**\n * The props of {@link InFlightFace}.\n *\n * @category CommandButton\n */\nexport interface InFlightFaceProps {\n /** The resting label, kept invisible so the control keeps its resting width. */\n holds: ReactNode;\n /** The in-flight wording, given to the spinner as its name. */\n label: string;\n /** The spinner's size in px. Defaults to 12. */\n spinnerSize?: number;\n}\n\n/**\n * What a command control draws while its dispatch is in flight: a spinner\n * alone, centred over its resting label held invisible, so the control does not\n * change width. The wording belongs on the control's accessible name and title.\n *\n * @category CommandButton\n */\nexport function InFlightFace({\n holds,\n label,\n spinnerSize = 12,\n}: Readonly<InFlightFaceProps>) {\n return (\n <InFlightFace__Root>\n <InFlightFace__Hold aria-hidden=\"true\">{holds}</InFlightFace__Hold>\n <InFlightFace__Spinner>\n <Spinner size={spinnerSize} aria-label={label} />\n </InFlightFace__Spinner>\n </InFlightFace__Root>\n );\n}\n\n/* One grid cell both children share, so the face sizes to the held label with no positioned ancestor. */\nconst InFlightFace__Root = styled.span`\n display: inline-grid;\n & > * {\n grid-area: 1 / 1;\n }\n`;\n\nconst InFlightFace__Hold = styled.span`\n visibility: hidden;\n`;\n\nconst InFlightFace__Spinner = styled.span`\n display: flex;\n align-items: center;\n justify-content: center;\n`;\n","import type { HTMLAttributes } from \"react\";\nimport styled, { keyframes } from \"styled-components\";\n\n/**\n * Props for {@link Spinner}. Any other `span` attribute passes through, except\n * `color` and `role`.\n *\n * @category EmptyState\n */\nexport interface SpinnerProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"role\"> {\n /** Outer diameter in pixels. Defaults to 12. */\n size?: number;\n /** Stroke width in pixels. Defaults to 2. */\n thickness?: number;\n /** Active arc colour. Defaults to the accent foreground. */\n color?: string;\n /** Accessible name for screen readers. Defaults to \"Loading\". */\n \"aria-label\"?: string;\n}\n\n/**\n * A small inline pending indicator: a ring with a coloured arc, sized to sit\n * beside a row label or value without reflowing the layout. It is\n * `role=\"status\"` with an accessible name. Under `prefers-reduced-motion` the\n * ring does not spin.\n *\n * @category EmptyState\n */\nexport function Spinner({\n size = 12,\n thickness = 2,\n color = \"var(--color-accent-fg)\",\n \"aria-label\": ariaLabel = \"Loading\",\n ...rest\n}: Readonly<SpinnerProps>) {\n return (\n <SpinnerEl\n {...rest}\n role=\"status\"\n aria-label={ariaLabel}\n $size={size}\n $thickness={thickness}\n $color={color}\n />\n );\n}\n\nconst spin = keyframes`\n to { transform: rotate(360deg); }\n`;\n\nconst SpinnerEl = styled.span<{\n $size: number;\n $thickness: number;\n $color: string;\n}>`\n display: inline-block;\n width: ${({ $size }) => `${$size}px`};\n height: ${({ $size }) => `${$size}px`};\n border-radius: var(--radius-circle);\n border: ${({ $thickness }) => `${$thickness}px`} solid\n var(--color-border-subtle);\n border-top-color: ${({ $color }) => $color};\n flex-shrink: 0;\n @media (prefers-reduced-motion: no-preference) {\n /* Off the motion scale: continuous rotation, not a UI transition. */\n animation: ${spin} 700ms linear infinite;\n }\n`;\n","import { type ReactNode, useEffect, useId } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { focusRing } from \"../focusRing\";\nimport { Tooltip } from \"../Tooltip\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\n\ninterface CommandGroupOwnProps<GroupValue extends Record<string, unknown>> {\n /** The group's current value, owned by the caller and written by the child inputs. */\n value: GroupValue;\n /** Fired exactly once, with the group's current `value`, on an explicit commit; never on a child input's own change. */\n onCommit: (v: GroupValue) => void;\n /** Marks the commit control unavailable, in an error tone, for example while there is no path to the craft. It stays focusable and says why (`gatedReason`); `onCommit` never fires while gated. */\n gated?: boolean;\n /** The group's inputs (wheels, sliders and so on), drawn as given. */\n children: ReactNode;\n /**\n * What the commit button draws. Defaults to \"Commit\". A string names the\n * button by itself; a node (an icon-only commit) MUST be paired with\n * `commitAriaLabel`, because every kit icon is `aria-hidden`, and omitting it\n * is a compile error. A node also squares the button's inset.\n */\n commitLabel?: ReactNode;\n /**\n * The commit button's accessible name, for when `commitLabel` cannot be one.\n * Always the ACTION, never the glyph (\"Commit framing\", not \"Return arrow\").\n */\n commitAriaLabel?: string;\n /** Why a gated commit is unavailable: the button's description, and its hover title. */\n gatedReason?: string;\n /**\n * Where the commit control sits relative to the inputs. `\"column\"` (default)\n * puts it on its own line under them; `\"row\"` puts it beside them, for a\n * corner-sized strip that cannot spare the height.\n */\n orientation?: \"column\" | \"row\";\n /**\n * Let the inputs break onto further lines. On by default, unlike the kit's\n * other rows. Turn it off for a strip that must stay one line.\n */\n wrap?: boolean;\n}\n\n/**\n * The commit control's two props, with the a11y pairing spelled as a type: a\n * `commitLabel` that is not a string has to come with a `commitAriaLabel`. The\n * component also warns in dev, for a caller who spreads a props bag past the\n * check.\n */\ntype CommandGroupCommitNaming =\n | { commitLabel?: string; commitAriaLabel?: string }\n | { commitLabel: ReactNode; commitAriaLabel: string };\n\n/**\n * Props for {@link CommandGroup}. A `commitLabel` that is not a string must come with a `commitAriaLabel`.\n *\n * @category CommandDelay\n */\nexport type CommandGroupProps<GroupValue extends Record<string, unknown>> =\n CommandGroupOwnProps<GroupValue> & CommandGroupCommitNaming;\n\n/**\n * Grouped-confirm (select-then-commit) primitive: child inputs write into the\n * caller's controlled `value`, and nothing dispatches until the explicit commit\n * fires `onCommit` once with the whole group's value. One delayed dispatch for\n * the group, not one per input. It has no data hooks and no dispatch of its\n * own.\n *\n * @category CommandDelay\n */\nexport function CommandGroup<GroupValue extends Record<string, unknown>>({\n value,\n onCommit,\n gated = false,\n children,\n commitLabel = \"Commit\",\n commitAriaLabel,\n gatedReason = \"No path: command dispatch is disabled\",\n orientation = \"column\",\n wrap = true,\n}: CommandGroupProps<GroupValue>) {\n // The types refuse a nameless glyph commit; this catches a spread props bag the checker could not see.\n const named =\n typeof commitLabel === \"string\" || typeof commitLabel === \"number\";\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n if (named || commitAriaLabel) return;\n console.warn(\n \"CommandGroup was given a non-text commitLabel and no commitAriaLabel, \" +\n \"so its commit button has no accessible name. Pass commitAriaLabel \" +\n \"naming the action (not the glyph).\",\n );\n }, [named, commitAriaLabel]);\n const reasonId = useId();\n\n return (\n <CommandGroup__Root data-gated={gated} $orientation={orientation}>\n <CommandGroup__Inputs $wrap={wrap}>{children}</CommandGroup__Inputs>\n {gated && <VisuallyHidden id={reasonId}>{gatedReason}</VisuallyHidden>}\n <Tooltip text={gated ? gatedReason : undefined} announce={false}>\n <CommandGroup__CommitButton\n type=\"button\"\n aria-disabled={gated || undefined}\n aria-describedby={gated ? reasonId : undefined}\n $gated={gated}\n $orientation={orientation}\n $icon={!named}\n aria-label={commitAriaLabel}\n onClick={() => {\n if (gated) return;\n onCommit(value);\n }}\n >\n {commitLabel}\n </CommandGroup__CommitButton>\n </Tooltip>\n </CommandGroup__Root>\n );\n}\n\nconst CommandGroup__Root = styled.div<{ $orientation: \"column\" | \"row\" }>`\n display: flex;\n flex-direction: ${({ $orientation }) => $orientation};\n ${({ $orientation }) => $orientation === \"row\" && \"align-items: center;\"}\n gap: var(--gap-control-row);\n`;\n\nconst CommandGroup__Inputs = styled.div<{ $wrap: boolean }>`\n display: flex;\n flex-wrap: ${({ $wrap }) => ($wrap ? \"wrap\" : \"nowrap\")};\n gap: var(--gap-control-row);\n align-items: center;\n`;\n\nconst CommandGroup__CommitButton = styled.button<{\n $gated: boolean;\n $orientation: \"column\" | \"row\";\n $icon: boolean;\n}>`\n align-self: ${({ $orientation }) =>\n $orientation === \"row\" ? \"center\" : \"flex-start\"};\n /* A commit control must not be squeezed to nothing by the inputs beside it. */\n ${({ $orientation }) => $orientation === \"row\" && \"flex-shrink: 0;\"}\n padding: var(--inset-commit-button);\n font-size: var(--font-size-compact);\n font-weight: 600;\n border-radius: var(--radius-regular);\n border: 1px solid var(--color-border-subtle);\n background: var(--color-surface-raised);\n color: var(--color-text-primary);\n cursor: pointer;\n\n ${({ $icon }) =>\n $icon &&\n css`\n /* The glyph centres in the box, and the inset is squared so the button is a square. */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: var(--inset-icon-button-compact);\n\n @media (pointer: coarse) {\n padding: var(--inset-icon-button);\n }\n `}\n\n ${focusRing}\n\n ${({ $gated }) =>\n $gated &&\n css`\n cursor: not-allowed;\n border-color: var(--color-nogo-status);\n background: var(--color-nogo-status);\n color: var(--color-nogo-on-status);\n opacity: 0.7;\n `}\n`;\n","import { type Reading, type Value, value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { Unit } from \"../Unit\";\nimport { withDelayCurrency } from \"./delayCurrency\";\n\n/**\n * Props for {@link SignalDelayBadge}.\n *\n * @category CommandDelay\n */\nexport interface SignalDelayBadgeProps {\n /** One-way separation in seconds. Rendered as-is; the caller decides IF. */\n oneWaySeconds: number;\n /** The delay as the reading it arrived in, so a quiet `comms.delay` draws the figure held. */\n delayReading?: Reading<Value<\"s\">> | null;\n /** So a caller can pin or re-tone it with `styled()`. */\n className?: string;\n}\n\n/**\n * How long it takes to REACH the other end, as one small chip (\"one-way ~4.2 s\").\n *\n * One-way, because the operator's question is when their words land. Draw it\n * beside a control that sends across the gap, so the light time is read before\n * the press rather than learned after it. Under a minute the figure keeps one\n * decimal.\n *\n * @category CommandDelay\n */\nexport function SignalDelayBadge({\n oneWaySeconds,\n delayReading,\n className,\n}: SignalDelayBadgeProps) {\n const oneWay = value(\"s\", oneWaySeconds);\n return (\n // Not a live region: the delay changes with every sample.\n <SignalDelayBadge__Chip\n className={className}\n role=\"group\"\n aria-label=\"Signal delay\"\n >\n one-way ~\n {/* A delay is a READOUT, not a countdown, so under a minute it keeps a decimal the ladder would truncate. */}\n <Unit\n value={withDelayCurrency(oneWay, delayReading)}\n {...(oneWay.lessThan(60)\n ? { scale: \"never\" as const, decimals: 1 }\n : {})}\n />\n </SignalDelayBadge__Chip>\n );\n}\n\nconst SignalDelayBadge__Chip = styled.div`\n /* Non-growing: it shares the console's corner row, and its text is short by construction. */\n flex: 0 0 auto;\n padding: var(--inset-chip-readout);\n font-family: var(--font-family-mono);\n font-size: var(--font-size-compact);\n white-space: nowrap;\n color: var(--color-text-muted);\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n`;\n","import { useEffect, useId } from \"react\";\nimport type { CommandDelayHandle } from \"./CommandDelay\";\nimport { useDelayRailStore } from \"./DelayRailContext\";\n\n/**\n * Something the rail draws that is not a command, such as an open microphone's\n * ribbon. A command handle is registered by `useCommand` itself, so one passed\n * here is a type error rather than a second entry.\n *\n * @category CommandDelay\n */\nexport type RailEntry = CommandDelayHandle & { send?: never };\n\n/**\n * Put a non-command entry on the nearest panel's delay rail for the life of the\n * calling component, updating it in place as it changes. `null` contributes\n * nothing, so an entry can come and go; outside a rail it is a no-op.\n *\n * @category CommandDelay\n */\nexport function useRailEntry(entry: RailEntry | null): void {\n const store = useDelayRailStore();\n const id = useId();\n const present = entry !== null;\n\n // Keyed on identity only, so a value change updates in place rather than re-registering.\n // biome-ignore lint/correctness/useExhaustiveDependencies: value changes are applied by the update effect, not by re-registering\n useEffect(() => {\n if (!store || !entry) return;\n return store.register({ id, ...entry });\n }, [store, id, present]);\n\n // The store's shallow-equal guard means an entry that did not actually move notifies nobody.\n useEffect(() => {\n if (!store || !entry) return;\n store.update(id, entry);\n }, [store, id, entry]);\n}\n","import { type ComponentPropsWithoutRef, type ReactNode, useId } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Button } from \"./Button\";\nimport { SendIcon } from \"./Icons\";\nimport { useTooltip } from \"./Tooltip\";\nimport { VisuallyHidden } from \"./VisuallyHidden\";\n\n/**\n * Props for {@link ComposerBar}. Any other `div` attribute is passed to the row.\n *\n * @category Console\n */\nexport interface ComposerBarProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * Input is not being accepted. Swaps the accent outline for the error one,\n * and tones `flag` to match.\n */\n blocked?: boolean;\n /**\n * A short label straddling the LEFT end of the row's top border, saying WHY\n * the outline turned red. The right end belongs to the console's delay\n * reading. A few upper-case words, such as `READ ONLY`, pinned so it never\n * changes the row's height.\n */\n flag?: string;\n /**\n * The glyph that says \"type here\", at the head of the row and in the\n * console's tone. Optional: a composer that CHOOSES rather than types has\n * nothing to prompt for.\n */\n prompt?: string;\n /**\n * Commit what is composed. Given, the row grows a send button at its far end;\n * omitted, the only send is whatever key the composer binds.\n *\n * The button is an ADDITION to that key, for a touch screen with no keyboard\n * up. Wire it to the same entry point the key handler calls, not a second\n * copy of the send path. It carries the verb and nothing else, so it never\n * reflows the composer.\n */\n onSend?: () => void;\n /**\n * Refuse the press. Distinct from `blocked`, which is about the row as a\n * whole: a composer with nothing typed in it is perfectly able to accept\n * input and still has nothing to send.\n */\n sendDisabled?: boolean;\n /**\n * The verb. Defaults to \"Send\". It is the button's ACCESSIBLE NAME whichever\n * way the button is drawn.\n */\n sendLabel?: string;\n /**\n * How to draw the verb: the glyph by default. `\"text\"` is for a composer\n * whose verb has no glyph, such as \"Open\", where a send arrow would claim the\n * row transmits something.\n */\n sendVariant?: \"icon\" | \"text\";\n /**\n * What the operator should know before pressing send, as a kit tooltip on\n * the button (pointer and keyboard focus) and as its accessible description. A\n * `\\n` starts a new line. It never disables the send: pressing it stays the\n * operator's choice.\n */\n sendTooltip?: string;\n /** The input the operator types into, drawn between the prompt and the send button. */\n children?: ReactNode;\n}\n\n/**\n * The input at the foot of a {@link Console}: a bordered, non-growing row whose\n * own border says whether input is being accepted. The border takes the\n * console's tone (`--console-tone-fg`), falling back to the primary accent, so\n * what is outlined is what the operator types into.\n *\n * The border turns error-toned when `blocked` (for example, no comms path), so\n * the operator sees it while typing rather than on pressing the key.\n *\n * It styles the row and nothing inside it except the send button (`onSend`)\n * and the prompt glyph (`prompt`); font and character pitch belong to the\n * caller. Whatever goes in sits flush on the row, with no outline of its own.\n *\n * @category Console\n */\nexport function ComposerBar({\n blocked = false,\n flag,\n prompt,\n onSend,\n sendDisabled = false,\n sendLabel = \"Send\",\n sendVariant = \"icon\",\n sendTooltip,\n children,\n ...rest\n}: ComposerBarProps) {\n const { anchor, tip } = useTooltip(sendTooltip);\n const described = useId();\n return (\n <ComposerBar__Row $blocked={blocked} {...rest}>\n {prompt !== undefined && (\n <ComposerBar__Prompt aria-hidden=\"true\">{prompt}</ComposerBar__Prompt>\n )}\n {children}\n {onSend !== undefined && (\n /* The anchor is the wrapper, not the button: a disabled button raises no pointer events. */\n <ComposerBar__SendAnchor {...anchor}>\n <ComposerBar__Send\n type=\"button\"\n disabled={sendDisabled}\n onClick={onSend}\n $icon={sendVariant === \"icon\"}\n {...(sendTooltip ? { \"aria-describedby\": described } : {})}\n {...(sendVariant === \"icon\" ? { \"aria-label\": sendLabel } : {})}\n >\n {sendVariant === \"icon\" ? <SendIcon size={16} /> : sendLabel}\n </ComposerBar__Send>\n {sendTooltip && (\n <VisuallyHidden id={described}>\n {sendTooltip.replace(\"\\n\", \". \")}\n </VisuallyHidden>\n )}\n {tip}\n </ComposerBar__SendAnchor>\n )}\n {flag !== undefined && (\n // `role=\"status\"`, never `alert`: a lost path is ambient, not an interruption.\n <ComposerBar__Flag $blocked={blocked} role=\"status\">\n {flag}\n </ComposerBar__Flag>\n )}\n </ComposerBar__Row>\n );\n}\n\nconst ComposerBar__Row = styled.div<{ $blocked: boolean }>`\n position: relative;\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: var(--gap-composer);\n padding: var(--inset-field);\n background: var(--color-surface-panel);\n border: 1px solid\n ${({ $blocked }) =>\n $blocked\n ? \"var(--color-nogo-text)\"\n : \"var(--console-tone-fg, var(--color-accent-fg))\"};\n border-radius: var(--radius-regular);\n`;\n\n// Its own margin rather than the row's gap, since a terminal's caret block must sit flush against the composed line.\nconst ComposerBar__Prompt = styled.span`\n flex: 0 0 auto;\n color: var(--console-tone-fg, var(--color-accent-fg));\n font-weight: bold;\n margin-right: var(--gap-prompt-glyph);\n`;\n\n/*\n * Pushed to the far end by its own auto margin, so it never moves with each\n * keystroke. `font-size` is stated, not inherited from a terminal's character\n * pitch on the row.\n */\nconst ComposerBar__SendAnchor = styled.span`\n display: inline-flex;\n\n /* A disabled button swallows pointer events, so they go to the anchor instead. */\n > button:disabled {\n pointer-events: none;\n }\n`;\n\nconst ComposerBar__Send = styled(Button)<{ $icon: boolean }>`\n flex: 0 0 auto;\n margin-left: auto;\n font-size: var(--font-size-compact);\n\n ${({ $icon }) =>\n $icon &&\n css`\n /* The glyph centres in the box, and the inset is squared so the button is a square. */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: var(--inset-icon-button);\n /* The row's own outline tone, so the control reads as belonging to the row. */\n color: var(--console-tone-fg, var(--color-accent-fg));\n\n @media (pointer: coarse) {\n padding: var(--inset-icon-button-touch);\n }\n `}\n`;\n\nconst ComposerBar__Flag = styled.div<{ $blocked: boolean }>`\n position: absolute;\n /* Straddles the top border by half its own height, which tracks the font size. */\n top: 0;\n transform: translateY(-50%);\n /* The left end of the border: a console's delay reading takes the right end, and the two can show together. */\n left: var(--inset-console-foot);\n /* Local ordering against its row only, so not on the --z-* ladder. */\n z-index: 1;\n /* Overlaps the prompt glyph, so it must not eat a press aimed at the control under it. */\n pointer-events: none;\n padding: var(--inset-chip);\n font-family: var(--font-family-mono);\n font-size: var(--font-size-caption);\n font-weight: bold;\n letter-spacing: 0.04em;\n border-radius: var(--radius-regular);\n\n ${({ $blocked }) =>\n $blocked\n ? css`\n color: var(--color-nogo-on-status);\n background: var(--color-nogo-status);\n border: 1px solid var(--color-nogo-on-status);\n `\n : css`\n color: var(--color-text-muted);\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-subtle);\n `}\n`;\n","import type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\n\n/*\n * The BOX a console is drawn in: a scrolling surface that takes the remaining\n * height of a panel body, with whatever the operator types into sitting INSIDE\n * it at the foot. Reached through `Console`, not exported bare.\n *\n * The nesting is exactly two deep: a quiet frame with a SUBTLE border, and one\n * bordered composer inside it that wears the console's accent. Never a third\n * box (an `<input>` with its own outline inside the composer).\n *\n * It is the positioning context, so a status badge pinned INSIDE the surface\n * costs no height on a widget at its `minSize`. Unlike `FramedDisplay` it takes\n * the remaining height; unlike `ScrollArea` it has a border and a positioning\n * context. It does not scroll: what goes inside scrolls itself.\n *\n * ## The standing reading sits at the FOOT, on the composer's TOP border\n *\n * `standing` is a readout of the LINK right now, drawn right-aligned over the\n * composer's top border, never over the scrollback's prose. It shares a column\n * with the queue's countdowns (the offset is the FOOT's inset, so the figures\n * line up exactly), and the two are never drawn together.\n *\n * `ComposerBar`'s own flag takes the LEFT end of the same border, since both\n * can be up at once: a terminal's separation and its refusal reveal on\n * different clocks. Neither knows about the other.\n *\n * With no composer there is no border to straddle, so the chip becomes an\n * ordinary right-aligned child at the foot, costing a band of height rather\n * than sitting over the prose.\n */\n/**\n * Which accent a {@link Console} is drawn in: `accent` (the primary accent) for\n * a terminal that dispatches to a craft, `info` for a message log.\n *\n * @category Console\n */\nexport type ConsoleTone = \"accent\" | \"info\";\n\nexport interface ConsoleFrameProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * Which accent this console answers in: the primary accent for a terminal\n * that dispatches to a craft, the informational one for a message log. The\n * frame paints nothing with it; it declares `--console-tone-fg` once for the\n * composer's border, prompt glyph and focus ring.\n */\n tone?: ConsoleTone;\n /**\n * What is still crossing, at the foot and immediately above the composer.\n * A slot of its own, so the frame knows whether there is a composer.\n */\n queue?: ReactNode;\n /**\n * The line the operator types on, at the foot, inside the frame and inset\n * from its edges. Given but falsy keeps the foot, so a mode switch does not\n * change the surface's height.\n */\n composer?: ReactNode;\n /**\n * A standing reading of the link, drawn over the TOP border of the composer\n * and right-aligned to the queue's column of times. Out of flow while there\n * IS a composer; an ordinary child at the foot when there is not.\n */\n standing?: ReactNode;\n children?: ReactNode;\n}\n\nexport function ConsoleFrame({\n tone = \"accent\",\n queue,\n composer,\n standing,\n children,\n ...rest\n}: ConsoleFrameProps) {\n // Any of the three grows a foot, so a standing reading never hangs over the log.\n const hasFoot =\n queue !== undefined || composer !== undefined || standing !== undefined;\n // A falsy `composer` keeps the foot but has no border, so the chip goes back into flow.\n const straddles = standing !== undefined && Boolean(composer);\n return (\n // A stable structural hook, since containment is invisible to a role query.\n <ConsoleFrame__Box data-console-frame=\"\" $tone={tone} {...rest}>\n <ConsoleFrame__Surface>{children}</ConsoleFrame__Surface>\n {hasFoot && (\n <ConsoleFrame__Foot $straddled={straddles}>\n {queue}\n {standing !== undefined && (\n /*\n A structural hook for the same reason. BEFORE the composer, so\n in the in-flow case the reading is announced before the control\n it sits above.\n */\n <ConsoleFrame__Standing\n data-console-standing=\"\"\n $straddle={straddles}\n >\n {standing}\n </ConsoleFrame__Standing>\n )}\n {composer}\n </ConsoleFrame__Foot>\n )}\n </ConsoleFrame__Box>\n );\n}\n\nconst ConsoleFrame__Box = styled.div<{ $tone: ConsoleTone }>`\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n background: var(--color-surface-panel);\n /* Subtle, not the tone: the accent belongs to the input's own border. */\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n overflow: hidden;\n\n ${({ $tone }) =>\n $tone === \"info\"\n ? css`\n --console-tone-fg: var(--color-info-text);\n `\n : css`\n --console-tone-fg: var(--color-accent-fg);\n `}\n`;\n\n// The scrollback half. `position: relative` is for the caller's own overlays; the frame pins nothing here.\nconst ConsoleFrame__Surface = styled.div`\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n display: flex;\n`;\n\n/*\n * A row, so a second standing reading sits beside the first in reading order.\n * `right` is the FOOT's inset, putting the chip's right edge on the\n * composer's, which lines its figure up with the queue's countdowns. `top` is\n * the same token as the foot's padding-top, so no hand-computed offset drifts\n * when the font size changes on a coarse pointer. That assumes the composer is\n * the foot's first box, which holds because the chip and the queue are never\n * drawn together. `z-index: 1` is local sibling ordering.\n */\nconst ConsoleFrame__Standing = styled.div<{ $straddle: boolean }>`\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: var(--gap-console-readings);\n /* Overlaps the composer's corner with nothing to click, so it must not take a press. */\n pointer-events: none;\n\n ${({ $straddle }) =>\n $straddle\n ? css`\n position: absolute;\n right: var(--inset-console-foot);\n top: var(--inset-console-foot-straddled);\n transform: translateY(-50%);\n z-index: 1;\n `\n : // No border to straddle, so it takes a line of its own and keeps only the column.\n css`\n align-self: flex-end;\n `}\n`;\n\n/*\n * Non-growing, so a filling queue or an opening picker never takes height from\n * the scrollback. Its own positioning context, so a composer's dropdown anchors\n * inside the console and the standing reading pins against the foot. The\n * padding makes the composer read as a control IN the console. `$straddled`\n * deepens the top inset so the half-chip above the border never lands on the\n * log's last line.\n */\nconst ConsoleFrame__Foot = styled.div<{ $straddled: boolean }>`\n position: relative;\n flex: 0 0 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--gap-console-foot);\n padding: var(--inset-console-foot);\n\n ${({ $straddled }) =>\n $straddled &&\n css`\n padding-top: var(--inset-console-foot-straddled);\n `}\n`;\n","import type { Reading, Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport {\n InFlightList,\n type InFlightListItem,\n signalDelayPresentation,\n} from \"./CommandDelay/InFlightList\";\nimport { SignalDelayBadge } from \"./CommandDelay/SignalDelayBadge\";\nimport { ConsoleFrame, type ConsoleTone } from \"./ConsoleFrame\";\n\nexport type { ConsoleTone };\n\n/**\n * Props for {@link Console}. Any other `div` attribute is passed to the frame.\n *\n * @category Console\n */\nexport interface ConsoleProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * Which accent this console is drawn in, declared once for the composer's\n * border, prompt glyph and focus ring.\n */\n tone?: ConsoleTone;\n /**\n * How long it takes to reach the other end, in seconds. `null` when there is\n * no measurable path. Neither `null` nor zero gets a {@link SignalDelayBadge}.\n */\n oneWaySeconds?: number | null;\n /**\n * The same delay as the reading it arrived in (`useCommand`'s\n * `delayReading`), so the badge's figure draws held while `comms.delay` is\n * quiet. Omitted, it draws as current.\n */\n delayReading?: Reading<Value<\"s\">> | null;\n /**\n * Whether anything this console sends can sit in a queue. A read-only viewer\n * passes `false` and gets NEITHER reading at a long delay.\n */\n canQueue?: boolean;\n /**\n * Force the {@link SignalDelayBadge} whatever the separation, for a terminal in\n * character mode (every keystroke goes on its own), which has no composed\n * line to queue.\n */\n alwaysBadge?: boolean;\n /**\n * What is still crossing, one entry per thing out. Drawn between the\n * scrollback and the composer, never inside the scroll.\n */\n inFlight?: InFlightListItem[];\n /**\n * Set when each `inFlight` entry carries the delay it was sent under, so the\n * queue stays after the live delay reading is gone: words sent at four\n * light-minutes are still crossing after the path drops. The badge and the\n * queue are still never drawn together.\n */\n inFlightFrozenAtDispatch?: boolean;\n /**\n * The line the operator types on, at the foot and inside the frame. Omitted,\n * the console grows no foot. Given but currently absent (a terminal in\n * character mode), the foot stays, so the surface does not change height\n * with the mode. A standing badge grows a foot for itself either way.\n */\n composer?: ReactNode;\n /** The scrollback: an emulator screen, a log, a list of rows. */\n children?: ReactNode;\n}\n\n/** Every console's queue name; not a prop, so an operator never hears two. */\nconst QUEUE_LABEL = \"Uplink queue\";\n\n/**\n * A console: a scrolling surface, what is still crossing on the link, the line\n * the operator is typing, and one reading of how far away the other end is\n * (the craft or device the console talks to, whose distance `oneWaySeconds`\n * gives).\n *\n * The delay reading is chosen here, not by the widget: a standing\n * {@link SignalDelayBadge} when the one-way delay is a second or less, so a\n * countdown would be over before it could be read, an {@link InFlightList} queue of what\n * is crossing when it is not, never both. Both sit at the foot. A widget says\n * only how far away the other end is (`oneWaySeconds`), whether it can queue\n * (`canQueue`) and what is queued (`inFlight`).\n *\n * The composer (usually a {@link ComposerBar}) and the scrollback are the\n * widget's; this places them and draws the frame around them.\n *\n * @category Console\n * @categoryDescription Console\n * A two-way text console over a delayed link: the scrollback, the line being\n * typed, and what is still crossing, framed with one reading of how far away\n * the other end (the craft the console talks to) is.\n */\nexport function Console({\n tone,\n oneWaySeconds = null,\n delayReading,\n canQueue = true,\n alwaysBadge = false,\n inFlight,\n inFlightFrozenAtDispatch = false,\n composer,\n children,\n ...rest\n}: ConsoleProps) {\n const presentation = signalDelayPresentation({\n oneWaySeconds,\n canQueue,\n alwaysBadge,\n });\n // \"badge\" implies a non-null separation, which the type cannot carry back here.\n const badgeSeconds = presentation === \"badge\" ? oneWaySeconds : null;\n const showStrip =\n inFlight !== undefined &&\n (presentation === \"strip\" ||\n (inFlightFrozenAtDispatch && presentation !== \"badge\"));\n return (\n // Two slots rather than one `footer`, so the frame knows whether there is a composer to place the reading against.\n <ConsoleFrame\n {...(tone !== undefined ? { tone } : {})}\n {...(badgeSeconds !== null\n ? {\n standing: (\n <SignalDelayBadge\n oneWaySeconds={badgeSeconds}\n delayReading={delayReading}\n />\n ),\n }\n : {})}\n {...(showStrip\n ? { queue: <InFlightList items={inFlight} aria-label={QUEUE_LABEL} /> }\n : {})}\n {...(composer !== undefined ? { composer } : {})}\n {...rest}\n >\n {children}\n </ConsoleFrame>\n );\n}\n","import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport styled, { type CSSObject } from \"styled-components\";\n\n/**\n * Props for {@link ContainerBreak}.\n *\n * @category Layout\n */\nexport interface ContainerBreakProps extends HTMLAttributes<HTMLDivElement> {\n /** Container width in px past which `wide` applies instead of `narrow`. */\n at: number;\n /** Styles applied while the container is narrower than `at`. */\n narrow?: CSSProperties;\n /** Styles applied once the container is at least `at` px wide, layered over `narrow`. */\n wide?: CSSProperties;\n /** The content the styles lay out. */\n children?: ReactNode;\n}\n\n/**\n * A `div` that switches between two sets of inline styles at a container-query\n * breakpoint, which a React `style` prop cannot express on its own. The\n * nearest ancestor with `container-type: inline-size` (a plain\n * `<div style={{ containerType: \"inline-size\" }}>` is enough) decides which\n * side of `at` this element is on. Both style sets are ordinary React\n * `CSSProperties`.\n *\n * @example\n * ```tsx\n * <div style={{ containerType: \"inline-size\" }}>\n * <ContainerBreak\n * at={360}\n * narrow={{ display: \"flex\", flexDirection: \"column\" }}\n * wide={{ flexDirection: \"row\" }}\n * >\n * {chart}\n * {legend}\n * </ContainerBreak>\n * </div>\n * ```\n *\n * @category Layout\n */\nexport function ContainerBreak({\n at,\n narrow,\n wide,\n children,\n ...rest\n}: ContainerBreakProps) {\n return (\n <ContainerBreak__Root $at={at} $narrow={narrow} $wide={wide} {...rest}>\n {children}\n </ContainerBreak__Root>\n );\n}\n\nconst ContainerBreak__Root = styled.div<{\n $at: number;\n $narrow?: CSSProperties;\n $wide?: CSSProperties;\n}>`\n ${({ $narrow }) => $narrow as CSSObject}\n @container (min-width: ${({ $at }) => $at}px) {\n ${({ $wide }) => $wide as CSSObject}\n }\n`;\n","/**\n * Value equality for widget config objects, as {@link useModalSaveBar} uses\n * it to decide whether a draft differs from the saved config. Call it yourself\n * only to build a dirty check of your own.\n *\n * - `undefined` and a missing key are treated the same\n * - object key order is ignored\n * - arrays compare by index\n * - `Date` values compare by time, `Set` by membership (primitives and\n * deep-equal members) and `Map` by entries\n *\n * @category Modal\n */\nexport function configEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n return a.every((item, i) => configEqual(item, b[i]));\n }\n\n if (a instanceof Date || b instanceof Date) {\n return (\n a instanceof Date &&\n b instanceof Date &&\n Object.is(a.getTime(), b.getTime())\n );\n }\n\n if (a instanceof Set || b instanceof Set) {\n if (!(a instanceof Set) || !(b instanceof Set)) return false;\n if (a.size !== b.size) return false;\n const rest = [...b];\n // Greedy match is exact here: configEqual is an equivalence relation.\n return [...a].every((x) => {\n const i = rest.findIndex((y) => configEqual(x, y));\n if (i === -1) return false;\n rest.splice(i, 1);\n return true;\n });\n }\n\n if (a instanceof Map || b instanceof Map) {\n if (!(a instanceof Map) || !(b instanceof Map)) return false;\n if (a.size !== b.size) return false;\n for (const [k, v] of a) {\n if (!b.has(k) || !configEqual(v, b.get(k))) return false;\n }\n return true;\n }\n\n if (typeof a === \"object\" && typeof b === \"object\") {\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n // Union of keys whose value is not undefined on either side.\n const keys = new Set<string>();\n for (const k of Object.keys(ao)) if (ao[k] !== undefined) keys.add(k);\n for (const k of Object.keys(bo)) if (bo[k] !== undefined) keys.add(k);\n for (const k of keys) {\n if (!configEqual(ao[k], bo[k])) return false;\n }\n return true;\n }\n\n return false;\n}\n","import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport styled from \"styled-components\";\nimport {\n ComboboxListbox,\n type ComboboxOption,\n filterComboboxOptions,\n flattenComboboxGroups,\n groupComboboxOptions,\n moveComboboxActiveIndex,\n} from \"./Combobox\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * One key a {@link DataKeyPicker} offers.\n *\n * @category Form\n */\nexport interface KeyOption extends ComboboxOption {\n /** A unit symbol drawn at the end of the option's row. */\n unit?: string;\n}\n\n/**\n * Props for {@link DataKeyPicker}.\n *\n * @category Form\n */\nexport interface DataKeyPickerProps {\n /** The keys on offer. An empty list is treated as not yet loaded, so no saved key is flagged as unavailable against it. */\n keys: KeyOption[];\n /** The picked key, or null for none. */\n value: string | null;\n /** Called with the picked key, or with null when cleared. */\n onChange: (key: string | null) => void;\n /** Adds a clear control while a key is picked. Defaults to false. */\n clearable?: boolean;\n /** Shown while no key is picked. Defaults to \"Search...\". */\n placeholder?: string;\n /**\n * What to call the thing the picked key names, for the message shown when a\n * saved key is not among `keys` and for the clear control's name. Defaults\n * to \"value\".\n */\n subjectNoun?: string;\n /** Put on the input, so a `<label htmlFor>` elsewhere names it. */\n id?: string;\n /** Names the input when no label points at it. */\n \"aria-label\"?: string;\n}\n\n/**\n * Shown in place of a label when a saved key is not in `keys`: rendering the\n * raw key would read as a valid selection that silently reads nothing.\n */\nconst RETIRED_MESSAGE = \"is no longer available\";\n\n/**\n * A searchable picker over a list of keys: a combobox input that filters as\n * the operator types, with the options grouped under their `group` headings\n * and each option's unit drawn beside it. Enter with nothing highlighted picks\n * the first match; Escape or a click outside closes the list.\n *\n * A saved `value` that is not among a non-empty `keys` is flagged in place\n * (\"This value is no longer available. Pick another.\") rather than shown as a\n * valid selection.\n *\n * @category Form\n */\nexport function DataKeyPicker({\n keys,\n value,\n onChange,\n clearable = false,\n placeholder = \"Search...\",\n subjectNoun = \"value\",\n id,\n \"aria-label\": ariaLabel,\n}: DataKeyPickerProps) {\n const [query, setQuery] = useState(\"\");\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(-1);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const listboxId = useId();\n const optionIdPrefix = useId();\n const retiredId = useId();\n const optionId = (key: string) => `${optionIdPrefix}-${key}`;\n\n const selectedOption = keys.find((k) => k.key === value);\n // An empty `keys` is a catalogue that has not arrived, so nothing is judged retired against it.\n const retired =\n value !== null &&\n value !== \"\" &&\n selectedOption === undefined &&\n keys.length > 0;\n\n const filtered = useMemo(\n () => filterComboboxOptions(keys, query),\n [keys, query],\n );\n\n const sortedGroups = useMemo(\n () => groupComboboxOptions(filtered),\n [filtered],\n );\n\n const flatOptions = useMemo(\n () => flattenComboboxGroups(sortedGroups),\n [sortedGroups],\n );\n\n const openPicker = useCallback(() => {\n setOpen(true);\n setQuery(\"\");\n setActiveIndex(-1);\n }, []);\n\n const closePicker = useCallback(() => {\n setOpen(false);\n setQuery(\"\");\n setActiveIndex(-1);\n }, []);\n\n const selectOption = useCallback(\n (key: string) => {\n onChange(key);\n closePicker();\n },\n [onChange, closePicker],\n );\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (!open) {\n if (e.key === \"Enter\" || e.key === \"ArrowDown\") openPicker();\n return;\n }\n switch (e.key) {\n case \"Escape\":\n closePicker();\n break;\n case \"ArrowDown\":\n e.preventDefault();\n setActiveIndex((i) =>\n moveComboboxActiveIndex(i, 1, flatOptions.length),\n );\n break;\n case \"ArrowUp\":\n e.preventDefault();\n setActiveIndex((i) =>\n moveComboboxActiveIndex(i, -1, flatOptions.length),\n );\n break;\n case \"Enter\": {\n // Falls back to the first filtered result, so a partial label plus Enter works.\n const opt =\n activeIndex >= 0 ? flatOptions[activeIndex] : flatOptions[0];\n if (opt) selectOption(opt.key);\n break;\n }\n }\n };\n\n useEffect(() => {\n if (!open) return;\n const onOutside = (e: PointerEvent) => {\n if (!containerRef.current?.contains(e.target as Node)) closePicker();\n };\n document.addEventListener(\"pointerdown\", onOutside);\n return () => document.removeEventListener(\"pointerdown\", onOutside);\n }, [open, closePicker]);\n\n const displayValue = open ? query : (selectedOption?.label ?? value ?? \"\");\n const activeOption =\n open && activeIndex >= 0 ? flatOptions[activeIndex] : undefined;\n\n return (\n <Container ref={containerRef}>\n <PickerInput\n ref={inputRef}\n id={id}\n aria-label={ariaLabel}\n value={displayValue}\n placeholder={value ? undefined : placeholder}\n $hasValue={!!value && !open}\n $retired={retired && !open}\n aria-invalid={retired && !open ? true : undefined}\n aria-describedby={retired && !open ? retiredId : undefined}\n onFocus={openPicker}\n onBlur={closePicker}\n onChange={(e) => {\n setQuery(e.target.value);\n setActiveIndex(-1);\n }}\n onKeyDown={handleKeyDown}\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-autocomplete=\"list\"\n aria-activedescendant={\n activeOption ? optionId(activeOption.key) : undefined\n }\n />\n {retired && !open && (\n <RetiredNote id={retiredId}>\n {`This ${subjectNoun} ${RETIRED_MESSAGE}. Pick another.`}\n </RetiredNote>\n )}\n {clearable && value && !open && (\n <ClearButton\n type=\"button\"\n aria-label={`Clear ${subjectNoun}`}\n onClick={() => {\n onChange(null);\n closePicker();\n }}\n >\n ×\n </ClearButton>\n )}\n {open && (\n <ComboboxListbox\n id={listboxId}\n groups={sortedGroups}\n flatOptions={flatOptions}\n activeIndex={activeIndex}\n selectedKey={value}\n getOptionId={optionId}\n onHoverIndex={setActiveIndex}\n onSelectKey={selectOption}\n aria-label={ariaLabel ?? \"Data keys\"}\n renderItem={(opt) => (\n <>\n <ItemLabel>{opt.label ?? opt.key}</ItemLabel>\n {opt.unit && <ItemUnit>{opt.unit}</ItemUnit>}\n </>\n )}\n />\n )}\n </Container>\n );\n}\n\nconst Container = styled.div`\n position: relative;\n`;\n\nconst PickerInput = styled.input<{ $hasValue: boolean; $retired?: boolean }>`\n background: var(--color-surface-raised);\n border: 1px solid ${({ $retired }) => ($retired ? \"var(--color-nogo-text)\" : \"var(--color-border-strong)\")};\n border-radius: var(--radius-regular);\n color: ${({ $hasValue, $retired }) => {\n if ($retired) return \"var(--color-nogo-text)\";\n return $hasValue ? \"var(--color-text-primary)\" : \"var(--color-text-muted)\";\n }};\n font-size: var(--font-size-value);\n padding: var(--inset-field);\n width: 100%;\n\n &:focus {\n border-color: var(--color-text-faint);\n outline: none;\n }\n\n ${focusRing}\n\n &::placeholder {\n color: var(--color-text-faint);\n }\n`;\n\nconst ClearButton = styled.button`\n position: absolute;\n right: 6px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n color: var(--color-text-dim);\n cursor: pointer;\n font-size: var(--font-size-lg);\n line-height: var(--line-height-flush);\n padding: var(--inset-glyph-tight);\n\n &:hover {\n color: var(--color-text-primary);\n }\n\n ${focusRing}\n`;\n\n/**\n * Sits under the input rather than replacing its text, so the operator can\n * still read which key went missing.\n */\nconst RetiredNote = styled.div`\n color: var(--color-nogo-text);\n font-size: var(--font-size-compact);\n margin-top: var(--gap-caption);\n`;\n\nconst ItemLabel = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-primary);\n`;\n\nconst ItemUnit = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-muted);\n margin-left: var(--gap-trailing-mark);\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { TONE_TEXT } from \"./tone\";\n\n/**\n * Props for {@link DataLine}.\n *\n * @category Readout\n */\nexport interface DataLineProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * What the reading IS, in one or two words. Set in a quiet uppercase so the\n * eye skips it on a second pass and lands on the reading.\n */\n label: ReactNode;\n /**\n * A chip qualifying the reading: a `Badge` saying which of two states it is in.\n * Sits at the head of the reading, inside the reading's own column, so the two\n * wrap together.\n */\n lead?: ReactNode;\n /** The reading. Write a quantity through {@link Unit}. */\n children?: ReactNode;\n /** How alarming the reading is. Defaults to `neutral`. */\n tone?: Tone;\n /**\n * Give the label a fixed column so the readings on consecutive lines line up\n * down their left edge; a long reading wraps inside its own column. Off by\n * default.\n */\n aligned?: boolean;\n}\n\n/**\n * One labelled reading on a line: what it is, then what it says.\n *\n * The label is quiet uppercase; the reading takes the primary foreground with\n * tabular figures, so a run of lines never reads as one block of grey. `tone`\n * colours the reading and never the label.\n *\n * For a settings row, where the value sits at the row's right edge, use\n * {@link ReadOnlyField} instead.\n *\n * @category Readout\n */\nexport function DataLine({\n label,\n lead,\n children,\n tone = \"neutral\",\n aligned = false,\n ...rest\n}: DataLineProps) {\n return (\n <DataLine__Root $aligned={aligned} {...rest}>\n <DataLine__Label>{label}</DataLine__Label>\n <DataLine__Value $tone={tone}>\n {lead}\n {children}\n </DataLine__Value>\n </DataLine__Root>\n );\n}\n\n/**\n * The label column's width, in `ch` of the grid container's font: room for\n * about ten of the smaller label's characters.\n */\nconst LABEL_COLUMN = \"7ch\";\n\nconst DataLine__Root = styled.div<{ $aligned: boolean }>`\n display: ${({ $aligned }) => ($aligned ? \"grid\" : \"flex\")};\n align-items: baseline;\n gap: var(--gap-data-line);\n min-width: 0;\n ${({ $aligned }) =>\n $aligned\n ? css`\n grid-template-columns: ${LABEL_COLUMN} minmax(0, 1fr);\n `\n : css`\n flex-wrap: wrap;\n `}\n`;\n\nconst DataLine__Label = styled.span`\n font-size: var(--font-size-caption);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n`;\n\nconst DataLine__Value = styled.span<{ $tone: Tone }>`\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--gap-data-line);\n font-size: var(--font-size-value);\n font-variant-numeric: tabular-nums;\n min-width: 0;\n color: ${({ $tone }) => TONE_TEXT[$tone]};\n`;\n","import type { ReactNode } from \"react\";\nimport { Fragment, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRingInset } from \"./focusRing\";\nimport { InlineOverflowGlow, useInlineOverflow } from \"./inlineOverflow\";\nimport type { UnitValue } from \"./readingCurrency\";\nimport { Unit } from \"./Unit\";\nimport { useKeyboardScrollable } from \"./useScrollerMetric\";\n\n/**\n * The fields every {@link DataTable} column carries, whatever it draws.\n *\n * @category DataTable\n */\nexport interface DataTableColumnBase {\n /** Stable identity for the column, and its React key. */\n key: string;\n /** The column's heading, in the table head. */\n header: ReactNode;\n /**\n * This column names its row: its cells are row headers\n * (`<th scope=\"row\">`), so a screen reader says the row's name with every\n * other cell in it. One column per table, normally the first.\n */\n rowHeader?: boolean;\n /**\n * `end` right-aligns the cell and its header. Use it for every numeric\n * column, so the digits line up.\n */\n align?: \"start\" | \"end\";\n /**\n * CSS width for the column, e.g. `\"1fr\"` or `\"9ch\"`. Omitted columns size\n * to their content. A `ch` width on a numeric column keeps it from\n * twitching as values change magnitude.\n */\n width?: string;\n /**\n * Minimum width for the column, so a text column in a narrow panel scrolls\n * instead of wrapping to single words.\n */\n minWidth?: string;\n}\n\n/**\n * One column of a {@link DataTable}: what each cell draws, from a `render` of\n * the caller's own, or from a `value` the table draws through {@link Unit}.\n * `value` takes a whole `Reading`, so a held figure keeps its mark, and a\n * `null` or `undefined` draws the null token.\n *\n * @category DataTable\n */\nexport type DataTableColumn<Row> = DataTableColumnBase &\n (\n | {\n /** Draws the cell yourself. Give this or `value`, never both. */\n render: (row: Row) => ReactNode;\n value?: never;\n }\n | {\n /**\n * The cell's figure, drawn through {@link Unit}: a whole `Reading`, or\n * a `value()` of a unit and a number. Give this or `render`, never both.\n */\n value: (row: Row) => UnitValue | null | undefined;\n render?: never;\n }\n );\n\n/** A cell's content, from whichever of the two the column gives. */\nfunction cellContent<Row>(col: DataTableColumn<Row>, row: Row): ReactNode {\n if (col.value !== undefined) return <Unit value={col.value(row) ?? null} />;\n return col.render(row);\n}\n\n/**\n * A run of rows under a heading, for a table whose rows arrive already\n * grouped (by body, by vessel, by stage).\n *\n * @category DataTable\n */\nexport interface DataTableSection<Row> {\n /** Unique within the table; the section's React key. */\n id: string;\n /** The heading drawn across the table above the section's rows. */\n title: ReactNode;\n /** The rows under this heading. */\n rows: Row[];\n}\n\n/**\n * Props for {@link DataTable}.\n *\n * @category DataTable\n */\nexport interface DataTableProps<Row> {\n /** The columns, left to right. */\n columns: ReadonlyArray<DataTableColumn<Row>>;\n /** Flat rows. Ignored when `sections` is given. */\n rows?: ReadonlyArray<Row>;\n /** Grouped rows. Takes precedence over `rows`. */\n sections?: ReadonlyArray<DataTableSection<Row>>;\n /** A stable, unique key for each row. */\n rowKey: (row: Row) => string;\n /**\n * Describes the table to a screen reader. It is not shown: the visible\n * heading is the widget's own.\n */\n caption: string;\n /** Shown in place of the body when there is nothing to list. */\n empty?: ReactNode;\n /**\n * Extra content for a row, rendered as a full-width row directly beneath it:\n * where per-row controls and augment slots go, so the columns stay aligned.\n */\n rowDetail?: (row: Row) => ReactNode;\n /** A class for the table's outer wrapper, so a styled-component can extend it. */\n className?: string;\n}\n\n/**\n * A real table for tabular readouts, so figures in a column can be compared\n * down the page.\n *\n * Semantic `<table>` throughout. Each section is a `<tbody>` of its own,\n * headed by a `<th scope=\"rowgroup\">` spanning the width, and a `rowHeader`\n * column makes each row's naming cell a `<th scope=\"row\">`. A table wider than\n * its slot scrolls sideways.\n *\n * @example\n * ```tsx\n * // `\"Mit\"` is the unit KSP measures science data in, and `\"science\"` is\n * // science points; any unit symbol `value()` accepts works the same way.\n * import { type DataTableColumn, DataTable } from \"@ksp-gonogo/ui-kit\";\n * import { value } from \"@ksp-gonogo/sitrep-sdk\";\n *\n * interface Subject {\n * subjectId: string;\n * title: string;\n * dataMits: number;\n * remaining: number;\n * }\n *\n * const columns: ReadonlyArray<DataTableColumn<Subject>> = [\n * { key: \"subject\", header: \"Subject\", rowHeader: true, width: \"1fr\", minWidth: \"22ch\", render: (s) => s.title },\n * { key: \"data\", header: \"Data\", align: \"end\", width: \"9ch\", value: (s) => value(\"Mit\", s.dataMits) },\n * { key: \"remaining\", header: \"Remaining\", align: \"end\", width: \"10ch\", value: (s) => value(\"science\", s.remaining) },\n * ];\n *\n * <DataTable\n * caption=\"Science aboard the active vessel, by subject\"\n * columns={columns}\n * rows={subjects}\n * rowKey={(s) => s.subjectId}\n * empty=\"No subject matches the filter.\"\n * />\n * ```\n *\n * @category DataTable\n * @categoryDescription DataTable\n * A table of rows under typed columns, with optional sections, for data an\n * operator scans across.\n */\nexport function DataTable<Row>({\n columns,\n rows,\n sections,\n rowKey,\n caption,\n empty,\n rowDetail,\n className,\n}: Readonly<DataTableProps<Row>>) {\n const groups: ReadonlyArray<DataTableSection<Row>> =\n sections ?? (rows ? [{ id: \"\", title: null, rows: [...rows] }] : []);\n const total = groups.reduce((n, g) => n + g.rows.length, 0);\n const [scroller, setScroller] = useState<HTMLDivElement | null>(null);\n const overflow = useInlineOverflow(scroller);\n const tabIndex = useKeyboardScrollable(scroller);\n\n return (\n <DataTable__Shell className={className}>\n <DataTable__Scroller ref={setScroller} tabIndex={tabIndex}>\n <DataTable__Table>\n <DataTable__Caption>{caption}</DataTable__Caption>\n <thead>\n <tr>\n {columns.map((col) => (\n <DataTable__HeaderCell\n key={col.key}\n scope=\"col\"\n $align={col.align ?? \"start\"}\n style={{\n width: col.width,\n minWidth: col.minWidth,\n }}\n >\n {col.header}\n </DataTable__HeaderCell>\n ))}\n </tr>\n </thead>\n {total === 0 && empty !== undefined && (\n <tbody>\n <tr>\n <DataTable__EmptyCell colSpan={columns.length}>\n {empty}\n </DataTable__EmptyCell>\n </tr>\n </tbody>\n )}\n {groups.map((group) => (\n <tbody key={group.id}>\n {group.title !== null && group.title !== undefined && (\n <tr>\n <DataTable__SectionCell\n scope=\"rowgroup\"\n colSpan={columns.length}\n >\n {group.title}\n </DataTable__SectionCell>\n </tr>\n )}\n {group.rows.map((row) => {\n const key = rowKey(row);\n const detail = rowDetail?.(row);\n const hasDetail =\n detail !== null &&\n detail !== undefined &&\n detail !== false &&\n detail !== \"\";\n return (\n <Fragment key={key}>\n <DataTable__Row $hasDetail={hasDetail}>\n {columns.map((col) => (\n <DataTable__Cell\n key={col.key}\n as={col.rowHeader ? \"th\" : undefined}\n scope={col.rowHeader ? \"row\" : undefined}\n $align={col.align ?? \"start\"}\n >\n {cellContent(col, row)}\n </DataTable__Cell>\n ))}\n </DataTable__Row>\n {hasDetail ? (\n <tr>\n <DataTable__DetailCell colSpan={columns.length}>\n {detail}\n </DataTable__DetailCell>\n </tr>\n ) : null}\n </Fragment>\n );\n })}\n </tbody>\n ))}\n </DataTable__Table>\n </DataTable__Scroller>\n <InlineOverflowGlow $position=\"left\" $visible={overflow.left} />\n <InlineOverflowGlow $position=\"right\" $visible={overflow.right} />\n </DataTable__Shell>\n );\n}\n\n/* Reaches out to the panel's edges, so a table scrolled sideways passes under a glow in the gutter rather than stopping at the padding. */\nconst DataTable__Shell = styled.div`\n position: relative;\n min-width: 0;\n margin-inline: calc(-1 * var(--bleed-inline));\n`;\n\n/*\n * Wide content scrolls inside the table rather than pushing the widget's\n * layout sideways. A flex box, so the end padding is part of what scrolls and\n * the last column comes to rest on the content column too.\n */\nconst DataTable__Scroller = styled.div`\n display: flex;\n padding-inline: var(--bleed-inline);\n overflow-x: auto;\n min-width: 0;\n ${focusRingInset}\n scrollbar-width: none;\n &::-webkit-scrollbar {\n display: none;\n }\n`;\n\nconst DataTable__Table = styled.table`\n width: 100%;\n border-collapse: collapse;\n font-size: var(--font-size-compact);\n`;\n\n/* Announced, not shown: the visible heading is the widget's own. */\nconst DataTable__Caption = styled.caption`\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n`;\n\nconst DataTable__HeaderCell = styled.th<{ $align: \"start\" | \"end\" }>`\n text-align: ${({ $align }) => $align};\n position: sticky;\n top: 0;\n /* Local sibling ordering over its own rows, not a rung on the app ladder. */\n z-index: 1;\n background: var(--color-surface-panel);\n color: var(--color-text-faint);\n font-size: var(--font-size-caption);\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n white-space: nowrap;\n padding: var(--inset-table-cell);\n border-bottom: 1px solid var(--color-border-subtle);\n`;\n\nconst DataTable__SectionCell = styled.th`\n text-align: start;\n background: var(--color-surface-raised);\n color: var(--color-text-muted);\n font-size: var(--font-size-caption);\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: var(--inset-table-section);\n border-bottom: 1px solid var(--color-border-subtle);\n`;\n\n/* A row and its detail are one record, so only the bottom of the pair is ruled. */\nconst DataTable__Row = styled.tr<{ $hasDetail: boolean }>`\n &:not(:last-child) > td,\n &:not(:last-child) > th {\n border-bottom: ${({ $hasDetail }) =>\n $hasDetail ? \"none\" : \"1px solid var(--color-border-subtle)\"};\n }\n`;\n\n/* A row header is the same cell to the eye; only its role differs. */\nconst DataTable__Cell = styled.td<{ $align: \"start\" | \"end\" }>`\n text-align: ${({ $align }) => $align};\n font-weight: inherit;\n color: var(--color-text-primary);\n padding: var(--inset-table-cell);\n font-variant-numeric: tabular-nums;\n vertical-align: baseline;\n`;\n\nconst DataTable__DetailCell = styled.td`\n padding: var(--inset-table-detail);\n border-bottom: 1px solid var(--color-border-subtle);\n`;\n\nconst DataTable__EmptyCell = styled.td`\n color: var(--color-text-faint);\n font-style: italic;\n padding: var(--inset-table-empty);\n`;\n","import { useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\n\nexport interface InlineOverflow {\n /** Content is scrolled past on the start edge. */\n left: boolean;\n /** Content remains beyond the end edge. */\n right: boolean;\n}\n\nconst NONE: InlineOverflow = { left: false, right: false };\n\n/**\n * Which inline edges of a horizontal scroller have content beyond them, kept\n * current across scrolling and across the scroller or its children resizing.\n */\nexport function useInlineOverflow(el: HTMLElement | null): InlineOverflow {\n const [overflow, setOverflow] = useState(NONE);\n // React's same-value bailout is not guaranteed with other work queued, so an unchanged measurement is filtered here.\n const last = useRef(NONE);\n\n useEffect(() => {\n if (!el) return;\n const update = () => {\n const left = el.scrollLeft > 1;\n const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;\n if (last.current.left === left && last.current.right === right) return;\n last.current = { left, right };\n setOverflow(last.current);\n };\n\n update();\n el.addEventListener(\"scroll\", update, { passive: true });\n const ro =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n ro?.observe(el);\n for (const child of Array.from(el.children)) ro?.observe(child);\n const mo = new MutationObserver(() => {\n for (const child of Array.from(el.children)) ro?.observe(child);\n update();\n });\n mo.observe(el, { childList: true });\n return () => {\n el.removeEventListener(\"scroll\", update);\n ro?.disconnect();\n mo.disconnect();\n };\n }, [el]);\n\n return overflow;\n}\n\n/**\n * The glow over a horizontal scroller's edge that says more content lies that\n * way. At least the panel gutter wide, so a strip reaching the panel's edge\n * fades out across the gutter instead of stopping hard at the padding. Render\n * it after the scroller: it paints over it by document order.\n */\nexport const InlineOverflowGlow = styled.div<{\n $position: \"left\" | \"right\";\n $visible: boolean;\n}>`\n position: absolute;\n top: 0;\n bottom: 0;\n ${({ $position }) => ($position === \"left\" ? \"left: 0;\" : \"right: 0;\")}\n width: max(28px, var(--bleed-inline));\n pointer-events: none;\n opacity: ${({ $visible }) => ($visible ? 1 : 0)};\n transition: opacity var(--duration-base) var(--ease-standard);\n background: linear-gradient(\n to ${({ $position }) => ($position === \"left\" ? \"right\" : \"left\")},\n rgba(255, 255, 255, 0.12),\n rgba(255, 255, 255, 0) 100%\n );\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n`;\n","import { bandIn, type Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport {\n heldNameMarker,\n InstrumentBound,\n InstrumentHeldMark,\n sayHeld,\n} from \"./instrumentCurrency\";\nimport { NULL_DISPLAY } from \"./NullValue\";\nimport {\n figureAttributes,\n resolveCurrency,\n type UnitValue,\n} from \"./readingCurrency\";\nimport { placedOnScale, standsApart } from \"./standsApart\";\nimport { useTooltip } from \"./Tooltip\";\nimport { type FormatsFor, speakQuantity, writeQuantity } from \"./units\";\n\n/**\n * A coloured segment of a {@link Dial}'s arc, in the dial's own unit.\n *\n * @category Gauge\n */\nexport interface DialZone<Unit extends string = string> {\n /** Lower bound of the coloured arc segment. */\n from: Value<Unit>;\n /** Upper bound of the coloured arc segment. */\n to: Value<Unit>;\n /** The segment's colour: any CSS colour, such as a theme `var()`. */\n color: string;\n}\n\n/**\n * A tick mark on a {@link Dial}'s face, with an optional label beside it.\n *\n * @category Gauge\n */\nexport interface DialTick<Unit extends string = string> {\n /** Value at which to draw the tick. */\n value: Value<Unit>;\n /** Optional short label (e.g. \"N\", \"E\"). */\n label?: string;\n}\n\n/**\n * Props for {@link Dial}.\n *\n * @category Gauge\n */\nexport interface DialProps<Unit extends string = string> {\n /** Current value: the needle position, or the whole reading it arrived in. */\n value: UnitValue<Unit>;\n /** The value at `startAngle`. */\n min: Value<Unit>;\n /** The value at `startAngle + sweep`. */\n max: Value<Unit>;\n /**\n * Drawn width in pixels. The SVG scales down to a narrower slot rather than\n * clipping.\n *\n * @defaultValue `120`\n */\n width?: number;\n /**\n * Drawn height in pixels.\n *\n * @defaultValue `120`\n */\n height?: number;\n /**\n * Degrees clockwise from up where `min` sits.\n *\n * @defaultValue `0`, the top\n */\n startAngle?: number;\n /**\n * Degrees swept from `min` to `max`.\n *\n * @defaultValue `360`, a full circle\n */\n sweep?: number;\n /**\n * Treat the value as wrapping (compass): value is taken modulo the range.\n * Without it a value outside `min..max` pins the needle at the nearer end.\n */\n wrap?: boolean;\n /** Coloured arc segments. */\n zones?: ReadonlyArray<DialZone<Unit>>;\n /** Tick marks around the face. */\n ticks?: ReadonlyArray<DialTick<Unit>>;\n /**\n * Pin the rung the centre readout is written at, for the cases where\n * convention beats magnitude. Absent, the value's own kind decides.\n */\n format?: FormatsFor<Unit>;\n /** Centre label override. Defaults to the value, written with its unit. */\n valueLabel?: string;\n /**\n * Draws a half-circle face with the figure under the hub, the instrument\n * layout for a gauge whose figure is the point: the arc is raised to the top\n * of the box and a strip is reserved under it for the figure. `regular` and\n * `large` set the figure's size. Absent, the figure sits in the centre of\n * the face. Honoured only for a face that sweeps 180° or less.\n */\n readout?: \"regular\" | \"large\";\n /** Needle colour. Defaults to `var(--color-text-primary)`. */\n needleColor?: string;\n /** Arc track colour. Defaults to `var(--color-border-subtle)`. */\n trackColor?: string;\n /** Accessible name naming what the dial measures; the value is spoken as `aria-valuetext`. */\n \"aria-label\": string;\n}\n\nconst TRACK_THICKNESS = 6;\nconst HUB_RADIUS = 3;\n\n/** The half-circle layout's face: a thicker track and hub, and the figure's size with its baseline below the hub (the strip reserved under the arc). */\nconst RAISED = { track: 8, hub: 4, needle: 0.92 } as const;\nconst READOUT = {\n regular: { size: 16, drop: 18 },\n large: { size: 24, drop: 32 },\n} as const;\n\n/** Point on a circle of radius `r`, `angleDeg` clockwise from up (12 o'clock). */\nfunction pointAt(\n cx: number,\n cy: number,\n r: number,\n angleDeg: number,\n): { x: number; y: number } {\n const a = (angleDeg * Math.PI) / 180;\n return { x: cx + r * Math.sin(a), y: cy - r * Math.cos(a) };\n}\n\nfunction arcPath(\n cx: number,\n cy: number,\n r: number,\n a0: number,\n a1: number,\n): string {\n const p0 = pointAt(cx, cy, r, a0);\n const p1 = pointAt(cx, cy, r, a1);\n const delta = a1 - a0;\n const largeArc = Math.abs(delta) > 180 ? 1 : 0;\n const sweepFlag = delta >= 0 ? 1 : 0;\n return `M ${p0.x.toFixed(2)} ${p0.y.toFixed(2)} A ${r} ${r} 0 ${largeArc} ${sweepFlag} ${p1.x.toFixed(2)} ${p1.y.toFixed(2)}`;\n}\n\n/**\n * A round instrument whose needle sweeps a configurable arc (by default a full\n * 360° compass), so it can show a heading that wraps as well as a bounded\n * value. With `readout`, a half-circle face puts its figure under the hub.\n *\n * Angles are degrees clockwise from 12 o'clock (up is 0°), as on a compass.\n * `startAngle` places `min`; `sweep` is the span from `min` to `max`.\n *\n * The whole axis is one kind: `value`, `min`, `max`, every zone bound and\n * every tick are `Value<Unit>` of the same unit, and the centre readout writes\n * that unit itself. Handed a whole `Reading`, a held figure is marked as held\n * and the {@link UncertaintyBand} of a figure a model carried forward (see\n * {@link TopicReckoning}) puts two bounds on the arc.\n *\n * The dial is a `role=\"meter\"` with the value as `aria-valuetext`. With no\n * value it draws no needle and is announced as an image naming the missing\n * value.\n *\n * @example A heading compass\n * ```tsx\n * import { useTelemetry, value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { Dial } from \"@ksp-gonogo/ui-kit\";\n *\n * function HeadingCompass() {\n * const attitude = useTelemetry(\"vessel.attitude\");\n * return (\n * <Dial\n * value={attitude.heading}\n * min={value(\"°\", 0)}\n * max={value(\"°\", 360)}\n * wrap\n * ticks={[\n * { value: value(\"°\", 0), label: \"N\" },\n * { value: value(\"°\", 90), label: \"E\" },\n * { value: value(\"°\", 180), label: \"S\" },\n * { value: value(\"°\", 270), label: \"W\" },\n * ]}\n * aria-label=\"Heading\"\n * />\n * );\n * }\n * ```\n *\n * @category Gauge\n * @categoryDescription Gauge\n * Instruments for a value against a scale: a dial and a tape, each with ticks\n * and coloured zones.\n */\nexport function Dial<Unit extends string = string>({\n value,\n min,\n max,\n width = 120,\n height = 120,\n startAngle = 0,\n sweep = 360,\n wrap = false,\n zones,\n ticks,\n valueLabel,\n readout,\n format,\n needleColor = \"var(--color-text-primary)\",\n trackColor = \"var(--color-border-subtle)\",\n \"aria-label\": ariaLabel,\n}: Readonly<DialProps<Unit>>) {\n const resolved = resolveCurrency(value);\n const { shown, held, caption, band } = resolved;\n const { anchor, tip } = useTooltip(caption);\n\n // The axis, unwrapped once into the face's angular geometry; every figure a reader sees goes back out through the unit layer.\n const current = shown?.magnitude ?? Number.NaN;\n const axisMin = min.magnitude;\n const axisMax = max.magnitude;\n const span = axisMax - axisMin;\n const safe = Number.isFinite(current) ? current : axisMin;\n const display =\n span > 0\n ? wrap\n ? axisMin + ((((safe - axisMin) % span) + span) % span)\n : Math.max(axisMin, Math.min(axisMax, safe))\n : axisMin;\n // No number, no needle; a present but non-finite number still falls back to the start of the scale.\n const hasFigure = shown != null;\n const face = shown == null ? null : { magnitude: display, unit: shown.unit };\n // An SVG `<text>` cannot contain a `<span>`, so this uses `writeQuantity`, with `speakQuantity` for `aria-valuetext`.\n const centreLabel =\n valueLabel ?? (face === null ? null : writeQuantity(face, { format }));\n const spoken = face === null ? NULL_DISPLAY : speakQuantity(face, { format });\n // The model's interval, narrowed to the figure's own unit.\n const interval =\n shown == null || band === null ? null : (bandIn(band, shown.unit) ?? null);\n\n const raised = readout !== undefined && sweep <= 180;\n const trackThickness = raised ? RAISED.track : TRACK_THICKNESS;\n const hubRadius = raised ? RAISED.hub : HUB_RADIUS;\n const figure = readout === undefined ? null : READOUT[readout];\n const cx = width / 2;\n // Raised: the hub sits on the arc's base line, with the figure's strip below it.\n const r =\n raised && figure !== null\n ? Math.min(\n (width - trackThickness) / 2,\n height - trackThickness - figure.drop,\n )\n : Math.min(width, height) / 2 - trackThickness - 2;\n const cy = raised ? r + trackThickness / 2 : height / 2;\n\n const angleOf = (v: number): number => {\n const t = span > 0 ? (v - axisMin) / span : 0;\n return startAngle + t * sweep;\n };\n\n /*\n * Onto the axis (clamped in the algebra, so another rung of the same kind\n * converts before it compares), then onto the face. Zone order and emptiness\n * are decided on quantities, since angles would flip under a negative sweep.\n */\n const onAxis = (q: Value<Unit>): Value<Unit> => q.max(min).min(max);\n const onFace = (q: Value<Unit>): number => angleOf(q.magnitude);\n\n // Compared as fractions of the sweep, from the angles the marks are drawn at.\n const onSweep = (angle: number): number =>\n sweep !== 0 ? (angle - startAngle) / sweep : 0;\n const drawnInterval =\n interval !== null &&\n standsApart(\n onSweep(angleOf(display)),\n [\n onSweep(onFace(onAxis(interval.lo))),\n onSweep(onFace(onAxis(interval.hi))),\n ],\n placedOnScale({ wraps: wrap }),\n )\n ? interval\n : null;\n\n const isFullCircle = sweep >= 360;\n const needle = pointAt(\n cx,\n cy,\n r * (raised ? RAISED.needle : 0.88),\n angleOf(display),\n );\n\n return (\n <Dial__Meter\n {...figureAttributes(resolved)}\n {...anchor}\n {...(hasFigure\n ? {\n role: \"meter\",\n \"aria-label\": sayHeld(ariaLabel, caption),\n ...heldNameMarker(caption),\n \"aria-valuenow\": display,\n \"aria-valuemin\": axisMin,\n \"aria-valuemax\": axisMax,\n \"aria-valuetext\": spoken,\n }\n : {\n /* With no value to state, the face is an image rather than a meter at its minimum. */\n role: \"img\",\n \"aria-label\": sayHeld(`${ariaLabel}: ${spoken}`, caption),\n ...heldNameMarker(caption),\n })}\n >\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n aria-hidden=\"true\"\n style={{\n display: \"block\",\n fontFamily: \"var(--font-family-mono)\",\n maxWidth: \"100%\",\n height: \"auto\",\n }}\n >\n {r > 0 &&\n (isFullCircle ? (\n <circle\n cx={cx}\n cy={cy}\n r={r}\n fill=\"none\"\n stroke={trackColor}\n strokeWidth={trackThickness}\n />\n ) : (\n <path\n d={arcPath(cx, cy, r, startAngle, startAngle + sweep)}\n fill=\"none\"\n stroke={trackColor}\n strokeWidth={trackThickness}\n strokeLinecap=\"round\"\n />\n ))}\n\n {/* The track's rounded ends take the colour of the zone that reaches them, so a zoned arc has no grey cap. */}\n {r > 0 &&\n !isFullCircle &&\n zones?.flatMap((z) => {\n const lo = onAxis(z.from.min(z.to));\n const hi = onAxis(z.from.max(z.to));\n if (!hi.greaterThan(lo)) return [];\n return [\n // Clamped onto the axis already, so \"not past the end\" is \"at the end\".\n lo.greaterThan(min) ? null : startAngle,\n max.greaterThan(hi) ? null : startAngle + sweep,\n ].flatMap((angle) => {\n if (angle === null) return [];\n const end = pointAt(cx, cy, r, angle);\n return [\n <circle\n key={`cap-${z.color}-${angle}`}\n data-dial-cap=\"\"\n cx={end.x}\n cy={end.y}\n r={trackThickness / 2}\n fill={z.color}\n />,\n ];\n });\n })}\n\n {r > 0 &&\n zones?.map((z) => {\n const lo = onAxis(z.from.min(z.to));\n const hi = onAxis(z.from.max(z.to));\n if (!hi.greaterThan(lo)) return null;\n return (\n <path\n key={`zone-${z.color}-${onFace(lo)}-${onFace(hi)}`}\n d={arcPath(cx, cy, r, onFace(lo), onFace(hi))}\n fill=\"none\"\n stroke={z.color}\n strokeWidth={trackThickness}\n strokeLinecap=\"butt\"\n />\n );\n })}\n\n {r > 0 &&\n ticks?.map((tk) => {\n const at = tk.value.magnitude;\n const a = angleOf(at);\n const outer = pointAt(cx, cy, r, a);\n const inner = pointAt(cx, cy, r - trackThickness, a);\n const labelPt = pointAt(cx, cy, r - trackThickness - 8, a);\n return (\n <g key={`tick-${at}-${tk.label ?? \"\"}`}>\n <line\n x1={inner.x}\n y1={inner.y}\n x2={outer.x}\n y2={outer.y}\n stroke=\"var(--color-text-faint)\"\n strokeWidth={1}\n />\n {tk.label && (\n <text\n x={labelPt.x}\n y={labelPt.y}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n fontSize={9}\n fill=\"var(--color-text-faint)\"\n >\n {tk.label}\n </text>\n )}\n </g>\n );\n })}\n\n {r > 0 &&\n drawnInterval !== null &&\n ([\"lo\", \"hi\"] as const).map((end) => {\n const a = onFace(onAxis(drawnInterval[end]));\n const inner = pointAt(cx, cy, r - trackThickness / 2, a);\n const outer = pointAt(cx, cy, r + trackThickness / 2, a);\n return (\n <InstrumentBound\n key={end}\n end={end}\n x1={inner.x}\n y1={inner.y}\n x2={outer.x}\n y2={outer.y}\n />\n );\n })}\n\n {r > 0 && hasFigure && (\n <>\n <line\n x1={cx}\n y1={cy}\n x2={needle.x}\n y2={needle.y}\n stroke={needleColor}\n strokeWidth={2}\n strokeLinecap=\"round\"\n />\n <circle cx={cx} cy={cy} r={hubRadius} fill={needleColor} />\n </>\n )}\n\n <text\n x={cx}\n y={raised && figure !== null ? cy + figure.drop : cy + r * 0.55}\n textAnchor=\"middle\"\n fontSize={raised && figure !== null ? figure.size : 13}\n fontWeight={raised ? undefined : \"bold\"}\n fill={\n centreLabel === null\n ? \"var(--color-text-muted)\"\n : \"var(--color-text-primary)\"\n }\n >\n {centreLabel ?? NULL_DISPLAY}\n {held && centreLabel !== null && (\n <InstrumentHeldMark\n size={raised ? 7 : 6}\n figureSize={raised && figure !== null ? figure.size : 13}\n />\n )}\n </text>\n </svg>\n {tip}\n </Dial__Meter>\n );\n}\n\nconst Dial__Meter = styled.div`\n display: block;\n max-width: 100%;\n`;\n","import { type ReactNode, useId, useRef, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Button, type ButtonSize } from \"./Button\";\nimport { focusRing, focusRingInset } from \"./focusRing\";\nimport { ChevronRightIcon } from \"./Icons\";\nimport { useKeyboardScrollable } from \"./useScrollerMetric\";\n\n/**\n * Props for {@link Disclosure}.\n *\n * @category Disclosure\n */\nexport interface DisclosureProps {\n /**\n * The always-visible trigger content (text, a glyph, a badge). Pass a\n * function of `open` when the label itself should read differently once\n * expanded (\"Show detail\" to \"Hide detail\", for example).\n */\n label: ReactNode | ((open: boolean) => ReactNode);\n /** The panel content, revealed when open. */\n children: ReactNode;\n /**\n * Accessible name for the trigger. Required when `label` is a non-text node\n * (an icon or glyph) so the button is not unlabelled to a screen reader.\n */\n \"aria-label\"?: string;\n /** A class for the outer element, so a styled-component can extend it. */\n className?: string;\n /**\n * Where the panel opens. `\"popover\"`, the default, pops it out over whatever\n * follows, sized to its content, for a compact hint beside a tight trigger.\n * `\"inline\"` expands it in flow below the trigger, full width, as an\n * accordion, and the trigger grows a rotating chevron unless\n * `chevron={false}`.\n */\n variant?: \"popover\" | \"inline\";\n /**\n * How tall an `inline` panel may grow. `\"cap\"` (default) stops at a fixed\n * height and scrolls. `\"auto\"` grows in flow, for a panel already inside a\n * scrolling body, where a second scroller would hide content.\n */\n panelHeight?: \"cap\" | \"auto\";\n /**\n * Whether the rotating chevron renders for `variant=\"inline\"`. Defaults to\n * `true`. Set `false` when `label` already reads as the affordance, such as\n * \"Show detail\"; the label then right-aligns where the chevron would sit.\n */\n chevron?: boolean;\n /**\n * Renders the trigger as the kit's ghost `Button` (bordered, padded chrome)\n * instead of a plain unstyled one, sized to its content and right-aligned.\n * Pair with `chevron={false}` and a worded `label`: a chevron-less label with\n * no chrome reads as plain text.\n */\n asButton?: boolean;\n /**\n * Size of the `asButton` trigger, the `Button` size of the same name.\n */\n buttonSize?: ButtonSize;\n /**\n * Whether the panel starts expanded. Defaults to `false`.\n *\n * Pass `true` where the panel is the primary content and the trigger lets it\n * be folded away in a short tile. It is read once, at mount; to reset it,\n * give the Disclosure a React key that changes with the condition.\n */\n defaultOpen?: boolean;\n}\n\n/**\n * A button that shows and hides a panel of detail. The trigger is a real\n * `<button aria-expanded>`, so Enter and Space toggle it, and Escape closes\n * the panel and returns focus to the trigger. Use it for detail on demand\n * wherever a hover-only reveal would be unreachable without a mouse. By\n * default the panel pops over what follows; `variant=\"inline\"` expands it in\n * flow as an accordion.\n *\n * @example\n * ```tsx\n * import { value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { Disclosure, Text, Unit } from \"@ksp-gonogo/ui-kit\";\n *\n * // The default: the detail pops out over what follows.\n * <Disclosure label=\"Why held?\">\n * <Text>\n * The last update arrived <Unit value={value(\"s\", 40)} /> ago.\n * </Text>\n * </Disclosure>\n *\n * // Inline: the detail opens in the flow of the page, with no chevron.\n * <Disclosure label=\"Details\" variant=\"inline\" chevron={false}>\n * <Text>Fuel is drawn from the lowest stage first.</Text>\n * </Disclosure>\n * ```\n *\n * @category Disclosure\n * @categoryDescription Disclosure\n * Detail on demand: a button that shows and hides a panel, reachable from the\n * keyboard where a hover reveal would not be.\n */\nexport function Disclosure({\n label,\n children,\n \"aria-label\": ariaLabel,\n className,\n variant = \"popover\",\n panelHeight = \"cap\",\n chevron = true,\n asButton = false,\n buttonSize = \"md\",\n defaultOpen = false,\n}: DisclosureProps) {\n const [open, setOpen] = useState(defaultOpen);\n const panelId = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const [panel, setPanel] = useState<HTMLDivElement | null>(null);\n const panelTabIndex = useKeyboardScrollable(panel);\n const showChevron = variant === \"inline\" && chevron;\n const resolvedLabel = typeof label === \"function\" ? label(open) : label;\n const triggerProps = {\n ref: triggerRef,\n type: \"button\" as const,\n \"aria-expanded\": open,\n \"aria-controls\": panelId,\n \"aria-label\": ariaLabel,\n onClick: () => setOpen((v) => !v),\n };\n const triggerBody = (\n <>\n {resolvedLabel}\n {showChevron && (\n <Disclosure__Chevron $open={open}>\n <ChevronRightIcon size=\"var(--icon-size-control)\" />\n </Disclosure__Chevron>\n )}\n </>\n );\n\n return (\n <Disclosure__Root\n className={className}\n $variant={variant}\n onKeyDown={(e) => {\n if (e.key === \"Escape\" && open) {\n setOpen(false);\n triggerRef.current?.focus();\n }\n }}\n >\n {asButton ? (\n <Disclosure__ButtonTrigger\n {...triggerProps}\n variant=\"ghost\"\n size={buttonSize}\n $inline={variant === \"inline\"}\n >\n {triggerBody}\n </Disclosure__ButtonTrigger>\n ) : (\n <Disclosure__Trigger\n {...triggerProps}\n $variant={variant}\n $align={variant === \"inline\" && !chevron ? \"end\" : \"between\"}\n >\n {triggerBody}\n </Disclosure__Trigger>\n )}\n {open && (\n <Disclosure__Panel\n ref={setPanel}\n id={panelId}\n role=\"group\"\n tabIndex={panelTabIndex}\n $variant={variant}\n $panelHeight={panelHeight}\n >\n {children}\n </Disclosure__Panel>\n )}\n </Disclosure__Root>\n );\n}\n\nconst Disclosure__Root = styled.div<{ $variant: \"popover\" | \"inline\" }>`\n position: relative;\n display: ${({ $variant }) => ($variant === \"inline\" ? \"flex\" : \"inline-flex\")};\n flex-direction: column;\n width: ${({ $variant }) => ($variant === \"inline\" ? \"100%\" : \"auto\")};\n`;\n\nconst Disclosure__Trigger = styled.button<{\n $variant: \"popover\" | \"inline\";\n $align: \"between\" | \"end\";\n}>`\n display: inline-flex;\n align-items: center;\n gap: var(--gap-glyph);\n padding: var(--inset-glyph-button);\n background: none;\n border: none;\n color: inherit;\n cursor: pointer;\n ${({ $variant, $align }) =>\n $variant === \"inline\" &&\n css`\n justify-content: ${$align === \"end\" ? \"flex-end\" : \"space-between\"};\n width: 100%;\n border-radius: var(--radius-regular);\n &:hover {\n background: var(--color-surface-sunken);\n }\n `}\n ${focusRing}\n`;\n\n/** The `asButton` trigger: the kit's ghost `Button`, pinned to the row's trailing edge when inline. */\nconst Disclosure__ButtonTrigger = styled(Button)<{ $inline: boolean }>`\n ${({ $inline }) =>\n $inline &&\n css`\n align-self: flex-end;\n `}\n`;\n\nconst Disclosure__Chevron = styled.span<{ $open: boolean }>`\n display: inline-flex;\n flex-shrink: 0;\n @media (prefers-reduced-motion: no-preference) {\n transition: transform var(--duration-base) var(--ease-standard);\n }\n transform: rotate(${({ $open }) => ($open ? 90 : 0)}deg);\n`;\n\nconst Disclosure__Panel = styled.div<{\n $variant: \"popover\" | \"inline\";\n $panelHeight: \"cap\" | \"auto\";\n}>`\n ${({ $variant }) =>\n $variant === \"popover\"\n ? css`\n position: absolute;\n top: 100%;\n right: 0;\n /* Local sibling ordering: lifts the popped panel above following rows, off the app z ladder. */\n z-index: 1;\n margin-top: var(--gap-disclosure);\n `\n : css`\n position: static;\n width: 100%;\n margin-top: var(--gap-disclosure);\n `}\n /* An accordion body grows in flow up to a cap, then scrolls, never spilling past the row below. */\n ${({ $variant, $panelHeight }) =>\n $variant === \"inline\" && $panelHeight === \"cap\"\n ? css`\n max-height: 16rem;\n overflow-y: auto;\n ${focusRingInset}\n `\n : \"\"}\n padding: var(--inset-surface);\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n`;\n","import type { Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled, { css } from \"styled-components\";\nimport {\n figureAttributes,\n resolveCurrency,\n type UnitValue,\n} from \"./readingCurrency\";\n\n/**\n * Props for {@link DivergingBar}.\n *\n * @category Meter\n */\nexport interface DivergingBarProps<Unit extends string = string> {\n /**\n * The signed quantity, or the whole reading it arrived in. Its sign picks the\n * direction: `>= 0` grows the fill rightward from the centre zero line in the\n * \"go\" green, `< 0` grows it leftward in the \"nogo\" red.\n */\n value: UnitValue<Unit>;\n /**\n * The largest `|value|` among the set this bar is compared against (e.g.\n * every term in a rate ledger), in the same unit. The fill reaches the\n * track's half-width when `|value| === maxAbs`; a non-positive scale renders\n * an empty track.\n */\n maxAbs: Value<Unit>;\n /** A class for the outer element, so a styled-component can extend it. */\n className?: string;\n}\n\n/**\n * A small bar centred on zero, for a signed quantity whose DIRECTION matters\n * as much as its size (a ledger term that produces or consumes). Decorative\n * and `aria-hidden`: pair it with the number, which carries the reading.\n *\n * A held figure fades the bar and nothing more; the number beside it says how\n * current it is.\n *\n * Hides itself when the nearest `inline-size` container is narrower than 300px,\n * where the number alone is the reading that matters. A {@link Panel} is such a\n * container, so a bar inside a widget needs none of its own; outside a panel,\n * wrap it in an element with `container-type: inline-size`.\n *\n * @category Meter\n */\nexport function DivergingBar<Unit extends string = string>({\n value,\n maxAbs,\n className,\n}: DivergingBarProps<Unit>) {\n const resolved = resolveCurrency(value);\n const { shown, held } = resolved;\n // `dividedBy` checks the two are the same kind, so the quotient is dimensionless before it becomes a CSS width.\n const pct =\n shown != null && maxAbs.isPositive()\n ? Math.min(50, shown.abs().dividedBy(maxAbs).magnitude * 50)\n : 0;\n return (\n <DivergingBar__Track\n aria-hidden=\"true\"\n data-testid=\"diverging-bar\"\n {...figureAttributes(resolved)}\n className={className}\n >\n <DivergingBar__Zero />\n {/* No fill without a number: a zero-width bar would read as producing and consuming nothing. */}\n {shown != null && (\n <DivergingBar__Fill\n $positive={!shown.isNegative()}\n $held={held}\n style={{ width: `${pct}%` }}\n />\n )}\n </DivergingBar__Track>\n );\n}\n\n/** Below this container width a label, a bar and a number do not fit on one line. */\nconst DIVERGING_BAR_MIN_CONTAINER = \"300px\";\n\nconst DivergingBar__Track = styled.div`\n position: relative;\n width: 3.5rem;\n height: 4px;\n flex: 0 0 auto;\n border-radius: var(--radius-pill);\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-subtle);\n display: none;\n\n @container (min-width: ${DIVERGING_BAR_MIN_CONTAINER}) {\n display: block;\n }\n`;\n\nconst DivergingBar__Zero = styled.div`\n position: absolute;\n left: 50%;\n top: 0;\n bottom: 0;\n width: 1px;\n background: var(--color-border-strong);\n`;\n\nconst DivergingBar__Fill = styled.div<{\n $positive: boolean;\n $held: boolean;\n}>`\n position: absolute;\n top: 0;\n bottom: 0;\n border-radius: var(--radius-pill);\n /* Faint rather than a second hue: the fill's colour already carries the direction. */\n ${({ $held }) => ($held ? \"opacity: 0.45;\" : \"\")}\n ${({ $positive }) =>\n $positive\n ? css`\n left: 50%;\n background: var(--color-go-mark);\n `\n : css`\n right: 50%;\n background: var(--color-nogo-mark);\n `}\n`;\n","import type { HTMLAttributes } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Props for {@link Divider}. Any other `hr` attribute passes through.\n *\n * @category Layout\n */\nexport interface DividerProps extends HTMLAttributes<HTMLHRElement> {\n /**\n * Vertical space above and below the rule, one of the {@link GapToken}\n * names. Omit it for a flush rule, spaced by its surroundings.\n */\n space?: GapToken;\n}\n\n/**\n * A full-width 1px horizontal rule in the subtle border colour, for separating\n * stacked sections. It renders a real `<hr>`, so it carries separator\n * semantics. Flush by default; `space` adds room above and below.\n *\n * @category Layout\n */\nexport function Divider({ space, ...rest }: DividerProps) {\n return <Divider__Root $space={space} {...rest} />;\n}\n\nconst Divider__Root = styled.hr<{ $space?: GapToken }>`\n border: 0;\n border-top: 1px solid var(--color-border-subtle);\n width: 100%;\n margin: ${({ $space }) => ($space ? `${GAP_VAR[$space]} 0` : \"0\")};\n`;\n","import { useId, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { TextButton } from \"./Button\";\n\n/**\n * How much prose stands unprompted, in characters: roughly two lines at the\n * kit's body size in a dashboard column.\n */\nconst DEFAULT_LIMIT = 160;\n\n/**\n * Under this much text past the limit the \"Show more\" button costs more room\n * than the tail it would reveal, so the text stands whole.\n */\nconst WORTH_CUTTING = 24;\n\n/**\n * Three periods rather than the ellipsis character, which the design system\n * does not allow.\n */\nconst CUT_MARK = \"...\";\n\n/**\n * Props for {@link ExpandableText}.\n *\n * @category Typography\n */\nexport interface ExpandableTextProps {\n /** The authored text, rendered verbatim whether cut or whole. */\n children: string;\n /** Characters shown before the cut. Defaults to 160. */\n limit?: number;\n /**\n * What the prose describes (\"Objectives\", \"Description\"), naming the control\n * to a screen reader as \"Show more of Objectives\".\n */\n subject?: string;\n /** A class for the wrapping element, so a styled-component can extend it. */\n className?: string;\n}\n\n/**\n * Prose written by the game or a mod (a contract's description, a part's\n * blurb), cut to a readable length with the rest behind a\n * \"Show more\" button.\n *\n * The cut is a prefix of the string ending on a whole word, followed by\n * `...`; text with no space before `limit` is cut hard at `limit`. It is a character count, not\n * a CSS line clamp. Text no more than 24 characters past `limit` is shown\n * whole with no button, as a bare text node. The reveal is instantaneous.\n *\n * @category Typography\n */\nexport function ExpandableText({\n children,\n limit = DEFAULT_LIMIT,\n subject,\n className,\n}: ExpandableTextProps) {\n const [expanded, setExpanded] = useState(false);\n const proseId = useId();\n\n const head = cut(children, limit);\n\n // Prose short enough to stand whole renders as the caller's own text node.\n if (head === undefined) return <>{children}</>;\n\n const verb = expanded ? \"Show less\" : \"Show more\";\n\n return (\n <ExpandableText__Root className={className}>\n <span id={proseId}>{expanded ? children : `${head}${CUT_MARK}`}</span>{\" \"}\n <ExpandableText__Toggle\n type=\"button\"\n /* A stable hook for a render scene, rather than a generated class name. */\n data-expandable-toggle=\"\"\n aria-controls={proseId}\n aria-expanded={expanded}\n aria-label={subject === undefined ? undefined : `${verb} of ${subject}`}\n onClick={() => setExpanded((open) => !open)}\n >\n {verb}\n </ExpandableText__Toggle>\n </ExpandableText__Root>\n );\n}\n\n/**\n * The prefix to show, or `undefined` when the text is short enough to stand\n * whole. Cuts on the last space at or before `limit`; a run-on with no space\n * is cut hard at `limit`.\n */\nfunction cut(text: string, limit: number): string | undefined {\n if (text.length <= limit + WORTH_CUTTING) return undefined;\n\n const boundary = text.lastIndexOf(\" \", limit);\n return boundary <= 0 ? text.slice(0, limit) : text.slice(0, boundary);\n}\n\nconst ExpandableText__Root = styled.span`\n /* Long prose may carry a word wider than its column, and a broken word beats a sideways scroll. */\n overflow-wrap: break-word;\n`;\n\nconst ExpandableText__Toggle = styled(TextButton)`\n /* Sits on the text's baseline as the paragraph's last word, so the cut and its undo read as one. */\n white-space: nowrap;\n`;\n","import {\n asDeterministic,\n type CelestialBody,\n poseAtIndex,\n type SystemPoses,\n type Value,\n value,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport type { UnitValue } from \"./readingCurrency\";\n\n/**\n * Turns a quantity computed from the body catalogue into the figure a readout\n * draws, carrying how exact and how current it is. Made by\n * {@link ephemerisFigureOf}; hand the result to `Unit`'s `value`.\n *\n * @category Unit\n */\nexport type EphemerisFigure = <Unit extends string>(\n figure: Value<Unit>,\n) => UnitValue<Unit>;\n\n/**\n * The figure-maker for quantities computed from `bodies`' orbits and the clock\n * alone: a phase angle between two planets, a transfer window between them, a\n * pair's separation. Pass the poses `useSystemInstant` returns and the bodies\n * the figure rests on.\n *\n * The figure is as current as the least current pose it rests on. Where every\n * one of those poses is `exact` (on a fixed conic, an orbit that never changes, as every body of a stock\n * install is) the figure is stamped deterministic and takes no mark, however long\n * ago the catalogue last arrived. Where any is `held`, because the instant is\n * past its provider's horizon or the catalogue stopped arriving under a body\n * that drifts, the figure comes back held as of the oldest of them. A `modelled`\n * pose claims no exactness and takes no mark.\n *\n * A figure that also rests on the craft does not come through here: give it\n * the craft's mark with {@link derivedMarking}, since one inexact input makes\n * the result inexact.\n *\n * @category Unit\n */\nexport function ephemerisFigureOf(\n poses: SystemPoses | undefined,\n bodies: readonly (CelestialBody | null | undefined)[],\n): EphemerisFigure {\n const resting = bodies.map((body) =>\n body == null ? null : poseAtIndex(poses, body.index),\n );\n const exact =\n resting.length > 0 && resting.every((pose) => pose?.currency === \"exact\");\n const heldAt = resting.reduce<number | null>((oldest, pose) => {\n if (pose?.currency !== \"held\" || pose.asOfUt === null) return oldest;\n return oldest === null ? pose.asOfUt : Math.min(oldest, pose.asOfUt);\n }, null);\n return (figure) => {\n const drawn = exact ? asDeterministic(figure) : figure;\n if (heldAt === null) return drawn;\n return {\n state: \"held\",\n value: drawn,\n asOfUt: value(\"ut\", heldAt) as Value<\"ut\">,\n grade: \"held\",\n reckoning: { status: \"none\" },\n };\n };\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\n\n/**\n * Props for {@link Fill}.\n *\n * @category Layout\n */\nexport interface FillProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Grow to fill a flex parent (`flex: 1 1 auto`) instead of taking full\n * height. Use for a slot nested in a column (e.g. a graph slot sitting\n * above a sibling notice row); omit for the outermost container that\n * fills the dashboard grid cell.\n */\n grow?: boolean;\n /** The content that takes the space. */\n children?: ReactNode;\n}\n\n/**\n * An overlay anchor that fills its parent: a `position: relative` flex column\n * that occupies exactly the space its parent gives it, so an absolutely\n * positioned overlay (e.g. a corner notice pill over a graph) has something to\n * sit on. It draws nothing itself. Full width and height by default; `grow`\n * for a slot nested in another flex column.\n *\n * @category Layout\n */\nexport function Fill({ grow = false, children, ...rest }: FillProps) {\n return (\n <Fill__Root $grow={grow} {...rest}>\n {children}\n </Fill__Root>\n );\n}\n\nconst Fill__Root = styled.div<{ $grow: boolean }>`\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n\n ${({ $grow }) =>\n $grow\n ? `flex: 1 1 auto;`\n : `\n height: 100%;\n width: 100%;\n `}\n`;\n","import type { ReactNode } from \"react\";\nimport { EmptyState } from \"./EmptyState\";\nimport { Stack } from \"./Stack\";\nimport { FilterRegion, type TermSegment, useRowFilter } from \"./useRowFilter\";\n\n/*\n * A filterable list over pre-processed rows: each carries the text it is\n * searchable by and its rendered node, so FilterList only matches strings.\n *\n * Providers add search terms, shown as toggles, through the universal\n * `filters` contribution segment, completed from the mounting widget's\n * context. Outside a widget the term list is empty and every row passes. The\n * filter model is `useRowFilter`, shared with hosts that render their own rows.\n */\n\n/**\n * One row of a {@link FilterList}: its key, the text it is matched against,\n * and the node to draw.\n *\n * @category FilterList\n */\nexport interface FilterRow {\n /** Stable React key for the row. */\n id: string;\n /** The text this row is matched against, built by the widget from its own fields. */\n searchText: string;\n /** The already-rendered row. */\n node: ReactNode;\n}\n\n/**\n * Props for {@link FilterList}.\n *\n * @category FilterList\n */\nexport interface FilterListProps {\n /** Every row, in display order; the list shows the ones that match. */\n rows: readonly FilterRow[];\n /** The contribution segment to pull toggle terms from. Defaults to the universal `filters`. */\n segment?: TermSegment;\n /** Shown when no row matches. Defaults to \"Nothing matches the filter\". */\n emptyLabel?: ReactNode;\n}\n\n/**\n * A filterable list of pre-rendered rows, with its filter control (toggle\n * chips for contributed terms, then a search box) above it. Each row carries\n * the text it is searchable by, so the list only matches strings: a row shows\n * while its `searchText` contains every selected term and the typed text,\n * case-insensitively.\n *\n * The toggle terms come from the `filters` contribution segment of the widget\n * the list is mounted in; outside a widget there are none and only the search\n * box shows. For rows the host renders itself (a table), use\n * {@link useRowFilter} with {@link FilterRegion} instead.\n *\n * @example\n * ```tsx\n * <FilterList\n * rows={contracts.map((c) => ({\n * id: c.id,\n * searchText: `${c.title} ${c.agency}`,\n * node: <Card title={c.title}>{c.agency}</Card>,\n * }))}\n * emptyLabel=\"No contracts match\"\n * />\n * ```\n *\n * @category FilterList\n * @categoryDescription FilterList\n * Lists an operator can narrow: rows filtered by typed text and contributed\n * filter chips, selectable rows, and the hook for filtering rows a widget draws\n * itself.\n */\nexport function FilterList({\n rows,\n segment = \"filters\",\n emptyLabel = \"Nothing matches the filter\",\n}: FilterListProps) {\n const filter = useRowFilter({ segment });\n const shown = rows.filter((row) => filter.matches(row.searchText));\n\n return (\n <FilterRegion filter={filter}>\n {shown.length > 0 ? (\n <Stack gap=\"related\">\n {shown.map((row) => (\n <div key={row.id}>{row.node}</div>\n ))}\n </Stack>\n ) : (\n <EmptyState>{emptyLabel}</EmptyState>\n )}\n </FilterRegion>\n );\n}\n","import {\n type HTMLAttributes,\n type ReactNode,\n useCallback,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styled from \"styled-components\";\nimport {\n type AnchoredPosition,\n type AnchorPoint,\n anchoredPosition,\n} from \"./anchoredPosition\";\n\n/**\n * The props of {@link Floating}.\n *\n * @category Floating\n */\nexport interface FloatingProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * The viewport point the layer attaches to, or a function returning it. A\n * function is re-read whenever the layer re-places (on resize and on any\n * scroll), so a layer anchored to an element follows it.\n */\n anchor: AnchorPoint | (() => AnchorPoint | null);\n /** What the layer shows. It has no surface of its own, so style it here. */\n children?: ReactNode;\n}\n\n/**\n * A layer drawn over the whole page rather than inside its widget: a hover\n * card, a tooltip, an anchored menu. It is portalled to the document body, so\n * no panel or scroll box clips it, and placed against the viewport by\n * {@link anchoredPosition}. It is measured before paint (and stays hidden\n * until then), and re-placed on every render, on resize and on any scroll.\n * The layer has no surface of its own: style what you put inside it. Any\n * other `div` attribute passes through to the layer.\n *\n * React events still bubble through the component tree, not the DOM, so a\n * pointer handler on an ancestor sees events from inside the layer.\n *\n * @example\n * ```tsx\n * function PartMenu({ details }: { details: ReactNode }) {\n * const [anchor, setAnchor] = useState<AnchorPoint | null>(null);\n * return (\n * <>\n * <Button onClick={(e) => setAnchor({ x: e.clientX, y: e.clientY })}>\n * Details\n * </Button>\n * {anchor && (\n * <Floating anchor={anchor} onMouseLeave={() => setAnchor(null)}>\n * <Box surface=\"raised\" pad=\"popover\" radius=\"floating\" bordered>\n * {details}\n * </Box>\n * </Floating>\n * )}\n * </>\n * );\n * }\n * ```\n *\n * @category Floating\n * @categoryDescription Floating\n * Layers drawn over the page instead of inside a widget: tooltips, hover cards\n * and anchored menus, placed against the viewport so no panel clips them.\n */\nexport function Floating({ anchor, style, children, ...rest }: FloatingProps) {\n const [host, setHost] = useState<HTMLDivElement | null>(null);\n const [pos, setPos] = useState<AnchoredPosition | null>(null);\n const anchorRef = useRef(anchor);\n anchorRef.current = anchor;\n\n const place = useCallback(() => {\n if (!host) return;\n const current = anchorRef.current;\n const at = typeof current === \"function\" ? current() : current;\n if (!at) return;\n const box = host.getBoundingClientRect();\n const next = anchoredPosition(\n at,\n { w: box.width, h: box.height },\n { w: window.innerWidth, h: window.innerHeight },\n );\n setPos((prev) =>\n prev && prev.left === next.left && prev.top === next.top ? prev : next,\n );\n }, [host]);\n\n // Every render re-places, since the anchor may have moved with it; an unchanged position is not a state change.\n useLayoutEffect(() => {\n place();\n });\n\n useLayoutEffect(() => {\n if (!host) return;\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(place);\n observer?.observe(host);\n window.addEventListener(\"resize\", place);\n // Captured: the dashboard scrolls inner containers, whose scroll events do not bubble.\n window.addEventListener(\"scroll\", place, true);\n return () => {\n observer?.disconnect();\n window.removeEventListener(\"resize\", place);\n window.removeEventListener(\"scroll\", place, true);\n };\n }, [host, place]);\n\n if (typeof document === \"undefined\") return null;\n return createPortal(\n <Floating__Root\n ref={setHost}\n // Hidden until measured, so the unplaced first frame never shows.\n style={{\n ...style,\n left: pos?.left ?? 0,\n top: pos?.top ?? 0,\n visibility: pos === null ? \"hidden\" : style?.visibility,\n }}\n {...rest}\n >\n {children}\n </Floating__Root>,\n document.body,\n );\n}\n\nconst Floating__Root = styled.div`\n position: fixed;\n z-index: var(--z-dropdown);\n`;\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Vertical alignment of the cells in a {@link Grid} row.\n *\n * @category Layout\n */\nexport type GridAlign = \"center\" | \"start\" | \"baseline\" | \"stretch\";\n\n/**\n * Props for {@link Grid}. Any other `div` attribute passes through.\n *\n * @category Layout\n */\nexport interface GridProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * `align-items` shorthand. Defaults to `center`; `baseline` is what a\n * label/value grid wants, so a caption and a larger value share a baseline;\n * `stretch` makes every cell in a row as tall as the tallest.\n */\n align?: GridAlign;\n /**\n * Fixed column template (e.g. `\"120px 1fr 60px\"`). Takes precedence over\n * `minColWidth` when both are set.\n */\n cols?: string;\n /**\n * Responsive columns: as many equal columns of at least this width as fit\n * (`repeat(auto-fill, minmax(min(minColWidth, 100%), 1fr))`). A container\n * narrower than `minColWidth` gets one column of its own width rather than\n * overflowing. Ignored when `cols` is set.\n */\n minColWidth?: string;\n /**\n * Auto-fit rather than auto-fill the `minColWidth` columns: a row with fewer\n * cells than columns stretches them across the row instead of leaving empty\n * tracks beside them. Only meaningful with `minColWidth`.\n */\n fit?: boolean;\n /** Gap between cells, one of the {@link GapToken} names. Defaults to `related-dense`. */\n gap?: GapToken;\n /**\n * Row gap, when it differs from the column gap: a label/value grid usually\n * wants its rows tighter than its columns.\n */\n rowGap?: GapToken;\n /** The cells, filled in reading order. */\n children?: ReactNode;\n}\n\nconst ALIGN_ITEMS: Record<GridAlign, string> = {\n center: \"center\",\n start: \"start\",\n baseline: \"baseline\",\n stretch: \"stretch\",\n};\n\n/**\n * A CSS grid, either with a fixed column template (`cols`) or with as many\n * equal columns of at least `minColWidth` as fit. With neither it is a single\n * column.\n *\n * @category Layout\n */\nexport function Grid({\n cols,\n minColWidth,\n fit = false,\n gap = \"related-dense\",\n rowGap,\n align = \"center\",\n children,\n ...rest\n}: GridProps) {\n return (\n <Grid__Root\n $cols={cols}\n $minColWidth={minColWidth}\n $fit={fit}\n $gap={gap}\n $rowGap={rowGap}\n $align={align}\n {...rest}\n >\n {children}\n </Grid__Root>\n );\n}\n\nconst Grid__Root = styled.div<{\n $cols?: string;\n $minColWidth?: string;\n $fit: boolean;\n $gap: GapToken;\n $rowGap?: GapToken;\n $align: GridAlign;\n}>`\n display: grid;\n align-items: ${({ $align }) => ALIGN_ITEMS[$align]};\n gap: ${({ $gap, $rowGap }) =>\n $rowGap ? `${GAP_VAR[$rowGap]} ${GAP_VAR[$gap]}` : GAP_VAR[$gap]};\n grid-template-columns: ${({ $cols, $minColWidth, $fit }) => {\n if ($cols) return $cols;\n if ($minColWidth)\n return `repeat(${$fit ? \"auto-fit\" : \"auto-fill\"}, minmax(min(${$minColWidth}, 100%), 1fr))`;\n return \"1fr\";\n }};\n`;\n","import type { HeldGrade } from \"@ksp-gonogo/sitrep-sdk\";\nimport { Badge, type BadgeSize } from \"./Badge\";\nimport { severityFromStreamStatus } from \"./status/severity\";\nimport { heldWord } from \"./status/streamStatusWord\";\nimport { Tooltip } from \"./Tooltip\";\n\n/**\n * Props for {@link HeldBadge}.\n *\n * @category Badge\n */\nexport interface HeldBadgeProps {\n /**\n * Why the reading behind this part of the widget stopped updating: `held`,\n * `disconnected`, `last-before-blackout`, `recorded`, `loading` or `no-game`.\n * Its word is the one {@link formatStreamStatus} gives the status of that name.\n */\n grade: HeldGrade;\n /** What is held, in the operator's terms, for the hover text: \"Contract board\", a part's title. */\n subject?: string;\n /** Text size, `sm` or `md`. Absent, `md`. */\n size?: BadgeSize;\n}\n\n/**\n * The badge a widget draws beside one part of itself whose reading is held: a\n * row, a card, a totals cell. It prints the grade's word and severity, so it\n * always agrees with the panel's own badge.\n *\n * It is not a live region, unlike {@link StreamStatusBadge}: the panel's own\n * badge announces the change once.\n *\n * @category Badge\n */\nexport function HeldBadge({ grade, subject, size }: HeldBadgeProps) {\n const word = heldWord(grade);\n return (\n <Tooltip\n text={subject === undefined ? undefined : `${subject}: ${word}`}\n focusable\n >\n <Badge tone={severityFromStreamStatus(grade)} size={size}>\n {word}\n </Badge>\n </Tooltip>\n );\n}\n","import {\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\";\nimport styled from \"styled-components\";\nimport { Floating } from \"./Floating\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * The props of {@link HoverCard}.\n *\n * @category Floating\n */\nexport interface HoverCardProps {\n /** The always-visible trigger content: a glyph, a tag, a short label. */\n trigger: ReactNode;\n /** Accessible name for the trigger, required since the trigger is usually a glyph. */\n \"aria-label\": string;\n /** What the card says. Read-only facts: a hover card holds no controls. */\n children: ReactNode;\n}\n\n/** How long the card waits after the pointer leaves, so it can cross the gap onto the card. */\nconst CLOSE_DELAY_MS = 120;\n\n/**\n * A card of facts about a trigger, shown while the trigger is hovered or\n * focused and gone when neither is, drawn over the page in a {@link Floating}\n * so no panel clips it. The trigger renders as an unstyled `button`. Escape\n * dismisses the card; the pointer can move onto the card without closing it.\n * The card is the trigger's description, so a screen reader hears it on focus.\n *\n * @example\n * ```tsx\n * <HoverCard trigger={<InfoIcon />} aria-label=\"Signal path\">\n * <Text>Relayed through {relayName}</Text>\n * </HoverCard>\n * ```\n *\n * @category Floating\n */\nexport function HoverCard({\n trigger,\n \"aria-label\": ariaLabel,\n children,\n}: HoverCardProps) {\n const [open, setOpen] = useState(false);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cardId = useId();\n\n const cancelClose = useCallback(() => {\n if (closeTimer.current === null) return;\n clearTimeout(closeTimer.current);\n closeTimer.current = null;\n }, []);\n const show = () => {\n cancelClose();\n setOpen(true);\n };\n const hideSoon = () => {\n cancelClose();\n closeTimer.current = setTimeout(() => setOpen(false), CLOSE_DELAY_MS);\n };\n const hide = useCallback(() => {\n cancelClose();\n setOpen(false);\n }, [cancelClose]);\n\n useEffect(() => cancelClose, [cancelClose]);\n\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") hide();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, hide]);\n\n const anchor = () => {\n const rect = triggerRef.current?.getBoundingClientRect();\n return rect ? { x: rect.left, y: rect.bottom } : null;\n };\n\n return (\n <>\n <HoverCard__Trigger\n ref={triggerRef}\n type=\"button\"\n aria-label={ariaLabel}\n aria-describedby={open ? cardId : undefined}\n onPointerEnter={show}\n onPointerLeave={hideSoon}\n onFocus={show}\n onBlur={hide}\n >\n {trigger}\n </HoverCard__Trigger>\n {open && (\n <Floating\n anchor={anchor}\n onPointerEnter={cancelClose}\n onPointerLeave={hideSoon}\n >\n <HoverCard__Card id={cardId} role=\"tooltip\">\n {children}\n </HoverCard__Card>\n </Floating>\n )}\n </>\n );\n}\n\nconst HoverCard__Trigger = styled.button`\n display: inline-flex;\n align-items: center;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n cursor: default;\n\n ${focusRing}\n`;\n\nconst HoverCard__Card = styled.div`\n padding: var(--inset-popover);\n border: 1px solid var(--color-border-strong);\n border-radius: var(--radius-regular);\n background: var(--color-surface-raised);\n color: var(--color-text-primary);\n`;\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { GAP_VAR, type GapToken } from \"./scales\";\n\n/**\n * Props for {@link Inline}. Any other `span` attribute passes through.\n *\n * @category Layout\n */\nexport interface InlineProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Gap between children, one of the {@link GapToken} names. Omitted, it is\n * the `related` gap of whatever container the run sits in, so it matches a\n * panel's or a card's spacing. Set it only where this run decides its own\n * spacing.\n */\n gap?: GapToken;\n /**\n * Adds `margin-left: 6px` so this run sits apart from a preceding sibling\n * run (e.g. a badge row followed by an action-button row).\n */\n inset?: boolean;\n /**\n * Lets the run break onto further lines and shrink with its container.\n * Without it the run stays on one line at its full width.\n *\n * Turn this on wherever the number of children comes from data, since a run\n * that cannot break overflows a narrow column.\n */\n wrap?: boolean;\n /** The runs of text and inline items, laid out in a line. */\n children?: ReactNode;\n}\n\n/**\n * A compact inline-flex `span` for a run of badges or action buttons. It does\n * not shrink (`flex-shrink: 0`), so a truncating sibling gives up space before\n * it does; set `wrap` when the number of children depends on data.\n *\n * @category Layout\n */\nexport function Inline({\n gap,\n inset = false,\n wrap = false,\n children,\n ...rest\n}: InlineProps) {\n return (\n <Inline__Root $gap={gap} $inset={inset} $wrap={wrap} {...rest}>\n {children}\n </Inline__Root>\n );\n}\n\nconst Inline__Root = styled.span<{\n $gap?: GapToken;\n $inset: boolean;\n $wrap: boolean;\n}>`\n display: inline-flex;\n gap: ${({ $gap }) => ($gap ? GAP_VAR[$gap] : \"var(--gap-related)\")};\n flex-shrink: ${({ $wrap }) => ($wrap ? 1 : 0)};\n ${({ $wrap }) => $wrap && `flex-wrap: wrap; min-width: 0;`}\n ${({ $inset }) => $inset && `margin-left: var(--gap-inline-cluster);`}\n`;\n","import type { KeyboardEvent, PointerEvent } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * What displacement MEANS.\n *\n * - `offset`: where the handle sits is the value. Needs bounds\n * - `rate`: where the handle sits is the speed the value changes at, and it\n * springs back to centre on release. Needs no bounds, for a value such as an\n * instant years out\n *\n * @category Form\n */\nexport type JogWheelMode = \"offset\" | \"rate\";\n\n/**\n * The props every {@link JogWheel} takes, whichever its mode.\n *\n * @category Form\n */\nexport interface JogWheelCommon {\n value: number;\n step: number;\n /** Drag/keyboard axis. Default \"horizontal\". */\n orientation?: \"horizontal\" | \"vertical\";\n onChange: (next: number) => void;\n /** Caret label formatter, also used as `aria-valuetext`. Default `String(Math.round(v))`; for a quantity, write it with {@link writeQuantity}. */\n format?: (v: number) => string;\n \"aria-label\": string;\n /**\n * The name drawn above the wheel. Defaults to `aria-label`; `false` draws\n * none, for a wheel the caller already names beside it.\n */\n label?: string | false;\n disabled?: boolean;\n /**\n * Box width in CSS px. Defaults to 56 horizontal / 24 vertical. Clamped up\n * to the smallest touch target. A `format` longer than a few\n * characters needs more than the default, as the caret label is clipped to\n * the box.\n */\n width?: number;\n /**\n * Box height in CSS px. Defaults to 24 horizontal / 56 vertical. Clamped up\n * to the smallest touch target.\n */\n height?: number;\n}\n\n/**\n * Props for {@link JogWheel}. In the default `offset` mode `min` and `max` are\n * required; in `rate` mode they are optional.\n *\n * @category Form\n */\nexport type JogWheelProps =\n | (JogWheelCommon & {\n mode?: \"offset\";\n min: number;\n max: number;\n })\n | (JogWheelCommon & {\n mode: \"rate\";\n /** Optional here: a rate control does not need somewhere to stop. */\n min?: number;\n max?: number;\n /**\n * How many `step`s per second at FULL displacement. The travel between\n * centre and full is what gives the fine end of the range, so this sets\n * the coarse end.\n */\n stepsPerSecond?: number;\n });\n\n/** Pixels of pointer travel per `step` of value. */\nconst SENSITIVITY_PX_PER_STEP = 4;\n\n/** Pointer travel from centre to FULL rate, in rate mode. */\nconst RATE_TRAVEL_PX = 80;\n\n/** How often a held rate control emits, in ms. */\nconst RATE_TICK_MS = 60;\n\n/** Default steps per second at full displacement. */\nconst DEFAULT_STEPS_PER_SECOND = 30;\n\n/**\n * The smallest either side of the wheel's box can be, in CSS px: WCAG 2.2 SC\n * 2.5.8 (Target Size, Minimum) at AA. Drag range is unaffected by the box, since the wheel captures\n * the pointer.\n *\n * @category Form\n */\nexport const JOG_WHEEL_MIN_TARGET_PX = 24;\n\n/**\n * Below this on the cross axis the 4px inset would clip the caret label.\n */\nconst COMPACT_CROSS_AXIS_PX = 32;\n\n/** Default box, per orientation: the long axis first. */\nconst DEFAULT_LONG_PX = 56;\nconst DEFAULT_SHORT_PX = JOG_WHEEL_MIN_TARGET_PX;\n\n/**\n * Pure clamp + quantise: move `value` by `deltaSteps` of `step` (fractional\n * deltaSteps welcome, for pointer drag), clamp to `[min,max]`, and snap to the\n * step grid.\n *\n * The grid is anchored at `min`. With an infinite `min` (an unbounded control),\n * the value moves by exactly the delta asked for and snaps to nothing.\n *\n * @category Form\n */\nexport function applyDelta(\n value: number,\n bounds: { min: number; max: number; step: number },\n deltaSteps: number,\n): number {\n const { min, max, step } = bounds;\n const raw = value + deltaSteps * step;\n const clamped = Math.min(max, Math.max(min, raw));\n if (!Number.isFinite(min)) return clamped;\n const snapped = min + Math.round((clamped - min) / step) * step;\n return Math.min(max, Math.max(min, snapped));\n}\n\n/**\n * A fine-grain tape scrubber against a fixed centre caret, for dialling a\n * number by small increments by pointer drag along its axis or by keyboard\n * (arrow keys move one `step`, Home and End go to `min` and `max`). A\n * focusable `role=\"slider\"`; nothing is emitted while `disabled`. Sized with\n * `width` and `height` in CSS px, as {@link Dial} and {@link Tape} are.\n *\n * Two limits:\n *\n * - The caret label and `aria-valuetext` are the same string, so a screen\n * reader cannot be given a fuller value description than the face shows\n * - The drag axis is viewport-relative, so a wheel rotated with a CSS\n * transform cannot be dragged along its drawn axis. Do not rotate it\n *\n * @example\n * ```tsx\n * <JogWheel\n * aria-label=\"Gain\"\n * value={gain}\n * min={0}\n * max={100}\n * step={1}\n * onChange={setGain}\n * />\n * ```\n *\n * @category Form\n */\nexport function JogWheel(props: JogWheelProps): JSX.Element {\n const {\n value,\n min,\n max,\n step,\n orientation = \"horizontal\",\n onChange,\n format,\n \"aria-label\": ariaLabel,\n label: caption = ariaLabel,\n disabled = false,\n width,\n height,\n } = props;\n const vertical = orientation === \"vertical\";\n const boxWidth = Math.max(\n JOG_WHEEL_MIN_TARGET_PX,\n width ?? (vertical ? DEFAULT_SHORT_PX : DEFAULT_LONG_PX),\n );\n const boxHeight = Math.max(\n JOG_WHEEL_MIN_TARGET_PX,\n height ?? (vertical ? DEFAULT_LONG_PX : DEFAULT_SHORT_PX),\n );\n const rate = props.mode === \"rate\";\n // A rate control has no ends, so its arithmetic runs unbounded rather than clamping to an invented pair.\n const lo = min ?? Number.NEGATIVE_INFINITY;\n const hi = max ?? Number.POSITIVE_INFINITY;\n const bounds = { min: lo, max: hi, step };\n const drag = useRef<{ start: number; startValue: number } | null>(null);\n // The latest value, so the interval does not close over the value the drag began at.\n const latest = useRef(value);\n latest.current = value;\n const [displacement, setDisplacement] = useState(0);\n\n const label = format ? format(value) : String(Math.round(value));\n const fraction =\n hi > lo && Number.isFinite(hi - lo) ? (value - lo) / (hi - lo) : 0.5;\n\n const emit = (next: number): void => {\n if (disabled) return;\n if (next !== value) onChange(next);\n };\n\n /**\n * While the handle is off centre, move the value at a speed set by how far.\n *\n * Cleaned up on release and on unmount, so it never drives a value nobody is\n * holding.\n */\n useEffect(() => {\n if (!rate || displacement === 0 || disabled) return;\n const perSecond =\n (props.mode === \"rate\" ? props.stepsPerSecond : undefined) ??\n DEFAULT_STEPS_PER_SECOND;\n const timer = setInterval(() => {\n const moved =\n latest.current +\n displacement * perSecond * step * (RATE_TICK_MS / 1000);\n onChange(moved);\n }, RATE_TICK_MS);\n return () => clearInterval(timer);\n }, [rate, displacement, disabled, step, onChange, props]);\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (disabled) return;\n let next: number | null = null;\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = applyDelta(value, bounds, 1);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = applyDelta(value, bounds, -1);\n break;\n case \"Home\":\n // Nowhere to go in a mode with no ends, so the key does nothing rather than jumping to an infinity.\n if (!Number.isFinite(lo)) return;\n next = lo;\n break;\n case \"End\":\n if (!Number.isFinite(hi)) return;\n next = hi;\n break;\n default:\n return;\n }\n e.preventDefault();\n emit(next);\n };\n\n const axisPos = (e: PointerEvent<HTMLDivElement>): number =>\n orientation === \"vertical\" ? e.clientY : e.clientX;\n\n const handlePointerDown = (e: PointerEvent<HTMLDivElement>): void => {\n if (disabled) return;\n drag.current = { start: axisPos(e), startValue: value };\n e.currentTarget.setPointerCapture(e.pointerId);\n };\n\n const handlePointerMove = (e: PointerEvent<HTMLDivElement>): void => {\n if (disabled || !drag.current) return;\n // Vertical: dragging UP (clientY decreases) should INCREASE the value.\n const travel =\n orientation === \"vertical\"\n ? drag.current.start - axisPos(e)\n : axisPos(e) - drag.current.start;\n if (rate) {\n // Displacement sets the speed, so nothing is emitted here: the ticking effect does the moving.\n const fractionOfFull = travel / RATE_TRAVEL_PX;\n setDisplacement(Math.max(-1, Math.min(1, fractionOfFull)));\n return;\n }\n const deltaSteps = travel / SENSITIVITY_PX_PER_STEP;\n emit(applyDelta(drag.current.startValue, bounds, deltaSteps));\n };\n\n const endDrag = (e: PointerEvent<HTMLDivElement>): void => {\n if (!drag.current) return;\n drag.current = null;\n // Springs back, so the value stops moving once the operator lets go.\n setDisplacement(0);\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n };\n\n return (\n <JogWheel__Frame>\n {caption !== false && (\n <JogWheel__Caption aria-hidden=\"true\">{caption}</JogWheel__Caption>\n )}\n <JogWheel__Root\n role=\"slider\"\n aria-label={ariaLabel}\n aria-orientation={orientation}\n aria-valuenow={Number.isFinite(value) ? value : undefined}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuetext={label}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n $orientation={orientation}\n $disabled={disabled}\n $width={boxWidth}\n $height={boxHeight}\n $compact={(vertical ? boxWidth : boxHeight) < COMPACT_CROSS_AXIS_PX}\n onKeyDown={handleKeyDown}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n >\n <JogWheel__Tape\n $orientation={orientation}\n style={\n orientation === \"vertical\"\n ? { transform: `translateY(${(0.5 - fraction) * 100}%)` }\n : { transform: `translateX(${(0.5 - fraction) * 100}%)` }\n }\n aria-hidden=\"true\"\n />\n <JogWheel__Caret $orientation={orientation} aria-hidden=\"true\" />\n <JogWheel__Label aria-hidden=\"true\">{label}</JogWheel__Label>\n </JogWheel__Root>\n </JogWheel__Frame>\n );\n}\n\nconst JogWheel__Frame = styled.div`\n display: inline-flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--gap-related-packed);\n`;\n\nconst JogWheel__Caption = styled.span`\n font-size: var(--font-size-caption);\n color: var(--color-text-muted);\n white-space: nowrap;\n`;\n\nconst JogWheel__Root = styled.div<{\n $orientation: \"horizontal\" | \"vertical\";\n $disabled: boolean;\n $width: number;\n $height: number;\n $compact: boolean;\n}>`\n position: relative;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n width: ${(p) => p.$width}px;\n height: ${(p) => p.$height}px;\n padding: ${(p) =>\n p.$compact ? \"var(--inset-jog-wheel-compact)\" : \"var(--inset-jog-wheel)\"};\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n background: var(--color-surface-raised);\n color: var(--color-text-primary);\n cursor: ${(p) =>\n p.$disabled\n ? \"not-allowed\"\n : p.$orientation === \"vertical\"\n ? \"ns-resize\"\n : \"ew-resize\"};\n touch-action: none;\n user-select: none;\n opacity: ${(p) => (p.$disabled ? 0.5 : 1)};\n\n ${focusRing}\n`;\n\nconst JogWheel__Tape = styled.div<{ $orientation: \"horizontal\" | \"vertical\" }>`\n position: absolute;\n inset: 0;\n background-image: repeating-linear-gradient(\n ${(p) => (p.$orientation === \"vertical\" ? \"0deg\" : \"90deg\")},\n var(--color-border-subtle) 0 1px,\n transparent 1px ${(p) => (p.$orientation === \"vertical\" ? \"8px\" : \"10px\")}\n );\n opacity: 0.6;\n pointer-events: none;\n`;\n\nconst JogWheel__Caret = styled.div<{ $orientation: \"horizontal\" | \"vertical\" }>`\n position: absolute;\n background: var(--color-accent-fg);\n pointer-events: none;\n ${(p) =>\n p.$orientation === \"vertical\"\n ? \"left: 0; right: 0; height: 2px; top: 50%;\"\n : \"top: 0; bottom: 0; width: 2px; left: 50%;\"}\n`;\n\nconst JogWheel__Label = styled.span`\n /* Last DOM sibling, so it paints over the absolute tape and caret without a z-index. */\n position: relative;\n font-family: var(--font-family-mono);\n font-size: var(--font-size-compact);\n color: var(--color-text-primary);\n background: var(--color-surface-raised);\n padding: var(--inset-jog-wheel-label);\n pointer-events: none;\n`;\n","import type {\n ReckoningBasis,\n SeriesReckonedSpan,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { reckoningBasisPhrase } from \"./reckoningBasisPhrase\";\n\n/**\n * One line on a {@link LineGraph}.\n *\n * @category LineGraph\n */\nexport interface LineGraphSeries {\n /** Unique within the graph; used as the line's React key. */\n id: string;\n /** Names this line in the chart's accessible name when part of it is reckoned. Not drawn; falls back to `id`. */\n label?: string;\n /** CSS colour for the stroke, e.g. `var(--color-nogo-mark)`. */\n color: string;\n /**\n * The line's points, ascending by `x`. Fewer than two renders no line for\n * this series. `x` and `y` are bare numbers in whatever unit the caller\n * chose, the same for every series: the chart scales each axis linearly\n * across the range it is given and labels neither. For a trend over time\n * pass seconds as `x`, a UT or seconds ago.\n */\n points: ReadonlyArray<{ x: number; y: number }>;\n /**\n * Indices into `points` that open a known hole: no data between the previous\n * point and that one (a comms blackout, a recording that overran). The stroke\n * and the sparkline fill break there, since a line through a span with no\n * readings would look like data.\n */\n breaks?: readonly number[];\n /**\n * Runs of `points` that a model carried forward rather than measured, by\n * index (`from` and `to` inclusive). Each is drawn muted and dashed in the\n * series' own colour, never recoloured, and the chart's accessible name says\n * how it was reckoned. A run carrying `bandLo`, `bandHi` and `bandKind` also\n * shades the region between its bounds, with no edge line, and the y domain\n * grows to include the bounds.\n */\n reckoned?: readonly SeriesReckonedSpan[];\n /**\n * How to draw between two points, from the plotted value's sample kind:\n * `\"linear\"` (the default) joins them with a line, `\"step\"` holds each value\n * until the next point and then steps, for a value that stands until it\n * changes, and `\"none\"` marks each point on its own and joins nothing, for a\n * series of separate occurrences.\n */\n interpolation?: \"linear\" | \"step\" | \"none\";\n}\n\n/**\n * A horizontal reference level on a {@link LineGraph}, such as a safe limit.\n *\n * @category LineGraph\n */\nexport interface LineGraphThreshold {\n /** Unique within the graph; used as the threshold's React key. */\n id: string;\n /** Name of the level, for the caller's own bookkeeping. Not rendered; `valueText` is the drawn annotation. */\n label?: string;\n /** The y value the threshold is drawn at, in the same terms as the series' `y`. */\n value: number;\n /** Defaults to a muted warning colour, distinct from the data series. */\n color?: string;\n /**\n * Short text drawn beside a `\"marker\"`-style threshold (e.g. `\"0.5\"`),\n * naming the level the tick sits at. Without it the marker is a bare tick.\n * Ignored by the `\"full\"` rule, which is its own annotation.\n */\n valueText?: string;\n}\n\n/**\n * How a {@link LineGraph} draws its thresholds: `\"full\"` is a dashed rule\n * across the frame, `\"marker\"` a short tick at the left edge.\n *\n * @category LineGraph\n */\nexport type LineGraphThresholdStyle = \"full\" | \"marker\";\n\n/**\n * Props for {@link LineGraph}.\n *\n * @category LineGraph\n */\nexport interface LineGraphProps {\n /** The lines to draw, all against one shared x and y domain. */\n series: readonly LineGraphSeries[];\n /** Horizontal reference levels. */\n thresholds?: readonly LineGraphThreshold[];\n /** Pins the Y domain; otherwise derived from every series' + threshold's values. */\n yDomain?: readonly [number, number];\n /** Chart height in pixels. Width always fills the parent. */\n height?: number;\n /**\n * Accessible name for the whole chart (`role=\"img\"`). Omit when the trend is\n * decorative beside a readout that already carries the reading, and the chart\n * renders `aria-hidden`.\n */\n \"aria-label\"?: string;\n /** Placed on the chart's root element, for a caller's own sizing or layout. */\n className?: string;\n /**\n * `\"chart\"` (default): quarter gridlines and bare strokes. `\"sparkline\"`: no\n * gridlines, and each series area-shaded down to the frame's bottom edge, for\n * a compact glance trend.\n */\n variant?: \"chart\" | \"sparkline\";\n /**\n * How a threshold draws at its y-height. `\"full\"` (default) is a dashed rule\n * spanning the frame. `\"marker\"` is a fixed ~24px tick at the left edge with\n * the threshold's `valueText` beside it, for a threshold that is context\n * rather than the subject of the chart.\n */\n thresholdStyle?: LineGraphThresholdStyle;\n}\n\nconst VIEW_W = 100;\nconst VIEW_H = 40;\n\nfunction computeDomain(\n series: readonly LineGraphSeries[],\n thresholds: readonly LineGraphThreshold[],\n): [number, number] {\n const ys: number[] = [];\n for (const s of series)\n for (const p of s.points) if (Number.isFinite(p.y)) ys.push(p.y);\n for (const s of series)\n for (const run of s.reckoned ?? [])\n for (const y of [...(run.bandLo ?? []), ...(run.bandHi ?? [])])\n if (Number.isFinite(y)) ys.push(y);\n for (const t of thresholds) if (Number.isFinite(t.value)) ys.push(t.value);\n if (ys.length === 0) return [0, 1];\n let min = ys[0];\n let max = ys[0];\n for (const y of ys) {\n if (y < min) min = y;\n if (y > max) max = y;\n }\n if (min === max) {\n // A flat/single-value read still needs headroom to draw a visible line rather than one hugging an edge.\n const pad = min === 0 ? 1 : Math.abs(min) * 0.5;\n return [min - pad, max + pad];\n }\n // 8% headroom top and bottom so a peak/trough never touches the frame.\n const span = max - min;\n return [min - span * 0.08, max + span * 0.08];\n}\n\n/** A reckoned run is muted, never recoloured; 0.6 keeps series colours above 3:1 against the surface. */\nconst RECKONED_STROKE_OPACITY = 0.6;\nconst RECKONED_DASHARRAY = \"5 3\";\n/** A band takes its series' colour on a chart, as an uncertainty region does on `LineChart`. */\nconst BAND_OPACITY = 0.15;\n/** The sparkline's own area fill is the series colour at 0.12, so a band in that colour would vanish into it: a neutral token reads apart. */\nconst BAND_FILL_ON_AREA = \"var(--color-text-primary)\";\nconst BAND_OPACITY_ON_AREA = 0.22;\n\ntype TaggedPoint = { x: number; y: number; basis?: ReckoningBasis };\n\n/** The run's band as a closed polygon's corner list, else null: all three band fields must be present, and a point with no finite x or bound ends the region short of it. */\nfunction bandCorners(\n s: LineGraphSeries,\n run: SeriesReckonedSpan,\n toX: (x: number) => number,\n toY: (y: number) => number,\n): string | null {\n const { bandLo, bandHi, bandKind } = run;\n if (!bandLo || !bandHi || bandKind === undefined) return null;\n const upper: string[] = [];\n const lower: string[] = [];\n for (let i = run.from; i <= run.to; i++) {\n const p = s.points[i];\n const lo = bandLo[i - run.from];\n const hi = bandHi[i - run.from];\n if (\n !p ||\n !Number.isFinite(p.x) ||\n !Number.isFinite(lo) ||\n !Number.isFinite(hi)\n )\n break;\n upper.push(`${toX(p.x)},${toY(hi)}`);\n lower.unshift(`${toX(p.x)},${toY(lo)}`);\n }\n return upper.length < 2 ? null : [...upper, ...lower].join(\" \");\n}\n\n/** Each point's reckoning basis, absent for a measured one. Runs may name indices a trimmed window no longer has, so they are clamped. */\nfunction tagPoints(s: LineGraphSeries): TaggedPoint[] {\n const basisAt: Array<ReckoningBasis | undefined> = new Array(s.points.length);\n for (const run of s.reckoned ?? []) {\n const last = Math.min(s.points.length - 1, run.to);\n for (let i = Math.max(0, run.from); i <= last; i++) basisAt[i] = run.basis;\n }\n return s.points.map((p, i) => ({ x: p.x, y: p.y, basis: basisAt[i] }));\n}\n\n/**\n * One unbroken run cut again where the reckoning basis changes, each piece\n * taking the previous piece's last point so the stroke stays joined.\n */\nfunction splitAtBasis(\n run: ReadonlyArray<TaggedPoint>,\n): Array<{ basis?: ReckoningBasis; points: ReadonlyArray<TaggedPoint> }> {\n const out: Array<{\n basis?: ReckoningBasis;\n points: ReadonlyArray<TaggedPoint>;\n }> = [];\n let start = 0;\n for (let i = 1; i <= run.length; i++) {\n if (i === run.length || run[i].basis !== run[start].basis) {\n const from = start > 0 ? start - 1 : start;\n out.push({ basis: run[start].basis, points: run.slice(from, i) });\n start = i;\n }\n }\n return out;\n}\n\n/**\n * One series' points cut into unbroken runs at its `breaks` indices.\n */\nfunction splitAtBreaks<Point extends { x: number; y: number }>(\n points: ReadonlyArray<Point>,\n breaks: readonly number[] | undefined,\n): Array<ReadonlyArray<Point>> {\n // A sample that is not a finite number is a hole, the same as a stated break.\n const holes = points.flatMap((p, i) =>\n Number.isFinite(p.x) && Number.isFinite(p.y) ? [] : [i],\n );\n if (holes.length > 0) {\n const finite = points.filter(\n (p) => Number.isFinite(p.x) && Number.isFinite(p.y),\n );\n const shifted = [...(breaks ?? []), ...holes]\n .map((cut) => cut - holes.filter((h) => h < cut).length)\n .filter((cut, i, all) => all.indexOf(cut) === i);\n return splitAtBreaks(finite, shifted);\n }\n if (!breaks || breaks.length === 0) return [points];\n const runs: Array<ReadonlyArray<Point>> = [];\n const cuts = [...new Set(breaks)]\n .filter((i) => i > 0 && i < points.length)\n .sort((a, b) => a - b);\n let start = 0;\n for (const cut of cuts) {\n runs.push(points.slice(start, cut));\n start = cut;\n }\n runs.push(points.slice(start));\n return runs;\n}\n\n/**\n * A run's points with a corner inserted before each one, so a line through\n * them holds the previous value until the next point and then steps.\n */\nfunction stepped<Point extends { x: number; y: number }>(\n run: readonly Point[],\n): Point[] {\n const out: Point[] = [];\n for (const [i, point] of run.entries()) {\n if (i > 0) out.push({ ...point, y: run[i - 1].y });\n out.push(point);\n }\n return out;\n}\n\nfunction computeXDomain(series: readonly LineGraphSeries[]): [number, number] {\n const xs: number[] = [];\n for (const s of series)\n for (const p of s.points) if (Number.isFinite(p.x)) xs.push(p.x);\n if (xs.length === 0) return [0, 1];\n let min = xs[0];\n let max = xs[0];\n for (const x of xs) {\n if (x < min) min = x;\n if (x > max) max = x;\n }\n return min === max ? [min - 1, max + 1] : [min, max];\n}\n\n/**\n * A minimal multi-series time-trend chart: coloured lines against a shared\n * domain, plus optional reference lines. For readings that need a trend rather\n * than a precise value; pair it with a {@link Unit} readout for the value.\n *\n * No axis ticks, no legend, no interaction. Gridlines are fixed quarter-marks.\n * Width fills the parent and strokes keep a constant width however the chart\n * is stretched. The y domain is derived from every series and threshold, with\n * headroom, unless `yDomain` pins it.\n *\n * @example A compact dose-rate trend with a safe limit marked\n * ```tsx\n * <LineGraph\n * variant=\"sparkline\"\n * height={96}\n * aria-label=\"Radiation dose rate, last 10 minutes\"\n * series={[\n * {\n * id: \"ambient\",\n * label: \"Ambient\",\n * color: \"var(--color-nogo-mark)\",\n * points: samples.map((s) => ({ x: s.ut, y: s.radPerHour })),\n * },\n * ]}\n * thresholds={[{ id: \"safe\", label: \"Safe threshold\", value: 0.5 }]}\n * thresholdStyle=\"marker\"\n * />\n * ```\n *\n * @category LineGraph\n * @categoryDescription LineGraph\n * A value over time: a line graph with series and thresholds, and the notice a\n * graph shows when it has nothing, or not enough, to draw.\n */\nexport function LineGraph({\n series,\n thresholds = [],\n yDomain,\n height = 120,\n \"aria-label\": ariaLabel,\n className,\n variant = \"chart\",\n thresholdStyle = \"full\",\n}: LineGraphProps) {\n const [yMin, yMax] = yDomain ?? computeDomain(series, thresholds);\n const [xMin, xMax] = computeXDomain(series);\n const ySpan = yMax - yMin || 1;\n const xSpan = xMax - xMin || 1;\n const isSparkline = variant === \"sparkline\";\n const tagged = series.map(tagPoints);\n\n const toX = (x: number) => ((x - xMin) / xSpan) * VIEW_W;\n const toY = (y: number) => VIEW_H - ((y - yMin) / ySpan) * VIEW_H;\n\n const chartLabel = ariaLabel\n ? [\n ariaLabel,\n ...series.flatMap((s) => {\n const name = s.label ?? s.id;\n const runs = s.reckoned ?? [];\n return [\n ...[...new Set(runs.map((run) => run.basis))].map(\n (basis) =>\n `${name}: part of this trace is reckoned, ${reckoningBasisPhrase(basis)}, not measured`,\n ),\n ...(runs.some((run) => run.bandLo && run.bandHi)\n ? [`${name}: the value is inside the shaded region`]\n : []),\n ];\n }),\n ].join(\"; \")\n : undefined;\n\n return (\n <LineGraph__Root className={className} style={{ height }}>\n <svg\n viewBox={`0 0 ${VIEW_W} ${VIEW_H}`}\n preserveAspectRatio=\"none\"\n width=\"100%\"\n height=\"100%\"\n role={ariaLabel ? \"img\" : undefined}\n aria-label={chartLabel}\n aria-hidden={ariaLabel ? undefined : \"true\"}\n >\n {!isSparkline &&\n [0.25, 0.5, 0.75].map((f) => (\n <line\n key={f}\n x1={0}\n x2={VIEW_W}\n y1={VIEW_H * f}\n y2={VIEW_H * f}\n stroke=\"var(--color-border-subtle)\"\n strokeWidth={0.4}\n vectorEffect=\"non-scaling-stroke\"\n />\n ))}\n\n {isSparkline &&\n series.map((s, si) => {\n // Fewer than two points draws nothing, as for the stroke, and separate occurrences have no area under them.\n if (s.points.length < 2 || s.interpolation === \"none\") return null;\n // One polygon per unbroken run, so a hole leaves unshaded ground.\n return splitAtBreaks(tagged[si], s.breaks).map((unstepped) => {\n const run =\n s.interpolation === \"step\" ? stepped(unstepped) : unstepped;\n if (run.length < 2) return null;\n const first = run[0];\n const last = run[run.length - 1];\n const areaPoints = [\n ...run.map((p) => `${toX(p.x)},${toY(p.y)}`),\n `${toX(last.x)},${VIEW_H}`,\n `${toX(first.x)},${VIEW_H}`,\n ].join(\" \");\n return (\n <polygon\n key={`${s.id}-area-${first.x}`}\n points={areaPoints}\n fill={s.color}\n fillOpacity={0.12}\n stroke=\"none\"\n />\n );\n });\n })}\n\n {series.flatMap((s) =>\n (s.reckoned ?? []).map((run, ri) => {\n const corners = bandCorners(s, run, toX, toY);\n if (corners === null) return null;\n return (\n <polygon\n // biome-ignore lint/suspicious/noArrayIndexKey: a band has no identity beyond its run's position\n key={`${s.id}-band-${ri}`}\n points={corners}\n data-band-kind={run.bandKind}\n fill={isSparkline ? BAND_FILL_ON_AREA : s.color}\n fillOpacity={isSparkline ? BAND_OPACITY_ON_AREA : BAND_OPACITY}\n stroke=\"none\"\n />\n );\n }),\n )}\n\n {thresholdStyle === \"full\" &&\n thresholds.map((t) => (\n <line\n key={t.id}\n x1={0}\n x2={VIEW_W}\n y1={toY(t.value)}\n y2={toY(t.value)}\n stroke={t.color ?? \"var(--color-warn-text)\"}\n strokeWidth={0.6}\n strokeDasharray=\"2 1.5\"\n vectorEffect=\"non-scaling-stroke\"\n />\n ))}\n\n {series.map((s) => {\n if (s.interpolation !== \"none\") return null;\n /*\n * Each occurrence its own mark and nothing between: a zero-length\n * line with a round cap, so the mark stays round however far the\n * chart is stretched.\n */\n return s.points.map((p) => (\n <line\n key={`${s.id}-mark-${p.x}`}\n x1={toX(p.x)}\n x2={toX(p.x)}\n y1={toY(p.y)}\n y2={toY(p.y)}\n stroke={s.color}\n strokeWidth={isSparkline ? 3 : 4}\n strokeLinecap=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n />\n ));\n })}\n\n {series.map((s, si) => {\n if (s.points.length < 2 || s.interpolation === \"none\") return null;\n /*\n * One polyline per unbroken run and per basis within it, keyed on its\n * own first x, since run boundaries move as data slides through the window.\n */\n return splitAtBreaks(tagged[si], s.breaks).flatMap((run) =>\n splitAtBasis(run).map((piece) => {\n if (piece.points.length < 2) return null;\n const drawn =\n s.interpolation === \"step\"\n ? stepped(piece.points)\n : piece.points;\n const points = drawn\n .map((p) => `${toX(p.x)},${toY(p.y)}`)\n .join(\" \");\n return (\n <polyline\n key={`${s.id}-${piece.points[0].x}`}\n points={points}\n data-reckoning-basis={piece.basis}\n fill=\"none\"\n stroke={s.color}\n strokeWidth={isSparkline ? 1 : 1.4}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeOpacity={\n piece.basis !== undefined\n ? RECKONED_STROKE_OPACITY\n : undefined\n }\n strokeDasharray={\n piece.basis !== undefined ? RECKONED_DASHARRAY : undefined\n }\n vectorEffect=\"non-scaling-stroke\"\n />\n );\n }),\n );\n })}\n </svg>\n\n {/* \"marker\" thresholds are an HTML overlay outside the stretched viewBox, so their size stays fixed on screen. */}\n {thresholdStyle === \"marker\" &&\n thresholds.map((t) => {\n const topPct = ((yMax - t.value) / ySpan) * 100;\n // A threshold outside the pinned domain is skipped rather than pinned to an edge it is not at.\n if (topPct < 0 || topPct > 100) return null;\n return (\n <LineGraph__ThresholdMarker\n key={t.id}\n aria-hidden=\"true\"\n data-threshold-marker={t.id}\n style={{\n top: `${topPct}%`,\n color: t.color ?? \"var(--color-warn-text)\",\n }}\n >\n <LineGraph__ThresholdTick />\n {t.valueText !== undefined && <span>{t.valueText}</span>}\n </LineGraph__ThresholdMarker>\n );\n })}\n </LineGraph__Root>\n );\n}\n\nconst LineGraph__ThresholdMarker = styled.div`\n position: absolute;\n left: 0;\n transform: translateY(-50%);\n display: inline-flex;\n align-items: center;\n gap: var(--gap-glyph);\n pointer-events: none;\n font-size: var(--font-size-caption);\n font-variant-numeric: tabular-nums;\n /* Single-glyph chrome text centring against a 2px tick: the flush rung. */\n line-height: var(--line-height-flush);\n`;\n\n/** The fixed ~24px tick, the length `Card`'s identity tab uses for a mark rather than a rule. */\nconst LineGraph__ThresholdTick = styled.span`\n display: inline-block;\n width: var(--size-mark);\n height: 2px;\n border-radius: var(--radius-pill);\n background: currentColor;\n`;\n\nconst LineGraph__Root = styled.div`\n position: relative;\n width: 100%;\n min-height: 0;\n\n svg {\n display: block;\n }\n`;\n","import type { ReckoningBasis } from \"@ksp-gonogo/sitrep-sdk\";\n\nconst PHRASE: Record<ReckoningBasis, string> = {\n // A combination joins readings of one moment; it never advances a value through time.\n combination: \"computed from several readings of the same moment\",\n \"kepler-propagation\": \"propagated forward on two-body motion\",\n \"linear-dead-reckoning\": \"carried forward at the last observed velocity\",\n \"powered-integration\": \"integrated forward through the burn\",\n \"rate-integration\": \"integrated forward at the last observed rate\",\n};\n\n/**\n * How a value was reckoned, in words, for a chart's accessible name: dash and\n * mutedness are not channels a screen reader has.\n *\n * @category LineGraph\n */\nexport function reckoningBasisPhrase(basis: ReckoningBasis): string {\n return PHRASE[basis];\n}\n","import type { LockedValue } from \"@ksp-gonogo/sitrep-sdk\";\nimport { isLocked } from \"@ksp-gonogo/sitrep-sdk\";\nimport { lockSentence } from \"@ksp-gonogo/sitrep-sdk/spine\";\nimport styled from \"styled-components\";\nimport { LockIcon } from \"./Icons\";\nimport type { LockSummary } from \"./LockScope\";\nimport { writeLockQuantity } from \"./lockQuantity\";\nimport { Tooltip } from \"./Tooltip\";\n\n/**\n * Props for {@link LockMark}.\n *\n * @category Panel\n */\nexport interface LockMarkProps {\n /** A locked scope's summary, or a gated field that arrived as a `LockedValue`. */\n lock: LockSummary | LockedValue;\n}\n\n/**\n * The compact form of a lock: a padlock and the name of what is missing, for\n * a tile too small for the full notice, or inline where a gated field would\n * be read. The whole sentence is its tooltip and its accessible name.\n *\n * @category Panel\n */\nexport function LockMark({ lock: given }: LockMarkProps) {\n const lock = isLocked(given) ? summaryOf(given) : given;\n const sentence =\n lock.hint === undefined ? lock.reason : `${lock.reason}. ${lock.hint}`;\n return (\n <Tooltip text={sentence} focusable>\n <LockMark__Body role=\"status\" aria-label={sentence}>\n <LockIcon />\n <LockMark__Name>{missingNames(lock)}</LockMark__Name>\n </LockMark__Body>\n </Tooltip>\n );\n}\n\n/** A gated field's lock as the same summary a locked scope hands its fallback. */\nfunction summaryOf(value: LockedValue): LockSummary {\n const locks = [\n { capability: { kind: \"topic\" as const, id: \"\" }, missing: value.locked },\n ];\n return { ...lockSentence(locks, writeLockQuantity), locks };\n}\n\n/** The unlocks' own names, which is the part of the sentence worth the space in a tiny tile. */\nfunction missingNames(lock: LockSummary): string {\n const names = [\n ...new Set(lock.locks.flatMap((l) => l.missing.map((m) => m.name))),\n ];\n return names.length > 0 ? names.join(\", \") : lock.reason;\n}\n\nconst LockMark__Body = styled.span`\n display: inline-flex;\n align-items: center;\n gap: var(--gap-figure-parts);\n max-width: 100%;\n min-width: 0;\n color: var(--color-text-muted);\n font-size: var(--font-size-caption);\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n`;\n\nconst LockMark__Name = styled.span`\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n`;\n","import { forwardRef, type SVGProps } from \"react\";\n\n/*\n * An original rendering of the standard spaceflight notation, not traced from\n * the game's artwork; only the hues follow KSP, through the --color-marker-*\n * tokens.\n */\n\n/**\n * Props for the navball marker icons ({@link ProgradeIcon} and the rest).\n *\n * The markers follow the standard spaceflight notation, with hues matching\n * KSP's navball through the `--color-marker-*` tokens. Shape carries the\n * meaning and hue only confirms it: every \"towards\" marker has a centre dot\n * and every \"away\" marker a centre cross (⊙ and ⊗), so the two halves of a\n * pair, which share a hue, stay distinct in greyscale. Each marker is drawn\n * first as a wider keyline in `currentColor` (the text colour, which\n * contrasts with the surface) and then as the coloured stroke on top. The\n * drawing is on a 24-unit viewBox. Any other SVG attribute passes through, and\n * the ref reaches the `svg` element.\n *\n * @category Icons\n */\nexport interface MarkerIconProps\n extends Omit<SVGProps<SVGSVGElement>, \"children\"> {\n /**\n * Rendered width and height, as pixels or any CSS length. Defaults to the\n * kit's standalone icon size, which grows a step on a coarse pointer.\n */\n size?: number | string;\n /** Width of the coloured stroke, in viewBox units. Defaults to 1.8. */\n strokeWidth?: number;\n /**\n * The icon's accessible name. With one, the icon renders as `role=\"img\"`\n * named by it; without one it is decorative and hidden from assistive\n * technology.\n */\n label?: string;\n}\n\n/** Extra width of the keyline beyond the coloured stroke, split across both sides. */\nconst KEYLINE_EXTRA = 1.4;\nconst DOT_RADIUS = 1.7;\n/** As in `Icons.tsx`: the token reaches the glyph through the presentation attribute, so it steps on a coarse pointer. */\nconst DEFAULT_SIZE = \"var(--icon-size-standalone)\";\nconst DEFAULT_STROKE_WIDTH = 1.8;\n\ntype MarkerColour = \"prograde\" | \"normal\" | \"radial\" | \"maneuver\" | \"target\";\n\ninterface MarkerShape {\n /** Stroked open paths; each string is one `d` attribute. */\n paths: string[];\n /** A filled centre dot, the ⊙ half of the notation. */\n dot?: [cx: number, cy: number];\n colour: MarkerColour;\n}\n\nconst RING = \"M12 6a6 6 0 1 0 0 12a6 6 0 1 0 0-12\";\n/** A tighter ring for the radial pair, leaving clear space for the chevrons outside it. */\nconst SMALL_RING = \"M12 7a5 5 0 1 0 0 10a5 5 0 1 0 0-10\";\n/**\n * The target square's four corner rays, carried onto the velocity ring: the\n * relative-velocity pair is prograde and retrograde in the target's frame.\n * Diagonal, so it reads as neither prograde nor retrograde.\n */\nconst RELATIVE_RAYS =\n \"M7.76 7.76L4.2 4.2M16.24 7.76L19.8 4.2M16.24 16.24L19.8 19.8M7.76 16.24L4.2 19.8\";\n/**\n * Two rails, the ∥ notation for parallel: along the target's own facing. An\n * open outline, so it is mistaken for neither the ring nor the square.\n */\nconst RAILS = \"M7 4.5V19.5M17 4.5V19.5\";\n/** The ⊗ half of the notation, centred on (cx, cy). */\nconst cross = (cx: number, cy: number): string =>\n `M${cx - 2.5} ${cy - 2.5}l5 5M${cx + 2.5} ${cy - 2.5}l-5 5`;\n\nconst SHAPES = {\n prograde: {\n colour: \"prograde\",\n paths: [RING, \"M12 6V2.5M6 12H2.5M18 12H21.5\"],\n dot: [12, 12],\n },\n retrograde: {\n colour: \"prograde\",\n paths: [\n RING,\n cross(12, 12),\n \"M7.76 7.76L5.3 5.3M16.24 7.76L18.7 5.3M12 18V21.5\",\n ],\n },\n normal: {\n colour: \"normal\",\n paths: [\"M12 4.5L19 17H5Z\"],\n dot: [12, 13],\n },\n antiNormal: {\n colour: \"normal\",\n paths: [\"M12 19.5L5 7H19Z\", cross(12, 11)],\n },\n radialOut: {\n colour: \"radial\",\n paths: [\n SMALL_RING,\n \"M10 4.5L12 2.5L14 4.5M19.5 10L21.5 12L19.5 14M10 19.5L12 21.5L14 19.5M4.5 10L2.5 12L4.5 14\",\n ],\n dot: [12, 12],\n },\n radialIn: {\n colour: \"radial\",\n paths: [\n SMALL_RING,\n \"M10 2.5L12 4.5L14 2.5M21.5 10L19.5 12L21.5 14M10 21.5L12 19.5L14 21.5M2.5 10L4.5 12L2.5 14\",\n cross(12, 12),\n ],\n },\n maneuver: {\n colour: \"maneuver\",\n paths: [\"M12 5L19 12L12 19L5 12Z\", \"M12 5V2\"],\n dot: [12, 12],\n },\n target: {\n colour: \"target\",\n paths: [\n \"M6 6H18V18H6Z\",\n \"M6 6L3.5 3.5M18 6L20.5 3.5M18 18L20.5 20.5M6 18L3.5 20.5\",\n ],\n dot: [12, 12],\n },\n antiTarget: {\n colour: \"target\",\n paths: [\n \"M6 6H18V18H6Z\",\n \"M6 6L3.5 3.5M18 6L20.5 3.5M18 18L20.5 20.5M6 18L3.5 20.5\",\n cross(12, 12),\n ],\n },\n relativePlus: {\n colour: \"target\",\n paths: [RING, RELATIVE_RAYS],\n dot: [12, 12],\n },\n relativeMinus: {\n colour: \"target\",\n paths: [RING, RELATIVE_RAYS, cross(12, 12)],\n },\n parallelPlus: {\n colour: \"target\",\n paths: [RAILS],\n dot: [12, 12],\n },\n parallelMinus: {\n colour: \"target\",\n paths: [RAILS, cross(12, 12)],\n },\n} satisfies Record<string, MarkerShape>;\n\n/**\n * The id of a navball marker: `prograde`, `retrograde`, `normal`,\n * `antiNormal`, `radialOut`, `radialIn`, `maneuver`, `target`, `antiTarget`,\n * `relativePlus`, `relativeMinus`, `parallelPlus` or `parallelMinus`. The\n * rendered `svg` carries it as `data-marker`.\n *\n * @category Icons\n */\nexport type MarkerId = keyof typeof SHAPES;\n\n/**\n * Every {@link MarkerId}, in the order a legend or contact sheet should show\n * them.\n *\n * @category Icons\n */\nexport const MARKER_IDS = Object.keys(SHAPES) as MarkerId[];\n\nfunction Layer({\n shape,\n colour,\n strokeWidth,\n dotRadius,\n}: {\n shape: MarkerShape;\n colour: string;\n strokeWidth: number;\n dotRadius: number;\n}) {\n return (\n <g\n fill=\"none\"\n stroke={colour}\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n {shape.paths.map((d) => (\n <path key={d} d={d} />\n ))}\n {shape.dot && (\n <circle\n cx={shape.dot[0]}\n cy={shape.dot[1]}\n r={dotRadius}\n fill={colour}\n stroke=\"none\"\n />\n )}\n </g>\n );\n}\n\nfunction makeMarker(id: MarkerId, displayName: string) {\n const shape: MarkerShape = SHAPES[id];\n const colour = `var(--color-marker-${shape.colour})`;\n const Marker = forwardRef<SVGSVGElement, MarkerIconProps>(\n (\n {\n size = DEFAULT_SIZE,\n strokeWidth = DEFAULT_STROKE_WIDTH,\n label,\n ...rest\n },\n ref,\n ) => {\n const frame = {\n ref,\n xmlns: \"http://www.w3.org/2000/svg\",\n width: size,\n height: size,\n viewBox: \"0 0 24 24\",\n \"data-marker\": id,\n };\n const layers = (\n <>\n <Layer\n shape={shape}\n colour=\"currentColor\"\n strokeWidth={strokeWidth + KEYLINE_EXTRA}\n dotRadius={DOT_RADIUS + KEYLINE_EXTRA / 2}\n />\n <Layer\n shape={shape}\n colour={colour}\n strokeWidth={strokeWidth}\n dotRadius={DOT_RADIUS}\n />\n </>\n );\n return label ? (\n <svg {...frame} role=\"img\" aria-label={label} {...rest}>\n {layers}\n </svg>\n ) : (\n <svg {...frame} aria-hidden=\"true\" {...rest}>\n {layers}\n </svg>\n );\n },\n );\n Marker.displayName = displayName;\n return Marker;\n}\n\n/**\n * The navball prograde marker: a ring with a centre dot and three ticks (top, left, right), in the prograde hue.\n *\n * @category Icons\n */\nexport const ProgradeIcon = makeMarker(\"prograde\", \"ProgradeIcon\");\n/**\n * The navball retrograde marker: a ring with a centre cross and three ticks (two diagonal above, one below), in the prograde hue.\n *\n * @category Icons\n */\nexport const RetrogradeIcon = makeMarker(\"retrograde\", \"RetrogradeIcon\");\n/**\n * The navball normal marker: an upward triangle with a centre dot, in the normal hue.\n *\n * @category Icons\n */\nexport const NormalIcon = makeMarker(\"normal\", \"NormalIcon\");\n/**\n * The navball anti-normal marker: a downward triangle with a centre cross, in the normal hue.\n *\n * @category Icons\n */\nexport const AntiNormalIcon = makeMarker(\"antiNormal\", \"AntiNormalIcon\");\n/**\n * The navball radial-out marker: a small ring with a centre dot and four chevrons pointing outward, in the radial hue.\n *\n * @category Icons\n */\nexport const RadialOutIcon = makeMarker(\"radialOut\", \"RadialOutIcon\");\n/**\n * The navball radial-in marker: a small ring with a centre cross and four chevrons pointing inward, in the radial hue.\n *\n * @category Icons\n */\nexport const RadialInIcon = makeMarker(\"radialIn\", \"RadialInIcon\");\n/**\n * The navball manoeuvre-node marker: a diamond with a centre dot and a tick at the top, in the manoeuvre hue.\n *\n * @category Icons\n */\nexport const ManeuverIcon = makeMarker(\"maneuver\", \"ManeuverIcon\");\n/**\n * The navball target marker: a square with corner rays and a centre dot, in the target hue.\n *\n * @category Icons\n */\nexport const TargetIcon = makeMarker(\"target\", \"TargetIcon\");\n/**\n * The navball anti-target marker: a square with corner rays and a centre cross, in the target hue.\n *\n * @category Icons\n */\nexport const AntiTargetIcon = makeMarker(\"antiTarget\", \"AntiTargetIcon\");\n/**\n * The relative-velocity prograde marker (prograde in the target's frame): a ring with diagonal corner rays and a centre dot, in the target hue.\n *\n * @category Icons\n */\nexport const RelativePlusIcon = makeMarker(\"relativePlus\", \"RelativePlusIcon\");\n/**\n * The relative-velocity retrograde marker (retrograde in the target's frame): a ring with diagonal corner rays and a centre cross, in the target hue.\n *\n * @category Icons\n */\nexport const RelativeMinusIcon = makeMarker(\n \"relativeMinus\",\n \"RelativeMinusIcon\",\n);\n/**\n * The parallel marker (along the target's own facing): two vertical rails with a centre dot, in the target hue.\n *\n * @category Icons\n */\nexport const ParallelPlusIcon = makeMarker(\"parallelPlus\", \"ParallelPlusIcon\");\n/**\n * The anti-parallel marker (against the target's own facing): two vertical rails with a centre cross, in the target hue.\n *\n * @category Icons\n */\nexport const ParallelMinusIcon = makeMarker(\n \"parallelMinus\",\n \"ParallelMinusIcon\",\n);\n\n/**\n * The tangent direction of the Frenet frame that n-body flight plans are\n * expressed in. The same component as {@link ProgradeIcon}.\n *\n * @category Icons\n */\nexport const TangentIcon = ProgradeIcon;\n\n/**\n * The normal direction of the Frenet frame, pointing at the centre of\n * curvature. The same component as {@link RadialInIcon}.\n *\n * @category Icons\n */\nexport const FrenetNormalIcon = RadialInIcon;\n\n/**\n * The binormal direction of the Frenet frame, which is the orbit normal. The\n * same component as {@link NormalIcon}.\n *\n * @category Icons\n */\nexport const BinormalIcon = NormalIcon;\n\n/**\n * The marker component for each {@link MarkerId}, for callers driven by data\n * rather than JSX.\n *\n * @example\n * ```tsx\n * function MarkerFor({ id, name }: { id: MarkerId; name: string }) {\n * const Marker = MARKER_ICONS[id];\n * return <Marker label={name} size={16} />;\n * }\n * ```\n *\n * @category Icons\n */\nexport const MARKER_ICONS: Record<MarkerId, typeof ProgradeIcon> = {\n prograde: ProgradeIcon,\n retrograde: RetrogradeIcon,\n normal: NormalIcon,\n antiNormal: AntiNormalIcon,\n radialOut: RadialOutIcon,\n radialIn: RadialInIcon,\n maneuver: ManeuverIcon,\n target: TargetIcon,\n antiTarget: AntiTargetIcon,\n relativePlus: RelativePlusIcon,\n relativeMinus: RelativeMinusIcon,\n parallelPlus: ParallelPlusIcon,\n parallelMinus: ParallelMinusIcon,\n};\n","import {\n type BandKind,\n bandIn,\n isStaticValue,\n type Reading,\n type Tone,\n type Value,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport {\n type HTMLAttributes,\n type ReactNode,\n useLayoutEffect,\n useRef,\n} from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { BarTrack } from \"./BarTrack\";\nimport { HeldFigure, HeldHost, HeldMark } from \"./HeldMark\";\nimport { magnitudeOr } from \"./magnitude\";\nimport { NullValue } from \"./NullValue\";\nimport { resolveCurrency, type UnitValue } from \"./readingCurrency\";\nimport { placedOnScale, standsApart } from \"./standsApart\";\nimport { Unit } from \"./Unit\";\nimport { UnitSharedFormat, useSharedFormat } from \"./UnitSharedFormat\";\nimport {\n type FormatQuantityOptions,\n type FormatsFor,\n speakQuantity,\n} from \"./units\";\n\n/**\n * Where a meter's label and figure sit relative to its bar.\n *\n * - `stacked`: the label and the figure on a line, the bar beneath them. The\n * default, for a meter that is a readout in its own right\n * - `row`: label, bar and figure on one line, for a list of meters read down a\n * column. Where the line runs out of room the figure, then the bar, wraps\n * rather than being clipped. Inside a `MeterStack` every row lines up\n *\n * @category Meter\n */\nexport type MeterLayout = \"stacked\" | \"row\";\n\ninterface MeterCommonProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Short label shown above the bar and used as the meter's accessible name. */\n label: string;\n /**\n * Semantic colour of the fill. Ignored when `fillColor` is set.\n *\n * @defaultValue `\"neutral\"`\n */\n tone?: Tone;\n /**\n * Arbitrary CSS colour for the fill, such as a resource's own from\n * {@link resourceColor}, for meters whose fill carries an identity rather than\n * a status. Wins over `tone` for the fill colour only.\n */\n fillColor?: string;\n /**\n * Text shown on the right of the header (e.g. \"5.0 rad/h\"). Defaults to the\n * figure, drawn through `<Unit>`. Also the `aria-valuetext`, so it stays a\n * plain string; pass `valueLabelNode` alongside it for visible markup.\n */\n valueLabel?: string;\n /**\n * Visual override for the header's value display. `aria-valuetext` still\n * reads `valueLabel`, so pass both: this for the eye, `valueLabel` for the\n * accessibility tree.\n */\n valueLabelNode?: ReactNode;\n /**\n * Where the label and figure sit relative to the bar. See {@link MeterLayout}.\n *\n * @defaultValue `\"stacked\"`\n */\n layout?: MeterLayout;\n /**\n * Draw the bar alone: no label or figure above it, as for a bar under a\n * heading that already says what it is. The label stays as the accessible\n * name and the figure as the spoken value. With no figure to draw the meter\n * renders nothing at all. Add `statement` to keep the figure, under the bar.\n */\n hideLabel?: boolean;\n /**\n * Keep the figure, `120 / 400 kg`, on a line under a bar drawn alone with\n * `hideLabel`. A capacity can change (a tank is staged away, a queue grows),\n * and the statement says how much of how much where the bar alone says only\n * how full. A headed meter already writes it in its head, so this changes\n * nothing there.\n */\n statement?: boolean;\n}\n\n/**\n * The props of {@link Meter}.\n *\n * @category Meter\n */\nexport interface MeterProps<UnitSymbol extends string = string>\n extends MeterCommonProps {\n /**\n * How much there is. Without a `capacity` it must already be a fraction, in\n * `ratio`. A {@link Reading} is accepted.\n *\n * `null`, or a reading with no value, draws a dash and an empty track, so\n * there is no need to check for a missing value first.\n */\n value: UnitValue<UnitSymbol> | null;\n /**\n * The whole that `value` is a fraction of, in the same unit. A\n * {@link Reading} is accepted. `null` is a capacity that could not be read,\n * and draws a dash.\n */\n capacity?: UnitValue<UnitSymbol> | null;\n /**\n * Fixes the unit `value` and `capacity` are written in. Rarely needed: by\n * default the two share one unit, chosen by magnitude.\n */\n format?: FormatsFor<UnitSymbol>;\n}\n\n/**\n * A labelled bar showing how full something is: a dose, a resource level, a\n * charge. The value is always written beside the bar, so colour never\n * carries meaning alone.\n *\n * Pass `value` alone when it is already a fraction, in `ratio`, or `value` and\n * `capacity` in the same unit for an amount out of a whole.\n *\n * ## Missing and held values\n *\n * With no number to show, the meter draws a dash and an empty track, never a\n * 0% bar. Given a {@link Reading}, a held value is marked as held, and a held\n * capacity hatches the unfilled part of the track.\n *\n * ## Bands\n *\n * Where a reading carries an {@link UncertaintyBand} (how far its forward\n * model, see {@link TopicReckoning}, would defend the figure), the meter draws\n * a tick at each end of it on the track: two marks, never a shaded interval.\n * The bar still shows the observed value, and the ticks sit where the model\n * puts the value now. A band on the capacity is ticked near the track's end, as a\n * fraction of the capacity drawn. A band in a different unit from the value or\n * capacity it belongs to draws nothing.\n *\n * @example A fraction on its own, which must be a `ratio`\n * ```tsx\n * import { value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { Meter } from \"@ksp-gonogo/ui-kit\";\n *\n * <Meter label=\"Shielding\" value={value(\"ratio\", 0.72)} tone=\"go\" />\n * ```\n *\n * @example An amount out of a capacity in the same unit\n * ```tsx\n * <Meter label=\"Ore\" value={value(\"kg\", 120)} capacity={value(\"kg\", 400)} />\n * ```\n *\n * @example A bar alone with its statement under it, as work advances toward a total that can move\n * ```tsx\n * <Meter\n * hideLabel\n * statement\n * label=\"Nodes researched\"\n * value={value(\"count\", 3)}\n * capacity={value(\"count\", 8)}\n * />\n * ```\n *\n * @example Row meters in a stack, sharing columns so every bar lines up\n * ```tsx\n * <MeterStack>\n * <Meter label=\"LF\" layout=\"row\" value={value(\"units\", 320)} capacity={value(\"units\", 400)} />\n * <Meter label=\"Ox\" layout=\"row\" value={value(\"units\", 391)} capacity={value(\"units\", 489)} />\n * </MeterStack>\n * ```\n *\n * @category Meter\n * @categoryDescription Meter\n * Bars for a value against its range: meters and stacks of them, level bars,\n * diverging bars, and the meters contributed to a widget.\n */\nexport function Meter<UnitSymbol extends string = string>({\n label,\n value,\n capacity,\n format,\n tone = \"neutral\",\n fillColor,\n valueLabel,\n valueLabelNode,\n layout = \"stacked\",\n hideLabel = false,\n statement = false,\n ...rest\n}: MeterProps<UnitSymbol>) {\n const shown = unwrap(value);\n const held = unwrap(capacity);\n const fraction = fillFraction(\n shown.figure,\n capacity === undefined ? undefined : held.figure,\n );\n if (fraction === null || !Number.isFinite(fraction)) {\n // Absence, not a zero bar.\n if (hideLabel) return null;\n return (\n <MeterFrame\n layout={layout}\n label={label}\n display={<NullValue />}\n {...rest}\n >\n {/* Decorative: the label and placeholder are the whole accessible content. */}\n <BarTrack percent={null} tone={tone} aria-hidden=\"true\" />\n </MeterFrame>\n );\n }\n const clamped = Math.min(1, Math.max(0, fraction));\n const bar = {\n label,\n layout,\n hideLabel,\n statement,\n pct: Math.round(clamped * 100),\n tone,\n fillColor,\n // A held capacity marks the track: the axis aged, not the reading on it.\n trackHeld: held.reading?.state === \"held\" && !isStaticValue(held.figure),\n fillHeld: shown.reading?.state === \"held\" && !isStaticValue(shown.figure),\n ...rest,\n };\n const endBounds = apartFrom(\n 1,\n boundsOn(held.reading, held.figure, held.figure),\n );\n if (capacity !== undefined) {\n // The scope encloses the whole bar, since `aria-valuetext` on the track is written at the settled rung.\n return (\n <UnitSharedFormat of={shown.figure?.unit} format={format}>\n <MeterPairBar\n {...bar}\n value={value}\n capacity={capacity}\n shown={shown}\n held={held}\n endBounds={endBounds}\n at={clamped}\n valueLabel={valueLabel}\n valueLabelNode={valueLabelNode}\n />\n </UnitSharedFormat>\n );\n }\n /*\n * No capacity, so the figure is the fraction and `<Unit>` draws it as it\n * arrived. The visible form is a node; `aria-valuetext` is a string from\n * `speakQuantity`, so it never announces \"72 percent-sign\".\n */\n const bounds = apartFrom(\n clamped,\n boundsOn(shown.reading, shown.figure, null),\n );\n return (\n <MeterBar\n {...bar}\n bounds={bounds}\n endBounds={endBounds}\n display={\n valueLabelNode ??\n (valueLabel === undefined ? (\n <Unit value={value} format={format} />\n ) : (\n <HeldLabel caption={heldCaption(value)}>{valueLabel}</HeldLabel>\n ))\n }\n spoken={withBands(\n valueLabel === undefined\n ? speakQuantity(shown.figure, { format })\n : sayCaption(valueLabel, heldCaption(value)),\n bounds,\n endBounds,\n )}\n />\n );\n}\n\n/** What a held reading's mark means in words, or null where the figure is current. */\nfunction heldCaption<UnitSymbol extends string>(\n input: UnitValue<UnitSymbol> | null | undefined,\n): string | null {\n const { held, caption } = resolveCurrency(input ?? null);\n return held ? caption : null;\n}\n\n/** A caller's own words for the figure, with the held reading's words after them. */\nfunction sayCaption(label: string, caption: string | null): string {\n return caption === null ? label : `${label}, ${caption}`;\n}\n\n/**\n * A caller's own string label for the figure, carrying the same held mark and\n * words `<Unit>` would have drawn, so a held meter always looks held. A\n * `valueLabelNode` is drawn as given: it holds its own `<Unit>`s, which mark\n * themselves.\n */\nfunction HeldLabel({\n caption,\n children,\n}: {\n caption: string | null;\n children: ReactNode;\n}) {\n if (caption === null) return <>{children}</>;\n return <HeldFigure caption={caption}>{children}</HeldFigure>;\n}\n\n/** One half of a meter, split into the figure and the reading it came in. */\ninterface Half<UnitSymbol extends string> {\n figure: Value<UnitSymbol> | null;\n reading: Reading<Value<UnitSymbol>> | null;\n}\n\n/**\n * The figure to draw, and the reading it came in where it came in one. The\n * discriminator is guarded on the runtime shape, since `in` throws on a\n * primitive and untyped JavaScript can pass one.\n */\nfunction unwrap<UnitSymbol extends string>(\n input: UnitValue<UnitSymbol> | null | undefined,\n): Half<UnitSymbol> {\n if (typeof input !== \"object\" || input === null || !(\"state\" in input)) {\n return { figure: input ?? null, reading: null };\n }\n return { figure: input.value ?? null, reading: input };\n}\n\n/**\n * The bar's fill, as the 0..1 a track is drawn from. `dividedBy` makes the two\n * halves the same kind, so the quotient is dimensionless. A capacity of zero is\n * no tank at all, so it yields `null`.\n */\nfunction fillFraction<UnitSymbol extends string>(\n figure: Value<UnitSymbol> | null,\n capacity: Value<UnitSymbol> | null | undefined,\n): number | null {\n if (figure === null || capacity === null) return null;\n if (capacity !== undefined && !capacity.isPositive()) return null;\n const drawn = capacity === undefined ? figure : figure.dividedBy(capacity);\n return drawn.magnitude;\n}\n\n/**\n * Where a band's two ends sit on the 0..1 track, and what they claim. The ends\n * are also kept as the bare quantities they arrived as, because the spoken\n * sentence names them in the value's own unit.\n */\ninterface MeterBounds {\n lo: number;\n hi: number;\n kind: BandKind;\n /** The low end as it arrived, for the sentence. */\n loSaid: { magnitude: number; unit: string };\n /** The high end as it arrived. */\n hiSaid: { magnitude: number; unit: string };\n}\n\n/**\n * The band this reading offers about its own figure, placed on the track.\n * `over` is what the two ends are divided by; `null` says the figure is already\n * a fraction. A divisor of zero places nothing.\n */\nfunction boundsOn<UnitSymbol extends string>(\n reading: Reading<Value<UnitSymbol>> | null,\n figure: Value<UnitSymbol> | null,\n over: Value<UnitSymbol> | null,\n): MeterBounds | null {\n if (reading === null || reading.reckoning.status !== \"available\") return null;\n if (figure === null) return null;\n const band = bandIn(reading.reckoning.band, figure.unit);\n if (!band) return null;\n const said = { loSaid: band.lo, hiSaid: band.hi, kind: band.kind };\n if (over === null) {\n return {\n lo: magnitudeOr(band.lo, 0),\n hi: magnitudeOr(band.hi, 0),\n ...said,\n };\n }\n if (!over.isPositive()) return null;\n return {\n lo: magnitudeOr(band.lo.dividedBy(over), 0),\n hi: magnitudeOr(band.hi.dividedBy(over), 0),\n ...said,\n };\n}\n\n/**\n * The bounds, or `null` where every one of them sits on the observation, so\n * they are neither marked nor spoken. `at` is the observation on the 0..1\n * track: the fill's end for the value's band, the track's end for the capacity's.\n */\nfunction apartFrom(at: number, bounds: MeterBounds | null): MeterBounds | null {\n if (bounds === null) return null;\n return standsApart(at, [bounds.lo, bounds.hi], placedOnScale())\n ? bounds\n : null;\n}\n\n/**\n * The intervals appended to what the track already announces, since the marks\n * are shapes with no reading of their own. A band is said as plain\n * containment, with no statistics vocabulary; the capacity's interval is its\n * own clause.\n */\nfunction withBands(\n spoken: string,\n bounds: MeterBounds | null,\n endBounds: MeterBounds | null,\n shared?: FormatQuantityOptions,\n): string {\n const said = (bound: MeterBounds, lead: string): string => {\n const lo = speakQuantity(bound.loSaid, shared);\n const hi = speakQuantity(bound.hiSaid, shared);\n // The connector tells a listener which two of three numbers are the band.\n return `${lead}with bands at ${lo} and ${hi}`;\n };\n const clauses = [\n bounds === null ? null : said(bounds, \"\"),\n endBounds === null ? null : said(endBounds, \"capacity \"),\n ].filter((clause): clause is string => clause !== null);\n return clauses.length === 0 ? spoken : `${spoken}, ${clauses.join(\", \")}`;\n}\n\n/**\n * A bound's place along the track, as a percentage. Two decimals, since a band\n * half a percent wide is a real claim; clamped, since a model fitted near a\n * limit routinely bounds past it.\n */\nfunction boundPct(fraction: number): number {\n if (!Number.isFinite(fraction)) return 0;\n const clamped = Math.min(1, Math.max(0, fraction));\n return Math.round(clamped * 10_000) / 100;\n}\n\n/**\n * A mark's position. It straddles its bound, except at the two ends, where it\n * tucks fully inside the track so it is not clipped.\n */\nfunction markAt(fraction: number): {\n style: { left: string; transform: string };\n} {\n const pct = boundPct(fraction);\n const shift = pct <= 0 ? 0 : pct >= 100 ? -2 : -1;\n return { style: { left: `${pct}%`, transform: `translateX(${shift}px)` } };\n}\n\ninterface MeterBarProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n label: string;\n layout: MeterLayout;\n hideLabel: boolean;\n /** Whether the figure is kept on a line under a bar drawn alone. */\n statement: boolean;\n /** The fill, as the whole percent `aria-valuenow` and the track both take. */\n pct: number;\n tone: Tone;\n fillColor?: string;\n /** Whether the AXIS has stopped being current. See `BarTrack`. */\n trackHeld: boolean;\n /**\n * Whether the figure has stopped being current: one treatment for the whole\n * meter rather than a mark per entry.\n */\n fillHeld: boolean;\n /** The value for the eye, as markup. */\n display: ReactNode;\n /** The same value for the ear, as the string an attribute can hold. */\n spoken: string;\n /** Where the model's two bounds sit, or `null` where it offers none. */\n bounds: MeterBounds | null;\n /** Where an uncertain capacity puts the track's end, or `null`. */\n endBounds: MeterBounds | null;\n}\n\n/**\n * The meter as it is drawn, given a fill fraction and the two forms of the\n * value that goes with it.\n */\nfunction MeterBar({\n label,\n layout,\n hideLabel,\n statement,\n pct,\n tone,\n fillColor,\n trackHeld,\n fillHeld,\n display,\n spoken,\n bounds,\n endBounds,\n ...rest\n}: MeterBarProps) {\n return (\n <MeterFrame\n layout={layout}\n label={label}\n display={display}\n hideHead={hideLabel}\n statement={statement}\n {...rest}\n >\n <BarTrack\n percent={pct}\n tone={tone}\n fillColor={fillColor}\n trackHeld={trackHeld}\n fillHeld={fillHeld}\n role=\"meter\"\n aria-label={label}\n aria-valuenow={pct}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuetext={spoken}\n />\n {/* Decorative: what the marks say is already in `aria-valuetext`. */}\n {(bounds !== null || endBounds !== null) && (\n <Meter__Marks aria-hidden=\"true\">\n {bounds !== null && (\n <>\n <Meter__Bound data-bound=\"lo\" {...markAt(bounds.lo)} />\n <Meter__Bound data-bound=\"hi\" {...markAt(bounds.hi)} />\n </>\n )}\n {endBounds !== null && (\n <>\n <Meter__Bound data-end-bound=\"lo\" {...markAt(endBounds.lo)} />\n <Meter__Bound data-end-bound=\"hi\" {...markAt(endBounds.hi)} />\n </>\n )}\n </Meter__Marks>\n )}\n </MeterFrame>\n );\n}\n\n/**\n * The label, the figure and the bar, placed by `layout`.\n *\n * `children` is what goes in the bar: the track, and the marks over it. In the\n * stacked form the label and figure share a head line above it; in the row form\n * all three are siblings on one line, the bar between the two words.\n */\nfunction MeterFrame({\n layout,\n label,\n display,\n hideHead = false,\n statement = false,\n children,\n ...rest\n}: Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n layout: MeterLayout;\n label: string;\n display: ReactNode;\n /** Draw the bar alone; the track carries the name and the value. */\n hideHead?: boolean;\n /** With `hideHead`, keep the figure on a line under the bar. */\n statement?: boolean;\n children: ReactNode;\n}) {\n if (hideHead) {\n return (\n <Meter__Root $row={false} {...rest}>\n <Meter__Bar $row={false}>{children}</Meter__Bar>\n {statement && (\n <Meter__Statement data-meter-part=\"statement\">\n {display}\n </Meter__Statement>\n )}\n </Meter__Root>\n );\n }\n if (layout === \"row\") {\n return (\n <Meter__Root $row data-meter-row=\"\" {...rest}>\n <Meter__Label data-meter-part=\"label\">{label}</Meter__Label>\n <Meter__Bar $row data-meter-part=\"bar\">\n {children}\n </Meter__Bar>\n <Meter__Value $row data-meter-part=\"figure\">\n {display}\n </Meter__Value>\n </Meter__Root>\n );\n }\n return (\n <Meter__Root $row={false} {...rest}>\n <Meter__Head>\n <Meter__Label>{label}</Meter__Label>\n <Meter__Value $row={false}>{display}</Meter__Value>\n </Meter__Head>\n <Meter__Bar $row={false}>{children}</Meter__Bar>\n </Meter__Root>\n );\n}\n\n/**\n * The two halves, written and spoken at one rung, so a tank never reads\n * `999 m / 1.0 km`. The `<Unit>`s report into the enclosing\n * `<UnitSharedFormat>`; the spoken string cannot report by rendering, so this\n * component reports on its behalf and writes it at the settled rung.\n */\nfunction MeterPairBar<UnitSymbol extends string = string>({\n value,\n capacity,\n shown,\n held,\n at,\n valueLabel,\n valueLabelNode,\n ...bar\n}: Omit<MeterBarProps, \"display\" | \"spoken\" | \"bounds\"> &\n Pick<MeterProps<UnitSymbol>, \"valueLabel\" | \"valueLabelNode\"> & {\n value: UnitValue<UnitSymbol> | null;\n capacity: UnitValue<UnitSymbol> | null;\n shown: Half<UnitSymbol>;\n held: Half<UnitSymbol>;\n /** Where the fill ends, as the 0..1 the bounds are compared against. */\n at: number;\n }) {\n // A figure overridden in both forms is neither drawn nor spoken, so it sits out of the group.\n const reporting = valueLabel === undefined;\n const fromValue = useSharedFormat(reporting ? shown.figure : undefined);\n const fromCapacity = useSharedFormat(reporting ? held.figure : undefined);\n // One group, so both halves hear the same answer; either serves, and on the first pass neither has one yet.\n const shared = fromValue ?? fromCapacity ?? {};\n /*\n * A row writes the symbol once, after the capacity (`960 / 1,000 units`), and\n * the pair carries one held mark at its end. Which half aged is still\n * drawn on the bar.\n */\n const caption = heldCaption(value) ?? heldCaption(capacity);\n const display =\n valueLabelNode ??\n (valueLabel !== undefined ? (\n <HeldLabel caption={caption}>{valueLabel}</HeldLabel>\n ) : bar.layout === \"row\" ? (\n <Meter__Pair $marked={bar.fillHeld || bar.trackHeld}>\n <Unit value={value} hideUnitInGroup />\n {\" / \"}\n <Unit value={capacity} />\n {(bar.fillHeld || bar.trackHeld) && (\n <HeldMark aria-hidden=\"true\" data-held-mark=\"\" />\n )}\n </Meter__Pair>\n ) : (\n <>\n <Unit value={value} />\n {\" / \"}\n <Unit value={capacity} />\n </>\n ));\n const spoken =\n valueLabel === undefined\n ? `${speakQuantity(shown.figure, shared)} of ${speakQuantity(held.figure, shared)}`\n : sayCaption(valueLabel, caption);\n // The band's ends are written at the group's rung but never reported into it, since they are only spoken.\n const bounds = apartFrom(\n at,\n boundsOn(shown.reading, shown.figure, held.figure),\n );\n return (\n <MeterBar\n {...bar}\n display={display}\n spoken={withBands(spoken, bounds, bar.endBounds, shared)}\n bounds={bounds}\n />\n );\n}\n\n/**\n * The narrowest a row meter's bar is drawn in a `MeterStack`, in pixels. A\n * number, since the stack does arithmetic with it.\n */\nconst ROW_BAR_FLOOR_PX = 48;\n\n/**\n * A vertical list of meters. Row meters in it share columns, so every label,\n * bar and figure lines up. Where the figures do not fit beside the bars, every\n * figure moves under its bar.\n *\n * @category Meter\n */\nexport function MeterStack({\n children,\n ...rest\n}: HTMLAttributes<HTMLDivElement>) {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const stack = ref.current;\n if (!stack || typeof ResizeObserver === \"undefined\") return;\n const place = () => {\n // A stack that has not been laid out has no width to decide against.\n if (stack.clientWidth === 0) return;\n stack.toggleAttribute(\"data-figures-below\", !figuresFitBeside(stack));\n };\n /*\n * Moving the figures resizes elements this observer is already watching, so the move is made on the next frame: made inside the delivery it ends the frame with undelivered observations, which the browser reports as a loop error.\n */\n let frame = 0;\n const placeNextFrame = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(place);\n };\n // Every label and figure is observed too, since a figure growing a digit can push the list over the line.\n const resize = new ResizeObserver(placeNextFrame);\n const watch = () => {\n resize.disconnect();\n resize.observe(stack);\n for (const part of rowParts(stack)) resize.observe(part);\n };\n const rows = new MutationObserver(watch);\n rows.observe(stack, { childList: true, subtree: true });\n watch();\n place();\n return () => {\n cancelAnimationFrame(frame);\n resize.disconnect();\n rows.disconnect();\n };\n }, []);\n return (\n <Meter__Stack ref={ref} {...rest}>\n {children}\n </Meter__Stack>\n );\n}\n\n/**\n * Keeps a row meter on its `MeterStack`'s columns while it shares one cell of\n * the stack with other lines, such as a caption under the bar. Wrap the meter\n * and the extra lines in one group.\n *\n * @category Meter\n */\nexport const MeterRowGroup = styled.div.attrs({\n \"data-meter-row-group\": \"\",\n} as HTMLAttributes<HTMLDivElement>)`\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: subgrid;\n row-gap: var(--gap-meter-rows);\n min-width: 0;\n\n & > * {\n grid-column: 1 / -1;\n min-width: 0;\n }\n`;\n\n/** The labels and figures of the row meters the stack lines up. */\nfunction rowParts(stack: HTMLElement): HTMLElement[] {\n return Array.from(\n stack.querySelectorAll<HTMLElement>(\n ':scope > [data-meter-row] > [data-meter-part=\"label\"], ' +\n ':scope > [data-meter-row] > [data-meter-part=\"figure\"], ' +\n ':scope > [data-meter-row-group] > [data-meter-row] > [data-meter-part=\"label\"], ' +\n ':scope > [data-meter-row-group] > [data-meter-row] > [data-meter-part=\"figure\"]',\n ),\n );\n}\n\n/**\n * Whether the widest label, a bar at its floor and the widest figure fit on\n * one line of the stack.\n *\n * Measured from the parts' own content widths, which do not change with the\n * decision, so the answer does not flip-flop.\n */\nfunction figuresFitBeside(stack: HTMLElement): boolean {\n const parts = rowParts(stack);\n if (parts.length === 0) return true;\n let label = 0;\n let figure = 0;\n for (const part of parts) {\n if (part.dataset.meterPart === \"label\") {\n label = Math.max(label, unwrappedWidth(part));\n } else {\n figure = Math.max(figure, part.scrollWidth);\n }\n }\n const style = getComputedStyle(stack);\n const px = (v: string) => Number.parseFloat(v) || 0;\n const gap = px(style.columnGap);\n const room =\n stack.clientWidth - px(style.paddingLeft) - px(style.paddingRight);\n return label + gap + ROW_BAR_FLOOR_PX + gap + figure <= room;\n}\n\n/**\n * A label's width on one line, read with wrapping briefly turned off, since a\n * wrapped label takes the column's width rather than its own.\n */\nfunction unwrappedWidth(label: HTMLElement): number {\n const before = label.style.whiteSpace;\n label.style.whiteSpace = \"nowrap\";\n const width = label.scrollWidth;\n label.style.whiteSpace = before;\n return width;\n}\n\n/*\n * Three columns: label, bar, figure, or two with the figures below. A label\n * wraps rather than truncating, since a truncated name reads as a different meter.\n */\nconst Meter__Stack = styled.div`\n display: grid;\n grid-template-columns:\n minmax(min-content, max-content)\n minmax(${ROW_BAR_FLOOR_PX}px, 1fr)\n max-content;\n gap: var(--gap-meter-columns);\n width: 100%;\n\n & > * {\n grid-column: 1 / -1;\n min-width: 0;\n }\n\n & > [data-meter-row],\n & > [data-meter-row-group] > [data-meter-row] {\n display: grid;\n grid-template-columns: subgrid;\n column-gap: normal;\n }\n\n & > [data-meter-row] > [data-meter-part=\"label\"],\n & > [data-meter-row-group] > [data-meter-row] > [data-meter-part=\"label\"] {\n white-space: normal;\n }\n\n &[data-figures-below] {\n grid-template-columns:\n minmax(min-content, max-content)\n minmax(${ROW_BAR_FLOOR_PX}px, 1fr);\n }\n\n &[data-figures-below] > [data-meter-row] > [data-meter-part=\"bar\"],\n &[data-figures-below]\n > [data-meter-row-group]\n > [data-meter-row]\n > [data-meter-part=\"bar\"] {\n grid-column: 2 / -1;\n }\n\n &[data-figures-below] > [data-meter-row] > [data-meter-part=\"figure\"],\n &[data-figures-below]\n > [data-meter-row-group]\n > [data-meter-row]\n > [data-meter-part=\"figure\"] {\n grid-column: 1 / -1;\n }\n`;\n\n/** A mark sits one pixel inside each edge of the track, in a layer over it. */\nconst MARK_INSET = \"1px\";\n\n/* In the row form the three parts wrap rather than shrink. */\nconst Meter__Root = styled.div<{ $row: boolean }>`\n display: flex;\n width: 100%;\n min-width: 0;\n ${({ $row }) =>\n $row\n ? css`\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n column-gap: var(--gap-meter-columns);\n row-gap: var(--gap-caption);\n `\n : css`\n flex-direction: column;\n gap: var(--gap-caption);\n `}\n`;\n\nconst Meter__Head = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--gap-label-value);\n min-width: 0;\n /* The value may drop to its own line at narrow widths rather than crushing the label. */\n flex-wrap: wrap;\n`;\n\nconst Meter__Label = styled.span`\n font-size: var(--font-size-caption);\n color: var(--color-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n /* Never shrinks below the name: overflow hidden zeroes a flex item's automatic minimum size. */\n flex: 0 0 auto;\n max-width: 100%;\n`;\n\n/* The row form writes its figure a step smaller, so a dense list keeps one line per meter. */\nconst Meter__Value = styled.span<{ $row: boolean }>`\n font-size: ${({ $row }) =>\n $row ? \"var(--font-size-compact)\" : \"var(--font-size-value)\"};\n color: var(--color-text-primary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n flex: 0 0 auto;\n /* Pins the value to the trailing edge on the shared line and when it wraps to its own. */\n margin-left: auto;\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n /* Room for the held mark Unit draws outside this box, reserved unconditionally so a quiet channel causes no reflow. */\n padding-right: max(0.44em, var(--inset-meter-mark));\n`;\n\n/* The figure under a bar drawn alone: the row form's smaller step, since it annotates the bar rather than heading it. */\nconst Meter__Statement = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-primary);\n font-variant-numeric: tabular-nums;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n`;\n\n/* The layer the marks are drawn in, over the track rather than inside its clip. */\nconst Meter__Marks = styled.div`\n position: absolute;\n inset: 0;\n pointer-events: none;\n`;\n\n/* The track and the marks over it, in one box the marks are positioned against. */\nconst Meter__Bar = styled.div<{ $row: boolean }>`\n position: relative;\n ${({ $row }) =>\n $row\n ? css`\n flex: 1 1 28px;\n min-width: 28px;\n `\n : \"\"}\n`;\n\n/* One nowrap box for the row form's pair, so its single held mark hangs off the end of the phrase. */\nconst Meter__Pair = styled(HeldHost)<{ $marked: boolean }>`\n /* The pair's own mark reserves the room; the figures inside it carry none. */\n &::after {\n content: ${({ $marked }) => ($marked ? '\"\"' : \"none\")};\n }\n & > span > [data-held-mark] {\n display: none;\n }\n & > span::after {\n content: none;\n }\n`;\n\n/**\n * One end of the model's interval: a 2px tick across the track. Neutral and\n * two-toned (dimmed white with a dark hairline) so it reads over the empty\n * track and over any fill colour.\n */\nconst Meter__Bound = styled.div`\n position: absolute;\n top: ${MARK_INSET};\n bottom: ${MARK_INSET};\n width: 2px;\n background: var(--color-text-primary);\n /* Inset and outset, so the mark keeps an edge against a light or saturated fill on either side of its end. */\n box-shadow:\n 0 0 0 1px rgb(0 0 0 / 0.55),\n inset 0 0 0 0.5px rgb(0 0 0 / 0.35);\n opacity: 0.62;\n pointer-events: none;\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { HTMLAttributes } from \"react\";\nimport styled from \"styled-components\";\nimport { TONE_MARK } from \"./tone\";\n\n/**\n * The one track height a bar has. A mark drawn over it sits one pixel inside\n * each edge, in a layer of its own, since the track's `overflow: hidden`\n * rounds the fill.\n */\nconst TRACK_HEIGHT = \"8px\";\n\nexport interface BarTrackProps extends HTMLAttributes<HTMLDivElement> {\n /** The filled share, 0-100, already clamped; `null` draws the empty track alone. */\n percent: number | null;\n tone: Tone;\n /** An arbitrary CSS colour that wins outright over the tone fill. */\n fillColor?: string;\n /** The capacity has stopped being current: the unfilled part hatches. */\n trackHeld?: boolean;\n /** The reading has stopped being current: the fill dims. */\n fillHeld?: boolean;\n}\n\n/**\n * The track and fill a `Meter` draws. The role and its aria attributes arrive\n * through `rest`, since a decorative track carries none.\n */\nexport function BarTrack({\n percent,\n tone,\n fillColor,\n trackHeld = false,\n fillHeld = false,\n children,\n ...rest\n}: BarTrackProps) {\n return (\n <BarTrack__Track data-track-held={trackHeld ? \"\" : undefined} {...rest}>\n {percent !== null && (\n <BarTrack__Fill\n $tone={tone}\n $fillColor={fillColor}\n $held={fillHeld}\n data-fill-held={fillHeld ? \"\" : undefined}\n style={{ width: `${percent}%` }}\n />\n )}\n {trackHeld && (\n <BarTrack__Hatch\n data-track-hatch=\"\"\n style={{ left: `${percent ?? 0}%` }}\n />\n )}\n {children}\n </BarTrack__Track>\n );\n}\n\n/* The edge stays solid whether or not the capacity is current: a held capacity is drawn by BarTrack__Hatch inside it. */\nconst BarTrack__Track = styled.div`\n width: 100%;\n border-radius: var(--radius-pill);\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-subtle);\n overflow: hidden;\n /* Positioned for the fill only: this overflow rounds the fill's ends and would clip any mark drawn over it. */\n position: relative;\n height: ${TRACK_HEIGHT};\n`;\n\n/**\n * The unfilled part of a track whose capacity has stopped being current,\n * hatched thinly in the held mark's hue: the extent is what aged, so the\n * figure's fill is left clear of it. Static, so there is no motion to reduce.\n */\nconst BarTrack__Hatch = styled.div`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n background-image: repeating-linear-gradient(\n -45deg,\n ${TONE_MARK.warn} 0 1px,\n transparent 1px 4px\n );\n opacity: 0.55;\n pointer-events: none;\n`;\n\nconst BarTrack__Fill = styled.div<{\n $tone: Tone;\n $fillColor?: string;\n $held: boolean;\n}>`\n height: 100%;\n border-radius: var(--radius-pill);\n transition: width var(--duration-slow) var(--ease-standard);\n /* A held reading dims the fill, not the hue and not the whole bar, so a label beside it stays readable. */\n ${({ $held }) => ($held ? \"opacity: 0.55;\" : \"\")}\n background: ${({ $tone, $fillColor }) => $fillColor ?? TONE_MARK[$tone]};\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n`;\n","import type { Reading, Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport styled from \"styled-components\";\nimport { formatKspDate } from \"./formatKspDate\";\nimport { HeldFigure } from \"./HeldMark\";\nimport { figureAttributes, resolveCurrency } from \"./readingCurrency\";\nimport { Tooltip } from \"./Tooltip\";\nimport { VisuallyHidden } from \"./VisuallyHidden\";\n\n/**\n * Which clock an instant is on. Under signal delay they differ for the same\n * event: SCET is when it happens at the craft, and the received clock is when\n * the telemetry showing it reaches a command centre, one light-time later.\n *\n * The qualifier belongs to each reading, not to the screen, since two vessels\n * have different light-times. It is drawn as `SCET` or `AT <vantage>`. A\n * space-centre channel describes no craft, so it takes no context.\n *\n * @category MissionDate\n */\nexport type TimeContext =\n /** The craft's own clock: when the event happens, or happened, out there. */\n | { frame: \"scet\" }\n /**\n * The arrival clock at the observing vantage: when the frame showing it\n * reaches, or reached, the operator. `vantage` names that command centre; it\n * is optional because a frame can arrive before the roster naming it does.\n */\n | { frame: \"received\"; vantage?: string };\n\n/**\n * The qualifier as rendered: token for the eye, phrase for the accessibility\n * tree, and a title spelling out what the reading means.\n */\nfunction describeContext(context: TimeContext): {\n token: string;\n spoken: string;\n title: string;\n} {\n if (context.frame === \"scet\") {\n return {\n token: \"SCET\",\n spoken: \"spacecraft event time\",\n title: \"Spacecraft event time: when this happens at the craft\",\n };\n }\n const { vantage } = context;\n return vantage === undefined\n ? {\n token: \"RECEIVED\",\n spoken: \"received\",\n title: \"When the telemetry showing this arrives\",\n }\n : {\n token: `AT ${vantage}`,\n spoken: `received at ${vantage}`,\n title: `When the telemetry showing this arrives at ${vantage}`,\n };\n}\n\n/*\n * Sized and dimmed relative to the number it qualifies, as `<Unit>`'s symbol\n * is. `text-transform` is pinned so a lowercasing parent cannot turn SCET into prose.\n */\nconst MissionDate__Context = styled.span`\n font-size: max(0.72em, 10px);\n opacity: 0.72;\n white-space: nowrap;\n text-transform: none;\n`;\n\n/**\n * Props for {@link MissionDate}.\n *\n * @category MissionDate\n */\nexport interface MissionDateProps {\n /**\n * Universal time: seconds since the game's epoch, not a duration. A plain\n * number is accepted for the client-interpolated view clock. A `Value<\"s\">`\n * is accepted too and is read the same way, as seconds since the epoch: it\n * is never drawn as a length of time here. For a length of time use `Unit`.\n */\n value:\n | Value<\"ut\">\n | Value<\"s\">\n | Reading<Value<\"ut\">>\n | Reading<Value<\"s\">>\n | number\n | null\n | undefined;\n /**\n * Which clock the instant is on, shown after it as `SCET` or `AT KSC`.\n *\n * Leave it `undefined` where the two clocks do not differ (a LAN session, a\n * vantage that is the craft itself), since a qualifier that is always present\n * is one nobody reads.\n */\n context?: TimeContext;\n}\n\n/**\n * A universal time, rendered as a date on the calendar the game is running:\n * `Y1 D5 03:22:37` under stock, `14 Mar 1957 03:22:37` under a real calendar.\n *\n * An instant rather than a quantity: {@link Unit} draws lengths of time, and\n * this draws points in it, since 9,201,600 seconds after the epoch is Year 2\n * Day 1, not \"106 days\". The calendar is the one the game reports on\n * `time.calendar`. A missing or non-finite value renders {@link NULL_DISPLAY},\n * not the epoch. A held `Reading` keeps its date and takes the held mark.\n *\n * @example\n * ```tsx\n * import { MissionDate } from \"@ksp-gonogo/ui-kit\";\n *\n * // A UT field off a payload, in seconds since the epoch.\n * <MissionDate value={course.completesAtUt} />\n *\n * // An event time under signal delay, qualified with its clock.\n * <MissionDate value={nodeUt} context={{ frame: \"scet\" }} />\n * <MissionDate value={nodeUt} context={{ frame: \"received\", vantage: \"KSC\" }} />\n * ```\n *\n * @category MissionDate\n * @categoryDescription MissionDate\n * Instants on the game's own calendar: a date for a point in time, and a\n * countdown to one. A length of time is a {@link Unit}.\n */\nexport function MissionDate({ value, context }: MissionDateProps) {\n // A held instant stays true, so a held reading adds only the mark, its grade and its last-valid instant.\n const carried = typeof value === \"number\" ? undefined : value;\n const currency = resolveCurrency(carried);\n const { shown, held, caption } = currency;\n const ut = typeof value === \"number\" ? value : shown?.magnitude;\n const figure = figureAttributes({ ...currency, shown: ut });\n const qualifier = context && describeContext(context);\n const date = formatKspDate(ut ?? Number.NaN);\n return (\n <>\n {held ? (\n /* The component otherwise renders bare text, so the mark needs a box to hang off. */\n <HeldFigure {...figure} caption={caption}>\n {date}\n </HeldFigure>\n ) : (\n <span data-figure={figure[\"data-figure\"]}>{date}</span>\n )}\n {qualifier && (\n <>\n {\" \"}\n <Tooltip text={qualifier.title} focusable>\n <MissionDate__Context>\n {/* The phrase replaces the token in the accessibility tree, as `<Unit>`'s word does. */}\n <span aria-hidden=\"true\">{qualifier.token}</span>\n <VisuallyHidden>{qualifier.spoken}</VisuallyHidden>\n </MissionDate__Context>\n </Tooltip>\n </>\n )}\n </>\n );\n}\n","import { value } from \"@ksp-gonogo/sitrep-sdk\";\nimport { useId, useState } from \"react\";\nimport { Button } from \"./Button\";\nimport { Cluster } from \"./Cluster\";\nimport { FieldLabel, Input } from \"./Form\";\nimport { kspCalendar } from \"./kspTime\";\nimport { NULL_DISPLAY } from \"./NullValue\";\nimport { Stack } from \"./Stack\";\nimport { Text } from \"./Text\";\nimport { writeQuantity } from \"./units\";\n\n/**\n * Props for {@link MissionDateField}.\n *\n * @category Form\n */\nexport interface MissionDateFieldProps {\n /**\n * The instant being edited, in seconds since the game's epoch, or `null` when\n * there is none: a reading that did not arrive, a field the producer withheld.\n *\n * A non-finite number is read as `null` too, rather than clamped to the epoch.\n */\n value: number | null;\n\n /** Called with the new instant, in whole seconds since the game's epoch. */\n onChange: (ut: number) => void;\n\n /** Names the whole group for a screen reader: \"Ignition\", \"Plan end\". */\n label: string;\n\n /** Greys the field and its buttons out and ignores input. */\n disabled?: boolean;\n\n /**\n * The coarse steps offered, in seconds, smallest first. Rendered as a minus\n * button and a plus button per entry. Defaults to a minute, ten minutes, an\n * hour and a day of the LIVE calendar.\n *\n * An empty list removes the row entirely, for a caller with another nudge\n * control beside this one.\n */\n steps?: number[];\n}\n\n/**\n * The five components of an instant on the game's own calendar.\n *\n * @category Form\n */\nexport interface MissionDateParts {\n /** The year, one-based: UT zero is Year 1. */\n year: number;\n /** The day of the year, one-based: UT zero is Day 1. */\n day: number;\n /** The hour of the day, from zero. */\n hour: number;\n /** The minute of the hour, from zero. */\n minute: number;\n /** The second of the minute, from zero. */\n second: number;\n}\n\n/**\n * Splits a UT (seconds since the game's epoch) into calendar components, with years and days ONE-BASED to match\n * every other date this kit renders: UT zero is Year 1 Day 1, not Year 0 Day 0.\n *\n * A non-finite or negative UT gives the epoch. Check for an absent instant\n * before calling this rather than showing it as the epoch.\n *\n * @category Form\n */\nexport function partsOfUt(ut: number): MissionDateParts {\n const { year: YEAR, day: DAY, hour: HOUR, minute: MINUTE } = kspCalendar();\n const clamped = Number.isFinite(ut) ? Math.max(0, ut) : 0;\n\n const year = Math.floor(clamped / YEAR) + 1;\n const inYear = clamped % YEAR;\n const day = Math.floor(inYear / DAY) + 1;\n const inDay = inYear % DAY;\n\n return {\n year,\n day,\n hour: Math.floor(inDay / HOUR),\n minute: Math.floor((inDay % HOUR) / MINUTE),\n second: Math.floor(inDay % MINUTE),\n };\n}\n\n/** What an instant typed into an empty field is built on top of. */\nconst EPOCH_PARTS: MissionDateParts = {\n year: 1,\n day: 1,\n hour: 0,\n minute: 0,\n second: 0,\n};\n\n/**\n * Recombines calendar components into a UT.\n *\n * Does not clamp an out-of-range component: an hour of 30 rolls into the next\n * day, so the operator never does the carry.\n *\n * @category Form\n */\nexport function utOfParts(parts: MissionDateParts): number {\n const { year: YEAR, day: DAY, hour: HOUR, minute: MINUTE } = kspCalendar();\n return (\n (parts.year - 1) * YEAR +\n (parts.day - 1) * DAY +\n parts.hour * HOUR +\n parts.minute * MINUTE +\n parts.second\n );\n}\n\n/**\n * A coarse step's label, written by the kit's duration formatter on the live\n * calendar's tiers, so a day's worth of seconds reads as a day.\n */\nfunction stepLabel(seconds: number): string {\n return writeQuantity(value(\"s\", seconds));\n}\n\n/**\n * A universal time ENTERED as a calendar instant: year, day, hour, minute,\n * second, each its own field, plus coarse steps. The fields are for an instant\n * you know; the steps are the tuning loop for one you are looking for.\n *\n * Calendar lengths come from the running game's calendar, so day 300 means\n * what the game's own clock calls day 300. Edits round to the second, so the\n * field never shows one instant and holds another.\n *\n * An instant nobody stated is not the epoch (UT 0, the start of the game's\n * calendar): `null` comes up empty with {@link NULL_DISPLAY}, the dash that\n * stands for no value, and nothing is committed until a component is typed.\n *\n * {@link UnitInput} with a `ut` unit renders this for you and emits a `Value`;\n * use this directly when you hold the instant as a bare number of seconds.\n *\n * @category Form\n */\nexport function MissionDateField({\n value,\n onChange,\n label,\n disabled,\n steps,\n}: MissionDateFieldProps) {\n const groupId = useId();\n const absentId = `${groupId}-absent`;\n // A non-finite number is the same absence as a null, reached by a different route.\n const instant = value !== null && Number.isFinite(value) ? value : null;\n const parts = instant === null ? null : partsOfUt(instant);\n const calendar = kspCalendar();\n const coarse = steps ?? [\n calendar.minute,\n 10 * calendar.minute,\n calendar.hour,\n calendar.day,\n ];\n\n /*\n * An in-progress edit is held as text for the one field being typed in, so a\n * cleared field stays empty instead of snapping back. Dropped on blur, so the\n * field never disagrees with the instant.\n */\n const [draft, setDraft] = useState<{\n key: keyof MissionDateParts;\n text: string;\n } | null>(null);\n\n const field = (\n key: keyof MissionDateParts,\n text: string,\n min: number,\n width: string,\n ) => (\n <Stack gap=\"caption\" key={key}>\n <FieldLabel htmlFor={`${groupId}-${key}`}>{text}</FieldLabel>\n <Input\n id={`${groupId}-${key}`}\n type=\"number\"\n inputMode=\"numeric\"\n // Named for the group as well as the column, so two of these on one panel are distinguishable.\n aria-label={`${label} ${text}`}\n // On every field, so the absence is spoken on focus.\n aria-describedby={parts === null ? absentId : undefined}\n min={min}\n step={1}\n style={{ width }}\n disabled={disabled}\n placeholder={parts === null ? NULL_DISPLAY : undefined}\n value={\n draft?.key === key\n ? draft.text\n : parts === null\n ? \"\"\n : String(parts[key])\n }\n onBlur={() => setDraft(null)}\n onChange={(event) => {\n const typed = event.target.value;\n setDraft({ key, text: typed });\n if (typed.trim() === \"\") return;\n const next = Number(typed);\n if (!Number.isFinite(next)) return;\n // The epoch is the base only once the operator has typed something.\n onChange(utOfParts({ ...(parts ?? EPOCH_PARTS), [key]: next }));\n }}\n />\n </Stack>\n );\n\n return (\n <Stack gap=\"related-dense\" role=\"group\" aria-label={label}>\n <Cluster gap=\"related-dense\" wrap justify=\"start\">\n {field(\"year\", \"YEAR\", 1, \"5rem\")}\n {field(\"day\", \"DAY\", 1, \"5rem\")}\n {field(\"hour\", \"HR\", 0, \"4rem\")}\n {field(\"minute\", \"MIN\", 0, \"4rem\")}\n {field(\"second\", \"SEC\", 0, \"4rem\")}\n </Cluster>\n {/* Words as well as the token, since the dash carries the absence only to an eye. */}\n {parts === null && (\n <Text id={absentId} level=\"muted\" size=\"sm\">\n {`${NULL_DISPLAY} no ${label.toLowerCase()} to show. Type one to state it.`}\n </Text>\n )}\n {coarse.length === 0 ? null : (\n <Cluster gap=\"related-packed\" wrap justify=\"start\">\n <Text level=\"faint\" size=\"sm\">\n NUDGE\n </Text>\n {/* Disabled over an absent instant: a step is relative, and there is nothing to step from. */}\n {coarse.map((step) => (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n key={`minus-${step}`}\n disabled={disabled || instant === null}\n aria-label={`${label} earlier by ${stepLabel(step)}`}\n onClick={() => instant !== null && onChange(instant - step)}\n >\n {`-${stepLabel(step)}`}\n </Button>\n ))}\n {coarse.map((step) => (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n key={`plus-${step}`}\n disabled={disabled || instant === null}\n aria-label={`${label} later by ${stepLabel(step)}`}\n onClick={() => instant !== null && onChange(instant + step)}\n >\n {`+${stepLabel(step)}`}\n </Button>\n ))}\n </Cluster>\n )}\n </Stack>\n );\n}\n","import type { ReactNode } from \"react\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styled from \"styled-components\";\nimport { Button } from \"./Button\";\nimport { focusRingInset } from \"./focusRing\";\nimport { CloseIcon } from \"./Icons\";\nimport { ModalChromeContext, type ModalChromeValue } from \"./ModalSaveBar\";\nimport { titleText } from \"./titleText\";\nimport { useKeyboardScrollable } from \"./useScrollerMetric\";\n\ninterface ModalEntry {\n id: string;\n title?: string;\n \"aria-label\"?: string;\n width?: string;\n content: ReactNode;\n}\n\n/**\n * What {@link useModal}'s `open` takes beside the content.\n *\n * @category Modal\n */\nexport interface ModalOpenOptions {\n /** The dialog's visible heading, which also names it for assistive technology. Without one, give `aria-label`. */\n title?: string;\n /** Names a dialog that has no visible `title`. Ignored when `title` is given. */\n \"aria-label\"?: string;\n /** CSS length for the dialog max-width. Defaults to 560px. */\n width?: string;\n}\n\n/**\n * What {@link useModal} returns: open and close dialogs.\n *\n * @category Modal\n */\nexport interface ModalContextValue {\n /** Opens a dialog showing `content` on top of any already open, and returns its id. */\n open: (content: ReactNode, options?: ModalOpenOptions) => string;\n /** Closes the dialog with this id, whether or not it is on top. Does nothing for an id already closed. */\n close: (id: string) => void;\n}\n\nconst ModalContext = createContext<ModalContextValue | null>(null);\n\n/** The id `open()` hands back is only ever compared against this module's own stack, so a counter is sufficient. */\nlet modalSeq = 0;\n\n/**\n * Holds the stack of open modal dialogs and renders them into `document.body`.\n * The dashboard mounts one at its root, so a widget on the dashboard calls\n * {@link useModal} without mounting its own. Anywhere else, such as a test that\n * renders a widget with a config form, wrap the tree in one: `useModal` needs it above it.\n *\n * Each dialog has a title bar with a close button, a scrolling body, and an\n * optional sticky footer set with {@link useModalSaveBar} or\n * {@link useModalChrome}. Escape, the close button and a click on the backdrop\n * close only the topmost dialog; when its content has reported unsaved\n * changes, they show a \"Discard unsaved changes?\" prompt instead. Focus is\n * trapped inside the dialog and returns to the opener when it closes.\n *\n * @category Modal\n * @categoryDescription Modal\n * Dialogs over the dashboard: opening one, its save bar and footer, and telling\n * whether its content has unsaved changes.\n */\nexport function ModalProvider({ children }: Readonly<{ children: ReactNode }>) {\n const [modals, setModals] = useState<ModalEntry[]>([]);\n\n const open = useCallback(\n (content: ReactNode, options?: ModalOpenOptions): string => {\n const id = `modal-${++modalSeq}`;\n setModals((prev) => [\n ...prev,\n {\n id,\n title: options?.title,\n \"aria-label\": options?.[\"aria-label\"],\n width: options?.width,\n content,\n },\n ]);\n return id;\n },\n [],\n );\n\n const close = useCallback((id: string) => {\n setModals((prev) => prev.filter((m) => m.id !== id));\n }, []);\n\n const value = useMemo(() => ({ open, close }), [open, close]);\n\n return (\n <ModalContext.Provider value={value}>\n {children}\n {modals.map((m, i) => (\n <ModalDialog\n key={m.id}\n entry={m}\n isTop={i === modals.length - 1}\n onClose={() => close(m.id)}\n />\n ))}\n </ModalContext.Provider>\n );\n}\n\n/**\n * Opens and closes modal dialogs. `open(content, options)` pushes a dialog\n * showing `content` and returns its id; `options` takes a `title`, an\n * `aria-label` for a dialog with no visible title, and a CSS `width` for the\n * dialog's max-width (560px by default). `close(id)` removes that dialog.\n * Throws when called outside a {@link ModalProvider}.\n *\n * @example\n * ```tsx\n * const { open } = useModal();\n * <Button onClick={() => open(<FlightDetails />, { title: \"Flight History\" })}>\n * History\n * </Button>\n * ```\n *\n * @category Modal\n */\nexport function useModal(): ModalContextValue {\n const ctx = useContext(ModalContext);\n if (!ctx) throw new Error(\"useModal must be used inside <ModalProvider>\");\n return ctx;\n}\n\ninterface ModalDialogProps {\n entry: ModalEntry;\n /** Only the topmost of a stack answers the keyboard. */\n isTop: boolean;\n onClose: () => void;\n}\n\nconst FOCUSABLE =\n 'button:not(:disabled), [href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex=\"-1\"])';\n\nfunction ModalDialog({ entry, isTop, onClose }: Readonly<ModalDialogProps>) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const isTopRef = useRef(isTop);\n isTopRef.current = isTop;\n\n // Focus goes back to whatever opened the dialog once it closes.\n useEffect(() => {\n const opener =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n return () => {\n if (opener?.isConnected) opener.focus();\n };\n }, []);\n const titleId = useId();\n // Dismiss only when both press and release land on the backdrop, so a text selection dragged out of the dialog does not close it.\n const downOnBackdropRef = useRef(false);\n\n // Footer and dirty state registered by content via useModalChrome.\n const [footer, setFooter] = useState<ReactNode>(null);\n const [dirty, setDirty] = useState(false);\n const [confirming, setConfirming] = useState(false);\n const confirmRef = useRef<HTMLButtonElement>(null);\n const dirtyRef = useRef(dirty);\n dirtyRef.current = dirty;\n const confirmingRef = useRef(confirming);\n confirmingRef.current = confirming;\n\n const chrome = useMemo<ModalChromeValue>(() => ({ setFooter, setDirty }), []);\n const [body, setBody] = useState<HTMLDivElement | null>(null);\n const bodyTabIndex = useKeyboardScrollable(body);\n\n // Every close path funnels here, so unsaved changes show the discard confirmation instead.\n const requestClose = useCallback(() => {\n if (dirtyRef.current) {\n setConfirming(true);\n return;\n }\n onClose();\n }, [onClose]);\n\n // While the discard confirmation is open, Escape cancels it rather than closing the modal.\n useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n if (e.key !== \"Escape\" || !isTopRef.current) return;\n if (confirmingRef.current) {\n e.stopPropagation();\n setConfirming(false);\n return;\n }\n requestClose();\n }\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [requestClose]);\n\n // Move focus into the confirmation when it appears.\n useEffect(() => {\n if (confirming) confirmRef.current?.focus();\n }, [confirming]);\n\n /*\n * Trap focus inside the dialog. The focusable set is read on every Tab, so a\n * control that renders late or becomes disabled is judged as it is now.\n * Re-runs when the form and confirmation views swap, to move focus into\n * whichever is shown.\n */\n // biome-ignore lint/correctness/useExhaustiveDependencies: `confirming` and `footer` are intentional triggers, they change the set of focusable elements.\n useEffect(() => {\n const el = dialogRef.current;\n if (!el) return;\n\n function handleTab(e: KeyboardEvent) {\n if (e.key !== \"Tab\" || !isTopRef.current || !el) return;\n const focusable = el.querySelectorAll<HTMLElement>(FOCUSABLE);\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last?.focus();\n }\n return;\n }\n if (document.activeElement === last) {\n e.preventDefault();\n first?.focus();\n }\n }\n document.addEventListener(\"keydown\", handleTab);\n if (!confirming) el.querySelector<HTMLElement>(FOCUSABLE)?.focus();\n return () => document.removeEventListener(\"keydown\", handleTab);\n }, [confirming, footer]);\n\n return (\n <>\n {createPortal(\n // Interactive, so it cannot also be role=\"presentation\"; keyboard users close with Escape.\n <Backdrop\n onMouseDown={(e) => {\n downOnBackdropRef.current = e.target === e.currentTarget;\n }}\n onMouseUp={(e) => {\n // Through requestClose, so the dirty guard can intercept.\n if (downOnBackdropRef.current && e.target === e.currentTarget) {\n requestClose();\n }\n downOnBackdropRef.current = false;\n }}\n >\n <Dialog\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={entry.title ? titleId : undefined}\n aria-label={entry.title ? undefined : entry[\"aria-label\"]}\n $width={entry.width}\n >\n <DialogHeader>\n {entry.title && (\n <DialogTitle id={titleId}>{entry.title}</DialogTitle>\n )}\n <CloseButton onClick={requestClose} aria-label=\"Close\">\n <CloseIcon size={16} />\n </CloseButton>\n </DialogHeader>\n <DialogBody ref={setBody} tabIndex={bodyTabIndex}>\n <ModalChromeContext.Provider value={chrome}>\n {entry.content}\n </ModalChromeContext.Provider>\n </DialogBody>\n {footer && !confirming && <DialogFooter>{footer}</DialogFooter>}\n {confirming && (\n <DialogFooter\n role=\"alertdialog\"\n aria-label=\"Discard unsaved changes?\"\n >\n <DiscardPrompt>Discard unsaved changes?</DiscardPrompt>\n <Button\n variant=\"ghost\"\n type=\"button\"\n onClick={() => setConfirming(false)}\n >\n Keep editing\n </Button>\n <Button\n variant=\"primary\"\n ref={confirmRef}\n type=\"button\"\n onClick={() => {\n setConfirming(false);\n onClose();\n }}\n >\n Discard\n </Button>\n </DialogFooter>\n )}\n </Dialog>\n </Backdrop>,\n document.body,\n )}\n </>\n );\n}\n\nconst Backdrop = styled.div`\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.72);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: var(--z-modal);\n`;\n\nconst Dialog = styled.div<{ $width?: string }>`\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-strong);\n border-radius: var(--radius-floating);\n min-width: min(320px, 100vw - 16px);\n max-width: ${({ $width }) => $width ?? \"560px\"};\n width: 90vw;\n max-height: 80vh;\n display: flex;\n flex-direction: column;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);\n`;\n\nconst DialogHeader = styled.div`\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--inset-modal-bar);\n border-bottom: 1px solid var(--color-border-subtle);\n flex-shrink: 0;\n`;\n\nconst DialogTitle = styled.h2`\n margin: 0;\n ${titleText}\n`;\n\nconst CloseButton = styled.button`\n background: none;\n border: none;\n color: var(--color-text-faint);\n cursor: pointer;\n line-height: var(--line-height-flush);\n padding: var(--inset-glyph-button);\n\n @media (hover: hover) {\n &:hover {\n color: var(--color-text-primary);\n }\n }\n @media (pointer: coarse) {\n min-width: 44px;\n min-height: 44px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n`;\n\nconst DialogBody = styled.div`\n padding: var(--inset-modal-body);\n overflow-y: auto;\n ${focusRingInset}\n -webkit-overflow-scrolling: touch;\n flex: 1;\n`;\n\n/* Sticky action region. Lives outside DialogBody so it never scrolls away. */\nconst DialogFooter = styled.div`\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--gap-control-row);\n padding: var(--inset-modal-bar);\n border-top: 1px solid var(--color-border-subtle);\n flex-shrink: 0;\n`;\n\nconst DiscardPrompt = styled.span`\n margin-right: auto;\n font-size: var(--font-size-compact);\n color: var(--color-text-primary);\n`;\n","import type { ReactNode } from \"react\";\nimport { createContext, useContext, useEffect, useMemo, useRef } from \"react\";\nimport { Button } from \"./Button\";\nimport { configEqual } from \"./configEqual\";\n\n/**\n * What a modal dialog offers the content inside it: a sticky footer and an\n * unsaved-changes flag. Read it through {@link useModalChrome} or\n * {@link useModalSaveBar} rather than directly.\n *\n * @category Modal\n */\nexport interface ModalChromeValue {\n /** Sets or replaces the sticky footer node. Pass null to clear it. */\n setFooter: (node: ReactNode) => void;\n /** Reports whether the current content has unsaved changes. */\n setDirty: (dirty: boolean) => void;\n}\n\n/**\n * The context each open modal dialog provides to its content, `null` outside\n * a modal. Content uses {@link useModalChrome} or {@link useModalSaveBar}\n * rather than reading it.\n *\n * @intent Published so content that calls useModalChrome can be rendered under a stand-in modal in a test.\n * @category Modal\n */\nexport const ModalChromeContext = createContext<ModalChromeValue | null>(null);\n\n/**\n * Shows `footer` as the enclosing modal's sticky footer, below the scrolling\n * body so it never scrolls out of view, and reports `dirty` so the modal asks\n * before discarding unsaved changes. The footer is cleared and the flag reset\n * on unmount. Does nothing outside a modal. For a plain Save button use\n * {@link useModalSaveBar}, which works out `dirty` for you.\n *\n * @category Modal\n */\nexport function useModalChrome(footer: ReactNode, dirty: boolean): void {\n const ctx = useContext(ModalChromeContext);\n const setFooter = ctx?.setFooter;\n const setDirty = ctx?.setDirty;\n\n useEffect(() => {\n setFooter?.(footer);\n return () => setFooter?.(null);\n }, [setFooter, footer]);\n\n useEffect(() => {\n setDirty?.(dirty);\n return () => setDirty?.(false);\n }, [setDirty, dirty]);\n}\n\n/**\n * Options for {@link useModalSaveBar}.\n *\n * @category Modal\n */\nexport interface ModalSaveBarOptions<Draft> {\n /** Called when the operator presses Save. */\n onSave: () => void;\n /**\n * The working draft the form would persist on Save, as the full config\n * object. Compared with its value when the modal opened and with `saved` to\n * decide whether there are unsaved changes.\n */\n value: Draft;\n /**\n * The config as currently persisted (the form's `config` prop). A draft\n * equal to it counts as clean even when it differs from the value at open.\n */\n saved: Draft;\n /** Save button label. Defaults to \"Save\". */\n saveLabel?: ReactNode;\n /** Extra buttons drawn to the left of Save, such as Cancel. */\n extra?: ReactNode;\n /**\n * Disables the Save button. Save is not disabled automatically when the form\n * has no changes, so the operator can always save again.\n */\n disabled?: boolean;\n}\n\n/**\n * Puts a primary Save button (and any `extra` buttons) in the enclosing\n * modal's sticky footer, and marks the modal dirty so it asks before\n * discarding unsaved edits. Use it in a widget's config form in place of a\n * Save button at the bottom of the form.\n *\n * The form counts as dirty only when `value` differs, by {@link configEqual},\n * from both its value when the modal opened and `saved`. The hook renders\n * nothing where it is called and does nothing outside a modal.\n *\n * The form is a widget's `configComponent`: register it beside the widget with\n * `registerComponent({ ..., configComponent: NavballConfigForm })`, and the\n * dashboard opens it in a modal with the saved `config` and an `onSave`.\n *\n * @example\n * ```tsx\n * import { useMemo, useState } from \"react\";\n * import type { ConfigComponentProps } from \"@ksp-gonogo/sitrep-sdk\";\n * import { ConfigForm, Switch, useModalSaveBar } from \"@ksp-gonogo/ui-kit\";\n *\n * interface NavballConfig {\n * controlMode?: boolean;\n * }\n *\n * function NavballConfigForm({ config, onSave }: ConfigComponentProps<NavballConfig>) {\n * const [controlMode, setControlMode] = useState(config.controlMode === true);\n * const candidate = useMemo(() => ({ controlMode }), [controlMode]);\n * useModalSaveBar({\n * onSave: () => onSave(candidate),\n * value: candidate,\n * saved: config,\n * });\n * return (\n * <ConfigForm>\n * <Switch label=\"Control mode\" checked={controlMode} onChange={setControlMode} />\n * </ConfigForm>\n * );\n * }\n * ```\n *\n * @category Modal\n */\nexport function useModalSaveBar<Draft>(\n options: Readonly<ModalSaveBarOptions<Draft>>,\n): void {\n const { onSave, value, saved, saveLabel = \"Save\", extra, disabled } = options;\n\n // Wrapped in an object, so a falsy first value still counts as captured.\n const baselineRef = useRef<{ v: Draft } | null>(null);\n if (baselineRef.current === null) baselineRef.current = { v: value };\n\n const dirty =\n !configEqual(value, baselineRef.current.v) && !configEqual(value, saved);\n\n const footer = useMemo(\n () => (\n <>\n {extra}\n <Button\n variant=\"primary\"\n type=\"button\"\n onClick={onSave}\n disabled={disabled}\n >\n {saveLabel}\n </Button>\n </>\n ),\n [extra, onSave, disabled, saveLabel],\n );\n useModalChrome(footer, dirty);\n}\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { Block__Root, type BlockProps, renderBlockAnatomy } from \"./Block\";\nimport { LiveRegion } from \"./LiveRegion\";\nimport { toneEdge } from \"./tone\";\n\n/**\n * Props for {@link Notice}: everything {@link BlockProps} takes, plus the\n * banner's tone and how loudly it is announced.\n *\n * @category EmptyState\n */\nexport interface NoticeProps extends BlockProps {\n /**\n * What kind of statement this is, drawn as the banner's whole border.\n * Defaults to `warn`.\n */\n tone?: Tone;\n /**\n * Interrupt rather than announce politely: the banner becomes\n * `role=\"alert\"` with `aria-live=\"assertive\"`. Keep it for a state that\n * must interrupt, such as a mission abort, and nothing softer.\n */\n assertive?: boolean;\n /** What the banner says. */\n children?: ReactNode;\n}\n\nconst Notice__Root = styled(Block__Root)<{ $tone: Tone }>`\n --gap-related: var(--gap-related-comfortable);\n --bleed-inline: 0px;\n\n background: var(--color-surface-sunken);\n border: 1px solid ${({ $tone }) => toneEdge($tone)};\n border-radius: var(--radius-regular);\n /* A banner is its own strip of the screen, so it takes the roomier inset. */\n padding: var(--inset-surface-standalone);\n`;\n\n/**\n * A banner stating something about the widget rather than a record inside it,\n * such as what is limiting the ship. It takes {@link Block}'s anatomy (title,\n * asides, footer) on a sunken surface bordered in its tone. Most banners need\n * only `title`, `tone` and their text; the other anatomy props are for a banner\n * that also carries a figure beside it or a footer of actions.\n *\n * Unlike a toned {@link Card}, a notice is announced to screen readers. By\n * default it is `role=\"note\"` and its text is read out politely when it\n * appears and whenever its words change; with `assertive` it is\n * `role=\"alert\"` and interrupts. A `role` passed by the caller replaces the\n * kit's and turns the announcement off.\n *\n * @example\n * ```tsx\n * <Notice tone=\"nogo\" title=\"Thrust limited\">\n * Two engines are flamed out; available thrust is 48%.\n * </Notice>\n * ```\n *\n * @category EmptyState\n */\nexport function Notice({\n tone = \"warn\",\n assertive = false,\n role,\n children,\n ...props\n}: NoticeProps): ReactNode {\n const rootRef = useRef<HTMLElement>(null);\n const [said, setSaid] = useState(\"\");\n useEffect(() => {\n const root = rootRef.current;\n if (assertive || role !== undefined || root === null) return;\n const text = spokenText(root);\n setSaid((previous) => (previous === text ? previous : text));\n });\n\n if (assertive) {\n return renderBlockAnatomy(Notice__Root, {\n ...props,\n children,\n role: role ?? \"alert\",\n \"aria-live\": role === undefined ? \"assertive\" : undefined,\n $tone: tone,\n });\n }\n const block = renderBlockAnatomy(Notice__Root, {\n ...props,\n children,\n ref: rootRef,\n // A note, so a caller's `aria-label` names something that may carry a name.\n role: role ?? \"note\",\n $tone: tone,\n });\n if (role !== undefined) return block;\n return (\n <>\n {block}\n <LiveRegion visuallyHidden>{said}</LiveRegion>\n </>\n );\n}\n\n/**\n * The notice's words. Text runs are joined with a space, so a title and the\n * body under it do not read as one word.\n */\nfunction spokenText(root: HTMLElement): string {\n const runs: string[] = [];\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {\n runs.push(node.textContent ?? \"\");\n }\n return runs.join(\" \").replace(/\\s+/g, \" \").trim();\n}\n","/**\n * The SVG and canvas faces of the reckoning mark, drawn from the same spec as\n * the DOM marks in `HeldMark.tsx`.\n */\nimport { RECKONING_MARK, type ReckoningKind } from \"./reckoningMarkSpec\";\n\n/** Half the held square's side in units of the mark's radius, which gives it the area of a dot of that radius, so the square weighs what the triangle beside it does. */\nconst HELD_SQUARE_HALF = 0.89;\n\n/** The opacity of a held position drawn beside a modelled one. */\nconst GHOST_OPACITY = 0.45;\n\n/**\n * The mark as an SVG shape centred on a point: a square for held, a triangle\n * pointing up for modelled.\n *\n * @category Unit\n */\nexport function ReckoningMarkSvg({\n kind,\n x,\n y,\n scale = 1,\n ghost = false,\n}: {\n kind: ReckoningKind;\n x: number;\n y: number;\n /** Multiplies the canvas-size radius, for a diagram drawn at other than 1x. */\n scale?: number;\n /** Drawn faint: a held position shown beside a modelled one. */\n ghost?: boolean;\n}) {\n const spec = RECKONING_MARK[kind];\n const r = spec.canvasRadius * scale;\n const common = {\n fill: spec.color,\n opacity: ghost ? GHOST_OPACITY : undefined,\n \"data-reckoning-mark\": kind,\n \"aria-hidden\": true as const,\n };\n if (kind === \"held\") {\n const half = r * HELD_SQUARE_HALF;\n return (\n <rect\n x={x - half}\n y={y - half}\n width={2 * half}\n height={2 * half}\n {...common}\n />\n );\n }\n const points = `${x},${y - r} ${x + r},${y + r * 0.8} ${x - r},${y + r * 0.8}`;\n return <polygon points={points} {...common} />;\n}\n\n/** A CSS custom property as the canvas needs it: a resolved colour, since a canvas cannot read `var()`. */\nfunction resolvedColor(\n canvas: HTMLCanvasElement,\n cssVar: string,\n fallback: string,\n): string {\n const v = getComputedStyle(canvas).getPropertyValue(cssVar).trim();\n return v || fallback;\n}\n\n/**\n * Options for {@link paintReckoningMark}.\n *\n * @category Unit\n */\nexport interface PaintReckoningMarkOptions {\n /** Drawn faint: a held position shown beside a modelled one. */\n ghost?: boolean;\n /** Multiplies the mark's size, for a canvas drawn at other than 1x. */\n scale?: number;\n}\n\n/**\n * Paints the mark on a canvas, centred on a point in canvas pixels: a square for\n * held, a triangle pointing up for modelled, in the hue the spec names.\n *\n * @intent The single-mark canvas face for a third-party canvas that draws one\n * held or modelled point; {@link paintReckonedPosition} composes it for a pair.\n * @category Unit\n */\nexport function paintReckoningMark(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D,\n kind: ReckoningKind,\n x: number,\n y: number,\n { ghost = false, scale = 1 }: PaintReckoningMarkOptions = {},\n): void {\n const spec = RECKONING_MARK[kind];\n const r = spec.canvasRadius * scale;\n ctx.save();\n ctx.globalAlpha = ghost ? GHOST_OPACITY : 1;\n ctx.fillStyle = resolvedColor(canvas, spec.cssVar, spec.fallback);\n ctx.beginPath();\n if (kind === \"held\") {\n const half = r * HELD_SQUARE_HALF;\n ctx.rect(x - half, y - half, 2 * half, 2 * half);\n } else {\n ctx.moveTo(x, y - r);\n ctx.lineTo(x + r, y + r * 0.8);\n ctx.lineTo(x - r, y + r * 0.8);\n ctx.closePath();\n }\n ctx.fill();\n ctx.restore();\n}\n\n/**\n * Where a craft was last observed and where a model puts it now, in canvas pixels.\n *\n * @category Unit\n */\nexport interface ReckonedPosition {\n /** The last observed position; drawn as the held mark. */\n held?: { x: number; y: number };\n /** Where the model puts the craft now; drawn as the modelled mark. */\n modelled?: { x: number; y: number };\n}\n\n/**\n * Paints a reckoned position: the held mark at the last observation, and the\n * modelled mark where the model puts the craft now. With both, the held mark\n * is drawn faint, so the pair reads as \"was here, modelled to be there\", joined\n * by a faint dashed track.\n *\n * @category Unit\n */\nexport function paintReckonedPosition(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D,\n { held, modelled }: ReckonedPosition,\n options: Omit<PaintReckoningMarkOptions, \"ghost\"> = {},\n): void {\n if (held !== undefined && modelled !== undefined) {\n ctx.save();\n ctx.globalAlpha = GHOST_OPACITY;\n ctx.strokeStyle = resolvedColor(\n canvas,\n RECKONING_MARK.modelled.cssVar,\n RECKONING_MARK.modelled.fallback,\n );\n ctx.lineWidth = 1;\n ctx.setLineDash([2, 3]);\n ctx.beginPath();\n ctx.moveTo(held.x, held.y);\n ctx.lineTo(modelled.x, modelled.y);\n ctx.stroke();\n ctx.restore();\n }\n if (held !== undefined) {\n paintReckoningMark(canvas, ctx, \"held\", held.x, held.y, {\n ...options,\n ghost: modelled !== undefined,\n });\n }\n if (modelled !== undefined) {\n paintReckoningMark(\n canvas,\n ctx,\n \"modelled\",\n modelled.x,\n modelled.y,\n options,\n );\n }\n}\n","import { NULL_DISPLAY } from \"./NullValue\";\nimport { ReckoningMarkSvg } from \"./reckoningMarkDraw\";\n\n/**\n * One line of a {@link PlotCrosshair} readout.\n *\n * @category LineGraph\n */\nexport interface PlotCrosshairRow {\n id: string;\n label: string;\n /** The colour of the thing the row reads, so the row ties back to its trace. */\n color: string;\n /** The reading, unit included. `null` is an absent sample and is drawn as the null token, never as a number. */\n value: string | null;\n /** How current the reading is (\"measured\", \"modelled\"), written after the value when there is room. */\n currency?: string;\n /** A model's figure: drawn with the kit's modelled mark. */\n modelled?: boolean;\n /** Dropped from the compact card of a small plot, where only the figures on the traces fit. */\n detail?: boolean;\n /** Where the row's marker stands on the plot, in pixels. Omitted for a row with no point on the plot, such as a limit. */\n y?: number | null;\n}\n\n/**\n * What a {@link PlotCrosshair} draws: where the line stands, the plot it\n * spans, the instant it names and the rows it reads.\n *\n * @category LineGraph\n */\nexport interface PlotCrosshairProps {\n /** Where the line stands, in pixels. */\n x: number;\n /** The plot's box, in pixels. The line spans it and the readout stays inside it. */\n plot: { x0: number; y0: number; x1: number; y1: number };\n /** Names the instant, written once above the rows. */\n heading: string;\n rows: readonly PlotCrosshairRow[];\n /**\n * A column outside the plot where the readout stands, when the chart has\n * given the card one (see `placePlotReadouts`). The card then sits at the\n * column's top and names the instant even when compact, and nothing it draws\n * covers the plot. Omitted, the card is drawn over the plot where it covers no dot.\n */\n column?: { x0: number; y0: number; x1: number; y1: number };\n /** The traces as drawn, one pixel point per sample with a non-finite `cy` for a hole. Over the plot, the card prefers a corner none of them crosses. */\n traces?: ReadonlyArray<{ cx: readonly number[]; cy: readonly number[] }>;\n}\n\nconst ROW_PX = 14;\nconst PAD_PX = 5;\nconst CHAR_PX = 6.2;\nconst GAP_PX = 10;\nconst MARK_PX = 12;\nconst EDGE_PX = 6;\nconst SWATCH_PX = 8;\nconst DOT_PX = 4.5;\nconst SWATCH_GAP_PX = 4;\nconst MAX_COMPACT_LABEL_CHARS = 12;\nconst FULL_MIN_PLOT_W = 190;\nconst FULL_MIN_PLOT_H = 80;\n\ninterface CardLayout {\n full: boolean;\n /** The card writes the instant above its rows. */\n named: boolean;\n headingChars: number;\n /** A column card writes each row over two lines, the label then its figure, so nothing is cut. */\n stacked: boolean;\n rows: readonly PlotCrosshairRow[];\n /** What each row's text says: the label and value on a full card, a shortened label and the value on a compact one. */\n text: (row: PlotCrosshairRow) => string;\n /** The figure's own line on a stacked card. */\n valueText: (row: PlotCrosshairRow) => string;\n currencyText: (row: PlotCrosshairRow) => string;\n cardW: number;\n cardH: number;\n headRows: number;\n fits: boolean;\n}\n\nfunction shortened(label: string, chars: number): string {\n if (chars < 1) return \"\";\n return label.length <= chars\n ? label\n : `${label.slice(0, Math.max(1, chars - 1))}\\u2026`;\n}\n\nfunction layoutCard(\n plot: PlotCrosshairProps[\"plot\"],\n heading: string,\n allRows: readonly PlotCrosshairRow[],\n column?: PlotCrosshairProps[\"column\"],\n): CardLayout {\n const room = column ?? plot;\n const plotW = room.x1 - room.x0;\n const plotH = plot.y1 - plot.y0;\n const full = plotW >= FULL_MIN_PLOT_W && plotH >= FULL_MIN_PLOT_H;\n const named = full || column !== undefined;\n // A column has the height for the limits too, since the card covers nothing there.\n const rows = full || column ? allRows : allRows.filter((row) => !row.detail);\n\n // A compact row keeps its series' identity: a swatch and as much of the label as the card can hold beside the value.\n const compactRoom =\n plotW - EDGE_PX * 2 - PAD_PX * 2 - SWATCH_PX - SWATCH_GAP_PX;\n const stacked = column !== undefined;\n const stackedChars = Math.floor(compactRoom / CHAR_PX);\n const valueText = (row: PlotCrosshairRow) =>\n shortened(\n row.value ?? NULL_DISPLAY,\n stackedChars - (row.currency ? row.currency.length + 2 : 0),\n );\n const text = (row: PlotCrosshairRow) => {\n const value = row.value ?? NULL_DISPLAY;\n if (stacked) return shortened(row.label, stackedChars);\n if (full) return `${row.label} ${value}`;\n const room =\n Math.floor((compactRoom - value.length * CHAR_PX) / CHAR_PX) -\n 1 -\n (row.modelled ? 2 : 0);\n const label = shortened(row.label, Math.min(room, MAX_COMPACT_LABEL_CHARS));\n if (label !== \"\") return `${label} ${value}`;\n // A value wider than the card is cut rather than left to spill out of it.\n const chars = Math.floor(compactRoom / CHAR_PX) - (row.modelled ? 2 : 0);\n return shortened(value, chars);\n };\n const currencyText = (row: PlotCrosshairRow) =>\n (full || stacked) && row.currency ? row.currency : \"\";\n const currencyW = (row: PlotCrosshairRow) =>\n currencyText(row)\n ? GAP_PX +\n currencyText(row).length * CHAR_PX +\n (row.modelled ? MARK_PX : 0)\n : row.modelled\n ? MARK_PX\n : 0;\n const swatchW = full ? 0 : SWATCH_PX + SWATCH_GAP_PX;\n const headingChars = Math.floor((plotW - EDGE_PX * 2 - PAD_PX * 2) / CHAR_PX);\n\n const textW = Math.max(\n ...rows.map((row) =>\n stacked\n ? swatchW +\n Math.max(\n text(row).length * CHAR_PX,\n valueText(row).length * CHAR_PX + currencyW(row),\n )\n : swatchW + text(row).length * CHAR_PX + currencyW(row),\n ),\n named ? Math.min(heading.length, headingChars) * CHAR_PX : 0,\n );\n const headRows = named ? 1 : 0;\n const cardH =\n (rows.length * (stacked ? 2 : 1) + headRows) * ROW_PX + PAD_PX * 2;\n return {\n full,\n named,\n headingChars,\n stacked,\n rows,\n text,\n valueText,\n currencyText,\n // A column card spans its column, so the captions standing under it line up with it.\n cardW: stacked ? plotW : Math.min(textW + PAD_PX * 2, plotW - EDGE_PX * 2),\n cardH,\n headRows,\n fits: cardH <= plotH - EDGE_PX * 2,\n };\n}\n\n/** Whether any stretch of the trace passes through the box, walking each segment in steps no wider than the card's rows. */\nfunction crossesBox(\n trace: { cx: readonly number[]; cy: readonly number[] },\n x: number,\n y: number,\n w: number,\n h: number,\n): boolean {\n const inside = (px: number, py: number) =>\n px >= x && px <= x + w && py >= y && py <= y + h;\n for (let i = 1; i < trace.cx.length; i++) {\n const [ax, ay, bx, by] = [\n trace.cx[i - 1],\n trace.cy[i - 1],\n trace.cx[i],\n trace.cy[i],\n ];\n if (![ax, ay, bx, by].every(Number.isFinite)) continue;\n const steps = Math.max(1, Math.ceil(Math.hypot(bx - ax, by - ay) / ROW_PX));\n for (let k = 0; k <= steps; k++) {\n if (inside(ax + ((bx - ax) * k) / steps, ay + ((by - ay) * k) / steps)) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction resolveCard({\n x,\n plot,\n heading,\n rows: allRows,\n column,\n traces = [],\n}: PlotCrosshairProps): {\n layout: CardLayout;\n place: { cardX: number; cardY: number } | undefined;\n} {\n const layout = layoutCard(plot, heading, allRows, column);\n const { rows, cardW, cardH, fits } = layout;\n const cardY0 = plot.y0 + EDGE_PX;\n const dots = rows.flatMap((row) =>\n row.y === undefined || row.y === null ? [] : [row.y],\n );\n const clearOfDots = (cardX: number, cardY: number) =>\n dots.every(\n (dy) =>\n x + DOT_PX < cardX ||\n x - DOT_PX > cardX + cardW ||\n dy + DOT_PX < cardY ||\n dy - DOT_PX > cardY + cardH,\n );\n\n // In a column the card stands there. Over the plot it takes the first corner, beside the line on the side with more room, that covers no dot.\n const roomRight = plot.x1 - x - EDGE_PX;\n const rightFirst = roomRight >= cardW || roomRight >= x - plot.x0;\n const clampX = (v: number) =>\n Math.max(plot.x0 + EDGE_PX, Math.min(plot.x1 - EDGE_PX - cardW, v));\n const sideXs = rightFirst\n ? [x + EDGE_PX, x - EDGE_PX - cardW]\n : [x - EDGE_PX - cardW, x + EDGE_PX];\n const corners = [cardY0, plot.y1 - EDGE_PX - cardH].flatMap((cy) =>\n sideXs.map((cx) => ({ cardX: clampX(cx), cardY: cy })),\n );\n const clearOfTraces = (cardX: number, cardY: number) =>\n traces.every((t) => !crossesBox(t, cardX, cardY, cardW, cardH));\n const overPlot =\n corners.find(\n (c) => clearOfDots(c.cardX, c.cardY) && clearOfTraces(c.cardX, c.cardY),\n ) ?? corners.find((c) => clearOfDots(c.cardX, c.cardY));\n const place = !fits\n ? undefined\n : column\n ? { cardX: column.x0, cardY: column.y0 + EDGE_PX }\n : overPlot;\n return { layout, place };\n}\n\n/**\n * Whether {@link PlotCrosshair} draws its readout card for these props: the\n * card fits, and over the plot there is a corner that covers no dot. A chart\n * that drew a legend in the same place hides it while the card is up, since\n * each row names its series in its colour.\n *\n * @category LineGraph\n */\nexport function plotCrosshairShowsCard(\n props: Pick<\n PlotCrosshairProps,\n \"x\" | \"plot\" | \"heading\" | \"rows\" | \"column\" | \"traces\"\n >,\n): boolean {\n return resolveCard(props).place !== undefined;\n}\n\n/**\n * A crosshair for an SVG plot: a vertical line at an instant, a dot on each\n * trace where it is read, and a readout of every row's exact value.\n *\n * The readout is drawn over the plot at a fixed corner on whichever side of\n * the line has room, so moving the line never resizes or reflows the chart. A\n * plot too small for the full card gets a compact one: a swatch, as much of\n * the label as fits, and the value, so an absent row still says whose it is.\n * An absent reading is the null token. Place it as the last child of the\n * plot's `<svg>`; it takes no pointer events, so the plot keeps them.\n *\n * @category LineGraph\n */\nexport function PlotCrosshair({\n x,\n plot,\n heading,\n rows: allRows,\n column,\n}: PlotCrosshairProps) {\n const { layout, place } = resolveCard({\n x,\n plot,\n heading,\n rows: allRows,\n column,\n });\n const {\n full,\n named,\n headingChars,\n stacked,\n rows,\n text,\n valueText,\n currencyText,\n cardW,\n cardH,\n headRows,\n } = layout;\n const drawn = place !== undefined;\n const cardX = place?.cardX ?? 0;\n const cardY = place?.cardY ?? 0;\n const textX = (cx: number) =>\n cx + PAD_PX + (full ? 0 : SWATCH_PX + SWATCH_GAP_PX);\n\n return (\n <g pointerEvents=\"none\" data-plot-crosshair=\"\">\n <line\n x1={x}\n y1={plot.y0}\n x2={x}\n y2={plot.y1}\n stroke=\"var(--color-text-primary)\"\n strokeWidth={1}\n opacity={0.7}\n />\n {rows.map((row) =>\n row.y === undefined || row.y === null ? null : (\n <circle\n key={row.id}\n cx={x}\n cy={row.y}\n r={3.5}\n fill={row.color}\n stroke=\"var(--color-text-primary)\"\n strokeWidth={1}\n />\n ),\n )}\n {drawn && (\n <g data-plot-crosshair-card=\"\">\n <rect\n x={cardX}\n y={cardY}\n width={cardW}\n height={cardH}\n rx={3}\n fill=\"var(--color-surface-panel)\"\n stroke=\"var(--color-border-strong)\"\n />\n {named && (\n <text\n x={cardX + PAD_PX}\n y={cardY + PAD_PX + 10}\n fill=\"var(--color-text-muted)\"\n fontSize={10}\n >\n {shortened(heading, headingChars)}\n </text>\n )}\n {rows.map((row, i) => {\n const lines = stacked ? 2 : 1;\n const labelBaseline =\n cardY + PAD_PX + (i * lines + headRows) * ROW_PX + 10;\n // A stacked card gives the figure the line under the label.\n const baseline = labelBaseline + (stacked ? ROW_PX : 0);\n return (\n <g key={row.id} data-plot-crosshair-row={row.id}>\n {!full && (\n <rect\n x={cardX + PAD_PX}\n y={labelBaseline - 8}\n width={SWATCH_PX}\n height={SWATCH_PX}\n fill={row.color}\n data-plot-crosshair-swatch=\"\"\n />\n )}\n <text\n x={textX(cardX)}\n y={labelBaseline}\n fill={\n row.value === null || stacked\n ? \"var(--color-text-faint)\"\n : row.color\n }\n fontSize={10}\n style={{ whiteSpace: \"pre\" }}\n >\n {text(row)}\n </text>\n {stacked && (\n <text\n x={textX(cardX)}\n y={baseline}\n fill={\n row.value === null ? \"var(--color-text-faint)\" : row.color\n }\n fontSize={10}\n style={{ whiteSpace: \"pre\" }}\n >\n {valueText(row)}\n </text>\n )}\n {row.modelled && (\n <ReckoningMarkSvg\n kind=\"modelled\"\n x={\n cardX +\n cardW -\n PAD_PX -\n currencyText(row).length * CHAR_PX -\n (currencyText(row) ? 8 : 4)\n }\n y={baseline - 3}\n />\n )}\n {currencyText(row) && (\n <text\n x={cardX + cardW - PAD_PX}\n y={baseline}\n textAnchor=\"end\"\n fill=\"var(--color-text-faint)\"\n fontSize={10}\n >\n {currencyText(row)}\n </text>\n )}\n </g>\n );\n })}\n </g>\n )}\n </g>\n );\n}\n","/**\n * What {@link placePlotReadouts} reads: the chart's measured size.\n *\n * @category LineGraph\n */\nexport interface PlotReadoutSpace {\n /** Width of the whole chart, in CSS pixels. */\n width: number;\n /** Height of the whole chart, in CSS pixels. */\n height: number;\n /** Width the column's widest line needs to be read whole: a legend chip, a caption, a card row, in CSS pixels. */\n contentWidth: number;\n}\n\n/**\n * Where a chart's reading furniture stands: the crosshair card, the legend and\n * the limit or target captions.\n *\n * `beside` gives them a column to the right of the plot, so none covers a\n * trace or a dot. `overlay` keeps them inside the plot, drawn only where they\n * cover no data.\n *\n * @category LineGraph\n */\nexport type PlotReadoutPlacement =\n | { placement: \"overlay\" }\n | { placement: \"beside\"; columnWidth: number };\n\n/** Narrowest chart that still leaves the plot a usable width beside a column. */\nconst BESIDE_MIN_WIDTH = 480;\n\n/** Shortest chart whose column can hold a card of a few rows. */\nconst BESIDE_MIN_HEIGHT = 110;\n\nconst COLUMN_MIN_PX = 112;\nconst COLUMN_MAX_PX = 260;\n/** The column never takes more than this share of the chart. */\nconst COLUMN_MAX_SHARE = 0.4;\n/** What the chart must keep for the plot, its axes and its margins once the column is taken. */\nconst PLOT_KEEPS_PX = 300;\n\n/**\n * Where the kit puts a chart's card, legend and captions, decided from\n * measured space. A chart with room gives them a column beside the plot, as\n * wide as its widest line needs so nothing is cut; one that cannot spare that\n * width, or is smaller, keeps them over the plot.\n *\n * @category LineGraph\n */\nexport function placePlotReadouts({\n width,\n height,\n contentWidth,\n}: PlotReadoutSpace): PlotReadoutPlacement {\n const columnWidth = Math.max(COLUMN_MIN_PX, Math.ceil(contentWidth));\n if (\n width < BESIDE_MIN_WIDTH ||\n height < BESIDE_MIN_HEIGHT ||\n columnWidth > Math.min(COLUMN_MAX_PX, width * COLUMN_MAX_SHARE) ||\n width - columnWidth < PLOT_KEEPS_PX\n ) {\n return { placement: \"overlay\" };\n }\n return { placement: \"beside\", columnWidth };\n}\n","import {\n type ReadFrameChoice,\n readFrameChoicesEqual,\n} from \"@ksp-gonogo/sitrep-sdk/frames\";\nimport { Field, FieldHint, FieldLabel, Select } from \"./Form\";\n\n/**\n * One frame a {@link ReadFrameControl} can offer, and what an operator calls it.\n *\n * @category Form\n */\nexport interface ReadFrameOption {\n /** The frame this option selects. */\n choice: ReadFrameChoice;\n /** The option's text in the list, as the operator reads it. */\n label: string;\n}\n\n/**\n * Props for {@link ReadFrameControl}.\n *\n * @category Form\n */\nexport interface ReadFrameControlProps {\n /** Put on the `<select>`, so `label` (as `htmlFor`) names it. */\n id: string;\n /** The control's visible label, such as \"Frame\". */\n label: string;\n /** The widget's current read frame. Shown as a blank, disabled placeholder when it matches nothing on `options`, e.g. before the widget's catalogue has arrived. */\n value: ReadFrameChoice;\n /** The frames offered, in the order listed. */\n options: readonly ReadFrameOption[];\n /** Called with the frame the operator picked. */\n onChange: (choice: ReadFrameChoice) => void;\n /** A line of help under the control. Omit for none. */\n hint?: string;\n}\n\n/**\n * The control a frame-dependent widget picks its read frame with: a labelled,\n * keyboard-operable `<select>` over `options`, with an optional hint below. A\n * read frame is the frame of reference a widget draws positions in, such as\n * centred on a body; it changes only the drawing. The Control Frame is the\n * frame the game itself is set to, reported on the `system.frame` Topic.\n * The selected option is found by frame equality with `value`\n * (`readFrameChoicesEqual` from `@ksp-gonogo/sitrep-sdk/frames`), so options\n * need no ids of their own.\n *\n * The caller decides what belongs on `options`: which named frames apply to\n * what it is drawing, and whether \"follow the Control Frame\" is one of them.\n * Offer that entry only when the live Control Frame would draw something the\n * other entries do not already draw; `readFrameChoicesEqual` is the check.\n *\n * @category Form\n */\nexport function ReadFrameControl({\n id,\n label,\n value,\n options,\n onChange,\n hint,\n}: Readonly<ReadFrameControlProps>) {\n const selectedIndex = options.findIndex((option) =>\n readFrameChoicesEqual(option.choice, value),\n );\n\n return (\n <Field>\n <FieldLabel htmlFor={id}>{label}</FieldLabel>\n <Select\n id={id}\n value={selectedIndex === -1 ? \"\" : String(selectedIndex)}\n onChange={(event) => {\n const option = options[Number(event.target.value)];\n if (option !== undefined) onChange(option.choice);\n }}\n >\n {selectedIndex === -1 && <option value=\"\" disabled />}\n {options.map((option, index) => (\n <option\n key={`${option.choice.kind}-${option.choice.bodyIndex ?? \"none\"}`}\n value={index}\n >\n {option.label}\n </option>\n ))}\n </Select>\n {hint !== undefined && <FieldHint>{hint}</FieldHint>}\n </Field>\n );\n}\n","import type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { NullValue } from \"./NullValue\";\nimport type { UnitValue } from \"./readingCurrency\";\nimport { Unit } from \"./Unit\";\n\n/**\n * What a read-only field can be handed. `null`/`undefined` show a placeholder.\n *\n * A quantity, or the whole `Reading` it arrived in, goes through {@link Unit},\n * so a held reading keeps its held mark. Not a bare number, which has lost the\n * unit that says how to write it: hand over `value(\"m\", 1)`,\n * `value(\"count\", 3)`, or `value(\"1\", x)` for a dimensionless reading (`\"1\"`\n * is the unit of a plain ratio or factor).\n *\n * @category Form\n */\nexport type ReadOnlyFieldValue =\n | boolean\n | string\n | UnitValue\n | null\n | undefined;\n\n/**\n * Props for {@link ReadOnlyField}.\n *\n * @category Form\n */\nexport interface ReadOnlyFieldProps {\n /** What the value IS. Read first, and read every time. */\n label: ReactNode;\n /** Why it matters, or where it comes from. Announced with the label. */\n description?: ReactNode;\n /** The value shown. See {@link ReadOnlyFieldValue} for what it can be. */\n value: ReadOnlyFieldValue;\n /** A class for the outer element, so a styled-component can extend it. */\n className?: string;\n}\n\n/**\n * A labelled value the reader cannot change: data, not a disabled control,\n * which some screen readers skip and which promises it would work otherwise.\n *\n * Renders a description list, one `<dl>` per field so a lone field stays valid,\n * so a reader gets the label and value as one unit. A quantity goes through\n * {@link Unit}.\n *\n * @category Form\n */\nexport function ReadOnlyField({\n label,\n description,\n value,\n className,\n}: ReadOnlyFieldProps) {\n return (\n <ReadOnlyField__List className={className}>\n <ReadOnlyField__Term>\n <ReadOnlyField__Label>{label}</ReadOnlyField__Label>\n {description !== undefined && description !== null && (\n <ReadOnlyField__Description>{description}</ReadOnlyField__Description>\n )}\n </ReadOnlyField__Term>\n <ReadOnlyField__Value $prose={typeof value === \"string\"}>\n <ReadOnlyFieldContent value={value} />\n </ReadOnlyField__Value>\n </ReadOnlyField__List>\n );\n}\n\n/**\n * The value half on its own, for a caller that already draws its own label,\n * drawn exactly as {@link ReadOnlyField} draws it.\n *\n * @category Form\n */\nexport function ReadOnlyFieldContent({\n value,\n}: {\n value: ReadOnlyFieldValue;\n}): ReactNode {\n if (value === null || value === undefined) return <NullValue />;\n if (typeof value === \"object\") return <Unit value={value} />;\n // \"On\"/\"Off\", as the game's own settings windows say.\n if (typeof value === \"boolean\") return value ? \"On\" : \"Off\";\n return value;\n}\n\nconst ReadOnlyField__List = styled.dl`\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--gap-field-term);\n margin: 0;\n`;\n\nconst ReadOnlyField__Term = styled.dt`\n display: flex;\n flex-direction: column;\n gap: var(--gap-caption);\n min-width: 0;\n /* Grows into the space a short value leaves and yields to a long one. */\n flex: 1 1 auto;\n`;\n\n/* The same rungs a writable row's label and description take, so a mixed column reads as one list. */\nconst ReadOnlyField__Label = styled.span`\n font-size: var(--font-size-value);\n color: var(--color-text-primary);\n`;\n\nconst ReadOnlyField__Description = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-dim);\n max-width: 32em;\n`;\n\nconst ReadOnlyField__Value = styled.dd<{ $prose?: boolean }>`\n margin: 0;\n min-width: 0;\n text-align: right;\n font-size: var(--font-size-value);\n font-variant-numeric: tabular-nums;\n color: var(--color-text-primary);\n\n /* A quantity never breaks from its symbol; a sentence wraps and takes at most half the row. */\n ${({ $prose }) =>\n $prose\n ? `\n white-space: normal;\n max-width: 50%;\n text-align: left;\n `\n : \"white-space: nowrap;\"}\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { TONE_TEXT } from \"./tone\";\n\n/**\n * Where a {@link Readout} sits: `hero` fills the remaining panel space and centres, `inline` sits baseline-aligned beside other content.\n *\n * @category Readout\n */\nexport type ReadoutSize = \"hero\" | \"inline\";\n\nconst Readout__Box = styled.div<{ $size: ReadoutSize; $tone: Tone }>`\n display: ${({ $size }) => ($size === \"hero\" ? \"flex\" : \"inline-flex\")};\n font-weight: 700;\n letter-spacing: 0.04em;\n color: ${({ $tone }) => TONE_TEXT[$tone]};\n ${({ $size }) =>\n $size === \"hero\"\n ? css`\n flex: 1;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--gap-tiny-content);\n text-align: center;\n /* A fluid size off the type scale, with a line height tuned to it so descenders do not clip. */\n font-size: clamp(20px, 6vw, 38px);\n line-height: 1.05;\n min-width: 0;\n `\n : css`\n align-items: baseline;\n gap: var(--gap-value-tag);\n /* Display tier: the type scale stops at lg, so this stays literal. */\n font-size: 22px;\n `}\n`;\n\n/**\n * Props for {@link Readout}. Any other `div` attribute passes through.\n *\n * @category Readout\n */\nexport interface ReadoutProps extends ComponentPropsWithoutRef<\"div\"> {\n /** Where it sits: `hero` fills the remaining panel space, `inline` sits beside other content. Defaults to `inline`. */\n size?: ReadoutSize;\n /**\n * Colours the figure with a {@link Tone}: `go`, `caution`, `warn` or `nogo`\n * for a state from best to worst, `offline` for data that is gone, `info` for\n * a note and `neutral` for a figure with no verdict. Defaults to `neutral`.\n * Set it from the state the figure reports, never for decoration.\n */\n tone?: Tone;\n}\n\n/**\n * The display-tier figure: bold, tracked, coloured by tone.\n *\n * `size=\"hero\"` is the dominant value of a widget's tiny form (see\n * {@link TinyEssentials}; ΔV, time to impact and warp rate are examples): it fills the remaining panel space and centres. The\n * default `size=\"inline\"` is the same treatment smaller, sitting beside other\n * content.\n *\n * Pair with {@link ReadoutCaption} for an optional sub-label.\n *\n * @category Readout\n * @categoryDescription Readout\n * Figures that stand on their own: the bold display readout, a labelled stat, a\n * data line, and the stats contributed to a widget.\n */\nexport function Readout({\n size = \"inline\",\n tone = \"neutral\",\n ...rest\n}: ReadoutProps) {\n return <Readout__Box $size={size} $tone={tone} {...rest} />;\n}\n\n/**\n * Muted uppercase secondary line for a {@link Readout}\n * (e.g. a mode tag), placed inside it after the figure. It renders a `span` and\n * takes every prop that element does.\n *\n * @category Readout\n */\nexport const ReadoutCaption = styled.span`\n font-size: var(--font-size-caption);\n font-weight: 400;\n letter-spacing: 0.05em;\n color: var(--color-text-muted);\n text-transform: uppercase;\n`;\n","import type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { focusRingInset } from \"./focusRing\";\nimport { statusFill } from \"./tone\";\n\n/**\n * Props for {@link Row}. Any other HTML attribute passes through to the\n * rendered element.\n *\n * @category Layout\n */\nexport interface RowProps extends HTMLAttributes<HTMLElement> {\n /** Rendered tag. Defaults to `li` (a `Row` typically sits in a plain `<ul>`). */\n as?: ElementType;\n /**\n * Makes the row a control: pointer cursor, a hover background, and a real\n * `:focus-visible` ring. Pair it with `as=\"button\"`, which is what a\n * selectable row must be so it is reachable by keyboard.\n */\n interactive?: boolean;\n /** Current selection, for an `interactive` row. */\n selected?: boolean;\n /**\n * Button type, for `as=\"button\"`: a bare `<button>` inside a form defaults to\n * `submit`.\n */\n type?: \"button\" | \"submit\" | \"reset\";\n /** Disabled, for `as=\"button\"`. */\n disabled?: boolean;\n /**\n * Lets the badges and actions drop to a second line when they cannot share\n * one with a readable name. The {@link RowName} keeps at least 12 characters\n * of width (or the whole row, if narrower) and the rest wraps. Off by\n * default, so a list never turns ragged without asking.\n */\n wrap?: boolean;\n /**\n * Marks the row as subordinate to the one above it (an \"of which\" line under\n * a total, a child under a tree node) by indenting it on the left. There is\n * one level of nesting.\n */\n nested?: boolean;\n /** The row's content: its label and value, or whatever the row lays out. */\n children?: ReactNode;\n}\n\n/**\n * A single spaced-between list row: name on the left, badges and actions on\n * the right. The truncating name child is `RowName` (also `Row.Name`).\n */\nfunction RowBase({\n as,\n interactive = false,\n selected = false,\n wrap = false,\n nested = false,\n type,\n children,\n ...rest\n}: RowProps) {\n return (\n <Row__Root\n as={as ?? \"li\"}\n type={type ?? (as === \"button\" ? \"button\" : undefined)}\n $interactive={interactive}\n $selected={selected}\n $wrap={wrap}\n $nested={nested}\n {...rest}\n >\n {children}\n </Row__Root>\n );\n}\n\n/**\n * The name inside a {@link Row}: fills the free width and truncates with an\n * ellipsis. Also reachable as `Row.Name`.\n *\n * @category Layout\n */\nexport const RowName = styled.span`\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n min-width: 0;\n color: var(--color-text-primary);\n`;\n\n/**\n * How much of the name a wrapping row refuses to give up; the `min()` keeps it\n * from overflowing a narrower row.\n */\nconst WRAPPED_NAME_FLOOR = \"min(12ch, 100%)\";\n\nconst Row__Root = styled.li<{\n $interactive: boolean;\n $selected: boolean;\n $wrap: boolean;\n $nested: boolean;\n}>`\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: var(--gap-row);\n font-size: var(--font-size-compact);\n padding: var(--inset-row);\n ${({ $wrap }) =>\n $wrap\n ? `\n flex-wrap: wrap;\n row-gap: var(--gap-row-wrap);\n\n & > ${RowName} {\n min-width: ${WRAPPED_NAME_FLOOR};\n }\n`\n : \"\"}\n ${({ $interactive }) =>\n $interactive\n ? css`\n width: 100%;\n border: none;\n background: transparent;\n color: var(--color-text-primary);\n padding: var(--inset-row-pressable);\n border-radius: var(--radius-regular);\n cursor: pointer;\n text-align: left;\n font-family: inherit;\n\n &:hover {\n background: var(--color-surface-panel);\n }\n\n ${focusRingInset}\n`\n : \"\"}\n ${({ $interactive, $selected }) =>\n $interactive && $selected\n ? `\n ${statusFill(\"go\")}\n\n &:hover {\n background: var(--color-go-status);\n }\n`\n : \"\"}\n /* After the interactive block, whose padding shorthand would reset it. */\n ${({ $nested }) => ($nested ? \"padding-left: var(--indent-row);\" : \"\")}\n`;\n\n/**\n * One list row with its children spread between the edges: the name on the\n * left in a {@link RowName}, badges and actions on the right. Renders an `li`\n * by default, so it belongs in a plain `ul`. Set `interactive` with\n * `as=\"button\"` for a selectable row.\n *\n * @example\n * ```tsx\n * <ul>\n * {vessels.map((v) => (\n * <Row key={v.id}>\n * <Row.Name>{v.name}</Row.Name>\n * <Inline>\n * <Badge tone=\"go\">{v.situation}</Badge>\n * </Inline>\n * </Row>\n * ))}\n * </ul>\n * ```\n *\n * @category Layout\n */\nexport const Row = Object.assign(RowBase, { Name: RowName });\n","/*\n * Resource-identity colour: a deterministic name -> colour mapping, so the same\n * resource renders the same colour everywhere. Identity, never severity.\n *\n * Two tiers, normalised into the same saturation and lightness range:\n * 1. A curated substring map for known resource kinds, ordered most specific\n * first. Each family owns one hue; members of a family are told apart by\n * a lightness hashed from their full name\n * 2. A golden-angle hash fallback for anything unrecognised, steered out of\n * every curated family's hue neighbourhood\n */\n\n/** Fixed saturation shared by both tiers, tuned by eye for the dark canvas. */\nconst SATURATION_PCT = 65;\n\n/**\n * Legible lightness range every curated member and Tier 2 fallback is mapped\n * into: below the floor a fill is hard to read on the dark canvas, above the\n * ceiling it washes out against highlight chrome.\n */\nconst LIGHTNESS_MIN_PCT = 38;\nconst LIGHTNESS_MAX_PCT = 72;\nconst LIGHTNESS_RANGE_PCT = LIGHTNESS_MAX_PCT - LIGHTNESS_MIN_PCT;\n\n/** Tier 2's fixed lightness (it gets its distinctness from hue), and a single-alias family's, which has no sibling to spread from. */\nconst LIGHTNESS_MID_PCT = 55;\n\n/** Golden angle in degrees: stepping a hash by this amount spreads hues\n * maximally without ever having to track which hues are already in use. */\nconst GOLDEN_ANGLE_DEG = 137.508;\n\n/** Default half-width, in degrees, of a curated family's Tier 2 reserved zone, before it is clamped to the gap to its nearest neighbour. */\nconst RESERVED_ZONE_DEFAULT_DEG = 10;\n\n/** Minimum gap, in degrees, kept between two adjacent families' reserved zones. */\nconst RESERVED_ZONE_MARGIN_DEG = 1.5;\n\n/** Hard stop on the reserved-zone rotation loop, so a pathological `CURATED` table cannot hang it. */\nconst MAX_ROTATIONS = 64;\n\n/**\n * The reserved-zone escape step is derived from the name and mapped into this\n * range, so two unknowns escaping the same zone rotate by different amounts\n * and diverge.\n */\nconst ESCAPE_STEP_MIN_DEG = 60;\nconst ESCAPE_STEP_RANGE_DEG = 241;\n\n/**\n * Fractional dither added to the escape step so it is never a whole number: a\n * whole-number step can orbit a small set of hues that all sit in reserved\n * zones.\n */\nconst ESCAPE_STEP_DITHER = GOLDEN_ANGLE_DEG - Math.floor(GOLDEN_ANGLE_DEG);\n\n/**\n * One curated family: every alias in `aliases` maps into the same `hue`.\n */\ninterface CuratedFamily {\n aliases: readonly string[];\n hue: number;\n}\n\n/**\n * Tier 1: curated substring matches against a lower-cased name. The first\n * match wins, so families are ordered most specific first: a family whose\n * alias contains another family's alias must come before it. Extend it rather\n * than widen an existing alias.\n */\nconst CURATED: readonly CuratedFamily[] = [\n { aliases: [\"liquidfuel\"], hue: 40 }, // amber\n { aliases: [\"lqdhydrogen\", \"hydrogen\"], hue: 205 }, // pale blue\n { aliases: [\"electriccharge\", \"ec\"], hue: 50 }, // yellow\n { aliases: [\"carbondioxide\", \"co2\", \"waste\"], hue: 65 }, // olive\n { aliases: [\"monopropellant\", \"monoprop\"], hue: 95 }, // yellow-green\n { aliases: [\"oxidizer\"], hue: 15 }, // red-orange\n { aliases: [\"oxygen\", \"air\"], hue: 190 }, // cyan\n { aliases: [\"water\"], hue: 215 }, // blue\n { aliases: [\"ore\"], hue: 28 }, // tan / brown\n { aliases: [\"food\"], hue: 130 }, // green\n { aliases: [\"xenon\"], hue: 275 }, // violet\n { aliases: [\"ablator\"], hue: 5 }, // grey-orange\n { aliases: [\"nitrogen\", \"ammonia\"], hue: 175 }, // teal\n];\n\n/** Shortest angular distance between two hues on the 0..360 wheel. */\nfunction hueDistance(a: number, b: number): number {\n const diff = Math.abs(a - b) % 360;\n return diff > 180 ? 360 - diff : diff;\n}\n\n/**\n * Each family's effective Tier 2 reserved-zone radius: the default, clamped to\n * half the gap to its nearest neighbour less the margin, so zones never overlap.\n */\nconst RESERVED_RADII_DEG: readonly number[] = CURATED.map((family, index) => {\n let nearestGap = Infinity;\n for (let other = 0; other < CURATED.length; other++) {\n if (other === index) continue;\n nearestGap = Math.min(\n nearestGap,\n hueDistance(family.hue, CURATED[other].hue),\n );\n }\n const halfGap = nearestGap / 2;\n return Math.max(\n 0,\n Math.min(RESERVED_ZONE_DEFAULT_DEG, halfGap - RESERVED_ZONE_MARGIN_DEG),\n );\n});\n\n/**\n * Every curated family's resolved Tier 2 reserved zone (centre and effective\n * radius).\n */\nexport const CURATED_RESERVED_ZONES: ReadonlyArray<{\n aliases: readonly string[];\n centre: number;\n radius: number;\n}> = CURATED.map((family, index) => ({\n aliases: family.aliases,\n centre: family.hue,\n radius: RESERVED_RADII_DEG[index],\n}));\n\n/**\n * Tier 1 family lookup: `key` must already be lower-cased. First alias match\n * wins; `undefined` when no curated family matches.\n */\nfunction matchCuratedFamily(\n key: string,\n table: readonly CuratedFamily[] = CURATED,\n): CuratedFamily | undefined {\n return table.find((candidate) =>\n candidate.aliases.some((alias) => key.includes(alias)),\n );\n}\n\n/**\n * Tier 1 hue lookup against a table (not exported from the package index).\n * `key` must already be lower-cased.\n */\nexport function matchCuratedHue(\n key: string,\n table: readonly CuratedFamily[] = CURATED,\n): number | undefined {\n return matchCuratedFamily(key, table)?.hue;\n}\n\n/**\n * FNV-1a, 32-bit: a fixed arithmetic definition, so colours stay deterministic\n * across engines and runs.\n */\nfunction fnv1a(str: string): number {\n let hash = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return hash >>> 0;\n}\n\n/** Positive modulo: JS `%` keeps the sign of the dividend. */\nfunction positiveMod(value: number, modulus: number): number {\n return ((value % modulus) + modulus) % modulus;\n}\n\n/**\n * Deterministic member lightness for a full resource name, hashed with a\n * namespaced key so it is independent of Tier 2's hue.\n */\nexport function memberLightness(key: string): number {\n const hash = fnv1a(`light:${key}`);\n const fraction = hash / 0xffffffff;\n return LIGHTNESS_MIN_PCT + fraction * LIGHTNESS_RANGE_PCT;\n}\n\n/**\n * Tier 1 hue and lightness for a full resource name, or `undefined` when no\n * curated family matches. A single-alias family sits at the mid lightness; a\n * multi-alias family spreads each member to its own hashed lightness.\n */\nexport function placedColor(\n key: string,\n): { hue: number; lightness: number } | undefined {\n const family = matchCuratedFamily(key);\n if (!family) return undefined;\n const lightness =\n family.aliases.length === 1 ? LIGHTNESS_MID_PCT : memberLightness(key);\n return { hue: family.hue, lightness };\n}\n\n/**\n * Whether a hue falls inside any curated family's reserved zone.\n */\nfunction isWithinReservedZone(hue: number): boolean {\n return CURATED_RESERVED_ZONES.some(\n ({ centre, radius }) => hueDistance(hue, centre) < radius,\n );\n}\n\n/**\n * Tier 2: `hue = (stableHash(name) * goldenAngle) mod 360`, rotated by a\n * name-derived step until it clears every reserved zone.\n */\nexport function hashHue(key: string): number {\n const hash = fnv1a(key);\n let hue = positiveMod(hash * GOLDEN_ANGLE_DEG, 360);\n // Upper bits of the hash, decoupled from the initial hue.\n const escapeStep =\n ESCAPE_STEP_MIN_DEG +\n ((hash >>> 8) % ESCAPE_STEP_RANGE_DEG) +\n ESCAPE_STEP_DITHER;\n let rotations = 0;\n while (isWithinReservedZone(hue) && rotations < MAX_ROTATIONS) {\n hue = positiveMod(hue + escapeStep, 360);\n rotations++;\n }\n return hue;\n}\n\n/**\n * Resolve a resource name to a stable, legible fill colour, as an `hsl()`\n * string. The same name always gets the same colour, case and surrounding\n * whitespace ignored. Known resources share a hue per family (fuels, for\n * instance) with a lightness that tells members apart; unrecognised names get\n * a hashed hue clear of every known family. The colour is an identity, never\n * a severity: use a tone for state.\n *\n * @example\n * ```tsx\n * <Meter label=\"LiquidFuel\" fillColor={resourceColor(\"LiquidFuel\")} value={amount} capacity={max} />\n * ```\n *\n * @category Theme\n * @categoryDescription Theme\n * The theme a widget is drawn with: its colours, borders and type, the colour\n * for each tone and resource, and whether the operator has asked for reduced\n * motion. Take colours from here, never write one by hand.\n */\nexport function resourceColor(name: string): string {\n const key = name.trim().toLowerCase();\n const placed = placedColor(key);\n const hue = placed?.hue ?? hashHue(key);\n const lightness = placed?.lightness ?? LIGHTNESS_MID_PCT;\n return `hsl(${Math.round(hue)}deg ${SATURATION_PCT}% ${Math.round(lightness)}%)`;\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\nimport { GAP_VAR, type GapToken, RADIUS_VAR } from \"./scales\";\nimport { statusFill } from \"./tone\";\n\n/**\n * Props for {@link SelectableRow}. Any other `button` attribute passes\n * through, except `type`, which is always `button`.\n *\n * @category FilterList\n */\nexport interface SelectableRowProps\n extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"type\"> {\n /**\n * Highlights the row as the active pick in a select-one list (a tracked\n * rotor, a targeted servo). Drives the go-toned fill and defaults\n * `aria-pressed`, so a caller sets the boolean once.\n */\n selected: boolean;\n /**\n * Gap between the stacked lines of content (a name line over a muted meta\n * line). Defaults to `caption`.\n */\n gap?: GapToken;\n /**\n * How the children sit. `stack` (default) is a column of lines. `split` is\n * one line whose first child takes the free width and whose other children\n * keep their own, for a name with a figure or tag trailing it.\n */\n layout?: \"stack\" | \"split\";\n /**\n * How `selected` reads. `fill` (default) floods the row in the go tone.\n * `outline` keeps the row's content on a raised ground edged in the accent,\n * for a row whose text should stay readable as it is.\n */\n selectedLook?: \"fill\" | \"outline\";\n /** What the row shows. */\n children?: ReactNode;\n}\n\n/**\n * A real `<button>` drawn as a full-width, left-aligned list row, for one\n * option in a pick-one list. It stacks its children vertically (typically a\n * name line over a muted meta line) and fills in the go tone while\n * `selected`. Text inherits the row's colour, so plain spans inside pick up\n * the selected tint. `aria-pressed` follows `selected`.\n *\n * @example\n * ```tsx\n * import { SelectableRow, Stack, Text } from \"@ksp-gonogo/ui-kit\";\n *\n * interface Servo {\n * id: string;\n * name: string;\n * group: string;\n * }\n *\n * function ServoList(props: {\n * servos: readonly Servo[];\n * selectedId: string | null;\n * select: (id: string) => void;\n * }) {\n * return (\n * <Stack gap=\"rows\">\n * {props.servos.map((s) => (\n * <SelectableRow\n * key={s.id}\n * selected={s.id === props.selectedId}\n * onClick={() => props.select(s.id)}\n * >\n * <span>{s.name}</span>\n * <Text level=\"muted\" size=\"xs\">{s.group}</Text>\n * </SelectableRow>\n * ))}\n * </Stack>\n * );\n * }\n * ```\n *\n * @category FilterList\n */\nexport function SelectableRow({\n selected,\n gap = \"caption\",\n layout = \"stack\",\n selectedLook = \"fill\",\n children,\n ...rest\n}: SelectableRowProps) {\n return (\n <SelectableRow__Root\n type=\"button\"\n aria-pressed={rest[\"aria-expanded\"] === undefined ? selected : undefined}\n $selected={selected}\n $gap={gap}\n $layout={layout}\n $look={selectedLook}\n {...rest}\n >\n {children}\n </SelectableRow__Root>\n );\n}\n\nconst SelectableRow__Root = styled.button<{\n $selected: boolean;\n $gap: GapToken;\n $layout: \"stack\" | \"split\";\n $look: \"fill\" | \"outline\";\n}>`\n display: flex;\n ${({ $layout }) =>\n $layout === \"split\"\n ? css`\n justify-content: space-between;\n align-items: baseline;\n > :first-child {\n flex: 1;\n min-width: 0;\n }\n `\n : css`\n flex-direction: column;\n `}\n gap: ${({ $gap }) => GAP_VAR[$gap]};\n width: 100%;\n text-align: left;\n padding: var(--inset-selectable-row);\n border-radius: ${RADIUS_VAR.regular};\n border: 1px solid\n ${({ $selected, $look }) =>\n !$selected\n ? \"var(--color-border-subtle)\"\n : $look === \"outline\"\n ? \"var(--color-accent-fg)\"\n : \"transparent\"};\n ${({ $selected, $look }) =>\n !$selected\n ? \"background: transparent; color: inherit;\"\n : $look === \"outline\"\n ? \"background: var(--color-surface-raised); color: inherit;\"\n : statusFill(\"go\")}\n cursor: pointer;\n font-family: inherit;\n\n ${({ $selected }) =>\n $selected\n ? \"\"\n : css`\n @media (hover: hover) {\n &:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n border-color: var(--color-border-strong);\n }\n }\n `}\n\n &:disabled,\n &[aria-disabled=\"true\"] {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n ${focusRing}\n`;\n","import { forwardRef, type InputHTMLAttributes } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * A native range input's attributes, less the `type`, which a slider always\n * is.\n *\n * @category Form\n */\nexport type SliderProps = Omit<InputHTMLAttributes<HTMLInputElement>, \"type\">;\n\n/**\n * A native range slider in the kit's accent, for a value set by feel along a\n * fixed span (a throttle, a gain). It fills the width it is given; the value\n * it stands for is the caller's to show beside it.\n *\n * @category Form\n */\nexport const Slider = forwardRef<HTMLInputElement, SliderProps>(\n function Slider(props, ref) {\n return <Slider__Input ref={ref} type=\"range\" {...props} />;\n },\n);\n\nconst Slider__Input = styled.input`\n width: 100%;\n min-width: 0;\n margin: 0;\n background: none;\n accent-color: var(--color-accent-fg);\n cursor: pointer;\n\n ${focusRing}\n\n &:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n`;\n","import type { Tone } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { TONE_TEXT } from \"./tone\";\n\n/**\n * Props for {@link Stat}.\n *\n * @category Readout\n */\nexport interface StatProps\n extends Omit<HTMLAttributes<HTMLDListElement>, \"title\"> {\n /** The heading over the figure. Also the figure's accessible label. */\n label: ReactNode;\n /** The figure. Write a quantity through {@link Unit}. */\n children?: ReactNode;\n /** One line under the figure, qualifying it: a rate, a horizon, a count. */\n detail?: ReactNode;\n /** How alarming the figure is. Defaults to `neutral`. */\n tone?: Tone;\n}\n\n/**\n * One cell of a stat strip, the row of key figures a widget keeps at the top of its body: a label, a figure, and at most one line under\n * it. A `<dl>` per cell, so the label is associated with the figure\n * programmatically and a cell stays valid wherever it is dropped.\n *\n * The strip is a {@link Grid} with `minColWidth=\"7rem\" fit align=\"stretch\"\n * gap=\"related-compact\"`, so every stat gets the same room and the row reflows\n * to fewer columns as the tile narrows. The strip is not a live region: add\n * `role=\"status\"` yourself only when its figures change rarely, since figures\n * that move every frame would flood a screen reader.\n *\n * @example\n * ```tsx\n * <Grid minColWidth=\"7rem\" fit align=\"stretch\" gap=\"related-compact\">\n * <Stat label=\"Next hire\" tone={affordable ? \"neutral\" : \"nogo\"}>\n * <Unit value={complex.nextHireCost} />\n * </Stat>\n * <Stat label=\"Science\" detail=\"this flight\">\n * <Unit value={career.science} />\n * </Stat>\n * </Grid>\n * ```\n *\n * @category Readout\n */\nexport function Stat({\n label,\n children,\n detail,\n tone = \"neutral\",\n ...rest\n}: StatProps) {\n return (\n <Stat__Root {...rest}>\n <Stat__Label>{label}</Stat__Label>\n <Stat__Figure $tone={tone}>{children}</Stat__Figure>\n {detail !== undefined && detail !== null && (\n <Stat__Detail>{detail}</Stat__Detail>\n )}\n </Stat__Root>\n );\n}\n\nconst Stat__Root = styled.dl`\n display: flex;\n flex-direction: column;\n gap: var(--gap-caption);\n margin: 0;\n min-width: 0;\n padding: var(--inset-surface);\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-subtle);\n border-radius: var(--radius-regular);\n`;\n\nconst Stat__Label = styled.dt`\n font-size: var(--font-size-caption);\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n`;\n\nconst Stat__Figure = styled.dd<{ $tone: Tone }>`\n margin: 0;\n min-width: 0;\n /* Bottom-aligned, so figures line up across the strip even where one label wraps. */\n margin-top: auto;\n display: flex;\n align-items: baseline;\n flex-wrap: wrap;\n gap: var(--gap-figure-parts);\n font-size: var(--font-size-figure);\n font-weight: 700;\n line-height: var(--line-height-tight);\n font-variant-numeric: tabular-nums;\n color: ${({ $tone }) => TONE_TEXT[$tone]};\n`;\n\nconst Stat__Detail = styled.dd`\n margin: 0;\n min-width: 0;\n font-size: var(--font-size-compact);\n color: var(--color-text-muted);\n`;\n","import type { StatEntry } from \"@ksp-gonogo/sitrep-sdk\";\nimport { useContributionsBySlotId } from \"./contributionsRead\";\nimport { HeldFigure } from \"./HeldMark\";\nimport { NullValue } from \"./NullValue\";\nimport { heldMarking } from \"./readingCurrency\";\nimport { Stat } from \"./Stat\";\nimport { Unit } from \"./Unit\";\n\n/**\n * Props for {@link StatContributions}.\n *\n * @category Readout\n */\nexport interface StatContributionsProps {\n /**\n * The full slot id the host widget declared (`\"astronaut-complex.readouts\"`),\n * not a bare segment.\n */\n slot: string;\n}\n\n/**\n * Every stat contributed to `slot`, drawn through the kit's own {@link Stat}.\n *\n * A fragment, not a wrapper: the cells land in the host's own `Grid` of stats\n * as siblings of its built-in ones. Renders nothing when\n * nothing is contributed, so a host can place it unconditionally. An entry's\n * quantity is drawn through {@link Unit}, else its text, else the null token;\n * a held figure is drawn with the held mark either way.\n *\n * @example\n * ```tsx\n * <Grid minColWidth=\"7rem\" fit align=\"stretch\" gap=\"related-compact\">\n * <Stat label=\"Funds\"><Unit value={funds} /></Stat>\n * <StatContributions slot=\"astronaut-complex.readouts\" />\n * </Grid>\n * ```\n *\n * @category Readout\n */\nexport function StatContributions({ slot }: StatContributionsProps) {\n // The kit cannot name a host widget's slot id, so the entry type is asserted against the contract shape.\n const entries = useContributionsBySlotId(slot) as readonly StatEntry[];\n if (entries.length === 0) return null;\n\n return (\n <>\n {entries.map((entry) => (\n <Stat\n key={entry.id}\n label={entry.label}\n detail={entry.detail}\n tone={entry.tone ?? \"neutral\"}\n >\n <StatFigure entry={entry} />\n </Stat>\n ))}\n </>\n );\n}\n\n/**\n * The figure: a quantity through `Unit`, else the entry's own text. An entry\n * carrying neither draws the null token rather than a blank cell.\n */\nexport function StatFigure({ entry }: { entry: StatEntry }) {\n // A `null` value still goes through `Unit`, which draws the null token itself.\n if (entry.value !== undefined) return <Unit value={entry.value} />;\n if (entry.text === undefined) return <NullValue />;\n const marking = heldMarking(entry.held);\n if (marking === null) return <>{entry.text}</>;\n return (\n <HeldFigure kind={marking.kind} caption={marking.caption}>\n {entry.text}\n </HeldFigure>\n );\n}\n","import { useCallback, useId } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\n\n/**\n * Props for {@link Stepper}.\n *\n * @category Form\n */\nexport interface StepperProps<Option> {\n /**\n * The values this control may hold, in the order it steps through them.\n * Ascending is the convention, because the increment control is drawn on the\n * right and reads as \"more\".\n */\n options: readonly Option[];\n /** The value now held. One not present in `options` reads as off the set:\n * both step controls stay live and stepping lands on the nearest end. */\n value: Option;\n /** Called with the option a step lands on. */\n onChange: (value: Option) => void;\n /** Names the quantity for a screen reader, and both step controls take their\n * own names from it. */\n label: string;\n /** How a value reads. Defaults to `String`, which suits a plain integer; for\n * a member carrying a unit, write it with {@link writeQuantity}. */\n format?: (value: Option) => string;\n /** Shows the value with both step controls off. */\n disabled?: boolean;\n /** Sits under the value, for the sentence that explains what the setting\n * costs. */\n children?: React.ReactNode;\n /** A test id for the element holding the value and both step controls. */\n \"data-testid\"?: string;\n}\n\n/**\n * A stepper over a FIXED ORDERED SET: eight step counts, four tolerances, five\n * quality levels. For a free quantity with a unit use {@link UnitInput}; for\n * one tuned by feel use {@link JogWheel}.\n *\n * Rendered as the ARIA spinbutton pattern. `aria-valuenow` carries the INDEX\n * rather than the value, because the members need not be evenly spaced;\n * `aria-valuetext` carries the formatted member, which is what gets announced.\n * Up and right step forward, down and left step back, Home and End go to the\n * ends.\n *\n * @category Form\n */\nexport function Stepper<Option>({\n options,\n value,\n onChange,\n label,\n format = String,\n disabled = false,\n children,\n \"data-testid\": testId,\n}: StepperProps<Option>) {\n const describedBy = useId();\n const index = options.indexOf(value);\n const atStart = index <= 0;\n const atEnd = index >= options.length - 1;\n\n const step = useCallback(\n (to: number) => {\n if (disabled || options.length === 0) {\n return;\n }\n const clamped = Math.min(Math.max(to, 0), options.length - 1);\n const next = options[clamped];\n if (next !== value) {\n onChange(next);\n }\n },\n [disabled, onChange, options, value],\n );\n\n // A held value outside the set steps to an end, the only answer that needs no guess at the neighbour.\n const back = () => step(index < 0 ? 0 : index - 1);\n const forward = () => step(index < 0 ? options.length - 1 : index + 1);\n\n return (\n <Stepper__Body data-testid={testId}>\n <Stepper__Step\n type=\"button\"\n aria-label={`Decrease ${label}`}\n onClick={back}\n disabled={disabled || (index >= 0 && atStart)}\n >\n −\n </Stepper__Step>\n <Stepper__Value\n role=\"spinbutton\"\n tabIndex={disabled ? -1 : 0}\n aria-label={label}\n aria-valuenow={index < 0 ? undefined : index}\n aria-valuemin={0}\n aria-valuemax={Math.max(options.length - 1, 0)}\n aria-valuetext={format(value)}\n aria-disabled={disabled || undefined}\n aria-describedby={children == null ? undefined : describedBy}\n onKeyDown={(event) => {\n switch (event.key) {\n case \"ArrowUp\":\n case \"ArrowRight\":\n event.preventDefault();\n forward();\n break;\n case \"ArrowDown\":\n case \"ArrowLeft\":\n event.preventDefault();\n back();\n break;\n case \"Home\":\n event.preventDefault();\n step(0);\n break;\n case \"End\":\n event.preventDefault();\n step(options.length - 1);\n break;\n default:\n break;\n }\n }}\n >\n {format(value)}\n </Stepper__Value>\n <Stepper__Step\n type=\"button\"\n aria-label={`Increase ${label}`}\n onClick={forward}\n disabled={disabled || (index >= 0 && atEnd)}\n >\n +\n </Stepper__Step>\n {children != null && (\n <Stepper__Note id={describedBy}>{children}</Stepper__Note>\n )}\n </Stepper__Body>\n );\n}\n\nconst Stepper__Body = styled.div`\n display: inline-grid;\n grid-template-columns: auto 1fr auto;\n align-items: center;\n gap: var(--gap-stepper);\n`;\n\nconst Stepper__Step = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--size-stepper-button);\n font-family: inherit;\n font-size: var(--font-size-compact);\n font-weight: 600;\n line-height: var(--line-height-flush);\n padding: var(--inset-stepper);\n border-radius: var(--radius-regular);\n cursor: pointer;\n background: var(--color-surface-raised);\n border: 1px solid var(--color-border-subtle);\n color: var(--color-text-muted);\n\n @media (hover: hover) {\n &:hover:not(:disabled) {\n border-color: var(--color-text-faint);\n color: var(--color-text-primary);\n }\n }\n\n ${focusRing}\n\n &:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n @media (pointer: coarse) {\n min-height: 44px;\n min-width: 44px;\n }\n`;\n\nconst Stepper__Value = styled.div`\n text-align: center;\n font-family: var(--font-family-mono);\n font-size: var(--font-size-value);\n font-variant-numeric: tabular-nums;\n color: var(--color-text-primary);\n padding: var(--inset-stepper);\n border-radius: var(--radius-regular);\n border: 1px solid var(--color-border-subtle);\n background: var(--color-surface-sunken);\n\n ${focusRing}\n\n &[aria-disabled=\"true\"] {\n opacity: 0.4;\n }\n`;\n\nconst Stepper__Note = styled.div`\n grid-column: 1 / -1;\n font-size: var(--font-size-compact);\n color: var(--color-text-muted);\n`;\n","import type { StreamStatusValue } from \"@ksp-gonogo/sitrep-sdk\";\nimport { Badge } from \"./Badge\";\nimport { LiveRegion } from \"./LiveRegion\";\nimport { severityFromStreamStatus } from \"./status/severity\";\nimport { formatStreamStatus } from \"./status/streamStatusWord\";\n\n/**\n * Props for {@link StreamStatusBadge}.\n *\n * @category Badge\n */\nexport interface StreamStatusBadgeProps {\n /** The stream status to show. */\n status: StreamStatusValue;\n}\n\n/**\n * A small `sm` {@link Badge} for a stream status: the status's word, as\n * {@link formatStreamStatus} writes it, in its severity, or nothing while the\n * status is `live`. It sits inside a polite live region that stays\n * mounted while empty, so the first change away from `live` is announced.\n *\n * The dashboard sets `recorded` and `last-before-blackout` itself from a\n * widget's declared channels and shows them in the panel header, so a widget\n * does not draw this for those. Use it for a status that is not the\n * panel's own (a sub-region reading a different Topic), or pass a status to\n * {@link Panel}'s `panelStatus` prop.\n *\n * @category Badge\n */\nexport function StreamStatusBadge({ status }: StreamStatusBadgeProps) {\n const label = formatStreamStatus(status);\n return (\n <LiveRegion>\n {label !== null && (\n <Badge tone={severityFromStreamStatus(status)} size=\"sm\">\n {label}\n </Badge>\n )}\n </LiveRegion>\n );\n}\n","import type {\n AugmentDefinition,\n ComponentDefinition,\n ContributionDefinition,\n SizeDelta,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport {\n getContributionsForSlot,\n onContributionsChange,\n} from \"@ksp-gonogo/sitrep-sdk/spine\";\nimport { useCallback, useEffect, useReducer } from \"react\";\nimport { getAugmentsForSlot, onAugmentsChange } from \"./augments\";\nimport { COMPONENT_SLOT_SEGMENTS } from \"./contributionsRead\";\nimport { useDomainAvailabilityStore } from \"./domainAvailability\";\n\n/** What a widget's definition says about its slots, which is all {@link sizeDeltaFor} reads. */\ntype SizedWidget = Pick<\n ComponentDefinition,\n \"id\" | \"augmentSlots\" | \"contributionSlots\"\n>;\n\n/**\n * The room, in grid units, that the extensions rendering in a widget ask of it:\n * the `sizeDelta` of every augment bound to a slot the widget declares in\n * `augmentSlots`, and of every contribution in the winning priority band of a\n * slot it declares in `contributionSlots` or carries as `badges`, `filters` or\n * `meters`, summed.\n *\n * An extension counts only while it renders, so one whose `requires` Domain is\n * not present adds nothing. A contribution in an absent Domain still holds its\n * band, as it does when rendering, so a lower band stays out. Per-instance\n * augment settings are not read: the delta is a property of the widget.\n *\n * @category Extensions\n */\nexport function sizeDeltaFor(\n def: SizedWidget,\n isDomainPresent: (domain: string) => boolean,\n): { w: number; h: number } {\n const total = { w: 0, h: 0 };\n const add = (delta: SizeDelta | undefined) => {\n total.w += delta?.w ?? 0;\n total.h += delta?.h ?? 0;\n };\n const renders = (e: Pick<AugmentDefinition, \"requires\">) =>\n e.requires === undefined || isDomainPresent(e.requires);\n\n for (const slot of new Set(def.augmentSlots ?? [])) {\n for (const augment of getAugmentsForSlot(slot)) {\n if (renders(augment)) add(augment.sizeDelta);\n }\n }\n const contributionSlots = new Set<string>([\n ...(def.contributionSlots ?? []),\n ...COMPONENT_SLOT_SEGMENTS.map((segment) => `${def.id}.${segment}`),\n ]);\n for (const slot of contributionSlots) {\n for (const contribution of getContributionsForSlot(slot)) {\n if (renders(contribution as Pick<ContributionDefinition, \"requires\">)) {\n add(contribution.sizeDelta);\n }\n }\n }\n return total;\n}\n\n/**\n * The definition with its `minSize` and `defaultSize` grown by `delta`: a\n * missing `minSize` counts from 1x1 and a missing `defaultSize` from the\n * dashboard's 3x3. The definition itself, unchanged, for a zero delta, so a\n * widget nothing extends keeps one identity.\n *\n * @category Extensions\n */\nexport function withSizeDelta<\n Def extends Pick<ComponentDefinition, \"minSize\" | \"defaultSize\">,\n>(def: Def, delta: { w: number; h: number }): Def {\n if (delta.w === 0 && delta.h === 0) return def;\n const grow = (base: { w: number; h: number }) => ({\n w: base.w + delta.w,\n h: base.h + delta.h,\n });\n return {\n ...def,\n minSize: grow(def.minSize ?? { w: 1, h: 1 }),\n defaultSize: grow(def.defaultSize ?? { w: 3, h: 3 }),\n };\n}\n\n/**\n * A function reading {@link sizeDeltaFor} against the live Domain presence of\n * the nearest store, whose identity changes whenever an augment or a\n * contribution registers or a Domain's presence changes, so a memo keyed on it\n * recomputes exactly then. Outside a store every Domain reads as absent.\n *\n * @category Extensions\n */\nexport function useSizeDeltaFor(): (def: SizedWidget) => {\n w: number;\n h: number;\n} {\n const store = useDomainAvailabilityStore();\n const [version, bump] = useReducer((n: number) => n + 1, 0);\n useEffect(() => {\n const stops = [\n onAugmentsChange(bump),\n onContributionsChange(bump),\n store?.subscribe(bump),\n ];\n return () => {\n for (const stop of stops) stop?.();\n };\n }, [store]);\n // `version` is the dependency that makes this a new function on a change.\n // biome-ignore lint/correctness/useExhaustiveDependencies: see above\n return useCallback(\n (def: SizedWidget) =>\n sizeDeltaFor(def, (domain) => store?.isAvailable(domain) ?? false),\n [store, version],\n );\n}\n","import type { KeyboardEvent, ReactNode } from \"react\";\nimport {\n useCallback,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport styled from \"styled-components\";\nimport { focusRingInset } from \"./focusRing\";\nimport { Grid } from \"./Grid\";\nimport { InlineOverflowGlow, useInlineOverflow } from \"./inlineOverflow\";\nimport { Section, SectionTitle } from \"./Section\";\nimport { useElementSize } from \"./useElementSize\";\nimport { VisuallyHidden } from \"./VisuallyHidden\";\n\n/**\n * One tab passed to {@link Tabs}: its label and the content shown when it is selected.\n *\n * @category Tabs\n */\nexport interface TabDescriptor {\n /**\n * Stable identity for the tab, the value `activeId` and `onChange` carry.\n * Falls back to `tab-` and the tab's position in the array (`tab-0`), which\n * moves if the tabs are reordered, so give every tab an `id` when the\n * selection is controlled.\n */\n id?: string;\n /** The tab's name in the strip, and its panel's accessible name. */\n label: string;\n /** What the tab's panel shows while it is selected. */\n content: ReactNode;\n /** Shows an attention dot beside the label, for a tab whose subsystem needs attention. */\n indicator?: boolean;\n /**\n * The tab's subsystem does not apply right now (nothing flying, no target).\n * It cannot be selected, roving navigation steps over it, and an active tab\n * that turns off falls through to the first tab that still applies. Prefer it\n * to an empty or dimmed panel.\n */\n disabled?: boolean;\n}\n\n/**\n * Props for {@link Tabs}.\n *\n * @category Tabs\n */\nexport interface TabsProps {\n /** The tabs, in strip order. */\n tabs: TabDescriptor[];\n /** The selected tab's id, for controlled selection. Omit it to let the component hold the selection, starting on the first tab. */\n activeId?: string;\n /** Called with the id of the tab the operator selects. */\n onChange?: (id: string) => void;\n /**\n * Lay every panel out side by side, each under its own label, once the\n * container is wide enough for a legible column each; below that, a tablist\n * and one panel. Opt-in, default `false`.\n */\n expandWhenRoomy?: boolean;\n /**\n * Accessible name for the tab strip itself, needed once a screen has more\n * than one. Ignored by the side-by-side layout, which has no strip.\n */\n \"aria-label\"?: string;\n /** As `aria-label`, when the name is already on screen as an element. */\n \"aria-labelledby\"?: string;\n /** A class for the outer element, so a styled-component can extend it. */\n className?: string;\n}\n\n/**\n * The minimum width in pixels each panel needs before {@link Tabs} with `expandWhenRoomy` lays them side by side.\n *\n * @category Tabs\n */\nexport const TABS_PANEL_MIN_WIDTH = 240;\n\n/** Gap between side-by-side panels: `Grid`'s `md` token as a literal, so the width check needs no theme. */\nconst TABS_PANEL_GAP = 8;\n\n/**\n * True when a container `containerWidth` pixels wide fits `panelCount` panels side by side at {@link TABS_PANEL_MIN_WIDTH} each, with an 8px gap between neighbours: two panels need 2 × 240 + 8 = 488px. Always false for fewer than two panels.\n *\n * @category Tabs\n */\nexport function shouldExpandTabs(\n containerWidth: number,\n panelCount: number,\n): boolean {\n if (containerWidth <= 0 || panelCount < 2) return false;\n const needed =\n panelCount * TABS_PANEL_MIN_WIDTH + (panelCount - 1) * TABS_PANEL_GAP;\n return containerWidth >= needed;\n}\n\n/**\n * A tab strip with one panel shown below it, following the\n * [WAI-ARIA Authoring Practices (APG) tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/):\n * arrow keys move between tabs, Home and End jump to the ends, and disabled\n * tabs are skipped. The strip tightens its spacing, then scrolls, when the\n * labels do not fit. With `expandWhenRoomy`, a container wide enough for every\n * enabled tab drops the strip and shows each panel side by side under its own\n * `h3` heading.\n *\n * @example\n * ```tsx\n * // Side by side when the tile is wide enough for both, tabs otherwise.\n * <Tabs\n * aria-label=\"Vessel systems\"\n * expandWhenRoomy\n * tabs={[\n * { id: \"power\", label: \"Power\", content: <PowerView /> },\n * { id: \"comms\", label: \"Comms\", content: <CommsView /> },\n * ]}\n * />\n * ```\n *\n * @category Tabs\n * @categoryDescription Tabs\n * Tabs that switch a widget's body between views.\n */\nexport function Tabs({\n tabs,\n activeId,\n onChange,\n expandWhenRoomy = false,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n className,\n}: Readonly<TabsProps>) {\n const uid = useId();\n const resolved = useMemo(\n () => tabs.map((t, i) => ({ ...t, id: t.id ?? `tab-${i}` })),\n [tabs],\n );\n\n const isControlled = activeId !== undefined;\n const [internalActiveId, setInternalActiveId] = useState(\n () => activeId ?? resolved[0]?.id ?? \"\",\n );\n const currentId = isControlled ? (activeId as string) : internalActiveId;\n // A disabled tab is never the one on screen, even when named; if every tab is disabled, the caller's choice stands.\n const named = resolved.find((t) => t.id === currentId);\n const active =\n named && !named.disabled\n ? named\n : (resolved.find((t) => !t.disabled) ?? named ?? resolved[0]);\n\n const select = useCallback(\n (id: string) => {\n if (!isControlled) setInternalActiveId(id);\n onChange?.(id);\n },\n [isControlled, onChange],\n );\n\n // Side-by-side mode measures and renders only the tabs that apply.\n const selectable = useMemo(\n () => resolved.filter((t) => !t.disabled),\n [resolved],\n );\n\n const { ref: sizeRef, size } = useElementSize<HTMLDivElement>({ w: 0, h: 0 });\n const expanded =\n expandWhenRoomy && shouldExpandTabs(size.w, selectable.length);\n\n const buttonRefs = useRef<Map<string, HTMLButtonElement>>(new Map());\n const barRef = useRef<HTMLDivElement | null>(null);\n /*\n * Tighter padding and tracking once the tabs stop fitting, decided against the\n * row's uncompacted width. It is cached, and only written while uncompacted,\n * because re-reading it while compacted would oscillate.\n */\n const naturalWidthRef = useRef<number | null>(null);\n const [compact, setCompact] = useState(false);\n // State as well as a ref: `expanded` swaps the strip in and out of the tree, and the overflow watch must follow it.\n const [scroller, setScroller] = useState<HTMLDivElement | null>(null);\n /*\n * Memoized so its identity is stable across renders. An inline ref callback\n * is a new function every render, and React detaches then reattaches a ref\n * whose identity changed, so an inline callback that calls a state setter\n * fires that setter on every render it causes, which is the render it just\n * caused: an infinite loop, React error #185.\n */\n const scrollerRef = useCallback((el: HTMLDivElement | null) => {\n barRef.current = el;\n setScroller(el);\n }, []);\n const overflow = useInlineOverflow(scroller);\n // Measured, since labels differ in width and the blob must land exactly on the active one.\n const [blob, setBlob] = useState<{ left: number; width: number } | null>(\n null,\n );\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-measure when the tab SET changes, since that is what changes the natural width.\n useLayoutEffect(() => {\n const bar = barRef.current;\n if (!bar) return;\n const measure = () => {\n // Recorded on every uncompacted pass, so a font swap is picked up too.\n setCompact((wasCompact) => {\n if (!wasCompact) naturalWidthRef.current = bar.scrollWidth;\n const natural = naturalWidthRef.current;\n return natural == null ? false : natural > bar.clientWidth;\n });\n };\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(bar);\n return () => ro.disconnect();\n }, [expanded, resolved.length]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-measure when the active tab or the tab set changes, which is exactly what moves the blob.\n useLayoutEffect(() => {\n const bar = barRef.current;\n const button = active ? buttonRefs.current.get(active.id) : undefined;\n if (!bar || !button) {\n setBlob(null);\n return;\n }\n const measure = () => {\n const el = buttonRefs.current.get(active?.id ?? \"\");\n if (!el) return;\n // offsetLeft is relative to the track, which scrolls with the tabs, so the blob does too.\n setBlob((prev) =>\n prev && prev.left === el.offsetLeft && prev.width === el.offsetWidth\n ? prev\n : { left: el.offsetLeft, width: el.offsetWidth },\n );\n };\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(bar);\n ro.observe(button);\n return () => ro.disconnect();\n }, [active?.id, resolved.length, expanded]);\n\n /**\n * Move `step` tabs from `from`, wrapping, and keep going while the landing\n * tab is disabled. Bounded by the tab count, so a set with nothing\n * selectable does not move.\n */\n const activateByIndex = useCallback(\n (from: number, step: number) => {\n const n = resolved.length;\n for (let i = 1; i <= n; i++) {\n const next = resolved[(((from + step * i) % n) + n) % n];\n if (!next || next.disabled) continue;\n select(next.id);\n buttonRefs.current.get(next.id)?.focus();\n return;\n }\n },\n [resolved, select],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLButtonElement>) => {\n const currentIdx = resolved.findIndex((t) => t.id === active?.id);\n if (currentIdx < 0) return;\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowDown\":\n e.preventDefault();\n activateByIndex(currentIdx, 1);\n break;\n case \"ArrowLeft\":\n case \"ArrowUp\":\n e.preventDefault();\n activateByIndex(currentIdx, -1);\n break;\n // Home/End start one step outside the strip, so the search lands on the end tab and walks inward past disabled ones.\n case \"Home\":\n e.preventDefault();\n activateByIndex(-1, 1);\n break;\n case \"End\":\n e.preventDefault();\n activateByIndex(resolved.length, -1);\n break;\n }\n },\n [resolved, active?.id, activateByIndex],\n );\n\n if (expanded) {\n return (\n <Tabs__Root ref={sizeRef} data-tabs-root=\"\" className={className}>\n <Grid\n minColWidth={`${TABS_PANEL_MIN_WIDTH}px`}\n align=\"start\"\n gap=\"related-comfortable\"\n >\n {selectable.map((tab) => (\n <Section key={tab.id}>\n <SectionTitle as=\"h3\" $rule>\n {tab.label}\n {tab.indicator && (\n <>\n <Tabs__Dot aria-hidden=\"true\" />\n <VisuallyHidden>, needs attention</VisuallyHidden>\n </>\n )}\n </SectionTitle>\n {tab.content}\n </Section>\n ))}\n </Grid>\n </Tabs__Root>\n );\n }\n\n return (\n <Tabs__Root ref={sizeRef} data-tabs-root=\"\" className={className}>\n <Tabs__BarShell>\n <Tabs__Scroller ref={scrollerRef}>\n <Tabs__Bar\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n >\n {blob && (\n <Tabs__Blob\n aria-hidden=\"true\"\n style={{ left: blob.left, width: blob.width }}\n />\n )}\n {resolved.map((tab) => {\n const isActive = tab.id === active?.id;\n return (\n <Tabs__Button\n key={tab.id}\n ref={(el) => {\n if (el) buttonRefs.current.set(tab.id, el);\n else buttonRefs.current.delete(tab.id);\n }}\n role=\"tab\"\n type=\"button\"\n id={`${uid}${tab.id}-tab`}\n aria-selected={isActive}\n aria-controls={`${uid}${tab.id}-panel`}\n aria-describedby={\n tab.indicator ? `${uid}${tab.id}-attention` : undefined\n }\n tabIndex={isActive ? 0 : -1}\n disabled={tab.disabled}\n $active={isActive}\n onClick={() => select(tab.id)}\n onKeyDown={handleKeyDown}\n $compact={compact}\n >\n {tab.label}\n {tab.indicator && (\n <>\n <Tabs__Dot aria-hidden=\"true\" />\n <span id={`${uid}${tab.id}-attention`} hidden>\n Needs attention\n </span>\n </>\n )}\n </Tabs__Button>\n );\n })}\n </Tabs__Bar>\n </Tabs__Scroller>\n <InlineOverflowGlow $position=\"left\" $visible={overflow.left} />\n <InlineOverflowGlow $position=\"right\" $visible={overflow.right} />\n </Tabs__BarShell>\n {active && (\n <Tabs__Panel\n role=\"tabpanel\"\n id={`${uid}${active.id}-panel`}\n aria-labelledby={`${uid}${active.id}-tab`}\n >\n {active.content}\n </Tabs__Panel>\n )}\n </Tabs__Root>\n );\n}\n\nconst Tabs__Root = styled.div`\n display: flex;\n flex-direction: column;\n gap: var(--gap-tabs-panel);\n /* Fills the panel and constrains children, so a flex:1 tab body can scroll. */\n flex: 1;\n min-height: 0;\n`;\n\n/*\n * Positioned wrapper so the overflow glows can sit over the strip's edges. It\n * reaches out to the panel's edges, so tabs scrolled out of view pass under a\n * glow sitting in the panel's gutter rather than being cut off at its padding.\n */\nconst Tabs__BarShell = styled.div`\n position: relative;\n margin-inline: calc(-1 * var(--bleed-inline));\n`;\n\n/* The scrolling strip. Its inline padding puts the track back on the content column while it rests. */\nconst Tabs__Scroller = styled.div`\n display: flex;\n padding-inline: var(--bleed-inline);\n overflow-x: auto;\n overflow-y: hidden;\n /* Native scrollbar hidden: the edge glows show scroll state. */\n scrollbar-width: none;\n -ms-overflow-style: none;\n &::-webkit-scrollbar {\n width: 0;\n height: 0;\n display: none;\n }\n`;\n\n// The track holding every tab; the blob measures and travels inside it.\nconst Tabs__Bar = styled.div`\n position: relative;\n flex: 1 0 auto;\n display: flex;\n gap: var(--gap-tab);\n background: var(--color-surface-sunken);\n border-radius: var(--radius-pill);\n padding: var(--inset-tab-track);\n flex-wrap: nowrap;\n`;\n\n// The selection blob. The buttons are `position: relative`, so DOM order alone puts the labels over it.\nconst Tabs__Blob = styled.span`\n position: absolute;\n top: var(--inset-tab-track);\n bottom: var(--inset-tab-track);\n border-radius: var(--radius-pill);\n background: var(--color-accent-bg);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);\n transition:\n left var(--duration-base) var(--ease-standard),\n width var(--duration-base) var(--ease-standard);\n\n /* With motion damped the blob simply appears on the new tab. */\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n`;\n\nconst Tabs__Dot = styled.span`\n display: inline-block;\n width: 7px;\n height: 7px;\n margin-left: var(--gap-trailing-mark);\n vertical-align: middle;\n border-radius: var(--radius-circle);\n background: var(--color-warn-mark);\n`;\n\nconst Tabs__Button = styled.button<{\n $active: boolean;\n $compact?: boolean;\n}>`\n /* Transparent: the blob behind it is the selected background. */\n background: transparent;\n border: none;\n /* Lifts the label over the blob by DOM order, no z-index involved. */\n position: relative;\n /* Tabs never shrink: a short label losing its word is worse than a long one continuing offscreen behind the overflow glow. */\n flex: 0 0 auto;\n white-space: nowrap;\n /* Inverts on the accent blob, where primary text fails contrast. */\n color: ${({ $active }) =>\n $active ? \"var(--color-text-inverse)\" : \"var(--color-text-faint)\"};\n cursor: pointer;\n font-size: var(--font-size-compact);\n font-weight: 700;\n text-transform: uppercase;\n border-radius: var(--radius-pill);\n /* Compact gives back the inset and tracking, never the label. Plain concatenation: a nested template literal breaks the parse. */\n ${({ $compact }) =>\n $compact\n ? \"padding: var(--inset-tab-compact);\" + \"letter-spacing: 0.04em;\"\n : \"padding: var(--inset-control);\" + \"letter-spacing: 0.12em;\"}\n /* The label gives under the press, the only feedback a tab gets on touch. */\n transition: transform var(--duration-fast) var(--ease-standard);\n\n &:active:not(:disabled) {\n transform: scale(0.96);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n &:active:not(:disabled) {\n transform: none;\n }\n }\n\n @media (hover: hover) {\n &:hover:not(:disabled) {\n color: var(--color-text-primary);\n }\n }\n\n /* Present but inert: legible enough to say what is missing, and the cursor says it will not respond. */\n &:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n\n ${focusRingInset}\n\n @media (pointer: coarse) {\n min-height: 44px;\n /* Compact still applies on touch: a tab scrolled half out of view is a smaller target, not a bigger one. */\n ${({ $compact }) =>\n $compact\n ? \"padding: var(--inset-tab-compact-touch);\"\n : \"padding: var(--inset-control-touch);\"}\n }\n`;\n\nconst Tabs__Panel = styled.div`\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n`;\n","import { bandIn, type Value } from \"@ksp-gonogo/sitrep-sdk\";\nimport { useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport {\n heldNameMarker,\n InstrumentBound,\n InstrumentHeldMark,\n InstrumentNoFigure,\n sayHeld,\n} from \"./instrumentCurrency\";\nimport { NULL_DISPLAY } from \"./NullValue\";\nimport {\n figureAttributes,\n resolveCurrency,\n type UnitValue,\n} from \"./readingCurrency\";\nimport { placedOnScale, standsApart } from \"./standsApart\";\nimport { useTooltip } from \"./Tooltip\";\nimport {\n type FormatsFor,\n type QuantityScale,\n quantityScale,\n speakQuantity,\n} from \"./units\";\n\n/**\n * A shaded band on a {@link Tape}'s scale, such as an ignition band.\n *\n * @category Gauge\n */\nexport interface TapeZone<Unit extends string = string> {\n /** Lower bound of the band. */\n from: Value<Unit>;\n /** Upper bound of the band. */\n to: Value<Unit>;\n /** Fill colour. Defaults to a faint warning tint. */\n color?: string;\n /** Names the band in the meter's spoken value. Never drawn: the band's colour and place say what it is. */\n label?: string;\n}\n\n/**\n * A point marker on a {@link Tape}'s scale, such as a gear-deploy altitude.\n *\n * @category Gauge\n */\nexport interface TapeMarker<Unit extends string = string> {\n /** Value at which to draw the marker. */\n value: Value<Unit>;\n /** Marker colour. Defaults to the accent foreground. */\n color?: string;\n /** Names the marker in the meter's spoken value. Never drawn, so a marker never competes with the scale for room. */\n label?: string;\n /** The low and high ends of an {@link UncertaintyBand} around `value`, drawn as the same two ticks a {@link Meter} draws. */\n bounds?: { lo: Value<Unit>; hi: Value<Unit> };\n}\n\n/**\n * Props for {@link Tape}.\n *\n * @category Gauge\n */\nexport interface TapeProps<Unit extends string = string> {\n /**\n * Which side of the track the scale labels sit on. Default \"left\" (track\n * against the right edge). \"right\" mirrors it, for a tape down a widget's left\n * edge whose numbers should face the content.\n */\n labelSide?: \"left\" | \"right\";\n /** Current value: the pointer position, or the whole reading it arrived in. */\n value: UnitValue<Unit>;\n /** Bottom of the scale. */\n min: Value<Unit>;\n /** Top of the scale. */\n max: Value<Unit>;\n /**\n * Drawn width in pixels.\n *\n * @defaultValue `92`\n */\n width?: number;\n /**\n * Drawn height in pixels.\n *\n * @defaultValue `220`\n */\n height?: number;\n /**\n * Fill the parent's height instead of using a fixed `height`, as a\n * full-height rail beside the main content. `height` is the fallback until\n * the first measurement.\n */\n fillHeight?: boolean;\n /** Interior tick spacing. Omit for no interior ticks. */\n tickStep?: Value<Unit>;\n /** Shaded bands on the scale. */\n zones?: ReadonlyArray<TapeZone<Unit>>;\n /** Point markers on the scale. */\n markers?: ReadonlyArray<TapeMarker<Unit>>;\n /** Draw a distinct ground line at this value (e.g. 0). Always drawn: off the scale it is pinned to the edge it lies beyond, with a chevron pointing out. */\n groundLine?: Value<Unit>;\n /** Draw a sea-level line at this value, a dashed twin of the ground line with the same edge-pinning. */\n seaLevel?: Value<Unit>;\n /**\n * Pin the rung the whole scale is written at. Absent, the rung is taken from\n * `max` and held for every tick, the pointer flag and the unit header, so one\n * strip never mixes \"500 m\" and \"1.0 km\".\n */\n format?: FormatsFor<Unit>;\n /** Accessible label naming what the scale measures (e.g. \"Altitude above terrain\"). */\n \"aria-label\": string;\n}\n\n/** How close, in pixels, a tick label may sit to the pointer's figure before the figure hides it: the figure is 11 px tall. */\nconst FIGURE_CLEARANCE_PX = 9;\nconst PAD_TOP = 12;\nconst PAD_BOTTOM = 12;\n// Left gutter wide enough for a 5-digit unit-less label (e.g. \"10000\").\nconst TRACK_X = 52;\nconst TRACK_W = 10;\n\n/** The most decimals a tick label is given. */\nconst MAX_TICK_DECIMALS = 4;\n\n/**\n * `scale` when it already writes every tick exactly, else the scale with the fewest decimals that does: more would change no figure. Fewer would write two 50 m ticks on a kilometre scale the same, or 1.25 km as \"1.3\".\n */\nfunction exactMarks(\n ticks: readonly number[],\n scale: QuantityScale,\n withDecimals: (decimals: number) => QuantityScale,\n): QuantityScale {\n const figures = (s: QuantityScale) =>\n ticks.map((t) => Number.parseFloat(s.mark(t).replace(/[^\\d.-]/g, \"\")));\n const exact = figures(withDecimals(MAX_TICK_DECIMALS));\n const isExact = (s: QuantityScale) =>\n figures(s).every((figure, i) => figure === exact[i]);\n if (isExact(scale)) return scale;\n for (let decimals = 0; decimals < MAX_TICK_DECIMALS; decimals++) {\n const candidate = withDecimals(decimals);\n if (isExact(candidate)) return candidate;\n }\n return withDecimals(MAX_TICK_DECIMALS);\n}\n\n/**\n * A vertical linear scale with a moving pointer, the altimeter or airspeed\n * strip instrument. A ruler of values with a pointer at the current `value`,\n * marked `zones` (e.g. an ignition band), point `markers` (e.g. a gear-deploy\n * altitude) and an optional `groundLine`.\n *\n * With no value it draws no pointer. Handed a whole `Reading`, a held figure\n * is marked as held and a model's interval puts two bounds on the scale.\n *\n * Renders as a `role=\"meter\"` on the current value (`aria-valuenow` clamped\n * into range, `aria-valuetext` carrying the written value). The SVG scale is\n * `aria-hidden`, so the widget using it provides a text summary of any zones or\n * markers an operator needs to hear.\n *\n * @category Gauge\n */\nexport function Tape<Unit extends string = string>({\n labelSide = \"left\",\n value,\n min,\n max,\n width = 92,\n height = 220,\n fillHeight = false,\n tickStep,\n zones,\n markers,\n groundLine,\n seaLevel,\n format,\n \"aria-label\": ariaLabel,\n}: Readonly<TapeProps<Unit>>) {\n // The wrapper's height is parent-driven, so measuring it has no feedback loop with the SVG sized from it.\n const wrapRef = useRef<HTMLDivElement>(null);\n const [measured, setMeasured] = useState(height);\n useEffect(() => {\n if (!fillHeight) return;\n const el = wrapRef.current;\n if (!el) return;\n const update = () => {\n const h = el.clientHeight;\n if (h > 0) setMeasured(h);\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, [fillHeight]);\n const h = fillHeight ? measured : height;\n\n const resolved = resolveCurrency(value);\n const { shown, held, caption, band } = resolved;\n const { anchor, tip } = useTooltip(caption);\n\n // Unwrapped once into pixel geometry; every figure a reader sees goes back out through the unit layer.\n const current = shown?.magnitude ?? Number.NaN;\n const axisMin = min.magnitude;\n const axisMax = max.magnitude;\n const span = axisMax - axisMin;\n const safe = Number.isFinite(current) ? current : axisMin;\n const clamped =\n span > 0 ? Math.max(axisMin, Math.min(axisMax, safe)) : axisMin;\n // No figure, no pointer: one resting at the foot of the rail would say the vessel is there.\n const hasFigure = shown != null;\n\n // One rung for the whole strip, so every mark and the head symbol agree.\n const scale = quantityScale(max, { format });\n const spoken =\n shown == null\n ? NULL_DISPLAY\n : speakQuantity(\n { magnitude: safe, unit: shown.unit },\n { format: scale.rung },\n );\n // Each mark picks its own rung, so a band's two ends are not rounded onto the same figure by the scale's.\n const said = (q: Value<Unit>): string => speakQuantity(q);\n const spokenMarks = [\n ...(markers ?? []).flatMap((m) =>\n m.label === undefined\n ? []\n : [\n m.bounds === undefined\n ? `${m.label} ${said(m.value)}`\n : `${m.label} ${said(m.value)}, between ${said(m.bounds.lo)} and ${said(m.bounds.hi)}`,\n ],\n ),\n ...(zones ?? []).flatMap((z) =>\n z.label === undefined\n ? []\n : [\n `${z.label} zone ${said(z.from.min(z.to))} to ${said(z.from.max(z.to))}`,\n ],\n ),\n ...(groundLine === undefined ? [] : [`ground ${said(groundLine)}`]),\n ...(seaLevel === undefined ? [] : [`sea level ${said(seaLevel)}`]),\n ];\n // The model's interval, narrowed to the figure's own unit: one in another kind is about something else.\n const interval =\n shown == null || band === null ? null : (bandIn(band, shown.unit) ?? null);\n\n // Floored: a squeezed `fillHeight` rail can measure under the padding, and SVG rejects a negative height.\n const usable = Math.max(0, h - PAD_TOP - PAD_BOTTOM);\n // value -> y: max at the top (y = PAD_TOP), min at the bottom.\n const yOf = (v: number): number => {\n if (!(span > 0)) return PAD_TOP + usable;\n const t = Math.max(0, Math.min(1, (v - axisMin) / span));\n return PAD_TOP + (1 - t) * usable;\n };\n\n // Zone ends are ordered in the value algebra (`min`/`max` convert first) before this unwraps them.\n const onRail = (q: Value<Unit>): number => yOf(q.magnitude);\n\n const trackTop = PAD_TOP;\n const trackBottom = PAD_TOP + usable;\n // Mirror the whole scale about the track when the labels read inboard.\n const mirrored = labelSide === \"right\";\n const trackX = mirrored ? width - TRACK_X - TRACK_W : TRACK_X;\n // The side the numeric scale is drawn on.\n const labelX = mirrored ? trackX + TRACK_W + 6 : trackX - 6;\n const labelAnchor = mirrored ? \"start\" : \"end\";\n\n // A level beyond either end sits on that edge with a chevron pointing out, so it is never lost off the rail.\n const drawLevel = (at: Value<Unit>, dashed: boolean) => {\n const y = onRail(at);\n const beyond = at.greaterThan(max) ? -1 : at.lessThan(min) ? 1 : 0;\n const x1 = trackX - 4;\n const x2 = trackX + TRACK_W + 4;\n const mid = trackX + TRACK_W / 2;\n return (\n <g data-level={dashed ? \"sea\" : \"ground\"}>\n <line\n x1={x1}\n y1={y}\n x2={x2}\n y2={y}\n stroke={\n dashed ? \"var(--color-info-mark)\" : \"var(--color-text-primary)\"\n }\n strokeWidth={dashed ? 1.5 : 2}\n strokeDasharray={dashed ? \"3 2\" : undefined}\n />\n {beyond !== 0 && (\n <polygon\n data-off-scale=\"true\"\n points={`${mid - 4},${y - beyond * 2} ${mid + 4},${y - beyond * 2} ${mid},${y + beyond * 4}`}\n fill={\n dashed ? \"var(--color-info-mark)\" : \"var(--color-text-primary)\"\n }\n />\n )}\n </g>\n );\n };\n const ticks: number[] = [];\n const step = tickStep?.magnitude ?? 0;\n if (step > 0 && span > 0) {\n // Each tick a whole number of steps, so the one at zero is zero: adding the step up from below lands a hair under it, which reads \"-0.0\".\n const firstIndex = Math.ceil(axisMin / step);\n const lastIndex = Math.floor(axisMax / step + 1e-9);\n for (let i = firstIndex; i <= lastIndex; i++)\n ticks.push(i === 0 ? 0 : i * step);\n }\n // Enough decimals that every tick is written exactly: a 50 m step written in kilometres needs two where the rung's own one gives 1.0, 1.0, 1.1.\n const tickScale = exactMarks(ticks, scale, (decimals) =>\n quantityScale(max, { format, decimals }),\n );\n\n const pointerY = yOf(clamped);\n // Compared as fractions of the rail, from the heights the marks are drawn at.\n const onScale = (y: number): number =>\n usable > 0 ? (PAD_TOP + usable - y) / usable : 0;\n const drawnInterval =\n interval !== null &&\n standsApart(\n onScale(pointerY),\n [onScale(onRail(interval.lo)), onScale(onRail(interval.hi))],\n placedOnScale(),\n )\n ? interval\n : null;\n\n return (\n <Tape__Meter\n ref={wrapRef}\n {...figureAttributes(resolved)}\n {...anchor}\n {...(hasFigure\n ? {\n role: \"meter\",\n \"aria-label\": sayHeld(ariaLabel, caption),\n ...heldNameMarker(caption),\n \"aria-valuenow\": clamped,\n \"aria-valuemin\": axisMin,\n \"aria-valuemax\": axisMax,\n \"aria-valuetext\":\n spokenMarks.length === 0\n ? spoken\n : `${spoken}; ${spokenMarks.join(\"; \")}`,\n }\n : {\n /* A meter must state a value, so a figureless rail is an image instead. */\n role: \"img\",\n \"aria-label\": sayHeld(`${ariaLabel}: ${spoken}`, caption),\n ...heldNameMarker(caption),\n })}\n style={fillHeight ? { height: \"100%\" } : undefined}\n >\n <svg\n width={width}\n height={h}\n viewBox={`0 0 ${width} ${h}`}\n aria-hidden=\"true\"\n style={\n fillHeight\n ? { display: \"block\", fontFamily: \"var(--font-family-mono)\" }\n : {\n display: \"block\",\n fontFamily: \"var(--font-family-mono)\",\n maxWidth: \"100%\",\n height: \"auto\",\n }\n }\n >\n {/* Track */}\n <rect\n x={trackX}\n y={trackTop}\n width={TRACK_W}\n height={usable}\n rx={2}\n fill=\"var(--color-surface-raised)\"\n />\n\n {/* Zones */}\n {zones?.map((z) => {\n const yHi = onRail(z.from.max(z.to));\n const yLo = onRail(z.from.min(z.to));\n const h = Math.max(0, yLo - yHi);\n return (\n <g key={`zone-${yLo}-${yHi}-${z.label ?? \"\"}`}>\n <rect\n x={trackX}\n y={yHi}\n width={TRACK_W}\n height={h}\n fill={z.color ?? \"var(--color-warn-mark)\"}\n opacity={0.55}\n />\n </g>\n );\n })}\n\n {/* Sea level under ground, so where they coincide the heavier ground line reads on top */}\n {seaLevel !== undefined && span > 0 && drawLevel(seaLevel, true)}\n {groundLine !== undefined && span > 0 && drawLevel(groundLine, false)}\n\n {/* Interior ticks and labels */}\n {ticks.map((t) => {\n const y = yOf(t);\n // The pointer's own figure is drawn at its height, so a tick label that close to it would be printed over by it.\n const underFigure =\n hasFigure &&\n Math.abs(\n y - Math.max(trackTop + 4, Math.min(trackBottom - 4, pointerY)),\n ) < FIGURE_CLEARANCE_PX;\n return (\n <g key={`tick-${t}`}>\n <line\n x1={mirrored ? trackX + TRACK_W + 4 : trackX - 4}\n y1={y}\n x2={mirrored ? trackX + TRACK_W : trackX}\n y2={y}\n stroke=\"var(--color-border-subtle)\"\n strokeWidth={1}\n />\n {!underFigure && (\n <text\n x={labelX}\n y={y}\n textAnchor={labelAnchor}\n dominantBaseline=\"middle\"\n fontSize={8}\n fill=\"var(--color-text-faint)\"\n >\n {tickScale.mark(t)}\n </text>\n )}\n </g>\n );\n })}\n\n {/* Markers */}\n {markers?.map((m) => {\n const { bounds } = m;\n const y = onRail(m.value);\n const color = m.color ?? \"var(--color-accent-fg)\";\n return (\n <g key={`marker-${y}-${m.label ?? \"\"}`} data-marker={m.label}>\n {/* Dashed and hollow, where the observed pointer is a solid bar, so a model's figure is never mistaken for a measurement */}\n <line\n x1={trackX}\n y1={y}\n x2={trackX + TRACK_W}\n y2={y}\n stroke={color}\n strokeWidth={2}\n strokeDasharray=\"2 2\"\n />\n <polygon\n points={\n mirrored\n ? `${trackX},${y} ${trackX - 5},${y - 3} ${trackX - 5},${y + 3}`\n : `${trackX + TRACK_W},${y} ${trackX + TRACK_W + 5},${y - 3} ${trackX + TRACK_W + 5},${y + 3}`\n }\n fill=\"none\"\n stroke={color}\n strokeWidth={1.5}\n />\n {bounds !== undefined &&\n ([\"lo\", \"hi\"] as const).map((end) => {\n const by = onRail(bounds[end]);\n return (\n <InstrumentBound\n key={end}\n end={end}\n x1={trackX}\n y1={by}\n x2={trackX + TRACK_W}\n y2={by}\n />\n );\n })}\n </g>\n );\n })}\n\n {/* The model's two bounds, across the rail */}\n {drawnInterval !== null &&\n ([\"lo\", \"hi\"] as const).map((end) => {\n const y = onRail(drawnInterval[end]);\n return (\n <InstrumentBound\n key={end}\n end={end}\n x1={trackX}\n y1={y}\n x2={trackX + TRACK_W}\n y2={y}\n />\n );\n })}\n\n {hasFigure && (\n <>\n <line\n x1={trackX - 6}\n y1={pointerY}\n x2={trackX + TRACK_W + 6}\n y2={pointerY}\n stroke=\"var(--color-accent-fg)\"\n strokeWidth={2}\n />\n <text\n x={mirrored ? labelX + 2 : labelX - 2}\n y={Math.max(trackTop + 4, Math.min(trackBottom - 4, pointerY))}\n textAnchor={labelAnchor}\n dominantBaseline=\"middle\"\n fontSize={11}\n fontWeight=\"bold\"\n fill=\"var(--color-accent-fg)\"\n >\n {scale.mark(safe)}\n {held && <InstrumentHeldMark size={5} />}\n </text>\n </>\n )}\n {!hasFigure && (\n <InstrumentNoFigure\n x={mirrored ? labelX + 8 : labelX - 8}\n y={trackTop + usable / 2}\n size={11}\n >\n {NULL_DISPLAY}\n </InstrumentNoFigure>\n )}\n\n {/* The scale's symbol, shown once; ticks and the flag stay symbol-less to fit the strip. */}\n {scale.symbol !== \"\" && (\n <text\n x={trackX + TRACK_W / 2}\n y={trackTop - 3}\n textAnchor=\"middle\"\n fontSize={8}\n fill=\"var(--color-text-faint)\"\n >\n {scale.symbol}\n </text>\n )}\n </svg>\n {tip}\n </Tape__Meter>\n );\n}\n\nconst Tape__Meter = styled.div`\n display: block;\n max-width: 100%;\n`;\n","import { useId } from \"react\";\nimport { Field, FieldHint, FieldLabel, Input } from \"./Form\";\nimport { Text } from \"./Text\";\n\n/**\n * Props for {@link TextField}.\n *\n * @category Form\n */\nexport interface TextFieldProps {\n /** Shown above the field and tied to it, so the field is never unlabelled. */\n label: string;\n /** The text now in the field. */\n value: string;\n /** Called with the whole new text on every edit. */\n onChange: (next: string) => void;\n /**\n * The message saying why the current text is refused, shown under the field;\n * setting it marks the field invalid. The field validates nothing itself, so\n * the caller decides what is refused (a duplicate name, a mod's naming rule).\n */\n invalid?: string;\n /** Shown faint in the empty field. Absent, the empty field shows nothing. */\n placeholder?: string;\n /** The most characters the field takes. Absent, no limit. */\n maxLength?: number;\n /** Shows the text but takes no input. */\n disabled?: boolean;\n /** A test id for the input element. */\n \"data-testid\"?: string;\n}\n\n/**\n * A single line of free text: a name somebody types.\n *\n * `TextField`, {@link UnitInput} or {@link Stepper}? The value decides:\n *\n * - `TextField` holds text whose content is the point and which nothing can\n * validate arithmetically, such as a name\n * - {@link UnitInput} holds a quantity with a unit, where any number in range\n * is legal\n * - {@link Stepper} holds one member of a small closed set, where the set is\n * the point\n *\n * The invalid message is `aria-describedby`-linked and `aria-invalid` is set\n * with it, so a screen reader hears the refusal on the field rather than only\n * seeing it beside it.\n *\n * @category Form\n */\nexport function TextField({\n label,\n value,\n onChange,\n invalid,\n placeholder,\n maxLength,\n disabled,\n \"data-testid\": testId,\n}: TextFieldProps) {\n const id = useId();\n const errorId = `${id}-invalid`;\n return (\n <Field>\n <FieldLabel htmlFor={id}>{label}</FieldLabel>\n <Input\n aria-describedby={invalid == null ? undefined : errorId}\n aria-invalid={invalid == null ? undefined : true}\n data-testid={testId}\n disabled={disabled}\n id={id}\n maxLength={maxLength}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n type=\"text\"\n value={value}\n />\n {invalid != null && (\n <FieldHint id={errorId}>\n <Text size=\"xs\" tone=\"warn\">\n {invalid}\n </Text>\n </FieldHint>\n )}\n </Field>\n );\n}\n","import {\n type OrbitTrajectory,\n TrajectoryKindLike,\n} from \"@ksp-gonogo/sitrep-sdk/spine\";\nimport { Stack } from \"./Stack\";\nimport { Text } from \"./Text\";\nimport { Tooltip } from \"./Tooltip\";\n\n/**\n * An orbit path that may not be drawn, and the reason. An orbit's elements are\n * the numbers that define its curve, and its horizon is how far ahead in game\n * time they can be trusted. The path is withheld when no horizon was stated for\n * it, the instant on screen is past its horizon, its shape (a conic drawn from\n * the elements, or an arc sampled by the provider) was not stated, the frame\n * asked for cannot be built (a body not yet received, say), or it could not be\n * sampled. It is the\n * `withheld` member of `OrbitTrajectory`, which `useOrbitTrajectory` from\n * `@ksp-gonogo/sitrep-sdk/frames` returns.\n *\n * @category EmptyState\n */\nexport type WithheldTrajectory = Extract<\n OrbitTrajectory,\n { shape: \"withheld\" }\n>;\n\n/**\n * The heading and sentence for a withheld path, for a widget that lays the\n * words out itself. Each reason has its own wording, saying what would let the\n * path be drawn. Use this rather than your own words, so every widget\n * describes a withheld path the same way.\n *\n * @category EmptyState\n */\nexport function trajectoryWithheldCopy(withheld: WithheldTrajectory): {\n heading: string;\n detail: string;\n} {\n switch (withheld.reason) {\n case \"no-horizon-stated\":\n return {\n heading: \"NO HORIZON STATED\",\n detail:\n \"Nothing says how far ahead these orbital elements can be trusted.\",\n };\n case \"past-horizon\":\n return withheld.trajectoryKind === TrajectoryKindLike.Integrated\n ? {\n heading: \"BEYOND INTEGRATION\",\n detail: \"The trajectory is not computed this far ahead yet.\",\n }\n : {\n heading: \"PAST HORIZON\",\n detail: \"These orbital elements cannot be trusted this far ahead.\",\n };\n case \"shape-not-stated\":\n return {\n heading: \"SHAPE NOT STATED\",\n detail: \"Nothing has said whether this trajectory is a conic.\",\n };\n case \"frame-unavailable\":\n return {\n heading: \"FRAME UNAVAILABLE\",\n detail:\n \"The trajectory is fine; the frame it was asked for cannot be built from the bodies known here. Pick another frame.\",\n };\n default:\n return {\n heading: \"NO PATH AVAILABLE\",\n detail: \"The integrated trajectory could not be sampled.\",\n };\n }\n}\n\n/**\n * Drawn in place of a path that is withheld: a heading and a sentence saying\n * why. It is announced as a status, not an alert, since a withheld path is\n * normal behaviour rather than a fault. `compact` shows the heading only, with\n * the sentence on hover, for a strip beside a readout or a caption.\n *\n * @category EmptyState\n */\nexport function TrajectoryWithheldNote({\n withheld,\n compact = false,\n}: Readonly<{ withheld: WithheldTrajectory; compact?: boolean }>) {\n const { heading, detail } = trajectoryWithheldCopy(withheld);\n return (\n <Tooltip text={compact ? detail : undefined} focusable>\n <Stack role=\"status\">\n <Text size=\"xs\">{heading}</Text>\n {!compact && (\n <Text level=\"muted\" size=\"xs\">\n {detail}\n </Text>\n )}\n </Stack>\n </Tooltip>\n );\n}\n","import styled from \"styled-components\";\n\n/**\n * A `span` that truncates to one line with an ellipsis, as a flex child that\n * fills the free width. The same behaviour as {@link RowName}, for a label\n * outside a {@link Row} (a grid cell, a card title). Its text is the neutral\n * text colour.\n *\n * @category Typography\n */\nexport const Truncate = styled.span`\n /* Its own theme colour rather than whatever the nearest element gives it, so no ancestor's default can reach the words. */\n color: var(--color-neutral-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n min-width: 0;\n`;\n","import {\n affineVectorUnitFor,\n type PointUnit,\n type Value,\n value,\n} from \"@ksp-gonogo/sitrep-sdk\";\nimport { useId, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { focusRing } from \"./focusRing\";\nimport { HeldFigure } from \"./HeldMark\";\nimport { JogWheel } from \"./JogWheel\";\nimport { MissionDateField, partsOfUt } from \"./MissionDateField\";\nimport { resolveCurrency, type UnitValue } from \"./readingCurrency\";\nimport { Slider } from \"./Slider\";\nimport { Stack } from \"./Stack\";\nimport { Text } from \"./Text\";\nimport type { FormatsFor } from \"./units\";\n\n/** Wide enough for `D6 04:17:36` in the caret label. */\nconst INSTANT_WHEEL_PX = 112;\n\n/**\n * Bounds for a POSITION slider, refused on a point-like unit: an instant can be\n * years out, and no range wide enough to reach it leaves useful precision. Use\n * {@link RateControl} for instants.\n *\n * @category Form\n */\nexport type SlidableRange<Unit extends string> = Unit extends PointUnit\n ? never\n : { min: number; max: number; step?: number };\n\n/**\n * A RATE wheel beside the field, on any unit. The handle's displacement is the\n * speed the value changes at, and it springs back to centre on release, so it\n * needs no bounds.\n *\n * @category Form\n */\nexport interface RateControl {\n /** One notch, in the unit a value of this kind MOVES BY: seconds for an instant, the field's own unit otherwise. */\n step: number;\n /** Notches per second at full displacement. The wheel's own default otherwise. */\n stepsPerSecond?: number;\n}\n\n/**\n * Props for {@link UnitInput}.\n *\n * @category Form\n */\nexport interface UnitInputProps<Unit extends string = string> {\n /**\n * The quantity being edited, or the whole `Reading` it arrived in. It\n * carries its own unit, exactly as {@link Unit}'s does; a held reading marks\n * the field's name and says so in it. Null or undefined shows an empty box.\n */\n value: UnitValue<NoInfer<Unit>> | null | undefined;\n /** The unit an emitted value carries, needed because there may be no value yet. */\n unit: Unit;\n /** Always a `Value`, never a number. */\n onChange: (next: Value<Unit>) => void;\n /** The control's visible name. Required: an `aria-label` alone leaves sighted operators guessing. */\n label: string;\n /**\n * Which rungs of this kind's ladder to type the value across, largest first,\n * one field per rung combining into a single value: `[\"h\", \"min\", \"s\"]` gives\n * hours, minutes and seconds that add up. Omit it for one field in `unit`.\n */\n rungs?: readonly FormatsFor<Unit>[];\n /** Supplying bounds adds a slider beside the field. See {@link SlidableRange}. */\n range?: SlidableRange<Unit>;\n /** Supplying a notch size adds a rate wheel beside the field. See {@link RateControl}. */\n rate?: RateControl;\n /** Greys the field, and any slider or wheel, out and ignores input. */\n disabled?: boolean;\n}\n\n/** How many of `unit` one `symbol` is worth, via the registry: the ladder tables do not carry time. */\nfunction worth(symbol: string, unit: string): number {\n try {\n return (value as (u: string, n: number) => Value)(symbol, 1).in(unit)\n .magnitude;\n } catch {\n // NaN rather than 1: a wrong scale would silently add a number in the wrong unit to the total.\n return Number.NaN;\n }\n}\n\n/**\n * A total broken across rung sizes, largest first. Every rung but the last\n * takes a whole number and the last takes the remainder, fraction included, so\n * the fields add back up exactly.\n */\nfunction splitAcross(total: number, sizes: readonly number[]): number[] {\n let rest = total;\n return sizes.map((size, index) => {\n if (!Number.isFinite(size) || size === 0) return 0;\n const whole =\n index === sizes.length - 1 ? rest / size : Math.trunc(rest / size);\n rest -= whole * size;\n return whole;\n });\n}\n\n/**\n * A quantity, typed: the input counterpart of {@link Unit}, built from the same\n * `Value<Unit>`, {@link FormatsFor} and unit conversions. It emits a `Value`,\n * never a number, so the unit stays attached from the keystroke to the wire\n * boundary. An unfinished edit (an emptied box, a lone minus sign) is shown but\n * never emitted.\n *\n * - One number field in `unit` by default\n * - `rungs` splits the value across several fields that add up\n * - `range` adds a slider beside the field; `rate` adds a rate wheel\n * - A point-like unit such as `ut` is entered as a game-calendar date through\n * {@link MissionDateField}, and `rungs` is ignored\n *\n * @example\n * ```tsx\n * import { value } from \"@ksp-gonogo/sitrep-sdk\";\n * import { Stack, UnitInput } from \"@ksp-gonogo/ui-kit\";\n * import { useState } from \"react\";\n *\n * function NodeEditor() {\n * const [prograde, setPrograde] = useState(value(\"m/s\", 0));\n * const [burn, setBurn] = useState(value(\"s\", 0));\n * const [nodeUt, setNodeUt] = useState(value(\"ut\", 0));\n * return (\n * <Stack>\n * <UnitInput\n * label=\"Prograde\"\n * unit=\"m/s\"\n * value={prograde}\n * onChange={setPrograde}\n * />\n * <UnitInput\n * // A duration typed as hours, minutes and seconds, emitted in seconds.\n * label=\"Burn time\"\n * unit=\"s\"\n * rungs={[\"h\", \"min\", \"s\"]}\n * value={burn}\n * onChange={setBurn}\n * />\n * <UnitInput\n * // A node time on the game calendar, nudged with a rate wheel.\n * label=\"Node UT\"\n * unit=\"ut\"\n * value={nodeUt}\n * rate={{ step: 60 }}\n * onChange={setNodeUt}\n * />\n * </Stack>\n * );\n * }\n * ```\n *\n * @category Form\n */\nexport function UnitInput<Unit extends string>({\n value: input,\n unit,\n onChange,\n label,\n rungs,\n range,\n rate,\n disabled,\n}: Readonly<UnitInputProps<Unit>>) {\n const id = useId();\n const { shown: current, held, caption } = resolveCurrency<Unit>(input);\n const name = held ? (\n <HeldFigure caption={caption}>{label}</HeldFigure>\n ) : (\n label\n );\n const bounds = range as\n | { min: number; max: number; step?: number }\n | undefined;\n // NaN, not zero, when nothing has been read: an absent value renders as an empty box.\n const magnitude = current ? current.magnitude : Number.NaN;\n // Keyed by field index (0 for the single field, the rung index in a rung row).\n const [typing, setTyping] = useState<Readonly<Record<number, Typing>>>({});\n const typed = (index: number): Typing | null => typing[index] ?? null;\n const type = (index: number, next: Typing) =>\n setTyping((held) => ({ ...held, [index]: next }));\n\n const wheel = rate ? (\n <Stack gap=\"related-packed\">\n <JogWheel\n mode=\"rate\"\n aria-label={`${label} rate`}\n value={magnitude}\n step={rate.step}\n stepsPerSecond={rate.stepsPerSecond}\n // A wheel against a value never read would dial away from an instant nobody stated.\n disabled={disabled || !Number.isFinite(magnitude)}\n format={isInstant(unit) ? caretDate : undefined}\n // A calendar instant is eleven characters, which the default box would clip.\n width={isInstant(unit) ? INSTANT_WHEEL_PX : undefined}\n onChange={(next) => onChange(value(unit, next))}\n />\n {/* A notch on an instant is an interval, so the notch unit is stated rather than inferred. */}\n <Text\n level=\"faint\"\n size=\"sm\"\n >{`${rate.step} ${movesBy(unit)} / notch`}</Text>\n </Stack>\n ) : null;\n\n if (isInstant(unit)) {\n // An instant is entered on the game calendar, which is its own rungs, so `rungs` is ignored here.\n return (\n <Control>\n {/* The date fields' group carries the name for assistive tech, so the visible one is not read twice. */}\n <GroupName aria-hidden=\"true\">{name}</GroupName>\n <Stack gap=\"related-dense\">\n <MissionDateField\n label={held && caption !== null ? `${label}, ${caption}` : label}\n value={Number.isFinite(magnitude) ? magnitude : null}\n disabled={disabled}\n // The rate wheel replaces the coarse nudge steps where there is one.\n steps={rate ? [] : undefined}\n onChange={(ut) => onChange(value(unit, ut))}\n />\n {wheel}\n </Stack>\n </Control>\n );\n }\n\n if (rungs && rungs.length > 0) {\n const sizes = rungs.map((symbol) => worth(String(symbol), unit));\n const parts = splitAcross(magnitude, sizes);\n const emit = (index: number, text: string) => {\n const amount = readNumber(text);\n if (amount === undefined) {\n // An unfinished edit is shown but never committed: an emptied box is not zero.\n type(index, { text, against: parts[index] });\n return;\n }\n const next = parts.slice();\n next[index] = amount;\n const total = next.reduce(\n (sum, each, i) =>\n Number.isFinite(sizes[i]) ? sum + each * sizes[i] : sum,\n 0,\n );\n type(index, { text, against: amount });\n onChange(value(unit, total));\n };\n\n return (\n <Control>\n <GroupName id={`${id}-label`}>{name}</GroupName>\n <RungRow role=\"group\" aria-labelledby={`${id}-label`}>\n {rungs.map((symbol, index) => (\n <RungCell key={String(symbol)}>\n <RungField\n type=\"number\"\n disabled={disabled}\n aria-label={`${label} ${String(symbol)}`}\n value={fieldText(typed(index), parts[index])}\n onChange={(event) => emit(index, event.target.value)}\n />\n <UnitSymbol aria-hidden=\"true\">{String(symbol)}</UnitSymbol>\n </RungCell>\n ))}\n </RungRow>\n {wheel}\n </Control>\n );\n }\n\n const emit = (text: string) => {\n const amount = readNumber(text);\n type(0, { text, against: amount ?? magnitude });\n if (amount !== undefined) {\n onChange(value(unit, amount));\n }\n };\n\n return (\n <Control>\n <FieldName htmlFor={id}>{name}</FieldName>\n <ValueRow>\n <SingleField\n id={id}\n type=\"number\"\n disabled={disabled}\n min={bounds?.min}\n max={bounds?.max}\n step={bounds?.step}\n value={fieldText(typed(0), magnitude)}\n onChange={(event) => emit(event.target.value)}\n />\n <UnitSymbol aria-hidden=\"true\">{unit}</UnitSymbol>\n </ValueRow>\n {bounds ? (\n <Slider\n disabled={disabled}\n aria-label={`${label} slider`}\n min={bounds.min}\n max={bounds.max}\n step={bounds.step ?? (bounds.max - bounds.min) / 100}\n // Parked at the low end while nothing has been read, rather than showing a handle at a position no value put it at.\n value={Number.isFinite(magnitude) ? magnitude : bounds.min}\n // A slider is never mid-edit, so it bypasses the typing buffer.\n onChange={(event) =>\n onChange(value(unit, readNumber(event.target.value) ?? bounds.min))\n }\n />\n ) : null}\n {wheel}\n </Control>\n );\n}\n\n/**\n * A field's in-progress text, and the magnitude it was typed against. Once the\n * value moves from anywhere else it no longer matches `against`, and the field\n * goes back to showing the value.\n */\ninterface Typing {\n text: string;\n against: number;\n}\n\n/** What a field shows: the text being typed, else the value it holds, else nothing. */\nfunction fieldText(typing: Typing | null, magnitude: number): string {\n if (typing !== null && Object.is(typing.against, magnitude)) {\n return typing.text;\n }\n return Number.isFinite(magnitude) ? String(round(magnitude)) : \"\";\n}\n\n/** The unit a value of this kind is moved by: its affine companion vector, else itself. */\nfunction movesBy(unit: string): string {\n return affineVectorUnitFor(unit) ?? unit;\n}\n\n/** True when this kind names an instant rather than an amount. */\nfunction isInstant(unit: string): boolean {\n return affineVectorUnitFor(unit) === \"s\";\n}\n\n/** An instant on the wheel's caret: day and clock only, since the fields beside it carry the year. */\nfunction caretDate(ut: number): string {\n if (!Number.isFinite(ut)) return \"\";\n const { day, hour, minute, second } = partsOfUt(ut);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return `D${day} ${pad(hour)}:${pad(minute)}:${pad(second)}`;\n}\n\n/** A typed field's number, or undefined while it holds no digit yet (empty, a lone minus). */\nfunction readNumber(text: string): number | undefined {\n const parsed = Number.parseFloat(text);\n return Number.isFinite(parsed) && /\\d/.test(text) ? parsed : undefined;\n}\n\n/** Trims float dust so a value that is rendered and typed back does not grow digits. */\nfunction round(n: number): number {\n return Number.isFinite(n) ? Math.round(n * 1e6) / 1e6 : 0;\n}\n\nconst Control = styled.div`\n display: flex;\n flex-direction: column;\n gap: var(--gap-field-label-compact);\n`;\n\nconst nameStyles = `\n font-size: var(--font-size-caption);\n color: var(--color-text-muted);\n letter-spacing: 0.06em;\n text-transform: uppercase;\n`;\n\nconst FieldName = styled.label`\n ${nameStyles}\n`;\n\nconst GroupName = styled.span`\n ${nameStyles}\n`;\n\nconst ValueRow = styled.div`\n display: grid;\n grid-template-columns: 1fr auto;\n align-items: center;\n gap: var(--gap-value-tag);\n`;\n\nconst SingleField = styled.input`\n background: var(--color-surface-panel);\n border: 1px solid var(--color-border-subtle);\n color: var(--color-text-primary);\n font-size: var(--font-size-value);\n padding: var(--inset-field-compact);\n border-radius: var(--radius-regular);\n text-align: right;\n font-variant-numeric: tabular-nums;\n min-width: 0;\n\n ${focusRing}\n`;\n\nconst UnitSymbol = styled.span`\n font-size: var(--font-size-compact);\n color: var(--color-text-faint);\n`;\n\nconst RungRow = styled.div`\n display: flex;\n gap: var(--gap-unit-parts);\n`;\n\nconst RungCell = styled.div`\n display: grid;\n grid-template-columns: 1fr auto;\n align-items: center;\n gap: var(--gap-unit-suffix);\n`;\n\nconst RungField = styled(SingleField)`\n width: 4.5em;\n`;\n","import { useEffect, useState } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\n/**\n * Whether the viewer has asked the OS to reduce motion, tracked live. For a\n * decision a stylesheet cannot reach (whether to replay a one-shot pulse, or\n * render a transition at all); a plain transition guards itself with a CSS\n * media query.\n *\n * Returns `false` when `matchMedia` is unavailable, so motion is suppressed\n * only on an explicit opt-out.\n *\n * @category Theme\n */\nexport function usePrefersReducedMotion(): boolean {\n const [reduced, setReduced] = useState(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return false;\n return window.matchMedia(QUERY).matches;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return;\n const mql = window.matchMedia(QUERY);\n const onChange = () => setReduced(mql.matches);\n onChange();\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return reduced;\n}\n","/**\n * The vessel as it is drawn on an orbit or a map. Shape and fill say how its\n * position is known, and a green outline says it is the vessel. One\n * definition, read by the SVG face and the canvas painter alike.\n */\nimport { RECKONING_MARK } from \"./reckoningMarkSpec\";\n\n/**\n * How the position a vessel is drawn at is known: `current` is a reading of\n * now, `held` is the last observation kept past its time, `modelled` is where a\n * model carries the craft to now, and `lost` is the last place of a craft that\n * has been given up on.\n *\n * @category Unit\n */\nexport type VesselMarkState = \"current\" | \"held\" | \"modelled\" | \"lost\";\n\n/** Half the held square's side, measured to the middle of its outline. */\nconst SQUARE_HALF = 0.86;\n/** The modelled triangle's corners, point up, as `[x, y]` pairs with y down. */\nconst TRIANGLE = [\n [0, -1.15],\n [1.05, 0.8],\n [-1.05, 0.8],\n] as const;\n/** The width of the outline round a held, modelled or lost shape. */\nconst OUTLINE_WIDTH = 0.3;\n\n/**\n * The vessel mark's geometry, in units of the radius it is drawn at, and its\n * hues. A current vessel is a circle in the vessel's green. A held one is a\n * square in the held hue and a modelled one a triangle, point up, in the\n * modelled hue, each outlined in the vessel's green, so the shape is never the\n * only thing that says which craft it is and the hue never the only thing that\n * says how it is known. A lost one is the held square emptied: the outline\n * alone, in the no-go hue, for a last place no longer known to be right. All\n * of them fill the same footprint.\n *\n * @category Unit\n */\nexport const VESSEL_MARK = {\n /** The vessel's green, as the theme token's name, for a canvas painter that resolves it itself. */\n cssVar: \"--color-accent-fg\",\n /** The vessel's green, as a CSS `var()` of the theme token. */\n color: \"var(--color-accent-fg)\",\n /** The vessel's green where the token cannot be read. */\n fallback: \"rgb(0 255 136)\",\n /** The hue of a lost vessel's outline. */\n lost: {\n /** The token's name. */\n cssVar: \"--color-nogo-mark\",\n /** The hue as a CSS `var()` of the token. */\n color: \"var(--color-nogo-mark)\",\n /** The hue where the token cannot be read. */\n fallback: \"rgb(255 77 77)\",\n },\n /** Half the held square's side, measured to the middle of its outline. */\n squareHalf: SQUARE_HALF,\n /** The modelled triangle's corners, point up, as `[x, y]` pairs with y down. */\n triangle: TRIANGLE,\n /** The width of the outline round a held, modelled or lost shape. */\n outlineWidth: OUTLINE_WIDTH,\n /**\n * The width of each of the keyline's two rings. A dark ring hugs the mark and\n * a light ring hugs the dark one, so the mark's edge always meets the dark\n * ring, whatever it is drawn on, and one ring or the other stands clear of\n * any ground: the light one on a dark ground, the dark one on a light ground.\n */\n keylineWidth: 0.2,\n /**\n * How far from its centre the largest shape reaches, outline included: its\n * farthest corner and half the outline round it. Two marks nearer than twice\n * this overlap.\n */\n reach:\n Math.max(\n Math.hypot(SQUARE_HALF, SQUARE_HALF),\n ...TRIANGLE.map(([x, y]) => Math.hypot(x, y)),\n ) +\n OUTLINE_WIDTH / 2,\n} as const;\n\n/** The mark is a drawing: the widget that places it says in words what the position is. */\nconst HIDDEN = { \"aria-hidden\": true } as const;\n\n/** The square's corners or the modelled triangle's, about a centre, at radius `r`. */\nfunction cornersOf(\n state: \"held\" | \"modelled\" | \"lost\",\n x: number,\n y: number,\n r: number,\n): [number, number][] {\n const half = VESSEL_MARK.squareHalf;\n const unit: readonly (readonly [number, number])[] =\n state === \"modelled\"\n ? VESSEL_MARK.triangle\n : [\n [-half, -half],\n [half, -half],\n [half, half],\n [-half, half],\n ];\n return unit.map(([px, py]) => [x + px * r, y + py * r]);\n}\n\n/** The keyline's two rings, outermost first, which is the order they are drawn in: each later one covers the inside of the one before. */\nconst KEYLINE_RINGS = [\n { ring: \"light\", color: \"rgb(250 250 250)\", widths: 2 },\n { ring: \"dark\", color: \"rgb(5 5 5)\", widths: 1 },\n] as const;\n\n/** The stroke that reaches `widths` ring widths beyond a mark's edge, half of any stroke lying inside the shape and under what is drawn over it. */\nfunction keylineStroke(\n state: VesselMarkState,\n r: number,\n widths: number,\n): number {\n const outline = state === \"current\" ? 0 : VESSEL_MARK.outlineWidth;\n return r * (outline + 2 * widths * VESSEL_MARK.keylineWidth);\n}\n\n/**\n * The props of {@link VesselMarkSvg}.\n *\n * @category Unit\n */\nexport interface VesselMarkSvgProps {\n /** The centre's x, in the diagram's own units. */\n x: number;\n /** The centre's y, in the diagram's own units, y down. */\n y: number;\n /** The radius of the current vessel's circle, in the diagram's own units. The held and modelled shapes fill the same footprint. */\n r: number;\n /** How the position is known. Absent, `\"current\"`: the green circle. */\n state?: VesselMarkState;\n /**\n * Draw the keyline round the mark: a dark ring and a light ring, so the mark\n * has an edge on any ground. For a mark that sits on a picture (a map, an\n * image) rather than a flat ground; on a flat ground the mark's own hues\n * already stand clear.\n */\n keyline?: boolean;\n}\n\n/**\n * The vessel mark as SVG shapes centred on a point, the triangle always point\n * up. Draw it outside any rotated group, so the shape does not turn with the\n * orbit. The group carries the point as its `translate`.\n *\n * @category Unit\n */\nexport function VesselMarkSvg({\n x,\n y,\n r,\n state = \"current\",\n keyline = false,\n}: Readonly<VesselMarkSvgProps>) {\n const corners =\n state === \"current\"\n ? \"\"\n : cornersOf(state, 0, 0, r)\n .map(([px, py]) => `${px},${py}`)\n .join(\" \");\n return (\n <g data-vessel-mark={state} transform={`translate(${x} ${y})`} {...HIDDEN}>\n {keyline &&\n KEYLINE_RINGS.map(({ ring, color, widths }) => {\n const border = {\n \"data-vessel-keyline\": ring,\n fill: \"none\",\n stroke: color,\n strokeWidth: keylineStroke(state, r, widths),\n strokeLinejoin: \"round\" as const,\n };\n return state === \"current\" ? (\n <circle key={ring} r={r} {...border} />\n ) : (\n <polygon key={ring} points={corners} {...border} />\n );\n })}\n {state === \"current\" ? (\n <circle r={r} fill={VESSEL_MARK.color} />\n ) : (\n <polygon\n points={corners}\n fill={state === \"lost\" ? \"none\" : RECKONING_MARK[state].color}\n stroke={state === \"lost\" ? VESSEL_MARK.lost.color : VESSEL_MARK.color}\n strokeWidth={r * VESSEL_MARK.outlineWidth}\n strokeLinejoin=\"round\"\n />\n )}\n </g>\n );\n}\n\n/** A CSS custom property as the canvas needs it: a resolved colour, since a canvas cannot read `var()`. */\nfunction resolvedColor(\n canvas: HTMLCanvasElement,\n cssVar: string,\n fallback: string,\n): string {\n const v = getComputedStyle(canvas).getPropertyValue(cssVar).trim();\n return v || fallback;\n}\n\n/** Traces a mark's silhouette as the current path. */\nfunction traceMark(\n ctx: CanvasRenderingContext2D,\n state: VesselMarkState,\n x: number,\n y: number,\n radius: number,\n): void {\n ctx.beginPath();\n if (state === \"current\") {\n ctx.arc(x, y, radius, 0, Math.PI * 2);\n return;\n }\n cornersOf(state, x, y, radius).forEach(([px, py], i) => {\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n });\n ctx.closePath();\n}\n\n/** Paints the keyline's two rings under where the mark is about to be painted. */\nfunction paintKeyline(\n ctx: CanvasRenderingContext2D,\n state: VesselMarkState,\n x: number,\n y: number,\n radius: number,\n): void {\n ctx.save();\n ctx.lineJoin = \"round\";\n for (const { color, widths } of KEYLINE_RINGS) {\n traceMark(ctx, state, x, y, radius);\n ctx.strokeStyle = color;\n ctx.lineWidth = keylineStroke(state, radius, widths);\n ctx.stroke();\n }\n ctx.restore();\n}\n\n/**\n * Options for {@link paintVesselMark} and {@link paintVesselPositions}.\n *\n * @category Unit\n */\nexport interface PaintVesselMarkOptions {\n /** Draw the keyline round the mark, a dark ring and a light ring, so it has an edge on any ground: see `VesselMarkSvgProps.keyline`. */\n keyline?: boolean;\n}\n\n/**\n * Paints the vessel mark on a canvas, centred on a point in canvas pixels, the\n * triangle always point up. `radius` is the current vessel's circle, 4 px\n * unless given.\n *\n * @category Unit\n */\nexport function paintVesselMark(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D,\n state: VesselMarkState,\n x: number,\n y: number,\n radius = 4,\n { keyline = false }: PaintVesselMarkOptions = {},\n): void {\n if (keyline) paintKeyline(ctx, state, x, y, radius);\n const green = resolvedColor(canvas, VESSEL_MARK.cssVar, VESSEL_MARK.fallback);\n ctx.save();\n traceMark(ctx, state, x, y, radius);\n if (state === \"current\") {\n ctx.fillStyle = green;\n ctx.fill();\n } else {\n if (state !== \"lost\") {\n const spec = RECKONING_MARK[state];\n ctx.fillStyle = resolvedColor(canvas, spec.cssVar, spec.fallback);\n ctx.fill();\n }\n ctx.strokeStyle =\n state === \"lost\"\n ? resolvedColor(\n canvas,\n VESSEL_MARK.lost.cssVar,\n VESSEL_MARK.lost.fallback,\n )\n : green;\n ctx.lineWidth = radius * VESSEL_MARK.outlineWidth;\n ctx.lineJoin = \"round\";\n ctx.stroke();\n }\n ctx.restore();\n}\n\n/**\n * Where a vessel is drawn, in canvas pixels, one point for each way its\n * position is known.\n *\n * @category Unit\n */\nexport type VesselPositions = Partial<\n Record<VesselMarkState, { x: number; y: number }>\n>;\n\n/**\n * Paints every position given with its own vessel mark, the one nearest to now\n * on top. A held position and a modelled one are joined by a faint dashed\n * track, so the pair reads as \"was here, modelled to be there\".\n *\n * @category Unit\n */\nexport function paintVesselPositions(\n canvas: HTMLCanvasElement,\n ctx: CanvasRenderingContext2D,\n positions: VesselPositions,\n radius = 4,\n options: PaintVesselMarkOptions = {},\n): void {\n const { held, modelled } = positions;\n if (held !== undefined && modelled !== undefined) {\n ctx.save();\n ctx.globalAlpha = 0.45;\n ctx.strokeStyle = resolvedColor(\n canvas,\n RECKONING_MARK.modelled.cssVar,\n RECKONING_MARK.modelled.fallback,\n );\n ctx.lineWidth = 1;\n ctx.setLineDash([2, 3]);\n ctx.beginPath();\n ctx.moveTo(held.x, held.y);\n ctx.lineTo(modelled.x, modelled.y);\n ctx.stroke();\n ctx.restore();\n }\n for (const state of [\"lost\", \"held\", \"current\", \"modelled\"] as const) {\n const at = positions[state];\n if (at !== undefined)\n paintVesselMark(canvas, ctx, state, at.x, at.y, radius, options);\n }\n}\n","import type { MeterEntry } from \"@ksp-gonogo/sitrep-sdk\";\nimport type { CSSProperties } from \"react\";\nimport { useContributions } from \"./contributionsRead\";\nimport { Meter, MeterStack } from \"./Meter\";\n\n/**\n * Props for {@link WidgetMeters}.\n *\n * @category Meter\n */\nexport interface WidgetMetersProps {\n /**\n * Render only the meters addressed at this row (a kerbal's name, a part id).\n * Omit in a whole-widget stack, which then shows only entries carrying no\n * `row` of their own. A row-addressed meter never appears in a stack that\n * names no row.\n */\n row?: string;\n /**\n * Inline style for the stack (an indent under a row, a max width). It sits\n * on the stack itself, so a row with no meters leaves no padding behind.\n */\n style?: CSSProperties;\n}\n\n/**\n * Every meter contributed to the mounting widget's `${componentId}.meters`\n * slot, drawn as a {@link MeterStack} of {@link Meter}s.\n *\n * Renders nothing when nothing is contributed, so a host can place it\n * unconditionally, one per row.\n *\n * An entry whose `value` is a whole `Reading` is passed to {@link Meter} as\n * is, so its band and held state are drawn the same whichever Uplink sent the\n * entry.\n *\n * @example Under each crew row, the meters Uplinks addressed to that kerbal\n * ```tsx\n * <li key={name}>\n * {name}\n * <WidgetMeters row={name} />\n * </li>\n * ```\n *\n * @category Meter\n */\nexport function WidgetMeters({ row, style }: WidgetMetersProps) {\n const entries = useContributions(\"meters\") as readonly MeterEntry[];\n const mine = entries.filter((entry) => entry.row === row);\n if (mine.length === 0) return null;\n\n return (\n <MeterStack style={style} role=\"group\" aria-label=\"meters\">\n {mine.map((entry) => (\n <Meter\n key={entry.id}\n label={entry.label}\n value={entry.value}\n tone={entry.tone}\n valueLabel={entry.valueLabel}\n />\n ))}\n </MeterStack>\n );\n}\n","import type { WidgetScope } from \"@ksp-gonogo/sitrep-sdk\";\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\n// Re-exported from the sdk so a widget merging into core and an Uplink merging into the sdk reach the one interface.\nexport type { WidgetScope, WidgetScopeRegistry } from \"@ksp-gonogo/sitrep-sdk\";\n\nconst WidgetScopeContext = createContext<{\n widget: string;\n scope: unknown;\n} | null>(null);\n\n/**\n * Publishes what this widget instance is currently focused on (the selected\n * resource, the followed body) to augments rendered in any of its slots, which\n * read it with {@link useWidgetScope}. It carries one scope value; several\n * unrelated fields belong in a slot's own props type instead.\n *\n * `widget` is the publishing widget's registered component id. It types\n * `scope` against that widget's `WidgetScopeRegistry` entry, and a reader only\n * receives the scope when it names the same widget.\n *\n * @category Extensions\n */\nexport function WidgetScopeProvider<Widget extends string>({\n widget,\n scope,\n children,\n}: {\n widget: Widget;\n scope: WidgetScope<Widget>;\n children?: ReactNode;\n}) {\n const value = useMemo(() => ({ widget, scope }), [widget, scope]);\n return (\n <WidgetScopeContext.Provider value={value}>\n {children}\n </WidgetScopeContext.Provider>\n );\n}\n\n/**\n * The current scope of the widget named by `componentId`, typed from its\n * `WidgetScopeRegistry` entry. `undefined` when that widget publishes none, or\n * when the augment is rendered outside it (in a test, say), so an augment must\n * handle `undefined`.\n *\n * @example\n * ```tsx\n * function StorageSection() {\n * const scope = useWidgetScope(\"power-systems\");\n * if (scope === undefined) return null;\n * return <ResourceDetail resource={scope.resource} />;\n * }\n * ```\n *\n * @category Extensions\n */\nexport function useWidgetScope<Widget extends string>(\n componentId: Widget,\n): WidgetScope<Widget> | undefined {\n const published = useContext(WidgetScopeContext);\n if (!published || published.widget !== componentId) return undefined;\n return published.scope as WidgetScope<Widget>;\n}\n","/**\n * The Topic-declaring fields of a widget definition that\n * {@link widgetDeclaredTopics} and {@link widgetDrawnFields} read. Any\n * `ComponentDefinition` satisfies it.\n *\n * @category Extensions\n */\nexport interface WidgetTopicDeclaration {\n channels?: readonly string[];\n optionalChannels?: readonly string[];\n channelFamilies?: readonly string[];\n optionalChannelFamilies?: readonly string[];\n channelsFromConfig?: (config: never) => readonly string[];\n fields?: readonly string[];\n dataRequirements?: readonly string[];\n}\n\n/**\n * Every concrete channel a widget declares it mounts on: its `channels`,\n * `optionalChannels` and `dataRequirements` joined, plus the Topics its\n * `channelsFromConfig` names for `config`, without duplicates. The dashboard\n * derives a widget's blackout badge from these. Empty for an undefined\n * definition.\n *\n * Families (`channelFamilies`, `optionalChannelFamilies`) are left out on\n * purpose: blackout is per craft and a family has no single craft, so see\n * {@link widgetDrawnFamilies} for them.\n *\n * @category Extensions\n */\nexport function widgetDeclaredTopics(\n def: WidgetTopicDeclaration | undefined,\n config?: unknown,\n): readonly string[] {\n if (!def) return [];\n return dedupe([\n ...(def.channels ?? []),\n ...(def.optionalChannels ?? []),\n ...(def.dataRequirements ?? []),\n ...configTopics(def, config),\n ]);\n}\n\n/**\n * Every field a widget declares it draws, as opposed to the channels it needs\n * live to render at all: its `fields` without duplicates, or, when it\n * declares none, its {@link widgetDeclaredTopics}. Topics from\n * `channelsFromConfig` are always included. The dashboard matches alarms to\n * widgets against this list.\n *\n * @category Extensions\n */\nexport function widgetDrawnFields(\n def: WidgetTopicDeclaration | undefined,\n config?: unknown,\n): readonly string[] {\n if (!def) return [];\n if (def.fields && def.fields.length > 0) {\n return dedupe([...def.fields, ...configTopics(def, config)]);\n }\n return widgetDeclaredTopics(def, config);\n}\n\n/**\n * Every family of runtime-built Topic ids a widget declares, required and\n * optional joined, without duplicates, such as `\"fleet.<vessel>.contact\"`. The\n * dashboard matches alarms against these with `topicMatchesFamily`.\n *\n * @category Extensions\n */\nexport function widgetDrawnFamilies(\n def: WidgetTopicDeclaration | undefined,\n): readonly string[] {\n if (!def) return [];\n return dedupe([\n ...(def.channelFamilies ?? []),\n ...(def.optionalChannelFamilies ?? []),\n ]);\n}\n\nfunction configTopics(\n def: WidgetTopicDeclaration,\n config: unknown,\n): readonly string[] {\n if (!def.channelsFromConfig || config === undefined) return [];\n return def.channelsFromConfig(config as never);\n}\n\nfunction dedupe(values: readonly string[]): readonly string[] {\n return [...new Set(values)];\n}\n","/**\n * Geometry for a flat map that repeats east and west: a ground track drawn as\n * one continuous line however often it crosses the antimeridian, and every\n * mark drawn in whichever copy of the world is in view.\n */\n\n/**\n * A point on the body, in degrees.\n *\n * @category Map\n */\nexport interface GroundPoint {\n /** Degrees north, from -90 at the south pole to 90 at the north. */\n lat: number;\n /** Degrees east. */\n lon: number;\n}\n\n/**\n * Break a ground track wherever it flies over a pole, where its longitude\n * flips by half a turn and a line joining the pair would run along the map's\n * top or bottom edge. Crossing the antimeridian is not a break: draw each run\n * with {@link wrapPath}, which carries it on into the next copy of the world.\n *\n * Geometric, not a tuned constant: a pair straddles a pole when the shorter\n * great-circle arc between them passes closer to it than the samples are to\n * each other. \"Passes\" means the arc's closest point to the pole lies between\n * them, which rules out a pair climbing on one leg; \"closer than each other\"\n * ties the test to the sampling, so a near miss the sampling resolves still\n * draws its jog.\n *\n * @category Map\n */\nexport function splitAtPoleCrossings<Point extends GroundPoint>(\n points: readonly Point[],\n): Point[][] {\n if (points.length === 0) return [];\n const runs: Point[][] = [[points[0]]];\n for (let i = 1; i < points.length; i++) {\n if (crossesAPole(points[i - 1], points[i])) runs.push([points[i]]);\n else runs[runs.length - 1].push(points[i]);\n }\n return runs;\n}\n\n/**\n * The copies of a projected path that cross a view of a map repeating every\n * `period` pixels across. The path is first made continuous, each point moved\n * by whole periods to lie within half a period of the one before, so a track\n * that leaves one edge of the world carries on past it rather than jumping\n * back across the map. Then it is shifted by every whole period that brings\n * some of it between `viewMin` and `viewMax`.\n *\n * Points may come from any copy of the world, such as a projection that wraps\n * longitude. The path must already be split where it crosses a pole (see\n * {@link splitAtPoleCrossings}), since half a turn of longitude is no jump a\n * continuous line can be told apart from.\n *\n * @category Map\n * @categoryDescription Map\n * Drawing on a flat map of a body that repeats east and west, such as a\n * `map-view.overlay`. A ground track is split only where it flies over a pole,\n * carried on across the antimeridian rather than drawn back across the map,\n * and drawn again in every repeat of the world in view.\n */\nexport function wrapPath<Point extends { x: number }>(\n points: readonly Point[],\n period: number,\n viewMin: number,\n viewMax: number,\n): Point[][] {\n if (points.length === 0 || !(period > 0)) return [];\n const xs: number[] = [points[0].x];\n let min = xs[0];\n let max = xs[0];\n for (let i = 1; i < points.length; i++) {\n const previous = xs[i - 1];\n const x = previous + wrapToHalfPeriod(points[i].x - previous, period);\n xs.push(x);\n if (x < min) min = x;\n if (x > max) max = x;\n }\n return repeatsInView(min, max, viewMin, viewMax, period).map((shift) =>\n points.map((point, i) => ({ ...point, x: xs[i] + shift })),\n );\n}\n\n/**\n * Every whole multiple of `period` that, added to the span `min` to `max`,\n * puts some of it between `viewMin` and `viewMax`, west to east. On a\n * map repeating every `period` pixels, these are the shifts at which to draw\n * a mark or a region so it appears in every copy of the world in view. A\n * single point is a span with `min` equal to `max`; widen the view by the\n * mark's own size so one straddling the edge is not dropped.\n *\n * @category Map\n */\nexport function repeatsInView(\n min: number,\n max: number,\n viewMin: number,\n viewMax: number,\n period: number,\n): number[] {\n if (!(period > 0) || !Number.isFinite(min) || !Number.isFinite(max))\n return [];\n const first = Math.ceil((viewMin - max) / period);\n const last = Math.floor((viewMax - min) / period);\n const shifts: number[] = [];\n // Adding zero turns the -0 of a negative k times zero into 0.\n for (let k = first; k <= last; k++) shifts.push(k * period + 0);\n return shifts;\n}\n\n/** `delta` moved by whole periods into the half-open range from minus half a period to plus half. */\nfunction wrapToHalfPeriod(delta: number, period: number): number {\n const half = period / 2;\n return ((((delta + half) % period) + period) % period) - half;\n}\n\ntype Vec3 = readonly [number, number, number];\n\nfunction crossesAPole(a: GroundPoint, b: GroundPoint): boolean {\n const u = unitVector(a);\n const v = unitVector(b);\n const separation = angleBetween(u, v);\n if (separation === 0) return false;\n\n const normal = normalise(cross(u, v));\n // Coincident or antipodal samples span no unique great circle.\n if (normal === null) return false;\n\n // Angular distance from the pole axis to the arc's great circle: the normal's z is its sine.\n const missDistance = Math.asin(Math.min(1, Math.abs(normal[2])));\n if (missDistance >= separation) return false;\n\n // The nearer pole, and the point of the great circle closest to it.\n const pole: Vec3 = [0, 0, a.lat + b.lat >= 0 ? 1 : -1];\n const closest = normalise(subtractProjection(pole, normal));\n if (closest === null) return false;\n\n // On the minor arc iff nearer to each end than the ends are to each other.\n const span = dot(u, v);\n return dot(u, closest) > span && dot(v, closest) > span;\n}\n\nfunction unitVector({ lat, lon }: GroundPoint): Vec3 {\n const phi = (lat * Math.PI) / 180;\n const lambda = (lon * Math.PI) / 180;\n const c = Math.cos(phi);\n return [c * Math.cos(lambda), c * Math.sin(lambda), Math.sin(phi)];\n}\n\nfunction dot(a: Vec3, b: Vec3): number {\n return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n}\n\nfunction cross(a: Vec3, b: Vec3): Vec3 {\n return [\n a[1] * b[2] - a[2] * b[1],\n a[2] * b[0] - a[0] * b[2],\n a[0] * b[1] - a[1] * b[0],\n ];\n}\n\nfunction angleBetween(a: Vec3, b: Vec3): number {\n return Math.acos(Math.max(-1, Math.min(1, dot(a, b))));\n}\n\n/** `v` with its component along the unit vector `n` removed. */\nfunction subtractProjection(v: Vec3, n: Vec3): Vec3 {\n const k = dot(v, n);\n return [v[0] - k * n[0], v[1] - k * n[1], v[2] - k * n[2]];\n}\n\n/** `null` rather than a NaN-laden vector when there is no direction to return. */\nfunction normalise(v: Vec3): Vec3 | null {\n const length = Math.hypot(v[0], v[1], v[2]);\n if (length < 1e-12) return null;\n return [v[0] / length, v[1] / length, v[2] / length];\n}\n"],"mappings":"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","names":["styled","styled","jsx","styled","jsx","jsxs","GroupHeader","styled","useId","styled","css","jsx","jsxs","styled","css","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","open","jsx","jsxs","useId","device","styled","jsx","jsxs","jsx","jsxs","value","styled","Fragment","jsx","jsxs","styled","styled","jsx","jsxs","styled","styled","jsx","jsxs","styled","styled","jsxs","Fragment","jsx","jsxs","styled","styled","jsx","styled","styled","jsx","ButtonGroup","styled","styled","styled","useCallback","useEffect","useRef","useState","styled","css","keyframes","forwardRef","useRef","useState","styled","jsx","jsxs","FitLabelButton","styled","styled","keyframes","jsx","jsx","jsxs","styled","Fragment","jsx","jsxs","useState","useRef","useEffect","useCallback","keyframes","styled","css","useEffect","useId","styled","css","jsx","jsxs","value","useEffect","useId","styled","css","styled","jsx","jsxs","styled","useEffect","useId","useId","useEffect","useId","styled","css","jsx","jsxs","useId","styled","css","styled","css","jsx","jsxs","jsx","styled","jsx","useCallback","useEffect","useId","useRef","useState","styled","Fragment","jsx","jsxs","value","useState","useRef","useId","useCallback","useEffect","styled","styled","css","jsx","jsxs","styled","css","Fragment","useState","styled","useEffect","useRef","useState","styled","jsx","jsxs","useState","Fragment","styled","styled","Fragment","jsx","jsxs","value","styled","useId","useRef","useState","styled","css","Fragment","jsx","jsxs","useState","useId","useRef","styled","css","styled","css","jsx","jsxs","value","styled","css","styled","jsx","styled","useId","useState","styled","Fragment","jsx","jsxs","useState","useId","styled","value","styled","jsx","jsx","useCallback","useLayoutEffect","useRef","useState","styled","jsx","useState","useRef","place","useCallback","useLayoutEffect","styled","styled","jsx","styled","jsx","useCallback","useEffect","useId","useRef","useState","styled","Fragment","jsx","jsxs","useState","useRef","useId","useCallback","useEffect","styled","styled","jsx","styled","useEffect","useRef","useState","styled","jsx","jsxs","value","useRef","useState","useEffect","styled","styled","jsx","jsxs","cut","styled","styled","jsx","jsxs","value","styled","forwardRef","Fragment","jsx","jsxs","bandIn","useLayoutEffect","useRef","styled","css","styled","jsx","jsxs","styled","Fragment","jsx","jsxs","value","bandIn","useRef","useLayoutEffect","place","styled","css","styled","Fragment","jsx","jsxs","styled","value","value","useId","useState","jsx","jsxs","value","useId","useState","createContext","useCallback","useContext","useEffect","useId","useMemo","useRef","useState","createPortal","styled","useEffect","useMemo","useRef","Fragment","jsx","jsxs","useEffect","value","useRef","useMemo","Fragment","jsx","jsxs","createContext","useState","useCallback","value","useMemo","useContext","useRef","useEffect","useId","createPortal","styled","useEffect","useRef","useState","styled","Fragment","jsx","jsxs","styled","useRef","useState","useEffect","jsx","jsx","jsxs","value","room","place","jsx","jsxs","value","styled","jsx","jsxs","value","styled","styled","css","jsx","styled","css","styled","css","jsx","styled","css","value","styled","css","jsx","styled","css","forwardRef","styled","jsx","forwardRef","Slider","styled","styled","jsx","jsxs","styled","Fragment","jsx","useCallback","useId","styled","jsx","jsxs","value","useId","useCallback","styled","jsx","useCallback","useEffect","useEffect","useCallback","useCallback","useId","useLayoutEffect","useMemo","useRef","useState","styled","Fragment","jsx","jsxs","useId","useMemo","useState","useCallback","useRef","useLayoutEffect","styled","bandIn","useEffect","useRef","useState","styled","Fragment","jsx","jsxs","value","useRef","useState","useEffect","h","bandIn","styled","useId","jsx","jsxs","value","useId","jsx","jsxs","styled","value","useId","useState","styled","jsx","jsxs","value","useId","useState","held","emit","styled","useEffect","useState","jsx","jsxs","resolvedColor","jsx","createContext","useContext","useMemo","jsx","value","cross"]}
|