Tesseract
import { useEffect, useRef } from 'react';
const css = `
.loader-tesseract { display: block; width: 104px; height: 104px; }
.loader-tesseract__edges { fill: none; stroke: light-dark(#5f626a, #a7abb2); stroke-width: 1; stroke-linejoin: round; }
.loader-tesseract__inner { fill: none; stroke: light-dark(#373a40, #f1f2f3); stroke-width: 1.2; }
.loader-tesseract__nodes { fill: light-dark(#373a40, #f1f2f3); }
`;
// The 16 corners of a 4-cube (every sign combination of x, y, z, w) and its 32
// edges: the pairs of corners that differ in exactly one coordinate.
const V = Array.from({ length: 16 }, (_, i) => [0, 1, 2, 3].map((b) => ((i >> b) & 1 ? 1 : -1)));
const E: [number, number][] = [];
for (let i = 0; i < 16; i++) for (let b = 0; b < 4; b++) if (!(i & (1 << b))) E.push([i, i | (1 << b)]);
function project(t: number) {
const a = t * 0.9; // turn through the x–w plane: the inner cube swaps with the outer one
const c = Math.cos(a);
const s = Math.sin(a);
const b = 0.45 + t * 0.25; // and slowly through y–z, so the 3D shadow keeps moving
const cb = Math.cos(b);
const sb = Math.sin(b);
return V.map(([x, y, z, w]) => {
const x1 = x * c - w * s;
const w1 = x * s + w * c;
const y1 = y * cb - z * sb;
const z1 = y * sb + z * cb;
const k4 = 1 / (2.6 - w1); // 4D → 3D perspective
const X = x1 * k4;
const Y = y1 * k4;
const Z = z1 * k4;
const k3 = 1 / (3 - Z); // 3D → 2D perspective
return { x: 52 + X * k3 * 120, y: 52 + Y * k3 * 120, w: w1 };
});
}
export function TesseractLoader() {
const edges = useRef<SVGPathElement>(null);
const inner = useRef<SVGPathElement>(null);
const nodes = useRef<SVGGElement>(null);
useEffect(() => {
const still = matchMedia('(prefers-reduced-motion: reduce)').matches;
let raf = 0;
const draw = (now: number) => {
const p = project(still ? 0.6 : now / 1000);
let d = '';
let din = '';
for (const [i, j] of E) {
const seg = `M${p[i].x.toFixed(1)} ${p[i].y.toFixed(1)}L${p[j].x.toFixed(1)} ${p[j].y.toFixed(1)}`;
// Edges of whichever cube is nearer in w are drawn brighter.
if (p[i].w > 0 && p[j].w > 0) din += seg;
else d += seg;
}
edges.current!.setAttribute('d', d);
inner.current!.setAttribute('d', din);
[...nodes.current!.children].forEach((n, i) => {
n.setAttribute('cx', p[i].x.toFixed(1));
n.setAttribute('cy', p[i].y.toFixed(1));
n.setAttribute('r', p[i].w > 0 ? '1.8' : '1.1');
});
if (!still) raf = requestAnimationFrame(draw);
};
raf = requestAnimationFrame(draw);
return () => cancelAnimationFrame(raf);
}, []);
return (
<>
<style>
{css}
</style>
<svg className="loader-tesseract" viewBox="0 0 104 104" role="status" aria-label="Loading">
<path ref={edges} className="loader-tesseract__edges" />
<path ref={inner} className="loader-tesseract__inner" />
<g ref={nodes} className="loader-tesseract__nodes">
{V.map((_, i) => (
<circle key={i} r={1.2} />
))}
</g>
</svg>
</>
);
}Markup where the component goes, CSS in your stylesheet, script before </body>. In Vue, Svelte or Angular, call initLoaderTesseract(el) on the mounted element instead.
<svg class="loader-tesseract" viewBox="0 0 104 104" role="status" aria-label="Loading">
<path class="loader-tesseract__edges"></path>
<path class="loader-tesseract__inner"></path>
<g class="loader-tesseract__nodes">
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
<circle r="1.2"></circle>
</g>
</svg>.loader-tesseract { display: block; width: 104px; height: 104px; }
.loader-tesseract__edges { fill: none; stroke: light-dark(#5f626a, #a7abb2); stroke-width: 1; stroke-linejoin: round; }
.loader-tesseract__inner { fill: none; stroke: light-dark(#373a40, #f1f2f3); stroke-width: 1.2; }
.loader-tesseract__nodes { fill: light-dark(#373a40, #f1f2f3); }function initLoaderTesseract(root) {
var edges = root.querySelector('.loader-tesseract__edges');
var inner = root.querySelector('.loader-tesseract__inner');
var nodes = root.querySelectorAll('.loader-tesseract__nodes circle');
if (!edges || !inner || nodes.length !== 16) return;
// The 16 corners of a 4-cube (every sign combination of x, y, z, w) and its 32
// edges: the pairs of corners that differ in exactly one coordinate.
var V = [];
for (var i = 0; i < 16; i++) V.push([0, 1, 2, 3].map(function (b) { return (i >> b) & 1 ? 1 : -1; }));
var E = [];
for (var j = 0; j < 16; j++) for (var b = 0; b < 4; b++) if (!(j & (1 << b))) E.push([j, j | (1 << b)]);
function project(t) {
var c = Math.cos(t * 0.9); // turn through the x-w plane: the inner cube swaps with the outer
var s = Math.sin(t * 0.9);
var cb = Math.cos(0.45 + t * 0.25); // and slowly through y-z, so the 3D shadow keeps moving
var sb = Math.sin(0.45 + t * 0.25);
return V.map(function (v) {
var x1 = v[0] * c - v[3] * s;
var w1 = v[0] * s + v[3] * c;
var y1 = v[1] * cb - v[2] * sb;
var z1 = v[1] * sb + v[2] * cb;
var k4 = 1 / (2.6 - w1); // 4D to 3D perspective
var k3 = 1 / (3 - z1 * k4); // 3D to 2D perspective
return { x: 52 + x1 * k4 * k3 * 120, y: 52 + y1 * k4 * k3 * 120, w: w1 };
});
}
var still = matchMedia('(prefers-reduced-motion: reduce)').matches;
var raf = 0;
function draw(now) {
var p = project(still ? 0.6 : now / 1000);
var d = '';
var din = '';
E.forEach(function (e) {
var a = p[e[0]];
var q = p[e[1]];
var seg = 'M' + a.x.toFixed(1) + ' ' + a.y.toFixed(1) + 'L' + q.x.toFixed(1) + ' ' + q.y.toFixed(1);
// Edges of whichever cube is nearer in w are drawn brighter.
if (a.w > 0 && q.w > 0) din += seg;
else d += seg;
});
edges.setAttribute('d', d);
inner.setAttribute('d', din);
nodes.forEach(function (n, k) {
n.setAttribute('cx', p[k].x.toFixed(1));
n.setAttribute('cy', p[k].y.toFixed(1));
n.setAttribute('r', p[k].w > 0 ? '1.8' : '1.1');
});
if (!still) raf = requestAnimationFrame(draw);
}
raf = requestAnimationFrame(draw);
return function () { cancelAnimationFrame(raf); };
}
document.querySelectorAll('.loader-tesseract').forEach(initLoaderTesseract);Goal: Create a "Tesseract" loader.
Visual behavior: A four-dimensional cube, a tesseract, turning through the fourth axis and projected down to a wireframe. The inner and outer cubes swap places endlessly while the whole shadow drifts, and the cube nearer in 4D is drawn brighter.
Technique:
- The 16 corners are every sign combination of (x, y, z, w); the 32 edges join corners that differ in exactly one coordinate.
- Each frame the corners rotate in the x-w plane (which turns the inner cube inside out) and slowly in y-z, then go through two perspective divides: 4D to 3D (1 / (2.6 - w)) and 3D to 2D (1 / (3 - z)).
- Edges whose both ends have w > 0 go into a bright path and the rest into a dim one; corner dots grow on the near side.
- Everything is three SVG elements updated in place: two path d strings and sixteen circles.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion a single still projection 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 "Tesseract" loader.
Visual behavior: A four-dimensional cube, a tesseract, turning through the fourth axis and projected down to a wireframe. The inner and outer cubes swap places endlessly while the whole shadow drifts, and the cube nearer in 4D is drawn brighter.
Technique:
- The 16 corners are every sign combination of (x, y, z, w); the 32 edges join corners that differ in exactly one coordinate.
- Each frame the corners rotate in the x-w plane (which turns the inner cube inside out) and slowly in y-z, then go through two perspective divides: 4D to 3D (1 / (2.6 - w)) and 3D to 2D (1 / (3 - z)).
- Edges whose both ends have w > 0 go into a bright path and the rest into a dim one; corner dots grow on the near side.
- Everything is three SVG elements updated in place: two path d strings and sixteen circles.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion a single still projection 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 four-dimensional cube, a tesseract, turning through the fourth axis and projected down to a wireframe. The inner and outer cubes swap places endlessly while the whole shadow drifts, and the cube nearer in 4D is drawn brighter.
- Dependencies
- None
- Difficulty
- Advanced
- Triggers
- continuous, loop, 3d, draw
- Techniques
- svg, raf, 3d transform
- 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 a single still projection is drawn.