@yeonseong/magic-loading
v0.2.2
Published
Transform boring loading spinners into unique fantasy magic circles generated from text
Maintainers
Readme
magic-loading
Turn boring loading spinners into unique fantasy magic circles.
Each text string deterministically generates a distinct magic circle with runes, alchemical symbols, concentric rings, and polygons — all rendered as resolution-independent SVG.

Features
- Deterministic — same text always produces the same magic circle
- Framework-agnostic — Web Component + Programmatic API
- Lightweight — ~5KB gzipped, zero dependencies
- SVG-based — crisp at any resolution
- Animated — determinate progress, indeterminate pulsing, burst completion
- Customizable — CSS custom properties for color override
Install
npm install @yeonseong/magic-loadingQuick Start
1. Import
// ESM
import '@yeonseong/magic-loading';
// CommonJS
require('@yeonseong/magic-loading');2. Use as Web Component
<magic-loading text="Loading..." progress="0.5" size="120"></magic-loading>That's it! The component auto-registers on import.
Usage
Web Component
<script type="module">
import '@yeonseong/magic-loading';
</script>
<!-- Determinate: progress bar style (0~1) -->
<magic-loading text="Uploading file" progress="0.7" size="150"></magic-loading>
<!-- Indeterminate: pulsating animation (omit progress) -->
<magic-loading text="Connecting..." size="150"></magic-loading>Attributes
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| text | string | — | Text to hash into a magic circle (required) |
| progress | number \| null | null | Progress 0–1. Omit for indeterminate mode |
| size | number | 120 | Width/height in pixels |
text— Different text produces a completely different magic circle. Use it to visually distinguish loading states (e.g."Uploading"vs"Processing").progress— Set0to1to animate layers sequentially. Omit entirely for a looping pulse animation.size— SVG scales cleanly to any size.
Methods
const el = document.querySelector('magic-loading');
// Trigger burst completion animation
await el.complete();Dynamic Updates
const el = document.querySelector('magic-loading');
// Update progress
el.setAttribute('progress', '0.8');
// Change text → regenerates the entire circle
el.setAttribute('text', 'Almost done...');
// Switch to indeterminate mode
el.removeAttribute('progress');Programmatic API
For more control, use create():
import { create } from '@yeonseong/magic-loading';
// Mount into a container element
const loader = create(document.getElementById('my-container'), {
text: 'Loading...',
size: 160,
progress: 0, // optional: omit for indeterminate
});
// Update progress (e.g. from fetch/upload callback)
loader.setProgress(0.5);
// Change text (regenerates the circle)
loader.setText('Almost done...');
// Burst completion animation (returns a Promise)
await loader.complete();
// Remove from DOM
loader.destroy();CDN (no build step)
<script src="https://unpkg.com/@yeonseong/magic-loading/dist/index.global.js"></script>
<div id="loader"></div>
<script>
const loader = MagicLoading.create(
document.getElementById('loader'),
{ text: 'Hello', size: 120 }
);
</script>Framework Examples
React
import '@yeonseong/magic-loading';
function Loader({ text, progress }) {
return <magic-loading text={text} progress={progress} size={120} />;
}Vue
<script setup>
import '@yeonseong/magic-loading';
</script>
<template>
<magic-loading :text="text" :progress="progress" size="120" />
</template>Svelte
<script>
import '@yeonseong/magic-loading';
export let text;
export let progress;
</script>
<magic-loading {text} {progress} size="120" />Customization
Override colors with CSS custom properties:
magic-loading {
--ml-color-primary: #ff6b6b;
--ml-color-secondary: #ffd93d;
--ml-color-glow: #ff6b6b;
}| Property | Default | Description |
|----------|---------|-------------|
| --ml-color-primary | Auto (from text hash) | Ring strokes, glyphs, center symbol |
| --ml-color-secondary | Auto (from text hash) | Inner polygon strokes |
| --ml-color-glow | Auto (from text hash) | Glow filter color |
By default, colors are deterministically derived from the text hash — each text gets its own unique color palette. Use CSS custom properties to override when you need brand-consistent colors.
Scoped overrides
/* All loaders on the page */
magic-loading {
--ml-color-primary: #00ffcc;
}
/* Only loaders inside .dark-panel */
.dark-panel magic-loading {
--ml-color-primary: #ff00ff;
--ml-color-secondary: #8800ff;
}Animation Modes
Determinate (progress set)
Layers activate sequentially as progress increases from 0 to 1:
| Progress | Effect | |----------|--------| | 0% | All layers dim (0.15 opacity), slow rotation | | ~25% | Outer ring fully visible | | ~50% | Second ring activates, glow intensifies | | ~75% | Inner polygon appears | | 100% | Everything at full brightness, fast rotation |
Indeterminate (progress omitted)
All layers pulse with a sine-wave rhythm. Constant rotation, gentle glow oscillation.
Burst (.complete())
A ~0.5s flash — all layers max brightness, rotation accelerates, glow flares to 3x, then fades out.
How It Works
- Input text is hashed with FNV-1a to produce deterministic parameters
- Parameters control: ring count, polygon sides, glyph set (runes / alchemy / zodiac / geometric), colors, rotation directions
- SVG is generated with concentric rings, glyphs, inner polygons, and a central symbol
- Animation adapts to progress — layers activate sequentially, rotation accelerates, glow intensifies
Glyph Sets
| Set | Symbols | |-----|---------| | Elder Futhark | ᚠ ᚢ ᚦ ᚨ ᚱ ᚲ ᚷ ᚹ ... | | Alchemy | ☉ ☽ ☿ ♀ ♂ ♃ ♄ △ ... | | Zodiac | ♈ ♉ ♊ ♋ ♌ ♍ ♎ ♏ ... | | Geometric | ✡ ⬡ ◈ △ ☆ ✧ ◇ ◉ ... |
The glyph set is automatically selected based on text hash. Each text produces a unique combination.
License
MIT
