{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "FuseButton-TS-CSS",
	"title": "FuseButton",
	"description": "Action button whose done state carries its own undo on a burning fuse: press, the label crossfades to Undo, a hairline burns for the undo window, and Undo or Escape runs it back.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "FuseButton.css",
			"target": "@components/FuseButton.css",
			"content": ".fuse-button {\n  --fb-ink: #f5f5f5;\n  --fb-bg: #27272a;\n  --fb-fuse: #f5a524;\n  --fb-fuse-h: 1.5px;\n  --fb-radius: 22px;\n  --fb-fade: 200ms;\n  --fb-press: 0.97;\n  --fb-h: 44px;\n  --fb-fs: 14px;\n  --fb-icon: 15px;\n  --fb-px: 20px;\n  --fb-ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n\n  position: relative;\n  display: inline-grid;\n  grid-template-columns: minmax(0, 1fr);\n  height: var(--fb-h);\n  border-radius: var(--fb-radius);\n  background: var(--fb-bg);\n  color: var(--fb-ink);\n  font-family: inherit;\n  font-size: var(--fb-fs);\n  font-weight: 500;\n  line-height: 1;\n  letter-spacing: 0.01em;\n  overflow: hidden;\n  isolation: isolate;\n  outline: none;\n  user-select: none;\n  -webkit-user-select: none;\n  -webkit-touch-callout: none;\n  -webkit-tap-highlight-color: transparent;\n  touch-action: manipulation;\n  transition: transform 160ms var(--fb-ease-out);\n}\n\n.fuse-button[data-pressed] {\n  transform: scale(var(--fb-press));\n}\n\n.fuse-button:has(:focus-visible) {\n  outline: 2px solid color-mix(in srgb, var(--fb-ink) 60%, transparent);\n  outline-offset: 3px;\n}\n\n.fuse-button__idle:focus-visible,\n.fuse-button__undo:focus-visible {\n  outline: none;\n}\n\n@supports not selector(:has(a)) {\n  .fuse-button__idle:focus-visible,\n  .fuse-button__undo:focus-visible {\n    outline: 2px solid color-mix(in srgb, var(--fb-ink) 60%, transparent);\n    outline-offset: -3px;\n  }\n}\n\n.fuse-button__face {\n  position: relative;\n  grid-area: 1 / 1;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 8px;\n  width: 100%;\n  height: 100%;\n  min-width: 0;\n  margin: 0;\n  padding: 0 var(--fb-px);\n  border: 0;\n  background: none;\n  color: inherit;\n  font: inherit;\n  letter-spacing: inherit;\n  white-space: nowrap;\n  cursor: pointer;\n  opacity: 0;\n  filter: blur(2px);\n  visibility: hidden;\n  transition:\n    opacity var(--fb-fade) ease,\n    filter var(--fb-fade) ease,\n    visibility 0s linear var(--fb-fade),\n    background-color 160ms ease;\n}\n\n.fuse-button[data-phase='idle'] .fuse-button__idle,\n.fuse-button[data-phase='armed'] .fuse-button__undo,\n.fuse-button[data-phase='settled'] .fuse-button__settled {\n  opacity: 1;\n  filter: blur(0);\n  visibility: visible;\n  transition-delay: 0s;\n}\n\n.fuse-button[data-instant] .fuse-button__face {\n  transition-duration: 0s;\n}\n\n.fuse-button__idle:disabled,\n.fuse-button__settled {\n  cursor: default;\n}\n\n.fuse-button[data-phase='idle']:has(.fuse-button__idle:disabled) {\n  opacity: 0.55;\n}\n\n.fuse-button__icon {\n  display: inline-flex;\n  width: var(--fb-icon);\n  height: var(--fb-icon);\n}\n\n.fuse-button__icon svg {\n  width: 100%;\n  height: 100%;\n}\n\n.fuse-button__icon--undo {\n  transform: rotate(-70deg);\n  transition: transform var(--fb-fade) var(--fb-ease-out);\n}\n\n.fuse-button[data-phase='armed'] .fuse-button__icon--undo {\n  transform: rotate(0deg);\n}\n\n.fuse-button__fuse {\n  position: absolute;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  height: var(--fb-fuse-h);\n  background: var(--fb-fuse);\n  box-shadow: 0 0 6px color-mix(in srgb, var(--fb-fuse) 55%, transparent);\n  transform-origin: left center;\n  pointer-events: none;\n}\n\n.fuse-button[data-fuse='top'] .fuse-button__fuse {\n  bottom: auto;\n  top: 0;\n}\n\n.fuse-button__rim {\n  position: absolute;\n  inset: 0;\n  width: 100%;\n  height: 100%;\n  overflow: visible;\n  pointer-events: none;\n  opacity: 0;\n  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--fb-fuse) 60%, transparent));\n  transition: opacity var(--fb-fade) ease;\n}\n\n.fuse-button[data-phase='armed'] .fuse-button__rim {\n  opacity: 1;\n}\n\n.fuse-button[data-instant] .fuse-button__rim {\n  transition-duration: 0s;\n}\n\n.fuse-button__rim rect {\n  x: calc(var(--fb-fuse-h) / 2);\n  y: calc(var(--fb-fuse-h) / 2);\n  width: calc(100% - var(--fb-fuse-h));\n  height: calc(100% - var(--fb-fuse-h));\n  rx: max(0px, calc(var(--fb-radius) - var(--fb-fuse-h) / 2));\n  fill: none;\n  stroke: var(--fb-fuse);\n  stroke-width: var(--fb-fuse-h);\n  stroke-linecap: round;\n  stroke-dasharray: 1;\n}\n\n.fuse-button__status {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  margin: -1px;\n  padding: 0;\n  border: 0;\n  overflow: hidden;\n  clip: rect(0 0 0 0);\n  white-space: nowrap;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .fuse-button__idle:not(:disabled):hover,\n  .fuse-button__undo:hover {\n    background: color-mix(in srgb, var(--fb-ink) 7%, transparent);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .fuse-button[data-pressed] {\n    transform: none;\n  }\n\n  .fuse-button .fuse-button__face {\n    filter: none;\n    transition:\n      opacity var(--fb-fade) ease,\n      visibility 0s linear var(--fb-fade);\n  }\n\n  .fuse-button .fuse-button__icon--undo {\n    transform: none;\n    transition: none;\n  }\n}\n\n@media (prefers-contrast: more) {\n  .fuse-button {\n    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fb-ink) 40%, transparent);\n  }\n}\n\n@media (forced-colors: active) {\n  .fuse-button__fuse {\n    background: Highlight;\n  }\n\n  .fuse-button__rim rect {\n    stroke: Highlight;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "FuseButton.tsx",
			"content": "'use client';\n\nimport React, { useEffect, useId, useRef, useState, type CSSProperties, type ReactNode } from 'react';\nimport { HugeiconsIcon } from '@hugeicons/react';\nimport { Archive02Icon, Tick02Icon, Undo02Icon } from '@hugeicons/core-free-icons';\n\nimport './FuseButton.css';\n\nexport type FusePosition = 'outline' | 'bottom' | 'top';\nexport type FuseCommitOn = 'press' | 'fuseEnd';\nexport type FuseSettle = 'reset' | 'stay';\nexport type FusePhase = 'idle' | 'armed' | 'settled';\nexport type FuseSize = 'sm' | 'md' | 'lg';\n\nexport interface FuseButtonProps {\n  label?: string;\n  undoLabel?: string;\n  doneLabel?: string;\n  icon?: ReactNode;\n  color?: string;\n  background?: string;\n  fuseColor?: string;\n  size?: FuseSize;\n  radius?: number;\n  undoWindow?: number;\n  fuse?: FusePosition;\n  fuseThickness?: number;\n  crossfadeMs?: number;\n  commitOn?: FuseCommitOn;\n  pauseOnHover?: boolean;\n  settle?: FuseSettle;\n  disabled?: boolean;\n  onCommit?: (reason: FuseCommitOn) => void;\n  onUndo?: () => void;\n  onFuseEnd?: () => void;\n  onPhaseChange?: (phase: FusePhase) => void;\n  className?: string;\n  type?: 'button' | 'submit' | 'reset';\n}\n\ninterface Latest {\n  onCommit?: (reason: FuseCommitOn) => void;\n  onUndo?: () => void;\n  onFuseEnd?: () => void;\n  onPhaseChange?: (phase: FusePhase) => void;\n  commitOn: FuseCommitOn;\n  settle: FuseSettle;\n}\n\nconst LINE: Keyframe[] = [{ transform: 'scaleX(1)' }, { transform: 'scaleX(0)' }];\nconst OUTLINE: Keyframe[] = [{ strokeDashoffset: 0 }, { strokeDashoffset: -1 }];\nconst SIZES: Record<FuseSize, { height: number; font: number; icon: number; px: number }> = {\n  sm: { height: 36, font: 13, icon: 14, px: 16 },\n  md: { height: 44, font: 14, icon: 15, px: 20 },\n  lg: { height: 52, font: 15, icon: 17, px: 24 }\n};\n\nconst FuseButton: React.FC<FuseButtonProps> = ({\n  label = 'Archive',\n  undoLabel = 'Undo',\n  doneLabel = 'Archived',\n  icon,\n  color = '#f5f5f5',\n  background = '#27272a',\n  fuseColor = '#f5a524',\n  size = 'md',\n  radius = 22,\n  undoWindow = 4000,\n  fuse = 'outline',\n  fuseThickness = 1.5,\n  crossfadeMs = 200,\n  commitOn = 'press',\n  pauseOnHover = true,\n  settle = 'reset',\n  disabled = false,\n  onCommit,\n  onUndo,\n  onFuseEnd,\n  onPhaseChange,\n  className = '',\n  type = 'button'\n}) => {\n  const [phase, setPhase] = useState<FusePhase>('idle');\n  const [instant, setInstant] = useState(false);\n  const rootRef = useRef<HTMLSpanElement>(null);\n  const idleRef = useRef<HTMLButtonElement>(null);\n  const undoRef = useRef<HTMLButtonElement>(null);\n  const lineRef = useRef<HTMLElement>(null);\n  const rimRef = useRef<SVGRectElement>(null);\n  const anim = useRef<Animation | null>(null);\n  const pause = useRef({ hover: false, hidden: false, canHoverPause: false });\n  const lastInput = useRef<'pointer' | 'keyboard'>('pointer');\n  const windowRef = useRef(undoWindow);\n  const latest = useRef<Latest>({ commitOn, settle });\n  latest.current = { onCommit, onUndo, onFuseEnd, onPhaseChange, commitOn, settle };\n  const statusId = useId();\n  const preset = SIZES[size] || SIZES.md;\n\n  const go = (next: FusePhase) => {\n    setInstant(lastInput.current === 'keyboard');\n    setPhase(next);\n    latest.current.onPhaseChange?.(next);\n  };\n\n  const syncPlayState = () => {\n    const a = anim.current;\n    if (!a) return;\n    const { hover, hidden } = pause.current;\n    if (hover || hidden) {\n      if (a.playState === 'running') a.pause();\n    } else if (a.playState === 'paused') {\n      a.play();\n    }\n  };\n\n  const light = (from = 0) => {\n    const el = fuse === 'outline' ? rimRef.current : lineRef.current;\n    if (!el) return;\n    anim.current?.cancel();\n    const a = el.animate(fuse === 'outline' ? OUTLINE : LINE, {\n      duration: windowRef.current,\n      easing: 'linear',\n      fill: 'forwards'\n    });\n    if (from) a.currentTime = from;\n    a.onfinish = () => {\n      const l = latest.current;\n      l.onFuseEnd?.();\n      if (l.commitOn === 'fuseEnd') l.onCommit?.('fuseEnd');\n      lastInput.current = 'pointer';\n      go(l.settle === 'stay' ? 'settled' : 'idle');\n    };\n    anim.current = a;\n    syncPlayState();\n  };\n\n  const arm = () => {\n    if (disabled || phase !== 'idle') return;\n    windowRef.current = undoWindow;\n    light();\n    pause.current.canHoverPause = false;\n    pause.current.hover = false;\n    if (commitOn === 'press') onCommit?.('press');\n    go('armed');\n  };\n\n  const undo = () => {\n    if (phase !== 'armed') return;\n    const a = anim.current;\n    if (a) {\n      a.onfinish = null;\n      a.pause();\n    }\n    onUndo?.();\n    go('idle');\n  };\n\n  useEffect(() => {\n    const inside = rootRef.current?.contains(document.activeElement);\n    if (phase === 'armed') undoRef.current?.focus({ preventScroll: true });\n    else if (inside) (phase === 'idle' ? idleRef.current : rootRef.current)?.focus({ preventScroll: true });\n  }, [phase]);\n\n  useEffect(() => {\n    const onVisibility = () => {\n      pause.current.hidden = document.hidden;\n      syncPlayState();\n    };\n    document.addEventListener('visibilitychange', onVisibility);\n    return () => {\n      document.removeEventListener('visibilitychange', onVisibility);\n      anim.current?.cancel();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    const a = anim.current;\n    if (!a || phase !== 'armed') return;\n    light(Number(a.currentTime) || 0);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [fuse]);\n\n  useEffect(() => {\n    if (pauseOnHover) return;\n    pause.current.hover = false;\n    syncPlayState();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [pauseOnHover]);\n\n  const handlePointerDown = (e: React.PointerEvent<HTMLSpanElement>) => {\n    lastInput.current = 'pointer';\n    const pressable = phase === 'armed' || (phase === 'idle' && !disabled);\n    if (e.button === 0 && pressable && rootRef.current) rootRef.current.dataset.pressed = '';\n  };\n  const release = () => {\n    if (rootRef.current) delete rootRef.current.dataset.pressed;\n  };\n  const handlePointerEnter = (e: React.PointerEvent<HTMLSpanElement>) => {\n    if (pauseOnHover && e.pointerType === 'mouse' && pause.current.canHoverPause) {\n      pause.current.hover = true;\n      syncPlayState();\n    }\n  };\n  const handlePointerLeave = (e: React.PointerEvent<HTMLSpanElement>) => {\n    release();\n    if (e.pointerType !== 'mouse') return;\n    pause.current.canHoverPause = true;\n    pause.current.hover = false;\n    syncPlayState();\n  };\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {\n    if (e.key === 'Enter' || e.key === ' ') lastInput.current = 'keyboard';\n    if (e.key === 'Escape' && phase === 'armed') {\n      e.preventDefault();\n      lastInput.current = 'keyboard';\n      undo();\n    }\n  };\n\n  const actionIcon = icon ?? <HugeiconsIcon icon={Archive02Icon} size={preset.icon} strokeWidth={1.8} />;\n  const line = <i ref={lineRef} className=\"fuse-button__fuse\" aria-hidden=\"true\" />;\n\n  return (\n    <span\n      ref={rootRef}\n      tabIndex={-1}\n      className={`fuse-button${className ? ` ${className}` : ''}`}\n      data-phase={phase}\n      data-fuse={fuse}\n      data-instant={instant ? '' : undefined}\n      aria-disabled={phase === 'settled' || undefined}\n      style={\n        {\n          '--fb-ink': color,\n          '--fb-bg': background,\n          '--fb-fuse': fuseColor,\n          '--fb-fuse-h': `${fuseThickness}px`,\n          '--fb-radius': `${radius}px`,\n          '--fb-fade': `${crossfadeMs}ms`,\n          '--fb-h': `${preset.height}px`,\n          '--fb-fs': `${preset.font}px`,\n          '--fb-icon': `${preset.icon}px`,\n          '--fb-px': `${preset.px}px`\n        } as CSSProperties\n      }\n      onPointerDown={handlePointerDown}\n      onPointerUp={release}\n      onPointerCancel={release}\n      onPointerEnter={handlePointerEnter}\n      onPointerLeave={handlePointerLeave}\n      onKeyDown={handleKeyDown}\n    >\n      <button\n        ref={idleRef}\n        type={type}\n        className=\"fuse-button__face fuse-button__idle\"\n        disabled={disabled}\n        inert={phase !== 'idle'}\n        onClick={arm}\n      >\n        <span className=\"fuse-button__icon\" aria-hidden=\"true\">\n          {actionIcon}\n        </span>\n        {label}\n      </button>\n      <button\n        ref={undoRef}\n        type=\"button\"\n        className=\"fuse-button__face fuse-button__undo\"\n        aria-describedby={statusId}\n        aria-keyshortcuts=\"Escape\"\n        inert={phase !== 'armed'}\n        onClick={undo}\n      >\n        <span className=\"fuse-button__icon fuse-button__icon--undo\" aria-hidden=\"true\">\n          <HugeiconsIcon icon={Undo02Icon} size={preset.icon} strokeWidth={2} />\n        </span>\n        {undoLabel}\n        {fuse !== 'outline' ? line : null}\n      </button>\n      <span className=\"fuse-button__face fuse-button__settled\" inert={phase !== 'settled'}>\n        <span className=\"fuse-button__icon\" aria-hidden=\"true\">\n          <HugeiconsIcon icon={Tick02Icon} size={preset.icon} strokeWidth={2.2} />\n        </span>\n        {doneLabel}\n      </span>\n      {fuse === 'outline' ? (\n        <svg className=\"fuse-button__rim\" aria-hidden=\"true\">\n          <rect ref={rimRef} pathLength=\"1\" />\n        </svg>\n      ) : null}\n      <span className=\"fuse-button__status\" id={statusId} role=\"status\" aria-live=\"polite\">\n        {phase === 'idle' ? '' : doneLabel}\n      </span>\n    </span>\n  );\n};\n\nexport default FuseButton;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"@hugeicons/react@^1.1.10",
		"@hugeicons/core-free-icons@^4.3.3"
	]
}