A single-component thinking indicator: a satellite orbits a center that morphs through chaos → pyramid → dot → cube while the AI is working, then resolves to a futurist A when done. Vanilla HTML, CSS, and a small JS animator. No frameworks, no build step.
The component takes one of three states: idle, thinking, done. Idle shows just the moon orbiting a faded ring (use it as a presence cue when Anton is awake but waiting). Thinking runs the full morph cycle. Done freezes into the futurist A with a confirming ring.
The indicator is a scalable SVG. Set its rendered size with the --om-size CSS variable (defaults to 18px). Recommended sizes: 16–18px inline next to text, 24–32px in compact UI affordances, 72–200px+ for hero/loading states.
Two themes only. The accent is the only animated color; the orbit ring sits at low opacity in the faded color.
#0E0F10#6B6F73#1F9CB0#F2F6FF#8A97AErgb(34 211 238)The thinking state runs two clocks in lockstep:
1400ms (idle: 4500ms).4800ms total — chaos → pyramid → dot → cube — each stage grows in then shrinks out (easeInOutQuad) before the next appears.stroke-dasharray 0→50 over 450ms), and the orbit ring locks at 35% opacity.All paths are authored in a 0 0 24 24 viewBox so the indicator scales cleanly to any size. Center is (12, 12). Orbit radius is 8.5. Pyramid + cube rotate to "face" the satellite — they share the orbit angle so the composition feels physically connected.
Drop a span anywhere. Set theme + state via data attributes; size via the --om-size CSS variable. The animator script (below) will fill in the SVG on load and on attribute changes.
<span class="orbit-morph" data-theme="light" <!-- light | dark --> data-state="thinking" <!-- idle | thinking | done --> style="--om-size: 24px"></span>
To switch states at runtime, just toggle the attribute:
const el = document.querySelector('.orbit-morph'); el.dataset.state = 'thinking'; // start working // …later… el.dataset.state = 'done'; // resolve to the A
Three rules. The dark-theme glow is a CSS filter so it runs on the GPU and scales with size.
/* size variable + base layout */ .orbit-morph { display: inline-block; } /* dark-theme accent glow when active */ .orbit-morph[data-theme="dark"][data-state="thinking"], .orbit-morph[data-theme="dark"][data-state="done"] { filter: drop-shadow(0 0 calc(var(--om-size, 18px) * 0.18) rgba(34, 211, 238, 0.55)); } /* idle is quieter */ .orbit-morph[data-state="idle"] svg { opacity: 0.7; }
One requestAnimationFrame loop per indicator. The SVG is rebuilt each frame from the current (angle, phase). The full implementation is below — drop it in a <script> tag at the end of the page (it auto-mounts every .orbit-morph on the page and watches for attribute changes).
Note: the same script is what's powering the live previews on this page — view source to inspect.
Everything you need is in this single HTML file. Copy the three sections (HTML markup, CSS rule block, and the <script> at the end) into your app. No npm install. No external assets.