{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "dither-stacked",
	"title": "Dither Stacked",
	"type": "registry:ui",
	"description": "Canvas dither chart from Amicro, ported to Svelte 5 and motion-sv.",
	"dependencies": [
		"@lucide/svelte",
		"runed"
	],
	"registryDependencies": [
		"https://amicro-sv.enisdev.com/r/dither-animated-value.json",
		"https://amicro-sv.enisdev.com/r/dither-math.json"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport { untrack } from 'svelte';\n\timport { prefersReducedMotion } from 'svelte/motion';\n\timport { CreditCard } from '@lucide/svelte';\n\timport { AnimationFrames } from 'runed';\n\timport DitherAnimatedValue from './DitherAnimatedValue.svelte';\n\timport { hash, clamp } from './dither-math.js';\n\n\texport interface Props {\n\t\ttheme?: 'dark' | 'light';\n\t\tcompact?: boolean;\n\t}\n\n\tlet { theme = 'dark', compact = false }: Props = $props();\n\n\tconst PERIODS = [\n\t\t{ name: 'Week', mult: 1 },\n\t\t{ name: 'Month', mult: 4 },\n\t\t{ name: 'Quarter', mult: 13 },\n\t\t{ name: 'Year', mult: 52 }\n\t];\n\n\tconst BRANCHES = ['Bishkek', 'Osh', 'Jalal-Abad', 'Karakol'];\n\tconst BANDS = [\n\t\t{ name: 'Cash', color: '#FFFFFF', share: 0.46 },\n\t\t{ name: 'QR', color: '#CBD5E1', share: 0.31 },\n\t\t{ name: 'Bank', color: '#94A3B8', share: 0.23 }\n\t];\n\n\tconst BASE_TOTAL = 150000;\n\tconst BRANCH_WEIGHTS = [0.45, 0.25, 0.18, 0.12];\n\n\tconst getAxisMax = (maxVal: number) => {\n\t\tif (maxVal === 0) return 100;\n\t\tconst target = maxVal * 1.05;\n\t\tconst power = Math.pow(10, Math.floor(Math.log10(target)));\n\t\tconst normalized = target / power;\n\t\tlet multiplier = 10;\n\t\tif (normalized <= 1.0) multiplier = 1;\n\t\telse if (normalized <= 2.0) multiplier = 2;\n\t\telse if (normalized <= 5.0) multiplier = 5;\n\n\t\treturn multiplier * power;\n\t};\n\n\tfunction drawRoundedRect(\n\t\tctx: CanvasRenderingContext2D,\n\t\tx: number,\n\t\ty: number,\n\t\tw: number,\n\t\th: number,\n\t\trTop: number,\n\t\trBottom: number\n\t) {\n\t\tctx.beginPath();\n\t\tctx.moveTo(x + rTop, y);\n\t\tctx.lineTo(x + w - rTop, y);\n\t\tctx.arcTo(x + w, y, x + w, y + rTop, rTop);\n\t\tctx.lineTo(x + w, y + h - rBottom);\n\t\tctx.arcTo(x + w, y + h, x + w - rBottom, y + h, rBottom);\n\t\tctx.lineTo(x + rBottom, y + h);\n\t\tctx.arcTo(x, y + h, x, y + h - rBottom, rBottom);\n\t\tctx.lineTo(x, y + rTop);\n\t\tctx.arcTo(x, y, x + rTop, y, rTop);\n\t\tctx.closePath();\n\t}\n\n\tlet periodIndex = $state(1); \n\tlet hoverBranch = $state<number | null>(null);\n\tlet hoverBand = $state<number | null>(null);\n\tlet canvas = $state<HTMLCanvasElement | null>(null);\n\n\tconst period = $derived(PERIODS[periodIndex]);\n\n\tconst computed = $derived.by(() => {\n\t\tlet tot = 0;\n\t\tconst branches = BRANCHES.map((b, bIdx) => {\n\t\t\tconst branchBase = BASE_TOTAL * BRANCH_WEIGHTS[bIdx];\n\t\t\tlet branchTotal = 0;\n\t\t\tconst bands = BANDS.map((band, bandIdx) => {\n\t\t\t\tconst wobble = 0.9 + 0.14 * Math.sin(bIdx * 3.1 + bandIdx * 1.7);\n\t\t\t\tconst val = Math.round(branchBase * period.mult * band.share * wobble);\n\t\t\t\tbranchTotal += Math.max(6, val);\n\t\t\t\treturn { value: Math.max(6, val) };\n\t\t\t});\n\t\t\ttot += branchTotal;\n\t\t\treturn { total: branchTotal, bands };\n\t\t});\n\n\t\tconst maxBranchTotal = Math.max(...branches.map((b) => b.total));\n\t\tconst axMax = getAxisMax(maxBranchTotal);\n\n\t\treturn { data: branches, totalValue: tot, axisMax: axMax };\n\t});\n\n\tconst data = $derived(computed.data);\n\tconst totalValue = $derived(computed.totalValue);\n\tconst axisMax = $derived(computed.axisMax);\n\n\tlet time = 0;\n\tlet drawn = new Map<string, number>();\n\tlet morphStartTime = 0;\n\tlet fromState = new Map<string, number>();\n\n\t$effect(() => {\n\n\t\tvoid data;\n\t\tfromState = new Map(drawn);\n\t\tmorphStartTime = performance.now();\n\t});\n\n\t$effect(() => {\n\t\tconst currentData = data;\n\t\tconst currentAxisMax = axisMax;\n\t\tconst el = canvas;\n\t\tif (!el) return;\n\n\t\tconst draw = () => {\n\t\t\ttime += 0.02;\n\t\t\tconst ctx = el.getContext('2d');\n\t\t\tif (!ctx) return;\n\n\t\t\tconst dpr = Math.min(window.devicePixelRatio || 1, 2);\n\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\tif (el.width !== rect.width * dpr || el.height !== rect.height * dpr) {\n\t\t\t\tel.width = rect.width * dpr;\n\t\t\t\tel.height = rect.height * dpr;\n\t\t\t}\n\n\t\t\tctx.save();\n\t\t\tctx.clearRect(0, 0, el.width, el.height);\n\t\t\tctx.scale(dpr, dpr);\n\n\t\t\tconst w = rect.width;\n\t\t\tconst h = rect.height;\n\t\t\tconst colW = w / BRANCHES.length;\n\t\t\tconst barW = Math.min(colW * 0.62, 54);\n\t\t\tconst cell = Math.max(3, Math.round(w / 200));\n\t\t\tconst HIGHLIGHT = '#FFFFFF';\n\n\t\t\tlet prog = 0;\n\t\t\tif (morphStartTime > 0) {\n\t\t\t\tprog = (performance.now() - morphStartTime) / 620;\n\t\t\t\tif (prog > 1) prog = 1;\n\t\t\t} else {\n\t\t\t\tprog = 1;\n\t\t\t}\n\n\t\t\tconst reducedMotion = untrack(() => prefersReducedMotion.current);\n\t\t\tif (reducedMotion) {\n\t\t\t\tprog = 1;\n\t\t\t\ttime = 0;\n\t\t\t}\n\n\t\t\tconst n = BRANCHES.length;\n\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tconst bp = 1 - Math.pow(1 - clamp((prog - i * 0.05) / (1 - (n - 1) * 0.05), 0, 1), 3);\n\t\t\t\tconst colX = i * colW;\n\t\t\t\tconst cx = colX + colW / 2;\n\t\t\t\tconst x0 = cx - barW / 2;\n\n\t\t\t\tlet currentY = h;\n\n\t\t\t\tfor (let j = 0; j < BANDS.length; j++) {\n\t\t\t\t\tconst key = `${i}-${j}`;\n\t\t\t\t\tconst targetH = (currentData[i].bands[j].value / currentAxisMax) * h;\n\t\t\t\t\tconst fromH = fromState.get(key) || 0;\n\t\t\t\t\tconst segH = Math.max(0.1, fromH + (targetH - fromH) * bp);\n\n\t\t\t\t\tdrawn.set(key, segH);\n\n\t\t\t\t\tconst yBottom = currentY;\n\t\t\t\t\tconst yTop = currentY - segH;\n\t\t\t\t\tconst rTop = j === BANDS.length - 1 ? 8 : 5;\n\t\t\t\t\tconst rBottom = j === 0 ? 7 : 5;\n\n\t\t\t\t\tconst hb = untrack(() => hoverBranch);\n\t\t\t\t\tconst hband = untrack(() => hoverBand);\n\n\t\t\t\t\tconst isHot = hb === i && hband === j;\n\t\t\t\t\tconst isOtherBranch = hb !== null && hb !== i;\n\t\t\t\t\tconst isOtherBandInBranch = hb === i && hband !== null && hband !== j;\n\n\t\t\t\t\tlet alpha = 1.0;\n\t\t\t\t\tif (isOtherBranch) alpha = 0.3;\n\t\t\t\t\telse if (isOtherBandInBranch) alpha = 0.48;\n\n\t\t\t\t\tconst color = isHot ? HIGHLIGHT : BANDS[j].color;\n\n\t\t\t\t\tctx.save();\n\t\t\t\t\tdrawRoundedRect(ctx, x0, yTop, barW, segH, rTop, rBottom);\n\t\t\t\t\tctx.clip();\n\n\t\t\t\t\tctx.globalAlpha = alpha * 0.85;\n\t\t\t\t\tctx.fillStyle = color;\n\n\t\t\t\t\tfor (let bx = x0; bx < x0 + barW; bx += cell) {\n\t\t\t\t\t\tfor (let by = yTop; by < yBottom; by += cell) {\n\t\t\t\t\t\t\tconst dx = bx - (x0 + barW / 2);\n\t\t\t\t\t\t\tconst dy = by - (yTop + segH / 2);\n\t\t\t\t\t\t\tconst dist = Math.sqrt(dx * dx + dy * dy);\n\n\t\t\t\t\t\t\tconst jitter = hash(bx, by);\n\t\t\t\t\t\t\tconst wave = Math.sin(dist * 0.1 - time * 2) * 0.15;\n\t\t\t\t\t\t\tconst sz = cell * (0.68 + wave + jitter * 0.2);\n\n\t\t\t\t\t\t\tctx.fillRect(bx + (cell - sz) / 2, by + (cell - sz) / 2, sz, sz);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tctx.restore();\n\t\t\t\t\tcurrentY = yTop;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.restore();\n\t\t};\n\n\t\tnew AnimationFrames(draw);\n\t});\n</script>\n\n{#if compact}\n\t<div class=\"relative w-full h-full flex items-center justify-center p-2\">\n\t\t<div class=\"relative w-full h-[120px]\">\n\t\t\t<canvas bind:this={canvas} class=\"w-full h-full block\"></canvas>\n\t\t</div>\n\t</div>\n{:else}\n\t<div\n\t\tclass=\"relative w-full rounded-3xl p-6 transition-colors border {theme === 'dark'\n\t\t\t? 'bg-[#181818] border-white/5 text-white'\n\t\t\t: 'bg-white border-neutral-200 text-black shadow-lg'}\"\n\t>\n\t\t<div\n\t\t\tclass=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-6\"\n\t\t>\n\t\t\t<div class=\"flex items-center gap-3\">\n\t\t\t\t<div\n\t\t\t\t\tclass=\"p-2.5 rounded-2xl {theme === 'dark'\n\t\t\t\t\t\t? 'bg-blue-500/10 text-blue-400'\n\t\t\t\t\t\t: 'bg-blue-50 text-blue-600'}\"\n\t\t\t\t>\n\t\t\t\t\t<CreditCard class=\"w-5 h-5\" />\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"flex items-baseline gap-2\">\n\t\t\t\t\t\t<span class=\"text-2xl font-bold tracking-tight\">\n\t\t\t\t\t\t\t<DitherAnimatedValue\n\t\t\t\t\t\t\t\tvalue={totalValue}\n\t\t\t\t\t\t\t\tformat={(current) => '$' + Math.round(current).toLocaleString('en-US')}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"text-xs {theme === 'dark' ? 'text-neutral-400' : 'text-neutral-500'}\"\n\t\t\t\t\t\t\t>total revenue</span\n\t\t\t\t\t\t>\n\t\t\t\t\t</div>\n\t\t\t\t\t<p class=\"text-xs {theme === 'dark' ? 'text-neutral-400' : 'text-neutral-500'}\">\n\t\t\t\t\t\tStacked payment channels per regional branch\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div\n\t\t\t\tclass=\"flex items-center p-1 rounded-full border text-xs font-medium {theme === 'dark'\n\t\t\t\t\t? 'bg-[#131313] border-white/10'\n\t\t\t\t\t: 'bg-neutral-100 border-neutral-200'}\"\n\t\t\t>\n\t\t\t\t{#each PERIODS as p, idx (p.name)}\n\t\t\t\t\t<button\n\t\t\t\t\t\tonclick={() => (periodIndex = idx)}\n\t\t\t\t\t\tclass=\"px-3 py-1 rounded-full transition-all cursor-pointer {periodIndex === idx\n\t\t\t\t\t\t\t? 'bg-blue-600 text-white shadow-sm'\n\t\t\t\t\t\t\t: theme === 'dark'\n\t\t\t\t\t\t\t\t? 'text-neutral-400 hover:text-white'\n\t\t\t\t\t\t\t\t: 'text-neutral-600 hover:text-black'}\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{p.name}\n\t\t\t\t\t</button>\n\t\t\t\t{/each}\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div class=\"flex items-center gap-4 mb-4 text-xs font-medium\">\n\t\t\t{#each BANDS as band, idx (band.name)}\n\t\t\t\t<div\n\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\tonmouseenter={() => (hoverBand = idx)}\n\t\t\t\t\tonmouseleave={() => (hoverBand = null)}\n\t\t\t\t\tclass=\"flex items-center gap-1.5 cursor-pointer opacity-80 hover:opacity-100\"\n\t\t\t\t>\n\t\t\t\t\t<div class=\"w-2.5 h-2.5 rounded-full\" style=\"background-color: {band.color}\"></div>\n\t\t\t\t\t<span>{band.name}</span>\n\t\t\t\t</div>\n\t\t\t{/each}\n\t\t</div>\n\n\t\t<div class=\"relative h-[200px] w-full rounded-xl overflow-hidden\">\n\t\t\t<canvas bind:this={canvas} class=\"w-full h-full block\"></canvas>\n\t\t</div>\n\n\t\t<div class=\"grid grid-cols-4 gap-2 mt-3 text-center text-xs font-semibold\">\n\t\t\t{#each BRANCHES as branch, idx (branch)}\n\t\t\t\t<div\n\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\tonmouseenter={() => (hoverBranch = idx)}\n\t\t\t\t\tonmouseleave={() => (hoverBranch = null)}\n\t\t\t\t\tclass=\"py-1.5 rounded-lg cursor-pointer transition-all {hoverBranch === idx\n\t\t\t\t\t\t? theme === 'dark'\n\t\t\t\t\t\t\t? 'bg-white/10 text-white'\n\t\t\t\t\t\t\t: 'bg-neutral-200 text-black'\n\t\t\t\t\t\t: theme === 'dark'\n\t\t\t\t\t\t\t? 'text-neutral-400 hover:text-white'\n\t\t\t\t\t\t\t: 'text-neutral-600 hover:text-black'}\"\n\t\t\t\t>\n\t\t\t\t\t{branch}\n\t\t\t\t</div>\n\t\t\t{/each}\n\t\t</div>\n\t</div>\n{/if}\n",
			"type": "registry:ui",
			"target": "amicro/DitherStackedChart.svelte"
		},
		{
			"content": "This file was ported from Amicro (React) and copied into your project by the\namicro-sv registry: https://amicro-sv.enisdev.com\n\nOriginal: https://github.com/Subhan-code/Amicro--Micro-transitions-\n\nMIT License\n\nCopyright (c) 2026 SYED SUBHAN UDDIN\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
			"type": "registry:file",
			"target": "amicro/LICENSE"
		}
	]
}