WebGL · 6.0 KB gzip
Energy Core
A pulsing hot core with a turbulent corona of flares: intense while streaming, unstable red on error, calm green on done. Supports a progress ring.
coreenergysuncoronawebglpremium
Interactive preview
Usage
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/energy-core'; <orbux-energy-core state="thinking" size="160px"></orbux-energy-core>
Browser-only alternative: Download HTML (self-contained) or load the hosted ESM bundle:
<script type="module" src="http://localhost:4321/orbux/energy-core.js"></script> <orbux-energy-core state="thinking" size="160px"></orbux-energy-core>
Component source energy-core.ts
import { type AgentState, lerp, type OrbuxFrameInfo, ShaderElement } from '@vikast908/core';
const FRAG = /* glsl */ `
precision highp float;
uniform vec2 uRes;
uniform float uTime;
uniform vec3 uColorA; // outer / corona
uniform vec3 uColorB; // hot inner
uniform float uActivity;
uniform float uState;
uniform float uProgress;
uniform vec3 uProgressColor;
float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7)))*43758.5453); }
float noise(vec2 p){
vec2 i=floor(p), f=fract(p);
float a=hash(i), b=hash(i+vec2(1,0)), c=hash(i+vec2(0,1)), d=hash(i+vec2(1,1));
vec2 u=f*f*(3.0-2.0*f);
return mix(a,b,u.x)+(c-a)*u.y*(1.0-u.x)+(d-b)*u.x*u.y;
}
float fbm(vec2 p){ float v=0.0,a=0.5; for(int i=0;i<4;i++){ v+=a*noise(p); p*=2.0; a*=0.5;} return v; }
void main(){
vec2 uv=(gl_FragCoord.xy*2.0-uRes)/min(uRes.x,uRes.y);
float r=length(uv); float ang=atan(uv.y,uv.x); float t=uTime;
// turbulent corona: noise flares modulated by angle & time
float flare=fbm(vec2(ang*2.5, t*0.9)) + fbm(vec2(ang*5.0 - t*0.6, t*0.4))*0.5;
float coronaR=0.34 + flare*0.5*(0.5+uActivity);
float corona=smoothstep(coronaR, coronaR-0.4, r)*(0.55+0.45*uActivity);
// pulsing hot core
float pulse=0.85+0.15*sin(t*3.0);
float core=smoothstep(0.34, 0.0, r)*pulse;
vec3 col=mix(uColorA, uColorB, core);
col=mix(col, uColorA, corona*0.5);
col+=vec3(core*0.85);
float glow=smoothstep(1.25,0.0,r)*0.3*uActivity;
float alpha=clamp(core + corona + glow, 0.0, 1.0);
if(uState>3.5&&uState<4.5) alpha*=smoothstep(0.16,0.27,r);
if(uState>2.5&&uState<3.5) alpha*=0.78+0.22*step(0.3,abs(sin(ang*4.0)));
if(uProgress>=0.0){
float a01=mod(atan(uv.x,uv.y)+6.2831853,6.2831853)/6.2831853;
float ring=smoothstep(0.03,0.0,abs(r-0.9));
float f=ring*step(a01,uProgress);
col=mix(col, uProgressColor, f*0.9);
alpha=max(alpha, f);
}
gl_FragColor=vec4(col, alpha);
}
`;
type RGB = [number, number, number];
interface Visual {
act: number;
a: RGB;
b: RGB;
}
const STATE_VISUALS: Record<AgentState, Visual> = {
idle: { act: 0.15, a: [0.3, 0.25, 0.6], b: [0.6, 0.5, 0.95] },
thinking: { act: 0.5, a: [0.4, 0.3, 0.9], b: [0.8, 0.6, 1.0] },
streaming: { act: 0.95, a: [0.3, 0.6, 1.0], b: [0.75, 0.95, 1.0] },
'tool-calling': { act: 0.72, a: [0.1, 0.7, 0.6], b: [0.6, 1.0, 0.8] },
waiting: { act: 0.2, a: [0.35, 0.35, 0.6], b: [0.6, 0.6, 0.85] },
done: { act: 0.3, a: [0.15, 0.6, 0.35], b: [0.6, 1.0, 0.7] },
error: { act: 0.6, a: [0.7, 0.15, 0.2], b: [1.0, 0.5, 0.3] },
};
/** Energy Core: a pulsing hot core with a turbulent corona of flares. */
export class OrbuxEnergyCore extends ShaderElement {
#act = 0.5;
#a: RGB = [0.4, 0.3, 0.9];
#b: RGB = [0.8, 0.6, 1.0];
protected fragmentSource(): string {
return FRAG;
}
protected override timeScale(): number {
return 0.4 + this.#act;
}
protected override fallbackBackground(): string {
return 'background:radial-gradient(circle at 50% 50%,#ffffff,var(--orbux-color-2,#f0abfc) 30%,var(--orbux-color,#6366f1) 75%);';
}
protected paint(gl: WebGLRenderingContext, info: OrbuxFrameInfo): void {
const v = STATE_VISUALS[info.state];
let a = v.a;
let b = v.b;
if (info.state === 'done') {
a = this.cssColorRGB('--orbux-color-success', v.a);
b = v.b;
} else if (info.state === 'error') {
a = this.cssColorRGB('--orbux-color-error', v.a);
b = v.b;
} else {
a = this.cssColorRGB('--orbux-color', v.a);
b = this.cssColorRGB('--orbux-color-2', v.b);
}
const rate = info.state === 'done' || info.state === 'error' ? 18 : 3.5;
const k = info.dt === 0 ? 1 : 1 - Math.exp(-rate * info.dt);
this.#act = lerp(this.#act, v.act, k);
for (let i = 0; i < 3; i++) {
this.#a[i] = lerp(this.#a[i] ?? 0, a[i] ?? 0, k);
this.#b[i] = lerp(this.#b[i] ?? 0, b[i] ?? 0, k);
}
gl.uniform3fv(this.uniform('uColorA'), this.#a);
gl.uniform3fv(this.uniform('uColorB'), this.#b);
gl.uniform1f(this.uniform('uActivity'), this.#act);
}
}
if (typeof customElements !== 'undefined' && !customElements.get('orbux-energy-core')) {
customElements.define('orbux-energy-core', OrbuxEnergyCore);
}
declare global {
interface HTMLElementTagNameMap {
'orbux-energy-core': OrbuxEnergyCore;
}
}
Depends on @vikast908/core. Paste the source, add the base
element, and set state from your agent code.
Attributes & properties
| Name | Type | Description |
|---|---|---|
state | AgentState | idle · thinking · streaming · tool-calling · waiting · done · error |
progress | number (0 to 1) | Determinate progress from 0 to 1. |
size | CSS length | Overall square size. |
--orbux-width | CSS length | Optional inline-size override. |
--orbux-height | CSS length | Optional block-size override. |
speed | number | Animation speed multiplier (1 = default). |
label | string | Accessible label (defaults per state). |
paused | boolean | Freeze 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.