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