Orbital Lab
Public Made by Adomby adom
The Adom logomark is a hydrogen 2p orbital: psi = (2p_x + 2p_y)/sqrt(2). A live Babylon.js lab that proves it (all 16 real spherical harmonics, the alignment cheat, and the directed loop), plus loader.html: the same animation as one dependency-free WebGL file for webview load screens.
name: orbital-lab description: >- The physics behind the Adom logomark (it is a hydrogen 2p orbital, psi = (2p_x + 2p_y)/sqrt(2)) and, for developers, the Adom loading animation: loader.html, a single dependency-free raw-WebGL file (24,379 bytes, 9,912 gzipped, zero requests, 0.057 ms JS per frame, one draw call) that plays the brand animation as a webview load screen. Use when someone asks why the Adom logo is an orbital, wants the orbital demo shown, needs the Adom loading animation or load screen for a webview, or asks how big or heavy the loader is and what it depends on.
Orbital Lab
The Adom logomark is a hydrogen 2p orbital: psi = (2p_x + 2p_y)/sqrt(2). A 2p orbital along the [110] diagonal is an equal superposition of 2p_x and 2p_y, and cut to the mark's quadrants it matches the logo at 99.3% IoU. The wiki page https://wiki.adom.inc/adom/orbital-lab holds the live proof (the Overview IS the app; the Final tab is the finished animation).
Embed the loader (what most people come for)
loader.html on that page is the animation as ONE file for webview load
screens. Grab it from the page's Files tab (or /blob/app/orbital-lab/loader.html)
and serve it beside your app:
<iframe src="loader.html"
style="position:absolute;inset:0;width:100%;height:100%;border:0;background:#0d1117">
</iframe>
<!-- when your app is ready, fade the iframe out and remove it -->
URL params (defaults are the tuned john 1 cut): pt (4.5) free-play seconds between epochs; dt (3) the dive onto the mark; ct (0.3) how late the outline cheat fades in; st (0) dead-still hold; logo (100) flat-mark peak opacity in percent; llead (1) logo fade lead seconds; tlead (1.5) teal-unification lead; ov (1) 0 makes the cheat its own act; intro (1) 0 skips the teal intro; interact (0) 1 arms click-to-zoom, for previews and demos only, never for real load screens, which must stay input-transparent; margin (0) percent of the container left empty around the mark, 80 sits it small in the middle, 20 nearly fills, zoom-in still reaches the screen and zoom-out caps at 40% past the margined framing.
Setting the margin (size inside the container)
margin is how an embedder controls how much of the container the mark
occupies; the loader always FILLS its container, margin just sizes the anim
down inside it. Implemented as camera distance, so perspective, the overlay
match and the epoch glide stay exact at every value.
- Big hero surface (a 1920x1080 webview, anim as a quiet centerpiece):
loader.html?margin=80, the mark sits small in the middle. - Content page (about page, side panel):
loader.html?margin=20, nearly fills. - Load screen default:
loader.html(margin 0, the classic framing).
Armed contexts (interact=1 or the wiki preview) also accept LIVE margin
changes by postMessage, no reload: post
{adomLoader:{margin:N}} at the loader's window and the spring carries the
camera to its new home, animated. Load screens never install this listener.
Zoom contract at any margin: zoom IN can bring the object right up to the screen; zoom OUT is capped at 40% past the margined epoch framing (radius <= home x 1.4); the spring always breathes back to the margined home, and every logo lands on the exact margined framing.
Sitting it on any surface (background)
The loader composites onto whatever it is placed over: ?bg=transparent runs
the GL context with alpha and clears at 0, so the CONTAINER's background shows
straight through the animation, which is how you sit the mark beside a wordmark
in a toolbar or over any surface color. ?bg=HEX (e.g. bg=20262e) paints a
solid instead. Default is the brand dark 0d1117. Armed contexts can switch it
live with postMessage({adomLoader:{bg:'transparent'}}) or a hex string. The
wiki page's live viewer demonstrates this with container-background swatches.
The numbers (why it is safe in a load path)
Measured, not estimated, on the shipped build:
- One file: 24,379 bytes on disk, 9,912 bytes gzipped, and that INCLUDES the shaders, the 286-point logomark vectors, the 128-sample measured edge profile, and the whole timeline.
- Zero external requests: no CDN, no fonts, no fetch. The file is its entire dependency closure. First frame in tens of milliseconds.
- 0.057 ms of JavaScript per frame (16 coefficient cosines plus uniform uploads, about 1.4% of a 240 Hz frame budget), then one draw call over 12,800 triangles / 6,561 vertices, all displacement in the vertex shader.
- requestAnimationFrame pauses it completely when the tab is hidden: a backgrounded webview costs nothing.
Modes: lightweight vs performance (pick one, done)
Two named presets so neither the developer nor the AI reasons about knobs:
loader.html?bg=transparent&mode=lightweight, spinner fleets, progress dialogs, battery: fps 30, dpr 1, seg 48 (4,608 triangles), no antialias. Measured on an eight-spinner 32x32 fleet: about 1.7% of one core total.loader.html, mode=performance is the default (alias mode=sexy), the full-quality look for hero surfaces and big canvases: uncapped fps, dpr up to 2, seg 80 (12,800 triangles), antialiased. Same fleet: about 8.2%.
Does it matter? For ONE spinner, not really: requestAnimationFrame already yields between frames and stops when hidden, so either mode is a rounding error. It matters for fleets, battery, and big surfaces: lightweight is about five times cheaper, and performance is visibly smoother-edged at hero sizes. Explicit params always override the mode; fps can also be changed live in armed contexts via postMessage({adomLoader:{fps:N}}).
Spinner duty (use it as the brand spinner, measured cost)
Safe to use as the spinner everywhere, including a 32x32 corner of a progress
dialog. Measured at 32x32 on a 240 Hz display: one instance about 1 to 3% of a
single core; eight concurrent instances about 9% total; eight instances capped
with ?fps=30 about 2% total, and the host page held a locked 240 fps in every
case. On a 60 Hz display divide by four. It does not need to yield manually:
requestAnimationFrame schedules against vsync so the thread idles between
frames, and when the tab or webview is hidden rAF stops entirely, cost exactly
zero. For dialog spinners the recommended embed is
loader.html?bg=transparent&fps=30 (fps floor is 20, below which motion would
slow); uncapped is also fine for a single spinner.
Why raw WebGL and not Babylon or three.js
A load screen plays while the real app is still fetching. Babylon.js is roughly 1.4 MB gzipped and three.js roughly 170 KB gzipped, both needing download plus parse before the first frame; a loading animation that first loads its own engine defeats its purpose. The MAIN viewer on the wiki page is Babylon.js 9.18, where the engine earns its weight (ArcRotate camera, GPU readbacks for the fit measurement); the loader is that lab distilled.
Fidelity guarantees
tools/build-loader.pyextracts the GLSL VERBATIM from the Babylon lab at build time: one source of truth, pixel-identical rendering.- Babylon's left-handed view and projection matrices are reproduced exactly; chirality is pixel-verified (teal lobe upper-right, navy lower-left).
- Camera feel is tuned against the lab's MEASURED response: the same 12-notch wheel burst dips both viewers to the same radius (3.99 vs 3.995) and both spring home in the same beat, via a decaying inertia integral plus the lab's 6% and 5% springs made frame-rate independent, zoom toward the cursor, and a glide home before every logo so the mark never shows distorted.
Mouse control and resizing (the contract embedders rely on)
- Inert by default: a load screen never eats input.
?interact=1arms click-to-capture (teal ring + glow); click-away or blur releases. ?logo=Nsets the logo fill's peak fade-in opacity in percent (default 100). 50 ghosts it, 10 whispers it, 0 disables the fill while the surface still sculpts into the mark shape. Live: postMessage{adomLoader:{logo:N}}.?speed=Nruns the ANIMATION clocks at N percent (10 = a tenth speed). Camera springs and interaction stay real time. Live: postMessage{adomLoader:{speed:N}}.?margin=-Nbleeds: the camera moves N percent closer, so -50 doubles the mark and it runs off every edge (clamp -70, near-plane safety). Same postMessage margin channel as positive values.?orbit=Dauto-rotates the camera D degrees per second during play (try 12). The yaw glides to the nearest whole turn as sculpting starts, so the epoch always lands square-on and play resumes with no unwind. Live: postMessage{adomLoader:{orbit:D}}.?emerge=1replaces the logo intro entirely: the equation runs from t=0 and the surface condenses out of nothing (2.6 s scale and fade) straight into play, then the normal pt/dt/ct/st timeline. Off by default; it is a taste option. Keepptabove ~3 s so the first epoch never lands mid-birth.- The "click to zoom" hint pill is OFF BY DEFAULT even when armed:
production wants purity, and the teal glow already signals the armed
state. Show it with
?hint=1, or flip it live in armed contexts withpostMessage({adomLoader:{hint:true}})(false hides it again). - Zoom to cursor: the world point under the pointer is re-solved every notch so it stays pinned (measured drift 0.002 world units over six notches).
- Springiness: an inertia integral (notch deposits
vel += r*0.085, consumed by1-0.8^(dt*240)), a radius spring (1-0.94^(dt*240)toward 4.4) and a centering pull (1-0.95^(dt*240)), all frame-rate independent; tuned by driving identical 12-notch bursts at both viewers (lab dip 3.995/~200 ms, loader 3.990/~250 ms). Before every logo the framing glides home, the wheel locks through the sit, and control returns from home when the sit ends. - Resizing: size is live input. Window resize + ResizeObserver, canvases
re-backed at devicePixelRatio (cap 2), FOV axis picked by the limiting
dimension; verified 1:1 GL-buffer tracking across portrait, landscape,
square, tiny and huge containers, including continuous resizes; re-backing coalesces into the render
loop (applied and drawn in the same frame) so drags never flash blank.
resize-proof.htmlin the repo is the drag-to-resize torture rig.
Rebuilding
python3 tools/build-loader.py # loader.html from orbital-lab.html
python3 tools/build-readme.py # readme.html for the wiki page
The wiki page renders readme.html (README.md is for cloners); page-visible edits go there.
---
name: orbital-lab
description: >-
The physics behind the Adom logomark (it is a hydrogen 2p orbital,
psi = (2p_x + 2p_y)/sqrt(2)) and, for developers, the Adom loading animation:
loader.html, a single dependency-free raw-WebGL file (24,379 bytes, 9,912
gzipped, zero requests, 0.057 ms JS per frame, one draw call) that plays the
brand animation as a webview load screen. Use when someone asks why the Adom
logo is an orbital, wants the orbital demo shown, needs the Adom loading
animation or load screen for a webview, or asks how big or heavy the loader
is and what it depends on.
---
# Orbital Lab
The Adom logomark is a hydrogen 2p orbital: psi = (2p_x + 2p_y)/sqrt(2). A 2p
orbital along the [110] diagonal is an equal superposition of 2p_x and 2p_y, and
cut to the mark's quadrants it matches the logo at 99.3% IoU. The wiki page
https://wiki.adom.inc/adom/orbital-lab holds the live proof (the Overview IS the
app; the Final tab is the finished animation).
## Embed the loader (what most people come for)
`loader.html` on that page is the animation as ONE file for webview load
screens. Grab it from the page's Files tab (or `/blob/app/orbital-lab/loader.html`)
and serve it beside your app:
<iframe src="loader.html"
style="position:absolute;inset:0;width:100%;height:100%;border:0;background:#0d1117">
</iframe>
<!-- when your app is ready, fade the iframe out and remove it -->
URL params (defaults are the tuned john 1 cut): pt (4.5) free-play seconds
between epochs; dt (3) the dive onto the mark; ct (0.3) how late the outline
cheat fades in; st (0) dead-still hold; logo (100) flat-mark peak opacity in
percent; llead (1) logo fade lead seconds; tlead (1.5) teal-unification lead;
ov (1) 0 makes the cheat its own act; intro (1) 0 skips the teal intro;
interact (0) 1 arms click-to-zoom, for previews and demos only, never for real
load screens, which must stay input-transparent; margin (0) percent of the
container left empty around the mark, 80 sits it small in the middle, 20
nearly fills, zoom-in still reaches the screen and zoom-out caps at 40%
past the margined framing.
## Setting the margin (size inside the container)
`margin` is how an embedder controls how much of the container the mark
occupies; the loader always FILLS its container, margin just sizes the anim
down inside it. Implemented as camera distance, so perspective, the overlay
match and the epoch glide stay exact at every value.
- Big hero surface (a 1920x1080 webview, anim as a quiet centerpiece):
`loader.html?margin=80` , the mark sits small in the middle.
- Content page (about page, side panel): `loader.html?margin=20` , nearly fills.
- Load screen default: `loader.html` (margin 0, the classic framing).
Armed contexts (interact=1 or the wiki preview) also accept LIVE margin
changes by postMessage, no reload: post
`{adomLoader:{margin:N}}` at the loader's window and the spring carries the
camera to its new home, animated. Load screens never install this listener.
Zoom contract at any margin: zoom IN can bring the object right up to the
screen; zoom OUT is capped at 40% past the margined epoch framing
(radius <= home x 1.4); the spring always breathes back to the margined home,
and every logo lands on the exact margined framing.
## Sitting it on any surface (background)
The loader composites onto whatever it is placed over: `?bg=transparent` runs
the GL context with alpha and clears at 0, so the CONTAINER's background shows
straight through the animation, which is how you sit the mark beside a wordmark
in a toolbar or over any surface color. `?bg=HEX` (e.g. `bg=20262e`) paints a
solid instead. Default is the brand dark 0d1117. Armed contexts can switch it
live with `postMessage({adomLoader:{bg:'transparent'}})` or a hex string. The
wiki page's live viewer demonstrates this with container-background swatches.
## The numbers (why it is safe in a load path)
Measured, not estimated, on the shipped build:
- One file: 24,379 bytes on disk, 9,912 bytes gzipped, and that INCLUDES the
shaders, the 286-point logomark vectors, the 128-sample measured edge
profile, and the whole timeline.
- Zero external requests: no CDN, no fonts, no fetch. The file is its entire
dependency closure. First frame in tens of milliseconds.
- 0.057 ms of JavaScript per frame (16 coefficient cosines plus uniform
uploads, about 1.4% of a 240 Hz frame budget), then one draw call over
12,800 triangles / 6,561 vertices, all displacement in the vertex shader.
- requestAnimationFrame pauses it completely when the tab is hidden: a
backgrounded webview costs nothing.
## Modes: lightweight vs performance (pick one, done)
Two named presets so neither the developer nor the AI reasons about knobs:
- `loader.html?bg=transparent&mode=lightweight` , spinner fleets, progress
dialogs, battery: fps 30, dpr 1, seg 48 (4,608 triangles), no antialias.
Measured on an eight-spinner 32x32 fleet: about 1.7% of one core total.
- `loader.html` , mode=performance is the default (alias mode=sexy), the
full-quality look for hero surfaces and big canvases: uncapped fps, dpr up
to 2, seg 80 (12,800 triangles), antialiased. Same fleet: about 8.2%.
Does it matter? For ONE spinner, not really: requestAnimationFrame already
yields between frames and stops when hidden, so either mode is a rounding
error. It matters for fleets, battery, and big surfaces: lightweight is about
five times cheaper, and performance is visibly smoother-edged at hero sizes.
Explicit params always override the mode; fps can also be changed live in
armed contexts via postMessage({adomLoader:{fps:N}}).
## Spinner duty (use it as the brand spinner, measured cost)
Safe to use as the spinner everywhere, including a 32x32 corner of a progress
dialog. Measured at 32x32 on a 240 Hz display: one instance about 1 to 3% of a
single core; eight concurrent instances about 9% total; eight instances capped
with `?fps=30` about 2% total, and the host page held a locked 240 fps in every
case. On a 60 Hz display divide by four. It does not need to yield manually:
requestAnimationFrame schedules against vsync so the thread idles between
frames, and when the tab or webview is hidden rAF stops entirely, cost exactly
zero. For dialog spinners the recommended embed is
`loader.html?bg=transparent&fps=30` (fps floor is 20, below which motion would
slow); uncapped is also fine for a single spinner.
## Why raw WebGL and not Babylon or three.js
A load screen plays while the real app is still fetching. Babylon.js is
roughly 1.4 MB gzipped and three.js roughly 170 KB gzipped, both needing
download plus parse before the first frame; a loading animation that first
loads its own engine defeats its purpose. The MAIN viewer on the wiki page is
Babylon.js 9.18, where the engine earns its weight (ArcRotate camera, GPU
readbacks for the fit measurement); the loader is that lab distilled.
## Fidelity guarantees
- `tools/build-loader.py` extracts the GLSL VERBATIM from the Babylon lab at
build time: one source of truth, pixel-identical rendering.
- Babylon's left-handed view and projection matrices are reproduced exactly;
chirality is pixel-verified (teal lobe upper-right, navy lower-left).
- Camera feel is tuned against the lab's MEASURED response: the same
12-notch wheel burst dips both viewers to the same radius (3.99 vs 3.995)
and both spring home in the same beat, via a decaying inertia integral plus
the lab's 6% and 5% springs made frame-rate independent, zoom toward the
cursor, and a glide home before every logo so the mark never shows
distorted.
## Mouse control and resizing (the contract embedders rely on)
- Inert by default: a load screen never eats input. `?interact=1` arms
click-to-capture (teal ring + glow); click-away or blur releases.
- `?logo=N` sets the logo fill's peak fade-in opacity in percent (default
100). 50 ghosts it, 10 whispers it, 0 disables the fill while the surface
still sculpts into the mark shape. Live: postMessage `{adomLoader:{logo:N}}`.
- `?speed=N` runs the ANIMATION clocks at N percent (10 = a tenth speed).
Camera springs and interaction stay real time. Live: postMessage
`{adomLoader:{speed:N}}`.
- `?margin=-N` bleeds: the camera moves N percent closer, so -50 doubles the
mark and it runs off every edge (clamp -70, near-plane safety). Same
postMessage margin channel as positive values.
- `?orbit=D` auto-rotates the camera D degrees per second during play (try
12). The yaw glides to the nearest whole turn as sculpting starts, so the
epoch always lands square-on and play resumes with no unwind. Live:
postMessage `{adomLoader:{orbit:D}}`.
- `?emerge=1` replaces the logo intro entirely: the equation runs from t=0
and the surface condenses out of nothing (2.6 s scale and fade) straight
into play, then the normal pt/dt/ct/st timeline. Off by default; it is a
taste option. Keep `pt` above ~3 s so the first epoch never lands mid-birth.
- The "click to zoom" hint pill is OFF BY DEFAULT even when armed:
production wants purity, and the teal glow already signals the armed
state. Show it with `?hint=1`, or flip it live in armed contexts with
`postMessage({adomLoader:{hint:true}})` (false hides it again).
- Zoom to cursor: the world point under the pointer is re-solved every notch
so it stays pinned (measured drift 0.002 world units over six notches).
- Springiness: an inertia integral (notch deposits `vel += r*0.085`, consumed
by `1-0.8^(dt*240)`), a radius spring (`1-0.94^(dt*240)` toward 4.4) and a
centering pull (`1-0.95^(dt*240)`), all frame-rate independent; tuned by
driving identical 12-notch bursts at both viewers (lab dip 3.995/~200 ms,
loader 3.990/~250 ms). Before every logo the framing glides home, the wheel
locks through the sit, and control returns from home when the sit ends.
- Resizing: size is live input. Window resize + ResizeObserver, canvases
re-backed at devicePixelRatio (cap 2), FOV axis picked by the limiting
dimension; verified 1:1 GL-buffer tracking across portrait, landscape,
square, tiny and huge containers, including continuous resizes; re-backing coalesces into the render
loop (applied and drawn in the same frame) so drags never flash blank.
`resize-proof.html` in the repo is the drag-to-resize torture rig.
## Rebuilding
python3 tools/build-loader.py # loader.html from orbital-lab.html
python3 tools/build-readme.py # readme.html for the wiki page
The wiki page renders readme.html (README.md is for cloners); page-visible
edits go there.