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
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
| Name | Type | Description |
|---|---|---|
state | AgentState | idle · thinking · streaming · tool-calling · waiting · done · error |
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.