{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "LatticeLoader-TS-CSS",
	"title": "LatticeLoader",
	"description": "Inline agent-status row: a 3x3 or 4x4 lattice whose cells brighten in a phase-offset wave beside a verb and a live stopwatch, resolving into a check or a cross when the task ends.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "LatticeLoader.css",
			"target": "@components/LatticeLoader.css",
			"content": ".lattice-loader {\n  --ll-n: 3;\n  --ll-cell: 6px;\n  --ll-gap: 2px;\n  --ll-font: 14px;\n  --ll-color: currentColor;\n  --ll-mark: #22c55e;\n  --ll-idle: 0.15;\n  --ll-glow: currentColor;\n  --ll-mark-glow: #22c55e;\n  --ll-peak: 1;\n  --ll-cycle: 864ms;\n  --ll-ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n  --ll-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  gap: calc(var(--ll-font) * 0.625);\n  font-family: inherit;\n  font-size: var(--ll-font);\n  line-height: 1;\n}\n\n.lattice-loader__grid {\n  display: grid;\n  flex: none;\n}\n\n.lattice-loader__layer {\n  grid-area: 1 / 1;\n  display: grid;\n  grid-template-columns: repeat(var(--ll-n), var(--ll-cell));\n  gap: var(--ll-gap);\n}\n\n.lattice-loader__cell {\n  width: var(--ll-cell);\n  height: var(--ll-cell);\n  border-radius: max(1px, calc(var(--ll-cell) * 0.25));\n  background: var(--ll-color);\n  opacity: var(--ll-idle);\n}\n\n.lattice-loader[data-shape='round'] .lattice-loader__cell {\n  border-radius: 50%;\n}\n\n.lattice-loader[data-glow] .lattice-loader__run .lattice-loader__cell:not([data-hole]) {\n  box-shadow: 0 0 calc(var(--ll-cell) * 1.2) calc(var(--ll-cell) * 0.12) var(--ll-glow);\n}\n\n.lattice-loader[data-glow] .lattice-loader__mark .lattice-loader__cell[data-on] {\n  box-shadow: 0 0 calc(var(--ll-cell) * 1.2) calc(var(--ll-cell) * 0.12) var(--ll-mark-glow);\n}\n\n.lattice-loader__run {\n  transition: opacity 200ms ease;\n}\n\n.lattice-loader__run .lattice-loader__cell {\n  animation: lattice-on var(--ll-cycle) var(--ll-ease-in-out) infinite;\n}\n\n.lattice-loader__run .lattice-loader__cell[data-lit='45'] {\n  animation-name: lattice-on-45;\n}\n\n.lattice-loader__run .lattice-loader__cell[data-lit='35'] {\n  animation-name: lattice-on-35;\n}\n\n.lattice-loader__run .lattice-loader__cell[data-lit='25'] {\n  animation-name: lattice-on-25;\n}\n\n.lattice-loader__run .lattice-loader__cell[data-hole] {\n  animation: none;\n  opacity: calc(var(--ll-idle) * 0.47);\n}\n\n.lattice-loader__mark {\n  opacity: 0;\n  transform: scale(0.9);\n  transform-origin: center;\n  transition:\n    opacity 160ms var(--ll-ease-out),\n    transform 160ms var(--ll-ease-out);\n}\n\n.lattice-loader__mark .lattice-loader__cell {\n  transition:\n    opacity 200ms ease,\n    background-color 200ms ease;\n}\n\n.lattice-loader__mark .lattice-loader__cell[data-on] {\n  background: var(--ll-mark);\n  opacity: var(--ll-peak);\n}\n\n.lattice-loader:not([data-status='working']) .lattice-loader__run {\n  opacity: 0;\n}\n\n.lattice-loader:not([data-status='working']) .lattice-loader__run .lattice-loader__cell {\n  animation-play-state: paused;\n}\n\n.lattice-loader:not([data-status='working']) .lattice-loader__mark {\n  opacity: 1;\n  transform: none;\n  transition:\n    opacity 200ms ease,\n    transform 200ms var(--ll-ease-out);\n}\n\n.lattice-loader__label {\n  position: relative;\n  display: inline-block;\n  font-weight: 500;\n}\n\n.lattice-loader__text {\n  position: absolute;\n  top: 0;\n  left: 0;\n  white-space: nowrap;\n  opacity: 0;\n  filter: blur(2px);\n  transition:\n    opacity 200ms ease,\n    filter 200ms ease;\n}\n\n.lattice-loader__text[data-active] {\n  position: static;\n  opacity: 1;\n  filter: blur(0);\n}\n\n.lattice-loader__timer {\n  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n  font-size: calc(var(--ll-font) * 0.875);\n  font-variant-numeric: tabular-nums;\n  opacity: 0.6;\n}\n\n.lattice-loader__sr {\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@keyframes lattice-on {\n  0%,\n  100% {\n    opacity: var(--ll-idle);\n  }\n\n  18%,\n  42% {\n    opacity: var(--ll-peak);\n  }\n\n  62% {\n    opacity: var(--ll-idle);\n  }\n}\n\n@keyframes lattice-on-45 {\n  0%,\n  100% {\n    opacity: var(--ll-idle);\n  }\n\n  13%,\n  31% {\n    opacity: var(--ll-peak);\n  }\n\n  45% {\n    opacity: var(--ll-idle);\n  }\n}\n\n@keyframes lattice-on-35 {\n  0%,\n  100% {\n    opacity: var(--ll-idle);\n  }\n\n  10%,\n  24% {\n    opacity: var(--ll-peak);\n  }\n\n  35% {\n    opacity: var(--ll-idle);\n  }\n}\n\n@keyframes lattice-on-25 {\n  0%,\n  100% {\n    opacity: var(--ll-idle);\n  }\n\n  7%,\n  17% {\n    opacity: var(--ll-peak);\n  }\n\n  25% {\n    opacity: var(--ll-idle);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .lattice-loader__run {\n    --ll-peak: 0.7;\n  }\n\n  .lattice-loader__run .lattice-loader__cell {\n    animation-delay: 0ms !important;\n    animation-duration: 1400ms !important;\n  }\n\n  .lattice-loader .lattice-loader__mark {\n    transform: none;\n  }\n\n  .lattice-loader .lattice-loader__text {\n    filter: none;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "LatticeLoader.tsx",
			"content": "'use client';\n\nimport React, { useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react';\n\nimport './LatticeLoader.css';\n\nexport type LatticeStatus = 'working' | 'done' | 'error';\nexport type LatticePatternName =\n  | 'arrow'\n  | 'dots'\n  | 'orbit'\n  | 'ripple'\n  | 'snake'\n  | 'spiral'\n  | 'sweep'\n  | 'spin'\n  | 'rain'\n  | 'pulse';\nexport type LatticeGrid = 3 | 4;\n\nexport interface LatticePattern {\n  cells: (number | null)[];\n  loop?: number;\n  scale?: number;\n  lit?: 0.25 | 0.35 | 0.45 | 0.62;\n}\n\nexport interface LatticeLoaderProps {\n  label?: string;\n  doneLabel?: string;\n  errorLabel?: string;\n  status?: LatticeStatus;\n  pattern?: LatticePatternName | LatticePattern;\n  grid?: LatticeGrid;\n  shape?: 'square' | 'round';\n  color?: string;\n  doneColor?: string;\n  errorColor?: string;\n  cellSize?: number;\n  gap?: number;\n  fontSize?: number;\n  step?: number;\n  idleOpacity?: number;\n  glow?: boolean;\n  glowColor?: string;\n  showTimer?: boolean;\n  elapsed?: number;\n  className?: string;\n  style?: CSSProperties;\n}\n\ntype ResolvedPattern = { cells: (number | null)[]; loop: number; scale: number; lit?: number };\n\nconst PATTERNS: Record<LatticePatternName, Partial<Record<LatticeGrid, ResolvedPattern>>> = {\n  arrow: { 3: { cells: [1, 2, 3, 0, 1, 2, 1, 2, 3], loop: 7.2, scale: 1 } },\n  dots: { 3: { cells: [0, 1, 2, 0, 1, 2, 0, 1, 2], loop: 3, scale: 2.4 } },\n  ripple: { 3: { cells: [2, 1, 2, 1, 0, 1, 2, 1, 2], loop: 4.8, scale: 1.5 } },\n  spiral: { 3: { cells: [0, 1, 2, 7, 8, 3, 6, 5, 4], loop: 9, scale: 1.2, lit: 0.35 } },\n  orbit: {\n    3: { cells: [0, 1, 2, 7, null, 3, 6, 5, 4], loop: 8, scale: 1.2 },\n    4: { cells: [0, 1, 2, 3, 11, null, null, 4, 10, null, null, 5, 9, 8, 7, 6], loop: 6, scale: 1.2, lit: 0.45 }\n  },\n  snake: {\n    3: { cells: [0, 1, 2, 5, 4, 3, 6, 7, 8], loop: 9, scale: 1, lit: 0.35 },\n    4: { cells: [0, 1, 2, 3, 7, 6, 5, 4, 8, 9, 10, 11, 15, 14, 13, 12], loop: 16, scale: 1, lit: 0.25 }\n  },\n  sweep: { 4: { cells: [0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6], loop: 5, scale: 1, lit: 0.45 } },\n  spin: { 4: { cells: [0, 0, 1, 1, 0, 0, 1, 1, 3, 3, 2, 2, 3, 3, 2, 2], loop: 4, scale: 1.6, lit: 0.35 } },\n  rain: { 4: { cells: [0, 2, 1, 3, 1, 3, 2, 4, 2, 4, 3, 5, 3, 5, 4, 6], loop: 4, scale: 1.2, lit: 0.35 } },\n  pulse: { 4: { cells: [2, 1, 1, 2, 1, 0, 0, 1, 1, 0, 0, 1, 2, 1, 1, 2], loop: 2.4, scale: 2.5, lit: 0.45 } }\n};\nconst DEFAULT_PATTERN: Record<LatticeGrid, LatticePatternName> = { 3: 'orbit', 4: 'sweep' };\nconst MARKS: Record<LatticeGrid, Record<'done' | 'error', number[]>> = {\n  3: { done: [2, 3, 5, 7], error: [0, 2, 4, 6, 8] },\n  4: { done: [7, 8, 10, 13], error: [0, 3, 5, 6, 9, 10, 12, 15] }\n};\n\nconst resolvePattern = (pattern: LatticePatternName | LatticePattern, grid: LatticeGrid): ResolvedPattern => {\n  if (typeof pattern === 'string') {\n    const named = PATTERNS[pattern];\n    return (named && named[grid]) || (PATTERNS[DEFAULT_PATTERN[grid]][grid] as ResolvedPattern);\n  }\n  const cells = Array.from({ length: grid * grid }, (_, i) => pattern.cells[i] ?? null);\n  const max = Math.max(0, ...cells.filter(v => v != null));\n  return { cells, loop: pattern.loop ?? max + 4.2, scale: pattern.scale ?? 1, lit: pattern.lit ?? 0.62 };\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 LatticeLoader: React.FC<LatticeLoaderProps> = ({\n  label = 'Thinking',\n  doneLabel = 'Done in',\n  errorLabel = 'Failed after',\n  status = 'working',\n  pattern = 'orbit',\n  grid = 3,\n  shape = 'round',\n  color = 'currentColor',\n  doneColor = '#22c55e',\n  errorColor = '#ef4444',\n  cellSize = 6,\n  gap = 2,\n  fontSize = 14,\n  step = 90,\n  idleOpacity = 0.15,\n  glow = false,\n  glowColor = '',\n  showTimer = true,\n  elapsed,\n  className = '',\n  style\n}) => {\n  const n: LatticeGrid = grid === 4 ? 4 : 3;\n  const pat = resolvePattern(pattern, n);\n  const marks = MARKS[n];\n  const d = step * pat.scale;\n  const cycle = Math.round(pat.loop * d);\n\n  const timerRef = useRef<HTMLSpanElement>(null);\n  const dsRef = useRef(0);\n  const markRef = useRef<'done' | 'error'>('done');\n  const mark = status === 'working' ? markRef.current : status;\n  markRef.current = mark;\n  const [announce, setAnnounce] = useState(`${label}, in progress`);\n\n  const paint = (ds: number) => {\n    dsRef.current = ds;\n    if (timerRef.current) timerRef.current.textContent = fmt(ds);\n  };\n\n  useLayoutEffect(() => {\n    if (elapsed != null) {\n      paint(Math.round(elapsed * 10));\n      return undefined;\n    }\n    if (status !== 'working') return undefined;\n    const startedAt = performance.now();\n    paint(0);\n    const id = setInterval(() => paint(Math.floor((performance.now() - startedAt) / 100)), 100);\n    return () => clearInterval(id);\n  }, [status, elapsed]);\n\n  useEffect(() => {\n    if (status === 'working') setAnnounce(`${label}, in progress`);\n    else setAnnounce(`${status === 'done' ? doneLabel : errorLabel}${showTimer ? ` ${spoken(dsRef.current)}` : ''}`);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [status]);\n\n  return (\n    <span\n      role=\"status\"\n      className={`lattice-loader${className ? ` ${className}` : ''}`}\n      data-status={status}\n      data-shape={shape}\n      data-glow={glow ? '' : undefined}\n      style={\n        {\n          '--ll-n': n,\n          '--ll-cell': `${cellSize}px`,\n          '--ll-gap': `${gap}px`,\n          '--ll-font': `${fontSize}px`,\n          '--ll-color': color,\n          '--ll-mark': status === 'error' ? errorColor : doneColor,\n          '--ll-idle': idleOpacity,\n          '--ll-glow': glowColor || color,\n          '--ll-mark-glow': glowColor || (status === 'error' ? errorColor : doneColor),\n          '--ll-cycle': `${cycle}ms`,\n          ...style\n        } as CSSProperties\n      }\n    >\n      <span className=\"lattice-loader__grid\" aria-hidden=\"true\">\n        <span className=\"lattice-loader__layer lattice-loader__run\">\n          {pat.cells.map((unit, i) => (\n            <span\n              key={i}\n              className=\"lattice-loader__cell\"\n              data-hole={unit == null ? '' : undefined}\n              data-lit={pat.lit && pat.lit !== 0.62 ? Math.round(pat.lit * 100) : undefined}\n              style={unit == null ? undefined : { animationDelay: `${Math.round(unit * d)}ms` }}\n            />\n          ))}\n        </span>\n        <span className=\"lattice-loader__layer lattice-loader__mark\">\n          {pat.cells.map((_, i) => (\n            <span key={i} className=\"lattice-loader__cell\" data-on={marks[mark].includes(i) ? '' : undefined} />\n          ))}\n        </span>\n      </span>\n      <span className=\"lattice-loader__label\" aria-hidden=\"true\">\n        <span className=\"lattice-loader__text\" data-active={status === 'working' ? '' : undefined}>\n          {label}\n        </span>\n        <span className=\"lattice-loader__text\" data-active={status === 'done' ? '' : undefined}>\n          {doneLabel}\n        </span>\n        <span className=\"lattice-loader__text\" data-active={status === 'error' ? '' : undefined}>\n          {errorLabel}\n        </span>\n      </span>\n      {showTimer ? (\n        <span ref={timerRef} className=\"lattice-loader__timer\" aria-hidden=\"true\">\n          0.0s\n        </span>\n      ) : null}\n      <span className=\"lattice-loader__sr\">{announce}</span>\n    </span>\n  );\n};\n\nexport default LatticeLoader;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}