{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "ElectricLogo-JS-CSS",
	"title": "ElectricLogo",
	"description": "Turns any logo into a living lightning outline, with arcs that leap off its edges.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "ElectricLogo.css",
			"target": "@components/ElectricLogo.css",
			"content": ".electric-logo {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "ElectricLogo.jsx",
			"content": "'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';\n\nimport './ElectricLogo.css';\n\nconst BOLT = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(\n  '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\"><path d=\"M39 3 12 37h17l-4 24 27-34H35z\" fill=\"#fff\"/></svg>'\n)}`;\nconst RASTER = 560;\nconst CELL = 4;\nconst FAR = 1e20;\nconst ARCS = 5;\nconst PULSES = 3;\nconst PIXEL_BUDGET = 4e6;\n\nconst hexToRgb = hex => {\n  let h = String(hex || '').replace('#', '');\n  if (h.length === 3) h = h.replace(/./g, c => c + c);\n  const n = parseInt(h.slice(0, 6), 16);\n  return Number.isNaN(n) ? [1, 1, 1] : [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n};\n\nconst transformLine = (f, d, v, z, n) => {\n  let k = 0;\n  v[0] = 0;\n  z[0] = -FAR;\n  z[1] = FAR;\n  for (let q = 1; q < n; q++) {\n    let s = (f[q] + q * q - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);\n    while (s <= z[k]) {\n      k--;\n      s = (f[q] + q * q - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);\n    }\n    k++;\n    v[k] = q;\n    z[k] = s;\n    z[k + 1] = FAR;\n  }\n  k = 0;\n  for (let q = 0; q < n; q++) {\n    while (z[k + 1] < q) k++;\n    d[q] = (q - v[k]) * (q - v[k]) + f[v[k]];\n  }\n};\n\nconst transformGrid = (grid, w, h) => {\n  const n = Math.max(w, h);\n  const f = new Float64Array(n);\n  const d = new Float64Array(n);\n  const v = new Int32Array(n);\n  const z = new Float64Array(n + 1);\n  for (let x = 0; x < w; x++) {\n    for (let y = 0; y < h; y++) f[y] = grid[y * w + x];\n    transformLine(f, d, v, z, h);\n    for (let y = 0; y < h; y++) grid[y * w + x] = d[y];\n  }\n  for (let y = 0; y < h; y++) {\n    for (let x = 0; x < w; x++) f[x] = grid[y * w + x];\n    transformLine(f, d, v, z, w);\n    for (let x = 0; x < w; x++) grid[y * w + x] = d[x];\n  }\n};\n\nconst blurLine = (src, dst, offset, stride, n, r) => {\n  const scale = 1 / (2 * r + 1);\n  let sum = 0;\n  for (let i = 0; i <= r && i < n; i++) sum += src[offset + i * stride];\n  for (let i = 0; i < n; i++) {\n    dst[offset + i * stride] = sum * scale;\n    if (i + r + 1 < n) sum += src[offset + (i + r + 1) * stride];\n    if (i - r >= 0) sum -= src[offset + (i - r) * stride];\n  }\n};\n\nconst blurGrid = (grid, w, h, r) => {\n  const tmp = new Float32Array(w * h);\n  for (let pass = 0; pass < 3; pass++) {\n    for (let y = 0; y < h; y++) blurLine(grid, tmp, y * w, 1, w, r);\n    for (let x = 0; x < w; x++) blurLine(tmp, grid, x, w, h, r);\n  }\n};\n\nconst readCoverage = (data, w, h) => {\n  const coverage = new Float32Array(w * h);\n  let clear = 0;\n  for (let i = 0; i < w * h; i++) if (data[i * 4 + 3] < 250) clear++;\n  if (clear > w * h * 0.01) {\n    for (let i = 0; i < w * h; i++) coverage[i] = data[i * 4 + 3] / 255;\n    return coverage;\n  }\n  let r = 0;\n  let g = 0;\n  let b = 0;\n  let n = 0;\n  const sample = i => {\n    r += data[i * 4];\n    g += data[i * 4 + 1];\n    b += data[i * 4 + 2];\n    n++;\n  };\n  for (let x = 0; x < w; x++) {\n    sample(x);\n    sample((h - 1) * w + x);\n  }\n  for (let y = 0; y < h; y++) {\n    sample(y * w);\n    sample(y * w + w - 1);\n  }\n  r /= n;\n  g /= n;\n  b /= n;\n  for (let i = 0; i < w * h; i++) {\n    const diff = Math.max(Math.abs(data[i * 4] - r), Math.abs(data[i * 4 + 1] - g), Math.abs(data[i * 4 + 2] - b));\n    coverage[i] = Math.min(1, Math.max(0, (diff - 24) / 48));\n  }\n  return coverage;\n};\n\nconst sampleField = (shape, x, y) => {\n  const { field, width, height } = shape;\n  const cx = Math.min(Math.max(x, 0.5), width - 0.5);\n  const cy = Math.min(Math.max(y, 0.5), height - 0.5);\n  const x0 = Math.min(Math.floor(cx - 0.5), width - 2);\n  const y0 = Math.min(Math.floor(cy - 0.5), height - 2);\n  const tx = cx - 0.5 - x0;\n  const ty = cy - 0.5 - y0;\n  const i = y0 * width + x0;\n  const top = field[i] + (field[i + 1] - field[i]) * tx;\n  const bottom = field[i + width] + (field[i + width + 1] - field[i + width]) * tx;\n  return top + (bottom - top) * ty + Math.hypot(x - cx, y - cy);\n};\n\nconst traceShape = image => {\n  const iw = image.naturalWidth || image.width;\n  const ih = image.naturalHeight || image.height;\n  if (!iw || !ih) return null;\n  const fit = RASTER / Math.max(iw, ih);\n  const w = Math.max(2, Math.round(iw * fit));\n  const h = Math.max(2, Math.round(ih * fit));\n  const canvas = document.createElement('canvas');\n  canvas.width = w;\n  canvas.height = h;\n  const ctx = canvas.getContext('2d', { willReadFrequently: true });\n  if (!ctx) return null;\n  ctx.drawImage(image, 0, 0, w, h);\n  const coverage = readCoverage(ctx.getImageData(0, 0, w, h).data, w, h);\n\n  let left = w;\n  let top = h;\n  let right = -1;\n  let bottom = -1;\n  for (let y = 0; y < h; y++) {\n    for (let x = 0; x < w; x++) {\n      if (coverage[y * w + x] <= 0.01) continue;\n      if (x < left) left = x;\n      if (x > right) right = x;\n      if (y < top) top = y;\n      if (y > bottom) bottom = y;\n    }\n  }\n  if (right < 0) return null;\n\n  const logoWidth = right - left + 1;\n  const logoHeight = bottom - top + 1;\n  const pad = Math.ceil(Math.max(logoWidth, logoHeight) * 0.25) + 2;\n  const width = logoWidth + pad * 2;\n  const height = logoHeight + pad * 2;\n  const outer = new Float32Array(width * height);\n  const inner = new Float32Array(width * height);\n  for (let y = 0; y < height; y++) {\n    for (let x = 0; x < width; x++) {\n      const sx = x - pad + left;\n      const sy = y - pad + top;\n      const a = sx >= 0 && sy >= 0 && sx < w && sy < h ? coverage[sy * w + sx] : 0;\n      const i = y * width + x;\n      if (a >= 1) {\n        outer[i] = 0;\n        inner[i] = FAR;\n      } else if (a <= 0) {\n        outer[i] = FAR;\n        inner[i] = 0;\n      } else {\n        const e = 0.5 - a;\n        outer[i] = e > 0 ? e * e : 0;\n        inner[i] = e < 0 ? e * e : 0;\n      }\n    }\n  }\n  transformGrid(outer, width, height);\n  transformGrid(inner, width, height);\n\n  const field = new Float32Array(width * height);\n  for (let i = 0; i < width * height; i++) field[i] = Math.sqrt(outer[i]) - Math.sqrt(inner[i]);\n\n  const points = [];\n  for (let y = 1; y < height - 1; y++) {\n    for (let x = 1; x < width - 1; x++) {\n      const i = y * width + x;\n      const d = field[i];\n      if (d > 0) continue;\n      if (field[i - 1] <= 0 && field[i + 1] <= 0 && field[i - width] <= 0 && field[i + width] <= 0) continue;\n      const gx = field[i + 1] - field[i - 1];\n      const gy = field[i + width] - field[i - width];\n      const len = Math.hypot(gx, gy) || 1;\n      points.push(x + 0.5 - (d * gx) / len, y + 0.5 - (d * gy) / len);\n    }\n  }\n  const stride = Math.max(1, Math.ceil(points.length / 2 / 3000)) * 2;\n  const edges = [];\n  for (let i = 0; i < points.length; i += stride) edges.push(points[i], points[i + 1]);\n\n  const size = Math.max(logoWidth, logoHeight);\n  const glowPad = Math.ceil((size * 0.7) / CELL);\n  const glowWidth = Math.ceil(logoWidth / CELL) + glowPad * 2;\n  const glowHeight = Math.ceil(logoHeight / CELL) + glowPad * 2;\n  const tight = new Float32Array(glowWidth * glowHeight);\n  for (let y = 0; y < height; y++) {\n    const gy = Math.floor((y - pad) / CELL) + glowPad;\n    for (let x = 0; x < width; x++) {\n      const gx = Math.floor((x - pad) / CELL) + glowPad;\n      tight[gy * glowWidth + gx] += Math.exp(-Math.abs(field[y * width + x]) / 1.5) / (CELL * CELL);\n    }\n  }\n  const wide = tight.slice();\n  const tightRadius = Math.max(1, Math.round((size * 0.035) / CELL));\n  const wideRadius = Math.max(2, Math.round((size * 0.13) / CELL));\n  blurGrid(tight, glowWidth, glowHeight, tightRadius);\n  blurGrid(wide, glowWidth, glowHeight, wideRadius);\n  const tightNorm = (Math.sqrt(2 * Math.PI * (tightRadius * tightRadius + tightRadius)) * CELL) / 3;\n  const wideNorm = (Math.sqrt(2 * Math.PI * (wideRadius * wideRadius + wideRadius)) * CELL) / 3;\n  const glow = new Float32Array(glowWidth * glowHeight * 2);\n  for (let i = 0; i < glowWidth * glowHeight; i++) {\n    glow[i * 2] = tight[i] * tightNorm;\n    glow[i * 2 + 1] = wide[i] * wideNorm;\n  }\n\n  return {\n    field,\n    edges,\n    width,\n    height,\n    pad,\n    logoWidth,\n    logoHeight,\n    glow,\n    glowWidth,\n    glowHeight,\n    glowOffset: pad - glowPad * CELL\n  };\n};\n\nconst spawnArc = (shape, time, focus) => {\n  const { edges, logoWidth, logoHeight } = shape;\n  const count = edges.length / 2;\n  if (count < 2) return null;\n  const size = Math.max(logoWidth, logoHeight);\n  let i = Math.floor(Math.random() * count);\n  if (focus) {\n    let found = false;\n    for (let attempt = 0; attempt < 40 && !found; attempt++) {\n      const j = Math.floor(Math.random() * count);\n      if (Math.hypot(edges[j * 2] - focus.x, edges[j * 2 + 1] - focus.y) < focus.radius) {\n        i = j;\n        found = true;\n      }\n    }\n    if (!found) return null;\n  }\n  const ax = edges[i * 2];\n  const ay = edges[i * 2 + 1];\n  for (let attempt = 0; attempt < 24; attempt++) {\n    const j = Math.floor(Math.random() * count);\n    const bx = edges[j * 2];\n    const by = edges[j * 2 + 1];\n    const len = Math.hypot(bx - ax, by - ay);\n    if (len < size * 0.08 || len > size * 0.3) continue;\n    const nx = -(by - ay) / len;\n    const ny = (bx - ax) / len;\n    const bow = len * (0.2 + Math.random() * 0.3);\n    const mx = (ax + bx) / 2;\n    const my = (ay + by) / 2;\n    const left = sampleField(shape, mx + nx * bow, my + ny * bow);\n    const right = sampleField(shape, mx - nx * bow, my - ny * bow);\n    if (Math.max(left, right) <= 0) continue;\n    return {\n      ax,\n      ay,\n      bx,\n      by,\n      bow: left >= right ? bow : -bow,\n      seed: 1 + Math.random() * 60,\n      born: time,\n      life: 0.35 + Math.random() * 0.45\n    };\n  }\n  return null;\n};\n\nconst vertex = `#version 300 es\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n  vUv = uv;\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nprecision highp int;\n\nuniform sampler2D tFieldFrom;\nuniform sampler2D tGlowFrom;\nuniform sampler2D tFieldTo;\nuniform sampler2D tGlowTo;\nuniform vec4 uMapFrom;\nuniform vec4 uSizeFrom;\nuniform vec4 uMapTo;\nuniform vec4 uSizeTo;\nuniform float uMorph;\nuniform vec2 uResolution;\nuniform float uUnit;\nuniform float uTime;\nuniform float uPresence;\nuniform vec3 uHover;\nuniform float uHoverRadius;\nuniform vec4 uPulses[${PULSES}];\nuniform float uPulseBoost;\nuniform float uFlash;\nuniform vec3 uColor;\nuniform vec3 uGlowColor;\nuniform float uIntensity;\nuniform float uGlow;\nuniform float uThickness;\nuniform float uStrands;\nuniform float uBend;\nuniform float uCrackle;\nuniform float uFlicker;\nuniform float uFill;\nuniform float uInk;\nuniform vec4 uArcEnds[${ARCS}];\nuniform vec4 uArcShape[${ARCS}];\n\nin vec2 vUv;\nout vec4 fragColor;\n\nuint scramble(uint x) {\n  x ^= x >> 16u;\n  x *= 0x7feb352du;\n  x ^= x >> 15u;\n  x *= 0x846ca68bu;\n  x ^= x >> 16u;\n  return x;\n}\n\nfloat fieldAt(sampler2D tex, vec4 map, vec4 size, vec2 p) {\n  vec2 f = (p - map.xy) / map.z;\n  vec2 c = clamp(f, vec2(0.5), size.xy - 0.5);\n  return (textureLod(tex, c / size.xy, 0.0).r + length(f - c)) * map.z;\n}\n\nvec2 glowAt(sampler2D tex, vec4 map, vec4 size, vec2 p) {\n  vec2 f = (p - map.xy) / map.z - map.w;\n  return textureLod(tex, f / (size.zw * ${CELL}.0), 0.0).rg;\n}\n\nfloat shape(vec2 p, float k) {\n  float to = fieldAt(tFieldTo, uMapTo, uSizeTo, p);\n  if (k >= 1.0) return to;\n  return mix(fieldAt(tFieldFrom, uMapFrom, uSizeFrom, p), to, k);\n}\n\nvec2 aura(vec2 p, float k) {\n  vec2 to = glowAt(tGlowTo, uMapTo, uSizeTo, p);\n  if (k >= 1.0) return to;\n  return mix(glowAt(tGlowFrom, uMapFrom, uSizeFrom, p), to, k);\n}\n\nvec4 corner(ivec2 c, uint seed) {\n  uint h = scramble(uint(c.x) * 0x8da6b343u + uint(c.y) * 0xd8163841u + seed * 0xcb1ab31fu);\n  return vec4(uvec4(h, h >> 8u, h >> 16u, h >> 24u) & 255u) / 127.5 - 1.0;\n}\n\nvec2 drift(vec2 p, uint seed, out mat2 jac) {\n  vec2 i = floor(p);\n  vec2 f = p - i;\n  vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n  vec2 du = 30.0 * f * f * (f * (f - 2.0) + 1.0);\n  ivec2 c = ivec2(i);\n  vec4 ga = corner(c, seed);\n  vec4 gb = corner(c + ivec2(1, 0), seed);\n  vec4 gc = corner(c + ivec2(0, 1), seed);\n  vec4 gd = corner(c + ivec2(1, 1), seed);\n  vec2 fb = f - vec2(1.0, 0.0);\n  vec2 fc = f - vec2(0.0, 1.0);\n  vec2 fd = f - vec2(1.0);\n  vec2 va = vec2(dot(ga.xy, f), dot(ga.zw, f));\n  vec2 vb = vec2(dot(gb.xy, fb), dot(gb.zw, fb));\n  vec2 vc = vec2(dot(gc.xy, fc), dot(gc.zw, fc));\n  vec2 vd = vec2(dot(gd.xy, fd), dot(gd.zw, fd));\n  vec2 k = va - vb - vc + vd;\n  vec4 g = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.x * u.y * (ga - gb - gc + gd);\n  jac = mat2(\n    g.xy + du * (u.yx * k.x + vec2(vb.x - va.x, vc.x - va.x)),\n    g.zw + du * (u.yx * k.y + vec2(vb.y - va.y, vc.y - va.y))\n  );\n  return va + u.x * (vb - va) + u.y * (vc - va) + u.x * u.y * k;\n}\n\nfloat wobble(vec2 p, uint seed) {\n  mat2 jac;\n  return drift(p, seed, jac).x;\n}\n\nvec2 ripple(vec2 p, out float surge) {\n  vec2 push = vec2(0.0);\n  surge = 0.0;\n  float width = uUnit * 10.0;\n  for (int i = 0; i < ${PULSES}; i++) {\n    vec4 pulse = uPulses[i];\n    if (pulse.w <= 0.0) continue;\n    vec2 d = p - pulse.xy;\n    float dist = length(d);\n    float front = (dist - pulse.z * uUnit * 150.0) / width;\n    float env = exp(-front * front) * pulse.w * exp(-pulse.z * 1.7) * smoothstep(0.0, uUnit * 8.0, dist);\n    push += d / max(dist, 1.0) * env * cos(front * 2.2) * uUnit * 7.5;\n    surge += env;\n  }\n  return push;\n}\n\nvec2 wander(vec2 p, float t, uint seed, float reachScale, out mat2 jac, out vec2 sway) {\n  vec2 q = p / uUnit;\n  mat2 ja;\n  mat2 jb;\n  mat2 jc;\n  mat2 jd;\n  vec2 a = drift(q * 0.028 + vec2(t * 0.29, t * 0.21), seed, ja);\n  vec2 b = drift(q * 0.085 + a * 0.4 + vec2(t * 0.83, -t * 0.61) + 17.0, seed + 1u, jb);\n  vec2 c = drift(p / 9.0 + b * 0.6 + vec2(t * 1.9, t * 1.3) + 5.0, seed + 2u, jc);\n  vec2 d = drift(p / 4.1 + vec2(-t * 2.7, t * 2.2) + 11.0, seed + 3u, jd);\n  float bendAmp = uBend * 8.0 * reachScale;\n  float rippleAmp = uBend * 3.2 * reachScale;\n  float crinkleAmp = uCrackle * 1.5 * reachScale;\n  jac = ja * (0.028 * bendAmp) + jb * (0.085 * rippleAmp) + jc * (crinkleAmp / 9.0) + jd * (crinkleAmp * 0.35 / 4.1);\n  sway = (a * bendAmp + b * rippleAmp) * uUnit;\n  return sway + (c + d * 0.35) * crinkleAmp;\n}\n\nvec2 glowShape(float line, float spread, float w) {\n  float x = abs(line);\n  float y = abs(spread);\n  return vec2(exp(-x * x / (w * w * 0.5)) + exp(-y / (w * 2.2)) * 0.6, exp(-y / (w * 4.5)) * 0.5);\n}\n\nvoid addArc(vec2 p, vec4 ends, vec4 info, float t, inout vec3 light, inout float energy, inout float hot) {\n  if (info.y < 0.002) return;\n  vec2 ab = ends.zw - ends.xy;\n  float len = max(length(ab), 1.0);\n  vec2 dir = ab / len;\n  vec2 rel = p - ends.xy;\n  float s = dot(rel, dir);\n  float h = dot(rel, vec2(-dir.y, dir.x));\n  float margin = abs(info.x) + uCrackle * (2.0 + len * 0.08) + uThickness * 12.0 + 10.0;\n  if (s < -margin || s > len + margin || abs(h) > margin) return;\n  float u = clamp(s / len, 0.0, 1.0);\n  float taper = sin(3.14159265 * u);\n  float bendSlope = s > 0.0 && s < len ? 3.14159265 / len * cos(3.14159265 * u) : 0.0;\n  float beyond = max(-s, 0.0) + max(s - len, 0.0);\n  for (int c = 0; c < 2; c++) {\n    uint seed = uint(info.z * 131.0) + uint(c) * 29u + 7u;\n    float jag = 0.0;\n    float jagSlope = 0.0;\n    float wave = max(len * 0.3, 14.0);\n    float weight = uCrackle * (1.5 + len * 0.05) * (c == 0 ? 1.0 : 1.5);\n    for (int o = 0; o < 3; o++) {\n      mat2 jac;\n      float n = drift(vec2(s / wave + info.z * 3.0, t * (1.4 + float(o) * 1.1)), seed + uint(o), jac).x;\n      jag += n * weight;\n      jagSlope += jac[0].x * weight / wave;\n      wave *= 0.42;\n      weight *= 0.4;\n    }\n    float offset = (info.x + jag) * taper;\n    float offsetSlope = (info.x + jag) * bendSlope + jagSlope * taper;\n    float across = (h - offset) / sqrt(1.0 + offsetSlope * offsetSlope);\n    float gap = length(vec2(beyond, across));\n    float w = uThickness * (c == 0 ? 0.9 : 0.6);\n    vec2 g = glowShape(gap, gap, w);\n    float k = info.y * (c == 0 ? 1.0 : 0.45);\n    light += (uColor * g.x + uGlowColor * g.y * uGlow) * k;\n    energy += (g.x + g.y * uGlow) * k;\n    hot += exp(-gap * gap / (w * w * 0.16)) * k * (c == 0 ? 1.0 : 0.0);\n  }\n}\n\nvoid main() {\n  vec2 p = vec2(vUv.x, 1.0 - vUv.y) * uResolution;\n  float t = uTime;\n  vec3 light = vec3(0.0);\n  float energy = 0.0;\n  float hot = 0.0;\n\n  float surge;\n  vec2 pr = p - ripple(p, surge);\n  float k = uMorph >= 1.0 ? 1.0 : smoothstep(0.0, 1.0, clamp(uMorph * 1.7 - 0.35 + 0.35 * wobble(p / uUnit * 0.018, 41u), 0.0, 1.0));\n  float transit = uMorph >= 1.0 ? 0.0 : sin(3.14159265 * uMorph);\n  float base = shape(pr, k);\n\n  vec2 toHover = p - uHover.xy;\n  float heat = min(uHover.z * exp(-dot(toHover, toHover) / (uHoverRadius * uHoverRadius)) + surge * 1.4 + transit * 0.5, 2.0);\n  float heatCap = min(uHover.z + uPulseBoost * 1.4 + transit * 0.5, 2.0);\n  float breath = 1.0 + uFlicker * 0.6 * wobble(vec2(t * 2.1, 7.0), 3u);\n  float grow = uPresence;\n\n  float edge = abs(base);\n  vec2 halo = aura(pr, k);\n  float ink = uInk;\n  float bloom = (halo.x * 0.16 + halo.y * 0.08) * (1.0 - ink * 0.65) * uGlow * (1.0 + heat * 1.2);\n  float body = smoothstep(0.75, -0.75, base) * uFill * (0.06 + 1.2 * min(halo.x, 1.0)) * (1.0 + heat * 0.5);\n  light += uGlowColor * bloom * grow * grow;\n  energy += bloom * grow * grow;\n\n  float reachScale = mix(0.15, 1.0, grow) * (1.0 + heat * 0.9);\n  float reach = (uUnit * uBend * 16.0 + uCrackle * 3.0) * (1.0 + heatCap * 0.9) + uThickness * 20.0 + 8.0;\n  if (edge < reach && grow > 0.0) {\n    float fade = smoothstep(reach, reach * 0.55, edge);\n    vec2 q = pr / uUnit;\n    float count = min(uStrands + heat * 2.5, 6.0);\n    float limit = min(uStrands + heatCap * 2.5, 6.0);\n    for (int i = 0; i < 6; i++) {\n      float fi = float(i);\n      if (fi >= limit) break;\n      float present = clamp(count - fi, 0.0, 1.0);\n      if (present <= 0.0) continue;\n      uint seed = uint(i) * 7u + 3u;\n      mat2 jac;\n      vec2 sway;\n      float lead = i == 0 ? 1.0 : 0.0;\n      vec2 warped = pr + wander(pr, t * (1.0 + fi * 0.19), seed, reachScale * mix(0.6 + fi * 0.2, 0.7, lead), jac, sway);\n      float dw = shape(warped, k);\n      vec2 slope = vec2(shape(warped + vec2(1.0, 0.0), k), shape(warped + vec2(0.0, 1.0), k)) - dw;\n      float d = dw / max(length(slope + jac * slope), 0.3);\n      float spread = shape(pr + sway, k);\n      float swell = 0.5 + 0.5 * wobble(q * 0.06 + vec2(t * 0.9, fi * 5.1 - t * 0.6), seed + 8u);\n      float w = uThickness * mix(0.5, 1.0, lead) * (0.5 + swell);\n      float vis = mix(0.3 + 0.45 * smoothstep(-0.25, 0.2, wobble(q * 0.035 + vec2(t * 0.21, fi * 3.7), seed + 5u)), 1.0, lead);\n      float spark = 1.0 - uFlicker * 0.3 * (0.5 + 0.5 * wobble(vec2(t * 6.0, fi * 2.3), seed + 6u));\n      float weight = max(vis, heat * 0.85) * spark * fade * present * (0.7 + 0.6 * swell);\n      vec2 g = glowShape(d, spread, w) * weight;\n      float soft = mix(1.0, mix(0.5, 1.0, lead), ink);\n      vec3 stroke = mix(uColor, uGlowColor, ink * (1.0 - lead) * 0.65);\n      float haze = uGlow * (1.0 + heat) * (1.0 - ink * 0.7);\n      light += stroke * g.x * soft + uGlowColor * g.y * haze;\n      energy += g.x * soft + g.y * haze;\n      hot += exp(-d * d / (w * w * 0.16)) * lead * weight;\n    }\n    light *= grow;\n    energy *= grow;\n  }\n\n  for (int i = 0; i < ${ARCS}; i++) addArc(pr, uArcEnds[i], uArcShape[i], t, light, energy, hot);\n\n  float gain = uIntensity * breath * (1.0 + heat * 0.45) * (1.0 + uFlash * 0.3) * 1.4;\n  float alpha = 1.0 - exp(-energy * gain);\n  vec3 color = mix(1.0 - exp(-light * gain), alpha * light / max(energy, 1e-4), ink);\n  color = mix(color, vec3(alpha), clamp(hot * grow, 0.0, 1.0) * ink * 0.85);\n  float tint = (1.0 - exp(-body * gain * 1.2)) * grow * grow * (1.0 - ink * 0.82);\n  color += uGlowColor * tint * (1.0 - alpha);\n  alpha += tint * (1.0 - alpha);\n  float grain = (fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))) - 0.5) / 255.0;\n  alpha = clamp(alpha + grain, 0.0, 1.0);\n  fragColor = vec4(clamp(color + grain, 0.0, alpha), alpha);\n}\n`;\n\nconst ElectricLogo = ({\n  src = BOLT,\n  color = '#ecc7ff',\n  glowColor = '#ad6dff',\n  scale = 0.7,\n  intensity = 1,\n  glow = 1,\n  thickness = 1.5,\n  strands = 4,\n  bend = 0.6,\n  crackle = 1.5,\n  arcs = 1,\n  flicker = 0.6,\n  fill = 0,\n  speed = 2.5,\n  interactive = true,\n  cursorIntensity = 0.75,\n  cursorRadius = 100,\n  theme = 'dark',\n  onRender,\n  className = '',\n  style\n}) => {\n  const containerRef = useRef(null);\n  const settingsRef = useRef(null);\n  const shapeRef = useRef(null);\n\n  useEffect(() => {\n    settingsRef.current = {\n      color,\n      glowColor,\n      scale,\n      intensity,\n      glow,\n      thickness,\n      strands,\n      bend,\n      crackle,\n      arcs,\n      flicker,\n      fill,\n      speed,\n      interactive,\n      cursorIntensity,\n      cursorRadius,\n      theme,\n      onRender\n    };\n  });\n\n  useEffect(() => {\n    let alive = true;\n    const image = new Image();\n    image.crossOrigin = 'anonymous';\n    image.decoding = 'async';\n    image.onload = () => {\n      if (!alive) return;\n      let traced = null;\n      try {\n        traced = traceShape(image);\n      } catch {\n        traced = null;\n      }\n      if (traced) shapeRef.current = traced;\n    };\n    image.src = src || BOLT;\n    return () => {\n      alive = false;\n      image.onload = null;\n    };\n  }, [src]);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return undefined;\n\n    const renderer = new Renderer({\n      dpr: Math.min(window.devicePixelRatio || 1, 2),\n      alpha: true,\n      premultipliedAlpha: true,\n      antialias: false\n    });\n    const gl = renderer.gl;\n    if (!renderer.isWebgl2) {\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n      return undefined;\n    }\n    gl.clearColor(0, 0, 0, 0);\n    const canvas = gl.canvas;\n    canvas.style.display = 'block';\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    container.appendChild(canvas);\n\n    const makeSlot = () => ({\n      shape: null,\n      field: new Texture(gl, {\n        image: new Float32Array([1000]),\n        width: 1,\n        height: 1,\n        internalFormat: gl.R16F,\n        format: gl.RED,\n        type: gl.FLOAT,\n        minFilter: gl.LINEAR,\n        magFilter: gl.LINEAR,\n        generateMipmaps: false,\n        flipY: false,\n        unpackAlignment: 1\n      }),\n      glow: new Texture(gl, {\n        image: new Float32Array([0, 0]),\n        width: 1,\n        height: 1,\n        internalFormat: gl.RG16F,\n        format: gl.RG,\n        type: gl.FLOAT,\n        minFilter: gl.LINEAR,\n        magFilter: gl.LINEAR,\n        generateMipmaps: false,\n        flipY: false,\n        unpackAlignment: 1\n      })\n    });\n    const slots = [makeSlot(), makeSlot()];\n\n    const arcEnds = Array.from({ length: ARCS * 4 }, () => 0);\n    const arcShape = Array.from({ length: ARCS * 4 }, () => 0);\n    const pulseData = Array.from({ length: PULSES * 4 }, () => 0);\n    const uniforms = {\n      tFieldFrom: { value: slots[1].field },\n      tGlowFrom: { value: slots[1].glow },\n      tFieldTo: { value: slots[0].field },\n      tGlowTo: { value: slots[0].glow },\n      uMapFrom: { value: [0, 0, 1, 0] },\n      uSizeFrom: { value: [1, 1, 1, 1] },\n      uMapTo: { value: [0, 0, 1, 0] },\n      uSizeTo: { value: [1, 1, 1, 1] },\n      uMorph: { value: 1 },\n      uResolution: { value: [1, 1] },\n      uUnit: { value: 1 },\n      uTime: { value: 0 },\n      uPresence: { value: 0 },\n      uHover: { value: [0, 0, 0] },\n      uHoverRadius: { value: 120 },\n      uPulses: { value: pulseData },\n      uPulseBoost: { value: 0 },\n      uFlash: { value: 0 },\n      uColor: { value: [1, 1, 1] },\n      uGlowColor: { value: [0.43, 0.48, 1] },\n      uIntensity: { value: 1 },\n      uGlow: { value: 1 },\n      uThickness: { value: 1.8 },\n      uStrands: { value: 3 },\n      uBend: { value: 1 },\n      uCrackle: { value: 1 },\n      uFlicker: { value: 0.4 },\n      uFill: { value: 0.5 },\n      uInk: { value: 0 },\n      uArcEnds: { value: arcEnds },\n      uArcShape: { value: arcShape }\n    };\n    const mesh = new Mesh(gl, {\n      geometry: new Triangle(gl),\n      program: new Program(gl, { vertex, fragment, uniforms, depthTest: false, depthWrite: false })\n    });\n\n    const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n    const pointer = { x: 0, y: 0, over: false };\n    const hover = { x: 0, y: 0, vx: 0, vy: 0, power: 0 };\n    const sparks = [];\n    const pulses = [];\n    let target = 0;\n    let pending = null;\n    let morph = 1;\n    let burst = null;\n    let progress = 0;\n    let ink = 0;\n    const hues = [\n      [1, 1, 1],\n      [1, 1, 1]\n    ];\n    let settled = false;\n    let time = 0;\n    let width = 1;\n    let height = 1;\n    let raf = 0;\n    let last = performance.now();\n    let visible = true;\n\n    const load = (slot, next) => {\n      slot.shape = next;\n      slot.field.image = next.field;\n      slot.field.width = next.width;\n      slot.field.height = next.height;\n      slot.field.needsUpdate = true;\n      slot.glow.image = next.glow;\n      slot.glow.width = next.glowWidth;\n      slot.glow.height = next.glowHeight;\n      slot.glow.needsUpdate = true;\n    };\n\n    const place = (shape, s) => {\n      const fit = Math.max(1e-4, Math.min((width * s.scale) / shape.logoWidth, (height * s.scale) / shape.logoHeight));\n      return {\n        fit,\n        ox: width / 2 - (shape.pad + shape.logoWidth / 2) * fit,\n        oy: height / 2 - (shape.pad + shape.logoHeight / 2) * fit,\n        unit: (Math.max(shape.logoWidth, shape.logoHeight) * fit) / 100\n      };\n    };\n\n    let resizePending = false;\n    const applyResize = () => {\n      width = Math.max(1, container.clientWidth);\n      height = Math.max(1, container.clientHeight);\n      renderer.dpr = Math.min(window.devicePixelRatio || 1, 2, Math.sqrt(PIXEL_BUDGET / (width * height)));\n      renderer.setSize(width, height);\n      uniforms.uResolution.value = [width, height];\n    };\n\n    const resize = () => {\n      resizePending = true;\n      start();\n    };\n\n    const frame = now => {\n      raf = 0;\n      if (resizePending) {\n        resizePending = false;\n        applyResize();\n      }\n      const s = settingsRef.current;\n      const dt = Math.min(0.05, Math.max(0, (now - last) / 1000));\n      last = now;\n\n      const incoming = shapeRef.current;\n      if (incoming && incoming !== slots[target].shape) pending = incoming;\n      if (pending && morph >= 1) {\n        if (slots[target].shape) {\n          target = 1 - target;\n          morph = 0;\n          sparks.length = 0;\n        }\n        load(slots[target], pending);\n        pending = null;\n      }\n      if (morph < 1) morph = Math.min(1, morph + dt / (pending ? 0.3 : 1.6));\n\n      const to = slots[target].shape;\n      const from = morph < 1 ? slots[1 - target].shape : null;\n      if (to) progress = Math.min(1, progress + dt / 1.4);\n      const presence = progress * progress * (3 - 2 * progress);\n\n      if (to && s) {\n        const near = place(to, s);\n        const far = from ? place(from, s) : near;\n        const blend = morph * morph * (3 - 2 * morph);\n        const unit = far.unit + (near.unit - far.unit) * blend;\n\n        const engaged = s.interactive && pointer.over;\n        if (engaged && hover.power < 0.01) {\n          hover.x = pointer.x;\n          hover.y = pointer.y;\n          hover.vx = 0;\n          hover.vy = 0;\n        }\n        hover.vx += ((pointer.x - hover.x) * 120 - hover.vx * 19) * dt;\n        hover.vy += ((pointer.y - hover.y) * 120 - hover.vy * 19) * dt;\n        hover.x += hover.vx * dt;\n        hover.y += hover.vy * dt;\n        hover.power += ((engaged ? 1 : 0) - hover.power) * (1 - Math.exp(-dt / (engaged ? 0.3 : 0.55)));\n\n        const motion = reducedMotion ? 0.2 : 1;\n        time += dt * s.speed * motion;\n\n        for (let i = sparks.length - 1; i >= 0; i--) {\n          if (time - sparks[i].born > sparks[i].life) sparks.splice(i, 1);\n        }\n        const focus = spot => ({\n          x: (spot.x - near.ox) / near.fit,\n          y: (spot.y - near.oy) / near.fit,\n          radius: Math.max(1, s.cursorRadius) / near.fit\n        });\n        if (burst && morph >= 1 && s.arcs > 0) {\n          for (let i = 0; i < 3 && sparks.length < ARCS; i++) {\n            const spark = spawnArc(to, time, focus(burst));\n            if (spark) sparks.push(spark);\n          }\n        }\n        burst = null;\n        if (!reducedMotion && presence > 0.8 && morph >= 1 && sparks.length < ARCS) {\n          const chance = dt * s.speed * s.arcs;\n          if (Math.random() < chance * 6 * hover.power * s.cursorIntensity) {\n            const spark = spawnArc(to, time, focus(hover));\n            if (spark) sparks.push(spark);\n          } else if (Math.random() < chance * 2.2) {\n            const spark = spawnArc(to, time, null);\n            if (spark) sparks.push(spark);\n          }\n        }\n\n        for (let i = 0; i < ARCS; i++) {\n          const o = i * 4;\n          const spark = sparks[i];\n          if (!spark) {\n            arcShape[o + 1] = 0;\n            continue;\n          }\n          const k = (time - spark.born) / spark.life;\n          arcEnds[o] = near.ox + spark.ax * near.fit;\n          arcEnds[o + 1] = near.oy + spark.ay * near.fit;\n          arcEnds[o + 2] = near.ox + spark.bx * near.fit;\n          arcEnds[o + 3] = near.oy + spark.by * near.fit;\n          arcShape[o] = spark.bow * near.fit;\n          arcShape[o + 1] = Math.sin(Math.PI * Math.min(1, Math.max(0, k))) * presence;\n          arcShape[o + 2] = spark.seed;\n        }\n\n        let boost = 0;\n        let flash = 0;\n        for (let i = pulses.length - 1; i >= 0; i--) {\n          if ((now - pulses[i].born) / 1000 > 2) pulses.splice(i, 1);\n        }\n        for (let i = 0; i < PULSES; i++) {\n          const o = i * 4;\n          const pulse = pulses[i];\n          if (!pulse) {\n            pulseData[o + 3] = 0;\n            continue;\n          }\n          const age = (now - pulse.born) / 1000;\n          pulseData[o] = pulse.x;\n          pulseData[o + 1] = pulse.y;\n          pulseData[o + 2] = age;\n          pulseData[o + 3] = 1;\n          boost = Math.max(boost, Math.exp(-age * 1.7));\n          flash += Math.exp(-age * 7);\n        }\n\n        const fromSlot = slots[1 - target];\n        uniforms.tFieldTo.value = slots[target].field;\n        uniforms.tGlowTo.value = slots[target].glow;\n        uniforms.tFieldFrom.value = fromSlot.field;\n        uniforms.tGlowFrom.value = fromSlot.glow;\n        uniforms.uMapTo.value = [near.ox, near.oy, near.fit, to.glowOffset];\n        uniforms.uSizeTo.value = [to.width, to.height, to.glowWidth, to.glowHeight];\n        if (from) {\n          uniforms.uMapFrom.value = [far.ox, far.oy, far.fit, from.glowOffset];\n          uniforms.uSizeFrom.value = [from.width, from.height, from.glowWidth, from.glowHeight];\n        }\n        uniforms.uMorph.value = morph;\n        uniforms.uUnit.value = unit;\n        uniforms.uTime.value = time;\n        uniforms.uPresence.value = presence;\n        uniforms.uHover.value = [hover.x, hover.y, hover.power * Math.max(0, s.cursorIntensity)];\n        uniforms.uHoverRadius.value = Math.max(1, s.cursorRadius);\n        uniforms.uPulseBoost.value = boost;\n        uniforms.uFlash.value = flash;\n        const targets = [hexToRgb(s.color), hexToRgb(s.glowColor)];\n        const shift = settled ? 1 - Math.exp(-dt / 0.35) : 1;\n        settled = true;\n        for (let i = 0; i < 2; i++) {\n          for (let c = 0; c < 3; c++) hues[i][c] += (targets[i][c] - hues[i][c]) * shift;\n        }\n        uniforms.uColor.value = hues[0].slice();\n        uniforms.uGlowColor.value = hues[1].slice();\n        uniforms.uIntensity.value = s.intensity;\n        uniforms.uGlow.value = s.glow;\n        uniforms.uThickness.value = s.thickness;\n        uniforms.uStrands.value = Math.max(1, Math.min(6, Math.round(s.strands)));\n        uniforms.uBend.value = s.bend;\n        uniforms.uCrackle.value = s.crackle;\n        uniforms.uFlicker.value = reducedMotion ? 0 : s.flicker;\n        ink += ((s.theme === 'light' ? 1 : 0) - ink) * (1 - Math.exp(-dt / 0.25));\n        uniforms.uFill.value = s.fill;\n        uniforms.uInk.value = ink;\n        renderer.render({ scene: mesh });\n        s.onRender?.(canvas);\n      }\n\n      if (visible) raf = requestAnimationFrame(frame);\n    };\n\n    const start = () => {\n      if (raf || !visible) return;\n      last = performance.now();\n      raf = requestAnimationFrame(frame);\n    };\n\n    const onMove = e => {\n      const rect = container.getBoundingClientRect();\n      pointer.x = e.clientX - rect.left;\n      pointer.y = e.clientY - rect.top;\n      pointer.over = true;\n    };\n    const onDown = e => {\n      onMove(e);\n      if (!settingsRef.current?.interactive || reducedMotion) return;\n      pulses.push({ x: pointer.x, y: pointer.y, born: performance.now() });\n      if (pulses.length > PULSES) pulses.shift();\n      burst = { x: pointer.x, y: pointer.y };\n    };\n    const onLeave = () => {\n      pointer.over = false;\n    };\n    container.addEventListener('pointermove', onMove);\n    container.addEventListener('pointerdown', onDown);\n    container.addEventListener('pointerleave', onLeave);\n    container.addEventListener('pointercancel', onLeave);\n\n    const resizeObserver = new ResizeObserver(resize);\n    resizeObserver.observe(container);\n    const intersectionObserver = new IntersectionObserver(([entry]) => {\n      visible = entry.isIntersecting;\n      start();\n    });\n    intersectionObserver.observe(container);\n\n    resize();\n    start();\n\n    return () => {\n      visible = false;\n      cancelAnimationFrame(raf);\n      resizeObserver.disconnect();\n      intersectionObserver.disconnect();\n      container.removeEventListener('pointermove', onMove);\n      container.removeEventListener('pointerdown', onDown);\n      container.removeEventListener('pointerleave', onLeave);\n      container.removeEventListener('pointercancel', onLeave);\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n      if (canvas.parentNode) canvas.parentNode.removeChild(canvas);\n    };\n  }, []);\n\n  return <div ref={containerRef} className={`electric-logo ${className}`.trim()} style={style} />;\n};\n\nexport default ElectricLogo;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}