{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "SpringCheck-JS-CSS",
	"title": "SpringCheck",
	"description": "Checkbox row where one spring fills the box, draws the tick and strikes through the label.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "SpringCheck.css",
			"target": "@components/SpringCheck.css",
			"content": ".spring-check {\n  --sc-ink: #ffffff;\n  --sc-fill: #ffffff;\n  --sc-check: #0b0b0f;\n  --sc-box: 28px;\n  --sc-radius: 9px;\n  --sc-font: 18px;\n  --sc-ring: 2px;\n  --sc-gap: 12px;\n  --sc-row: 44px;\n  --sc-rule: 1.5px;\n  --sc-origin: left center;\n  --sc-ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n\n  display: inline-flex;\n  align-items: center;\n  gap: var(--sc-gap);\n  min-height: var(--sc-row);\n  margin: 0;\n  padding: 0;\n  border: 0;\n  background: none;\n  color: var(--sc-ink);\n  font-family: inherit;\n  font-size: var(--sc-font);\n  font-weight: 500;\n  line-height: 1.2;\n  letter-spacing: -0.01em;\n  text-align: left;\n  cursor: pointer;\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  outline: none;\n}\n\n.spring-check:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n\n.spring-check__press {\n  flex: none;\n  width: var(--sc-box);\n  height: var(--sc-box);\n  border-radius: var(--sc-radius);\n  transition: transform 160ms var(--sc-ease-out);\n}\n\n.spring-check[data-pressed] .spring-check__press {\n  transform: scale(0.95);\n}\n\n.spring-check:focus-visible .spring-check__press {\n  outline: 2px solid color-mix(in srgb, var(--sc-ink) 45%, transparent);\n  outline-offset: 3px;\n}\n\n.spring-check__box {\n  position: relative;\n  display: grid;\n  place-items: center;\n  width: 100%;\n  height: 100%;\n  border-radius: inherit;\n  overflow: hidden;\n  transform-origin: center;\n}\n\n.spring-check__ring {\n  position: absolute;\n  inset: 0;\n  border-radius: inherit;\n  box-shadow: inset 0 0 0 var(--sc-ring) var(--sc-ink);\n  opacity: 0.28;\n  transition: opacity 120ms ease;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .spring-check:not(:disabled):hover .spring-check__ring {\n    opacity: 0.5;\n  }\n}\n\n.spring-check__fill {\n  position: absolute;\n  inset: 0;\n  border-radius: inherit;\n  background: var(--sc-fill);\n  transform-origin: center;\n}\n\n.spring-check__tick {\n  position: relative;\n  width: 68%;\n  height: 68%;\n  overflow: visible;\n  fill: none;\n  stroke: var(--sc-check);\n  stroke-width: 2.6;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n.spring-check__label {\n  position: relative;\n  display: inline-block;\n}\n\n.spring-check__word {\n  display: inline-block;\n}\n\n.spring-check__rule {\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 46%;\n  height: var(--sc-rule);\n  border-radius: 2px;\n  background: currentColor;\n  transform-origin: var(--sc-origin);\n  pointer-events: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .spring-check__press {\n    transition: none;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "SpringCheck.jsx",
			"content": "'use client';\n\nimport { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport { animate, useMotionValue, useMotionValueEvent, useReducedMotion } from 'motion/react';\nimport { Tick02Icon } from '@hugeicons/core-free-icons';\n\nimport './SpringCheck.css';\n\nconst VISUAL_DURATION = 0.2;\nconst RULE_END = 0.84;\nconst SWELL = 0.35;\nconst TICK_PATH = String(Tick02Icon[0][1].d);\nconst ORIGIN = { left: 'left center', center: 'center', right: 'right center', none: 'left center' };\n\nconst clamp01 = value => Math.min(1, Math.max(0, value));\nconst zetaOf = bounce => (bounce <= 0 ? 1 : -Math.log(bounce) / Math.sqrt(Math.PI ** 2 + Math.log(bounce) ** 2));\n\nconst readings = (t, doneOpacity, strikeLag) => {\n  const held = clamp01(t);\n  return {\n    fill: `scale(${Math.max(t, 0)})`,\n    box: `scale(${1 + SWELL * Math.max(0, t - 1)})`,\n    tick: 1 - held,\n    word: 1 - (1 - doneOpacity) * held,\n    rule: `scaleX(${clamp01((held - strikeLag) / (RULE_END - strikeLag))})`\n  };\n};\n\nexport default function SpringCheck({\n  label = 'Ship the build',\n  checked,\n  defaultChecked = false,\n  onChange,\n  disabled = false,\n  color = '#ffffff',\n  fillColor = '#ffffff',\n  checkColor = '#0b0b0f',\n  boxSize = 28,\n  boxRadius = 9,\n  fontSize = 18,\n  bounce = 0.2,\n  strikeLag = 0.12,\n  doneOpacity = 0.42,\n  strike = 'left',\n  ariaLabel,\n  className = ''\n}) {\n  const controlled = checked !== undefined;\n  const [inner, setInner] = useState(defaultChecked);\n  const on = controlled ? checked : inner;\n  const reduce = useReducedMotion();\n\n  const t = useMotionValue(on ? 1 : 0);\n  const viaPointer = useRef(false);\n  const instant = useRef(false);\n  const rowRef = useRef(null);\n  const boxRef = useRef(null);\n  const fillRef = useRef(null);\n  const tickRef = useRef(null);\n  const wordRef = useRef(null);\n  const ruleRef = useRef(null);\n  const cfg = useRef({ doneOpacity, strikeLag });\n  cfg.current = { doneOpacity, strikeLag };\n\n  const write = value => {\n    const r = readings(value, cfg.current.doneOpacity, cfg.current.strikeLag);\n    if (fillRef.current) fillRef.current.style.transform = r.fill;\n    if (boxRef.current) boxRef.current.style.transform = r.box;\n    if (tickRef.current) tickRef.current.style.strokeDashoffset = r.tick;\n    if (wordRef.current) wordRef.current.style.opacity = r.word;\n    if (ruleRef.current) ruleRef.current.style.transform = r.rule;\n  };\n  useMotionValueEvent(t, 'change', write);\n  useLayoutEffect(() => {\n    write(t.get());\n  });\n\n  useEffect(() => {\n    const target = on ? 1 : 0;\n    if (reduce || instant.current) {\n      instant.current = false;\n      t.jump(target);\n      return undefined;\n    }\n    if (t.get() === target && t.getVelocity() === 0) return undefined;\n    const controls = animate(t, target, {\n      type: 'spring',\n      visualDuration: VISUAL_DURATION,\n      bounce: 1 - zetaOf(bounce)\n    });\n    return () => controls.stop();\n  }, [on, reduce, bounce, t]);\n\n  const handlePointerDown = e => {\n    if (e.button !== 0 || disabled) return;\n    viaPointer.current = true;\n    if (!reduce && rowRef.current) rowRef.current.dataset.pressed = '';\n  };\n  const handlePointerUp = () => {\n    if (rowRef.current) delete rowRef.current.dataset.pressed;\n  };\n  const handlePointerCancel = () => {\n    viaPointer.current = false;\n    handlePointerUp();\n  };\n  const toggle = () => {\n    if (disabled) return;\n    instant.current = !viaPointer.current;\n    viaPointer.current = false;\n    const next = !on;\n    if (!controlled) setInner(next);\n    onChange?.(next);\n  };\n\n  const r = readings(t.get(), doneOpacity, strikeLag);\n  const ring = boxSize >= 24 ? 2 : 1.5;\n  const gap = Math.min(16, Math.max(8, Math.round(boxSize * 0.43)));\n  const ruleHeight = Math.max(1.5, Math.round(fontSize / 6) / 2);\n\n  return (\n    <button\n      ref={rowRef}\n      type=\"button\"\n      role=\"checkbox\"\n      aria-checked={on}\n      aria-label={ariaLabel}\n      disabled={disabled}\n      className={`spring-check${className ? ` ${className}` : ''}`}\n      style={{\n        '--sc-ink': color,\n        '--sc-fill': fillColor,\n        '--sc-check': checkColor,\n        '--sc-box': `${boxSize}px`,\n        '--sc-radius': `${boxRadius}px`,\n        '--sc-font': `${fontSize}px`,\n        '--sc-ring': `${ring}px`,\n        '--sc-gap': `${gap}px`,\n        '--sc-row': `${Math.max(44, boxSize + 16)}px`,\n        '--sc-rule': `${ruleHeight}px`,\n        '--sc-origin': ORIGIN[strike] || ORIGIN.left\n      }}\n      onPointerDown={handlePointerDown}\n      onPointerUp={handlePointerUp}\n      onPointerCancel={handlePointerCancel}\n      onPointerLeave={handlePointerCancel}\n      onClick={toggle}\n    >\n      <span className=\"spring-check__press\">\n        <span ref={boxRef} className=\"spring-check__box\" style={{ transform: r.box }}>\n          <span className=\"spring-check__ring\" aria-hidden=\"true\" />\n          <span ref={fillRef} className=\"spring-check__fill\" style={{ transform: r.fill }} />\n          <svg className=\"spring-check__tick\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n            <path ref={tickRef} d={TICK_PATH} pathLength={1} strokeDasharray={1} style={{ strokeDashoffset: r.tick }} />\n          </svg>\n        </span>\n      </span>\n      <span className=\"spring-check__label\">\n        <span ref={wordRef} className=\"spring-check__word\" style={{ opacity: r.word }}>\n          {label}\n        </span>\n        {strike !== 'none' ? (\n          <span ref={ruleRef} className=\"spring-check__rule\" aria-hidden=\"true\" style={{ transform: r.rule }} />\n        ) : null}\n      </span>\n    </button>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12",
		"@hugeicons/core-free-icons@^4.3.3"
	]
}