LOVE design system
The design system for LOVE, the LSST Operations and Visualization Environment: the web interface observers and engineers use to monitor and control the Rubin Observatory control system. This system replaces the original internal-tool styling with one consistent, accessible visual language, in a dark theme for night operations and a light theme for daytime work.
Principles
- State is never color alone. Every status carries an icon shape and a text label as well as a color. Someone with deuteranopia, someone on a washed-out control-room monitor, and someone reading a printed log all get the same meaning.
- One meaning per color. Cyan (
accent) means "you can interact with this." Green, violet, yellow, orange and magenta-red mean system states, and nothing else. Chart series use their own palette, and the force map uses its own diverging scale. - Dark first, light equal. Dark is the default because operators work at night with dark-adapted eyes. Light is designed and checked separately, not produced by inverting dark.
- Numbers are the content. Telemetry is set in IBM Plex Mono with tabular figures, so values stay aligned and don't jitter as they update.
- Hide complexity until asked. Panels lead with a summary state; raw data and engineering detail sit one step deeper (semantic zoom, "raw data", detail cards).
Color
Surfaces and text
| Role | Dark | Light | Rule |
|---|---|---|---|
bg-page |
#0A0E14 | #F2F5F8 | App background |
bg-surface |
#111821 | #FFFFFF | Panels; every contrast check is made against this |
bg-raised |
#18212C | #F6F8FA | Inset groups, table headers |
text-primary |
#E9EEF4 (15.3:1) | #0F161E (18.2:1) | Values, titles |
text-secondary |
#AEB9C6 (9.0:1) | #455262 (8.0:1) | Labels |
text-muted |
#8592A1 (5.6:1) | #5F6C7B (5.4:1) | Units, timestamps, the faintest text allowed |
border-control |
#5A6878 (3.1:1) | #8693A2 (3.1:1) | Any control's outline |
The neutrals are a cool blue-gray rather than the old teal-gray. That keeps the night-sky character while letting status colors read cleanly against it.
Accent: interaction only
accent is cyan (#56C2F0 dark, #056E9F light), carried over from the original LOVE identity. It marks links, toggles, focus rings and the selected item. It never means a state, which is why the "in progress" status is violet rather than blue.
A solid accent fill means a command. Only a CommandButton, which sends something to the telescope or a subsystem, is filled cyan. Buttons that only change the LOVE interface (save a view, add a panel, open raw data) are neutral. So an operator can tell from color, glyph and label alone whether a click will move hardware.
Status: the core of the system
| Status | Dark signal | Light signal / ink | Icon | LOVE states |
|---|---|---|---|---|
ok |
#4DD991 | #2CBF6E / #0B6A3A | ✓ in circle | ENABLED, IN POSITION, DONE, CONFIGURED, TRACKING, STATIONARY |
active |
#A08CFF | #7457E8 / #5336C8 | rotating arc | MOVING, SLEWING, HOMING, RUNNING, INTEGRATING, RAISING, LOWERING |
warning |
#F9E04B | #F0BC00 / #6E5300 | triangle ! | Watcher WARNING, UNCONFIGURED, near a limit |
serious |
#FF8B42 | #D9580F / #9E3F08 | diamond ! | Watcher SERIOUS, CONFIGURE FAILED, LOAD FAILED |
critical |
#F2558E | #B01049 / #A50E44 | octagon × | FAULT, FAILED, Watcher CRITICAL, breakaway |
neutral |
#97A3B1 | #7D8896 / #45505E | pause / minus / dashed ring / ? | STANDBY, DISABLED, PARKED, NOT IN POSITION, OFFLINE, UNKNOWN |
What changed from the original, and why:
- STANDBY is no longer yellow. Standby is a normal resting state, not a problem. Yellow is reserved for things that need attention, so a screen in a healthy state is calm.
- Fault is magenta-red, not orange. Under deuteranopia, the old green and orange collapse into the same olive. Pushing critical toward magenta, and making it darker than green, keeps the two apart.
- Each tone has three tokens.
status-*is the signal color for dots, marks and drawings.status-*-textis the ink for text and icons inside a badge.status-*-tintis the badge or banner background. In light mode, signal yellow and green fall below 3:1 on white, so badge text and icons always use the dark ink, and marks drawn in those colors get a 1px ink outline.
Validation. The six status colors, including accent, were run through the data-viz palette validator as an all-pairs set, simulating protanopia and deuteranopia with the Machado 2009 model:
- Dark theme: worst CVD ΔE 8.2 (target ≥ 8). Worst normal-vision ΔE 16.0 (floor ≥ 15).
- Light theme: worst CVD ΔE 8.0. Worst normal-vision ΔE 15.9.
All text pairs meet WCAG AA at 4.5:1.
Data visualization
- Diverging (
div-neg-4…div-mid…div-pos-4). Blue ↔ gray ↔ orange, for signed values. The M1M3 force map uses it, centered on 0 N. Blue/orange is the pairing that survives every common color-vision deficiency. The midpoint is gray so that "zero" reads as "nothing." This replaces the old rainbow scale, which had no natural midpoint and put false boundaries in the data. - Sequential (
seq-100…seq-800). A single cyan hue for one-sided magnitudes such as temperature, pressure and wind. - Categorical (
series-1…series-8). Fixed order, never cycled. Charts with 2 or more series always show a legend, and series with up to 4 lines also get direct labels. - Status colors are never used as series colors.
Diagrams and the light path
Telescope, dome and instrument drawings use diagram-structure for fills and diagram-outline for outlines, with optics in diagram-optic. The light itself is beam, a warm starlight glow drawn through every element the light currently reaches. It stops at the first closed cover, unaligned mirror or shut shutter, so "where is the light blocked?" is answered at a glance.
Typography
IBM Plex Sans is the interface face. It has open apertures and clearly different I, l and 1, it's legible at 12px on a dim screen, and its technical, engineered character suits an observatory. IBM Plex Mono is used for every live value. Both are free on Google Fonts.
- 12px is the minimum size for any text. The original used 13px gray-on-navy labels at under 3:1.
- Panel titles are sentence case at 16/22 semibold. All-caps is reserved for the
overlinesection label and for badges. - Long state names wrap or abbreviate in the layout. They are never cut off mid-word like "NOTINPOSI…".
Shape and space
- Spacing: a 4px base (
space-14 →space-632). Panels pad 16, groups separate by 24, panels separate by 32. - Radius:
radius-sm4 for badges, inputs and buttonsradius-md8 for inset groupsradius-lg12 for panelsradius-fullfor actuator dots, hardpoints and toggles- One badge style. The original mixed three: pill, bracketed bar and plain text. There is now one badge: a tinted rectangle with
radius-sm, an icon and an uppercase label.
Interaction
- Focus. Every control shows a 2px
focus-ringon:focus-visible. Map elements such as actuators and hardpoints are keyboard reachable with arrow keys. - Two kinds of button that never look alike:
| UI action (Button) | Command (CommandButton) | |
|---|---|---|
| Affects | Only the LOVE interface | The telescope or a subsystem (CSC) |
| Fill | Neutral (bg-raised), outline or ghost |
Solid accent |
| Glyph | None, or a UI icon | Transmit glyph (dot sending waves) |
| Label | Verb | Verb + target CSC in mono (Slew · ATMCS) |
| Feedback | Immediate | Sending… → ACKNOWLEDGED → DONE, or a failure message with the CSC's reason |
- Destructive and safety commands (STOP, lower mirror to static support) are CommandButtons in
status-critical. They ask for confirmation, and they are always visible, never in an overflow menu. Routine commands stay one click, so operators aren't trained to click through dialogs. - Commands without permission are disabled with a lock glyph, and the reason is shown next to them.
- Hover is a wash, never a color change of the data. Hover uses
bg-hover. - Menus: an icon means "this does something". Action rows carry an icon. Information rows (status, alarms, the signed-in user) and lists of places to go don't. Choices show a check. See Menu.
Theme
- Dark is the primary theme, and this system opens in it.
- Automatic is the default in the app. It is dark from sunset to sunrise at Cerro Pachón and light during the day, computed from the sun's altitude at the summit rather than the viewer's clock or operating-system setting. People can pick Light or Dark instead. The choice is per device, and it persists across views and reloads.
- Shift T switches between light and dark, and turns Automatic off.
Limits and verdicts
A condition with limits always shows four things together:
- the value, in mono
- a LimitGauge, with hatched warning and closure zones
- the limits in words
- a status badge: WITHIN LIMITS, NEAR LIMIT or CLOSE
Above the rows, a Verdict sums them up in one sentence that names the reasons. Limits in the prototype are illustrative unless marked "documented".
Alarms
- Raising. Views raise alarms for their own conditions: wind past the closure limit, an actuator in fault, a script that stopped its queue, the dome blocking the beam.
- The banner. The most important unacknowledged alarm sits in an AlarmBanner under the toolbar on every view. The bell shows how many alarms are unacknowledged, colored by the highest severity.
- Acknowledging is a command to the Watcher, with the same permission rules as any other command.
- Latching. Alarms latch: they stay until someone acknowledges them, and come back if the condition returns.
Scripts and queues
- One queue per telescope. ScriptQueue:1 runs MainTel and ScriptQueue:2 runs AuxTel. Every command in the queue view names which one it goes to.
- A failed script stops the queue on purpose. The view leads with a critical notice, highlights the error and traceback, and offers three ways forward: requeue, edit and requeue, or resume past it.
- Reordering, adding, removing and requeueing are commands, and they all appear in the command log. Terminate is a critical CommandButton that asks for confirmation; Stop script is the gentle default.
Charts and maps
- Linked hover. Small multiples share one time axis. Hovering one chart shows the same moment on all of them, and the arrow keys do the same for keyboard users.
- Data and its limits. Warning and closure zones are drawn on charts as shaded bands, with a dashed line labelled "Close N".
- Sky map orientation. "Looking up" (east on the left, like the all-sky camera) is the default, and "From above" (east on the right) matches the dome plan. AuxTel is
series-1(ring), MainTelseries-2(filled disc). Queued targets are diamonds, outlined instatus-criticalwhen below the pointing limit.
View editor
- Two levels of layout. Views are composed of components on a 12-column grid. Inside a component, panels sit in columns that can be resized, reordered and hidden, and illustrations can be scaled down to a legible minimum. See ArrangePanel.
- Save thumbnail has three states:
- blocked when nothing has changed
- ready when there are changes; saving the thumbnail also saves the view
- alert (
status-serious) when the view was saved without an up-to-date thumbnail - Previews and displays. "Layout size" previews a desk monitor (1920 px) or the control-room wall (3840 px at 1.5×) at true layout width. The control-room wall is a per-device display profile, not a separate layout.
Color
Every color has a dark and a light value. Ratios are contrast against --bg-surface in that theme.
Bg
- --bg-pageDark #0A0E141.1:1Light #F2F5F81.1:1
App background behind panels. Dark is the primary theme for night operations.
- --bg-surfaceDark #1118211.0:1Light #FFFFFF1.0:1
Panel (widget) background. The surface all contrast checks are made against.
- --bg-raisedDark #18212C1.1:1Light #F6F8FA1.1:1
Inset groups inside a panel: table headers, detail cards, control strips.
- --bg-inputDark #0D12191.1:1Light #FFFFFF1.0:1
Text fields, selects and numeric inputs. Always paired with border-control.
- --bg-hoverDark #212C391.3:1Light #EBEFF41.2:1
Hover wash for rows, list items and ghost buttons.
- --bg-selectedDark #13303F1.3:1Light #E1F0F81.2:1
Selected row / selected item background (accent tint).
Border
- --border-subtleDark #222C381.3:1Light #E3E8EE1.2:1
Dividers inside a panel, table row rules, chart gridlines.
- --border-defaultDark #2B36431.5:1Light #D9E0E81.3:1
Panel outline and section separators.
- --border-controlDark #5A68783.1:1Light #8693A23.1:1
Borders of interactive controls (inputs, secondary buttons, toggles). Meets 3:1 against bg-surface.
Text
- --text-primaryDark #E9EEF415.3:1Light #0F161E18.2:1
Values, titles and body text. 15:1 dark / 18:1 light on bg-surface.
- --text-secondaryDark #AEB9C69.0:1Light #4552628.0:1
Field labels, secondary descriptions. ≥7:1 on bg-surface.
- --text-mutedDark #8592A15.6:1Light #5F6C7B5.4:1
Units, timestamps, axis ticks, placeholder text. ≥4.5:1 on every surface; never use anything fainter for text.
- --text-on-accentDark #0710181.1:1Light #FFFFFF1.0:1
Text and icons on accent-filled buttons.
Accent
- --accentDark #56C2F08.8:1Light #056E9F5.6:1
Interactive color only: primary buttons, links, focus, selection, toggles. Never encodes a system state.
- --accent-hoverDark #7FD2F510.6:1Light #04587F7.8:1
Hover/pressed state of accent fills and links.
Focus
- --focus-ringDark #7FD2F510.6:1Light #056E9F5.6:1
2px keyboard focus outline, offset 2px. Every interactive element shows it on :focus-visible.
Status
- --status-okDark #4DD9919.9:1Light #2CBF6E2.4:1
Nominal / ready: ENABLED, IN POSITION, DONE, CONFIGURED, TRACKING, STATIONARY. Icon: check in circle. Signal color for marks, dots and diagram fills; always paired with icon + label.
- --status-ok-textDark #4DD9919.9:1Light #0B6A3A6.7:1
Text and icon color inside a ok badge or message.
- --status-ok-tintDark #1933311.3:1Light #E3F6EA1.1:1
Background of a ok badge, banner or highlighted row.
- --status-activeDark #A08CFF6.5:1Light #7457E84.9:1
In progress: MOVING, SLEWING, HOMING, RUNNING, INTEGRATING, RAISING, LOWERING. Icon: rotating arc. Violet, so it never reads as the cyan accent. Signal color for marks, dots and diagram fills; always paired with icon + label.
- --status-active-textDark #A08CFF6.5:1Light #5336C87.7:1
Text and icon color inside a active badge or message.
- --status-active-tintDark #2528401.2:1Light #EEEAFD1.2:1
Background of a active badge, banner or highlighted row.
- --status-warningDark #F9E04B13.4:1Light #F0BC001.8:1
Needs attention: Watcher WARNING, UNCONFIGURED, approaching a limit. Icon: triangle with ! Signal color for marks, dots and diagram fills; always paired with icon + label.
- --status-warning-textDark #F9E04B13.4:1Light #6E53007.2:1
Text and icon color inside a warning badge or message.
- --status-warning-tintDark #3134271.4:1Light #FCF2CF1.1:1
Background of a warning badge, banner or highlighted row.
- --status-seriousDark #FF8B427.7:1Light #D9580F3.9:1
Watcher SERIOUS, CONFIGURE FAILED, LOAD FAILED. Icon: diamond with ! Signal color for marks, dots and diagram fills; always paired with icon + label.
- --status-serious-textDark #FF8B427.7:1Light #9E3F086.6:1
Text and icon color inside a serious badge or message.
- --status-serious-tintDark #3228261.2:1Light #FCE8DC1.2:1
Background of a serious badge, banner or highlighted row.
- --status-criticalDark #F2558E5.5:1Light #B010497.0:1
FAULT, FAILED, Watcher CRITICAL, hardpoint breakaway. Icon: octagon with ×. Magenta-red so it separates from ok-green under deuteranopia. Signal color for marks, dots and diagram fills; always paired with icon + label.
- --status-critical-textDark #F2558E5.5:1Light #A50E447.6:1
Text and icon color inside a critical badge or message.
- --status-critical-tintDark #3021301.2:1Light #FBE3EA1.2:1
Background of a critical badge, banner or highlighted row.
- --status-neutralDark #97A3B17.0:1Light #7D88963.6:1
Idle states that are not problems: STANDBY, DISABLED, PARKED, NOT IN POSITION, OFFLINE, UNKNOWN. Each has its own icon (pause, minus, dashed ring, ?). Signal color for marks, dots and diagram fills; always paired with icon + label.
- --status-neutral-textDark #97A3B17.0:1Light #45505E8.2:1
Text and icon color inside a neutral badge or message.
- --status-neutral-tintDark #242B351.3:1Light #ECEFF31.2:1
Background of a neutral badge, banner or highlighted row.
Div
- --div-neg-4Dark #7DC2FF9.4:1Light #0058A87.1:1
Diverging scale, negative arm (blue). Step 4 = largest magnitude. Used for signed values such as M1M3 actuator force (−).
- --div-neg-3Dark #58A2F26.7:1Light #287CCF4.3:1
Diverging scale, negative arm (blue). Step 4 = largest magnitude. Used for signed values such as M1M3 actuator force (−).
- --div-neg-2Dark #3F83CB4.5:1Light #64A2E72.7:1
Diverging scale, negative arm (blue). Step 4 = largest magnitude. Used for signed values such as M1M3 actuator force (−).
- --div-neg-1Dark #35669A3.0:1Light #A4C8F11.7:1
Diverging scale, negative arm (blue). Step 4 = largest magnitude. Used for signed values such as M1M3 actuator force (−).
- --div-midDark #43484E1.9:1Light #E5E8EC1.2:1
Diverging scale midpoint: zero. Neutral gray, never a hue.
- --div-pos-1Dark #8F51282.9:1Light #EAB89B1.8:1
Diverging scale, positive arm (orange). Step 4 = largest magnitude.
- --div-pos-2Dark #BB672C4.3:1Light #D988542.8:1
Diverging scale, positive arm (orange). Step 4 = largest magnitude.
- --div-pos-3Dark #E183446.4:1Light #BD5B004.5:1
Diverging scale, positive arm (orange). Step 4 = largest magnitude.
- --div-pos-4Dark #FFA56C9.2:1Light #9536007.5:1
Diverging scale, positive arm (orange). Step 4 = largest magnitude.
Seq
- --seq-100Dark #DBF3FF15.5:1Light #DBF3FF1.1:1
Sequential cyan ramp for one-sided magnitude (temperature, pressure, wind). In dark mode map low values to high step numbers so small values recede into the surface.
- --seq-200Dark #AFE0F712.6:1Light #AFE0F71.4:1
Sequential cyan ramp for one-sided magnitude (temperature, pressure, wind). In dark mode map low values to high step numbers so small values recede into the surface.
- --seq-300Dark #7DCAEB9.8:1Light #7DCAEB1.8:1
Sequential cyan ramp for one-sided magnitude (temperature, pressure, wind). In dark mode map low values to high step numbers so small values recede into the surface.
- --seq-400Dark #4DB2DB7.4:1Light #4DB2DB2.4:1
Sequential cyan ramp for one-sided magnitude (temperature, pressure, wind). In dark mode map low values to high step numbers so small values recede into the surface.
- --seq-500Dark #1096C25.2:1Light #1096C23.4:1
Sequential cyan ramp for one-sided magnitude (temperature, pressure, wind). In dark mode map low values to high step numbers so small values recede into the surface.
- --seq-600Dark #007AA33.7:1Light #007AA34.9:1
Sequential cyan ramp for one-sided magnitude (temperature, pressure, wind). In dark mode map low values to high step numbers so small values recede into the surface.
- --seq-700Dark #005F802.5:1Light #005F807.1:1
Sequential cyan ramp for one-sided magnitude (temperature, pressure, wind). In dark mode map low values to high step numbers so small values recede into the surface.
- --seq-800Dark #00445E1.7:1Light #00445E10.6:1
Sequential cyan ramp for one-sided magnitude (temperature, pressure, wind). In dark mode map low values to high step numbers so small values recede into the surface.
Series
- --series-1Dark #3987E54.9:1Light #2A78D64.4:1
Categorical chart series, assigned in fixed order 1→8, never cycled. Lines and scatter beyond 3 series need direct labels.
- --series-2Dark #D959264.6:1Light #EB68343.2:1
Categorical chart series, assigned in fixed order 1→8, never cycled. Lines and scatter beyond 3 series need direct labels.
- --series-3Dark #199E705.2:1Light #1BAF7A2.8:1
Categorical chart series, assigned in fixed order 1→8, never cycled. Lines and scatter beyond 3 series need direct labels.
- --series-4Dark #C985005.8:1Light #EDA1002.2:1
Categorical chart series, assigned in fixed order 1→8, never cycled. Lines and scatter beyond 3 series need direct labels.
- --series-5Dark #D551814.5:1Light #E87BA42.7:1
Categorical chart series, assigned in fixed order 1→8, never cycled. Lines and scatter beyond 3 series need direct labels.
- --series-6Dark #0083003.6:1Light #0083004.9:1
Categorical chart series, assigned in fixed order 1→8, never cycled. Lines and scatter beyond 3 series need direct labels.
- --series-7Dark #9085E95.7:1Light #6250D65.8:1
Categorical chart series, assigned in fixed order 1→8, never cycled. Lines and scatter beyond 3 series need direct labels.
- --series-8Dark #E667675.5:1Light #E349484.0:1
Categorical chart series, assigned in fixed order 1→8, never cycled. Lines and scatter beyond 3 series need direct labels.
Diagram
- --diagram-structureDark #1C26321.2:1Light #E4EAF01.2:1
Fill of mechanical structure in telescope, dome and instrument drawings.
- --diagram-outlineDark #3E4B5B2.0:1Light #AEB9C62.0:1
Outline of structure in drawings.
- --diagram-opticDark #6FA9C97.0:1Light #3F7FA34.4:1
Optical elements (mirrors, filters, gratings) when idle.
Beam
- --beamDark #FFE7A814.7:1Light #C98A002.9:1
The light path. Drawn as a glow through every element light currently reaches; stops where light is blocked.
Typography
Interface · "IBM Plex Sans"
- Auxiliary Telescopedisplay28px/34px · 600
View titles.
- Dome and mounttitle16px/22px · 600
Panel titles. Sentence case, never all caps.
- MOUNToverline12px/16px · 600
Section labels inside a panel. The only uppercase style besides badges.
- Move dome to home positionbody14px/20px · 400
Default text, field labels.
- Time to limitlabel12px/16px · 500
Secondary labels, units, captions. 12px is the minimum size for any text.
- IN POSITIONbadge12px/16px · 600
Status badge text.
Telemetry · "IBM Plex Mono"
- 183.402°value14px/20px · 500
Live values in tables and summaries. Tabular figures so columns align and numbers don't jitter.
- −12.88 Nvalue-lg24px/30px · 500
Headline readouts (selected actuator, current azimuth).
Spacing and shape
Spacing
- space-14px
Icon-to-label gap.
- space-28px
Gap between related controls; badge horizontal padding.
- space-312px
Row padding in tables and lists.
- space-416px
Panel inner padding.
- space-524px
Gap between groups inside a panel.
- space-632px
Gap between panels.
Radius
- radius-sm4px
Badges, inputs, buttons, table cells.
- radius-md8px
Inset groups and cards inside panels.
- radius-lg12px
Panels.
- radius-full999px
Toggles, actuator dots, hardpoint chips.
Components
Live previews. Most of them respond to clicks and the keyboard.
AlarmBanner
StatusThe newest unacknowledged alarm, under the toolbar on every view. Acknowledging is a command to the Watcher
An AlarmBanner shows the most important unacknowledged alarm on every view, directly under the toolbar.
- Which alarm. Alarms raised by the current view come first, then the highest severity (
critical>serious>warning), then the newest. "N more" opens the full list in the notifications menu. - Acknowledging is a command. Acknowledge is a CommandButton with the target
Watcher. A read-only user sees why they can't acknowledge. - Alarms latch. An alarm stays until someone acknowledges it, even after its condition clears; the banner then adds "condition cleared". If an acknowledged alarm's condition comes back, it is raised again.
- Navigation. The "Open" button (for example "Open Environment") is a UI button that goes to the view that raised the alarm. It is hidden when you're already there.
- Tint. The background is the severity's tint mixed 70% into
bg-surface. Userole="alert".
StatusBadge
StatusOne badge for every state: tint + icon shape + label
The single way to show a system state: a tinted rectangle with an icon shape and an uppercase label.
- Pick the tone by meaning, not by word. STANDBY and PARKED are
neutral, because idle isn't a problem. Yellow is only for things that need attention. - The icon shape carries the meaning for color-blind readers and grayscale prints: ✓ ok, arc in progress, triangle warning, diamond serious, octagon critical, and pause, minus, dashed ring or ? for idle and unknown states.
- Never truncate the label. Give the badge its natural width. If space is short, let the row wrap.
- Text and icon use
status-*-textand the background usesstatus-*-tint. The badge hasrole="status"so screen readers announce changes.
Verdict
StatusOne sentence that answers: is it safe, and if not, why
A Verdict answers one question in one sentence, such as "is it safe to observe?", and names the reason when the answer isn't a plain yes.
- Three states.
ok(safe),warning(safe, with conditions to watch) andcritical(conditions call for closing). Each state has its status icon, a tinted icon disc and a border in its signal color. - The reason is required for warning and critical. It lists the conditions by name, joined in plain English: "Dew point margin and humidity are near the closure limit."
- It describes, it doesn't decide. The Environment view says the decision to open or close stays with the observers. A Verdict never sends a command by itself.
- Announce changes politely. Use
role="status", so a screen reader reads the new verdict when it changes.
Button
ControlsUI actions: change the view, never the telescope
UI buttons change only what you see in LOVE: views, panels, filters, layout, raw data. They never reach the observatory, so they never use the accent fill.
- Neutral (
bg-raisedfill,border-default) is the main UI action in a panel: Save view, Apply. - Outline (
border-control, 3:1) is for secondary UI actions. - Ghost (
text-secondary) is for low-emphasis actions such as Raw data or Expand. - Icon-only buttons carry an
aria-labeland the same three styles. - If a button sends anything to the telescope or a subsystem, it is a CommandButton, not a Button.
CommandButton
ControlsCommands to the observatory: accent fill, transmit glyph, named target, visible lifecycle. Click them.
A CommandButton sends a command to the telescope or one of its subsystems. It is the only control with a solid accent fill, so anything that fills cyan reaches the observatory.
It is told apart from a UI button in four ways, so it never relies on color alone:
- Solid
accentfill. UI buttons are never filled with accent. - The transmit glyph (a dot sending waves) before the label.
- The target segment. The CSC that receives the command is named at the right end in mono (
ATMCS,ATDome,ScriptQueue:2). Icon-only commands put the target in theiraria-labeland tooltip. - A visible lifecycle. While sending, the button shows Sending… and blocks double-sends. A badge beside it then shows
ACKNOWLEDGED(active) and finishes asDONEwith the duration, or as a critical message giving the CSC's reason. The button itself never turns a status color.
Rules:
- Without permission, the button is disabled with a lock glyph, and the reason sits next to it rather than only in a tooltip.
- STOP is a CommandButton in
status-criticalwith the octagon glyph and the targetALL. It is always visible and asks for confirmation. - Confirmation is for safety-critical commands only (STOP, raise or lower mirror, open dome). Routine commands like Slew stay one click, so operators aren't trained to click through dialogs.
ForceMap
DataM1M3 actuators on the diverging scale, faults by shape
The M1M3 actuator map, colored on the diverging blue ↔ gray ↔ orange scale and centered on 0 N. It replaces the rainbow scale, which had no zero point and isn't color-blind safe.
- Faulted actuators keep their position but change shape. They become a hollow ring with an × in
status-criticaland leave the color scale altogether, so a fault can never be mistaken for a large force. - Selection is the
focus-ringoutline. Every actuator is keyboard-focusable, and its detail card shows applied force, commanded force and following error invalue-lg. - The legend always shows numeric end points and the zero. It never shows "±Infinity"; with no data, the map shows an empty state instead.
LightPath
DataThe beam token stops where light is blocked
The drawing language for the telescope, dome and instrument views: diagram-structure fills, diagram-outline outlines and diagram-optic optics. The light itself is drawn in beam.
- The beam is drawn through every element the light reaches, and ends at the first thing blocking it. The blocking element is outlined in a status color that matches its badge.
- Every drawing has a list of the same elements beside it, each with its badge. Screen readers and color-blind readers get the state from the list, not from the picture.
LimitGauge
DataA value against its warning and closure zones. Hatching keeps the zones apart without color
A LimitGauge shows a value against its warning and closure zones, so a glance answers "how close are we?" as well as "are we over?"
- Track. The track is
bg-raised. The warning zone isstatus-warning-tintwith 135° hatching, and the closure zone isstatus-critical-tintwith 45° hatching. The two hatch angles keep the zones distinct without relying on color. - Marker. The current value is a 4px
text-primarymarker with a 2pxbg-surfacehalo. - Direction. The zones sit at the high end for upper limits (humidity, wind) and at the low end for lower limits (dew point margin).
- Layout. Always pair the gauge with the number, the limits in words ("Warn ≥ 80% · Close ≥ 90%") and a status badge (WITHIN LIMITS, NEAR LIMIT, CLOSE). The gauge itself is
aria-hidden. In narrow containers, drop the gauge before dropping the words.
WindRose
DataShare of time per direction and speed band. One hue, light to dark, area-true rings
A WindRose shows how much of the time the wind blew from each of 16 directions, stacked by speed band.
- Color. One hue, from the sequential scale. In light mode the bands run
seq-300(slowest) toseq-800(fastest); in dark mode they run the other way,seq-700toseq-100, so faster wind is always more prominent. Never use a rainbow, and never use status colors for speed. - Geometry.
- The radius grows with the square root of the share, so wedge area is true to the data.
- Wedges have a 1.5px
bg-surfacegap. - The share of calm time is printed in the centre.
- Overlays. The current wind direction is a
text-primaryline with an arrowhead. The dome slit (series-1) and the telescope (series-2) are drawn on the rim, so you can see where the wind hits relative to the pointing. - Legend and access. Always show a legend with km/h units, and hover tooltips with the direction, speed band and share. Give a text summary in
<desc>.
ScriptQueueItem
ListsWaiting scripts: a drag handle that also works with arrow keys, the reason, the estimate and the load state
A ScriptQueueItem is one script waiting in a ScriptQueue.
- What the row shows. From left to right:
- its position
- a drag handle
- the path, with the folder in
text-mutedand the file name intext-primary, in mono - why it's running
- the estimate
- its load state: CONFIGURED (neutral), LOADING (active), CONFIGURE FAILED (serious)
- Reordering is a command. Dragging the handle, or pressing ↑ and ↓ on it, sends
moveto the ScriptQueue. The move is announced in a live region and logged. - Expanding a row shows its configuration and command buttons (Move to top, Remove). Those are commands, so they are never hidden in a menu.
- Failures. A configure failure gets a critical-tinted border, and the error is shown as text, not only in a tooltip.
Menu
NavigationActions carry an icon; information and destinations do not; choices show a check
A Menu is a popover list opened from a bar button. It follows one rule: an icon means the row does something.
- Actions have a 16px icon in
text-secondary. Examples: Add log entry, Emergency contacts, LOVE configuration file, Switch user. - Information rows have no icon. Examples: the signed-in user, alarms, status rows, recent commands.
- Destinations have no icon either. Examples: the list of views in the breadcrumb menu.
- Choices (theme, display) are
menuitemradio. They show a check in the icon slot when selected, and a short state on the right ("Dark now", "Now: Desk"). - Keyboard. ↑ ↓ move between items, Home and End jump to the first and last, and Esc or Tab closes the menu and returns focus to its button.
- Keep menus short. Keyboard shortcuts live in their own dialog, not repeated in every menu.
ArrangePanel
EditorEdit mode: each panel gets a strip to move or hide it, columns resize at their divider, illustrations at their corner
ArrangePanel covers the view editor's controls for arranging the panels inside a component. They appear only in edit mode, and panel contents are inert while editing.
- Panel strip. Each panel gets a strip with:
- a grip, for drag, or ↑ ↓ and Shift ← → on the keyboard
- move up and down
- move to the previous or next column
- hide
Hidden panels are listed in the arrange bar as "Show" buttons named after each panel. The last visible panel can't be hidden. - Columns resize at the line between them, by drag or ← → on the focused separator, in steps of 1/12, with at least 2/12 per column. "Add column" allows up to 4 columns, and an empty column offers "Remove column". - Illustrations resize from a round handle at their bottom-right corner, by drag, the arrow keys, or Home to reset. Each has a minimum size so it stays legible: about 340px for the dome plan and 260px for the actuator maps. Shrinking an illustration gives its freed space to the information next to it. - History and saving. Every change goes through undo and redo, and is saved with the view. - Responsive layout. Panels respond to their own width (container queries), not the window's, so a narrow column reflows its contents.
TelemetryPanel
PanelsPanel chrome, status rows, mono values and a limit gauge
The panel every LOVE widget sits in: a bg-surface card with a sentence-case title, a "Raw data" escape hatch, and overline group labels each followed by a summary badge.
- Labels and values. Labels sit on the left in
text-secondary. Values sit on the right inmonowith tabular figures, and a target follows as→ valueintext-muted. - Limit gauges. Forbidden zones are hatched in
status-warning, so they read without color. The current position is a solid tick, and the commanded target is anactivediamond.