All loaders

CSS / SVG · 3.2 KB gzip

Comet Track

A bright comet that follows a rounded-square track and changes speed and color with state.

comettrackorbitmechanical
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/comet-track';

<orbux-comet-track state="thinking"></orbux-comet-track>

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

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

<orbux-comet-track state="thinking"></orbux-comet-track>

Component source comet-track.ts

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

/**
 * Comet Track: a bright comet tracing a rounded-square track. Fast while
 * calling a tool, medium while streaming, parked on an accent-green track on
 * `done`, red on `error`.
 */
const STYLES = /* css */ `
:host { --_dur: 2.4s; }
svg { inline-size: 100%; block-size: 100%; overflow: visible; }
.track {
  fill: none;
  stroke: color-mix(in srgb, var(--orbux-color, #6366f1) 30%, transparent);
  stroke-width: 6;
}
.comet {
  fill: none;
  stroke: var(--orbux-color-2, #a855f7);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 42 258;
  filter: drop-shadow(0 0 5px var(--orbux-color-2, #a855f7));
  animation: orbux-comet calc(var(--_dur) * var(--orbux-speed-scale)) linear infinite;
}
@keyframes orbux-comet { to { stroke-dashoffset: -300; } }

/* ---- states ---- */
:host([data-state="thinking"])     { --_dur: 2.4s; }
:host([data-state="streaming"])    { --_dur: 1.5s; }
:host([data-state="streaming"]) .comet { stroke-dasharray: 64 236; }
:host([data-state="tool-calling"]) { --_dur: 0.9s; }
:host([data-state="tool-calling"]) .comet { stroke-dasharray: 18 48; animation-timing-function: steps(12, end); }
:host([data-state="tool-calling"]) .track { stroke-dasharray: 12 10; }
:host([data-state="idle"]),
:host([data-state="waiting"])      { --_dur: 4.5s; }
:host([data-state="idle"]) .comet { animation: none; opacity: .32; stroke-dashoffset: -40; }
:host([data-state="waiting"]) .comet { animation: none; opacity: .72; stroke-dashoffset: -120; }
:host([data-state="waiting"]) .track { opacity: .85; }

:host([data-state="done"]) .comet { opacity: 0; animation:none; }
:host([data-state="done"]) .track { stroke: var(--orbux-color-success, #22c55e); }

:host([data-state="error"]) .comet { stroke: var(--orbux-color-error, #ef4444); filter: drop-shadow(0 0 5px var(--orbux-color-error, #ef4444)); animation:orbux-comet-error 200ms ease-out 1; }
:host([data-state="error"]) .track { stroke: color-mix(in srgb, var(--orbux-color-error, #ef4444) 30%, transparent); }
@keyframes orbux-comet-error { to { stroke-dashoffset:-52; } }
:host([data-settled="true"]) .comet { animation:none; }

:host([data-paused="true"]) .comet { animation-play-state: paused; }
:host([data-reduced-motion="true"]) .comet { animation: none !important; }
:host([data-reduced-motion="true"][data-state="streaming"]) .comet { stroke-dasharray:72 228; }
:host([data-reduced-motion="true"][data-state="tool-calling"]) .track { stroke-dasharray:18 8; }
:host([data-reduced-motion="true"][data-state="waiting"]) .comet { opacity:.35; }
`;

export class OrbuxCometTrack extends OrbuxElement {
  protected build(): void {
    const style = document.createElement('style');
    style.textContent = STYLES;
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 100 100');
    svg.innerHTML =
      '<rect class="track" part="track" x="16" y="16" width="68" height="68" rx="22" pathLength="300"/>' +
      '<rect class="comet" part="comet" x="16" y="16" width="68" height="68" rx="22" pathLength="300"/>';
    this.root.append(style, svg);
  }
}

if (typeof customElements !== 'undefined' && !customElements.get('orbux-comet-track')) {
  customElements.define('orbux-comet-track', OrbuxCometTrack);
}

declare global {
  interface HTMLElementTagNameMap {
    'orbux-comet-track': OrbuxCometTrack;
  }
}

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.