| 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 |
|
| 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 |
|
| 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. |