{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "pixel-canvas", "title": "Pixel Canvas", "author": "designbycode", "dependencies": [], "registryDependencies": [], "files": [ { "path": "resources/js/registry/new-york/lib/pixel-canvas.ts", "content": "export type PixelShape = 'square' | 'circle' | 'diamond' | 'star' | 'hexagon';\n\nexport type AnimationType =\n | 'radial'\n | 'wave'\n | 'random'\n | 'diagonal'\n | 'spiral';\n\nexport type AnimationDirection = 'appear' | 'disappear';\n\nexport interface PixelConfig {\n /** Array of colors for pixels */\n colors: string[];\n /** Gap between pixels in pixels */\n gap: number;\n /** Animation speed (0-100) */\n speed: number;\n /** Minimum pixel size */\n minSize: number;\n /** Maximum pixel size */\n maxSize: number;\n /** Shimmer intensity (0-1) */\n shimmerIntensity: number;\n /** Shape of pixels */\n shape: PixelShape;\n /** Animation pattern type */\n animationType: AnimationType;\n /** Whether to disable focus events */\n noFocus: boolean;\n}\n\nexport interface PixelState {\n x: number;\n y: number;\n color: string;\n size: number;\n maxSize: number;\n minSize: number;\n speed: number;\n delay: number;\n sizeStep: number;\n counter: number;\n counterStep: number;\n isIdle: boolean;\n isReverse: boolean;\n isShimmer: boolean;\n}\n\nexport const defaultPixelConfig: PixelConfig = {\n colors: ['#f8fafc', '#f1f5f9', '#cbd5e1'],\n gap: 6,\n speed: 35,\n minSize: 0.5,\n maxSize: 2,\n shimmerIntensity: 0.5,\n shape: 'square',\n animationType: 'radial',\n noFocus: false,\n};\n\n// Preset color palettes\nexport const colorPresets = {\n slate: ['#f8fafc', '#f1f5f9', '#cbd5e1'],\n blue: ['#dbeafe', '#93c5fd', '#3b82f6'],\n emerald: ['#d1fae5', '#6ee7b7', '#10b981'],\n amber: ['#fef3c7', '#fcd34d', '#f59e0b'],\n rose: ['#ffe4e6', '#fda4af', '#f43f5e'],\n violet: ['#ede9fe', '#c4b5fd', '#8b5cf6'],\n cyan: ['#cffafe', '#67e8f9', '#06b6d4'],\n sunset: ['#fef3c7', '#fdba74', '#f97316'],\n ocean: ['#cffafe', '#7dd3fc', '#0ea5e9'],\n forest: ['#dcfce7', '#86efac', '#22c55e'],\n neon: ['#f0fdf4', '#4ade80', '#22d3ee'],\n midnight: ['#1e293b', '#334155', '#475569'],\n};\n\nexport function getRandomValue(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n}\n\nexport function clampValue(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/** Maximum delay cap in milliseconds to ensure responsive animations */\nconst MAX_DELAY = 1200;\n\n/**\n * Calculate delay based on animation type\n * All delays are capped at MAX_DELAY to ensure responsive animations\n */\nexport function calculateDelay(\n x: number,\n y: number,\n canvasWidth: number,\n canvasHeight: number,\n animationType: AnimationType,\n): number {\n let delay: number;\n\n switch (animationType) {\n case 'radial': {\n const dx = x - canvasWidth / 2;\n const dy = y - canvasHeight / 2;\n delay = Math.sqrt(dx * dx + dy * dy);\n break;\n }\n case 'wave': {\n delay = x + y * 0.5;\n break;\n }\n case 'random': {\n delay =\n Math.random() *\n Math.min(MAX_DELAY, Math.max(canvasWidth, canvasHeight));\n break;\n }\n case 'diagonal': {\n delay = (x + y) * 0.7;\n break;\n }\n case 'spiral': {\n const dx = x - canvasWidth / 2;\n const dy = y - canvasHeight / 2;\n const angle = Math.atan2(dy, dx);\n const distance = Math.sqrt(dx * dx + dy * dy);\n delay = distance + angle * 50;\n break;\n }\n default:\n delay = 0;\n }\n\n // Normalize delay to be within 0 to MAX_DELAY range\n const maxRawDelay = Math.sqrt(\n canvasWidth * canvasWidth + canvasHeight * canvasHeight,\n );\n const normalizedDelay = (delay / maxRawDelay) * MAX_DELAY;\n\n return Math.min(normalizedDelay, MAX_DELAY);\n}\n\n/**\n * Draw pixel with specified shape\n */\nexport function drawPixel(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n size: number,\n maxSize: number,\n color: string,\n shape: PixelShape,\n): void {\n // Ensure size is never negative\n const safeSize = Math.max(0, size);\n\n if (safeSize <= 0) {\n return;\n }\n\n const centerOffset = maxSize * 0.5 - safeSize * 0.5;\n const cx = x + centerOffset + safeSize / 2;\n const cy = y + centerOffset + safeSize / 2;\n\n ctx.fillStyle = color;\n\n switch (shape) {\n case 'square':\n ctx.fillRect(\n x + centerOffset,\n y + centerOffset,\n safeSize,\n safeSize,\n );\n break;\n\n case 'circle':\n ctx.beginPath();\n ctx.arc(cx, cy, safeSize / 2, 0, Math.PI * 2);\n ctx.fill();\n break;\n\n case 'diamond':\n ctx.beginPath();\n ctx.moveTo(cx, cy - safeSize / 2);\n ctx.lineTo(cx + safeSize / 2, cy);\n ctx.lineTo(cx, cy + safeSize / 2);\n ctx.lineTo(cx - safeSize / 2, cy);\n ctx.closePath();\n ctx.fill();\n break;\n\n case 'star': {\n const spikes = 5;\n const outerRadius = safeSize / 2;\n const innerRadius = safeSize / 4;\n ctx.beginPath();\n\n for (let i = 0; i < spikes * 2; i++) {\n const radius = i % 2 === 0 ? outerRadius : innerRadius;\n const angle = (i * Math.PI) / spikes - Math.PI / 2;\n const px = cx + Math.cos(angle) * radius;\n const py = cy + Math.sin(angle) * radius;\n\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n\n ctx.closePath();\n ctx.fill();\n break;\n }\n\n case 'hexagon': {\n const sides = 6;\n const radius = safeSize / 2;\n ctx.beginPath();\n\n for (let i = 0; i < sides; i++) {\n const angle = (i * Math.PI * 2) / sides - Math.PI / 2;\n const px = cx + Math.cos(angle) * radius;\n const py = cy + Math.sin(angle) * radius;\n\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n\n ctx.closePath();\n ctx.fill();\n break;\n }\n }\n}\n\n/**\n * Create initial pixel state\n */\nexport function createPixelState(\n x: number,\n y: number,\n color: string,\n delay: number,\n speed: number,\n minSize: number,\n maxSize: number,\n canvasWidth: number,\n canvasHeight: number,\n): PixelState {\n return {\n x,\n y,\n color,\n size: 0,\n maxSize: getRandomValue(minSize, maxSize),\n minSize,\n speed: getRandomValue(0.1, 0.9) * speed,\n delay,\n sizeStep: Math.random() * 0.4,\n counter: 0,\n counterStep: Math.random() * 4 + (canvasWidth + canvasHeight) * 0.01,\n isIdle: false,\n isReverse: false,\n isShimmer: false,\n };\n}\n\n/**\n * Update pixel state for appear animation\n */\nexport function updatePixelAppear(\n pixel: PixelState,\n shimmerIntensity: number,\n): PixelState {\n const updated = { ...pixel, isIdle: false };\n\n if (updated.counter <= updated.delay) {\n updated.counter += updated.counterStep;\n\n return updated;\n }\n\n if (updated.size >= updated.maxSize) {\n updated.isShimmer = true;\n }\n\n if (updated.isShimmer) {\n return updatePixelShimmer(updated, shimmerIntensity);\n } else {\n updated.size += updated.sizeStep;\n }\n\n return updated;\n}\n\n/**\n * Update pixel state for disappear animation\n */\nexport function updatePixelDisappear(pixel: PixelState): PixelState {\n const updated = { ...pixel, isShimmer: false, counter: 0 };\n\n if (updated.size <= 0) {\n updated.isIdle = true;\n updated.size = 0;\n\n return updated;\n }\n\n updated.size = Math.max(0, updated.size - 0.1);\n\n return updated;\n}\n\n/**\n * Update pixel shimmer effect\n */\nexport function updatePixelShimmer(\n pixel: PixelState,\n intensity: number,\n): PixelState {\n const updated = { ...pixel };\n const shimmerSpeed = updated.speed * intensity;\n\n // Ensure minSize is at least 0.1 to prevent negative sizes\n const safeMinSize = Math.max(0.1, updated.minSize);\n\n if (updated.size >= updated.maxSize) {\n updated.isReverse = true;\n } else if (updated.size <= safeMinSize) {\n updated.isReverse = false;\n updated.size = safeMinSize; // Ensure we don't go below minSize\n }\n\n if (updated.isReverse) {\n updated.size -= shimmerSpeed;\n } else {\n updated.size += shimmerSpeed;\n }\n\n // Clamp size to prevent negative values and keep within bounds\n updated.size = Math.max(\n safeMinSize,\n Math.min(updated.size, updated.maxSize * 1.2),\n );\n\n return updated;\n}\n", "type": "registry:lib" } ], "meta": { "category": "lib", "version": "1.0.0" }, "type": "registry:lib" }