{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Balatro-TS-TW",
	"title": "Balatro",
	"description": "The swirling Balatro paint shader, with crisp pixels, a cursor that stirs the paint and click whirlpools.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Balatro/Balatro.tsx",
			"content": "'use client';\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\n\nexport interface BalatroProps {\n  color1?: string;\n  color2?: string;\n  color3?: string;\n  contrast?: number;\n  lighting?: number;\n  swirl?: number;\n  speed?: number;\n  rotationSpeed?: number;\n  scale?: number;\n  detail?: number;\n  offset?: [number, number];\n  pixelSize?: number;\n  crt?: number;\n  mouseInteraction?: boolean;\n  mouseStrength?: number;\n  clickSwirl?: boolean;\n  intro?: boolean;\n  lightMode?: boolean;\n  opacity?: number;\n  maxDevicePixelRatio?: number;\n  className?: string;\n  style?: CSSProperties;\n}\n\ninterface Settings {\n  color1: number[];\n  color2: number[];\n  color3: number[];\n  contrast: number;\n  lighting: number;\n  swirl: number;\n  speed: number;\n  rotation: number;\n  scale: number;\n  detail: number;\n  offset: number[];\n  pixelSize: number;\n  crt: number;\n  mouse: boolean;\n  strength: number;\n  clickSwirl: boolean;\n  intro: boolean;\n  light: boolean;\n  opacity: number;\n  maxRatio: number;\n}\n\ninterface Program {\n  program: WebGLProgram;\n  uniforms: Record<string, WebGLUniformLocation | null>;\n}\n\ninterface Target {\n  texture: WebGLTexture;\n  framebuffer: WebGLFramebuffer;\n}\n\ninterface Twirl {\n  x: number;\n  y: number;\n  age: number;\n  direction: number;\n}\n\nconst PIXEL_BUDGET = 2.4e6;\nconst MAX_TWIRLS = 4;\nconst FIELD_SCALE = 8;\nconst STIR_RADIUS = 90;\n\nconst VERTEX = `#version 300 es\nconst vec2 corners[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\nvoid main() {\n  gl_Position = vec4(corners[gl_VertexID], 0.0, 1.0);\n}`;\n\nconst FIELD = `#version 300 es\nprecision highp float;\nuniform sampler2D uPrev;\nuniform vec2 uSize;\nuniform vec2 uCss;\nuniform vec2 uFrom;\nuniform vec2 uTo;\nuniform vec2 uPush;\nuniform float uRadius;\nuniform float uDecay;\nout vec4 fragColor;\n\nvoid main() {\n  vec2 cell = gl_FragCoord.xy / uSize;\n  vec2 p = vec2(cell.x, 1.0 - cell.y) * uCss;\n  vec2 seg = uTo - uFrom;\n  float len2 = max(dot(seg, seg), 1e-6);\n  float h = clamp(dot(p - uFrom, seg) / len2, 0.0, 1.0);\n  vec2 gap = p - uFrom - seg * h;\n  vec2 delta = uPush * exp(-dot(gap, gap) / (uRadius * uRadius));\n  vec2 src = p - delta;\n  vec2 prev = texture(uPrev, vec2(src.x / uCss.x, 1.0 - src.y / uCss.y)).xy;\n  fragColor = vec4((prev + delta) * uDecay, 0.0, 1.0);\n}`;\n\nconst PAINT = `#version 300 es\nprecision highp float;\nuniform vec2 uCss;\nuniform vec2 uCanvas;\nuniform float uBlock;\nuniform float uFlow;\nuniform float uSpin;\nuniform vec2 uOffset;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSwirl;\nuniform float uScale;\nuniform int uDetail;\nuniform sampler2D uField;\nuniform float uFieldOn;\nuniform vec4 uTwirls[${MAX_TWIRLS}];\nuniform float uReveal;\nuniform float uLight;\nuniform float uOpacity;\nout vec4 fragColor;\n\nvec3 paint(vec2 screenSize, vec2 coords) {\n  vec2 uv = (coords - 0.5 * screenSize) / length(screenSize) - uOffset;\n  float radius = length(uv);\n  float speed = uSpin + 302.2;\n  float angle = atan(uv.y, uv.x) + speed - 20.0 * (uSwirl * radius + (1.0 - uSwirl));\n  vec2 mid = (screenSize / length(screenSize)) / 2.0;\n  uv = vec2(radius * cos(angle) + mid.x, radius * sin(angle) + mid.y) - mid;\n\n  uv *= 30.0 / uScale;\n  speed = uFlow;\n  vec2 uv2 = vec2(uv.x + uv.y);\n  for (int i = 0; i < 8; i++) {\n    if (i >= uDetail) break;\n    uv2 += sin(max(uv.x, uv.y)) + uv;\n    uv += 0.5 * vec2(cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121), sin(uv2.x - 0.113 * speed));\n    uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n  }\n\n  float contrastMod = 0.25 * uContrast + 0.5 * uSwirl + 1.2;\n  float amount = min(2.0, max(0.0, length(uv) * 0.035 * contrastMod));\n  float c1 = max(0.0, 1.0 - contrastMod * abs(1.0 - amount));\n  float c2 = max(0.0, 1.0 - contrastMod * abs(amount));\n  float c3 = 1.0 - min(1.0, c1 + c2);\n  float light = (uLighting - 0.2) * max(c1 * 5.0 - 4.0, 0.0) + uLighting * max(c2 * 5.0 - 4.0, 0.0);\n  vec3 gap = uLight > 0.5 ? mix(uColor3, vec3(1.0), 0.9) : uColor3;\n  float base = 0.3 / uContrast;\n  vec3 color = base * uColor1 + (1.0 - base) * (uColor1 * c1 + uColor2 * c2 + gap * c3) + light;\n  return mix(gap, color, uReveal);\n}\n\nvoid main() {\n  vec2 cell = floor(gl_FragCoord.xy) + 0.5;\n  vec2 p = vec2(cell.x, uCanvas.y - cell.y) * uBlock;\n  if (uFieldOn > 0.5) {\n    p -= texture(uField, vec2(p.x / uCss.x, 1.0 - p.y / uCss.y)).xy;\n  }\n  for (int i = 0; i < ${MAX_TWIRLS}; i++) {\n    vec4 twirl = uTwirls[i];\n    if (twirl.w <= 0.0) continue;\n    vec2 rel = p - twirl.xy;\n    float turn = twirl.z * exp(-dot(rel, rel) / (twirl.w * twirl.w));\n    float c = cos(turn);\n    float s = sin(turn);\n    p = twirl.xy + vec2(c * rel.x - s * rel.y, s * rel.x + c * rel.y);\n  }\n  vec3 color = paint(uCss, vec2(p.x, uCss.y - p.y));\n  float grain = (fract(52.9829189 * fract(dot(cell, vec2(0.06711056, 0.00583715)))) - 0.5) / 255.0;\n  color = clamp(color + grain, 0.0, 1.0);\n  fragColor = vec4(color * uOpacity, uOpacity);\n}`;\n\nconst SCREEN = `#version 300 es\nprecision highp float;\nuniform sampler2D uPaint;\nuniform vec2 uCanvas;\nuniform vec2 uSource;\nuniform float uDevice;\nuniform float uCrt;\nout vec4 fragColor;\n\nfloat smoother(float edge0, float edge1, float x) {\n  float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n  return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid main() {\n  vec2 device = vec2(gl_FragCoord.x, uCanvas.y - gl_FragCoord.y);\n  vec2 cell = floor(device / uDevice);\n  vec4 texel = texture(uPaint, vec2((cell.x + 0.5) / uSource.x, 1.0 - (cell.y + 0.5) / uSource.y));\n  vec3 color = texel.rgb;\n  float glow = dot(color, vec3(0.2126, 0.7152, 0.0722));\n  float period = max(uDevice, 3.0);\n  float row = fract(device.y / period);\n  float line = smoother(0.5, 0.92, row) * (1.0 - smoother(0.92, 1.0, row));\n  color *= 1.0 - uCrt * 0.34 * line * (1.0 - 0.7 * glow);\n  vec2 screen = gl_FragCoord.xy / uCanvas;\n  float edge = smoother(0.0, 0.18, screen.x) * smoother(0.0, 0.18, 1.0 - screen.x);\n  edge *= smoother(0.0, 0.24, screen.y) * smoother(0.0, 0.24, 1.0 - screen.y);\n  color *= mix(1.0, 0.78 + 0.22 * edge, uCrt);\n  color *= 1.0 + uCrt * 0.1;\n  fragColor = vec4(min(color, vec3(texel.a)), texel.a);\n}`;\n\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst parseColor = (value: string): number[] | null => {\n  const text = String(value || '').trim();\n  const hex = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(text);\n  if (hex) {\n    const full = hex[1].length === 3 ? hex[1].replace(/./g, digit => digit + digit) : hex[1];\n    return [0, 2, 4].map(index => parseInt(full.slice(index, index + 2), 16) / 255);\n  }\n  const rgb = /^rgba?\\(\\s*([\\d.]+)[\\s,]+([\\d.]+)[\\s,]+([\\d.]+)/i.exec(text);\n  if (rgb) return [rgb[1], rgb[2], rgb[3]].map(channel => clamp(parseFloat(channel) / 255, 0, 1));\n  return null;\n};\n\nconst compile = (gl: WebGL2RenderingContext, type: number, source: string) => {\n  const shader = gl.createShader(type);\n  if (!shader) return null;\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    console.error(gl.getShaderInfoLog(shader));\n    gl.deleteShader(shader);\n    return null;\n  }\n  return shader;\n};\n\nconst link = (gl: WebGL2RenderingContext, source: string): Program | null => {\n  const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX);\n  const fragment = compile(gl, gl.FRAGMENT_SHADER, source);\n  const program = vertex && fragment ? gl.createProgram() : null;\n  if (!vertex || !fragment || !program) {\n    if (vertex) gl.deleteShader(vertex);\n    if (fragment) gl.deleteShader(fragment);\n    return null;\n  }\n  gl.attachShader(program, vertex);\n  gl.attachShader(program, fragment);\n  gl.linkProgram(program);\n  gl.deleteShader(vertex);\n  gl.deleteShader(fragment);\n  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n    console.error(gl.getProgramInfoLog(program));\n    gl.deleteProgram(program);\n    return null;\n  }\n  const uniforms: Program['uniforms'] = {};\n  const count = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);\n  for (let index = 0; index < count; index++) {\n    const info = gl.getActiveUniform(program, index);\n    if (info) uniforms[info.name.replace(/\\[0\\]$/, '')] = gl.getUniformLocation(program, info.name);\n  }\n  return { program, uniforms };\n};\n\nconst smoother = (t: number) => {\n  const x = clamp(t, 0, 1);\n  return x * x * x * (x * (x * 6 - 15) + 10);\n};\n\nconst Balatro = ({\n  color1 = '#DE443B',\n  color2 = '#006BB4',\n  color3 = '#162325',\n  contrast = 3.5,\n  lighting = 0.4,\n  swirl = 0.25,\n  speed = 1,\n  rotationSpeed = 0,\n  scale = 1,\n  detail = 5,\n  offset = [0, 0],\n  pixelSize = 1.5,\n  crt = 0,\n  mouseInteraction = true,\n  mouseStrength = 1,\n  clickSwirl = true,\n  intro = true,\n  lightMode = false,\n  opacity = 1,\n  maxDevicePixelRatio = 2,\n  className = '',\n  style\n}: BalatroProps) => {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const wakeRef = useRef<(() => void) | null>(null);\n  const [generation, setGeneration] = useState(0);\n\n  const settings: Settings = {\n    color1: parseColor(color1) || [0.871, 0.267, 0.231],\n    color2: parseColor(color2) || [0, 0.42, 0.706],\n    color3: parseColor(color3) || [0.086, 0.137, 0.145],\n    contrast: clamp(contrast, 0.5, 10),\n    lighting: clamp(lighting, 0, 1.5),\n    swirl: clamp(swirl, 0, 1),\n    speed: Math.max(0, speed),\n    rotation: clamp(rotationSpeed, -10, 10),\n    scale: clamp(scale, 0.2, 5),\n    detail: clamp(Math.round(detail), 1, 8),\n    offset: Array.isArray(offset) ? [Number(offset[0]) || 0, Number(offset[1]) || 0] : [0, 0],\n    pixelSize: clamp(pixelSize, 0, 24),\n    crt: clamp(crt, 0, 1),\n    mouse: mouseInteraction,\n    strength: clamp(mouseStrength, 0, 3),\n    clickSwirl,\n    intro,\n    light: lightMode,\n    opacity: clamp(opacity, 0, 1),\n    maxRatio: clamp(maxDevicePixelRatio, 0.5, 3)\n  };\n  const settingsRef = useRef(settings);\n  settingsRef.current = settings;\n\n  useEffect(() => {\n    wakeRef.current?.();\n  });\n\n  useEffect(() => {\n    const container = containerRef.current;\n    const canvas = canvasRef.current;\n    if (!container || !canvas) return;\n\n    const gl = canvas.getContext('webgl2', {\n      alpha: true,\n      premultipliedAlpha: true,\n      antialias: false,\n      depth: false,\n      stencil: false,\n      powerPreference: 'high-performance'\n    });\n    const paintProgram = gl ? link(gl, PAINT) : null;\n    const screenProgram = gl ? link(gl, SCREEN) : null;\n    if (!gl || !paintProgram || !screenProgram) return;\n    const floatTargets = !!(\n      gl.getExtension('EXT_color_buffer_float') || gl.getExtension('EXT_color_buffer_half_float')\n    );\n    const fieldProgram = floatTargets ? link(gl, FIELD) : null;\n\n    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n    const vao = gl.createVertexArray();\n    const view = { cssWidth: 1, cssHeight: 1, block: 1, device: 1, width: 1, height: 1, crt: false };\n    const paintTarget = {\n      texture: gl.createTexture() as WebGLTexture,\n      framebuffer: gl.createFramebuffer() as WebGLFramebuffer,\n      width: 0,\n      height: 0\n    };\n    const field: { width: number; height: number; targets: Target[]; energy: number } = {\n      width: 0,\n      height: 0,\n      targets: [],\n      energy: 0\n    };\n    const pointer = { x: 0, y: 0, lastX: 0, lastY: 0, vx: 0, inside: false, ready: false };\n    const steer = { value: 0 };\n    const twirls: Twirl[] = [];\n    const twirlData = new Float32Array(MAX_TWIRLS * 4);\n    let flow = 14;\n    let spin = 0;\n    let opening = settingsRef.current.intro && !reduced && generation === 0 ? 0 : 1;\n    let raf = 0;\n    let last = 0;\n    let visible = true;\n    let pageVisible = document.visibilityState !== 'hidden';\n    let lost = false;\n\n    const makeTarget = (width: number, height: number): Target => {\n      const texture = gl.createTexture() as WebGLTexture;\n      gl.bindTexture(gl.TEXTURE_2D, texture);\n      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG16F, width, height, 0, gl.RG, gl.HALF_FLOAT, null);\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      const framebuffer = gl.createFramebuffer() as WebGLFramebuffer;\n      gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n      gl.clearColor(0, 0, 0, 0);\n      gl.clear(gl.COLOR_BUFFER_BIT);\n      gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n      return { texture, framebuffer };\n    };\n\n    const dropField = () => {\n      field.targets.forEach(target => {\n        gl.deleteTexture(target.texture);\n        gl.deleteFramebuffer(target.framebuffer);\n      });\n      field.targets = [];\n      field.width = 0;\n      field.height = 0;\n      field.energy = 0;\n    };\n\n    let resizePending = false;\n    const applyResize = () => {\n      const s = settingsRef.current;\n      const rect = container.getBoundingClientRect();\n      const cssWidth = Math.max(1, rect.width);\n      const cssHeight = Math.max(1, rect.height);\n      const dpr = Math.min(window.devicePixelRatio || 1, s.maxRatio);\n      const budget = Math.ceil(Math.sqrt((cssWidth * cssHeight * dpr * dpr) / PIXEL_BUDGET) - 0.05);\n      const device = Math.max(s.pixelSize > 0 ? Math.round(s.pixelSize * dpr) : 1, budget, 1);\n      const width = Math.max(1, Math.ceil((cssWidth * dpr) / device));\n      const height = Math.max(1, Math.ceil((cssHeight * dpr) / device));\n      const crt = s.crt > 0;\n      const canvasWidth = crt ? width * device : width;\n      const canvasHeight = crt ? height * device : height;\n      if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {\n        canvas.width = canvasWidth;\n        canvas.height = canvasHeight;\n      }\n      canvas.style.width = `${(width * device) / dpr}px`;\n      canvas.style.height = `${(height * device) / dpr}px`;\n      canvas.style.imageRendering = !crt && s.pixelSize > 0 && device >= 2 ? 'pixelated' : 'auto';\n      if (crt && (paintTarget.width !== width || paintTarget.height !== height)) {\n        gl.bindTexture(gl.TEXTURE_2D, paintTarget.texture);\n        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\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        gl.bindFramebuffer(gl.FRAMEBUFFER, paintTarget.framebuffer);\n        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, paintTarget.texture, 0);\n        gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n        paintTarget.width = width;\n        paintTarget.height = height;\n      }\n      view.cssWidth = cssWidth;\n      view.cssHeight = cssHeight;\n      view.block = device / dpr;\n      view.device = device;\n      view.width = width;\n      view.height = height;\n      view.crt = crt;\n      if (!fieldProgram) return;\n      const fieldWidth = clamp(Math.ceil(cssWidth / FIELD_SCALE), 8, 320);\n      const fieldHeight = clamp(Math.ceil(cssHeight / FIELD_SCALE), 8, 320);\n      if (fieldWidth === field.width && fieldHeight === field.height) return;\n      dropField();\n      field.width = fieldWidth;\n      field.height = fieldHeight;\n      field.targets = [makeTarget(fieldWidth, fieldHeight), makeTarget(fieldWidth, fieldHeight)];\n    };\n\n    const resize = () => {\n      resizePending = true;\n      wake();\n    };\n\n    const stir = (\n      fromX: number,\n      fromY: number,\n      toX: number,\n      toY: number,\n      pushX: number,\n      pushY: number,\n      decay: number\n    ) => {\n      if (!fieldProgram) return;\n      const [read, write] = field.targets;\n      gl.bindFramebuffer(gl.FRAMEBUFFER, write.framebuffer);\n      gl.viewport(0, 0, field.width, field.height);\n      gl.useProgram(fieldProgram.program);\n      gl.activeTexture(gl.TEXTURE0);\n      gl.bindTexture(gl.TEXTURE_2D, read.texture);\n      const u = fieldProgram.uniforms;\n      gl.uniform1i(u.uPrev, 0);\n      gl.uniform2f(u.uSize, field.width, field.height);\n      gl.uniform2f(u.uCss, view.cssWidth, view.cssHeight);\n      gl.uniform2f(u.uFrom, fromX, fromY);\n      gl.uniform2f(u.uTo, toX, toY);\n      gl.uniform2f(u.uPush, pushX, pushY);\n      gl.uniform1f(u.uRadius, STIR_RADIUS);\n      gl.uniform1f(u.uDecay, decay);\n      gl.drawArrays(gl.TRIANGLES, 0, 3);\n      field.targets = [write, read];\n    };\n\n    const frame = (now: number) => {\n      raf = 0;\n      if (resizePending) {\n        resizePending = false;\n        applyResize();\n      }\n      if (lost) return;\n      const s = settingsRef.current;\n      const dt = clamp((now - last) / 1000 || 0, 0, 0.05);\n      last = Math.max(last, now);\n      const pace = reduced ? 0.2 : 1;\n      flow += dt * s.speed * 7 * pace;\n      spin += dt * s.rotation * 0.2 * pace;\n      if (opening < 1) opening = Math.min(1, opening + dt / 2.2);\n\n      const tracking = s.mouse && pointer.inside;\n      const steerTarget = tracking ? (pointer.x / view.cssWidth - 0.5) * 0.35 * s.strength : 0;\n      steer.value += (steerTarget - steer.value) * (1 - Math.exp(-dt / 0.8));\n\n      gl.bindVertexArray(vao);\n      if (fieldProgram && field.targets.length) {\n        const dx = pointer.x - pointer.lastX;\n        const dy = pointer.y - pointer.lastY;\n        const moved = tracking && pointer.ready && (dx !== 0 || dy !== 0);\n        const decay = Math.exp(-dt / 1.6);\n        if (moved || field.energy > 0.05) {\n          const amount = 0.85 * s.strength * (reduced ? 0.4 : 1);\n          const travel = moved ? Math.hypot(dx, dy) * amount : 0;\n          const steps = Math.max(1, Math.ceil(travel / (STIR_RADIUS * 0.3)));\n          for (let step = 0; step < steps; step++) {\n            const a = step / steps;\n            const b = (step + 1) / steps;\n            stir(\n              pointer.lastX + dx * a,\n              pointer.lastY + dy * a,\n              pointer.lastX + dx * b,\n              pointer.lastY + dy * b,\n              moved ? dx * (b - a) * amount : 0,\n              moved ? dy * (b - a) * amount : 0,\n              step === steps - 1 ? decay : 1\n            );\n          }\n          field.energy = field.energy * decay + travel;\n          if (field.energy <= 0.05) {\n            field.targets.forEach(target => {\n              gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);\n              gl.clearColor(0, 0, 0, 0);\n              gl.clear(gl.COLOR_BUFFER_BIT);\n            });\n          }\n        }\n      }\n      pointer.lastX = pointer.x;\n      pointer.lastY = pointer.y;\n\n      for (let index = twirls.length - 1; index >= 0; index--) {\n        twirls[index].age += dt;\n        if (twirls[index].age >= 3.2) twirls.splice(index, 1);\n      }\n      twirlData.fill(0);\n      twirls.forEach((twirl, index) => {\n        const t = twirl.age;\n        const rise = smoother(t / 0.25);\n        const fall = 1 - smoother((t - 0.25) / 2.95);\n        twirlData[index * 4] = twirl.x;\n        twirlData[index * 4 + 1] = twirl.y;\n        twirlData[index * 4 + 2] = twirl.direction * 1.9 * rise * fall;\n        twirlData[index * 4 + 3] = 150 * (1 + 0.6 * smoother(t / 3.2));\n      });\n\n      const eased = 1 - Math.pow(1 - opening, 3);\n      const unwind = 1 - eased;\n      gl.bindFramebuffer(gl.FRAMEBUFFER, view.crt ? paintTarget.framebuffer : null);\n      gl.viewport(0, 0, view.width, view.height);\n      gl.useProgram(paintProgram.program);\n      const u = paintProgram.uniforms;\n      gl.activeTexture(gl.TEXTURE0);\n      gl.bindTexture(gl.TEXTURE_2D, field.targets.length ? field.targets[0].texture : null);\n      gl.uniform1i(u.uField, 0);\n      gl.uniform1f(u.uFieldOn, field.targets.length && field.energy > 0.05 ? 1 : 0);\n      gl.uniform2f(u.uCss, view.cssWidth, view.cssHeight);\n      gl.uniform2f(u.uCanvas, view.width, view.height);\n      gl.uniform1f(u.uBlock, view.block);\n      gl.uniform1f(u.uFlow, flow);\n      gl.uniform1f(u.uSpin, -0.4 + spin + steer.value + 2.6 * unwind * unwind);\n      gl.uniform2f(u.uOffset, s.offset[0], s.offset[1]);\n      gl.uniform3f(u.uColor1, s.color1[0], s.color1[1], s.color1[2]);\n      gl.uniform3f(u.uColor2, s.color2[0], s.color2[1], s.color2[2]);\n      gl.uniform3f(u.uColor3, s.color3[0], s.color3[1], s.color3[2]);\n      gl.uniform1f(u.uContrast, s.contrast);\n      gl.uniform1f(u.uLighting, s.lighting);\n      gl.uniform1f(u.uSwirl, Math.min(1, s.swirl + 0.55 * unwind));\n      gl.uniform1f(u.uScale, s.scale);\n      gl.uniform1i(u.uDetail, s.detail);\n      gl.uniform4fv(u.uTwirls, twirlData);\n      gl.uniform1f(u.uReveal, smoother(opening / 0.45));\n      gl.uniform1f(u.uLight, s.light ? 1 : 0);\n      gl.uniform1f(u.uOpacity, s.opacity);\n      gl.drawArrays(gl.TRIANGLES, 0, 3);\n\n      if (view.crt) {\n        gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n        gl.viewport(0, 0, canvas.width, canvas.height);\n        gl.useProgram(screenProgram.program);\n        const v = screenProgram.uniforms;\n        gl.activeTexture(gl.TEXTURE0);\n        gl.bindTexture(gl.TEXTURE_2D, paintTarget.texture);\n        gl.uniform1i(v.uPaint, 0);\n        gl.uniform2f(v.uCanvas, canvas.width, canvas.height);\n        gl.uniform2f(v.uSource, view.width, view.height);\n        gl.uniform1f(v.uDevice, view.device);\n        gl.uniform1f(v.uCrt, s.crt);\n        gl.drawArrays(gl.TRIANGLES, 0, 3);\n      }\n\n      const settling = Math.abs(steerTarget - steer.value) > 0.0005 || field.energy > 0.05 || twirls.length > 0;\n      const busy = s.speed > 0 || s.rotation !== 0 || opening < 1 || settling;\n      if (busy && visible && pageVisible) raf = requestAnimationFrame(frame);\n    };\n\n    const wake = () => {\n      if (raf || lost || !visible || !pageVisible) return;\n      last = performance.now();\n      raf = requestAnimationFrame(frame);\n    };\n    wakeRef.current = () => {\n      resize();\n      wake();\n    };\n\n    const locate = (event: PointerEvent) => {\n      const rect = container.getBoundingClientRect();\n      return {\n        x: event.clientX - rect.left,\n        y: event.clientY - rect.top,\n        inside:\n          event.clientX >= rect.left &&\n          event.clientX <= rect.right &&\n          event.clientY >= rect.top &&\n          event.clientY <= rect.bottom\n      };\n    };\n\n    const onPointerMove = (event: PointerEvent) => {\n      const spot = locate(event);\n      if (!pointer.ready || !pointer.inside) {\n        pointer.lastX = spot.x;\n        pointer.lastY = spot.y;\n      }\n      pointer.vx = spot.x - pointer.x;\n      pointer.x = spot.x;\n      pointer.y = spot.y;\n      pointer.inside = spot.inside;\n      pointer.ready = true;\n      wake();\n    };\n    const onPointerDown = (event: PointerEvent) => {\n      const s = settingsRef.current;\n      const spot = locate(event);\n      if (!spot.inside || !s.clickSwirl || reduced) return;\n      twirls.push({ x: spot.x, y: spot.y, age: 0, direction: pointer.vx < 0 ? 1 : -1 });\n      if (twirls.length > MAX_TWIRLS) twirls.shift();\n      wake();\n    };\n    const onLeave = () => {\n      pointer.inside = false;\n      wake();\n    };\n    const onPointerUp = (event: PointerEvent) => {\n      if (event.pointerType !== 'mouse') onLeave();\n    };\n    window.addEventListener('pointermove', onPointerMove, { passive: true });\n    window.addEventListener('pointerdown', onPointerDown, { passive: true });\n    window.addEventListener('pointerup', onPointerUp, { passive: true });\n    window.addEventListener('pointercancel', onPointerUp, { passive: true });\n    document.documentElement.addEventListener('pointerleave', onLeave);\n\n    const onLost = (event: Event) => {\n      event.preventDefault();\n      lost = true;\n      cancelAnimationFrame(raf);\n      raf = 0;\n    };\n    const onRestored = () => setGeneration(value => value + 1);\n    canvas.addEventListener('webglcontextlost', onLost);\n    canvas.addEventListener('webglcontextrestored', onRestored);\n\n    const resizeObserver = new ResizeObserver(() => {\n      resize();\n      wake();\n    });\n    resizeObserver.observe(container);\n\n    const intersection = new IntersectionObserver(entries => {\n      visible = entries.some(entry => entry.isIntersecting);\n      wake();\n    });\n    intersection.observe(container);\n\n    const onVisibility = () => {\n      pageVisible = document.visibilityState !== 'hidden';\n      wake();\n    };\n    document.addEventListener('visibilitychange', onVisibility);\n\n    resize();\n    wake();\n\n    return () => {\n      cancelAnimationFrame(raf);\n      wakeRef.current = null;\n      resizeObserver.disconnect();\n      intersection.disconnect();\n      window.removeEventListener('pointermove', onPointerMove);\n      window.removeEventListener('pointerdown', onPointerDown);\n      window.removeEventListener('pointerup', onPointerUp);\n      window.removeEventListener('pointercancel', onPointerUp);\n      document.documentElement.removeEventListener('pointerleave', onLeave);\n      document.removeEventListener('visibilitychange', onVisibility);\n      canvas.removeEventListener('webglcontextlost', onLost);\n      canvas.removeEventListener('webglcontextrestored', onRestored);\n      if (!gl.isContextLost()) {\n        dropField();\n        gl.deleteTexture(paintTarget.texture);\n        gl.deleteFramebuffer(paintTarget.framebuffer);\n        gl.deleteVertexArray(vao);\n        gl.deleteProgram(paintProgram.program);\n        gl.deleteProgram(screenProgram.program);\n        if (fieldProgram) gl.deleteProgram(fieldProgram.program);\n      }\n    };\n  }, [generation]);\n\n  return (\n    <div ref={containerRef} className={`relative h-full w-full overflow-hidden ${className}`.trim()} style={style}>\n      <canvas ref={canvasRef} className=\"absolute inset-0 block h-full w-full\" aria-hidden=\"true\" />\n    </div>\n  );\n};\n\nexport default Balatro;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}