{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ascii-converter",
  "type": "registry:block",
  "title": "Ascii converter",
  "description": "Ascii converter",
  "files": [
    {
      "path": "components/usages/asciiconverterusage.tsx",
      "content": "import AsciiConverter from \"@/registry/open-source/ascii-converter\";\r\n\r\nexport default function Page() {\r\n\treturn <AsciiConverter />;\r\n}\r\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/asciiconverterusage.tsx",
      "content": "import AsciiConverter from \"@/registry/open-source/ascii-converter\";\r\n\r\nexport default function Page() {\r\n\treturn <AsciiConverter />;\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/ascii-converter.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport { useEffect, useRef, useState, type ChangeEvent } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Slider } from \"@/components/ui/slider\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Download, GripVertical, Upload } from \"lucide-react\";\n\n// Credit:\n// https://v0.app/chat/image-to-ascii-pvp9Kq4jLgZ\n\n// Define a type for colored ASCII characters\ntype ColoredChar = {\n\tchar: string;\n\tcolor: string;\n};\n\nexport default function AsciiConverter() {\n\tconst [resolution, setResolution] = useState(0.11);\n\tconst [inverted, setInverted] = useState(false);\n\tconst [grayscale, setGrayscale] = useState(true);\n\tconst [charSet, setCharSet] = useState(\"standard\");\n\tconst [loading, setLoading] = useState(true);\n\tconst [imageLoaded, setImageLoaded] = useState(false);\n\tconst [asciiArt, setAsciiArt] = useState<string>(\"\");\n\tconst [coloredAsciiArt, setColoredAsciiArt] = useState<ColoredChar[][]>([]);\n\tconst [leftPanelWidth, setLeftPanelWidth] = useState(25); // percentage\n\tconst [isDragging, setIsDragging] = useState(false);\n\tconst [isDraggingFile, setIsDraggingFile] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst [isDesktop, setIsDesktop] = useState(false);\n\tconst [isHydrated, setIsHydrated] = useState(false);\n\tconst [sidebarNarrow, setSidebarNarrow] = useState(false);\n\tconst containerRef = useRef<HTMLDivElement>(null);\n\tconst canvasRef = useRef<HTMLCanvasElement>(null);\n\tconst imageRef = useRef<HTMLImageElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement>(null);\n\tconst previewRef = useRef<HTMLDivElement>(null);\n\t// Add a new ref for the output canvas\n\tconst outputCanvasRef = useRef<HTMLCanvasElement>(null);\n\n\tconst charSets = {\n\t\tstandard: \" .:-=+*#%@\",\n\t\tdetailed: \" .,:;i1tfLCG08@\",\n\t\tblocks: \" ░▒▓█\",\n\t\tminimal: \" .:█\",\n\t};\n\n\t// Set hydration state\n\tuseEffect(() => {\n\t\tsetIsHydrated(true);\n\t}, []);\n\n\tuseEffect(() => {\n\t\tif (!isHydrated) return;\n\n\t\t// Check if we're on the client side\n\t\tsetIsDesktop(window.innerWidth >= 768);\n\n\t\t// Add resize listener\n\t\tconst handleResize = () => {\n\t\t\tconst newIsDesktop = window.innerWidth >= 768;\n\t\t\tsetIsDesktop(newIsDesktop);\n\n\t\t\t// Reset panel width if switching between mobile and desktop\n\t\t\tif (newIsDesktop !== isDesktop) {\n\t\t\t\tsetLeftPanelWidth(25); // Reset to default when switching layouts\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"resize\", handleResize);\n\n\t\t// Load default image\n\t\tloadDefaultImage();\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"resize\", handleResize);\n\t\t};\n\t}, [isDesktop, isHydrated]);\n\n\t// Check if sidebar is narrow\n\tuseEffect(() => {\n\t\tif (!isHydrated || !isDesktop) return;\n\n\t\t// Check if sidebar is narrow (less than 200px)\n\t\tconst checkSidebarWidth = () => {\n\t\t\tif (containerRef.current) {\n\t\t\t\tconst containerWidth = containerRef.current.clientWidth;\n\t\t\t\tconst sidebarWidth = (leftPanelWidth / 100) * containerWidth;\n\t\t\t\tsetSidebarNarrow(sidebarWidth < 350);\n\t\t\t}\n\t\t};\n\n\t\tcheckSidebarWidth();\n\n\t\t// Add resize listener to check sidebar width\n\t\twindow.addEventListener(\"resize\", checkSidebarWidth);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"resize\", checkSidebarWidth);\n\t\t};\n\t}, [leftPanelWidth, isHydrated, isDesktop]);\n\n\tuseEffect(() => {\n\t\tif (imageLoaded && imageRef.current) {\n\t\t\tconvertToAscii();\n\t\t}\n\t}, [resolution, inverted, grayscale, charSet, imageLoaded]);\n\n\tuseEffect(() => {\n\t\tconst handleMouseMove = (e: MouseEvent) => {\n\t\t\tif (isDragging && containerRef.current) {\n\t\t\t\tconst containerRect = containerRef.current.getBoundingClientRect();\n\t\t\t\tconst newLeftWidth =\n\t\t\t\t\t((e.clientX - containerRect.left) / containerRect.width) * 100;\n\n\t\t\t\t// Limit the minimum width of each panel to 20%\n\t\t\t\tif (newLeftWidth >= 20 && newLeftWidth <= 80) {\n\t\t\t\t\tsetLeftPanelWidth(newLeftWidth);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst handleMouseUp = () => {\n\t\t\tsetIsDragging(false);\n\t\t};\n\n\t\tif (isDragging) {\n\t\t\tdocument.addEventListener(\"mousemove\", handleMouseMove);\n\t\t\tdocument.addEventListener(\"mouseup\", handleMouseUp);\n\t\t}\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMouseMove);\n\t\t\tdocument.removeEventListener(\"mouseup\", handleMouseUp);\n\t\t};\n\t}, [isDragging]);\n\n\tconst startDragging = () => {\n\t\tsetIsDragging(true);\n\t};\n\n\tconst loadDefaultImage = () => {\n\t\tsetLoading(true);\n\t\tsetError(null);\n\t\tsetImageLoaded(false);\n\n\t\t// Create a new image element\n\t\tconst img = new Image();\n\t\timg.crossOrigin = \"anonymous\";\n\n\t\timg.onload = () => {\n\t\t\tif (img.width === 0 || img.height === 0) {\n\t\t\t\tsetError(\"Invalid image dimensions\");\n\t\t\t\tsetLoading(false);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\timageRef.current = img;\n\t\t\tsetImageLoaded(true);\n\t\t\tsetLoading(false);\n\t\t};\n\n\t\timg.onerror = () => {\n\t\t\tsetError(\"Failed to load image\");\n\t\t\tsetLoading(false);\n\t\t};\n\n\t\t// Set the source after setting up event handlers\n\t\timg.src = \"/itjustworks.jpg\";\n\t};\n\n\tconst loadImage = (src: string) => {\n\t\tsetLoading(true);\n\t\tsetError(null);\n\t\tsetImageLoaded(false);\n\n\t\t// Create a new image element\n\t\tconst img = new Image();\n\t\timg.crossOrigin = \"anonymous\";\n\n\t\timg.onload = () => {\n\t\t\tif (img.width === 0 || img.height === 0) {\n\t\t\t\tsetError(\"Invalid image dimensions\");\n\t\t\t\tsetLoading(false);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\timageRef.current = img;\n\t\t\tsetImageLoaded(true);\n\t\t\tsetLoading(false);\n\t\t};\n\n\t\timg.onerror = () => {\n\t\t\tsetError(\"Failed to load image\");\n\t\t\tsetLoading(false);\n\t\t};\n\n\t\t// Set the source after setting up event handlers\n\t\timg.src = src;\n\t};\n\n\tconst handleFileUpload = (file: File) => {\n\t\tif (!file.type.startsWith(\"image/\")) {\n\t\t\tsetError(\"Please upload an image file\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst reader = new FileReader();\n\t\treader.onload = (e) => {\n\t\t\tif (e.target?.result) {\n\t\t\t\tloadImage(e.target.result as string);\n\t\t\t}\n\t\t};\n\t\treader.onerror = () => {\n\t\t\tsetError(\"Failed to read file\");\n\t\t};\n\t\treader.readAsDataURL(file);\n\t};\n\n\tconst handleFileInputChange = (e: ChangeEvent<HTMLInputElement>) => {\n\t\tif (e.target.files && e.target.files[0]) {\n\t\t\thandleFileUpload(e.target.files[0]);\n\t\t}\n\t};\n\n\tconst handleDragOver = (e: React.DragEvent) => {\n\t\te.preventDefault();\n\t\tsetIsDraggingFile(true);\n\t};\n\n\tconst handleDragLeave = () => {\n\t\tsetIsDraggingFile(false);\n\t};\n\n\tconst handleDrop = (e: React.DragEvent) => {\n\t\te.preventDefault();\n\t\tsetIsDraggingFile(false);\n\n\t\tif (e.dataTransfer.files && e.dataTransfer.files[0]) {\n\t\t\thandleFileUpload(e.dataTransfer.files[0]);\n\t\t}\n\t};\n\n\t// Helper function to adjust color brightness\n\tconst adjustColorBrightness = (\n\t\tr: number,\n\t\tg: number,\n\t\tb: number,\n\t\tfactor: number\n\t): string => {\n\t\t// Ensure the colors are visible against black background\n\t\tconst minBrightness = 40; // Minimum brightness to ensure visibility\n\t\tr = Math.max(Math.min(Math.round(r * factor), 255), minBrightness);\n\t\tg = Math.max(Math.min(Math.round(g * factor), 255), minBrightness);\n\t\tb = Math.max(Math.min(Math.round(b * factor), 255), minBrightness);\n\t\treturn `rgb(${r}, ${g}, ${b})`;\n\t};\n\n\t// Add this function after the adjustColorBrightness function\n\tconst renderToCanvas = () => {\n\t\tif (!outputCanvasRef.current || !asciiArt || coloredAsciiArt.length === 0)\n\t\t\treturn;\n\n\t\tconst canvas = outputCanvasRef.current;\n\t\tconst ctx = canvas.getContext(\"2d\");\n\t\tif (!ctx) return;\n\n\t\t// Clear the canvas\n\t\tctx.clearRect(0, 0, canvas.width, canvas.height);\n\n\t\t// Set font properties to match the DOM rendering\n\t\tconst fontSize = 8; // Base font size in pixels\n\t\tctx.font = `${fontSize}px monospace`;\n\t\tctx.textBaseline = \"top\";\n\n\t\t// Calculate dimensions\n\t\tconst lineHeight = fontSize;\n\t\tconst charWidth = fontSize * 0.6; // Approximate width of monospace character\n\n\t\t// Resize canvas to fit the ASCII art\n\t\tif (grayscale) {\n\t\t\tconst lines = asciiArt.split(\"\\n\");\n\t\t\tconst maxLineLength = Math.max(...lines.map((line) => line.length));\n\t\t\tcanvas.width = maxLineLength * charWidth;\n\t\t\tcanvas.height = lines.length * lineHeight;\n\t\t} else {\n\t\t\tcanvas.width = coloredAsciiArt[0].length * charWidth;\n\t\t\tcanvas.height = coloredAsciiArt.length * lineHeight;\n\t\t}\n\n\t\t// Re-apply font after canvas resize\n\t\tctx.font = `${fontSize}px monospace`;\n\t\tctx.textBaseline = \"top\";\n\n\t\t// Render the ASCII art\n\t\tif (grayscale) {\n\t\t\tctx.fillStyle = \"white\";\n\t\t\tasciiArt.split(\"\\n\").forEach((line, lineIndex) => {\n\t\t\t\tctx.fillText(line, 0, lineIndex * lineHeight);\n\t\t\t});\n\t\t} else {\n\t\t\tcoloredAsciiArt.forEach((row, rowIndex) => {\n\t\t\t\trow.forEach((col, colIndex) => {\n\t\t\t\t\tctx.fillStyle = col.color;\n\t\t\t\t\tctx.fillText(\n\t\t\t\t\t\tcol.char,\n\t\t\t\t\t\tcolIndex * charWidth,\n\t\t\t\t\t\trowIndex * lineHeight\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t};\n\n\t// Add this effect to trigger canvas rendering when ASCII art changes\n\tuseEffect(() => {\n\t\tif (imageLoaded && !loading && !error) {\n\t\t\trenderToCanvas();\n\t\t}\n\t}, [asciiArt, coloredAsciiArt, grayscale, loading, error, imageLoaded]);\n\n\tconst convertToAscii = () => {\n\t\ttry {\n\t\t\tif (!canvasRef.current || !imageRef.current) {\n\t\t\t\tthrow new Error(\"Canvas or image not available\");\n\t\t\t}\n\n\t\t\tconst img = imageRef.current;\n\n\t\t\t// Validate image dimensions\n\t\t\tif (img.width === 0 || img.height === 0) {\n\t\t\t\tthrow new Error(\"Invalid image dimensions\");\n\t\t\t}\n\n\t\t\tconst canvas = canvasRef.current;\n\t\t\tconst ctx = canvas.getContext(\"2d\");\n\t\t\tif (!ctx) {\n\t\t\t\tthrow new Error(\"Could not get canvas context\");\n\t\t\t}\n\n\t\t\t// Calculate dimensions based on resolution\n\t\t\tconst width = Math.floor(img.width * resolution);\n\t\t\tconst height = Math.floor(img.height * resolution);\n\n\t\t\t// Set canvas dimensions to match the image\n\t\t\tcanvas.width = img.width;\n\t\t\tcanvas.height = img.height;\n\n\t\t\t// Clear the canvas first\n\t\t\tctx.clearRect(0, 0, canvas.width, canvas.height);\n\n\t\t\t// Draw image to canvas\n\t\t\tctx.drawImage(img, 0, 0, img.width, img.height);\n\n\t\t\t// Get image data - this is where the error was occurring\n\t\t\tlet imageData;\n\t\t\ttry {\n\t\t\t\timageData = ctx.getImageData(0, 0, img.width, img.height);\n\t\t\t} catch (e) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Failed to get image data. This might be a CORS issue.\"\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst data = imageData.data;\n\n\t\t\t// Choose character set\n\t\t\tconst chars = charSets[charSet as keyof typeof charSets];\n\n\t\t\t// Calculate aspect ratio correction for monospace font\n\t\t\tconst fontAspect = 0.5; // Width/height ratio of monospace font characters\n\t\t\tconst widthStep = Math.ceil(img.width / width);\n\t\t\tconst heightStep = Math.ceil(img.height / height / fontAspect);\n\n\t\t\tlet result = \"\";\n\t\t\tconst coloredResult: ColoredChar[][] = [];\n\n\t\t\t// Process the image\n\t\t\tfor (let y = 0; y < img.height; y += heightStep) {\n\t\t\t\tconst coloredRow: ColoredChar[] = [];\n\n\t\t\t\tfor (let x = 0; x < img.width; x += widthStep) {\n\t\t\t\t\tconst pos = (y * img.width + x) * 4;\n\n\t\t\t\t\tconst r = data[pos];\n\t\t\t\t\tconst g = data[pos + 1];\n\t\t\t\t\tconst b = data[pos + 2];\n\n\t\t\t\t\t// Calculate brightness based on grayscale setting\n\t\t\t\t\tlet brightness;\n\t\t\t\t\tif (grayscale) {\n\t\t\t\t\t\t// Standard grayscale calculation\n\t\t\t\t\t\tbrightness = (r * 0.299 + g * 0.587 + b * 0.114) / 255;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Color-aware brightness (perceived luminance)\n\t\t\t\t\t\tbrightness = Math.sqrt(\n\t\t\t\t\t\t\t0.299 * (r / 255) * (r / 255) +\n\t\t\t\t\t\t\t0.587 * (g / 255) * (g / 255) +\n\t\t\t\t\t\t\t0.114 * (b / 255) * (b / 255)\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Invert if needed\n\t\t\t\t\tif (inverted) brightness = 1 - brightness;\n\n\t\t\t\t\t// Map brightness to character\n\t\t\t\t\tconst charIndex = Math.floor(brightness * (chars.length - 1));\n\t\t\t\t\tconst char = chars[charIndex];\n\n\t\t\t\t\tresult += char;\n\n\t\t\t\t\t// For colored mode, store the character and its color\n\t\t\t\t\tif (!grayscale) {\n\t\t\t\t\t\t// Adjust color brightness based on the character density\n\t\t\t\t\t\t// Characters with more \"ink\" (later in the charset) should be brighter\n\t\t\t\t\t\tconst brightnessFactor =\n\t\t\t\t\t\t\t(charIndex / (chars.length - 1)) * 1.5 + 0.5;\n\t\t\t\t\t\tconst color = adjustColorBrightness(\n\t\t\t\t\t\t\tr,\n\t\t\t\t\t\t\tg,\n\t\t\t\t\t\t\tb,\n\t\t\t\t\t\t\tbrightnessFactor\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcoloredRow.push({ char, color });\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// For grayscale mode, we still need to populate the array\n\t\t\t\t\t\tcoloredRow.push({ char, color: \"white\" });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresult += \"\\n\";\n\t\t\t\tcoloredResult.push(coloredRow);\n\t\t\t}\n\n\t\t\tsetAsciiArt(result);\n\t\t\tsetColoredAsciiArt(coloredResult);\n\t\t\tsetError(null);\n\t\t} catch (err) {\n\t\t\tconsole.error(\"Error converting to ASCII:\", err);\n\t\t\tsetError(\n\t\t\t\terr instanceof Error ? err.message : \"Unknown error occurred\"\n\t\t\t);\n\t\t\tsetAsciiArt(\"\");\n\t\t\tsetColoredAsciiArt([]);\n\t\t}\n\t};\n\n\tconst downloadAsciiArt = () => {\n\t\tif (!asciiArt) {\n\t\t\tsetError(\"No ASCII art to download\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst element = document.createElement(\"a\");\n\t\tconst file = new Blob([asciiArt], { type: \"text/plain\" });\n\t\telement.href = URL.createObjectURL(file);\n\t\telement.download = \"ascii-art.txt\";\n\t\tdocument.body.appendChild(element);\n\t\telement.click();\n\t\tdocument.body.removeChild(element);\n\t};\n\n\treturn (\n\t\t<div className=\"min-h-screen w-full bg-black text-foreground\">\n\t\t\t<div\n\t\t\t\tref={containerRef}\n\t\t\t\tclassName=\"flex flex-col md:flex-row min-h-screen w-full overflow-hidden select-none\"\n\t\t\t\tonDragOver={handleDragOver}\n\t\t\t\tonDragLeave={handleDragLeave}\n\t\t\t\tonDrop={handleDrop}\n\t\t\t>\n\t\t\t\t{/* ASCII Art Preview - Top on mobile, Right on desktop */}\n\t\t\t\t<div\n\t\t\t\t\tref={previewRef}\n\t\t\t\t\tclassName={`order-1 md:order-2 flex-1 bg-background overflow-auto flex items-center justify-center ${isDraggingFile ? \"bg-opacity-50\" : \"\"\n\t\t\t\t\t\t} relative`}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\t...(isHydrated && isDesktop\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\twidth: `${100 - leftPanelWidth}%`,\n\t\t\t\t\t\t\t\tmarginLeft: `${leftPanelWidth}%`,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{isDraggingFile && (\n\t\t\t\t\t\t<div className=\"absolute inset-0 flex items-center justify-center bg-background bg-opacity-70 z-10 select-none\">\n\t\t\t\t\t\t\t<div className=\"text-foreground text-xl font-mono\">\n\t\t\t\t\t\t\t\tDrop image here\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{loading ? (\n\t\t\t\t\t\t<div className=\"text-foreground font-mono select-none\">\n\t\t\t\t\t\t\tLoading image...\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t<div className=\"text-red-400 font-mono p-4 text-center select-none\">\n\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t<div className=\"mt-2 text-foreground text-sm\">\n\t\t\t\t\t\t\t\tTry uploading a different image or refreshing the page.\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<canvas\n\t\t\t\t\t\t\tref={outputCanvasRef}\n\t\t\t\t\t\t\tclassName=\"max-w-full select-text\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tfontSize: \"0.4rem\",\n\t\t\t\t\t\t\t\tlineHeight: \"0.4rem\",\n\t\t\t\t\t\t\t\tfontFamily: \"monospace\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\n\t\t\t\t</div>\n\n\t\t\t\t{/* Resizable divider - Only visible on desktop after hydration */}\n\t\t\t\t{isHydrated && isDesktop && (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"order-3 w-2 bg-stone-800 hover:bg-stone-700 cursor-col-resize items-center justify-center z-10 transition-opacity duration-300\"\n\t\t\t\t\t\tonMouseDown={startDragging}\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tleft: `${leftPanelWidth}%`,\n\t\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\t\tbottom: 0,\n\t\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<GripVertical className=\"h-6 w-6 text-stone-500\" />\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t{/* Control Panel - Bottom on mobile, Left on desktop */}\n\t\t\t\t<div\n\t\t\t\t\tclassName={`order-2 md:order-1 w-full md:h-auto p-2 md:p-4 bg-stone-900 font-mono text-stone-300 transition-opacity duration-300 ${!isHydrated ? \"opacity-0\" : \"opacity-100\"\n\t\t\t\t\t\t}`}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\t\theight: \"auto\",\n\t\t\t\t\t\tflex: \"0 0 auto\",\n\t\t\t\t\t\t...(isHydrated && isDesktop\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\t\t\tbottom: 0,\n\t\t\t\t\t\t\t\twidth: `${leftPanelWidth}%`,\n\t\t\t\t\t\t\t\toverflowY: \"auto\",\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"space-y-4 p-2 md:p-4 border border-stone-700 rounded-md\">\n\t\t\t\t\t\t<div className=\"space-y-1\">\n\t\t\t\t\t\t\t<h1 className=\"text-lg text-stone-100 font-bold\">\n\t\t\t\t\t\t\t\tASCII Art Converter\n\t\t\t\t\t\t\t</h1>\n\t\t\t\t\t\t\t{error && (\n\t\t\t\t\t\t\t\t<p className=\"text-red-400 text-sm mt-2\">{error}</p>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div className=\"space-y-4 pt-2\">\n\t\t\t\t\t\t\t<div className=\"space-y-2 border-t border-stone-700 pt-4\">\n\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t<Label\n\t\t\t\t\t\t\t\t\t\thtmlFor=\"resolution\"\n\t\t\t\t\t\t\t\t\t\tclassName=\"text-stone-300\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\tResolution: {resolution.toFixed(2)}\n\t\t\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<Slider\n\t\t\t\t\t\t\t\t\tid=\"resolution\"\n\t\t\t\t\t\t\t\t\tmin={0.05}\n\t\t\t\t\t\t\t\t\tmax={0.3}\n\t\t\t\t\t\t\t\t\tstep={0.01}\n\t\t\t\t\t\t\t\t\tvalue={[resolution]}\n\t\t\t\t\t\t\t\t\tonValueChange={(value) => setResolution(value[0])}\n\t\t\t\t\t\t\t\t\tclassName=\"[&>span]:border-none [&_.bg-primary]:bg-stone-800 [&>.bg-background]:bg-stone-500/30\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"space-y-2 border-t border-stone-700 pt-4\">\n\t\t\t\t\t\t\t\t<Label htmlFor=\"charset\" className=\"text-stone-300\">\n\t\t\t\t\t\t\t\t\tCharacter Set\n\t\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t\t<Select value={charSet} onValueChange={setCharSet}>\n\t\t\t\t\t\t\t\t\t<SelectTrigger\n\t\t\t\t\t\t\t\t\t\tid=\"charset\"\n\t\t\t\t\t\t\t\t\t\tclassName=\"bg-stone-800 border-stone-700 text-stone-300\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<SelectValue placeholder=\"Select character set\" />\n\t\t\t\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t\t\t\t<SelectContent className=\"bg-stone-800 border-stone-700 text-stone-300\">\n\t\t\t\t\t\t\t\t\t\t<SelectItem\n\t\t\t\t\t\t\t\t\t\t\tvalue=\"standard\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"focus:bg-stone-700 focus:text-stone-100\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tStandard\n\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t<SelectItem\n\t\t\t\t\t\t\t\t\t\t\tvalue=\"detailed\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"focus:bg-stone-700 focus:text-stone-100\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tDetailed\n\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t<SelectItem\n\t\t\t\t\t\t\t\t\t\t\tvalue=\"blocks\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"focus:bg-stone-700 focus:text-stone-100\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tBlock Characters\n\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t<SelectItem\n\t\t\t\t\t\t\t\t\t\t\tvalue=\"minimal\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"focus:bg-stone-700 focus:text-stone-100\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tMinimal\n\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2 border-t border-stone-700 pt-4\">\n\t\t\t\t\t\t\t\t<Switch\n\t\t\t\t\t\t\t\t\tid=\"invert\"\n\t\t\t\t\t\t\t\t\tchecked={inverted}\n\t\t\t\t\t\t\t\t\tonCheckedChange={setInverted}\n\t\t\t\t\t\t\t\t\tclassName=\"data-[state=checked]:bg-stone-600\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<Label htmlFor=\"invert\" className=\"text-stone-300\">\n\t\t\t\t\t\t\t\t\tInvert Colors\n\t\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2 border-t border-stone-700 pt-4\">\n\t\t\t\t\t\t\t\t<Switch\n\t\t\t\t\t\t\t\t\tid=\"grayscale\"\n\t\t\t\t\t\t\t\t\tchecked={grayscale}\n\t\t\t\t\t\t\t\t\tonCheckedChange={setGrayscale}\n\t\t\t\t\t\t\t\t\tclassName=\"data-[state=checked]:bg-stone-600\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<Label htmlFor=\"grayscale\" className=\"text-stone-300\">\n\t\t\t\t\t\t\t\t\tGrayscale Mode\n\t\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"hidden\">\n\t\t\t\t\t\t\t\t<canvas\n\t\t\t\t\t\t\t\t\tref={canvasRef}\n\t\t\t\t\t\t\t\t\twidth=\"300\"\n\t\t\t\t\t\t\t\t\theight=\"300\"\n\t\t\t\t\t\t\t\t></canvas>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"file\"\n\t\t\t\t\t\t\t\t\tref={fileInputRef}\n\t\t\t\t\t\t\t\t\taccept=\"image/*\"\n\t\t\t\t\t\t\t\t\tonChange={handleFileInputChange}\n\t\t\t\t\t\t\t\t\tclassName=\"hidden\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"flex gap-2 pt-4 border-t border-stone-700\">\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\tif (!asciiArt) {\n\t\t\t\t\t\t\t\t\t\t\tsetError(\"No ASCII art to copy\");\n\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst el = document.createElement(\"textarea\");\n\t\t\t\t\t\t\t\t\t\tel.value = asciiArt;\n\t\t\t\t\t\t\t\t\t\tdocument.body.appendChild(el);\n\t\t\t\t\t\t\t\t\t\tel.select();\n\t\t\t\t\t\t\t\t\t\tdocument.execCommand(\"copy\");\n\t\t\t\t\t\t\t\t\t\tdocument.body.removeChild(el);\n\t\t\t\t\t\t\t\t\t\talert(\"ASCII art copied to clipboard!\");\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tclassName=\"flex-1 bg-stone-700 hover:bg-stone-600 text-stone-200 border-stone-600\"\n\t\t\t\t\t\t\t\t\tdisabled={loading || !imageLoaded}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{sidebarNarrow ? \"Copy\" : \"Copy ASCII Art\"}\n\t\t\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tonClick={downloadAsciiArt}\n\t\t\t\t\t\t\t\t\tclassName=\"bg-stone-700 hover:bg-stone-600 text-stone-200 border-stone-600\"\n\t\t\t\t\t\t\t\t\ttitle=\"Download ASCII Art\"\n\t\t\t\t\t\t\t\t\tdisabled={loading || !imageLoaded || !asciiArt}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Download className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\t\t\t\t\tclassName=\"bg-stone-700 hover:bg-stone-600 text-stone-200 border-stone-600\"\n\t\t\t\t\t\t\t\t\ttitle=\"Upload Image\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Upload className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/button.tsx",
      "content": "import * as React from \"react\";\r\n\r\nimport { cn } from \"@/registry/utilities/cn\";\r\nimport { Slot } from \"@radix-ui/react-slot\";\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\n\r\nconst buttonVariants = cva(\r\n\t\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm text-white hover:text-gray-400 font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\r\n\t{\r\n\t\tvariants: {\r\n\t\t\tvariant: {\r\n\t\t\t\tsimple:\r\n\t\t\t\t\t\"bg-secondary relative z-10 bg-transparent hover:border-secondary hover:bg-secondary/50  border border-transparent dark:text-white text-sm md:text-sm transition font-medium duration-200  rounded-md px-4 py-2  flex items-center justify-center\",\r\n\t\t\t\tdefault: \"bg-primary text-primary-foreground hover:bg-primary/90\",\r\n\t\t\t\tdestructive:\r\n\t\t\t\t\t\"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\r\n\t\t\t\toutline:\r\n\t\t\t\t\t\"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\r\n\t\t\t\tsecondary:\r\n\t\t\t\t\t\"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\r\n\t\t\t\tghost: \"hover:bg-accent hover:text-black hover:stroke-black dark:text-white text-black\",\r\n\t\t\t\tlink: \"text-primary underline-offset-4 hover:underline\",\r\n\t\t\t},\r\n\t\t\tsize: {\r\n\t\t\t\tdefault: \"h-10 px-4 py-2\",\r\n\t\t\t\tsm: \"h-9 rounded-md px-3\",\r\n\t\t\t\tlg: \"h-11 rounded-md px-8\",\r\n\t\t\t\ticon: \"h-10 w-10\",\r\n\t\t\t},\r\n\t\t},\r\n\t\tdefaultVariants: {\r\n\t\t\tvariant: \"default\",\r\n\t\t\tsize: \"default\",\r\n\t\t},\r\n\t}\r\n);\r\n\r\nexport interface ButtonProps\r\n\textends React.ButtonHTMLAttributes<HTMLButtonElement>,\r\n\t\tVariantProps<typeof buttonVariants> {\r\n\tasChild?: boolean;\r\n}\r\n\r\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\r\n\t({ className, variant, size, asChild = false, ...props }, ref) => {\r\n\t\tconst Comp = asChild ? Slot : \"button\";\r\n\t\treturn (\r\n\t\t\t<Comp\r\n\t\t\t\tclassName={cn(buttonVariants({ variant, size, className }))}\r\n\t\t\t\tref={ref}\r\n\t\t\t\t{...props}\r\n\t\t\t/>\r\n\t\t);\r\n\t}\r\n);\r\nButton.displayName = \"Button\";\r\n\r\nexport { Button, buttonVariants };\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/label.tsx",
      "content": "\"use client\";\n\nimport React from \"react\";\n\nimport { cn } from \"@/registry/utilities/cn\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nconst labelVariants = cva(\n\t\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n);\n\nconst Label = React.forwardRef<\n\tReact.ElementRef<typeof LabelPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n\t\tVariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n\t<LabelPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(labelVariants(), className)}\n\t\t{...props}\n\t/>\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/slider.tsx",
      "content": "\"use client\";\r\n\r\nimport React from \"react\";\r\n\r\nimport { cn } from \"@/registry/utilities/cn\";\r\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\r\n\r\nconst Slider = React.forwardRef<\r\n\tReact.ElementRef<typeof SliderPrimitive.Root>,\r\n\tReact.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\r\n>(({ className, ...props }, ref) => (\r\n\t<SliderPrimitive.Root\r\n\t\tref={ref}\r\n\t\tclassName={cn(\r\n\t\t\t\"relative flex w-full touch-none select-none items-center\",\r\n\t\t\tclassName\r\n\t\t)}\r\n\t\t{...props}\r\n\t>\r\n\t\t<SliderPrimitive.Track className=\"relative h-2 w-full grow overflow-hidden rounded-full bg-secondary\">\r\n\t\t\t<SliderPrimitive.Range className=\"absolute h-full bg-primary\" />\r\n\t\t</SliderPrimitive.Track>\r\n\t\t<SliderPrimitive.Thumb className=\"block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\" />\r\n\t\t<SliderPrimitive.Thumb className=\"block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\" />\r\n\t</SliderPrimitive.Root>\r\n));\r\nSlider.displayName = SliderPrimitive.Root.displayName;\r\n\r\nexport { Slider };\r\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/switch.tsx",
      "content": "\"use client\";\r\n\r\nimport React from \"react\";\r\n\r\nimport { cn } from \"@/registry/utilities/cn\";\r\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\";\r\n\r\nconst Switch = React.forwardRef<\r\n\tReact.ElementRef<typeof SwitchPrimitives.Root>,\r\n\tReact.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\r\n>(({ className, ...props }, ref) => (\r\n\t<SwitchPrimitives.Root\r\n\t\tclassName={cn(\r\n\t\t\t\"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\r\n\t\t\tclassName\r\n\t\t)}\r\n\t\t{...props}\r\n\t\tref={ref}\r\n\t>\r\n\t\t<SwitchPrimitives.Thumb\r\n\t\t\tclassName={cn(\r\n\t\t\t\t\"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\"\r\n\t\t\t)}\r\n\t\t/>\r\n\t</SwitchPrimitives.Root>\r\n));\r\nSwitch.displayName = SwitchPrimitives.Root.displayName;\r\n\r\nexport { Switch };\r\n",
      "type": "registry:ui"
    }
  ]
}