All loaders

CSS / SVG · 4.0 KB gzip

Crimson Sigil

A clean ocular sigil with a soft rim, rotating iris sweep, and focused pupil.

signatureanime-inspiredocularsigilenergy
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/crimson-sigil';

<orbux-crimson-sigil state="thinking"></orbux-crimson-sigil>

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

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

<orbux-crimson-sigil state="thinking"></orbux-crimson-sigil>

Component source crimson-sigil.ts

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

/**
 * Crimson Sigil: a clean ocular mark — soft rim, rotating iris sweep, and a
 * focused pupil. Restraint over ornament: theme tokens only, transform/opacity
 * motion, crisp state language.
 */
const STYLES = /* css */ `
:host {
  --_tone: var(--orbux-color, #ef3340);
  --_tone-2: var(--orbux-color-2, #fb7185);
  --_dur: 2.8s;
  --_ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --_ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
}

.sigil {
  position: relative;
  inline-size: 100%;
  block-size: 100%;
  display: grid;
  place-items: center;
}

/* Soft outer glow via box-shadow (cheaper than filter: drop-shadow on the host) */
.rim {
  position: absolute;
  inset: 10%;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--_tone) 55%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--_tone-2) 18%, transparent),
    0 0 14px color-mix(in srgb, var(--_tone) 32%, transparent);
  transition: border-color 180ms var(--_ease-out), box-shadow 180ms var(--_ease-out), opacity 160ms ease;
}

/* Rotating energy sweep — single clear motion language */
.iris {
  position: absolute;
  inset: 16%;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    transparent 0 58%,
    color-mix(in srgb, var(--_tone) 35%, transparent) 72%,
    var(--_tone-2) 88%,
    transparent 100%
  );
  -webkit-mask: radial-gradient(farthest-side, #0000 62%, #000 64%);
  mask: radial-gradient(farthest-side, #0000 62%, #000 64%);
  animation: orbux-crimson-spin calc(var(--_dur) * var(--orbux-speed-scale)) linear infinite;
}

/* Focused core — no hardcoded black; theme-aware dark */
.pupil {
  position: absolute;
  inset: 34%;
  border-radius: 50%;
  background:
    radial-gradient(
      circle at 34% 30%,
      color-mix(in srgb, var(--_tone-2) 55%, white) 0 10%,
      transparent 18%
    ),
    radial-gradient(
      circle at 50% 55%,
      color-mix(in srgb, var(--_tone) 22%, transparent) 0 42%,
      color-mix(in srgb, var(--_tone) 55%, #0a0a0b) 70%,
      color-mix(in srgb, var(--_tone) 35%, #050506) 100%
    );
  box-shadow:
    inset 0 0 0 1.5px color-mix(in srgb, var(--_tone) 45%, transparent),
    0 0 10px color-mix(in srgb, var(--_tone) 28%, transparent);
  transition:
    border-radius 180ms var(--_ease-out),
    transform 180ms var(--_ease-out),
    box-shadow 180ms var(--_ease-out),
    opacity 160ms ease;
}

/* Subtle breathing on the pupil while active */
.glint {
  position: absolute;
  inset: 38%;
  border-radius: 50%;
  background: radial-gradient(
    circle at 32% 28%,
    color-mix(in srgb, white 55%, transparent),
    transparent 55%
  );
  opacity: 0.55;
  animation: orbux-crimson-breathe calc(1.6s * var(--orbux-speed-scale)) var(--_ease-in-out) infinite;
  pointer-events: none;
}

@keyframes orbux-crimson-spin {
  to { transform: rotate(1turn); }
}
@keyframes orbux-crimson-breathe {
  0%, 100% { opacity: 0.35; transform: scale(0.96); }
  50% { opacity: 0.7; transform: scale(1); }
}
@keyframes orbux-crimson-done {
  0% { transform: scale(1); }
  50% { transform: scale(1.06); }
  100% { transform: scale(1); }
}
@keyframes orbux-crimson-error {
  0%, 100% { transform: translateX(0); }
  30% { transform: translateX(-3%); }
  60% { transform: translateX(3%); }
}

/* ---- states ---- */
:host([data-state="thinking"]) { --_dur: 2.8s; }
:host([data-state="streaming"]) { --_dur: 1.4s; }
:host([data-state="streaming"]) .rim {
  border-color: color-mix(in srgb, var(--_tone) 78%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--_tone-2) 35%, transparent),
    0 0 18px color-mix(in srgb, var(--_tone) 48%, transparent);
}
:host([data-state="streaming"]) .glint {
  animation-duration: calc(0.9s * var(--orbux-speed-scale));
}

:host([data-state="tool-calling"]) { --_dur: 1.1s; }
:host([data-state="tool-calling"]) .iris {
  animation-timing-function: steps(8, end);
  animation-direction: reverse;
}
:host([data-state="tool-calling"]) .pupil {
  border-radius: 26%;
}

:host([data-state="idle"]) .iris,
:host([data-state="idle"]) .glint { animation: none; }
:host([data-state="idle"]) .sigil { opacity: 0.5; }
:host([data-state="idle"]) .iris { opacity: 0.35; transform: rotate(24deg); }

:host([data-state="waiting"]) .iris,
:host([data-state="waiting"]) .glint { animation: none; }
:host([data-state="waiting"]) .sigil { opacity: 0.82; }
:host([data-state="waiting"]) .pupil { transform: scale(0.9); opacity: 0.88; }
:host([data-state="waiting"]) .iris { opacity: 0.55; transform: rotate(-18deg); }

: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: #fda4af;
}
:host([data-state="done"]) .iris,
:host([data-state="error"]) .iris,
:host([data-state="done"]) .glint,
:host([data-state="error"]) .glint { animation: none; }
:host([data-state="done"]) .sigil { animation: orbux-crimson-done 220ms var(--_ease-out) 1; }
:host([data-state="error"]) .sigil { animation: orbux-crimson-error 200ms var(--_ease-out) 1; }
:host([data-settled="true"]) .sigil { animation: none; }

:host([data-paused="true"]) .iris,
:host([data-paused="true"]) .glint { animation-play-state: paused; }

:host([data-reduced-motion="true"]) .iris,
:host([data-reduced-motion="true"]) .glint { animation: none !important; }
:host([data-reduced-motion="true"][data-state="thinking"]) .iris {
  opacity: 0.75;
  transform: rotate(40deg);
}
:host([data-reduced-motion="true"][data-state="streaming"]) .rim {
  border-width: 3px;
}
:host([data-reduced-motion="true"][data-state="tool-calling"]) .pupil {
  border-radius: 26%;
}
:host([data-reduced-motion="true"][data-state="waiting"]) .pupil {
  transform: scale(0.9);
  opacity: 0.7;
}
`;

export class OrbuxCrimsonSigil extends OrbuxElement {
  protected build(): void {
    const style = document.createElement('style');
    style.textContent = STYLES;
    const sigil = document.createElement('div');
    sigil.className = 'sigil';
    sigil.setAttribute('part', 'sigil');
    sigil.innerHTML =
      '<span class="rim" part="rim"></span>' +
      '<span class="iris" part="iris"></span>' +
      '<span class="pupil" part="pupil"></span>' +
      '<span class="glint" part="glint" aria-hidden="true"></span>';
    this.root.append(style, sigil);
  }
}

if (typeof customElements !== 'undefined' && !customElements.get('orbux-crimson-sigil')) {
  customElements.define('orbux-crimson-sigil', OrbuxCrimsonSigil);
}

declare global {
  interface HTMLElementTagNameMap {
    'orbux-crimson-sigil': OrbuxCrimsonSigil;
  }
}

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.