{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "StatusMark-JS-TW",
	"title": "StatusMark",
	"description": "A 20px status glyph for agent task lists that morphs in place from a dashed idle ring to a spinning or real-progress arc, then draws a check or a cross, with an optional label strike.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "StatusMark/StatusMark.jsx",
			"content": "'use client';\n\nimport { useEffect, useLayoutEffect, useRef } from 'react';\nimport { animate, useMotionValue, useReducedMotion } from 'motion/react';\n\nconst UI = { type: 'spring', duration: 0.3, bounce: 0 };\nconst MORPH = { duration: 0.3, ease: [0.77, 0, 0.175, 1] };\nconst CHECK = 'M7.5 12.25 10.5 15.25 16.75 8.75';\nconst CROSS = 'M8.5 8.5 15.5 15.5M15.5 8.5 8.5 15.5';\nconst TEXT = {\n  pending: 'Pending',\n  running: 'In progress',\n  done: 'Completed',\n  failed: 'Failed',\n  cancelled: 'Cancelled'\n};\nconst IDLE_DASH = 0.3;\nconst STYLE = '@keyframes sm-breathe{50%{opacity:.45}}';\n\nconst clamp01 = v => Math.min(1, Math.max(0, v));\n\nexport default function StatusMark({\n  status = 'pending',\n  progress,\n  label,\n  color = 'currentColor',\n  doneColor = '#22c55e',\n  errorColor = '#ef4444',\n  size = 20,\n  strokeWidth = 2,\n  dashes = 8,\n  fontSize = 14,\n  spinDuration = 1100,\n  arcLength = 0.68,\n  drawDuration = 240,\n  fillOpacity = 0.06,\n  strike = true,\n  strikeDelay = 60,\n  className = '',\n  style\n}) {\n  const reduce = useReducedMotion();\n  const r = 10 - strokeWidth / 2;\n  const C = 2 * Math.PI * r;\n  const P = C / Math.max(1, dashes);\n  const determinate = status === 'running' && Number.isFinite(progress);\n  const indeterminate = status === 'running' && !determinate;\n  const solid = status === 'running' || status === 'done' || status === 'failed';\n  const targetArc = indeterminate ? arcLength : determinate ? clamp01(progress) : 1;\n\n  const mode = useMotionValue(solid ? 1 : 0);\n  const arc = useMotionValue(targetArc);\n  const travel = useMotionValue(0);\n  const ringRef = useRef(null);\n  const geo = useRef({ C, P });\n  geo.current = { C, P };\n  const gen = useRef(0);\n\n  const writeDash = () => {\n    const g = geo.current;\n    const m = mode.get();\n    const a = arc.get();\n    const dash = IDLE_DASH * g.P + (a * g.C - IDLE_DASH * g.P) * m;\n    const gap = (1 - IDLE_DASH) * g.P + ((1 - a) * g.C - (1 - IDLE_DASH) * g.P) * m;\n    ringRef.current?.setAttribute('stroke-dasharray', `${Math.max(0, dash)} ${Math.max(0, gap)}`);\n  };\n  useLayoutEffect(() => {\n    writeDash();\n    ringRef.current?.setAttribute('stroke-dashoffset', String(travel.get()));\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [C, P]);\n  useEffect(() => {\n    const offs = [\n      mode.on('change', writeDash),\n      arc.on('change', writeDash),\n      travel.on('change', v => ringRef.current?.setAttribute('stroke-dashoffset', String(v)))\n    ];\n    return () => {\n      offs.forEach(off => off());\n      mode.stop();\n      arc.stop();\n      travel.stop();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    const g = ++gen.current;\n    if (reduce) {\n      mode.jump(solid ? 1 : 0);\n      arc.jump(targetArc);\n      travel.jump(0);\n      return;\n    }\n    if (mode.get() === 0) arc.jump(targetArc);\n    animate(mode, solid ? 1 : 0, MORPH);\n    animate(arc, targetArc, UI);\n    if (indeterminate) {\n      const t0 = travel.get();\n      animate(travel, [t0, t0 - C], { duration: spinDuration / 1000, ease: 'linear', repeat: Infinity });\n      return;\n    }\n    const unit = determinate ? C : P;\n    const to = Math.floor(travel.get() / unit) * unit;\n    animate(travel, to, UI).then(() => {\n      if (gen.current === g) travel.jump(0);\n    });\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [status, determinate, targetArc, reduce, C, P, spinDuration]);\n\n  const spoken = TEXT[status] + (determinate ? `, ${Math.round(clamp01(progress) * 100)}%` : '');\n  const hasLabel = label !== undefined && label !== null;\n\n  return (\n    <span\n      className={`group relative inline-flex items-center align-middle leading-none [gap:calc(var(--sm-size)*0.5)]${className ? ` ${className}` : ''}`}\n      data-status={status}\n      data-indeterminate={indeterminate ? '' : undefined}\n      data-strike={strike ? '' : undefined}\n      style={{\n        '--sm-size': `${size}px`,\n        '--sm-stroke': strokeWidth,\n        '--sm-color': color,\n        '--sm-done': doneColor,\n        '--sm-error': errorColor,\n        '--sm-fill': fillOpacity,\n        '--sm-font': `${fontSize}px`,\n        '--sm-draw': `${drawDuration}ms`,\n        '--sm-strike-delay': `${120 + strikeDelay}ms`,\n        '--sm-check-delay': '120ms',\n        ...style\n      }}\n    >\n      <style>{STYLE}</style>\n      <svg\n        className=\"shrink-0 overflow-visible [color:var(--sm-color)] [transition:color_200ms_ease] group-data-[status=done]:[color:var(--sm-done)] group-data-[status=failed]:[color:var(--sm-error)]\"\n        viewBox=\"0 0 24 24\"\n        width={size}\n        height={size}\n        role={hasLabel ? undefined : 'img'}\n        aria-label={hasLabel ? undefined : spoken}\n        aria-hidden={hasLabel || undefined}\n      >\n        <circle\n          className=\"[fill:currentColor] [stroke:currentColor] [stroke-width:var(--sm-stroke)] [fill-opacity:0] [stroke-opacity:0] [transition:fill-opacity_180ms_ease,stroke-opacity_200ms_ease] group-data-[status=running]:[stroke-opacity:0.2] group-data-[status=done]:[fill-opacity:var(--sm-fill)] group-data-[status=failed]:[fill-opacity:var(--sm-fill)]\"\n          cx=\"12\"\n          cy=\"12\"\n          r={r}\n          transform=\"rotate(-90 12 12)\"\n        />\n        <circle\n          ref={ringRef}\n          className=\"fill-none [stroke:currentColor] [stroke-width:var(--sm-stroke)] [stroke-linecap:round] opacity-[0.55] [transition:opacity_200ms_ease] group-data-[status=running]:opacity-100 group-data-[status=done]:opacity-100 group-data-[status=failed]:opacity-100 motion-reduce:group-data-[indeterminate]:animate-[sm-breathe_1400ms_cubic-bezier(0.77,0,0.175,1)_infinite]\"\n          cx=\"12\"\n          cy=\"12\"\n          r={r}\n          transform=\"rotate(-90 12 12)\"\n        />\n        <path\n          className=\"fill-none [stroke:currentColor] [stroke-width:var(--sm-stroke)] [stroke-linecap:round] [stroke-linejoin:round] [stroke-dasharray:1_2] [stroke-dashoffset:1.05] opacity-0 [transition:stroke-dashoffset_160ms_cubic-bezier(0.23,1,0.32,1),opacity_0ms_linear_160ms] group-data-[status=done]:[stroke-dashoffset:0] group-data-[status=done]:opacity-100 group-data-[status=done]:[transition:stroke-dashoffset_var(--sm-draw)_cubic-bezier(0.23,1,0.32,1)_var(--sm-check-delay),opacity_0ms_linear_var(--sm-check-delay)] motion-reduce:[stroke-dashoffset:0] motion-reduce:[transition:opacity_200ms_ease] motion-reduce:group-data-[status=done]:[transition:opacity_200ms_ease]\"\n          d={CHECK}\n          pathLength=\"1\"\n        />\n        <path\n          className=\"fill-none [stroke:currentColor] [stroke-width:var(--sm-stroke)] [stroke-linecap:round] [stroke-linejoin:round] [stroke-dasharray:1_2] [stroke-dashoffset:1.05] opacity-0 [transition:stroke-dashoffset_160ms_cubic-bezier(0.23,1,0.32,1),opacity_0ms_linear_160ms] group-data-[status=failed]:[stroke-dashoffset:0] group-data-[status=failed]:opacity-100 group-data-[status=failed]:[transition:stroke-dashoffset_var(--sm-draw)_cubic-bezier(0.23,1,0.32,1)_var(--sm-check-delay),opacity_0ms_linear_var(--sm-check-delay)] group-data-[status=cancelled]:[stroke-dashoffset:0] group-data-[status=cancelled]:opacity-100 group-data-[status=cancelled]:[transition:stroke-dashoffset_var(--sm-draw)_cubic-bezier(0.23,1,0.32,1)_var(--sm-check-delay),opacity_0ms_linear_var(--sm-check-delay)] motion-reduce:[stroke-dashoffset:0] motion-reduce:[transition:opacity_200ms_ease] motion-reduce:group-data-[status=failed]:[transition:opacity_200ms_ease] motion-reduce:group-data-[status=cancelled]:[transition:opacity_200ms_ease]\"\n          d={CROSS}\n          pathLength=\"1\"\n        />\n      </svg>\n      {hasLabel ? <span className=\"sr-only\">{spoken}: </span> : null}\n      {hasLabel ? (\n        <span className=\"relative leading-[1.25] opacity-[0.65] [color:var(--sm-color)] [font-size:var(--sm-font)] [transition:opacity_200ms_ease] group-data-[status=running]:opacity-100 group-data-[status=done]:opacity-60 group-data-[status=failed]:opacity-100 group-data-[status=cancelled]:opacity-[0.55]\">\n          {label}\n          <span\n            className=\"pointer-events-none absolute inset-x-0 top-1/2 origin-left scale-x-0 bg-current [translate:0_-50%] [height:max(1px,calc(var(--sm-font)/14))] [transition:transform_160ms_cubic-bezier(0.23,1,0.32,1)] group-data-[status=done]:group-data-[strike]:scale-x-100 group-data-[status=done]:group-data-[strike]:[transition:transform_280ms_cubic-bezier(0.23,1,0.32,1)_var(--sm-strike-delay)] motion-reduce:scale-x-100! motion-reduce:opacity-0 motion-reduce:[transition:opacity_200ms_ease] motion-reduce:group-data-[status=done]:group-data-[strike]:opacity-100 motion-reduce:group-data-[status=done]:group-data-[strike]:[transition:opacity_200ms_ease]\"\n            aria-hidden=\"true\"\n          />\n        </span>\n      ) : null}\n    </span>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}