{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "matrix",
	"title": "Matrix",
	"type": "registry:ui",
	"description": "An ambient dot-matrix canvas animation with configurable presets.",
	"files": [
		{
			"content": "<script lang=\"ts\" module>\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport type { Frame } from \"./presets.js\";\n\n\texport type MatrixMode = \"default\" | \"vu\";\n\n\texport type MatrixProps = HTMLAttributes<HTMLDivElement> & {\n\t\t/** Number of rows in the matrix grid. */\n\t\trows: number;\n\t\t/** Number of columns in the matrix grid. */\n\t\tcols: number;\n\t\t/**\n\t\t * Static pattern to display. A 2D array of brightness values in\n\t\t * `[0, 1]`. When set, animation is disabled and `frames` is ignored.\n\t\t */\n\t\tpattern?: Frame;\n\t\t/**\n\t\t * Ordered frames to loop through for animation. Ignored when\n\t\t * `pattern` is provided.\n\t\t */\n\t\tframes?: Frame[];\n\t\t/**\n\t\t * Playback rate in frames per second when animating `frames`.\n\t\t * @default 12\n\t\t */\n\t\tfps?: number;\n\t\t/**\n\t\t * Start animating automatically on mount. Ignored when a static\n\t\t * `pattern` is provided.\n\t\t * @default true\n\t\t */\n\t\tautoplay?: boolean;\n\t\t/**\n\t\t * Loop the frame sequence. When `false`, animation halts on the last\n\t\t * frame.\n\t\t * @default true\n\t\t */\n\t\tloop?: boolean;\n\t\t/**\n\t\t * Cell diameter in pixels.\n\t\t * @default 10\n\t\t */\n\t\tsize?: number;\n\t\t/**\n\t\t * Gap between cells in pixels.\n\t\t * @default 2\n\t\t */\n\t\tgap?: number;\n\t\t/**\n\t\t * CSS colors for active and inactive cells. Defaults map `on` to the\n\t\t * current text color and `off` to the muted foreground token.\n\t\t * @default { on: \"currentColor\", off: \"var(--muted-foreground)\" }\n\t\t */\n\t\tpalette?: { on: string; off: string };\n\t\t/**\n\t\t * Global brightness multiplier applied to every cell, clamped to\n\t\t * `[0, 1]`.\n\t\t * @default 1\n\t\t */\n\t\tbrightness?: number;\n\t\t/**\n\t\t * ARIA label for the container. Falls back to `\"matrix display\"` when\n\t\t * omitted.\n\t\t */\n\t\tariaLabel?: string;\n\t\t/** Invoked whenever the active frame index changes during animation. */\n\t\tonFrame?: (index: number) => void;\n\t\t/**\n\t\t * Rendering mode. `\"vu\"` reads `levels` each render to draw a\n\t\t * bottom-anchored meter instead of `frames` or `pattern`.\n\t\t * @default \"default\"\n\t\t */\n\t\tmode?: MatrixMode;\n\t\t/**\n\t\t * Per-column level values in `[0, 1]` used when `mode=\"vu\"`. Ignored\n\t\t * in other modes.\n\t\t */\n\t\tlevels?: number[];\n\t\t/** Bound reference to the root container element. */\n\t\tref?: HTMLDivElement | null;\n\t};\n</script>\n\n<script lang=\"ts\">\n\timport { cn } from \"$UTILS$.js\";\n\timport { clamp, ensureFrameSize, vu } from \"./presets.js\";\n\n\tlet {\n\t\trows,\n\t\tcols,\n\t\tpattern,\n\t\tframes,\n\t\tfps = 12,\n\t\tautoplay = true,\n\t\tloop = true,\n\t\tsize = 10,\n\t\tgap = 2,\n\t\tpalette = { on: \"currentColor\", off: \"var(--muted-foreground)\" },\n\t\tbrightness = 1,\n\t\tariaLabel,\n\t\tonFrame,\n\t\tmode = \"default\",\n\t\tlevels,\n\t\tclass: className,\n\t\tref = $bindable(null),\n\t\t...restProps\n\t}: MatrixProps = $props();\n\n\tlet frameIndex = $state(0);\n\tlet isPlaying = $state(true);\n\tlet rafId: number | null = null;\n\tlet lastTime = 0;\n\tlet accumulator = 0;\n\n\t// Reset animation state when frames or autoplay change (mirrors React dep array)\n\t$effect(() => {\n\t\tvoid frames;\n\t\tframeIndex = 0;\n\t\tisPlaying = autoplay && !pattern;\n\t\tlastTime = 0;\n\t\taccumulator = 0;\n\t});\n\n\t// Animation loop — fixed-timestep accumulator\n\t$effect(() => {\n\t\tif (!frames || frames.length === 0 || !isPlaying) return;\n\t\tconst frameInterval = 1000 / fps;\n\t\t// Capture loop + onFrame so effect re-runs on changes\n\t\tconst currentLoop = loop;\n\t\tconst currentOnFrame = onFrame;\n\n\t\tconst animate = (currentTime: number) => {\n\t\t\tif (lastTime === 0) lastTime = currentTime;\n\t\t\tconst deltaTime = currentTime - lastTime;\n\t\t\tlastTime = currentTime;\n\t\t\taccumulator += deltaTime;\n\n\t\t\tif (accumulator >= frameInterval) {\n\t\t\t\taccumulator -= frameInterval;\n\t\t\t\tconst next = frameIndex + 1;\n\t\t\t\tif (next >= frames!.length) {\n\t\t\t\t\tif (currentLoop) {\n\t\t\t\t\t\tframeIndex = 0;\n\t\t\t\t\t\tcurrentOnFrame?.(0);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tisPlaying = false;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tframeIndex = next;\n\t\t\t\t\tcurrentOnFrame?.(next);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trafId = requestAnimationFrame(animate);\n\t\t};\n\n\t\trafId = requestAnimationFrame(animate);\n\n\t\treturn () => {\n\t\t\tif (rafId !== null) {\n\t\t\t\tcancelAnimationFrame(rafId);\n\t\t\t\trafId = null;\n\t\t\t}\n\t\t};\n\t});\n\n\tconst currentFrame = $derived.by(() => {\n\t\tif (mode === \"vu\" && levels && levels.length > 0) {\n\t\t\treturn ensureFrameSize(vu(cols, levels), rows, cols);\n\t\t}\n\t\tif (pattern) {\n\t\t\treturn ensureFrameSize(pattern, rows, cols);\n\t\t}\n\t\tif (frames && frames.length > 0) {\n\t\t\treturn ensureFrameSize(frames[frameIndex] || frames[0], rows, cols);\n\t\t}\n\t\treturn ensureFrameSize([], rows, cols);\n\t});\n\n\tconst cellPositions = $derived.by(() => {\n\t\tconst positions: { x: number; y: number }[][] = [];\n\t\tfor (let row = 0; row < rows; row++) {\n\t\t\tpositions[row] = [];\n\t\t\tfor (let col = 0; col < cols; col++) {\n\t\t\t\tpositions[row][col] = {\n\t\t\t\t\tx: col * (size + gap),\n\t\t\t\t\ty: row * (size + gap),\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn positions;\n\t});\n\n\tconst svgDimensions = $derived({\n\t\twidth: cols * (size + gap) - gap,\n\t\theight: rows * (size + gap) - gap,\n\t});\n\n\t// SVG `id` is document-global: multiple <Matrix> instances on a page would\n\t// all reference the first instance's gradients/filter. Namespace every\n\t// defined id with a stable per-instance prefix.\n\tconst uid = $props.id();\n\tconst onId = `matrix-pixel-on-${uid}`;\n\tconst offId = `matrix-pixel-off-${uid}`;\n\tconst glowId = `matrix-glow-${uid}`;\n</script>\n\n<div\n\tbind:this={ref}\n\tdata-slot=\"matrix\"\n\trole=\"img\"\n\taria-label={ariaLabel ?? \"matrix display\"}\n\tclass={cn(\"relative inline-block\", className)}\n\tstyle=\"--matrix-on: {palette.on}; --matrix-off: {palette.off}; --matrix-gap: {gap}px; --matrix-size: {size}px;\"\n\t{...restProps}\n>\n\t<svg\n\t\twidth={svgDimensions.width}\n\t\theight={svgDimensions.height}\n\t\tviewBox=\"0 0 {svgDimensions.width} {svgDimensions.height}\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\tclass=\"block\"\n\t\tstyle=\"overflow: visible;\"\n\t>\n\t\t<defs>\n\t\t\t<radialGradient id={onId} cx=\"50%\" cy=\"50%\" r=\"50%\">\n\t\t\t\t<stop offset=\"0%\" stop-color=\"var(--matrix-on)\" stop-opacity=\"1\" />\n\t\t\t\t<stop offset=\"70%\" stop-color=\"var(--matrix-on)\" stop-opacity=\"0.85\" />\n\t\t\t\t<stop offset=\"100%\" stop-color=\"var(--matrix-on)\" stop-opacity=\"0.6\" />\n\t\t\t</radialGradient>\n\t\t\t<radialGradient id={offId} cx=\"50%\" cy=\"50%\" r=\"50%\">\n\t\t\t\t<stop offset=\"0%\" stop-color=\"var(--matrix-off)\" stop-opacity=\"1\" />\n\t\t\t\t<stop offset=\"100%\" stop-color=\"var(--matrix-off)\" stop-opacity=\"0.7\" />\n\t\t\t</radialGradient>\n\t\t\t<filter id={glowId} x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n\t\t\t\t<feGaussianBlur stdDeviation=\"2\" result=\"blur\" />\n\t\t\t\t<feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"over\" />\n\t\t\t</filter>\n\t\t</defs>\n\n\t\t{#each currentFrame as row, rowIndex (rowIndex)}\n\t\t\t{#each row as value, colIndex (colIndex)}\n\t\t\t\t{@const pos = cellPositions[rowIndex]?.[colIndex]}\n\t\t\t\t{#if pos}\n\t\t\t\t\t{@const opacity = clamp(brightness * value)}\n\t\t\t\t\t{@const isActive = opacity > 0.5}\n\t\t\t\t\t{@const isOn = opacity > 0.05}\n\t\t\t\t\t<circle\n\t\t\t\t\t\tclass={cn(\"matrix-pixel\", !isOn && \"opacity-20 dark:opacity-[0.1]\")}\n\t\t\t\t\t\tcx={pos.x + size / 2}\n\t\t\t\t\t\tcy={pos.y + size / 2}\n\t\t\t\t\t\tr={(size / 2) * 0.9}\n\t\t\t\t\t\tfill={isOn ? `url(#${onId})` : `url(#${offId})`}\n\t\t\t\t\t\topacity={isOn ? opacity : 0.1}\n\t\t\t\t\t\tstyle=\"transform: scale({isActive ? 1.1 : 1}); filter: {isActive\n\t\t\t\t\t\t\t? `url(#${glowId})`\n\t\t\t\t\t\t\t: 'none'};\"\n\t\t\t\t\t/>\n\t\t\t\t{/if}\n\t\t\t{/each}\n\t\t{/each}\n\t</svg>\n</div>\n\n<style>\n\t:global(.matrix-pixel) {\n\t\ttransition:\n\t\t\topacity 300ms ease-out,\n\t\t\ttransform 150ms ease-out,\n\t\t\tfilter 150ms ease-out;\n\t\ttransform-origin: center;\n\t\ttransform-box: fill-box;\n\t}\n</style>\n",
			"type": "registry:ui",
			"target": "matrix/matrix.svelte"
		},
		{
			"content": "export type Frame = number[][];\n\nexport function clamp(value: number): number {\n\treturn Math.max(0, Math.min(1, value));\n}\n\nexport function ensureFrameSize(frame: Frame, rows: number, cols: number): Frame {\n\tconst result: Frame = [];\n\tfor (let r = 0; r < rows; r++) {\n\t\tconst row = frame[r] || [];\n\t\tresult.push([]);\n\t\tfor (let c = 0; c < cols; c++) {\n\t\t\tresult[r][c] = row[c] ?? 0;\n\t\t}\n\t}\n\treturn result;\n}\n\nexport function emptyFrame(rows: number, cols: number): Frame {\n\treturn Array.from({ length: rows }, () => Array(cols).fill(0));\n}\n\nexport function setPixel(frame: Frame, row: number, col: number, value: number): void {\n\tif (row >= 0 && row < frame.length && col >= 0 && col < frame[0].length) {\n\t\tframe[row][col] = value;\n\t}\n}\n\nexport const digits: Frame[] = [\n\t[\n\t\t[0, 1, 1, 1, 0],\n\t\t[1, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 1, 1, 1, 0],\n\t],\n\t[\n\t\t[0, 0, 1, 0, 0],\n\t\t[0, 1, 1, 0, 0],\n\t\t[0, 0, 1, 0, 0],\n\t\t[0, 0, 1, 0, 0],\n\t\t[0, 0, 1, 0, 0],\n\t\t[0, 0, 1, 0, 0],\n\t\t[0, 1, 1, 1, 0],\n\t],\n\t[\n\t\t[0, 1, 1, 1, 0],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 0, 0, 0, 1],\n\t\t[0, 0, 0, 1, 0],\n\t\t[0, 0, 1, 0, 0],\n\t\t[0, 1, 0, 0, 0],\n\t\t[1, 1, 1, 1, 1],\n\t],\n\t[\n\t\t[0, 1, 1, 1, 0],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 0, 0, 0, 1],\n\t\t[0, 0, 1, 1, 0],\n\t\t[0, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 1, 1, 1, 0],\n\t],\n\t[\n\t\t[0, 0, 0, 1, 0],\n\t\t[0, 0, 1, 1, 0],\n\t\t[0, 1, 0, 1, 0],\n\t\t[1, 0, 0, 1, 0],\n\t\t[1, 1, 1, 1, 1],\n\t\t[0, 0, 0, 1, 0],\n\t\t[0, 0, 0, 1, 0],\n\t],\n\t[\n\t\t[1, 1, 1, 1, 1],\n\t\t[1, 0, 0, 0, 0],\n\t\t[1, 1, 1, 1, 0],\n\t\t[0, 0, 0, 0, 1],\n\t\t[0, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 1, 1, 1, 0],\n\t],\n\t[\n\t\t[0, 1, 1, 1, 0],\n\t\t[1, 0, 0, 0, 0],\n\t\t[1, 0, 0, 0, 0],\n\t\t[1, 1, 1, 1, 0],\n\t\t[1, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 1, 1, 1, 0],\n\t],\n\t[\n\t\t[1, 1, 1, 1, 1],\n\t\t[0, 0, 0, 0, 1],\n\t\t[0, 0, 0, 1, 0],\n\t\t[0, 0, 1, 0, 0],\n\t\t[0, 1, 0, 0, 0],\n\t\t[0, 1, 0, 0, 0],\n\t\t[0, 1, 0, 0, 0],\n\t],\n\t[\n\t\t[0, 1, 1, 1, 0],\n\t\t[1, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 1, 1, 1, 0],\n\t\t[1, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 1, 1, 1, 0],\n\t],\n\t[\n\t\t[0, 1, 1, 1, 0],\n\t\t[1, 0, 0, 0, 1],\n\t\t[1, 0, 0, 0, 1],\n\t\t[0, 1, 1, 1, 1],\n\t\t[0, 0, 0, 0, 1],\n\t\t[0, 0, 0, 0, 1],\n\t\t[0, 1, 1, 1, 0],\n\t],\n];\n\nexport const chevronLeft: Frame = [\n\t[0, 0, 0, 1, 0],\n\t[0, 0, 1, 0, 0],\n\t[0, 1, 0, 0, 0],\n\t[0, 0, 1, 0, 0],\n\t[0, 0, 0, 1, 0],\n];\n\nexport const chevronRight: Frame = [\n\t[0, 1, 0, 0, 0],\n\t[0, 0, 1, 0, 0],\n\t[0, 0, 0, 1, 0],\n\t[0, 0, 1, 0, 0],\n\t[0, 1, 0, 0, 0],\n];\n\nexport const loader: Frame[] = (() => {\n\tconst frames: Frame[] = [];\n\tconst size = 7;\n\tconst center = 3;\n\tconst radius = 2.5;\n\n\tfor (let frame = 0; frame < 12; frame++) {\n\t\tconst f = emptyFrame(size, size);\n\t\tfor (let i = 0; i < 8; i++) {\n\t\t\tconst angle = (frame / 12) * Math.PI * 2 + (i / 8) * Math.PI * 2;\n\t\t\tconst x = Math.round(center + Math.cos(angle) * radius);\n\t\t\tconst y = Math.round(center + Math.sin(angle) * radius);\n\t\t\tconst brightness = 1 - i / 10;\n\t\t\tsetPixel(f, y, x, Math.max(0.2, brightness));\n\t\t}\n\t\tframes.push(f);\n\t}\n\n\treturn frames;\n})();\n\nexport const pulse: Frame[] = (() => {\n\tconst frames: Frame[] = [];\n\tconst size = 7;\n\tconst center = 3;\n\n\tfor (let frame = 0; frame < 16; frame++) {\n\t\tconst f = emptyFrame(size, size);\n\t\tconst phase = (frame / 16) * Math.PI * 2;\n\t\tconst intensity = (Math.sin(phase) + 1) / 2;\n\n\t\tsetPixel(f, center, center, 1);\n\n\t\tconst radius = Math.floor((1 - intensity) * 3) + 1;\n\t\tfor (let dy = -radius; dy <= radius; dy++) {\n\t\t\tfor (let dx = -radius; dx <= radius; dx++) {\n\t\t\t\tconst dist = Math.sqrt(dx * dx + dy * dy);\n\t\t\t\tif (Math.abs(dist - radius) < 0.7) {\n\t\t\t\t\tsetPixel(f, center + dy, center + dx, intensity * 0.6);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tframes.push(f);\n\t}\n\n\treturn frames;\n})();\n\nexport function vu(columns: number, levels: number[]): Frame {\n\tconst rows = 7;\n\tconst frame = emptyFrame(rows, columns);\n\n\tfor (let col = 0; col < Math.min(columns, levels.length); col++) {\n\t\tconst level = Math.max(0, Math.min(1, levels[col]));\n\t\tconst height = Math.floor(level * rows);\n\n\t\tfor (let row = 0; row < rows; row++) {\n\t\t\tconst rowFromBottom = rows - 1 - row;\n\t\t\tif (rowFromBottom < height) {\n\t\t\t\tlet brightness = 1;\n\t\t\t\tif (row < rows * 0.3) {\n\t\t\t\t\tbrightness = 1;\n\t\t\t\t} else if (row < rows * 0.6) {\n\t\t\t\t\tbrightness = 0.8;\n\t\t\t\t} else {\n\t\t\t\t\tbrightness = 0.6;\n\t\t\t\t}\n\t\t\t\tframe[row][col] = brightness;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn frame;\n}\n\nexport const wave: Frame[] = (() => {\n\tconst frames: Frame[] = [];\n\tconst rows = 7;\n\tconst cols = 7;\n\n\tfor (let frame = 0; frame < 24; frame++) {\n\t\tconst f = emptyFrame(rows, cols);\n\t\tconst phase = (frame / 24) * Math.PI * 2;\n\n\t\tfor (let col = 0; col < cols; col++) {\n\t\t\tconst colPhase = (col / cols) * Math.PI * 2;\n\t\t\tconst height = Math.sin(phase + colPhase) * 2.5 + 3.5;\n\t\t\tconst row = Math.floor(height);\n\n\t\t\tif (row >= 0 && row < rows) {\n\t\t\t\tsetPixel(f, row, col, 1);\n\t\t\t\tconst frac = height - row;\n\t\t\t\tif (row > 0) setPixel(f, row - 1, col, 1 - frac);\n\t\t\t\tif (row < rows - 1) setPixel(f, row + 1, col, frac);\n\t\t\t}\n\t\t}\n\n\t\tframes.push(f);\n\t}\n\n\treturn frames;\n})();\n\nexport const snake: Frame[] = (() => {\n\tconst frames: Frame[] = [];\n\tconst rows = 7;\n\tconst cols = 7;\n\tconst path: Array<[number, number]> = [];\n\n\tlet x = 0;\n\tlet y = 0;\n\tlet dx = 1;\n\tlet dy = 0;\n\n\tconst visited = new Set<string>();\n\twhile (path.length < rows * cols) {\n\t\tpath.push([y, x]);\n\t\tvisited.add(`${y},${x}`);\n\n\t\tconst nextX = x + dx;\n\t\tconst nextY = y + dy;\n\n\t\tif (\n\t\t\tnextX >= 0 &&\n\t\t\tnextX < cols &&\n\t\t\tnextY >= 0 &&\n\t\t\tnextY < rows &&\n\t\t\t!visited.has(`${nextY},${nextX}`)\n\t\t) {\n\t\t\tx = nextX;\n\t\t\ty = nextY;\n\t\t} else {\n\t\t\tconst newDx = -dy;\n\t\t\tconst newDy = dx;\n\t\t\tdx = newDx;\n\t\t\tdy = newDy;\n\n\t\t\tconst nextX = x + dx;\n\t\t\tconst nextY = y + dy;\n\n\t\t\tif (\n\t\t\t\tnextX >= 0 &&\n\t\t\t\tnextX < cols &&\n\t\t\t\tnextY >= 0 &&\n\t\t\t\tnextY < rows &&\n\t\t\t\t!visited.has(`${nextY},${nextX}`)\n\t\t\t) {\n\t\t\t\tx = nextX;\n\t\t\t\ty = nextY;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst snakeLength = 5;\n\tfor (let frame = 0; frame < path.length; frame++) {\n\t\tconst f = emptyFrame(rows, cols);\n\n\t\tfor (let i = 0; i < snakeLength; i++) {\n\t\t\tconst idx = frame - i;\n\t\t\tif (idx >= 0 && idx < path.length) {\n\t\t\t\tconst [y, x] = path[idx];\n\t\t\t\tconst brightness = 1 - i / snakeLength;\n\t\t\t\tsetPixel(f, y, x, brightness);\n\t\t\t}\n\t\t}\n\n\t\tframes.push(f);\n\t}\n\n\treturn frames;\n})();\n",
			"type": "registry:ui",
			"target": "matrix/presets.ts"
		},
		{
			"content": "import Root from \"./matrix.svelte\";\n\nexport {\n\tRoot,\n\t//\n\tRoot as Matrix,\n};\nexport type { MatrixProps, MatrixMode } from \"./matrix.svelte\";\nexport type { Frame } from \"./presets.js\";\nexport { chevronLeft, chevronRight, digits, loader, pulse, snake, vu, wave } from \"./presets.js\";\n",
			"type": "registry:ui",
			"target": "matrix/index.ts"
		}
	]
}