BRIEFS
Circuit Subsystem Spec
REFERENCE / Circuit Subsystem Spec
The “circuit” is the marching-dash visual language that frames the whole site and wires the OS
homepage together. This doc specs how it works and lays groundwork for a future admin circuit
editor (draw/edit traces from the dontpanic login, persisted + replayed live).
Companions: docs/DESIGN-SYSTEM.md (UI catalog) · docs/STYLE-MAP.md (token tables — see its
CIRCUITS + OS HOME SURFACES sections for every var).
1. Visual language
- Marching ants: dashed strokes that animate (the dash-offset loops by exactly
±(length + gap)so the march is seamless). Precomputed as--dash-marchinlib/theme-css.ts. - Two readings: (a) a framed box (every card/panel/window has a dashed “circuit”
border); (b) the OS bus — a PCB-style tree of orthogonal traces on the
/homepage that visually wires the panels to a central hub, as if the UI is a circuit board. - Underlay + dash: each border is actually two strokes — a faint solid
--c-circuitunderlay beneath the animated--c-border-dasheddashes — so even when motion is off the trace still reads.
Global toggle: effects.toggles.marchingAnts:false → sets html[data-marching-ants] and
freezes every .dash-animated march (the underlay stays).
2. Two subsystems
2a. Per-element marching borders — DashedBorderFull.astro
src/components/DashedBorderFull.astro. Drops an absolutely-positioned SVG (two <rect>s:
solid --c-circuit underlay + dashed --c-border-dashed) into any position:relative box.
- Animate it: pass
animatedwhen the box is inside a.group(desktop hover) or a.t-line(mobile scroll-in) → adds.dash-animated. - Geometry comes from
--dash-length/--dash-gap/--dash-width/--dash-linecap/--circuit-width(defaults = themaincircuit fromcircuits.md). Props can override. - Own circuit per region: a box can remap those vars via scoped custom properties to get
its own dash colour/geometry —
LinkCard(link circuit) and the OS Action Center / Terminal / MOTD each do this.
circuits.md configs (→ STYLE-MAP CIRCUITS table): main (default border), card (quick
links), osBus (the homepage tree), osActionCenter / osTerminal / osMotd (panel borders).
Rule.astro is the non-animated cousin — a single solid divider (--c-rule), never marched.
2b. Runtime trace router — os-circuit.ts + CircuitTraces.astro
src/scripts/os-circuit.ts draws the OS bus (the homepage tree) at runtime into the shared
SVG host CircuitTraces.astro ([data-os-traces]). Because the panels are placed responsively
(clamp/vw/vh), the geometry cannot be hard-coded — it measures the live boxes and
redraws.
3. Router spec (os-circuit.ts)
3a. The opt-in contract (HTML hooks)
| Attribute | On | Meaning |
|---|---|---|
data-os-hub | the hub ring (HubNode) | the tree root; its rect → ring centre + radius |
data-os-node="<id>" | a target box | a node other wires can attach to (e.g. action-center, spotlight) |
data-os-wire="<targetId>" | a source box | ”wire me to <targetId>” — hub (trunk) or action-center (folder-tree branch) |
data-os-terminal | the console | marks the tall console so its trunk anchors to the hub centre |
The SVG host CircuitTraces.astro contains three <g>s: .os-trace-track (static underlay),
.os-trace-line (the marching dashes), .os-trace-nodes (junction dots, radius NODE_R).
3b. What gets drawn
- Central spine — a single vertical bus through the hub centre. Desktop: full scene height behind the cards. Mobile: links the stacked console → hub → buttons.
- Trunks (desktop) — each
data-os-wire="hub"panel routes an orthogonal elbow from its inner edge to a ring-entry point (NAVIGATION CENTER, console, spotlight). - Folder-tree (desktop) — the launcher buttons (
data-os-wire="action-center") branch off a vertical spine in the gutter (TREE_GUTTERpx) just left of the button stack, each tapping it with a short stub that runs a few px into the button (TAP_INTO).
3c. Anchor logic (important for editing)
For each trunk the tap point (where the trace leaves the panel) is chosen for stability so it doesn’t slide as the window scales:
tapX= the panel’s inner edge (right edge if left of the hub, else left edge).anchorY= the panel’s own vertical centre (b.cy) for short side panels (nav centre, spotlight) — glued to the box; the hub centre (hub.cy) for the console (data-os-terminal) so its trunk leaves the hub centre and runs straight across.tapYisanchorYclamped into the panel (withTAP_PAD).- Ring entry: left panels enter the ring’s left point, or the upper/lower-left diagonal
(
hub.cx ± diag,diag = hubR·0.7071) when the tap is clearly above/below the hub centre.
3d. Redraw triggers
draw() is rAF-debounced via schedule() and re-run on: window resize, ResizeObserver on
main.os-desktop, document.fonts.ready (DOS-V load changes widths), and astro:page-load
(SPA nav). The SVG viewBox is set to pixel space (1 unit = 1px) so strokes stay crisp. Below
DESKTOP_MIN (768px) only the spine is drawn.
3e. Constants
DESKTOP_MIN=768, NODE_R=6 (junction dot), TREE_GUTTER=64 (spine offset left of buttons),
TAP_PAD=14 (keep tap inside corners), TAP_INTO=6 (stub into button edge).
4. Tokens
Circuit colours/geometry are config-driven — see docs/STYLE-MAP.md:
- CIRCUITS section:
main/card/osBus/osActionCenter/osTerminal/osMotd→ their--c-circuit/--c-border-dashed/--dash-*/--circuit-widthfamilies. - OS HOME SURFACES section:
os.hub.{ring,logo,node,spin}→--os-hub-*.
5. 🌌 Future: admin circuit-path editor (groundwork)
The user’s stretch idea: an in-page edit mode (admin dontpanic only) to draw/edit the
homepage traces, persisted and replayed live. This section is the concept — not built.
5a. Where it hooks in
os-circuit.ts currently generates paths (SVG path d strings) + nodes ([x,y]) and
renders them into .os-trace-track / .os-trace-line / .os-trace-nodes. An editor would
supply or override that paths/nodes set — replacing or overlaying the auto-router.
5b. Authored-path data model
A PocketBase collection circuits (admin-gated, public read):
| field | type | notes |
|---|---|---|
name | text | layout name / breakpoint label |
segments | json | ordered list of orthogonal segments, each as anchors, not raw px |
min_width | number | which breakpoint this layout applies at |
enabled | bool | toggle without deleting |
Anchors, not pixels. An authored segment stores endpoints relative to nodes, e.g.
{ from: { node: "action-center", edge: "right", t: 0.5 }, to: { node: "hub", point: "ring-left" }, route: "elbow" }.
At draw time the renderer resolves each anchor against the live rel(el) rect.
Invariant: re-anchor to live node rects on every redraw — never persist absolute coordinates.
5c. Editor UX (concept)
- Toggle “wire mode” (admin only) → overlay a grid; click a node edge, click a target → an orthogonal segment is proposed (snap to the same elbow logic as §3c).
- Drag to adjust the mid-run; delete to remove. Save → POST to
circuits. - Non-admins just get the rendered result (read the collection at load, fall back to the auto-router if empty).
5d. Open questions
- Per-breakpoint authoring vs one responsive model.
- Editor vs auto-router precedence (full replace, or author only specific trunks).
- How to author the central spine + folder-tree (or leave those auto).
- Undo/history + validation (no dangling anchors when a node is removed).
Create a PanicOS account (email + password) to hold a verified name across devices and keep your message streak. Or continue as a guest — a guest name is set on THIS device only and shows as @guest.
CHOOSE A DISPLAY NAME
STATUS