Components / KATAYABURI / Kaleidoscope
Kaleidoscope beta
Draw once, get ten drawings: a pointer sketch mirrored into a living mandala.
It draws alone until you take the pen. Then it is your fault, ten times.
<div class="k-kal" data-kal role="img" aria-label="A kaleidoscope drawing stage"> <canvas aria-hidden="true"></canvas> <div class="k-kal-bar"><button class="k-kal-btn" type="button" data-kal-clear>Clear</button></div> </div> <p style="font-size:12.5px;color:var(--k-ink-3);margin-top:9px">It draws alone until you take the pen. Then it is your fault, ten times.</p>
/* KATA Kaleidoscope — KATAYABURI. Draw once, get ten. */
.k-kal{background:var(--k-zinc-950);border-radius:14px;padding:12px;font-family:var(--k-font)}
.k-kal canvas{display:block;width:100%;height:262px;border-radius:8px;touch-action:none;cursor:crosshair}
.k-kal-bar{display:flex;justify-content:center;margin-top:10px}
.k-kal-btn{border:1px solid var(--k-zinc-700);background:none;color:var(--k-zinc-300);font-family:inherit;font-size:12.5px;font-weight:550;padding:6px 14px;border-radius:99px;cursor:pointer}
.k-kal-btn:hover{color:var(--k-on-color);border-color:var(--k-zinc-500)}
// KATA Kaleidoscope — one stroke, ten reflections.
document.querySelectorAll('[data-kal]').forEach(function (stage) {
var canvas = stage.querySelector('canvas');
var ctx = canvas.getContext('2d');
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var SEG = 10, W, H, cx, cy, drawing = false, prev = null, auto = !reduced, t = 0;
var paper = getComputedStyle(stage).getPropertyValue('--k-on-color').trim() || '#ffffff';
var accent = getComputedStyle(stage).getPropertyValue('--k-accent').trim() || '#0e9594';
function size() {
W = canvas.clientWidth; H = canvas.clientHeight;
canvas.width = W; canvas.height = H;
cx = W / 2; cy = H / 2;
ctx.fillStyle = '#09090b'; ctx.fillRect(0, 0, W, H);
}
function stroke(a, b, color) {
ctx.strokeStyle = color; ctx.lineWidth = 2.4; ctx.lineCap = 'round';
for (var i = 0; i < SEG; i++) {
var ang = (Math.PI * 2 / SEG) * i;
var flip = i % 2 === 1;
[false, true].forEach(function (mirror) {
var s = (flip ? -1 : 1) * (mirror ? -1 : 1);
ctx.beginPath();
ctx.moveTo(cx + rot(a, ang, s).x, cy + rot(a, ang, s).y);
ctx.lineTo(cx + rot(b, ang, s).x, cy + rot(b, ang, s).y);
ctx.stroke();
});
}
}
function rot(p, ang, sx) {
var dx = (p.x - cx) * sx, dy = p.y - cy;
return { x: dx * Math.cos(ang) - dy * Math.sin(ang), y: dx * Math.sin(ang) + dy * Math.cos(ang) };
}
function autoPen() {
if (!auto) return;
t += .016;
var p = { x: cx + Math.sin(t * 1.9) * W * .3, y: cy + Math.sin(t * 2.7) * H * .32 };
if (prev) stroke(prev, p, Math.floor(t * 2) % 2 ? paper : accent);
prev = p;
requestAnimationFrame(autoPen);
}
function pos(e) { var r = canvas.getBoundingClientRect(); return { x: e.clientX - r.left, y: e.clientY - r.top }; }
canvas.addEventListener('pointerdown', function (e) { auto = false; drawing = true; prev = pos(e); canvas.setPointerCapture(e.pointerId); });
canvas.addEventListener('pointermove', function (e) { if (!drawing) return; var p = pos(e); stroke(prev, p, paper); prev = p; });
canvas.addEventListener('pointerup', function () { drawing = false; });
stage.querySelector('[data-kal-clear]').addEventListener('click', function () { ctx.fillStyle = '#09090b'; ctx.fillRect(0, 0, W, H); });
size();
window.addEventListener('resize', size);
if (!reduced) requestAnimationFrame(autoPen);
});
Installation
CLI
node kata-ui.mjs add kaleidoscope
Writes the part’s CSS and JS into your project. New to the CLI? See the CLI doc.
Manual
- Load
tokens.cssonce, before any part. - Copy the CSS tab into your stylesheet.
- Copy the HTML where it belongs. The part has a script — include it after the markup; it initializes itself.
Usage
The stage draws by itself until you touch it — a slow lissajous pen — then your strokes take over, mirrored across ten segments. The Clear button is the only adult in the room. Screenshots of it make excellent album covers and terrible documentation.
Accessibility
A drawing toy: the canvas carries a plain label, and nothing on any sane page may depend on what gets drawn. Under prefers-reduced-motion the pen rests and the stage waits, blank and patient, for a hand.
Anatomy
The public surface of this part, read from its own source. Class names are the API — the CSS tab above is the full contract.
.k-kal | class |
.k-kal-bar | class |
.k-kal-btn | class |
data-kal | data hook |
data-kal-clear | data hook |
aria-label | ARIA |
aria-hidden | ARIA |
Related parts
Bounce Letters beta
A headline whose letters hop when touched — and do the wave when you click.
Carousel Ring beta
A merry-go-round of cards in real 3D: eight panels riding a ring that never stops turning.
Confetti Cannon beta
A button that fires confetti out of both corners of its stage. You already pressed it twice.
Fireworks beta
Click the night and it blooms: rockets, bursts and falling sparks on one canvas.