All loaders

CSS / SVG · 3.6 KB gzip

Vortex Sphere

A compressed energy sphere with counter-rotating currents and a turbulent core.

signatureanime-inspiredenergyvortexorb
Interactive preview

Usage

Download HTML

Packages are @vikast908/core and @vikast908/loaders. Prefer 0.1.1+ (0.1.0 loaders is broken on npm).

npm install @vikast908/loaders@0.1.1 @vikast908/core@0.1.1
import '@vikast908/loaders/vortex-sphere';

<orbux-vortex-sphere state="thinking"></orbux-vortex-sphere>

Browser-only alternative: Download HTML (self-contained) or load the hosted ESM bundle:

<script type="module" src="http://localhost:4321/orbux/vortex-sphere.js"></script>

<orbux-vortex-sphere state="thinking"></orbux-vortex-sphere>

Component source vortex-sphere.ts

import { OrbuxElement } from '@vikast908/core';

const STYLES = /* css */ `
:host { --_tone:var(--orbux-color,#38bdf8);--_tone-2:var(--orbux-color-2,#6366f1);--_dur:1.25s; }
.vortex { position:relative;inline-size:100%;block-size:100%;display:grid;place-items:center;filter:drop-shadow(0 0 10px color-mix(in srgb,var(--_tone) 55%,transparent)); }
.shell { position:absolute;inset:10%;border-radius:50%;background:radial-gradient(circle at 38% 32%,#fff 0 3%,color-mix(in srgb,var(--_tone) 72%,white) 5%,var(--_tone) 28%,var(--_tone-2) 62%,transparent 72%);box-shadow:inset -8px -10px 16px color-mix(in srgb,var(--_tone-2) 72%,#020617),inset 6px 5px 10px #ffffff66;animation:orbux-vortex-breathe calc(1.8s * var(--orbux-speed-scale)) cubic-bezier(.77,0,.175,1) infinite; }
.ring { position:absolute;inset:var(--_inset);border-radius:50%;background:conic-gradient(from var(--_from),transparent 0 12%,color-mix(in srgb,var(--_tone) 84%,white) 16% 23%,transparent 28% 52%,var(--_tone-2) 58% 65%,transparent 72%);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 3px),#000 calc(100% - 2px));mask:radial-gradient(farthest-side,transparent calc(100% - 3px),#000 calc(100% - 2px));animation:orbux-vortex-spin calc(var(--_dur) * var(--orbux-speed-scale)) linear infinite; }
.ring:nth-of-type(even) { animation-direction:reverse; }
.core { position:absolute;inset:37%;border-radius:50%;background:#e0f2fe;box-shadow:0 0 12px 4px color-mix(in srgb,var(--_tone) 70%,transparent); }
.streak { position:absolute;inset:19%;border-radius:50%;border-block-start:2px solid color-mix(in srgb,var(--_tone) 82%,white);rotate:var(--_angle);opacity:.72; }
@keyframes orbux-vortex-spin { to { transform:rotate(360deg); } }
@keyframes orbux-vortex-breathe { 50% { transform:scale(.94); } }
@keyframes orbux-vortex-done { 50% { transform:scale(1.06); } }
@keyframes orbux-vortex-error { 35% { transform:translateX(-4%); } 70% { transform:translateX(4%); } }
:host([data-state="streaming"]) { --_dur:.62s; }
:host([data-state="streaming"]) .shell { transform:scale(.95); }
:host([data-state="tool-calling"]) .ring { animation-timing-function:steps(6,end); }
:host([data-state="idle"]) .ring,:host([data-state="waiting"]) .ring,:host([data-state="idle"]) .shell,:host([data-state="waiting"]) .shell { animation:none; }
:host([data-state="idle"]) .vortex { opacity:.58; }
:host([data-state="waiting"]) .shell { transform:scale(.92); }
:host([data-state="done"]) { --_tone:var(--orbux-color-success,#22c55e);--_tone-2:#86efac; }
:host([data-state="error"]) { --_tone:var(--orbux-color-error,#ef4444);--_tone-2:#f97316; }
:host([data-state="done"]) .ring,:host([data-state="error"]) .ring,:host([data-state="done"]) .shell,:host([data-state="error"]) .shell { animation:none; }
:host([data-state="done"]) .vortex { animation:orbux-vortex-done 220ms cubic-bezier(.23,1,.32,1) 1; }
:host([data-state="error"]) .vortex { animation:orbux-vortex-error 200ms cubic-bezier(.23,1,.32,1) 1; }
:host([data-settled="true"]) .vortex { animation:none; }
:host([data-paused="true"]) * { animation-play-state:paused !important; }
:host([data-reduced-motion="true"]) * { animation:none !important; }
:host([data-reduced-motion="true"][data-state="streaming"]) .ring:first-of-type { transform:rotate(32deg); }
`;

export class OrbuxVortexSphere extends OrbuxElement {
  protected build(): void {
    const style = document.createElement('style');
    style.textContent = STYLES;
    const vortex = document.createElement('div');
    vortex.className = 'vortex';
    vortex.setAttribute('part', 'vortex');
    vortex.innerHTML = `<span class="shell"></span><i class="ring" style="--_inset:5%;--_from:20deg"></i><i class="ring" style="--_inset:14%;--_from:150deg"></i><i class="ring" style="--_inset:24%;--_from:260deg"></i>${Array.from({ length: 6 }, (_, index) => `<b class="streak" style="--_angle:${index * 60}deg"></b>`).join('')}<span class="core"></span>`;
    this.root.append(style, vortex);
  }
}
if (typeof customElements !== 'undefined' && !customElements.get('orbux-vortex-sphere'))
  customElements.define('orbux-vortex-sphere', OrbuxVortexSphere);
declare global {
  interface HTMLElementTagNameMap {
    'orbux-vortex-sphere': OrbuxVortexSphere;
  }
}

Depends on @vikast908/core. Paste the source, add the base element, and set state from your agent code.

Attributes & properties

NameTypeDescription
stateAgentState idle · thinking · streaming · tool-calling · waiting · done · error
sizeCSS lengthOverall square size.
--orbux-widthCSS lengthOptional inline-size override.
--orbux-heightCSS lengthOptional block-size override.
speednumberAnimation speed multiplier (1 = default).
labelstringAccessible label (defaults per state).
pausedbooleanFreeze the animation.

Respects prefers-reduced-motion, exposes status/progressbar semantics with a live label, and auto-pauses (sets data-paused) when offscreen or the tab is hidden.