OXYGENHUB

docs: Backdrops, living backgrounds specified (refs #156) 2f14c5f39e81

Which pages get a backdrop and which never do; every mark stands for
something real the viewer may see; colours from the theme tokens in both
looks and themes; drift, cursor lighting without pull, meaningful links,
parallax; reduced motion, a 1 ms frame budget and a Moving/Still/Off
setting; the dashboard's Your sky first.
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-13 17:42 UTC
Signature
✔ signed by Andy McCall
Hash
2f14c5f39e815cc36debf235bb475071b4829407626ee46655fa86c978c99d50
Depends on
93be9ce4653e
Depended on by
ffb67a81188c da8f7826a645 6246d1e51f4f 0a3b244b748f 6907a989e82a df5c69cbd047
In bookmarks
api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
1 atom, 1 tree operation, 1 file

Browse the tree at this change

added docs/backgrounds.md +189 −0
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.