video2ascii 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useVideoToAscii.ts","../src/lib/ascii-charsets.ts","../src/lib/webgl/shaders/vertex.glsl","../src/lib/webgl/shaders/fragment.glsl","../src/lib/webgl/utils.ts","../src/lib/webgl/types.ts","../src/hooks/useAsciiMouseEffect.ts","../src/hooks/useAsciiRipple.ts","../src/hooks/useAsciiAudio.ts","../src/components/VideoToAscii.tsx"],"sourcesContent":["import { useRef, useState, useCallback, useEffect, useMemo } from \"react\";\nimport { getCharArray, DEFAULT_CHARSET } from \"@/lib/ascii-charsets\";\nimport {\n VERTEX_SHADER,\n FRAGMENT_SHADER,\n compileShader,\n createProgram,\n createFullscreenQuad,\n createVideoTexture,\n createAsciiAtlas,\n calculateGridDimensions,\n CHAR_WIDTH_RATIO,\n type UseVideoToAsciiOptions,\n type AsciiContext,\n type AsciiStats,\n type UniformSetter,\n type UniformLocations,\n} from \"@/lib/webgl\";\n\nexport type { UseVideoToAsciiOptions, AsciiContext, AsciiStats };\n\nconst MAX_TRAIL_LENGTH = 24;\nconst MAX_RIPPLES = 8;\n\n// Hook Implementation\nexport function useVideoToAscii(\n options: UseVideoToAsciiOptions = {}\n): AsciiContext {\n const {\n fontSize = 10,\n colored = false,\n blend = 0,\n highlight = 0,\n charset = DEFAULT_CHARSET,\n maxWidth = 900,\n onStats,\n } = options;\n\n // DOM refs\n const containerRef = useRef<HTMLDivElement>(null);\n const videoRef = useRef<HTMLVideoElement>(null);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n // WebGL refs - these hold the GPU resources\n const glRef = useRef<WebGL2RenderingContext | null>(null);\n const programRef = useRef<WebGLProgram | null>(null);\n const videoTextureRef = useRef<WebGLTexture | null>(null);\n const atlasTextureRef = useRef<WebGLTexture | null>(null);\n const animationRef = useRef<number>(0);\n\n // Feature hooks register their uniform setters here\n const uniformSettersRef = useRef<Map<string, UniformSetter>>(new Map());\n // Cached uniform locations for performance (avoid lookup every frame)\n const uniformLocationsRef = useRef<UniformLocations | null>(null);\n\n // Benchmark/stats refs\n const frameCountRef = useRef(0);\n const frameTimesRef = useRef<number[]>([]);\n const lastFpsTimeRef = useRef(performance.now());\n\n // State\n const [dimensions, setDimensions] = useState({ cols: 80, rows: 24 });\n const [stats, setStats] = useState<AsciiStats>({ fps: 0, frameTime: 0 });\n const [isReady, setIsReady] = useState(false);\n const [isPlaying, setIsPlaying] = useState(false);\n\n // Calculate grid size based on font size\n const charWidth = fontSize * CHAR_WIDTH_RATIO;\n const cols = Math.floor(maxWidth / charWidth);\n // Memoize chars array so it only recalculates when charset changes\n const chars = useMemo(() => getCharArray(charset), [charset]);\n\n // Feature hooks call this to register their uniform setter\n const registerUniformSetter = useCallback(\n (id: string, setter: UniformSetter) => {\n uniformSettersRef.current.set(id, setter);\n },\n []\n );\n\n const unregisterUniformSetter = useCallback((id: string) => {\n uniformSettersRef.current.delete(id);\n }, []);\n\n // Cache all uniform locations after program is compiled\n // This avoids expensive getUniformLocation calls every frame\n const cacheUniformLocations = useCallback(\n (gl: WebGL2RenderingContext, program: WebGLProgram): UniformLocations => {\n const get = (name: string) => gl.getUniformLocation(program, name);\n\n return {\n // Core uniforms\n u_video: get(\"u_video\"),\n u_asciiAtlas: get(\"u_asciiAtlas\"),\n u_resolution: get(\"u_resolution\"),\n u_charSize: get(\"u_charSize\"),\n u_gridSize: get(\"u_gridSize\"),\n u_numChars: get(\"u_numChars\"),\n u_colored: get(\"u_colored\"),\n u_blend: get(\"u_blend\"),\n u_highlight: get(\"u_highlight\"),\n\n // Mouse uniforms\n u_mouse: get(\"u_mouse\"),\n u_mouseRadius: get(\"u_mouseRadius\"),\n u_trailLength: get(\"u_trailLength\"),\n u_trail: Array.from({ length: MAX_TRAIL_LENGTH }, (_, i) =>\n get(`u_trail[${i}]`)\n ),\n\n // Ripple uniforms\n u_time: get(\"u_time\"),\n u_rippleEnabled: get(\"u_rippleEnabled\"),\n u_rippleSpeed: get(\"u_rippleSpeed\"),\n u_ripples: Array.from({ length: MAX_RIPPLES }, (_, i) =>\n get(`u_ripples[${i}]`)\n ),\n\n // Audio uniforms\n u_audioLevel: get(\"u_audioLevel\"),\n u_audioReactivity: get(\"u_audioReactivity\"),\n u_audioSensitivity: get(\"u_audioSensitivity\"),\n };\n },\n []\n );\n\n // Initialize WebGL\n const initWebGL = useCallback(() => {\n const canvas = canvasRef.current;\n const video = videoRef.current;\n if (!canvas || !video || !video.videoWidth) return false;\n\n // Figure out grid dimensions from video aspect ratio\n const grid = calculateGridDimensions(\n video.videoWidth,\n video.videoHeight,\n cols\n );\n setDimensions(grid);\n\n // Set canvas size\n const pixelWidth = grid.cols * charWidth;\n const pixelHeight = grid.rows * fontSize;\n canvas.width = pixelWidth;\n canvas.height = pixelHeight;\n\n // Get WebGL2 context (WebGL2 has better texture handling)\n const gl = canvas.getContext(\"webgl2\", {\n antialias: false,\n preserveDrawingBuffer: false,\n });\n if (!gl) {\n console.error(\"WebGL2 not supported\");\n return false;\n }\n glRef.current = gl;\n\n // Compile shaders (vertex positions the quad, fragment does the ASCII magic)\n const vertexShader = compileShader(gl, VERTEX_SHADER, gl.VERTEX_SHADER);\n const fragmentShader = compileShader(\n gl,\n FRAGMENT_SHADER,\n gl.FRAGMENT_SHADER\n );\n if (!vertexShader || !fragmentShader) return false;\n\n // Link shaders into a program\n const program = createProgram(gl, vertexShader, fragmentShader);\n if (!program) return false;\n programRef.current = program;\n gl.useProgram(program);\n\n // Create a fullscreen quad (two triangles covering the canvas)\n createFullscreenQuad(gl, program);\n\n // Create textures for video frame and ASCII character atlas\n videoTextureRef.current = createVideoTexture(gl);\n atlasTextureRef.current = createAsciiAtlas(gl, chars, fontSize);\n\n // Cache all uniform locations for fast access during render\n const locations = cacheUniformLocations(gl, program);\n uniformLocationsRef.current = locations;\n\n // Tell the shader which texture units to use\n gl.uniform1i(locations.u_video, 0); // texture unit 0\n gl.uniform1i(locations.u_asciiAtlas, 1); // texture unit 1\n\n // Set static uniforms that don't change during playback\n gl.uniform2f(locations.u_resolution, pixelWidth, pixelHeight);\n gl.uniform2f(locations.u_charSize, charWidth, fontSize);\n gl.uniform2f(locations.u_gridSize, cols, grid.rows);\n gl.uniform1f(locations.u_numChars, chars.length);\n\n // Initialize feature uniforms to disabled state\n gl.uniform2f(locations.u_mouse, -1, -1);\n gl.uniform1f(locations.u_mouseRadius, 0);\n gl.uniform1i(locations.u_trailLength, 0);\n gl.uniform1f(locations.u_rippleEnabled, 0);\n gl.uniform1f(locations.u_audioLevel, 0);\n gl.uniform1f(locations.u_audioReactivity, 0);\n gl.uniform1f(locations.u_audioSensitivity, 0);\n\n gl.viewport(0, 0, pixelWidth, pixelHeight);\n\n setIsReady(true);\n return true;\n }, [cols, charWidth, fontSize, chars, cacheUniformLocations]);\n\n // Render loop - runs every frame while video is playing\n const render = useCallback(() => {\n const gl = glRef.current;\n const video = videoRef.current;\n const program = programRef.current;\n const locations = uniformLocationsRef.current;\n\n if (!gl || !video || !program || !locations || video.paused || video.ended)\n return;\n\n const frameStart = performance.now();\n\n // Upload current video frame to GPU\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, videoTextureRef.current);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);\n // Generate mipmaps for better quality when sampling large areas\n gl.generateMipmap(gl.TEXTURE_2D);\n\n // Bind the ASCII atlas texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, atlasTextureRef.current);\n\n // Update uniforms that can change each frame\n gl.uniform1i(locations.u_colored, colored ? 1 : 0);\n gl.uniform1f(locations.u_blend, blend / 100);\n gl.uniform1f(locations.u_highlight, highlight / 100);\n\n // Let feature hooks update their uniforms\n for (const setter of uniformSettersRef.current.values()) {\n setter(gl, program, locations);\n }\n\n // Draw the quad (shader does all the work)\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Track performance\n const frameEnd = performance.now();\n frameCountRef.current++;\n frameTimesRef.current.push(frameEnd - frameStart);\n if (frameTimesRef.current.length > 60) frameTimesRef.current.shift();\n\n // Update FPS counter every second\n const now = performance.now();\n if (now - lastFpsTimeRef.current >= 1000) {\n const avgFrameTime =\n frameTimesRef.current.reduce((a, b) => a + b, 0) /\n frameTimesRef.current.length;\n const newStats = { fps: frameCountRef.current, frameTime: avgFrameTime };\n setStats(newStats);\n onStats?.(newStats);\n frameCountRef.current = 0;\n lastFpsTimeRef.current = now;\n }\n\n // Schedule next frame\n animationRef.current = requestAnimationFrame(render);\n }, [colored, blend, highlight, onStats]);\n\n // Video Event Handlers\n useEffect(() => {\n const video = videoRef.current;\n if (!video) return;\n\n const handleLoadedMetadata = () => {\n initWebGL();\n };\n\n const handlePlay = () => {\n setIsPlaying(true);\n animationRef.current = requestAnimationFrame(render);\n };\n\n const handlePause = () => {\n setIsPlaying(false);\n cancelAnimationFrame(animationRef.current);\n };\n\n const handleEnded = () => {\n setIsPlaying(false);\n cancelAnimationFrame(animationRef.current);\n };\n\n video.addEventListener(\"loadedmetadata\", handleLoadedMetadata);\n video.addEventListener(\"play\", handlePlay);\n video.addEventListener(\"pause\", handlePause);\n video.addEventListener(\"ended\", handleEnded);\n\n // If video is already loaded when we mount\n if (video.readyState >= 1) {\n handleLoadedMetadata();\n }\n\n return () => {\n video.removeEventListener(\"loadedmetadata\", handleLoadedMetadata);\n video.removeEventListener(\"play\", handlePlay);\n video.removeEventListener(\"pause\", handlePause);\n video.removeEventListener(\"ended\", handleEnded);\n cancelAnimationFrame(animationRef.current);\n };\n }, [initWebGL, render]);\n\n // Reinitialize when config changes\n useEffect(() => {\n if (videoRef.current && videoRef.current.readyState >= 1) {\n initWebGL();\n }\n }, [initWebGL]);\n\n // Cleanup WebGL resources when unmounting\n useEffect(() => {\n return () => {\n const gl = glRef.current;\n if (gl) {\n if (videoTextureRef.current) gl.deleteTexture(videoTextureRef.current);\n if (atlasTextureRef.current) gl.deleteTexture(atlasTextureRef.current);\n if (programRef.current) gl.deleteProgram(programRef.current);\n }\n cancelAnimationFrame(animationRef.current);\n };\n }, []);\n\n // Playback Controls\n const play = useCallback(() => {\n videoRef.current?.play();\n }, []);\n\n const pause = useCallback(() => {\n videoRef.current?.pause();\n }, []);\n\n const toggle = useCallback(() => {\n const video = videoRef.current;\n if (!video) return;\n if (video.paused) {\n video.play();\n } else {\n video.pause();\n }\n }, []);\n\n // Spacebar to toggle play/pause\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.code === \"Space\" && e.target === document.body) {\n e.preventDefault();\n toggle();\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggle]);\n\n return {\n containerRef,\n videoRef,\n canvasRef,\n glRef,\n programRef,\n uniformLocationsRef,\n registerUniformSetter,\n unregisterUniformSetter,\n dimensions,\n stats,\n isReady,\n isPlaying,\n play,\n pause,\n toggle,\n };\n}\n","/**\n * ASCII Character Set Definitions\n *\n * Character sets are ordered from dark (low brightness) to light (high brightness).\n * The shader maps pixel brightness to character index, so the first character\n * represents the darkest pixels and the last represents the brightest.\n *\n * To add a new character set:\n * 1. Add an entry to ASCII_CHARSETS with a unique key\n * 2. Order characters from dark → light (spaces/dots first, dense chars last)\n * 3. The key becomes available in CharsetKey type automatically\n */\n\nexport const ASCII_CHARSETS = {\n /** Classic 10-character gradient - good balance of detail and performance */\n standard: {\n name: \"Standard\",\n chars: \" .:-=+*#%@\",\n },\n\n /** Unicode block characters - chunky retro aesthetic */\n blocks: {\n name: \"Blocks\",\n chars: \" ░▒▓█\",\n },\n\n /** Minimal 5-character set - high contrast, fast rendering */\n minimal: {\n name: \"Minimal\",\n chars: \" .oO@\",\n },\n\n /** Binary on/off - pure silhouette mode */\n binary: {\n name: \"Binary\",\n chars: \" █\",\n },\n\n /** 70-character gradient - maximum detail, best for high resolution */\n detailed: {\n name: \"Detailed\",\n chars:\n \" .'`^\\\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$\",\n },\n\n /** Dot-based - pointillist aesthetic */\n dots: {\n name: \"Dots\",\n chars: \" ·•●\",\n },\n\n /** Directional arrows - experimental */\n arrows: {\n name: \"Arrows\",\n chars: \" ←↙↓↘→↗↑↖\",\n },\n\n /** Moon phases - decorative gradient */\n emoji: {\n name: \"Emoji\",\n chars: \" ░▒▓🌑🌒🌓🌔🌕\",\n },\n} as const;\n\n/** Type-safe key for selecting character sets */\nexport type CharsetKey = keyof typeof ASCII_CHARSETS;\n\n/** Default character set used when none is specified */\nexport const DEFAULT_CHARSET: CharsetKey = \"standard\";\n\n/**\n * Get the character array for a given charset key.\n * Uses spread operator to correctly handle multi-byte unicode characters.\n */\nexport function getCharArray(charset: CharsetKey): string[] {\n return [...ASCII_CHARSETS[charset].chars];\n}\n\n/**\n * Get the display name for a charset\n */\nexport function getCharsetName(charset: CharsetKey): string {\n return ASCII_CHARSETS[charset].name;\n}\n","#version 300 es\n\n// Fullscreen quad - passes texture coords to fragment shader\n\nin vec2 a_position;\nin vec2 a_texCoord;\nout vec2 v_texCoord;\n\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_texCoord = a_texCoord;\n}\n","#version 300 es\nprecision highp float;\n\n// Textures\nuniform sampler2D u_video;\nuniform sampler2D u_asciiAtlas;\n\n// Dimensions\nuniform vec2 u_resolution;\nuniform vec2 u_charSize;\nuniform vec2 u_gridSize;\nuniform float u_numChars;\n\n// Rendering options\nuniform bool u_colored;\nuniform float u_blend;\nuniform float u_highlight;\n\n// Audio\nuniform float u_audioLevel;\nuniform float u_audioReactivity;\nuniform float u_audioSensitivity;\n\n// Mouse\nuniform vec2 u_mouse;\nuniform float u_mouseRadius;\nuniform vec2 u_trail[24];\nuniform int u_trailLength;\n\n// Ripple\nuniform vec4 u_ripples[8];\nuniform float u_time;\nuniform float u_rippleEnabled;\nuniform float u_rippleSpeed;\n\nin vec2 v_texCoord;\nout vec4 fragColor;\n\nvoid main() {\n // Figure out which ASCII cell this pixel is in\n vec2 cellCoord = floor(v_texCoord * u_gridSize);\n vec2 thisCell = cellCoord;\n \n // Sample video at cell center (mipmaps handle averaging)\n vec2 cellCenter = (cellCoord + 0.5) / u_gridSize;\n vec4 videoColor = texture(u_video, cellCenter);\n \n // Perceived brightness using human eye sensitivity weights\n float baseBrightness = dot(videoColor.rgb, vec3(0.299, 0.587, 0.114));\n \n // Audio reactivity - louder = brighter, silence = darker\n float minBrightness = mix(0.3, 0.0, u_audioSensitivity);\n float maxBrightness = mix(1.0, 5.0, u_audioSensitivity);\n float audioMultiplier = mix(minBrightness, maxBrightness, u_audioLevel);\n float audioModulated = baseBrightness * audioMultiplier;\n float brightness = mix(baseBrightness, audioModulated, u_audioReactivity);\n \n // Cursor glow - blocky circle effect\n float cursorGlow = 0.0;\n float cursorRadius = 5.0;\n \n vec2 mouseCell = floor(u_mouse * u_gridSize);\n float cellDist = length(thisCell - mouseCell);\n if (cellDist <= cursorRadius && u_mouse.x >= 0.0) {\n cursorGlow += 1.0 - cellDist / cursorRadius;\n }\n \n // Trail effect\n for (int i = 0; i < 12; i++) {\n if (i >= u_trailLength) break;\n vec2 trailPos = u_trail[i];\n if (trailPos.x < 0.0) continue;\n \n vec2 trailCell = floor(trailPos * u_gridSize);\n float trailDist = length(thisCell - trailCell);\n float trailRadius = cursorRadius * 0.8;\n \n if (trailDist <= trailRadius) {\n float fade = 1.0 - float(i) / float(u_trailLength);\n cursorGlow += (1.0 - trailDist / trailRadius) * 0.5 * fade;\n }\n }\n cursorGlow = min(cursorGlow, 1.0);\n \n // Ripple effect - expanding rings on click\n float rippleGlow = 0.0;\n if (u_rippleEnabled > 0.5) {\n for (int i = 0; i < 8; i++) {\n vec4 ripple = u_ripples[i];\n if (ripple.w < 0.5) continue;\n \n float age = u_time - ripple.z;\n if (age < 0.0) continue;\n \n vec2 rippleCell = floor(ripple.xy * u_gridSize);\n float cellDist = length(thisCell - rippleCell);\n float initialRadius = 5.0;\n \n float distFromEdge = max(0.0, cellDist - initialRadius);\n float rippleSpeed = u_rippleSpeed;\n float reachTime = distFromEdge / rippleSpeed;\n float timeSinceReached = age - reachTime;\n \n float fadeDuration = 0.5;\n if (timeSinceReached >= 0.0 && timeSinceReached < fadeDuration) {\n float pop = 1.0 - timeSinceReached / fadeDuration;\n pop = pop * pop;\n rippleGlow += pop * 0.3;\n }\n }\n rippleGlow = min(rippleGlow, 1.0);\n }\n \n // Map brightness to character index (0 = darkest char, numChars-1 = brightest)\n float charIndex = floor(brightness * (u_numChars - 0.001));\n \n // Find the character in the atlas (horizontal strip of pre-rendered chars)\n float atlasX = charIndex / u_numChars;\n vec2 cellPos = fract(v_texCoord * u_gridSize);\n vec2 atlasCoord = vec2(atlasX + cellPos.x / u_numChars, cellPos.y);\n vec4 charColor = texture(u_asciiAtlas, atlasCoord);\n \n // Pick the color - video colors or green terminal aesthetic\n vec3 baseColor;\n if (u_colored) {\n baseColor = videoColor.rgb;\n } else {\n baseColor = vec3(0.0, 1.0, 0.0);\n }\n \n // Background highlight behind each character\n float bgIntensity = 0.15 + u_highlight * 0.35;\n vec3 bgColor = baseColor * bgIntensity;\n vec3 textColor = baseColor * 1.2;\n vec3 finalColor = mix(bgColor, textColor, charColor.r);\n \n // Add cursor and ripple glow\n finalColor += cursorGlow * baseColor * 0.5;\n finalColor += rippleGlow * baseColor;\n \n // Blend with original video if requested\n vec3 blendedColor = mix(finalColor, videoColor.rgb, u_blend);\n \n fragColor = vec4(blendedColor, 1.0);\n}\n","// Compiles a GLSL shader from source code\nexport function compileShader(\n gl: WebGL2RenderingContext,\n source: string,\n type: number\n): WebGLShader | null {\n const shader = gl.createShader(type);\n if (!shader) {\n console.error(\"Failed to create shader\");\n return null;\n }\n\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n console.error(\"Shader compile error:\", gl.getShaderInfoLog(shader));\n gl.deleteShader(shader);\n return null;\n }\n\n return shader;\n}\n\n// Links vertex and fragment shaders into a program\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vertexShader: WebGLShader,\n fragmentShader: WebGLShader\n): WebGLProgram | null {\n const program = gl.createProgram();\n if (!program) {\n console.error(\"Failed to create program\");\n return null;\n }\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error(\"Program link error:\", gl.getProgramInfoLog(program));\n gl.deleteProgram(program);\n return null;\n }\n\n return program;\n}\n\n// Creates a fullscreen quad (two triangles covering the viewport)\nexport function createFullscreenQuad(\n gl: WebGL2RenderingContext,\n program: WebGLProgram\n): void {\n // Vertex positions in clip space (-1 to 1)\n const positions = new Float32Array([\n -1,\n -1, // bottom-left\n 1,\n -1, // bottom-right\n -1,\n 1, // top-left\n -1,\n 1, // top-left\n 1,\n -1, // bottom-right\n 1,\n 1, // top-right\n ]);\n\n // Texture coords (0 to 1), Y flipped because video origin is top-left\n const texCoords = new Float32Array([0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0]);\n\n // Position attribute\n const posBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);\n const posLoc = gl.getAttribLocation(program, \"a_position\");\n gl.enableVertexAttribArray(posLoc);\n gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);\n\n // Texture coordinate attribute\n const texBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, texBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);\n const texLoc = gl.getAttribLocation(program, \"a_texCoord\");\n gl.enableVertexAttribArray(texLoc);\n gl.vertexAttribPointer(texLoc, 2, gl.FLOAT, false, 0, 0);\n}\n\n// Creates a texture for video frames with mipmapping for quality\nexport function createVideoTexture(\n gl: WebGL2RenderingContext\n): WebGLTexture | null {\n const texture = gl.createTexture();\n if (!texture) return null;\n\n gl.bindTexture(gl.TEXTURE_2D, texture);\n\n // Mipmapping gives better quality when sampling large areas\n gl.texParameteri(\n gl.TEXTURE_2D,\n gl.TEXTURE_MIN_FILTER,\n gl.LINEAR_MIPMAP_LINEAR\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n return texture;\n}\n\n// Renders ASCII characters to a horizontal strip texture\nexport function createAsciiAtlas(\n gl: WebGL2RenderingContext,\n chars: string[],\n charSize: number = 64\n): WebGLTexture | null {\n // Draw characters to an offscreen canvas\n const canvas = document.createElement(\"canvas\");\n canvas.width = charSize * chars.length;\n canvas.height = charSize;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return null;\n\n // Black background, white text (shader colorizes)\n ctx.fillStyle = \"#000\";\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.fillStyle = \"#fff\";\n ctx.font = `${charSize * 0.8}px monospace`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n\n // Draw each character centered in its cell\n for (let i = 0; i < chars.length; i++) {\n const x = i * charSize + charSize / 2;\n const y = charSize / 2;\n ctx.fillText(chars[i], x, y);\n }\n\n // Upload canvas to GPU\n const texture = gl.createTexture();\n if (!texture) return null;\n\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n return texture;\n}\n\n// Helper to set uniforms with cached locations\nexport type UniformSetter = {\n set1f: (name: string, value: number) => void;\n set2f: (name: string, x: number, y: number) => void;\n set1i: (name: string, value: number) => void;\n};\n\nexport function createUniformSetter(\n gl: WebGL2RenderingContext,\n program: WebGLProgram\n): UniformSetter {\n const cache = new Map<string, WebGLUniformLocation | null>();\n\n const getLocation = (name: string) => {\n if (!cache.has(name)) {\n cache.set(name, gl.getUniformLocation(program, name));\n }\n return cache.get(name)!;\n };\n\n return {\n set1f: (name, value) => gl.uniform1f(getLocation(name), value),\n set2f: (name, x, y) => gl.uniform2f(getLocation(name), x, y),\n set1i: (name, value) => gl.uniform1i(getLocation(name), value),\n };\n}\n\n// Calculates ASCII grid dimensions from video aspect ratio\nexport function calculateGridDimensions(\n videoWidth: number,\n videoHeight: number,\n cols: number\n): { cols: number; rows: number } {\n const aspectRatio = videoWidth / videoHeight;\n // Divide by 2 because chars are ~2x taller than wide\n const rows = Math.round(cols / aspectRatio / 2);\n return { cols, rows };\n}\n","import type { CharsetKey } from \"../ascii-charsets\";\n\n// Constants\nexport const CHAR_WIDTH_RATIO = 0.6;\n\n// Core Types\nexport interface AsciiStats {\n fps: number;\n frameTime: number;\n}\n\nexport interface GridDimensions {\n cols: number;\n rows: number;\n}\n\n// Function that feature hooks register to set their uniforms each frame\nexport type UniformSetter = (\n gl: WebGL2RenderingContext,\n program: WebGLProgram,\n locations: UniformLocations\n) => void;\n\n// Cached uniform locations - looked up once at init, used every frame\nexport interface UniformLocations {\n // Core\n u_video: WebGLUniformLocation | null;\n u_asciiAtlas: WebGLUniformLocation | null;\n u_resolution: WebGLUniformLocation | null;\n u_charSize: WebGLUniformLocation | null;\n u_gridSize: WebGLUniformLocation | null;\n u_numChars: WebGLUniformLocation | null;\n u_colored: WebGLUniformLocation | null;\n u_blend: WebGLUniformLocation | null;\n u_highlight: WebGLUniformLocation | null;\n\n // Mouse\n u_mouse: WebGLUniformLocation | null;\n u_mouseRadius: WebGLUniformLocation | null;\n u_trailLength: WebGLUniformLocation | null;\n u_trail: (WebGLUniformLocation | null)[];\n\n // Ripple\n u_time: WebGLUniformLocation | null;\n u_rippleEnabled: WebGLUniformLocation | null;\n u_rippleSpeed: WebGLUniformLocation | null;\n u_ripples: (WebGLUniformLocation | null)[];\n\n // Audio\n u_audioLevel: WebGLUniformLocation | null;\n u_audioReactivity: WebGLUniformLocation | null;\n u_audioSensitivity: WebGLUniformLocation | null;\n}\n\n// Hook Options\nexport interface UseVideoToAsciiOptions {\n fontSize?: number;\n colored?: boolean;\n blend?: number;\n highlight?: number;\n charset?: CharsetKey;\n maxWidth?: number;\n onStats?: (stats: AsciiStats) => void;\n}\n\nexport interface UseAsciiMouseEffectOptions {\n enabled?: boolean;\n trailLength?: number;\n}\n\nexport interface UseAsciiRippleOptions {\n enabled?: boolean;\n speed?: number;\n}\n\nexport interface UseAsciiAudioOptions {\n enabled?: boolean;\n reactivity?: number;\n sensitivity?: number;\n}\n\n// Context returned by useVideoToAscii\nexport interface AsciiContext {\n containerRef: React.RefObject<HTMLDivElement | null>;\n videoRef: React.RefObject<HTMLVideoElement | null>;\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n glRef: React.RefObject<WebGL2RenderingContext | null>;\n programRef: React.RefObject<WebGLProgram | null>;\n uniformLocationsRef: React.RefObject<UniformLocations | null>;\n registerUniformSetter: (id: string, setter: UniformSetter) => void;\n unregisterUniformSetter: (id: string) => void;\n dimensions: GridDimensions;\n stats: AsciiStats;\n isReady: boolean;\n isPlaying: boolean;\n play: () => void;\n pause: () => void;\n toggle: () => void;\n}\n\n// Event handlers returned by feature hooks\nexport interface MouseEffectHandlers {\n onMouseMove: (e: React.MouseEvent<HTMLDivElement>) => void;\n onMouseLeave: () => void;\n}\n\nexport interface RippleHandlers {\n onClick: (e: React.MouseEvent<HTMLDivElement>) => void;\n}\n\n// Component Props - extends core options with feature-specific props\nexport interface VideoToAsciiProps extends UseVideoToAsciiOptions {\n src: string;\n\n // Mouse effect\n enableMouse?: boolean;\n trailLength?: number;\n\n // Ripple effect\n enableRipple?: boolean;\n rippleSpeed?: number;\n\n // Audio\n audioReactivity?: number;\n audioSensitivity?: number;\n\n showStats?: boolean;\n className?: string;\n}\n\n// Legacy types for backwards compat\nexport interface VideoToAsciiWebGLProps extends VideoToAsciiProps {\n showBenchmark?: boolean;\n muted?: boolean;\n}\n\nexport interface BenchmarkStats extends AsciiStats {\n gpuTime: number;\n}\n\nexport interface WebGLResources {\n gl: WebGL2RenderingContext;\n program: WebGLProgram;\n videoTexture: WebGLTexture;\n atlasTexture: WebGLTexture;\n}\n\nexport interface Ripple {\n x: number;\n y: number;\n startTime: number;\n}\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type {\n AsciiContext,\n UseAsciiMouseEffectOptions,\n MouseEffectHandlers,\n} from \"@/lib/webgl\";\n\nexport type { UseAsciiMouseEffectOptions, MouseEffectHandlers };\n\nconst MAX_TRAIL_LENGTH = 24;\n\ninterface MousePosition {\n x: number;\n y: number;\n}\n\n// Hook Implementation\nexport function useAsciiMouseEffect(\n ascii: AsciiContext,\n options: UseAsciiMouseEffectOptions = {}\n): MouseEffectHandlers {\n const { enabled = true, trailLength = 24 } = options;\n\n // Current mouse position in normalized coords (0-1)\n const mouseRef = useRef<MousePosition>({ x: -1, y: -1 });\n // Array of previous positions for the trail effect\n const trailRef = useRef<MousePosition[]>([]);\n // Keep options in refs so event handlers have fresh values\n const enabledRef = useRef(enabled);\n const trailLengthRef = useRef(trailLength);\n\n useEffect(() => {\n enabledRef.current = enabled;\n trailLengthRef.current = trailLength;\n }, [enabled, trailLength]);\n\n // Register uniform setter - called every frame by core hook\n useEffect(() => {\n if (!enabled) return;\n\n const uniformSetter = (\n gl: WebGL2RenderingContext,\n _program: WebGLProgram,\n locations: NonNullable<typeof ascii.uniformLocationsRef.current>\n ) => {\n // Pass current mouse position to shader\n gl.uniform2f(locations.u_mouse, mouseRef.current.x, mouseRef.current.y);\n\n // Pass trail array to shader\n const trail = trailRef.current;\n gl.uniform1i(locations.u_trailLength, trail.length);\n\n // Fill all trail uniform slots (unused ones get -1,-1)\n for (let i = 0; i < MAX_TRAIL_LENGTH; i++) {\n const loc = locations.u_trail[i];\n if (loc) {\n const pos = trail[i] || { x: -1, y: -1 };\n gl.uniform2f(loc, pos.x, pos.y);\n }\n }\n };\n\n ascii.registerUniformSetter(\"mouse\", uniformSetter);\n\n return () => {\n ascii.unregisterUniformSetter(\"mouse\");\n };\n }, [ascii, enabled]);\n\n // Called when mouse moves over the container\n const onMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\n if (!enabledRef.current) return;\n\n const rect = e.currentTarget.getBoundingClientRect();\n const newPos: MousePosition = {\n // Convert pixel coords to 0-1 range\n x: (e.clientX - rect.left) / rect.width,\n y: (e.clientY - rect.top) / rect.height,\n };\n\n // Add old position to the front of the trail\n if (mouseRef.current.x >= 0) {\n trailRef.current.unshift({ ...mouseRef.current });\n // Keep trail at max length\n if (trailRef.current.length > trailLengthRef.current) {\n trailRef.current.pop();\n }\n }\n\n mouseRef.current = newPos;\n }, []);\n\n // Reset when mouse leaves\n const onMouseLeave = useCallback(() => {\n mouseRef.current = { x: -1, y: -1 };\n trailRef.current = [];\n }, []);\n\n return { onMouseMove, onMouseLeave };\n}\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type {\n AsciiContext,\n UseAsciiRippleOptions,\n RippleHandlers,\n} from \"@/lib/webgl\";\n\nexport type { UseAsciiRippleOptions, RippleHandlers };\n\nconst MAX_RIPPLES = 8;\n\ninterface Ripple {\n x: number;\n y: number;\n startTime: number;\n}\n\n// Hook Implementation\nexport function useAsciiRipple(\n ascii: AsciiContext,\n options: UseAsciiRippleOptions = {}\n): RippleHandlers {\n const { enabled = false, speed = 40 } = options;\n\n // Active ripples - each has position and start time\n const ripplesRef = useRef<Ripple[]>([]);\n const enabledRef = useRef(enabled);\n const speedRef = useRef(speed);\n\n useEffect(() => {\n enabledRef.current = enabled;\n speedRef.current = speed;\n }, [enabled, speed]);\n\n // Register uniform setter - runs every frame\n useEffect(() => {\n if (!enabled) return;\n\n const uniformSetter = (\n gl: WebGL2RenderingContext,\n _program: WebGLProgram,\n locations: NonNullable<typeof ascii.uniformLocationsRef.current>\n ) => {\n const currentTime = performance.now() / 1000; // convert to seconds\n\n gl.uniform1f(locations.u_time, currentTime);\n gl.uniform1f(locations.u_rippleEnabled, 1.0);\n gl.uniform1f(locations.u_rippleSpeed, speedRef.current);\n\n // Remove old ripples that have expanded past the screen\n const maxDist = Math.sqrt(\n ascii.dimensions.cols ** 2 + ascii.dimensions.rows ** 2\n );\n const maxLifetime = maxDist / speedRef.current + 1.0;\n ripplesRef.current = ripplesRef.current.filter(\n (r) => currentTime - r.startTime < maxLifetime\n );\n\n // Pass ripple data to shader (vec4: x, y, startTime, enabled)\n for (let i = 0; i < MAX_RIPPLES; i++) {\n const loc = locations.u_ripples[i];\n if (loc) {\n const ripple = ripplesRef.current[i];\n if (ripple) {\n gl.uniform4f(loc, ripple.x, ripple.y, ripple.startTime, 1.0);\n } else {\n gl.uniform4f(loc, 0, 0, 0, 0.0); // disabled\n }\n }\n }\n };\n\n ascii.registerUniformSetter(\"ripple\", uniformSetter);\n\n return () => {\n ascii.unregisterUniformSetter(\"ripple\");\n };\n }, [ascii, enabled]);\n\n // Spawn a new ripple where the user clicks\n const onClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\n if (!enabledRef.current) return;\n\n const rect = e.currentTarget.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = (e.clientY - rect.top) / rect.height;\n\n // Add new ripple at the front\n ripplesRef.current.unshift({\n x,\n y,\n startTime: performance.now() / 1000,\n });\n\n // Cap at max ripples\n if (ripplesRef.current.length > MAX_RIPPLES) {\n ripplesRef.current.pop();\n }\n }, []);\n\n return { onClick };\n}\n","import { useEffect, useRef } from \"react\";\nimport type { AsciiContext, UseAsciiAudioOptions } from \"@/lib/webgl\";\n\nexport type { UseAsciiAudioOptions };\n\n// Hook Implementation\nexport function useAsciiAudio(\n ascii: AsciiContext,\n options: UseAsciiAudioOptions = {}\n): void {\n const { enabled = false, reactivity = 50, sensitivity = 50 } = options;\n\n // Web Audio API refs - these persist across renders\n const audioContextRef = useRef<AudioContext | null>(null);\n const analyzerRef = useRef<AnalyserNode | null>(null);\n const sourceRef = useRef<MediaElementAudioSourceNode | null>(null);\n const dataArrayRef = useRef<Uint8Array<ArrayBuffer> | null>(null);\n const volumeRef = useRef(0);\n const connectedVideoRef = useRef<HTMLVideoElement | null>(null);\n\n // Keep options in refs so the uniform setter closure always has fresh values\n const enabledRef = useRef(enabled);\n const reactivityRef = useRef(reactivity);\n const sensitivityRef = useRef(sensitivity);\n\n useEffect(() => {\n enabledRef.current = enabled;\n reactivityRef.current = reactivity;\n sensitivityRef.current = sensitivity;\n }, [enabled, reactivity, sensitivity]);\n\n // Reads frequency data from the analyzer and calculates average volume\n const updateVolume = () => {\n const analyzer = analyzerRef.current;\n const dataArray = dataArrayRef.current;\n if (!analyzer || !dataArray) return;\n\n // getByteFrequencyData fills the array with frequency values (0-255)\n analyzer.getByteFrequencyData(dataArray);\n\n // Average all frequency bins to get overall loudness\n let sum = 0;\n for (let i = 0; i < dataArray.length; i++) {\n sum += dataArray[i];\n }\n const average = sum / dataArray.length / 255; // normalize to 0-1\n\n // Smooth the volume so it doesn't jump around too much\n volumeRef.current = volumeRef.current * 0.7 + average * 0.3;\n };\n\n // Connect to video's audio stream\n useEffect(() => {\n if (!enabled) return;\n\n const video = ascii.videoRef.current;\n if (!video) return;\n\n const connectAudio = () => {\n // If we already connected this exact video element, just resume\n if (connectedVideoRef.current === video && audioContextRef.current) {\n audioContextRef.current.resume();\n return;\n }\n\n try {\n // AudioContext is the entry point to Web Audio API\n if (!audioContextRef.current) {\n audioContextRef.current = new AudioContext();\n }\n\n const ctx = audioContextRef.current;\n\n // AnalyserNode lets us extract frequency/time data from audio\n const analyzer = ctx.createAnalyser();\n analyzer.fftSize = 256; // smaller = faster, less detailed\n analyzer.smoothingTimeConstant = 0.8; // 0-1, higher = smoother\n analyzerRef.current = analyzer;\n\n // This array will hold the frequency data each frame\n dataArrayRef.current = new Uint8Array(\n analyzer.frequencyBinCount\n ) as Uint8Array<ArrayBuffer>;\n\n // createMediaElementSource connects our video element to the audio graph\n // IMPORTANT: a video can only be connected once, ever\n const source = ctx.createMediaElementSource(video);\n source.connect(analyzer);\n analyzer.connect(ctx.destination); // so we still hear the audio\n sourceRef.current = source;\n connectedVideoRef.current = video;\n\n ctx.resume();\n } catch (error) {\n console.warn(\"Failed to connect audio analyzer:\", error);\n }\n };\n\n const handlePlay = () => {\n connectAudio();\n };\n\n video.addEventListener(\"play\", handlePlay);\n\n // If video is already playing when this hook mounts\n if (!video.paused) {\n connectAudio();\n }\n\n return () => {\n video.removeEventListener(\"play\", handlePlay);\n };\n }, [ascii.videoRef, enabled]);\n\n // Register our uniform setter - this gets called every frame by the core hook\n useEffect(() => {\n if (!enabled) return;\n\n const uniformSetter = (\n gl: WebGL2RenderingContext,\n _program: WebGLProgram,\n locations: NonNullable<typeof ascii.uniformLocationsRef.current>\n ) => {\n // Update volume from audio analyzer\n updateVolume();\n\n // Pass values to the shader\n gl.uniform1f(locations.u_audioLevel, volumeRef.current);\n gl.uniform1f(locations.u_audioReactivity, reactivityRef.current / 100);\n gl.uniform1f(locations.u_audioSensitivity, sensitivityRef.current / 100);\n };\n\n ascii.registerUniformSetter(\"audio\", uniformSetter);\n\n return () => {\n ascii.unregisterUniformSetter(\"audio\");\n };\n }, [ascii, enabled]);\n\n // Cleanup audio context when component unmounts\n useEffect(() => {\n return () => {\n if (audioContextRef.current) {\n audioContextRef.current.close();\n }\n };\n }, []);\n}\n","\"use client\";\n\nimport { useVideoToAscii } from \"@/hooks/useVideoToAscii\";\nimport { useAsciiMouseEffect } from \"@/hooks/useAsciiMouseEffect\";\nimport { useAsciiRipple } from \"@/hooks/useAsciiRipple\";\nimport { useAsciiAudio } from \"@/hooks/useAsciiAudio\";\nimport { CHAR_WIDTH_RATIO, type VideoToAsciiProps } from \"@/lib/webgl\";\n\nexport type { VideoToAsciiProps };\n\n// Component Implementation\nexport function VideoToAscii({\n src,\n fontSize = 10,\n colored = false,\n blend = 0,\n highlight = 0,\n charset = \"standard\",\n maxWidth = 900,\n enableMouse = true,\n trailLength = 24,\n enableRipple = false,\n rippleSpeed = 40,\n audioReactivity = 0,\n audioSensitivity = 50,\n showStats = false,\n className = \"\",\n}: VideoToAsciiProps) {\n // Core hook handles WebGL setup and rendering\n const ascii = useVideoToAscii({\n fontSize,\n colored,\n blend,\n highlight,\n charset,\n maxWidth,\n });\n\n // Destructure to avoid linter issues with accessing refs\n const {\n containerRef,\n videoRef,\n canvasRef,\n stats,\n dimensions,\n isReady,\n isPlaying,\n } = ascii;\n\n // Feature hooks - always call them (React rules), enable/disable via options\n const mouseHandlers = useAsciiMouseEffect(ascii, {\n enabled: enableMouse,\n trailLength,\n });\n\n const rippleHandlers = useAsciiRipple(ascii, {\n enabled: enableRipple,\n speed: rippleSpeed,\n });\n\n useAsciiAudio(ascii, {\n enabled: audioReactivity > 0,\n reactivity: audioReactivity,\n sensitivity: audioSensitivity,\n });\n\n // Calculate canvas size in pixels\n const charWidth = fontSize * CHAR_WIDTH_RATIO;\n const pixelWidth = dimensions.cols * charWidth;\n const pixelHeight = dimensions.rows * fontSize;\n\n return (\n <div className={`video-to-ascii ${className}`}>\n {/* Hidden video element - feeds frames to WebGL */}\n <video\n ref={videoRef}\n src={src}\n muted={audioReactivity === 0}\n loop\n playsInline\n crossOrigin=\"anonymous\"\n style={{ display: \"none\" }}\n />\n\n {/* Interactive container */}\n <div\n ref={containerRef}\n className=\"relative cursor-pointer select-none overflow-hidden rounded\"\n style={{\n width: pixelWidth || \"100%\",\n height: pixelHeight || \"auto\",\n backgroundColor: \"#000\",\n }}\n {...(enableMouse ? mouseHandlers : {})}\n {...(enableRipple ? rippleHandlers : {})}\n >\n {/* WebGL canvas - all ASCII rendering happens here */}\n <canvas\n ref={canvasRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"block\",\n }}\n />\n\n {/* Stats overlay */}\n {showStats && isReady && (\n <div className=\"absolute top-2 left-2 bg-black/70 text-green-400 px-2 py-1 text-xs font-mono rounded\">\n {stats.fps} FPS | {stats.frameTime.toFixed(2)}ms | {dimensions.cols}\n ×{dimensions.rows}\n </div>\n )}\n\n {/* Play indicator */}\n {!isPlaying && isReady && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/50\">\n <div className=\"text-white text-lg\">▶ Press Space to Play</div>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nexport default VideoToAscii;\n"],"mappings":"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","names":["useCallback","useEffect","useRef","MAX_TRAIL_LENGTH","useRef","useEffect","useCallback","useCallback","useEffect","useRef","MAX_RIPPLES","useEffect","useRef"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "video2ascii",
3
+ "version": "1.0.0",
4
+ "description": "WebGL-powered video to ASCII converter for React",
5
+ "author": "Elijah Kurien",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/elijah058/video-to-ascii"
10
+ },
11
+ "keywords": [
12
+ "ascii",
13
+ "video",
14
+ "webgl",
15
+ "react",
16
+ "ascii-art"
17
+ ],
18
+ "main": "dist/index.js",
19
+ "module": "dist/index.mjs",
20
+ "types": "dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.mjs",
25
+ "require": "./dist/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "scripts": {
32
+ "dev": "next dev",
33
+ "build": "next build",
34
+ "build:lib": "tsup",
35
+ "start": "next start",
36
+ "lint": "eslint",
37
+ "prepublishOnly": "npm run build:lib"
38
+ },
39
+ "peerDependencies": {
40
+ "react": ">=18.0.0",
41
+ "react-dom": ">=18.0.0"
42
+ },
43
+ "dependencies": {},
44
+ "devDependencies": {
45
+ "@tailwindcss/postcss": "^4",
46
+ "@types/node": "^20",
47
+ "@types/react": "^19",
48
+ "@types/react-dom": "^19",
49
+ "eslint": "^9",
50
+ "eslint-config-next": "16.1.0",
51
+ "next": "16.1.0",
52
+ "raw-loader": "^4.0.2",
53
+ "react": "19.2.3",
54
+ "react-dom": "19.2.3",
55
+ "tailwindcss": "^4",
56
+ "tsup": "^8",
57
+ "typescript": "^5"
58
+ }
59
+ }