{
  "slug": "workflow-card",
  "name": "Workflow Card",
  "description": "A compact project card that unfolds into grouped checklists with soft spring motion, progress rings, status selection and teammate filters.",
  "summary": "A single theme-synced project card with a soft, interruptible reveal, coordinated status picker, gliding task highlights, segmented progress and animated teammate selection. Includes live content, layout and motion controls.",
  "category": "cards",
  "tags": [
    "workflow",
    "task",
    "checklist",
    "spring",
    "gsap",
    "project",
    "expand",
    "onboarding"
  ],
  "dependencies": [
    "gsap",
    "@gsap/react",
    "motion",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "version": "1.0.0",
  "createdDate": "2026-10-12",
  "updatedDate": "2026-10-12",
  "interactive": true,
  "highlights": [
    "Compact-to-expanded reveal with a measured shell and coordinated status picker",
    "Classic spring defaults: stiffness 105, damping 24, mass 1",
    "Segmented progress ring and reversible checkbox draw animation",
    "Four independent workflow statuses with native keyboard-accessible radio selection",
    "Teammate filters highlight assigned tasks without changing their order",
    "Grouped checklists, inherited light/dark themes and adjustable density, size and corners",
    "Controlled or uncontrolled state, callbacks and scoped GSAP cleanup",
    "Gliding task hover highlight and a subtle increase in ring opacity on hover",
    "Stable header and checklist alignment during opening, closing, selecting and deselecting"
  ],
  "anatomy": [
    "<article> (Layered card shell and accessible heading)",
    "<header> (Progress ring, expansion triggers, status and teammate filters)",
    "<fieldset> (Inline workflow status selector)",
    "<section> (Named checklist with completion count)",
    "<button role=\"checkbox\"> (Assigned task with animated check)",
    "<footer> (Completion summary and project action)"
  ],
  "physics": {
    "engine": "GSAP + spring easing",
    "description": "GSAP owns playback, using the existing Motion spring solver for the reveal. Shell and status-picker heights animate together from their current positions; hover, checkbox and selection feedback animate without shifting text.",
    "parameters": [
      {
        "label": "Stiffness",
        "value": "105 (range 60–240)"
      },
      {
        "label": "Damping",
        "value": "24 (range 14–36)"
      },
      {
        "label": "Mass",
        "value": "1"
      },
      {
        "label": "Reveal",
        "value": "0.75s"
      },
      {
        "label": "Hover highlight",
        "value": "0.38s"
      },
      {
        "label": "Close grace period",
        "value": "240ms"
      }
    ]
  },
  "accessibility": {
    "role": "article",
    "aria": "Named article, aria-expanded and aria-controls on disclosure buttons, inert collapsed content, aria-checked task buttons, aria-pressed teammate filters and polite announcements.",
    "reducedMotion": "Height, progress and task updates apply immediately when reduced motion is requested; content remains fully usable.",
    "keyboard": [
      {
        "key": "Tab / Shift+Tab",
        "description": "Move through expansion, status, teammate and task controls."
      },
      {
        "key": "Enter / Space",
        "description": "Toggle the focused control."
      },
      {
        "key": "Arrow keys",
        "description": "Move between native status radio options."
      },
      {
        "key": "Escape",
        "description": "Close the status selector and restore status focus, or collapse the card and focus its progress button."
      }
    ]
  },
  "guidelines": {
    "recommended": [
      "Compact project summaries and task dashboards",
      "Onboarding and launch checklists",
      "Collaborative workspaces with grouped milestones"
    ],
    "bestPractices": [
      "Use stable section, task and teammate IDs.",
      "Provide onSectionsChange when passing controlled sections.",
      "Keep workflow status independent of checklist completion when a review phase is needed.",
      "Supply onOpen to navigate or display project details.",
      "Use the default 105/24 physics for a smooth reveal with very little bounce.",
      "Set openOnHover to false for an explicit disclosure-only interface."
    ]
  },
  "props": [
    {
      "name": "title",
      "type": "string",
      "description": "Project title.",
      "defaultValue": "\"Onboarding flow\""
    },
    {
      "name": "description",
      "type": "string",
      "description": "Optional supporting text above the checklists."
    },
    {
      "name": "dueDate",
      "type": "string",
      "description": "Display label for the deadline.",
      "defaultValue": "\"Oct 24\""
    },
    {
      "name": "sections",
      "type": "WorkflowSection[]",
      "description": "Controlled grouped checklists; task IDs must be unique within each section."
    },
    {
      "name": "defaultSections",
      "type": "WorkflowSection[]",
      "description": "Initial grouped checklists in uncontrolled mode.",
      "defaultValue": "WORKFLOW_SECTIONS"
    },
    {
      "name": "onSectionsChange",
      "type": "(sections: WorkflowSection[]) => void",
      "description": "Receives updated sections when a task is toggled."
    },
    {
      "name": "assignees",
      "type": "WorkflowAssignee[]",
      "description": "Team members with stable IDs, names and optional colors. Click to highlight their tasks.",
      "defaultValue": "WORKFLOW_ASSIGNEES"
    },
    {
      "name": "status",
      "type": "WorkflowStatus",
      "description": "Controlled status: todo, progress, review or done. Independent of task completion."
    },
    {
      "name": "defaultStatus",
      "type": "WorkflowStatus",
      "description": "Initial status for uncontrolled usage.",
      "defaultValue": "\"progress\""
    },
    {
      "name": "onStatusChange",
      "type": "(status: WorkflowStatus) => void",
      "description": "Called when a status is selected."
    },
    {
      "name": "expanded",
      "type": "boolean",
      "description": "Controlled expansion state."
    },
    {
      "name": "defaultExpanded",
      "type": "boolean",
      "description": "Initial expansion state.",
      "defaultValue": "false"
    },
    {
      "name": "onExpandedChange",
      "type": "(expanded: boolean) => void",
      "description": "Called when expansion is requested by hover, focus or a button."
    },
    {
      "name": "openOnHover",
      "type": "boolean",
      "description": "Reveal on hover and keyboard focus; touch users use the title or ring button.",
      "defaultValue": "true"
    },
    {
      "name": "closeDelay",
      "type": "number",
      "description": "Delay before closing after pointer/focus leaves, in seconds.",
      "defaultValue": "0.24"
    },
    {
      "name": "theme",
      "type": "\"system\" | \"light\" | \"dark\"",
      "description": "Inherits the website CSS theme tokens by default; optional explicit light or dark override.",
      "defaultValue": "\"system\""
    },
    {
      "name": "accent",
      "type": "string",
      "description": "Accent for focus rings and fallback teammate colors.",
      "defaultValue": "\"#32966a\""
    },
    {
      "name": "width",
      "type": "number",
      "description": "Preferred width, clamped to 260–480px and constrained by the parent.",
      "defaultValue": "320"
    },
    {
      "name": "radius",
      "type": "number",
      "description": "Surface corner radius, clamped to 8–28px.",
      "defaultValue": "18"
    },
    {
      "name": "density",
      "type": "\"compact\" | \"comfortable\"",
      "description": "Checklist row height, 28 or 34px.",
      "defaultValue": "\"comfortable\""
    },
    {
      "name": "stiffness",
      "type": "number",
      "description": "Spring tension, clamped to 60–240. The default has a soft, classic feel.",
      "defaultValue": "105"
    },
    {
      "name": "damping",
      "type": "number",
      "description": "Spring friction, clamped to 14–36. Higher values reduce overshoot.",
      "defaultValue": "24"
    },
    {
      "name": "duration",
      "type": "number",
      "description": "Reveal duration in seconds, clamped to 0.3–1.2.",
      "defaultValue": "0.75"
    },
    {
      "name": "showAssignees",
      "type": "boolean",
      "description": "Show teammate filter buttons.",
      "defaultValue": "true"
    },
    {
      "name": "showDueDate",
      "type": "boolean",
      "description": "Show the deadline label.",
      "defaultValue": "true"
    },
    {
      "name": "showFooter",
      "type": "boolean",
      "description": "Show completion summary and the open/close action.",
      "defaultValue": "true"
    },
    {
      "name": "showSectionLabels",
      "type": "boolean",
      "description": "Show section names and completion counts.",
      "defaultValue": "true"
    },
    {
      "name": "className",
      "type": "string",
      "description": "Additional article classes."
    },
    {
      "name": "onOpen",
      "type": "() => void",
      "description": "Connect the footer Open action to project details. Without it, the footer closes the card."
    }
  ],
  "usage": "import { WorkflowCard } from \"@/components/ui/workflow-card\";\n\nexport function ProjectOverview() {\n  return (\n    <WorkflowCard\n      title=\"Onboarding flow\"\n      dueDate=\"Oct 24\"\n      defaultExpanded\n      stiffness={105}\n      damping={24}\n      defaultSections={[{\n        id: \"setup\",\n        label: \"Getting started\",\n        tasks: [\n          { id: \"welcome\", label: \"Welcome screen\", done: true },\n          { id: \"workspace\", label: \"Workspace setup\" },\n        ],\n      }]}\n    />\n  );\n}",
  "files": [
    {
      "name": "workflow-card.tsx",
      "path": "src/registry/ui/workflow-card.tsx",
      "code": "\"use client\";\n\nimport { useId, useMemo, useRef, useState, type CSSProperties } from \"react\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { spring, useReducedMotion } from \"motion/react\";\nimport { ArrowRight, Check, CircleDashed, CircleDot, Eye } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport const WORKFLOW_STATUSES = {\n  todo: { label: \"To do\", color: \"#8060bd\", Icon: CircleDashed },\n  progress: { label: \"In progress\", color: \"#b57a29\", Icon: CircleDot },\n  review: { label: \"In review\", color: \"#557bc4\", Icon: Eye },\n  done: { label: \"Done\", color: \"#32966a\", Icon: Check },\n} as const;\n\nexport type WorkflowStatus = keyof typeof WORKFLOW_STATUSES;\n\nexport interface WorkflowAssignee {\n  id: string;\n  name: string;\n  color?: string;\n}\n\nexport interface WorkflowTask {\n  id: string;\n  label: string;\n  done?: boolean;\n  assigneeId?: string;\n}\n\nexport interface WorkflowSection {\n  id: string;\n  label?: string;\n  tasks: WorkflowTask[];\n}\n\nexport interface WorkflowCardProps {\n  title?: string;\n  description?: string;\n  dueDate?: string;\n  sections?: WorkflowSection[];\n  defaultSections?: WorkflowSection[];\n  onSectionsChange?: (sections: WorkflowSection[]) => void;\n  assignees?: WorkflowAssignee[];\n  status?: WorkflowStatus;\n  defaultStatus?: WorkflowStatus;\n  onStatusChange?: (status: WorkflowStatus) => void;\n  expanded?: boolean;\n  defaultExpanded?: boolean;\n  onExpandedChange?: (expanded: boolean) => void;\n  openOnHover?: boolean;\n  closeDelay?: number;\n  theme?: \"system\" | \"light\" | \"dark\";\n  accent?: string;\n  width?: number;\n  radius?: number;\n  density?: \"compact\" | \"comfortable\";\n  stiffness?: number;\n  damping?: number;\n  duration?: number;\n  showAssignees?: boolean;\n  showDueDate?: boolean;\n  showFooter?: boolean;\n  showSectionLabels?: boolean;\n  className?: string;\n  onOpen?: () => void;\n}\n\nexport const WORKFLOW_ASSIGNEES: WorkflowAssignee[] = [\n  { id: \"mara\", name: \"Mara\", color: \"#91c788\" },\n  { id: \"jonas\", name: \"Jonas\", color: \"#a095d7\" },\n  { id: \"priya\", name: \"Priya\", color: \"#8ca6dc\" },\n];\n\nexport const WORKFLOW_SECTIONS: WorkflowSection[] = [\n  {\n    id: \"setup\",\n    label: \"Getting started\",\n    tasks: [\n      {\n        id: \"welcome\",\n        label: \"Welcome screen\",\n        done: true,\n        assigneeId: \"mara\",\n      },\n      {\n        id: \"workspace\",\n        label: \"Workspace setup\",\n        done: true,\n        assigneeId: \"jonas\",\n      },\n      {\n        id: \"invite\",\n        label: \"Invite teammates\",\n        done: true,\n        assigneeId: \"mara\",\n      },\n    ],\n  },\n  {\n    id: \"follow-through\",\n    label: \"Follow through\",\n    tasks: [\n      { id: \"checklist\", label: \"First-run checklist\", assigneeId: \"priya\" },\n      { id: \"emails\", label: \"Lifecycle emails\", assigneeId: \"jonas\" },\n    ],\n  },\n];\n\nconst focusClass =\n  \"outline-none focus-visible:ring-2 focus-visible:ring-[var(--wf-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wf-surface)]\";\nconst bounded = (value: number, min: number, max: number, fallback: number) =>\n  Number.isFinite(value) ? gsap.utils.clamp(min, max, value) : fallback;\nconst themeTokens = [\n  [\"surface\", \"card\", \"#ffffff\", \"#222222\"],\n  [\"ink\", \"foreground\", \"#252525\", \"#eeeeee\"],\n  [\"muted\", \"muted-foreground\", \"#858583\", \"#92928f\"],\n  [\"line\", \"border\", \"#dededb\", \"#484845\"],\n  [\"hover\", \"muted\", \"#f4f4f2\", \"#2c2c2a\"],\n  [\"outer\", \"muted\", \"#eaeae7\", \"#1b1b1a\"],\n] as const;\n\nfunction WorkflowRow({\n  task,\n  person,\n  dimmed,\n  onToggle,\n  duration,\n  ease,\n}: {\n  task: WorkflowTask;\n  person?: WorkflowAssignee;\n  dimmed: boolean;\n  onToggle: () => void;\n  duration: number;\n  ease: (progress: number) => number;\n}) {\n  const rootRef = useRef<HTMLButtonElement>(null);\n  const mounted = useRef(false);\n  const [initialDone] = useState(!!task.done);\n  const reduced = useReducedMotion();\n  useGSAP(\n    () => {\n      const root = rootRef.current;\n      if (!root) return;\n      const time = reduced || !mounted.current ? 0 : duration;\n      gsap.to(root, {\n        opacity: dimmed ? 0.4 : 1,\n        duration: time * 0.5,\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelector(\"[data-check]\"), {\n        scale: task.done ? 1 : 0,\n        duration: time * 0.65,\n        ease,\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelector(\"[data-tick]\"), {\n        strokeDashoffset: task.done ? 0 : 1,\n        duration: time * 0.45,\n        ease: \"power2.out\",\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelector(\"[data-label]\"), {\n        opacity: task.done ? 0.58 : 1,\n        duration: time * 0.55,\n        ease: \"power2.out\",\n        overwrite: \"auto\",\n      });\n      mounted.current = true;\n    },\n    { scope: rootRef, dependencies: [task.done, dimmed, duration, ease, reduced] },\n  );\n\n  return (\n    <button\n      ref={rootRef}\n      type=\"button\"\n      role=\"checkbox\"\n      aria-checked={!!task.done}\n      aria-label={`${task.label}${person ? `, assigned to ${person.name}` : \"\"}`}\n      onClick={onToggle}\n      data-slot=\"workflow-task\"\n      data-done={!!task.done}\n      className={cn(\n        \"group/task relative z-10 flex w-full cursor-pointer pointer-coarse:[--wf-touch-height:44px] items-center gap-2.5 rounded-md px-2 text-left\",\n        focusClass,\n      )}\n      style={{\n        minHeight: \"max(var(--wf-row-height), var(--wf-touch-height, 0px))\",\n      }}\n    >\n      <span\n        className=\"relative flex size-3.5 shrink-0 items-center justify-center rounded-full border border-[var(--wf-line)]\"\n        aria-hidden=\"true\"\n      >\n        <span\n          data-check\n          className=\"absolute -inset-px rounded-full bg-[var(--wf-ink)]\"\n          style={{ transform: `scale(${initialDone ? 1 : 0})` }}\n        />\n        <svg viewBox=\"0 0 14 14\" className=\"relative size-3.5 text-[var(--wf-surface)]\" fill=\"none\">\n          <path\n            data-tick\n            d=\"m4 7 2 2 4-4\"\n            pathLength=\"1\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.65\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            strokeDasharray=\"1\"\n            strokeDashoffset={initialDone ? 0 : 1}\n          />\n        </svg>\n      </span>\n      <span data-label className=\"min-w-0 flex-1 py-1.5 text-[12px] leading-[1.4] wrap-anywhere\">\n        {task.label}\n      </span>\n      {person && (\n        <span\n          aria-hidden=\"true\"\n          title={person.name}\n          data-row-person\n          className=\"flex size-4 shrink-0 items-center justify-center rounded-[4px] text-[9px] font-semibold opacity-0\"\n          style={{\n            background: `color-mix(in srgb, ${person.color ?? \"var(--wf-accent)\"}, transparent 80%)`,\n            color: person.color ?? \"var(--wf-accent)\",\n          }}\n        >\n          {person.name.slice(0, 1)}\n        </span>\n      )}\n    </button>\n  );\n}\n\nexport function WorkflowCard({\n  title = \"Onboarding flow\",\n  description,\n  dueDate = \"Oct 24\",\n  sections,\n  defaultSections = WORKFLOW_SECTIONS,\n  onSectionsChange,\n  assignees = WORKFLOW_ASSIGNEES,\n  status,\n  defaultStatus = \"progress\",\n  onStatusChange,\n  expanded,\n  defaultExpanded = false,\n  onExpandedChange,\n  openOnHover = true,\n  closeDelay = 0.24,\n  theme = \"system\",\n  accent = \"#32966a\",\n  width = 320,\n  radius = 18,\n  density = \"comfortable\",\n  stiffness = 105,\n  damping = 24,\n  duration = 0.75,\n  showAssignees = true,\n  showDueDate = true,\n  showFooter = true,\n  showSectionLabels = true,\n  className,\n  onOpen,\n}: WorkflowCardProps) {\n  const [ownSections, setOwnSections] = useState(defaultSections);\n  const [ownStatus, setOwnStatus] = useState(defaultStatus);\n  const [ownExpanded, setOwnExpanded] = useState(defaultExpanded);\n  const [pickerOpen, setPickerOpen] = useState(false);\n  const [hovered, setHovered] = useState(false);\n  const [filter, setFilter] = useState<string | null>(null);\n  const [announcement, setAnnouncement] = useState(\"\");\n  const reduced = useReducedMotion();\n  const rootRef = useRef<HTMLElement>(null);\n  const panelRef = useRef<HTMLDivElement>(null);\n  const contentRef = useRef<HTMLDivElement>(null);\n  const pickerRef = useRef<HTMLDivElement>(null);\n  const pickerContentRef = useRef<HTMLFieldSetElement>(null);\n  const highlightRef = useRef<HTMLSpanElement>(null);\n  const hoveredRowRef = useRef<HTMLElement | null>(null);\n  const pressedRef = useRef<HTMLElement | null>(null);\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const statusRef = useRef<HTMLButtonElement>(null);\n  const closeRef = useRef<gsap.core.Tween | null>(null);\n  const syncRef = useRef<((immediate?: boolean) => void) | null>(null);\n  const id = useId();\n  const currentSections = sections ?? ownSections;\n  const currentStatus = status ?? ownStatus;\n  const isOpen = expanded ?? ownExpanded;\n  const tasks = currentSections.flatMap((section) => section.tasks);\n  const complete = tasks.filter((task) => task.done).length;\n  const segmentCount = Math.max(1, Math.min(tasks.length, 20));\n  const filteredPerson = showAssignees\n    ? assignees.find((person) => person.id === filter)\n    : undefined;\n  const visibleTasks = filteredPerson\n    ? tasks.filter((task) => task.assigneeId === filteredPerson.id)\n    : tasks;\n  const visibleComplete = visibleTasks.filter((task) => task.done).length;\n  const statusSpec = WORKFLOW_STATUSES[currentStatus];\n  const time = bounded(duration, 0.3, 1.2, 0.75);\n  const tension = bounded(stiffness, 60, 240, 105);\n  const friction = bounded(damping, 14, 36, 24);\n  const ease = useMemo(() => {\n    const response = spring({\n      keyframes: [0, 1],\n      stiffness: tension,\n      damping: friction,\n      mass: 1,\n    });\n    const settle = Math.max(0.8, 16 / friction);\n    const end = response.next(settle * 1000).value;\n    return (progress: number) =>\n      progress === 1 ? 1 : response.next(progress * settle * 1000).value / end;\n  }, [tension, friction]);\n\n  const changeExpanded = (next: boolean) => {\n    closeRef.current?.kill();\n    if (expanded === undefined) setOwnExpanded(next);\n    onExpandedChange?.(next);\n    if (!next) setPickerOpen(false);\n  };\n\n  const { contextSafe } = useGSAP(\n    () => {\n      const root = rootRef.current;\n      const panel = panelRef.current;\n      const content = contentRef.current;\n      if (!root || !panel || !content) return;\n      const picker = pickerRef.current;\n      const pickerContent = pickerContentRef.current;\n      if (!picker || !pickerContent) return;\n      const media = gsap.matchMedia();\n      media.add(\n        {\n          reduce: \"(prefers-reduced-motion: reduce)\",\n          animate: \"(prefers-reduced-motion: no-preference)\",\n        },\n        (context) => {\n          let reveal: gsap.core.Timeline | null = null;\n          const sync = context.add(\"sync\", (immediate = false) => {\n            const open = root.dataset.expanded === \"true\";\n            const choosing = open && root.dataset.pickerOpen === \"true\";\n            const instant = immediate || context.conditions?.reduce;\n            const pickerHeight = choosing ? pickerContent.offsetHeight : 0;\n            reveal?.kill();\n            reveal = gsap\n              .timeline({\n                defaults: {\n                  duration: instant ? 0 : time,\n                  ease: open ? ease : \"power3.out\",\n                  overwrite: \"auto\",\n                },\n              })\n              .to(panel, { height: open ? content.offsetHeight + pickerHeight : 0 }, 0)\n              .to(picker, { height: pickerHeight }, 0)\n              .to(\n                pickerContent,\n                {\n                  opacity: choosing ? 1 : 0,\n                  y: choosing ? 0 : -3,\n                  duration: instant ? 0 : time * 0.48,\n                },\n                0,\n              )\n              .to(\n                content,\n                {\n                  opacity: open ? 1 : 0,\n                  duration: instant ? 0 : time * (open ? 0.65 : 0.4),\n                  ease: \"power2.out\",\n                },\n                0,\n              );\n          });\n          syncRef.current = (immediate) => sync(immediate);\n          sync(true);\n          const observer = new ResizeObserver(() => sync());\n          observer.observe(content);\n          observer.observe(pickerContent);\n          return () => {\n            observer.disconnect();\n            reveal?.kill();\n            syncRef.current = null;\n          };\n        },\n      );\n      return () => {\n        closeRef.current?.kill();\n        media.revert();\n      };\n    },\n    { scope: rootRef, dependencies: [time, ease], revertOnUpdate: true },\n  );\n\n  useGSAP(\n    (context) => {\n      context.ignore(() => syncRef.current?.());\n    },\n    { scope: rootRef, dependencies: [isOpen, pickerOpen, time, ease] },\n  );\n\n  useGSAP(\n    () => {\n      gsap.to(rootRef.current?.querySelectorAll(\"[data-segment]\") ?? [], {\n        opacity: (index: number) =>\n          index < Math.round((visibleComplete / Math.max(1, visibleTasks.length)) * segmentCount)\n            ? hovered\n              ? 1\n              : 0.82\n            : hovered\n              ? 0.28\n              : 0.14,\n        duration: reduced ? 0 : time * 0.6,\n        stagger: reduced ? 0 : 0.018,\n        ease: \"power2.out\",\n        overwrite: \"auto\",\n      });\n    },\n    {\n      scope: rootRef,\n      dependencies: [visibleComplete, visibleTasks.length, segmentCount, time, hovered, reduced],\n    },\n  );\n\n  const scheduleClose = () =>\n    contextSafe(() => {\n      closeRef.current?.kill();\n      closeRef.current = gsap.delayedCall(bounded(closeDelay, 0, 1, 0.24), () => {\n        if (\n          !rootRef.current?.contains(document.activeElement) ||\n          !document.activeElement?.matches(\":focus-visible\")\n        )\n          changeExpanded(false);\n      });\n    })();\n  const press = contextSafe((target: HTMLElement, down: boolean) => {\n    if (reduced) return;\n    gsap.to(target, {\n      scale: down ? 0.985 : 1,\n      duration: down ? 0.12 : time * 0.6,\n      ease: down ? \"power2.out\" : ease,\n      overwrite: \"auto\",\n    });\n  });\n  const highlightRow = (target: HTMLElement | null) =>\n    contextSafe(() => {\n      const highlight = highlightRef.current;\n      const content = contentRef.current;\n      if (!highlight || !content || hoveredRowRef.current === target) return;\n      const previous = hoveredRowRef.current;\n      hoveredRowRef.current = target;\n      const previousPerson = previous?.querySelector(\"[data-row-person]\");\n      if (previousPerson)\n        gsap.to(previousPerson, {\n          opacity: 0,\n          duration: reduced ? 0 : 0.25,\n          overwrite: \"auto\",\n        });\n      if (!target) {\n        gsap.to(highlight, {\n          opacity: 0,\n          duration: reduced ? 0 : 0.3,\n          overwrite: \"auto\",\n        });\n        return;\n      }\n      const rect = target.getBoundingClientRect();\n      const parent = content.getBoundingClientRect();\n      if (!previous)\n        gsap.set(highlight, {\n          x: rect.left - parent.left,\n          y: rect.top - parent.top,\n          width: rect.width,\n          height: rect.height,\n        });\n      gsap.to(highlight, {\n        x: rect.left - parent.left,\n        y: rect.top - parent.top,\n        width: rect.width,\n        height: rect.height,\n        opacity: 1,\n        duration: reduced ? 0 : 0.38,\n        ease: \"power3.out\",\n        overwrite: \"auto\",\n      });\n      const person = target.querySelector(\"[data-row-person]\");\n      if (person)\n        gsap.to(person, {\n          opacity: 1,\n          duration: reduced ? 0 : 0.3,\n          overwrite: \"auto\",\n        });\n    })();\n  const releasePress = () => {\n    if (pressedRef.current) press(pressedRef.current, false);\n    pressedRef.current = null;\n  };\n\n  useGSAP(\n    () => {\n      const root = rootRef.current;\n      if (!root) return;\n      gsap.to(root.querySelectorAll(\"[data-assignee]\"), {\n        opacity: (_, target: HTMLElement) =>\n          filteredPerson && target.dataset.assignee !== filter ? 0.45 : 1,\n        duration: reduced ? 0 : 0.4,\n        ease: \"power2.out\",\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelectorAll(\"[data-assignee-ring]\"), {\n        opacity: (_, target: HTMLElement) =>\n          target.parentElement?.dataset.assignee === filteredPerson?.id ? 1 : 0,\n        scale: (_, target: HTMLElement) =>\n          target.parentElement?.dataset.assignee === filteredPerson?.id ? 1 : 0.86,\n        duration: reduced ? 0 : 0.4,\n        ease,\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelectorAll(\"[data-status-ring]\"), {\n        opacity: (_, target: HTMLElement) => (target.dataset.statusRing === currentStatus ? 1 : 0),\n        duration: reduced ? 0 : 0.32,\n        overwrite: \"auto\",\n      });\n    },\n    {\n      scope: rootRef,\n      dependencies: [filter, filteredPerson?.id, currentStatus, showAssignees, ease, reduced],\n    },\n  );\n\n  const toggleTask = (sectionId: string, taskId: string) => {\n    const next = currentSections.map((section) =>\n      section.id !== sectionId\n        ? section\n        : {\n            ...section,\n            tasks: section.tasks.map((task) =>\n              task.id !== taskId ? task : { ...task, done: !task.done },\n            ),\n          },\n    );\n    if (sections === undefined) setOwnSections(next);\n    onSectionsChange?.(next);\n    const task = next\n      .find((section) => section.id === sectionId)\n      ?.tasks.find((item) => item.id === taskId);\n    setAnnouncement(`${task?.label} ${task?.done ? \"completed\" : \"reopened\"}`);\n  };\n  const footerLabel = filteredPerson\n    ? `${filteredPerson.name} · ${visibleComplete} of ${visibleTasks.length} done`\n    : tasks.length === 0\n      ? \"No tasks yet\"\n      : complete === tasks.length\n        ? \"All done. Nicely done.\"\n        : `${tasks.length - complete} left to do`;\n  const inherited = theme === \"system\";\n  const light = theme !== \"dark\";\n  const curve = bounded(radius, 8, 28, 18);\n  const colors = Object.fromEntries(\n    themeTokens.map(([name, token, lightColor, darkColor]) => [\n      `--wf-${name}`,\n      inherited ? `var(--${token}, ${lightColor})` : light ? lightColor : darkColor,\n    ]),\n  );\n\n  return (\n    <article\n      ref={rootRef}\n      data-slot=\"workflow-card\"\n      data-expanded={isOpen}\n      data-picker-open={pickerOpen}\n      data-theme={theme}\n      aria-labelledby={`${id}-title`}\n      className={cn(\"relative isolate w-full max-w-full font-sans\", className)}\n      style={\n        {\n          width: bounded(width, 260, 480, 320),\n          borderRadius: curve + 3,\n          \"--wf-accent\": accent,\n          ...colors,\n          \"--wf-row-height\": density === \"compact\" ? \"28px\" : \"34px\",\n          background: \"var(--wf-outer)\",\n          padding: 3,\n          color: \"var(--wf-ink)\",\n          boxShadow: light\n            ? \"0 3px 8px -4px #00000018, inset 0 0 0 1px #00000004\"\n            : \"0 3px 12px -6px #00000080, inset 0 0 0 1px #ffffff04\",\n        } as CSSProperties\n      }\n      onPointerEnter={(event) => {\n        closeRef.current?.kill();\n        if (event.pointerType !== \"touch\") setHovered(true);\n        if (openOnHover && event.pointerType !== \"touch\") changeExpanded(true);\n      }}\n      onPointerLeave={() => {\n        setHovered(false);\n        highlightRow(null);\n        releasePress();\n        if (openOnHover) scheduleClose();\n      }}\n      onPointerOver={(event) => {\n        const row = (event.target as HTMLElement).closest<HTMLElement>(\n          '[data-slot=\"workflow-task\"]',\n        );\n        if (row) highlightRow(row);\n      }}\n      onFocusCapture={(event) => {\n        closeRef.current?.kill();\n        highlightRow(\n          (event.target as HTMLElement).closest<HTMLElement>('[data-slot=\"workflow-task\"]'),\n        );\n        if (event.target.matches(\":focus-visible\")) setHovered(true);\n        if (\n          openOnHover &&\n          event.target.matches(\":focus-visible\") &&\n          !event.currentTarget.contains(event.relatedTarget)\n        )\n          changeExpanded(true);\n      }}\n      onBlurCapture={(event) => {\n        if (!event.currentTarget.contains(event.relatedTarget)) {\n          setHovered(false);\n          highlightRow(null);\n        }\n        if (openOnHover && !event.currentTarget.contains(event.relatedTarget)) scheduleClose();\n      }}\n      onKeyDown={(event) => {\n        if (event.key !== \"Escape\") return;\n        event.stopPropagation();\n        if (pickerOpen) {\n          setPickerOpen(false);\n          statusRef.current?.focus();\n        } else {\n          changeExpanded(false);\n          triggerRef.current?.focus();\n        }\n      }}\n      onPointerDown={(event) => {\n        const target = (event.target as HTMLElement).closest<HTMLElement>(\"button\");\n        if (target && target.dataset.slot !== \"workflow-task\") {\n          pressedRef.current = target;\n          press(target, true);\n        }\n      }}\n      onPointerUp={releasePress}\n      onPointerCancel={releasePress}\n      onLostPointerCapture={releasePress}\n    >\n      <div\n        className=\"relative overflow-hidden bg-[var(--wf-surface)]\"\n        style={{\n          borderRadius: curve,\n          boxShadow: light ? \"0 1px 3px #00000008\" : \"0 1px 3px #00000030\",\n        }}\n      >\n        <header className=\"flex items-center gap-2.5 px-3 py-3\">\n          <button\n            ref={triggerRef}\n            type=\"button\"\n            aria-expanded={isOpen}\n            aria-controls={`${id}-panel`}\n            aria-label={`${isOpen ? \"Collapse\" : \"Expand\"} ${title}`}\n            onClick={() => changeExpanded(!isOpen)}\n            className={cn(\n              \"relative flex size-8 shrink-0 cursor-pointer pointer-coarse:size-10 items-center justify-center rounded-full\",\n              focusClass,\n            )}\n          >\n            <svg\n              viewBox=\"0 0 36 36\"\n              className=\"absolute inset-0 size-full -rotate-90\"\n              aria-hidden=\"true\"\n            >\n              {Array.from({ length: segmentCount }, (_, index) => {\n                const count = segmentCount;\n                const circumference = 2 * Math.PI * 14;\n                return (\n                  <circle\n                    key={index}\n                    data-segment\n                    cx=\"18\"\n                    cy=\"18\"\n                    r=\"14\"\n                    fill=\"none\"\n                    stroke=\"var(--wf-ink)\"\n                    strokeWidth=\"2.7\"\n                    strokeLinecap=\"round\"\n                    strokeDasharray={`${Math.max(1, circumference / count - 3.8)} ${circumference}`}\n                    strokeDashoffset={(-index * circumference) / count}\n                    style={{ opacity: 0.16 }}\n                  />\n                );\n              })}\n            </svg>\n            <span aria-hidden=\"true\" className=\"text-[11px] font-semibold tabular-nums\">\n              {complete}\n            </span>\n          </button>\n          <div className=\"min-w-0 flex-1\">\n            <h3\n              id={`${id}-title`}\n              className=\"text-[13px] leading-5 font-semibold tracking-[-0.025em]\"\n            >\n              <button\n                type=\"button\"\n                aria-expanded={isOpen}\n                aria-controls={`${id}-panel`}\n                onClick={() => changeExpanded(!isOpen)}\n                className={cn(\n                  \"block max-w-full cursor-pointer rounded-sm text-left wrap-anywhere\",\n                  focusClass,\n                )}\n              >\n                {title}\n              </button>\n            </h3>\n            <div className=\"flex flex-wrap items-center gap-x-1.5 gap-y-1 text-[10px] leading-4\">\n              <button\n                ref={statusRef}\n                type=\"button\"\n                aria-expanded={pickerOpen && isOpen}\n                aria-controls={`${id}-status`}\n                aria-label={`Change status, currently ${statusSpec.label}`}\n                onClick={() => {\n                  changeExpanded(true);\n                  setPickerOpen(!pickerOpen);\n                }}\n                className={cn(\n                  \"flex w-24 shrink-0 cursor-pointer items-center gap-1 rounded px-1.5\",\n                  focusClass,\n                )}\n                style={{\n                  color: `color-mix(in srgb, ${statusSpec.color}, var(--wf-ink) 18%)`,\n                  background: `color-mix(in srgb, ${statusSpec.color}, transparent 86%)`,\n                }}\n              >\n                <statusSpec.Icon size={10} strokeWidth={2} aria-hidden=\"true\" />\n                {statusSpec.label}\n              </button>\n              {showDueDate && dueDate && (\n                <span className=\"text-[var(--wf-muted)]\">Due {dueDate}</span>\n              )}\n            </div>\n          </div>\n          {showAssignees && assignees.length > 0 && (\n            <div className=\"flex shrink-0 -space-x-1.5\" aria-label=\"Filter by teammate\">\n              {assignees.slice(0, 5).map((person) => (\n                <button\n                  key={person.id}\n                  data-assignee={person.id}\n                  type=\"button\"\n                  title={`${filter === person.id ? \"Show everyone\" : `Filter ${person.name}'s tasks`}`}\n                  aria-label={`Filter ${person.name}'s tasks`}\n                  aria-pressed={filter === person.id}\n                  onClick={() => {\n                    changeExpanded(true);\n                    setFilter(filter === person.id ? null : person.id);\n                  }}\n                  className={cn(\n                    \"relative flex size-6 cursor-pointer pointer-coarse:size-8 items-center justify-center rounded-full border-2 border-[var(--wf-surface)] text-[9px] font-semibold hover:z-10\",\n                    focusClass,\n                  )}\n                  style={{\n                    background: `color-mix(in srgb, ${person.color ?? accent}, var(--wf-surface) ${light ? 60 : 75}%)`,\n                    color: person.color ?? accent,\n                    zIndex: filter === person.id ? 10 : undefined,\n                  }}\n                >\n                  <span\n                    data-assignee-ring\n                    aria-hidden=\"true\"\n                    className=\"pointer-events-none absolute -inset-0.5 rounded-full border border-current opacity-0\"\n                  />\n                  {person.name.slice(0, 1)}\n                </button>\n              ))}\n            </div>\n          )}\n        </header>\n        <div\n          ref={panelRef}\n          id={`${id}-panel`}\n          inert={!isOpen}\n          aria-hidden={!isOpen}\n          className=\"overflow-hidden\"\n          style={{ height: defaultExpanded ? \"auto\" : 0 }}\n        >\n          <div\n            ref={pickerRef}\n            className=\"overflow-hidden\"\n            inert={!pickerOpen || !isOpen}\n            aria-hidden={!pickerOpen || !isOpen}\n            style={{ height: 0 }}\n          >\n            <fieldset\n              ref={pickerContentRef}\n              id={`${id}-status`}\n              className=\"mx-3 flex flex-wrap gap-1.5 border-b border-[var(--wf-line)] pb-3 pt-1\"\n            >\n              <legend className=\"sr-only\">Workflow status</legend>\n              {(Object.entries(WORKFLOW_STATUSES) as [WorkflowStatus, typeof statusSpec][]).map(\n                ([key, spec]) => (\n                  <label\n                    key={key}\n                    className=\"relative flex cursor-pointer items-center gap-1 rounded-md px-2 py-1.5 text-[10px]\"\n                    style={{\n                      color: `color-mix(in srgb, ${spec.color}, var(--wf-ink) 18%)`,\n                      background: `color-mix(in srgb, ${spec.color}, transparent 88%)`,\n                    }}\n                  >\n                    <input\n                      disabled={!pickerOpen || !isOpen}\n                      type=\"radio\"\n                      name={`${id}-workflow-status`}\n                      value={key}\n                      checked={currentStatus === key}\n                      onChange={() => {\n                        if (status === undefined) setOwnStatus(key);\n                        onStatusChange?.(key);\n                        setPickerOpen(false);\n                        setAnnouncement(`Status changed to ${spec.label}`);\n                        statusRef.current?.focus();\n                      }}\n                      className=\"peer absolute inset-0 z-10 size-full cursor-pointer opacity-0\"\n                    />\n                    <span\n                      data-status-ring={key}\n                      aria-hidden=\"true\"\n                      className=\"pointer-events-none absolute inset-0 rounded-md border border-current opacity-0\"\n                    />\n                    <span\n                      aria-hidden=\"true\"\n                      className=\"pointer-events-none absolute inset-0 rounded-md peer-focus-visible:ring-2 peer-focus-visible:ring-current\"\n                    />\n                    <spec.Icon size={11} aria-hidden=\"true\" />\n                    {spec.label}\n                  </label>\n                ),\n              )}\n            </fieldset>\n          </div>\n          <div ref={contentRef} className=\"relative\" onPointerLeave={() => highlightRow(null)}>\n            <span\n              ref={highlightRef}\n              data-hover-highlight\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute top-0 left-0 rounded-md bg-[var(--wf-hover)] opacity-0\"\n            />\n            <div className=\"relative px-2 pb-2\">\n              {description && (\n                <p className=\"px-2 pb-3 text-[11px] leading-relaxed text-[var(--wf-muted)] wrap-anywhere\">\n                  {description}\n                </p>\n              )}\n              {currentSections.map((section, index) => (\n                <section\n                  data-section\n                  key={section.id}\n                  aria-label={section.label ?? `Checklist ${index + 1}`}\n                  className={index > 0 ? \"mt-2 border-t border-[var(--wf-line)] pt-2\" : \"\"}\n                >\n                  {showSectionLabels && section.label && (\n                    <h4 className=\"mb-1 flex items-center justify-between gap-2 px-2 text-[9px] font-medium tracking-[0.08em] text-[var(--wf-muted)] uppercase\">\n                      <span className=\"min-w-0 wrap-anywhere\">{section.label}</span>\n                      <span className=\"shrink-0 tabular-nums\">\n                        {section.tasks.filter((task) => task.done).length}/{section.tasks.length}\n                      </span>\n                    </h4>\n                  )}\n                  {section.tasks.map((task) => (\n                    <WorkflowRow\n                      key={task.id}\n                      task={task}\n                      person={assignees.find((person) => person.id === task.assigneeId)}\n                      dimmed={!!filteredPerson && task.assigneeId !== filter}\n                      onToggle={() => toggleTask(section.id, task.id)}\n                      duration={time}\n                      ease={ease}\n                    />\n                  ))}\n                </section>\n              ))}\n              {tasks.length === 0 && (\n                <p className=\"px-2 py-4 text-[12px] text-[var(--wf-muted)]\">\n                  A fresh start. Add your first task.\n                </p>\n              )}\n            </div>\n            {showFooter && (\n              <footer className=\"flex min-h-10 items-center justify-between gap-2 rounded-b-[inherit] bg-[var(--wf-hover)] px-4 py-2 text-[10px] text-[var(--wf-muted)]\">\n                <span className=\"min-w-0 wrap-anywhere\">{footerLabel}</span>\n                <button\n                  type=\"button\"\n                  aria-label={onOpen ? `Open ${title}` : `Collapse ${title}`}\n                  onClick={() => (onOpen ? onOpen() : changeExpanded(false))}\n                  className={cn(\n                    \"group/open flex shrink-0 cursor-pointer items-center gap-1 rounded-sm py-1 font-medium text-[var(--wf-ink)]\",\n                    focusClass,\n                  )}\n                >\n                  {onOpen ? \"Open\" : \"Close\"}\n                  <ArrowRight size={12} aria-hidden=\"true\" />\n                </button>\n              </footer>\n            )}\n          </div>\n        </div>\n      </div>\n      <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n        {announcement}\n      </span>\n    </article>\n  );\n}\n\nexport default WorkflowCard;\n",
      "content": "\"use client\";\n\nimport { useId, useMemo, useRef, useState, type CSSProperties } from \"react\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { spring, useReducedMotion } from \"motion/react\";\nimport { ArrowRight, Check, CircleDashed, CircleDot, Eye } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport const WORKFLOW_STATUSES = {\n  todo: { label: \"To do\", color: \"#8060bd\", Icon: CircleDashed },\n  progress: { label: \"In progress\", color: \"#b57a29\", Icon: CircleDot },\n  review: { label: \"In review\", color: \"#557bc4\", Icon: Eye },\n  done: { label: \"Done\", color: \"#32966a\", Icon: Check },\n} as const;\n\nexport type WorkflowStatus = keyof typeof WORKFLOW_STATUSES;\n\nexport interface WorkflowAssignee {\n  id: string;\n  name: string;\n  color?: string;\n}\n\nexport interface WorkflowTask {\n  id: string;\n  label: string;\n  done?: boolean;\n  assigneeId?: string;\n}\n\nexport interface WorkflowSection {\n  id: string;\n  label?: string;\n  tasks: WorkflowTask[];\n}\n\nexport interface WorkflowCardProps {\n  title?: string;\n  description?: string;\n  dueDate?: string;\n  sections?: WorkflowSection[];\n  defaultSections?: WorkflowSection[];\n  onSectionsChange?: (sections: WorkflowSection[]) => void;\n  assignees?: WorkflowAssignee[];\n  status?: WorkflowStatus;\n  defaultStatus?: WorkflowStatus;\n  onStatusChange?: (status: WorkflowStatus) => void;\n  expanded?: boolean;\n  defaultExpanded?: boolean;\n  onExpandedChange?: (expanded: boolean) => void;\n  openOnHover?: boolean;\n  closeDelay?: number;\n  theme?: \"system\" | \"light\" | \"dark\";\n  accent?: string;\n  width?: number;\n  radius?: number;\n  density?: \"compact\" | \"comfortable\";\n  stiffness?: number;\n  damping?: number;\n  duration?: number;\n  showAssignees?: boolean;\n  showDueDate?: boolean;\n  showFooter?: boolean;\n  showSectionLabels?: boolean;\n  className?: string;\n  onOpen?: () => void;\n}\n\nexport const WORKFLOW_ASSIGNEES: WorkflowAssignee[] = [\n  { id: \"mara\", name: \"Mara\", color: \"#91c788\" },\n  { id: \"jonas\", name: \"Jonas\", color: \"#a095d7\" },\n  { id: \"priya\", name: \"Priya\", color: \"#8ca6dc\" },\n];\n\nexport const WORKFLOW_SECTIONS: WorkflowSection[] = [\n  {\n    id: \"setup\",\n    label: \"Getting started\",\n    tasks: [\n      {\n        id: \"welcome\",\n        label: \"Welcome screen\",\n        done: true,\n        assigneeId: \"mara\",\n      },\n      {\n        id: \"workspace\",\n        label: \"Workspace setup\",\n        done: true,\n        assigneeId: \"jonas\",\n      },\n      {\n        id: \"invite\",\n        label: \"Invite teammates\",\n        done: true,\n        assigneeId: \"mara\",\n      },\n    ],\n  },\n  {\n    id: \"follow-through\",\n    label: \"Follow through\",\n    tasks: [\n      { id: \"checklist\", label: \"First-run checklist\", assigneeId: \"priya\" },\n      { id: \"emails\", label: \"Lifecycle emails\", assigneeId: \"jonas\" },\n    ],\n  },\n];\n\nconst focusClass =\n  \"outline-none focus-visible:ring-2 focus-visible:ring-[var(--wf-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wf-surface)]\";\nconst bounded = (value: number, min: number, max: number, fallback: number) =>\n  Number.isFinite(value) ? gsap.utils.clamp(min, max, value) : fallback;\nconst themeTokens = [\n  [\"surface\", \"card\", \"#ffffff\", \"#222222\"],\n  [\"ink\", \"foreground\", \"#252525\", \"#eeeeee\"],\n  [\"muted\", \"muted-foreground\", \"#858583\", \"#92928f\"],\n  [\"line\", \"border\", \"#dededb\", \"#484845\"],\n  [\"hover\", \"muted\", \"#f4f4f2\", \"#2c2c2a\"],\n  [\"outer\", \"muted\", \"#eaeae7\", \"#1b1b1a\"],\n] as const;\n\nfunction WorkflowRow({\n  task,\n  person,\n  dimmed,\n  onToggle,\n  duration,\n  ease,\n}: {\n  task: WorkflowTask;\n  person?: WorkflowAssignee;\n  dimmed: boolean;\n  onToggle: () => void;\n  duration: number;\n  ease: (progress: number) => number;\n}) {\n  const rootRef = useRef<HTMLButtonElement>(null);\n  const mounted = useRef(false);\n  const [initialDone] = useState(!!task.done);\n  const reduced = useReducedMotion();\n  useGSAP(\n    () => {\n      const root = rootRef.current;\n      if (!root) return;\n      const time = reduced || !mounted.current ? 0 : duration;\n      gsap.to(root, {\n        opacity: dimmed ? 0.4 : 1,\n        duration: time * 0.5,\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelector(\"[data-check]\"), {\n        scale: task.done ? 1 : 0,\n        duration: time * 0.65,\n        ease,\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelector(\"[data-tick]\"), {\n        strokeDashoffset: task.done ? 0 : 1,\n        duration: time * 0.45,\n        ease: \"power2.out\",\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelector(\"[data-label]\"), {\n        opacity: task.done ? 0.58 : 1,\n        duration: time * 0.55,\n        ease: \"power2.out\",\n        overwrite: \"auto\",\n      });\n      mounted.current = true;\n    },\n    { scope: rootRef, dependencies: [task.done, dimmed, duration, ease, reduced] },\n  );\n\n  return (\n    <button\n      ref={rootRef}\n      type=\"button\"\n      role=\"checkbox\"\n      aria-checked={!!task.done}\n      aria-label={`${task.label}${person ? `, assigned to ${person.name}` : \"\"}`}\n      onClick={onToggle}\n      data-slot=\"workflow-task\"\n      data-done={!!task.done}\n      className={cn(\n        \"group/task relative z-10 flex w-full cursor-pointer pointer-coarse:[--wf-touch-height:44px] items-center gap-2.5 rounded-md px-2 text-left\",\n        focusClass,\n      )}\n      style={{\n        minHeight: \"max(var(--wf-row-height), var(--wf-touch-height, 0px))\",\n      }}\n    >\n      <span\n        className=\"relative flex size-3.5 shrink-0 items-center justify-center rounded-full border border-[var(--wf-line)]\"\n        aria-hidden=\"true\"\n      >\n        <span\n          data-check\n          className=\"absolute -inset-px rounded-full bg-[var(--wf-ink)]\"\n          style={{ transform: `scale(${initialDone ? 1 : 0})` }}\n        />\n        <svg viewBox=\"0 0 14 14\" className=\"relative size-3.5 text-[var(--wf-surface)]\" fill=\"none\">\n          <path\n            data-tick\n            d=\"m4 7 2 2 4-4\"\n            pathLength=\"1\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.65\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            strokeDasharray=\"1\"\n            strokeDashoffset={initialDone ? 0 : 1}\n          />\n        </svg>\n      </span>\n      <span data-label className=\"min-w-0 flex-1 py-1.5 text-[12px] leading-[1.4] wrap-anywhere\">\n        {task.label}\n      </span>\n      {person && (\n        <span\n          aria-hidden=\"true\"\n          title={person.name}\n          data-row-person\n          className=\"flex size-4 shrink-0 items-center justify-center rounded-[4px] text-[9px] font-semibold opacity-0\"\n          style={{\n            background: `color-mix(in srgb, ${person.color ?? \"var(--wf-accent)\"}, transparent 80%)`,\n            color: person.color ?? \"var(--wf-accent)\",\n          }}\n        >\n          {person.name.slice(0, 1)}\n        </span>\n      )}\n    </button>\n  );\n}\n\nexport function WorkflowCard({\n  title = \"Onboarding flow\",\n  description,\n  dueDate = \"Oct 24\",\n  sections,\n  defaultSections = WORKFLOW_SECTIONS,\n  onSectionsChange,\n  assignees = WORKFLOW_ASSIGNEES,\n  status,\n  defaultStatus = \"progress\",\n  onStatusChange,\n  expanded,\n  defaultExpanded = false,\n  onExpandedChange,\n  openOnHover = true,\n  closeDelay = 0.24,\n  theme = \"system\",\n  accent = \"#32966a\",\n  width = 320,\n  radius = 18,\n  density = \"comfortable\",\n  stiffness = 105,\n  damping = 24,\n  duration = 0.75,\n  showAssignees = true,\n  showDueDate = true,\n  showFooter = true,\n  showSectionLabels = true,\n  className,\n  onOpen,\n}: WorkflowCardProps) {\n  const [ownSections, setOwnSections] = useState(defaultSections);\n  const [ownStatus, setOwnStatus] = useState(defaultStatus);\n  const [ownExpanded, setOwnExpanded] = useState(defaultExpanded);\n  const [pickerOpen, setPickerOpen] = useState(false);\n  const [hovered, setHovered] = useState(false);\n  const [filter, setFilter] = useState<string | null>(null);\n  const [announcement, setAnnouncement] = useState(\"\");\n  const reduced = useReducedMotion();\n  const rootRef = useRef<HTMLElement>(null);\n  const panelRef = useRef<HTMLDivElement>(null);\n  const contentRef = useRef<HTMLDivElement>(null);\n  const pickerRef = useRef<HTMLDivElement>(null);\n  const pickerContentRef = useRef<HTMLFieldSetElement>(null);\n  const highlightRef = useRef<HTMLSpanElement>(null);\n  const hoveredRowRef = useRef<HTMLElement | null>(null);\n  const pressedRef = useRef<HTMLElement | null>(null);\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const statusRef = useRef<HTMLButtonElement>(null);\n  const closeRef = useRef<gsap.core.Tween | null>(null);\n  const syncRef = useRef<((immediate?: boolean) => void) | null>(null);\n  const id = useId();\n  const currentSections = sections ?? ownSections;\n  const currentStatus = status ?? ownStatus;\n  const isOpen = expanded ?? ownExpanded;\n  const tasks = currentSections.flatMap((section) => section.tasks);\n  const complete = tasks.filter((task) => task.done).length;\n  const segmentCount = Math.max(1, Math.min(tasks.length, 20));\n  const filteredPerson = showAssignees\n    ? assignees.find((person) => person.id === filter)\n    : undefined;\n  const visibleTasks = filteredPerson\n    ? tasks.filter((task) => task.assigneeId === filteredPerson.id)\n    : tasks;\n  const visibleComplete = visibleTasks.filter((task) => task.done).length;\n  const statusSpec = WORKFLOW_STATUSES[currentStatus];\n  const time = bounded(duration, 0.3, 1.2, 0.75);\n  const tension = bounded(stiffness, 60, 240, 105);\n  const friction = bounded(damping, 14, 36, 24);\n  const ease = useMemo(() => {\n    const response = spring({\n      keyframes: [0, 1],\n      stiffness: tension,\n      damping: friction,\n      mass: 1,\n    });\n    const settle = Math.max(0.8, 16 / friction);\n    const end = response.next(settle * 1000).value;\n    return (progress: number) =>\n      progress === 1 ? 1 : response.next(progress * settle * 1000).value / end;\n  }, [tension, friction]);\n\n  const changeExpanded = (next: boolean) => {\n    closeRef.current?.kill();\n    if (expanded === undefined) setOwnExpanded(next);\n    onExpandedChange?.(next);\n    if (!next) setPickerOpen(false);\n  };\n\n  const { contextSafe } = useGSAP(\n    () => {\n      const root = rootRef.current;\n      const panel = panelRef.current;\n      const content = contentRef.current;\n      if (!root || !panel || !content) return;\n      const picker = pickerRef.current;\n      const pickerContent = pickerContentRef.current;\n      if (!picker || !pickerContent) return;\n      const media = gsap.matchMedia();\n      media.add(\n        {\n          reduce: \"(prefers-reduced-motion: reduce)\",\n          animate: \"(prefers-reduced-motion: no-preference)\",\n        },\n        (context) => {\n          let reveal: gsap.core.Timeline | null = null;\n          const sync = context.add(\"sync\", (immediate = false) => {\n            const open = root.dataset.expanded === \"true\";\n            const choosing = open && root.dataset.pickerOpen === \"true\";\n            const instant = immediate || context.conditions?.reduce;\n            const pickerHeight = choosing ? pickerContent.offsetHeight : 0;\n            reveal?.kill();\n            reveal = gsap\n              .timeline({\n                defaults: {\n                  duration: instant ? 0 : time,\n                  ease: open ? ease : \"power3.out\",\n                  overwrite: \"auto\",\n                },\n              })\n              .to(panel, { height: open ? content.offsetHeight + pickerHeight : 0 }, 0)\n              .to(picker, { height: pickerHeight }, 0)\n              .to(\n                pickerContent,\n                {\n                  opacity: choosing ? 1 : 0,\n                  y: choosing ? 0 : -3,\n                  duration: instant ? 0 : time * 0.48,\n                },\n                0,\n              )\n              .to(\n                content,\n                {\n                  opacity: open ? 1 : 0,\n                  duration: instant ? 0 : time * (open ? 0.65 : 0.4),\n                  ease: \"power2.out\",\n                },\n                0,\n              );\n          });\n          syncRef.current = (immediate) => sync(immediate);\n          sync(true);\n          const observer = new ResizeObserver(() => sync());\n          observer.observe(content);\n          observer.observe(pickerContent);\n          return () => {\n            observer.disconnect();\n            reveal?.kill();\n            syncRef.current = null;\n          };\n        },\n      );\n      return () => {\n        closeRef.current?.kill();\n        media.revert();\n      };\n    },\n    { scope: rootRef, dependencies: [time, ease], revertOnUpdate: true },\n  );\n\n  useGSAP(\n    (context) => {\n      context.ignore(() => syncRef.current?.());\n    },\n    { scope: rootRef, dependencies: [isOpen, pickerOpen, time, ease] },\n  );\n\n  useGSAP(\n    () => {\n      gsap.to(rootRef.current?.querySelectorAll(\"[data-segment]\") ?? [], {\n        opacity: (index: number) =>\n          index < Math.round((visibleComplete / Math.max(1, visibleTasks.length)) * segmentCount)\n            ? hovered\n              ? 1\n              : 0.82\n            : hovered\n              ? 0.28\n              : 0.14,\n        duration: reduced ? 0 : time * 0.6,\n        stagger: reduced ? 0 : 0.018,\n        ease: \"power2.out\",\n        overwrite: \"auto\",\n      });\n    },\n    {\n      scope: rootRef,\n      dependencies: [visibleComplete, visibleTasks.length, segmentCount, time, hovered, reduced],\n    },\n  );\n\n  const scheduleClose = () =>\n    contextSafe(() => {\n      closeRef.current?.kill();\n      closeRef.current = gsap.delayedCall(bounded(closeDelay, 0, 1, 0.24), () => {\n        if (\n          !rootRef.current?.contains(document.activeElement) ||\n          !document.activeElement?.matches(\":focus-visible\")\n        )\n          changeExpanded(false);\n      });\n    })();\n  const press = contextSafe((target: HTMLElement, down: boolean) => {\n    if (reduced) return;\n    gsap.to(target, {\n      scale: down ? 0.985 : 1,\n      duration: down ? 0.12 : time * 0.6,\n      ease: down ? \"power2.out\" : ease,\n      overwrite: \"auto\",\n    });\n  });\n  const highlightRow = (target: HTMLElement | null) =>\n    contextSafe(() => {\n      const highlight = highlightRef.current;\n      const content = contentRef.current;\n      if (!highlight || !content || hoveredRowRef.current === target) return;\n      const previous = hoveredRowRef.current;\n      hoveredRowRef.current = target;\n      const previousPerson = previous?.querySelector(\"[data-row-person]\");\n      if (previousPerson)\n        gsap.to(previousPerson, {\n          opacity: 0,\n          duration: reduced ? 0 : 0.25,\n          overwrite: \"auto\",\n        });\n      if (!target) {\n        gsap.to(highlight, {\n          opacity: 0,\n          duration: reduced ? 0 : 0.3,\n          overwrite: \"auto\",\n        });\n        return;\n      }\n      const rect = target.getBoundingClientRect();\n      const parent = content.getBoundingClientRect();\n      if (!previous)\n        gsap.set(highlight, {\n          x: rect.left - parent.left,\n          y: rect.top - parent.top,\n          width: rect.width,\n          height: rect.height,\n        });\n      gsap.to(highlight, {\n        x: rect.left - parent.left,\n        y: rect.top - parent.top,\n        width: rect.width,\n        height: rect.height,\n        opacity: 1,\n        duration: reduced ? 0 : 0.38,\n        ease: \"power3.out\",\n        overwrite: \"auto\",\n      });\n      const person = target.querySelector(\"[data-row-person]\");\n      if (person)\n        gsap.to(person, {\n          opacity: 1,\n          duration: reduced ? 0 : 0.3,\n          overwrite: \"auto\",\n        });\n    })();\n  const releasePress = () => {\n    if (pressedRef.current) press(pressedRef.current, false);\n    pressedRef.current = null;\n  };\n\n  useGSAP(\n    () => {\n      const root = rootRef.current;\n      if (!root) return;\n      gsap.to(root.querySelectorAll(\"[data-assignee]\"), {\n        opacity: (_, target: HTMLElement) =>\n          filteredPerson && target.dataset.assignee !== filter ? 0.45 : 1,\n        duration: reduced ? 0 : 0.4,\n        ease: \"power2.out\",\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelectorAll(\"[data-assignee-ring]\"), {\n        opacity: (_, target: HTMLElement) =>\n          target.parentElement?.dataset.assignee === filteredPerson?.id ? 1 : 0,\n        scale: (_, target: HTMLElement) =>\n          target.parentElement?.dataset.assignee === filteredPerson?.id ? 1 : 0.86,\n        duration: reduced ? 0 : 0.4,\n        ease,\n        overwrite: \"auto\",\n      });\n      gsap.to(root.querySelectorAll(\"[data-status-ring]\"), {\n        opacity: (_, target: HTMLElement) => (target.dataset.statusRing === currentStatus ? 1 : 0),\n        duration: reduced ? 0 : 0.32,\n        overwrite: \"auto\",\n      });\n    },\n    {\n      scope: rootRef,\n      dependencies: [filter, filteredPerson?.id, currentStatus, showAssignees, ease, reduced],\n    },\n  );\n\n  const toggleTask = (sectionId: string, taskId: string) => {\n    const next = currentSections.map((section) =>\n      section.id !== sectionId\n        ? section\n        : {\n            ...section,\n            tasks: section.tasks.map((task) =>\n              task.id !== taskId ? task : { ...task, done: !task.done },\n            ),\n          },\n    );\n    if (sections === undefined) setOwnSections(next);\n    onSectionsChange?.(next);\n    const task = next\n      .find((section) => section.id === sectionId)\n      ?.tasks.find((item) => item.id === taskId);\n    setAnnouncement(`${task?.label} ${task?.done ? \"completed\" : \"reopened\"}`);\n  };\n  const footerLabel = filteredPerson\n    ? `${filteredPerson.name} · ${visibleComplete} of ${visibleTasks.length} done`\n    : tasks.length === 0\n      ? \"No tasks yet\"\n      : complete === tasks.length\n        ? \"All done. Nicely done.\"\n        : `${tasks.length - complete} left to do`;\n  const inherited = theme === \"system\";\n  const light = theme !== \"dark\";\n  const curve = bounded(radius, 8, 28, 18);\n  const colors = Object.fromEntries(\n    themeTokens.map(([name, token, lightColor, darkColor]) => [\n      `--wf-${name}`,\n      inherited ? `var(--${token}, ${lightColor})` : light ? lightColor : darkColor,\n    ]),\n  );\n\n  return (\n    <article\n      ref={rootRef}\n      data-slot=\"workflow-card\"\n      data-expanded={isOpen}\n      data-picker-open={pickerOpen}\n      data-theme={theme}\n      aria-labelledby={`${id}-title`}\n      className={cn(\"relative isolate w-full max-w-full font-sans\", className)}\n      style={\n        {\n          width: bounded(width, 260, 480, 320),\n          borderRadius: curve + 3,\n          \"--wf-accent\": accent,\n          ...colors,\n          \"--wf-row-height\": density === \"compact\" ? \"28px\" : \"34px\",\n          background: \"var(--wf-outer)\",\n          padding: 3,\n          color: \"var(--wf-ink)\",\n          boxShadow: light\n            ? \"0 3px 8px -4px #00000018, inset 0 0 0 1px #00000004\"\n            : \"0 3px 12px -6px #00000080, inset 0 0 0 1px #ffffff04\",\n        } as CSSProperties\n      }\n      onPointerEnter={(event) => {\n        closeRef.current?.kill();\n        if (event.pointerType !== \"touch\") setHovered(true);\n        if (openOnHover && event.pointerType !== \"touch\") changeExpanded(true);\n      }}\n      onPointerLeave={() => {\n        setHovered(false);\n        highlightRow(null);\n        releasePress();\n        if (openOnHover) scheduleClose();\n      }}\n      onPointerOver={(event) => {\n        const row = (event.target as HTMLElement).closest<HTMLElement>(\n          '[data-slot=\"workflow-task\"]',\n        );\n        if (row) highlightRow(row);\n      }}\n      onFocusCapture={(event) => {\n        closeRef.current?.kill();\n        highlightRow(\n          (event.target as HTMLElement).closest<HTMLElement>('[data-slot=\"workflow-task\"]'),\n        );\n        if (event.target.matches(\":focus-visible\")) setHovered(true);\n        if (\n          openOnHover &&\n          event.target.matches(\":focus-visible\") &&\n          !event.currentTarget.contains(event.relatedTarget)\n        )\n          changeExpanded(true);\n      }}\n      onBlurCapture={(event) => {\n        if (!event.currentTarget.contains(event.relatedTarget)) {\n          setHovered(false);\n          highlightRow(null);\n        }\n        if (openOnHover && !event.currentTarget.contains(event.relatedTarget)) scheduleClose();\n      }}\n      onKeyDown={(event) => {\n        if (event.key !== \"Escape\") return;\n        event.stopPropagation();\n        if (pickerOpen) {\n          setPickerOpen(false);\n          statusRef.current?.focus();\n        } else {\n          changeExpanded(false);\n          triggerRef.current?.focus();\n        }\n      }}\n      onPointerDown={(event) => {\n        const target = (event.target as HTMLElement).closest<HTMLElement>(\"button\");\n        if (target && target.dataset.slot !== \"workflow-task\") {\n          pressedRef.current = target;\n          press(target, true);\n        }\n      }}\n      onPointerUp={releasePress}\n      onPointerCancel={releasePress}\n      onLostPointerCapture={releasePress}\n    >\n      <div\n        className=\"relative overflow-hidden bg-[var(--wf-surface)]\"\n        style={{\n          borderRadius: curve,\n          boxShadow: light ? \"0 1px 3px #00000008\" : \"0 1px 3px #00000030\",\n        }}\n      >\n        <header className=\"flex items-center gap-2.5 px-3 py-3\">\n          <button\n            ref={triggerRef}\n            type=\"button\"\n            aria-expanded={isOpen}\n            aria-controls={`${id}-panel`}\n            aria-label={`${isOpen ? \"Collapse\" : \"Expand\"} ${title}`}\n            onClick={() => changeExpanded(!isOpen)}\n            className={cn(\n              \"relative flex size-8 shrink-0 cursor-pointer pointer-coarse:size-10 items-center justify-center rounded-full\",\n              focusClass,\n            )}\n          >\n            <svg\n              viewBox=\"0 0 36 36\"\n              className=\"absolute inset-0 size-full -rotate-90\"\n              aria-hidden=\"true\"\n            >\n              {Array.from({ length: segmentCount }, (_, index) => {\n                const count = segmentCount;\n                const circumference = 2 * Math.PI * 14;\n                return (\n                  <circle\n                    key={index}\n                    data-segment\n                    cx=\"18\"\n                    cy=\"18\"\n                    r=\"14\"\n                    fill=\"none\"\n                    stroke=\"var(--wf-ink)\"\n                    strokeWidth=\"2.7\"\n                    strokeLinecap=\"round\"\n                    strokeDasharray={`${Math.max(1, circumference / count - 3.8)} ${circumference}`}\n                    strokeDashoffset={(-index * circumference) / count}\n                    style={{ opacity: 0.16 }}\n                  />\n                );\n              })}\n            </svg>\n            <span aria-hidden=\"true\" className=\"text-[11px] font-semibold tabular-nums\">\n              {complete}\n            </span>\n          </button>\n          <div className=\"min-w-0 flex-1\">\n            <h3\n              id={`${id}-title`}\n              className=\"text-[13px] leading-5 font-semibold tracking-[-0.025em]\"\n            >\n              <button\n                type=\"button\"\n                aria-expanded={isOpen}\n                aria-controls={`${id}-panel`}\n                onClick={() => changeExpanded(!isOpen)}\n                className={cn(\n                  \"block max-w-full cursor-pointer rounded-sm text-left wrap-anywhere\",\n                  focusClass,\n                )}\n              >\n                {title}\n              </button>\n            </h3>\n            <div className=\"flex flex-wrap items-center gap-x-1.5 gap-y-1 text-[10px] leading-4\">\n              <button\n                ref={statusRef}\n                type=\"button\"\n                aria-expanded={pickerOpen && isOpen}\n                aria-controls={`${id}-status`}\n                aria-label={`Change status, currently ${statusSpec.label}`}\n                onClick={() => {\n                  changeExpanded(true);\n                  setPickerOpen(!pickerOpen);\n                }}\n                className={cn(\n                  \"flex w-24 shrink-0 cursor-pointer items-center gap-1 rounded px-1.5\",\n                  focusClass,\n                )}\n                style={{\n                  color: `color-mix(in srgb, ${statusSpec.color}, var(--wf-ink) 18%)`,\n                  background: `color-mix(in srgb, ${statusSpec.color}, transparent 86%)`,\n                }}\n              >\n                <statusSpec.Icon size={10} strokeWidth={2} aria-hidden=\"true\" />\n                {statusSpec.label}\n              </button>\n              {showDueDate && dueDate && (\n                <span className=\"text-[var(--wf-muted)]\">Due {dueDate}</span>\n              )}\n            </div>\n          </div>\n          {showAssignees && assignees.length > 0 && (\n            <div className=\"flex shrink-0 -space-x-1.5\" aria-label=\"Filter by teammate\">\n              {assignees.slice(0, 5).map((person) => (\n                <button\n                  key={person.id}\n                  data-assignee={person.id}\n                  type=\"button\"\n                  title={`${filter === person.id ? \"Show everyone\" : `Filter ${person.name}'s tasks`}`}\n                  aria-label={`Filter ${person.name}'s tasks`}\n                  aria-pressed={filter === person.id}\n                  onClick={() => {\n                    changeExpanded(true);\n                    setFilter(filter === person.id ? null : person.id);\n                  }}\n                  className={cn(\n                    \"relative flex size-6 cursor-pointer pointer-coarse:size-8 items-center justify-center rounded-full border-2 border-[var(--wf-surface)] text-[9px] font-semibold hover:z-10\",\n                    focusClass,\n                  )}\n                  style={{\n                    background: `color-mix(in srgb, ${person.color ?? accent}, var(--wf-surface) ${light ? 60 : 75}%)`,\n                    color: person.color ?? accent,\n                    zIndex: filter === person.id ? 10 : undefined,\n                  }}\n                >\n                  <span\n                    data-assignee-ring\n                    aria-hidden=\"true\"\n                    className=\"pointer-events-none absolute -inset-0.5 rounded-full border border-current opacity-0\"\n                  />\n                  {person.name.slice(0, 1)}\n                </button>\n              ))}\n            </div>\n          )}\n        </header>\n        <div\n          ref={panelRef}\n          id={`${id}-panel`}\n          inert={!isOpen}\n          aria-hidden={!isOpen}\n          className=\"overflow-hidden\"\n          style={{ height: defaultExpanded ? \"auto\" : 0 }}\n        >\n          <div\n            ref={pickerRef}\n            className=\"overflow-hidden\"\n            inert={!pickerOpen || !isOpen}\n            aria-hidden={!pickerOpen || !isOpen}\n            style={{ height: 0 }}\n          >\n            <fieldset\n              ref={pickerContentRef}\n              id={`${id}-status`}\n              className=\"mx-3 flex flex-wrap gap-1.5 border-b border-[var(--wf-line)] pb-3 pt-1\"\n            >\n              <legend className=\"sr-only\">Workflow status</legend>\n              {(Object.entries(WORKFLOW_STATUSES) as [WorkflowStatus, typeof statusSpec][]).map(\n                ([key, spec]) => (\n                  <label\n                    key={key}\n                    className=\"relative flex cursor-pointer items-center gap-1 rounded-md px-2 py-1.5 text-[10px]\"\n                    style={{\n                      color: `color-mix(in srgb, ${spec.color}, var(--wf-ink) 18%)`,\n                      background: `color-mix(in srgb, ${spec.color}, transparent 88%)`,\n                    }}\n                  >\n                    <input\n                      disabled={!pickerOpen || !isOpen}\n                      type=\"radio\"\n                      name={`${id}-workflow-status`}\n                      value={key}\n                      checked={currentStatus === key}\n                      onChange={() => {\n                        if (status === undefined) setOwnStatus(key);\n                        onStatusChange?.(key);\n                        setPickerOpen(false);\n                        setAnnouncement(`Status changed to ${spec.label}`);\n                        statusRef.current?.focus();\n                      }}\n                      className=\"peer absolute inset-0 z-10 size-full cursor-pointer opacity-0\"\n                    />\n                    <span\n                      data-status-ring={key}\n                      aria-hidden=\"true\"\n                      className=\"pointer-events-none absolute inset-0 rounded-md border border-current opacity-0\"\n                    />\n                    <span\n                      aria-hidden=\"true\"\n                      className=\"pointer-events-none absolute inset-0 rounded-md peer-focus-visible:ring-2 peer-focus-visible:ring-current\"\n                    />\n                    <spec.Icon size={11} aria-hidden=\"true\" />\n                    {spec.label}\n                  </label>\n                ),\n              )}\n            </fieldset>\n          </div>\n          <div ref={contentRef} className=\"relative\" onPointerLeave={() => highlightRow(null)}>\n            <span\n              ref={highlightRef}\n              data-hover-highlight\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute top-0 left-0 rounded-md bg-[var(--wf-hover)] opacity-0\"\n            />\n            <div className=\"relative px-2 pb-2\">\n              {description && (\n                <p className=\"px-2 pb-3 text-[11px] leading-relaxed text-[var(--wf-muted)] wrap-anywhere\">\n                  {description}\n                </p>\n              )}\n              {currentSections.map((section, index) => (\n                <section\n                  data-section\n                  key={section.id}\n                  aria-label={section.label ?? `Checklist ${index + 1}`}\n                  className={index > 0 ? \"mt-2 border-t border-[var(--wf-line)] pt-2\" : \"\"}\n                >\n                  {showSectionLabels && section.label && (\n                    <h4 className=\"mb-1 flex items-center justify-between gap-2 px-2 text-[9px] font-medium tracking-[0.08em] text-[var(--wf-muted)] uppercase\">\n                      <span className=\"min-w-0 wrap-anywhere\">{section.label}</span>\n                      <span className=\"shrink-0 tabular-nums\">\n                        {section.tasks.filter((task) => task.done).length}/{section.tasks.length}\n                      </span>\n                    </h4>\n                  )}\n                  {section.tasks.map((task) => (\n                    <WorkflowRow\n                      key={task.id}\n                      task={task}\n                      person={assignees.find((person) => person.id === task.assigneeId)}\n                      dimmed={!!filteredPerson && task.assigneeId !== filter}\n                      onToggle={() => toggleTask(section.id, task.id)}\n                      duration={time}\n                      ease={ease}\n                    />\n                  ))}\n                </section>\n              ))}\n              {tasks.length === 0 && (\n                <p className=\"px-2 py-4 text-[12px] text-[var(--wf-muted)]\">\n                  A fresh start. Add your first task.\n                </p>\n              )}\n            </div>\n            {showFooter && (\n              <footer className=\"flex min-h-10 items-center justify-between gap-2 rounded-b-[inherit] bg-[var(--wf-hover)] px-4 py-2 text-[10px] text-[var(--wf-muted)]\">\n                <span className=\"min-w-0 wrap-anywhere\">{footerLabel}</span>\n                <button\n                  type=\"button\"\n                  aria-label={onOpen ? `Open ${title}` : `Collapse ${title}`}\n                  onClick={() => (onOpen ? onOpen() : changeExpanded(false))}\n                  className={cn(\n                    \"group/open flex shrink-0 cursor-pointer items-center gap-1 rounded-sm py-1 font-medium text-[var(--wf-ink)]\",\n                    focusClass,\n                  )}\n                >\n                  {onOpen ? \"Open\" : \"Close\"}\n                  <ArrowRight size={12} aria-hidden=\"true\" />\n                </button>\n              </footer>\n            )}\n          </div>\n        </div>\n      </div>\n      <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n        {announcement}\n      </span>\n    </article>\n  );\n}\n\nexport default WorkflowCard;\n",
      "type": "registry:ui",
      "target": "@ui/workflow-card.tsx"
    }
  ],
  "type": "registry:ui",
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "title": "Workflow Card"
}
