{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "article-2",
  "description": "A long-form article with a sticky table-of-contents sidebar that highlights the active section as you scroll (scroll-spy). Collapses to a single column on mobile. Ideal for documentation and in-depth guides.",
  "dependencies": [
    "@remixicon/react",
    "@base-ui/react"
  ],
  "registryDependencies": [
    "avatar",
    "badge"
  ],
  "files": [
    {
      "path": "registry/blocks/article/2/article-block.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { RiTimeLine } from \"@remixicon/react\"\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { cn } from \"@/lib/utils\"\n\ntype Section = { id: string; title: string; body: string[] }\n\nconst sections: Section[] = [\n  {\n    id: \"overview\",\n    title: \"Overview\",\n    body: [\n      \"Design tokens are the single source of truth for the visual language of a product. Instead of hard-coding colors and spacing in components, you reference named values that can change in one place.\",\n      \"This guide walks through how we structure tokens, how they map to themes, and how to adopt them without a disruptive migration.\",\n    ],\n  },\n  {\n    id: \"structure\",\n    title: \"Token structure\",\n    body: [\n      \"We split tokens into three tiers: primitives, semantic tokens, and component tokens. Primitives are raw values, semantic tokens describe intent, and component tokens bind intent to a specific part.\",\n      \"Keeping these tiers separate means a rebrand touches primitives only, while component code keeps referring to the same semantic names.\",\n    ],\n  },\n  {\n    id: \"theming\",\n    title: \"Theming\",\n    body: [\n      \"Each theme remaps the semantic tier. A dark theme overrides background and foreground tokens, and every component that reads those names updates automatically with no per-component work.\",\n      \"Because the contract is the token name, you can ship a new theme without touching a single component file.\",\n    ],\n  },\n  {\n    id: \"adoption\",\n    title: \"Adoption\",\n    body: [\n      \"Roll out tokens incrementally. Start with color, then spacing, then typography, shipping each tier behind the same review process you already use.\",\n      \"Within a few sprints the hard-coded values are gone and every surface speaks the same visual language.\",\n    ],\n  },\n]\n\nexport default function ArticleBlock() {\n  const [active, setActive] = React.useState(sections[0].id)\n  const scrollRef = React.useRef<HTMLDivElement>(null)\n\n  React.useEffect(() => {\n    const root = scrollRef.current\n    if (!root) return\n\n    const update = () => {\n      const rootTop = root.getBoundingClientRect().top\n      const threshold = root.clientHeight * 0.3\n      let current = sections[0].id\n      for (const section of sections) {\n        const el = document.getElementById(section.id)\n        if (el && el.getBoundingClientRect().top - rootTop <= threshold) {\n          current = section.id\n        }\n      }\n      const atBottom =\n        root.scrollTop + root.clientHeight >= root.scrollHeight - 48\n      setActive(atBottom ? sections[sections.length - 1].id : current)\n    }\n\n    update()\n    root.addEventListener(\"scroll\", update, { passive: true })\n    return () => root.removeEventListener(\"scroll\", update)\n  }, [])\n\n  return (\n    <section className=\"flex min-h-svh w-full justify-center bg-background px-6 py-16 text-foreground\">\n      <div className=\"mx-auto w-full max-w-4xl\">\n        <div className=\"max-w-2xl\">\n          <Badge variant=\"secondary\" className=\"mb-4\">\n            Guide\n          </Badge>\n          <h1 className=\"text-3xl font-bold tracking-tight text-balance\">\n            A practical guide to design tokens\n          </h1>\n          <div className=\"mt-5 flex items-center gap-3\">\n            <Avatar className=\"size-9\">\n              <AvatarImage\n                src=\"https://i.pravatar.cc/80?img=32\"\n                alt=\"Priya Nair\"\n                className=\"grayscale\"\n              />\n              <AvatarFallback>PN</AvatarFallback>\n            </Avatar>\n            <div className=\"flex flex-col\">\n              <span className=\"text-sm font-medium\">Priya Nair</span>\n              <span className=\"flex items-center gap-3 text-xs text-muted-foreground\">\n                <span className=\"tabular-nums\">Jun 12, 2026</span>\n                <span className=\"flex items-center gap-1\">\n                  <RiTimeLine className=\"size-3.5\" aria-hidden=\"true\" />8 Min\n                  Read\n                </span>\n              </span>\n            </div>\n          </div>\n        </div>\n\n        <div className=\"mt-10 flex flex-col gap-10 md:flex-row md:gap-12\">\n          <div\n            ref={scrollRef}\n            className=\"flex min-w-0 flex-1 flex-col gap-10 md:max-h-[26rem] md:overflow-y-auto md:pr-5\"\n          >\n            {sections.map((section) => (\n              <section key={section.id} id={section.id} className=\"scroll-mt-2\">\n                <h2 className=\"text-xl font-semibold tracking-tight\">\n                  {section.title}\n                </h2>\n                <div className=\"mt-3 flex flex-col gap-4 text-[15px]/relaxed text-foreground/80\">\n                  {section.body.map((paragraph, pIndex) => (\n                    <p key={`${section.id}-${pIndex}`}>{paragraph}</p>\n                  ))}\n                </div>\n              </section>\n            ))}\n          </div>\n\n          <aside className=\"hidden w-52 shrink-0 md:block\">\n            <p className=\"mb-3 text-xs font-medium tracking-wide text-muted-foreground uppercase\">\n              On This Page\n            </p>\n            <nav className=\"flex flex-col border-l border-border\">\n              {sections.map((section) => (\n                <a\n                  key={section.id}\n                  href={`#${section.id}`}\n                  onClick={() => setActive(section.id)}\n                  aria-current={active === section.id ? \"page\" : undefined}\n                  className={cn(\n                    \"-ml-px border-l py-1.5 pl-4 text-sm transition-colors\",\n                    active === section.id\n                      ? \"border-primary font-medium text-foreground\"\n                      : \"border-transparent text-muted-foreground hover:text-foreground\"\n                  )}\n                >\n                  {section.title}\n                </a>\n              ))}\n            </nav>\n          </aside>\n        </div>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "height": "70vh",
    "tier": "free"
  },
  "categories": [
    "article"
  ],
  "type": "registry:block"
}