OXYGENHUB
# 2f14c5f39e81 oxygenhub / docs / backgrounds.md
9.0 KiB · Markdown · last changed docs: Backdrops, living backgrounds specified (refs #156) 22 days ago renderedsource blame history raw
1
# Backdrops: living backgrounds for OxygenHub
2
3
Status: proposal, 2026-10-13 (oxygenhub#156). Nothing here is built yet;
4
the dashboard's scene comes first.
5
6
## 1. What a backdrop is
7
8
A backdrop is a quiet, moving picture behind a page's content: a field of
9
small marks that drift slowly and respond a little to the cursor. It
10
gives the hub a sense of motion and depth without asking for attention.
11
12
Three rules shape everything else:
13
14
1. **True, not invented.** The footer says "nothing here is inferred:
15
   what a page says, the object store says". A backdrop keeps that
16
   promise. Every mark stands for something real the viewer may see: one
17
   of their repositories, a change, a push, a runner. Nothing is made-up
18
   decoration that only looks like data, such as fake chunks or fake
19
   peers.
20
2. **Behind, never in the way.** Content is opaque cards on top. A
21
   backdrop shows only in the margins and the gaps between cards. It never
22
   takes a click, never moves the layout, and is never needed to read or
23
   use the page.
24
3. **Cheap and polite.** It costs well under a millisecond a frame. It
25
   stops when the tab is hidden, and it is still for people who ask for
26
   reduced motion. Anyone can turn it off.
27
28
## 2. Which pages
29
30
| Page | Scene | Shows |
31
|---|---|---|
32
| Dashboard | **Your sky** (§5) | Your repositories as clusters; their activity in the last 30 days as the marks around them |
33
| Signed-out home, Explore | **The hub's sky** | The same scene from public repositories only |
34
| Sign in, register, device sign-in | **The mark** | The Oxygen mark's two rings with marks orbiting them; brand only, no data |
35
| A repository's Diffusion tab | **Ripples** | The upstream and its consumers as the Diffusion mark draws them: a pulse goes out on a bump, green or red as its run ends |
36
| Admin › Runners › Images | **The swarm** | Runners as nodes; chunks moving between them while an image is fetched, from the swarm tracker's live state |
37
| Not found, server error | **Adrift** | One mark drifting away from the rest |
38
39
**Never:** code, file, blame, diff, conflict and proposal pages, the
40
editor, issues and proposals while being written, settings and admin
41
tables. Those pages are for reading closely, and motion behind them
42
costs attention. Repository overviews have none for the same reason;
43
their README is the picture.
44
45
Each scene is its own issue, done one at a time, the dashboard first.
46
47
## 3. Colours
48
49
A backdrop takes its colours from the page's own tokens, so it is right in
50
both looks (GitHub, the default, and classic) and in both themes (light,
51
the default, and dark), and it follows a theme switch at once. Three new
52
tokens join each look's light and dark sets:
53
54
| Token | Use | GitHub light | GitHub dark | Classic (dark) |
55
|---|---|---|---|---|
56
| `--bd-mark` | a resting mark | `--mute` at 16% | `--mute` at 22% | `--acc` at 18% |
57
| `--bd-lit` | a mark near the cursor | `--acc` (#0969da) at 70% | brand cyan #00F0FF at 75% | #00F0FF at 85% |
58
| `--bd-link` | a line between lit marks | `--acc` at 22% | #00F0FF at 28% | #00F0FF at 35% |
59
60
Two more colour rules:
61
62
- **The brand gradient (#00F0FF → #0072FF)** is kept for moments: a new
63
  push arriving, the mark scene. A whole field in it would shout.
64
- **Status colours are used only where the data has a status:** the
65
  Diffusion pill's green, amber and red, and a run's outcome. They never
66
  appear as decoration.
67
68
In light mode a backdrop must be barely there: if you notice it before the
69
content, it is too strong. The contrast test (`tests/contrast.rs`) gains a
70
check that `--bd-mark` stays under 1.3:1 against `--bg`.
71
72
## 4. Motion and the cursor
73
74
- **Drift.** Marks move slowly, at most 12 px a second, along gentle
75
  streams around their cluster. Two streams can cross: their marks pass
76
  each other without touching, and the change in order means nothing.
77
  That is how Oxygen's changes behave, which is where this comes from.
78
- **The cursor lights, it does not pull.** Marks within about 140 px of
79
  the cursor light up in `--bd-lit`, ramping in over 150 ms and fading
80
  out over 600 ms. Nothing is attracted or pushed, so the cursor never
81
  rearranges the picture.
82
- **Lines mean something.** Among the lit marks, a faint line joins two
83
  marks only when they belong together in the data: changes of the same
84
  repository, or a change and the one it depends on where the scene has
85
  dependencies. Lines fade as the cursor moves on or rests for 2 s.
86
- **Labels, sparingly.** When the cursor rests near a cluster, its name
87
  fades in beside it in `--mute`, small. This is the only text a backdrop
88
  draws, and it is never a link; the dashboard's own list is the link.
89
- **Parallax.** Three depth layers shift by at most 6 px, 3 px and 1 px
90
  against the cursor's offset from the page's centre. That gives a little
91
  depth with no feeling of motion sickness.
92
- **Arrivals.** When the page learns of new activity, its mark appears
93
  with one soft ring in the brand gradient that widens and fades over
94
  1.2 s. This is the one moment a backdrop may draw the eye, and only
95
  briefly.
96
97
## 5. The dashboard: Your sky
98
99
**Data.** The dashboard handler already loads your repositories and the
100
activity feed from the database. The backdrop needs a little more of the
101
same:
102
103
- one record per repository you can see on the dashboard: its name and
104
  its activity count in the last 30 days;
105
- up to 400 events from the last 30 days across those repositories
106
  (pushes, proposals, issues, releases): each event's repository, kind
107
  and time.
108
109
The handler embeds these as JSON in the page (`<script
110
type="application/json" id="backdrop">`), so the backdrop costs no
111
second request and never reads a repository's store. That matters:
112
some stores are slow, such as GCS-backed ones. Private repositories
113
appear only for viewers who may see them, exactly as the list beside
114
them does.
115
116
**Layout.**
117
118
- Each repository is a cluster, placed by its name's BLAKE3 on a golden
119
  angle spiral, so your sky looks the same on every visit and on every
120
  device.
121
- The most active repositories sit nearest the middle layer and the
122
  quiet ones further back.
123
- A cluster's events are its marks: today's are brightest and a month
124
  old are faint.
125
- Kinds differ by shape, not colour: a push is a dot, a proposal is a
126
  small diamond, an issue is a ring and a release is a four-point star.
127
128
**Live.** When the page learns of new activity (it already polls for the
129
notification count), the event joins its cluster with an arrival ring
130
(§4). Nothing reloads.
131
132
## 6. Engine
133
134
One script, `assets/backdrop.js`, an ES module of about 15 KB with no
135
build step, served by the hub like `hub.js`:
136
137
- **Drawing.** WebGL2 with instanced quads for marks (one draw for all)
138
  and one line buffer for links. If WebGL2 is unavailable, it falls back
139
  to Canvas 2D; if that fails too, there is no backdrop and nothing
140
  breaks.
141
- **The canvas.** One fixed full-viewport `<canvas aria-hidden="true">`
142
  behind `main`, with `pointer-events: none`. Its device pixel ratio is
143
  capped at 1.5.
144
- **Frame rate.** It runs at 60 fps while the cursor moves, eases to
145
  30 fps when the cursor is still, and pauses on `visibilitychange`.
146
  After 2 minutes without input it draws one last frame and stops until
147
  the next movement.
148
- **Reduced motion.** With `prefers-reduced-motion: reduce` it draws one
149
  still frame with no drift, parallax or arrival rings; lit marks still
150
  respond, instantly.
151
- **Budget.** Under 1 ms of script a frame on a 2020 laptop, checked
152
  with the browser's performance panel before each scene ships.
153
- **Scenes are data.** A scene is a preset of layout, shapes, counts and
154
  which data it reads; the engine is shared.
155
156
**Why not Rust and WASM?** The rest of Oxygen is Rust, so it is a fair
157
question. But a scene of a few hundred marks is light work for a small
158
script, and WASM would add a second toolchain to the hub build and to
159
the offline CI image (wasm32 target, wasm-bindgen, web-sys vendored) for
160
no visible gain. What makes these backdrops new is that they show real
161
data. Revisit WASM if a scene ever needs heavy layout, such as a
162
repository's full change graph of thousands of changes, where sharing
163
oxy-core's own types would pay.
164
165
## 7. The viewer's choice
166
167
Settings › Appearance gains **Background: Moving (default), Still, Off**,
168
kept in a `backdrop` cookie read before first paint, like the theme
169
cookie, so a page never flashes a backdrop it should not have. Still draws
170
the reduced-motion frame. `Save-Data: on` and a pointer that is coarse
171
only (a phone) default to Still.
172
173
## 8. Order of work
174
175
1. The engine and the dashboard's Your sky, with the tokens, the
176
   setting and the contrast check (the next issue).
177
2. Signed-out home and Explore (the same scene, public data).
178
3. Sign in and register (the mark).
179
4. Diffusion ripples.
180
5. The swarm on Admin › Runners › Images.
181
6. Adrift on error pages.
182
183
## 9. Open questions
184
185
- Should a cluster's lit marks show the event's title on rest, as a
186
  tooltip would? It is useful, but it is more text in the background.
187
  For now, no: the activity list beside them has the titles.
188
- Organisation pages could have their own sky (the organisation's
189
  repositories). That is cheap once Your sky exists.