Plasma Globe
import { useEffect, useState } from 'react';
const css = `
.loader-plasma-globe { display: block; width: 108px; height: 120px; overflow: visible; }
.loader-plasma-globe__glass {
fill: color-mix(in oklab, light-dark(#373a40, #f1f2f3) 4%, transparent);
stroke: light-dark(#6d7179, #7d8087);
stroke-width: 1;
}
.loader-plasma-globe__shine { fill: none; stroke: color-mix(in oklab, light-dark(#373a40, #f1f2f3) 35%, transparent); stroke-width: 1.5; stroke-linecap: round; }
.loader-plasma-globe__base { fill: light-dark(#ebedf0, #232529); stroke: light-dark(#cbced3, #393c41); stroke-width: 1; }
.loader-plasma-globe__bolt {
fill: none;
stroke: light-dark(#373a40, #f1f2f3);
stroke-width: 1;
stroke-linejoin: round;
filter: drop-shadow(0 0 2px light-dark(#373a40, #f1f2f3)) drop-shadow(0 0 5px light-dark(#50535b, #c6c8cd));
}
.loader-plasma-globe__core { fill: light-dark(#373a40, #f1f2f3); filter: drop-shadow(0 0 6px light-dark(#373a40, #f1f2f3)); }
.loader-plasma-globe__strike { fill: light-dark(#373a40, #f1f2f3); }
`;
const C = { x: 54, y: 54 };
const R = 44;
/** A wandering filament from the core to a point on the glass at angle `a`. */
function filament(a: number) {
const end = { x: C.x + Math.cos(a) * (R - 1), y: C.y + Math.sin(a) * (R - 1) };
let d = `M${C.x} ${C.y}`;
const n = 10;
for (let i = 1; i < n; i++) {
const t = i / n;
// Perpendicular wander, strongest mid-way and growing toward the glass.
const k = Math.sin(t * Math.PI) * 6 + t * 2;
const nx = -Math.sin(a);
const ny = Math.cos(a);
const off = (Math.random() - 0.5) * 2 * k;
d += ` L${(C.x + (end.x - C.x) * t + nx * off).toFixed(1)} ${(C.y + (end.y - C.y) * t + ny * off).toFixed(1)}`;
}
return { d: `${d} L${end.x.toFixed(1)} ${end.y.toFixed(1)}`, end };
}
export function PlasmaGlobeLoader() {
const [bolts, setBolts] = useState<{ d: string; end: { x: number; y: number } }[]>([]);
useEffect(() => {
const still = matchMedia('(prefers-reduced-motion: reduce)').matches;
// Each filament's foot drifts round the glass at its own pace.
const feet = [0.3, 1.5, 2.6, 3.9, 5.1].map((a, i) => ({ a, v: (i % 2 ? -1 : 1) * (0.25 + i * 0.07) }));
let last = performance.now();
const step = () => {
const now = performance.now();
const dt = (now - last) / 1000;
last = now;
feet.forEach((f) => (f.a += f.v * dt + (Math.random() - 0.5) * 0.05));
setBolts(feet.map((f) => filament(f.a)));
};
step();
if (still) return;
const id = window.setInterval(step, 70);
return () => clearInterval(id);
}, []);
return (
<>
<style>
{css}
</style>
<svg className="loader-plasma-globe" viewBox="0 0 108 120" role="status" aria-label="Loading">
<circle className="loader-plasma-globe__glass" cx={C.x} cy={C.y} r={R} />
{bolts.map((b, i) => (
<g key={i}>
<path className="loader-plasma-globe__bolt" d={b.d} />
<circle className="loader-plasma-globe__strike" cx={b.end.x} cy={b.end.y} r={1.6} />
</g>
))}
<circle className="loader-plasma-globe__core" cx={C.x} cy={C.y} r={5} />
<path className="loader-plasma-globe__shine" d="M30 28a32 32 0 0 1 14-9" />
<path className="loader-plasma-globe__base" d="M38 96h32l6 16H32z" />
</svg>
</>
);
}Markup where the component goes, CSS in your stylesheet, script before </body>. In Vue, Svelte or Angular, call initLoaderPlasmaGlobe(el) on the mounted element instead.
<svg class="loader-plasma-globe" viewBox="0 0 108 120" role="status" aria-label="Loading">
<circle class="loader-plasma-globe__glass" cx="54" cy="54" r="44"></circle>
<circle class="loader-plasma-globe__core" cx="54" cy="54" r="5"></circle>
<path class="loader-plasma-globe__shine" d="M30 28a32 32 0 0 1 14-9"></path>
<path class="loader-plasma-globe__base" d="M38 96h32l6 16H32z"></path>
</svg>.loader-plasma-globe { display: block; width: 108px; height: 120px; overflow: visible; }
.loader-plasma-globe__glass {
fill: color-mix(in oklab, light-dark(#373a40, #f1f2f3) 4%, transparent);
stroke: light-dark(#6d7179, #7d8087);
stroke-width: 1;
}
.loader-plasma-globe__shine { fill: none; stroke: color-mix(in oklab, light-dark(#373a40, #f1f2f3) 35%, transparent); stroke-width: 1.5; stroke-linecap: round; }
.loader-plasma-globe__base { fill: light-dark(#ebedf0, #232529); stroke: light-dark(#cbced3, #393c41); stroke-width: 1; }
.loader-plasma-globe__bolt {
fill: none;
stroke: light-dark(#373a40, #f1f2f3);
stroke-width: 1;
stroke-linejoin: round;
filter: drop-shadow(0 0 2px light-dark(#373a40, #f1f2f3)) drop-shadow(0 0 5px light-dark(#50535b, #c6c8cd));
}
.loader-plasma-globe__core { fill: light-dark(#373a40, #f1f2f3); filter: drop-shadow(0 0 6px light-dark(#373a40, #f1f2f3)); }
.loader-plasma-globe__strike { fill: light-dark(#373a40, #f1f2f3); }function initLoaderPlasmaGlobe(root) {
var core = root.querySelector('.loader-plasma-globe__core');
if (!core) return;
var NS = 'http://www.w3.org/2000/svg';
var C = { x: 54, y: 54 };
var R = 44;
var group = document.createElementNS(NS, 'g');
root.insertBefore(group, core);
// A wandering filament from the core to a point on the glass at angle a.
function filament(a) {
var end = { x: C.x + Math.cos(a) * (R - 1), y: C.y + Math.sin(a) * (R - 1) };
var d = 'M' + C.x + ' ' + C.y;
for (var i = 1; i < 10; i++) {
var t = i / 10;
// Sideways wander, strongest mid-way and growing toward the glass.
var k = Math.sin(t * Math.PI) * 6 + t * 2;
var off = (Math.random() - 0.5) * 2 * k;
d += ' L' + (C.x + (end.x - C.x) * t - Math.sin(a) * off).toFixed(1) + ' ' + (C.y + (end.y - C.y) * t + Math.cos(a) * off).toFixed(1);
}
return { d: d + ' L' + end.x.toFixed(1) + ' ' + end.y.toFixed(1), end: end };
}
// Each filament's foot drifts round the glass at its own pace.
var feet = [0.3, 1.5, 2.6, 3.9, 5.1].map(function (a, i) { return { a: a, v: (i % 2 ? -1 : 1) * (0.25 + i * 0.07) }; });
var last = performance.now();
function step() {
var now = performance.now();
var dt = (now - last) / 1000;
last = now;
group.textContent = '';
feet.forEach(function (f) {
f.a += f.v * dt + (Math.random() - 0.5) * 0.05;
var b = filament(f.a);
var path = document.createElementNS(NS, 'path');
path.setAttribute('class', 'loader-plasma-globe__bolt');
path.setAttribute('d', b.d);
var dot = document.createElementNS(NS, 'circle');
dot.setAttribute('class', 'loader-plasma-globe__strike');
dot.setAttribute('cx', b.end.x.toFixed(1));
dot.setAttribute('cy', b.end.y.toFixed(1));
dot.setAttribute('r', '1.6');
group.appendChild(path);
group.appendChild(dot);
});
}
step();
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var timer = setInterval(step, 70);
return function () {
clearInterval(timer);
group.remove();
};
}
document.querySelectorAll('.loader-plasma-globe').forEach(initLoaderPlasmaGlobe);Goal: Create a "Plasma Globe" loader.
Visual behavior: A plasma globe: live filaments crawl from the glowing electrode out to the glass, each wandering and flickering, their feet drifting slowly round the sphere where they strike it.
Technique:
- An SVG globe: a faint glass circle with a shine stroke, a base and a glowing core.
- Five filaments run from the core to a point on the glass; each is a ten-point polyline pushed sideways at random, most in the middle and toward the glass.
- An interval regenerates them every 70ms; each foot's angle drifts at its own speed and direction with a little jitter, and a small dot marks where it strikes.
- Stacked drop-shadow filters give the filaments their glow.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion one still set of filaments is drawn.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Plasma Globe" loader.
Visual behavior: A plasma globe: live filaments crawl from the glowing electrode out to the glass, each wandering and flickering, their feet drifting slowly round the sphere where they strike it.
Technique:
- An SVG globe: a faint glass circle with a shine stroke, a base and a glowing core.
- Five filaments run from the core to a point on the glass; each is a ten-point polyline pushed sideways at random, most in the middle and toward the glass.
- An interval regenerates them every 70ms; each foot's angle drifts at its own speed and direction with a little jitter, and a small dot marks where it strikes.
- Stacked drop-shadow filters give the filaments their glow.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion one still set of filaments is drawn.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
A plasma globe: live filaments crawl from the glowing electrode out to the glass, each wandering and flickering, their feet drifting slowly round the sphere where they strike it.
- Dependencies
- None
- Difficulty
- Advanced
- Triggers
- continuous, glow, ambient
- Techniques
- svg, interval, filter
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion one still set of filaments is drawn.