{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "argon",
  "type": "registry:ui",
  "title": "Argon",
  "description": "SVG gas-discharge tube lettering — ignition travels along each glyph's tube with a seeded strike flicker, then holds with a faint mains hum; the letter under the pointer destabilises. Neon, argon and mercury presets; fully lit and steady under reduced motion.",
  "dependencies": [],
  "registryDependencies": [
    "https://crucibleui.com/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/default/text/argon/argon.tsx",
      "type": "registry:ui",
      "target": "components/crucible/argon.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\";\n\n/** Built-in gas presets, or any CSS color string. */\nexport type ArgonGas = \"neon\" | \"argon\" | \"mercury\" | (string & {});\n\nexport interface ArgonHandle {\n  /** Switch every tube off and run the ignition intro again. */\n  replay(): void;\n  /** Destabilise one glyph (by index among visible glyphs), or a seeded random one. */\n  flicker(index?: number): void;\n}\n\nexport interface ArgonProps extends Omit<React.HTMLAttributes<HTMLElement>, \"children\"> {\n  /** The lettering. Use \"\\n\" for a stacked multi-line sign. @default \"Open late\" */\n  text?: string;\n  /** Gas fill: \"neon\" (red-orange), \"argon\" (ice blue), \"mercury\" (cold white), or any CSS color. @default \"argon\" */\n  gas?: ArgonGas;\n  /** Element the (visually hidden) real text is rendered as, so it stays a heading. @default \"h2\" */\n  as?: \"h1\" | \"h2\" | \"h3\" | \"p\";\n  /** Glyph size in px at natural size; the sign scales down to fit its container. @default 96 */\n  fontSize?: number;\n  /** Lit tube stroke width in px at natural size. @default 2.4 */\n  strokeWidth?: number;\n  /** Render mounting clips beneath each glyph and a back-wire behind every line. @default true */\n  clips?: boolean;\n  /** Wait until the sign scrolls into view before igniting (otherwise ignite on mount). @default true */\n  igniteOnView?: boolean;\n  /** Glow strength: bloom size/opacity and interior gas fill. 0–2. @default 1 */\n  intensity?: number;\n  /** When no pointer has been present for ~1.5s, glyphs occasionally destabilise on their own. @default true */\n  idle?: boolean;\n  /** Hold the sign fully lit and steady — no intro, hum, or flicker. @default false */\n  paused?: boolean;\n  /** Imperative handle: `replay()` and `flicker(index?)`. */\n  ref?: React.Ref<ArgonHandle>;\n}\n\nconst GAS_PRESETS: Record<string, string> = {\n  neon: \"#ff5a3c\",\n  argon: \"#7cc4ff\",\n  mercury: \"#e8f1ff\",\n};\n\n/** Stagger between glyph ignitions (ms). */\nconst STAGGER = 70;\n/** Time for the ignition to travel the dash window (ms). */\nconst DRAW_MS = 1400;\n/** Dash window, in em — longer than any glyph's outline. */\nconst DASH_EM = 10;\n/** Photosensitivity budget: max opacity dips from hover/idle flicker in any 1s window. */\nconst MAX_DIPS_PER_SECOND = 3;\nconst FLICKER_DIPS = 2;\n\ninterface Glyph {\n  char: string;\n  line: number;\n  /** Start x relative to the line's left edge. */\n  x: number;\n  /** Advance width. */\n  w: number;\n}\n\ninterface Layout {\n  glyphs: Glyph[];\n  lineWidths: number[];\n  measured: boolean;\n}\n\nfunction mulberry32(seed: number) {\n  let a = seed >>> 0;\n  return () => {\n    a = (a + 0x6d2b79f5) >>> 0;\n    let t = a;\n    t = Math.imul(t ^ (t >>> 15), t | 1);\n    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\nfunction hashString(s: string): number {\n  let h = 2166136261;\n  for (let i = 0; i < s.length; i++) {\n    h ^= s.charCodeAt(i);\n    h = Math.imul(h, 16777619);\n  }\n  return h >>> 0;\n}\n\n/** Deterministic pre-measure layout (SSR + first client render; hidden until measured). */\nfunction estimateLayout(lines: string[], fontSize: number): Layout {\n  const glyphs: Glyph[] = [];\n  const lineWidths: number[] = [];\n  const adv = fontSize * 0.6;\n  lines.forEach((line, li) => {\n    const chars = Array.from(line);\n    chars.forEach((char, ci) => {\n      if (!/\\s/.test(char)) glyphs.push({ char, line: li, x: ci * adv, w: adv });\n    });\n    lineWidths.push(chars.length * adv);\n  });\n  return { glyphs, lineWidths, measured: false };\n}\n\n/** Seeded strike: 2–3 uneven partial dips inside the first ~200ms of a glyph lighting. */\nfunction strikeKeyframes(rng: () => number): Keyframe[] {\n  const dips = rng() < 0.5 ? 2 : 3;\n  const total = 240;\n  const frames: Keyframe[] = [{ opacity: 0, offset: 0 }];\n  let t = 18 + rng() * 14;\n  frames.push({ opacity: 0.85 + rng() * 0.15, offset: t / total });\n  for (let d = 0; d < dips; d++) {\n    t += 22 + rng() * 22;\n    frames.push({ opacity: 0.2 + rng() * 0.25, offset: Math.min(t, 200) / total });\n    t += 18 + rng() * 22;\n    frames.push({ opacity: 0.8 + rng() * 0.2, offset: Math.min(t, 210) / total });\n  }\n  frames.push({ opacity: 1, offset: 1 });\n  return frames;\n}\n\n/** Seeded ~400ms destabilise: exactly two partial dips, then a wobble back to steady. */\nfunction flickerKeyframes(rng: () => number): Keyframe[] {\n  return [\n    { opacity: 1, offset: 0 },\n    { opacity: 0.28 + rng() * 0.14, offset: 0.08 + rng() * 0.04 },\n    { opacity: 0.88 + rng() * 0.1, offset: 0.2 },\n    { opacity: 0.72 + rng() * 0.1, offset: 0.34 },\n    { opacity: 0.38 + rng() * 0.14, offset: 0.46 + rng() * 0.04 },\n    { opacity: 1, offset: 0.62 },\n    { opacity: 0.9 + rng() * 0.06, offset: 0.8 },\n    { opacity: 1, offset: 1 },\n  ];\n}\n\n/**\n * Argon — a headline rendered as bent gas-discharge glass tubes. Each glyph\n * is an SVG text outline in three layers: a dim unlit tube, a bright lit core\n * with a white-hot center, and a blurred bloom. On view, ignition travels\n * along each tube glyph-by-glyph with an uneven strike flicker, then holds\n * with a near-imperceptible mains hum. The glyph nearest the pointer\n * destabilises briefly. Flicker is photosensitivity-budgeted (≤3 dips/s);\n * reduced motion renders the sign fully lit and steady.\n */\nexport function Argon({\n  text = \"Open late\",\n  gas = \"argon\",\n  as = \"h2\",\n  fontSize = 96,\n  strokeWidth = 2.4,\n  clips = true,\n  igniteOnView = true,\n  intensity = 1,\n  idle = true,\n  paused = false,\n  ref,\n  className,\n  style,\n  ...props\n}: ArgonProps) {\n  const reducedMotion = useReducedMotion();\n  const staticMode = reducedMotion || paused;\n  const Tag = as;\n  const uid = React.useId().replace(/:/g, \"\");\n  const filterId = `argon-bloom-${uid}`;\n  const maskId = `argon-mask-${uid}`;\n\n  const lines = React.useMemo(() => text.split(\"\\n\"), [text]);\n  const seed = React.useMemo(() => hashString(text), [text]);\n  const color = GAS_PRESETS[gas] ?? gas;\n  const hot = `color-mix(in srgb, ${color} 42%, #ffffff)`;\n  const k = Math.max(0, Math.min(2, intensity));\n\n  const [layout, setLayout] = React.useState<Layout>(() => estimateLayout(lines, fontSize));\n  const [igniteKey, setIgniteKey] = React.useState(0);\n  const [lit, setLit] = React.useState(false);\n  const [inView, setInView] = React.useState(false);\n  const [docVisible, setDocVisible] = React.useState(true);\n\n  const svgRef = React.useRef<SVGSVGElement>(null);\n  const measureRefs = React.useRef<(SVGTextElement | null)[]>([]);\n  const dashRefs = React.useRef<(SVGTextElement | null)[][]>([]);\n  const glowRefs = React.useRef<(SVGGElement | null)[]>([]);\n  const bloomRefs = React.useRef<(SVGTextElement | null)[]>([]);\n  const fillRefs = React.useRef<(SVGTextElement | null)[]>([]);\n  const playedKeyRef = React.useRef(0);\n  const activeFlickers = React.useRef(new Map<number, Animation>());\n  const dipTimes = React.useRef<number[]>([]);\n  const flickerCount = React.useRef(0);\n  const lastPointer = React.useRef(-Infinity);\n  const hoverIndex = React.useRef(-1);\n\n  // ---- geometry -----------------------------------------------------------\n  const pad = Math.max(strokeWidth * 6, fontSize * 0.2);\n  const lineHeight = fontSize * 1.18;\n  const ascent = fontSize * 0.8;\n  const clipSpace = clips ? fontSize * 0.2 : 0;\n  const maxLineWidth = Math.max(1, ...layout.lineWidths);\n  const vbW = maxLineWidth + pad * 2;\n  const vbH = pad * 2 + ascent + (lines.length - 1) * lineHeight + fontSize * 0.22 + clipSpace;\n  const baselineOf = (line: number) => pad + ascent + line * lineHeight;\n  const lineOffset = (line: number) => pad + (maxLineWidth - (layout.lineWidths[line] ?? 0)) / 2;\n  const dashLen = fontSize * DASH_EM;\n\n  // ---- measure real glyph advances (kerning + letter-spacing included) -----\n  const measure = React.useCallback(() => {\n    const glyphs: Glyph[] = [];\n    const lineWidths: number[] = [];\n    lines.forEach((line, li) => {\n      const el = measureRefs.current[li];\n      if (!el) {\n        lineWidths.push(0);\n        return;\n      }\n      let width = 0;\n      try {\n        width = el.getComputedTextLength();\n      } catch {\n        width = 0;\n      }\n      lineWidths.push(width);\n      let unit = 0;\n      for (const char of Array.from(line)) {\n        if (!/\\s/.test(char)) {\n          try {\n            const start = el.getStartPositionOfChar(unit).x;\n            const end = el.getEndPositionOfChar(unit + char.length - 1).x;\n            glyphs.push({ char, line: li, x: start, w: Math.max(0, end - start) });\n          } catch {\n            /* unaddressable character — skip */\n          }\n        }\n        unit += char.length;\n      }\n    });\n    setLayout({ glyphs, lineWidths, measured: true });\n  }, [lines]);\n\n  React.useLayoutEffect(() => {\n    measure();\n    const fonts = typeof document !== \"undefined\" ? document.fonts : undefined;\n    if (!fonts) return;\n    let alive = true;\n    const onLoad = () => alive && measure();\n    fonts.ready.then(onLoad).catch(() => {});\n    fonts.addEventListener?.(\"loadingdone\", onLoad);\n    return () => {\n      alive = false;\n      fonts.removeEventListener?.(\"loadingdone\", onLoad);\n    };\n  }, [measure, fontSize]);\n\n  // ---- visibility (in view + tab visible) ---------------------------------\n  React.useEffect(() => {\n    const node = svgRef.current;\n    if (!node) return;\n    if (typeof IntersectionObserver === \"undefined\") {\n      setInView(true);\n      return;\n    }\n    const io = new IntersectionObserver(([entry]) => setInView(!!entry?.isIntersecting), {\n      threshold: 0.35,\n    });\n    io.observe(node);\n    return () => io.disconnect();\n  }, []);\n\n  React.useEffect(() => {\n    const onVis = () => setDocVisible(document.visibilityState !== \"hidden\");\n    onVis();\n    document.addEventListener(\"visibilitychange\", onVis);\n    return () => document.removeEventListener(\"visibilitychange\", onVis);\n  }, []);\n\n  // ---- ignition trigger ---------------------------------------------------\n  React.useEffect(() => {\n    if (igniteKey !== 0) return;\n    if (!igniteOnView || inView) setIgniteKey(1);\n  }, [igniteOnView, inView, igniteKey]);\n\n  // Static mode (reduced motion / paused): everything lit, nothing animating.\n  React.useEffect(() => {\n    if (!staticMode) return;\n    playedKeyRef.current = Math.max(playedKeyRef.current, igniteKey);\n    activeFlickers.current.forEach((a) => a.cancel());\n    activeFlickers.current.clear();\n    setLit(true);\n  }, [staticMode, igniteKey]);\n\n  const glyphCount = layout.glyphs.length;\n  const measured = layout.measured;\n\n  React.useLayoutEffect(() => {\n    if (staticMode || !measured || igniteKey === 0 || playedKeyRef.current === igniteKey) return;\n    playedKeyRef.current = igniteKey;\n    setLit(false);\n    activeFlickers.current.forEach((a) => a.cancel());\n    activeFlickers.current.clear();\n\n    const anims: Animation[] = [];\n    const len = fontSize * DASH_EM;\n    for (let i = 0; i < glyphCount; i++) {\n      const delay = i * STAGGER;\n      const rng = mulberry32(seed ^ Math.imul(i + 1, 0x9e3779b1) ^ Math.imul(igniteKey, 0x85ebca6b));\n      for (const el of dashRefs.current[i] ?? []) {\n        if (!el?.animate) continue;\n        anims.push(\n          el.animate([{ strokeDashoffset: len }, { strokeDashoffset: 0 }], {\n            duration: DRAW_MS,\n            delay,\n            easing: \"cubic-bezier(0.3, 0.15, 0.55, 1)\",\n            fill: \"both\",\n          })\n        );\n      }\n      const strike = strikeKeyframes(rng);\n      const g = glowRefs.current[i];\n      const b = bloomRefs.current[i];\n      if (g?.animate) anims.push(g.animate(strike, { duration: 240, delay, fill: \"both\" }));\n      if (b?.animate) anims.push(b.animate(strike, { duration: 240, delay, fill: \"both\" }));\n      const f = fillRefs.current[i];\n      if (f?.animate) {\n        anims.push(\n          f.animate([{ opacity: 0 }, { opacity: 1 }], {\n            duration: 520,\n            delay: delay + DRAW_MS * 0.3,\n            easing: \"ease-out\",\n            fill: \"both\",\n          })\n        );\n      }\n    }\n    let cancelled = false;\n    Promise.all(anims.map((a) => a.finished))\n      .then(() => {\n        if (cancelled) return;\n        setLit(true);\n        // Committed state now matches the end frames — release the animations.\n        anims.forEach((a) => a.cancel());\n      })\n      .catch(() => {});\n    return () => {\n      cancelled = true;\n      anims.forEach((a) => a.cancel());\n    };\n  }, [igniteKey, staticMode, measured, glyphCount, seed, fontSize]);\n\n  // ---- flicker (hover / idle / handle) ------------------------------------\n  const flicker = React.useCallback(\n    (index?: number) => {\n      if (staticMode || !lit || glyphCount === 0) return;\n      const n = flickerCount.current++;\n      const rng = mulberry32(seed ^ Math.imul(n + 17, 0x27d4eb2f));\n      const i =\n        index === undefined ? Math.floor(rng() * glyphCount) : Math.max(0, Math.min(glyphCount - 1, index));\n      if (activeFlickers.current.has(i)) return;\n      const now = performance.now();\n      dipTimes.current = dipTimes.current.filter((t) => now - t < 1000);\n      if (dipTimes.current.length + FLICKER_DIPS > MAX_DIPS_PER_SECOND) return;\n      const frames = flickerKeyframes(rng);\n      const opts: KeyframeAnimationOptions = { duration: 400, easing: \"linear\" };\n      const g = glowRefs.current[i];\n      const b = bloomRefs.current[i];\n      const f = fillRefs.current[i];\n      if (!g?.animate) return;\n      dipTimes.current.push(now, now + 180);\n      const a = g.animate(frames, opts);\n      b?.animate?.(frames, opts);\n      f?.animate?.(frames, opts);\n      activeFlickers.current.set(i, a);\n      const done = () => {\n        if (activeFlickers.current.get(i) === a) activeFlickers.current.delete(i);\n      };\n      a.finished.then(done, done);\n    },\n    [staticMode, lit, glyphCount, seed]\n  );\n\n  React.useImperativeHandle(\n    ref,\n    () => ({\n      replay: () => {\n        if (staticMode) return;\n        setIgniteKey((key) => key + 1);\n      },\n      flicker,\n    }),\n    [flicker, staticMode]\n  );\n\n  // Idle ghost: the sign occasionally destabilises on its own when nobody's pointing.\n  const running = lit && !staticMode && inView && docVisible;\n  React.useEffect(() => {\n    if (!running || !idle) return;\n    const rng = mulberry32(seed ^ 0x51ed27);\n    let timer: ReturnType<typeof setTimeout>;\n    const schedule = () => {\n      timer = setTimeout(\n        () => {\n          if (performance.now() - lastPointer.current > 1500) flicker();\n          schedule();\n        },\n        2600 + rng() * 3200\n      );\n    };\n    schedule();\n    return () => clearTimeout(timer);\n  }, [running, idle, seed, flicker]);\n\n  React.useEffect(() => {\n    const flickers = activeFlickers.current;\n    return () => {\n      flickers.forEach((a) => a.cancel());\n      flickers.clear();\n    };\n  }, []);\n\n  const nearestGlyph = (clientX: number, clientY: number) => {\n    const svg = svgRef.current;\n    const ctm = svg?.getScreenCTM();\n    if (!svg || !ctm) return -1;\n    const p = new DOMPoint(clientX, clientY).matrixTransform(ctm.inverse());\n    let best = -1;\n    let bestD = fontSize * 0.9;\n    layout.glyphs.forEach((g, i) => {\n      const cx = lineOffset(g.line) + g.x + g.w / 2;\n      const cy = baselineOf(g.line) - fontSize * 0.36;\n      const d = Math.hypot((p.x - cx) * 1.15, p.y - cy);\n      if (d < bestD) {\n        bestD = d;\n        best = i;\n      }\n    });\n    return best;\n  };\n\n  const onPointerMove = (e: React.PointerEvent<SVGSVGElement>) => {\n    lastPointer.current = performance.now();\n    const i = nearestGlyph(e.clientX, e.clientY);\n    if (i !== hoverIndex.current) {\n      hoverIndex.current = i;\n      if (i >= 0) flicker(i);\n    }\n  };\n  const onPointerDown = (e: React.PointerEvent<SVGSVGElement>) => {\n    lastPointer.current = performance.now();\n    const i = nearestGlyph(e.clientX, e.clientY);\n    if (i >= 0) flicker(i);\n  };\n  const onPointerLeave = () => {\n    lastPointer.current = performance.now();\n    hoverIndex.current = -1;\n  };\n\n  // ---- render -------------------------------------------------------------\n  const restOffset = staticMode || lit ? 0 : dashLen;\n  const restFill = staticMode || lit ? 1 : 0;\n  const humming = lit && !staticMode;\n  const bloomBlur = strokeWidth * (0.9 + 1.4 * k);\n  const tubeTransition = \"stroke 450ms ease, fill 450ms ease\";\n\n  const textProps = (g: Glyph) => ({\n    x: lineOffset(g.line) + g.x,\n    y: baselineOf(g.line),\n  });\n\n  return (\n    <Tag\n      data-crucible=\"argon\"\n      className={cn(\"relative inline-block max-w-full font-semibold\", className)}\n      style={style}\n      {...props}\n    >\n      <span className=\"sr-only\">{text}</span>\n      <style>{`\n        @keyframes crucible-argon-hum {\n          0%, 100% { opacity: 1; }\n          23% { opacity: 0.997; }\n          41% { opacity: 1; }\n          67% { opacity: 0.9975; }\n          84% { opacity: 0.999; }\n        }\n        @keyframes crucible-argon-hum-bloom {\n          0%, 100% { opacity: 1; }\n          31% { opacity: 0.99; }\n          58% { opacity: 1; }\n          79% { opacity: 0.992; }\n        }\n        [data-crucible=\"argon\"] [data-hum] {\n          animation: crucible-argon-hum 2.3s ease-in-out infinite;\n          animation-play-state: var(--argon-play, running);\n        }\n        [data-crucible=\"argon\"] [data-hum=\"bloom\"] {\n          animation-name: crucible-argon-hum-bloom;\n          animation-duration: 3.1s;\n        }\n        @media (prefers-reduced-motion: reduce) {\n          [data-crucible=\"argon\"] [data-hum] { animation: none; }\n        }\n      `}</style>\n      <svg\n        ref={svgRef}\n        aria-hidden\n        focusable=\"false\"\n        width={vbW}\n        viewBox={`0 0 ${vbW} ${vbH}`}\n        className=\"block h-auto max-w-full select-none overflow-visible\"\n        style={\n          {\n            visibility: layout.measured ? \"visible\" : \"hidden\",\n            \"--argon-play\": running ? \"running\" : \"paused\",\n          } as React.CSSProperties\n        }\n        onPointerMove={onPointerMove}\n        onPointerDown={onPointerDown}\n        onPointerLeave={onPointerLeave}\n      >\n        <defs>\n          <filter id={filterId} x=\"-30%\" y=\"-40%\" width=\"160%\" height=\"180%\" colorInterpolationFilters=\"sRGB\">\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation={bloomBlur} result=\"near\" />\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation={bloomBlur * 3.4} result=\"far\" />\n            <feMerge>\n              <feMergeNode in=\"far\" />\n              <feMergeNode in=\"near\" />\n            </feMerge>\n          </filter>\n          {/* Hides the inner half of every outline stroke — tubes read as clean\n              rims, and overlapping contours in variable fonts never show. */}\n          <mask id={maskId} maskUnits=\"userSpaceOnUse\" x={0} y={0} width={vbW} height={vbH}>\n            <rect x={0} y={0} width={vbW} height={vbH} fill=\"#fff\" />\n            <g style={{ fontSize }} fontFamily=\"inherit\" fontWeight=\"inherit\" fill=\"#000\" stroke=\"none\">\n              {layout.glyphs.map((g, i) => (\n                <text key={i} {...textProps(g)}>\n                  {g.char}\n                </text>\n              ))}\n            </g>\n          </mask>\n        </defs>\n\n        <g style={{ fontSize }} fontFamily=\"inherit\" fontWeight=\"inherit\" strokeLinejoin=\"round\" strokeLinecap=\"round\">\n          {/* measuring copies — never painted */}\n          <g visibility=\"hidden\">\n            {lines.map((line, li) => (\n              <text\n                key={li}\n                ref={(el) => {\n                  measureRefs.current[li] = el;\n                }}\n                x={0}\n                y={baselineOf(li)}\n                style={{ whiteSpace: \"pre\" }}\n              >\n                {line}\n              </text>\n            ))}\n          </g>\n\n          {/* back-wire + mounting clips */}\n          {clips && (\n            <g>\n              {lines.map((_, li) => {\n                const lineGlyphs = layout.glyphs.filter((g) => g.line === li);\n                if (lineGlyphs.length === 0) return null;\n                const first = lineGlyphs[0]!;\n                const last = lineGlyphs[lineGlyphs.length - 1]!;\n                const off = lineOffset(li);\n                const wy = baselineOf(li) + fontSize * 0.15;\n                return (\n                  <line\n                    key={li}\n                    x1={off + first.x - fontSize * 0.08}\n                    x2={off + last.x + last.w + fontSize * 0.08}\n                    y1={wy}\n                    y2={wy}\n                    stroke=\"rgba(255,255,255,0.09)\"\n                    strokeWidth={Math.max(1, fontSize * 0.012)}\n                  />\n                );\n              })}\n              {layout.glyphs.map((g, i) => {\n                const cx = lineOffset(g.line) + g.x + g.w / 2;\n                const by = baselineOf(g.line);\n                const cw = fontSize * 0.085;\n                const ch = fontSize * 0.06;\n                return (\n                  <g key={i}>\n                    <line\n                      x1={cx}\n                      x2={cx}\n                      y1={by + ch * 0.6}\n                      y2={by + fontSize * 0.15}\n                      stroke=\"rgba(255,255,255,0.12)\"\n                      strokeWidth={Math.max(1, fontSize * 0.01)}\n                    />\n                    <rect\n                      x={cx - cw / 2}\n                      y={by - ch * 0.35}\n                      width={cw}\n                      height={ch}\n                      rx={ch * 0.3}\n                      style={{\n                        fill: `color-mix(in srgb, ${color} ${restFill ? 14 : 4}%, #2b2b30)`,\n                        stroke: \"rgba(255,255,255,0.2)\",\n                        transition: \"fill 600ms ease\",\n                      }}\n                      strokeWidth={Math.max(0.75, fontSize * 0.006)}\n                    />\n                  </g>\n                );\n              })}\n            </g>\n          )}\n\n          {/* unlit glass tubes */}\n          {layout.glyphs.map((g, i) => (\n            <text key={i} {...textProps(g)} fill=\"rgba(255,255,255,0.03)\" stroke=\"none\">\n              {g.char}\n            </text>\n          ))}\n          <g mask={`url(#${maskId})`}>\n            {layout.glyphs.map((g, i) => (\n              <text\n                key={i}\n                {...textProps(g)}\n                fill=\"none\"\n                stroke=\"rgba(226,232,240,0.2)\"\n                strokeWidth={strokeWidth * 1.5}\n              >\n                {g.char}\n              </text>\n            ))}\n          </g>\n\n          {/* bloom */}\n          <g filter={`url(#${filterId})`} opacity={Math.min(1, 0.6 * k)} data-hum={humming ? \"bloom\" : undefined}>\n            {layout.glyphs.map((g, i) => (\n              <text\n                key={i}\n                ref={(el) => {\n                  bloomRefs.current[i] = el;\n                  (dashRefs.current[i] ??= [])[0] = el;\n                }}\n                {...textProps(g)}\n                fill=\"none\"\n                strokeWidth={strokeWidth * (2.2 + k)}\n                strokeDasharray={`${dashLen} ${dashLen}`}\n                style={{ stroke: color, strokeDashoffset: restOffset, transition: tubeTransition }}\n              >\n                {g.char}\n              </text>\n            ))}\n          </g>\n\n          {/* gas glowing inside the glass */}\n          <g data-hum={humming ? \"core\" : undefined}>\n            {layout.glyphs.map((g, i) => (\n              <text\n                key={i}\n                ref={(el) => {\n                  fillRefs.current[i] = el;\n                }}\n                {...textProps(g)}\n                stroke=\"none\"\n                style={{\n                  fill: color,\n                  fillOpacity: 0.08 * k,\n                  opacity: restFill,\n                  transition: tubeTransition,\n                }}\n              >\n                {g.char}\n              </text>\n            ))}\n          </g>\n\n          {/* lit tubes: core rim + white-hot inner edge (outer half only, via mask) */}\n          <g data-hum={humming ? \"core\" : undefined} mask={`url(#${maskId})`}>\n            {layout.glyphs.map((g, i) => (\n              <g\n                key={i}\n                ref={(el) => {\n                  glowRefs.current[i] = el;\n                }}\n              >\n                <text\n                  ref={(el) => {\n                    (dashRefs.current[i] ??= [])[1] = el;\n                  }}\n                  {...textProps(g)}\n                  fill=\"none\"\n                  strokeWidth={strokeWidth * 2}\n                  strokeDasharray={`${dashLen} ${dashLen}`}\n                  style={{\n                    stroke: color,\n                    strokeOpacity: Math.min(1, 0.65 + 0.35 * k),\n                    strokeDashoffset: restOffset,\n                    transition: tubeTransition,\n                  }}\n                >\n                  {g.char}\n                </text>\n                <text\n                  ref={(el) => {\n                    (dashRefs.current[i] ??= [])[2] = el;\n                  }}\n                  {...textProps(g)}\n                  fill=\"none\"\n                  strokeWidth={strokeWidth * 0.9}\n                  strokeDasharray={`${dashLen} ${dashLen}`}\n                  style={{ stroke: hot, strokeDashoffset: restOffset, transition: tubeTransition }}\n                >\n                  {g.char}\n                </text>\n              </g>\n            ))}\n          </g>\n        </g>\n      </svg>\n    </Tag>\n  );\n}\n"
    }
  ]
}