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