{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "ThoughtLine-TS-CSS",
	"title": "ThoughtLine",
	"description": "Reasoning-trace header: a glyph and a label breathe beside a live clock while steps appear beneath, then the line settles on one beat into \"Thought for 4.2s\" through a blur crossfade and the trace folds into it.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "ThoughtLine.css",
			"target": "@components/ThoughtLine.css",
			"content": ".thought-line {\n  --tl-ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n  --tl-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n  --tl-font: 16px;\n  --tl-color: currentColor;\n  --tl-glyph: currentColor;\n  --tl-settle: 350ms;\n  --tl-blur: 2px;\n  --tl-shimmer: 1.8s;\n  --tl-done: 0.75;\n  --tl-timer: 0.55;\n\n  display: inline-flex;\n  flex-direction: column;\n  align-items: flex-start;\n  color: var(--tl-color);\n  font-family: inherit;\n  font-size: var(--tl-font);\n  font-weight: 500;\n  line-height: 1.2;\n}\n\n.thought-line__head {\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  gap: 0.3em;\n  margin: 0;\n  padding: 0;\n  border: 0;\n  background: none;\n  color: inherit;\n  font: inherit;\n  text-align: left;\n  white-space: nowrap;\n  outline: none;\n  cursor: default;\n}\n\n.thought-line__head[data-toggle] {\n  cursor: pointer;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.thought-line__glyph {\n  display: inline-flex;\n  flex: none;\n  width: 1.1em;\n  height: 1.1em;\n  margin-right: 0.2em;\n  color: var(--tl-glyph);\n}\n\n.thought-line__glyph svg {\n  display: block;\n  width: 100%;\n  height: 100%;\n}\n\n.thought-line__dot {\n  width: 0.5em;\n  height: 0.5em;\n  margin: auto;\n  border-radius: 50%;\n  background: currentColor;\n}\n\n.thought-line__label {\n  display: inline-grid;\n}\n\n.thought-line__text {\n  grid-area: 1 / 1;\n  width: max-content;\n  opacity: 0;\n  filter: blur(var(--tl-blur));\n  transition:\n    opacity var(--tl-settle) var(--tl-ease-out),\n    filter var(--tl-settle) var(--tl-ease-out);\n}\n\n.thought-line__text[data-active] {\n  opacity: 1;\n  filter: blur(0);\n}\n\n.thought-line__text--done[data-active] {\n  opacity: var(--tl-done);\n}\n\n.thought-line__breath {\n  display: inline-block;\n}\n\n.thought-line[data-working] .thought-line__breath[data-shimmer] {\n  background: linear-gradient(\n    100deg,\n    color-mix(in srgb, var(--tl-color) 50%, transparent) 30%,\n    var(--tl-color) 50%,\n    color-mix(in srgb, var(--tl-color) 50%, transparent) 70%\n  );\n  background-size: 250% 100%;\n  background-position: 125% 0;\n  -webkit-background-clip: text;\n  background-clip: text;\n  color: transparent;\n  -webkit-text-fill-color: transparent;\n  animation: thought-line-shimmer var(--tl-shimmer) linear infinite;\n}\n\n@keyframes thought-line-shimmer {\n  to {\n    background-position: -125% 0;\n  }\n}\n\n.thought-line__timer {\n  font-variant-numeric: tabular-nums;\n  opacity: var(--tl-timer);\n  transition:\n    opacity var(--tl-settle) ease,\n    transform var(--tl-settle) var(--tl-ease-in-out);\n}\n\n.thought-line__timer[data-done] {\n  opacity: var(--tl-done);\n}\n\n.thought-line__chevron {\n  display: inline-flex;\n  margin-left: 0.1em;\n  opacity: 0;\n  transition:\n    opacity 200ms ease,\n    transform 200ms var(--tl-ease-out);\n}\n\n.thought-line__chevron[data-on] {\n  opacity: 0.55;\n}\n\n.thought-line[data-open] .thought-line__chevron {\n  transform: rotate(180deg);\n}\n\n.thought-line__sr {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  overflow: hidden;\n  clip: rect(0 0 0 0);\n  white-space: nowrap;\n}\n\n.thought-line__trace {\n  display: grid;\n  grid-template-rows: 0fr;\n  width: 0;\n  min-width: 100%;\n  transition: grid-template-rows var(--tl-settle) var(--tl-ease-out);\n}\n\n.thought-line__trace[data-open] {\n  grid-template-rows: 1fr;\n}\n\n.thought-line__fold {\n  min-height: 0;\n  overflow-x: visible;\n  overflow-y: clip;\n}\n\n.thought-line__steps {\n  display: flex;\n  flex-direction: column;\n  gap: 0.45em;\n  width: max-content;\n  padding: 0.6em 0 0.2em 1.6em;\n  font-size: 0.875em;\n  font-weight: 400;\n  white-space: nowrap;\n}\n\n.thought-line__step {\n  display: flex;\n  align-items: center;\n  gap: 0.5em;\n  opacity: 1;\n  transform: translateY(0);\n  transition:\n    opacity 200ms var(--tl-ease-out),\n    transform 200ms var(--tl-ease-out);\n}\n\n@starting-style {\n  .thought-line__step {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n}\n\n.thought-line__mark {\n  display: inline-grid;\n  flex: none;\n  place-items: center;\n  width: 1em;\n  height: 1em;\n  opacity: 0.55;\n}\n\n.thought-line__pulse {\n  display: block;\n  width: 0.4em;\n  height: 0.4em;\n  border-radius: 50%;\n  background: currentColor;\n  animation: thought-line-pulse 1.6s var(--tl-ease-in-out) infinite;\n}\n\n@keyframes thought-line-pulse {\n  0%,\n  100% {\n    opacity: 0.55;\n  }\n\n  50% {\n    opacity: 1;\n  }\n}\n\n.thought-line__step-text {\n  transition: opacity var(--tl-settle) ease;\n}\n\n.thought-line__step[data-done] .thought-line__step-text {\n  opacity: 0.55;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .thought-line__text {\n    filter: none !important;\n    transition: opacity var(--tl-settle) ease;\n  }\n\n  .thought-line__timer {\n    transition: opacity var(--tl-settle) ease;\n  }\n\n  .thought-line__step {\n    transform: none !important;\n    transition: opacity 200ms ease;\n  }\n\n  .thought-line__pulse {\n    animation-duration: 2.4s;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "ThoughtLine.tsx",
			"content": "'use client';\n\nimport React, { useEffect, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';\nimport { animate, useReducedMotion, type AnimationPlaybackControls } from 'motion/react';\nimport { HugeiconsIcon } from '@hugeicons/react';\nimport { ArrowDown01Icon, SparklesIcon, Tick02Icon } from '@hugeicons/core-free-icons';\nimport './ThoughtLine.css';\n\nexport type ThoughtLineGlyph = 'sparkle' | 'dot' | 'none' | ReactNode;\n\nexport interface ThoughtLineProps {\n  label?: string;\n  doneLabel?: string;\n  renderLabel?: (text: string, working: boolean) => ReactNode;\n  glyph?: ThoughtLineGlyph;\n  steps?: string[];\n  collapsible?: boolean;\n  collapseOnSettle?: boolean;\n  color?: string;\n  glyphColor?: string;\n  fontSize?: number;\n  breathPeriod?: number;\n  breathDepth?: number;\n  shimmer?: boolean;\n  shimmerDuration?: number;\n  settleDuration?: number;\n  settleBlur?: number;\n  working?: boolean;\n  settleAfter?: number;\n  elapsed?: number;\n  showTimer?: boolean;\n  onSettle?: (seconds: number) => void;\n  className?: string;\n  style?: CSSProperties;\n}\n\nconst EASE_OUT: [number, number, number, number] = [0.23, 1, 0.32, 1];\nconst EASE_IN_OUT: [number, number, number, number] = [0.77, 0, 0.175, 1];\nconst GLYPH_DONE = 0.55;\nconst EMPTY_STEPS: string[] = [];\n\nconst fmt = (ds: number) =>\n  ds < 600 ? `${(ds / 10).toFixed(1)}s` : `${Math.floor(ds / 600)}m ${((ds % 600) / 10).toFixed(1)}s`;\nconst spoken = (ds: number) =>\n  ds < 600\n    ? `${(ds / 10).toFixed(1)} seconds`\n    : `${Math.floor(ds / 600)} minutes ${((ds % 600) / 10).toFixed(1)} seconds`;\n\nconst ThoughtLine: React.FC<ThoughtLineProps> = ({\n  label = 'Thinking…',\n  doneLabel = '',\n  renderLabel,\n  glyph = 'sparkle',\n  steps = EMPTY_STEPS,\n  collapsible = true,\n  collapseOnSettle = true,\n  color = 'currentColor',\n  glyphColor = '',\n  fontSize = 16,\n  breathPeriod = 1.6,\n  breathDepth = 0.45,\n  shimmer = true,\n  shimmerDuration = 1.8,\n  settleDuration = 350,\n  settleBlur = 2,\n  working = true,\n  settleAfter = 0,\n  elapsed,\n  showTimer = true,\n  onSettle,\n  className = '',\n  style\n}) => {\n  const reduce = useReducedMotion();\n  const [autoSettled, setAutoSettled] = useState(false);\n  const [open, setOpen] = useState(true);\n  const isWorking = working && !autoSettled;\n  const doneText = doneLabel || (showTimer ? 'Thought for' : 'Done thinking');\n  const hasTrace = steps.length > 0;\n  const depth = reduce ? Math.min(breathDepth, 0.2) : breathDepth;\n  const period = reduce ? breathPeriod * 1.5 : breathPeriod;\n  const trough = 1 - depth;\n  const sheen = shimmer && !reduce;\n\n  const glyphRef = useRef<HTMLSpanElement>(null);\n  const breathRef = useRef<HTMLSpanElement>(null);\n  const timerRef = useRef<HTMLSpanElement>(null);\n  const stackRef = useRef<HTMLSpanElement>(null);\n  const workRef = useRef<HTMLSpanElement>(null);\n  const doneRef = useRef<HTMLSpanElement>(null);\n  const dsRef = useRef(0);\n  const prevWorking = useRef(isWorking);\n  const latest = useRef<{ onSettle?: (seconds: number) => void }>({});\n  latest.current = { onSettle };\n  const [announce, setAnnounce] = useState(label);\n\n  useEffect(() => {\n    if (working) setAutoSettled(false);\n  }, [working]);\n  useEffect(() => {\n    if (isWorking) setOpen(true);\n    else if (collapseOnSettle) setOpen(false);\n  }, [isWorking, collapseOnSettle]);\n\n  useEffect(() => {\n    const glyphEl = glyphRef.current;\n    const breathEl = breathRef.current;\n    if (!breathEl) return undefined;\n    const s = settleDuration / 1000;\n    const loop = (el: HTMLElement, delay: number) =>\n      animate(el, { opacity: [trough, 1, trough] }, { duration: period, ease: EASE_IN_OUT, repeat: Infinity, delay });\n    let cancelled = false;\n    const running: AnimationPlaybackControls[] = [];\n    if (isWorking) {\n      if (depth > 0) {\n        if (sheen) running.push(animate(breathEl, { opacity: 1 }, { duration: 0.2, ease: EASE_OUT }));\n        if (glyphEl) {\n          const lead = animate(glyphEl, { opacity: trough }, { duration: 0.2, ease: EASE_OUT });\n          running.push(lead);\n          lead.then(() => {\n            if (cancelled) return;\n            running.push(loop(glyphEl, 0));\n            if (!sheen) running.push(loop(breathEl, 0.14));\n          });\n        } else if (!sheen) {\n          running.push(loop(breathEl, 0.14));\n        }\n      } else {\n        if (glyphEl) running.push(animate(glyphEl, { opacity: 1 }, { duration: 0.2, ease: EASE_OUT }));\n        running.push(animate(breathEl, { opacity: 1 }, { duration: 0.2, ease: EASE_OUT }));\n      }\n    } else {\n      if (glyphEl) running.push(animate(glyphEl, { opacity: GLYPH_DONE }, { duration: s, ease: EASE_OUT }));\n      running.push(animate(breathEl, { opacity: 1 }, { duration: s, ease: EASE_OUT }));\n    }\n    return () => {\n      cancelled = true;\n      running.forEach(a => a.stop());\n    };\n  }, [isWorking, period, depth, trough, settleDuration, glyph, sheen]);\n\n  const paint = (ds: number) => {\n    dsRef.current = ds;\n    if (timerRef.current) timerRef.current.textContent = fmt(ds);\n  };\n  useLayoutEffect(() => {\n    if (elapsed != null) {\n      paint(Math.round(elapsed * 10));\n      return undefined;\n    }\n    if (!isWorking) return undefined;\n    const startedAt = performance.now();\n    paint(0);\n    const id = setInterval(() => {\n      const ds = Math.floor((performance.now() - startedAt) / 100);\n      paint(ds);\n      if (settleAfter > 0 && ds >= Math.round(settleAfter * 10)) setAutoSettled(true);\n    }, 100);\n    return () => clearInterval(id);\n  }, [isWorking, elapsed, settleAfter]);\n\n  useLayoutEffect(() => {\n    const t = timerRef.current;\n    const stack = stackRef.current;\n    if (!t || !stack) return undefined;\n    const place = (glide: boolean) => {\n      const active = isWorking ? workRef.current : doneRef.current;\n      if (!active) return;\n      const shift = active.offsetWidth - stack.offsetWidth;\n      if (!glide) t.style.transition = 'none';\n      t.style.transform = `translateX(${shift}px)`;\n      if (!glide) {\n        void t.offsetWidth;\n        t.style.transition = '';\n      }\n    };\n    place(prevWorking.current !== isWorking);\n    prevWorking.current = isWorking;\n    const ro = new ResizeObserver(() => place(false));\n    if (workRef.current) ro.observe(workRef.current);\n    if (doneRef.current) ro.observe(doneRef.current);\n    return () => ro.disconnect();\n  }, [isWorking, label, doneText, fontSize, showTimer]);\n\n  useEffect(() => {\n    if (isWorking) {\n      setAnnounce(label);\n      return;\n    }\n    setAnnounce(showTimer ? `${doneText} ${spoken(dsRef.current)}` : doneText);\n    latest.current.onSettle?.(dsRef.current / 10);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [isWorking]);\n\n  const toggle = hasTrace && collapsible;\n  const head = (\n    <>\n      {glyph !== 'none' ? (\n        <span ref={glyphRef} className=\"thought-line__glyph\" aria-hidden=\"true\">\n          {glyph === 'sparkle' ? (\n            <HugeiconsIcon icon={SparklesIcon} size=\"100%\" strokeWidth={2} />\n          ) : glyph === 'dot' ? (\n            <span className=\"thought-line__dot\" />\n          ) : (\n            glyph\n          )}\n        </span>\n      ) : null}\n      <span ref={stackRef} className=\"thought-line__label\" aria-hidden=\"true\">\n        <span ref={workRef} className=\"thought-line__text\" data-active={isWorking ? '' : undefined}>\n          <span ref={breathRef} className=\"thought-line__breath\" data-shimmer={sheen ? '' : undefined}>\n            {renderLabel ? renderLabel(label, true) : label}\n          </span>\n        </span>\n        <span\n          ref={doneRef}\n          className=\"thought-line__text thought-line__text--done\"\n          data-active={isWorking ? undefined : ''}\n        >\n          {renderLabel ? renderLabel(doneText, false) : doneText}\n        </span>\n      </span>\n      {showTimer ? (\n        <span ref={timerRef} className=\"thought-line__timer\" data-done={isWorking ? undefined : ''} aria-hidden=\"true\">\n          0.0s\n        </span>\n      ) : null}\n      {collapsible ? (\n        <span className=\"thought-line__chevron\" data-on={hasTrace ? '' : undefined} aria-hidden=\"true\">\n          <HugeiconsIcon icon={ArrowDown01Icon} size=\"1em\" strokeWidth={2.2} />\n        </span>\n      ) : null}\n      <span className=\"thought-line__sr\" role=\"status\">\n        {announce}\n      </span>\n    </>\n  );\n\n  return (\n    <div\n      className={`thought-line${className ? ` ${className}` : ''}`}\n      data-working={isWorking ? '' : undefined}\n      data-open={open && hasTrace ? '' : undefined}\n      style={\n        {\n          '--tl-font': `${fontSize}px`,\n          '--tl-color': color,\n          '--tl-glyph': glyphColor || color,\n          '--tl-settle': `${settleDuration}ms`,\n          '--tl-blur': `${settleBlur}px`,\n          '--tl-shimmer': `${shimmerDuration}s`,\n          ...style\n        } as CSSProperties\n      }\n    >\n      {collapsible ? (\n        <button\n          type=\"button\"\n          className=\"thought-line__head\"\n          data-toggle={toggle ? '' : undefined}\n          aria-expanded={toggle ? open : undefined}\n          tabIndex={toggle ? 0 : -1}\n          onClick={() => {\n            if (toggle) setOpen(v => !v);\n          }}\n        >\n          {head}\n        </button>\n      ) : (\n        <div className=\"thought-line__head\">{head}</div>\n      )}\n      {hasTrace ? (\n        <div className=\"thought-line__trace\" data-open={open ? '' : undefined} aria-hidden={!open}>\n          <div className=\"thought-line__fold\">\n            <div className=\"thought-line__steps\">\n              {steps.map((text, i) => {\n                const done = !isWorking || i < steps.length - 1;\n                return (\n                  <div key={`${i}-${text}`} className=\"thought-line__step\" data-done={done ? '' : undefined}>\n                    <span className=\"thought-line__mark\" aria-hidden=\"true\">\n                      {done ? (\n                        <HugeiconsIcon icon={Tick02Icon} size=\"1em\" strokeWidth={2.5} />\n                      ) : (\n                        <i className=\"thought-line__pulse\" />\n                      )}\n                    </span>\n                    <span className=\"thought-line__step-text\">{text}</span>\n                  </div>\n                );\n              })}\n            </div>\n          </div>\n        </div>\n      ) : null}\n    </div>\n  );\n};\n\nexport default ThoughtLine;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12",
		"@hugeicons/react@^1.1.10",
		"@hugeicons/core-free-icons@^4.3.3"
	]
}