{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "social-proof-footer",
  "title": "Logo Wall Footer",
  "description": "A customer logo wall — marquee or plotted grid — over stats and link columns.",
  "files": [
    {
      "path": "components/spectrumui/blocks/footers/social-proof-footer.tsx",
      "content": "'use client';\n\nimport { useEffect, useRef, useState, useSyncExternalStore, type SVGProps } from 'react';\nimport { cn } from '@/lib/utils';\nimport { IconStar, FooterBar, type FooterSocial } from './footer-kit';\n\n/**\n * Twelve customer marks, invented and drawn here rather than imported.\n *\n * A logo wall that ships with real companies in it looks convincing on this\n * page and is wrong in every install — whoever copies the block inherits\n * somebody else's trademarks and has to strip them before shipping. These are\n * drawn to the same register instead: one colour, `currentColor`, geometry\n * built from straight lines, rectangles and circles on a 24px grid so the set\n * stays optically consistent and crisp at 20px. Pass your own `customers` and\n * nothing else about the block changes.\n */\ntype MarkProps = SVGProps<SVGSVGElement>;\n\nfunction Mark({ children, ...props }: MarkProps & { children: React.ReactNode }) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n      {children}\n    </svg>\n  );\n}\n\nexport function MarkRidgeline(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <path d=\"M2 19.5h20L14.5 7l-3 4.7L8.2 5.5 2 19.5Z\" />\n    </Mark>\n  );\n}\n\nexport function MarkPostmark(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <rect\n        x=\"3\"\n        y=\"3\"\n        width=\"18\"\n        height=\"18\"\n        rx=\"5\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n      />\n      <circle cx=\"12\" cy=\"12\" r=\"3.25\" />\n    </Mark>\n  );\n}\n\nexport function MarkCalla(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <rect x=\"3\" y=\"3\" width=\"13\" height=\"13\" rx=\"3.5\" opacity=\"0.45\" />\n      <rect x=\"8\" y=\"8\" width=\"13\" height=\"13\" rx=\"3.5\" />\n    </Mark>\n  );\n}\n\nexport function MarkHollowCreek(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <circle cx=\"12\" cy=\"12\" r=\"8\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" />\n      <circle cx=\"12\" cy=\"12\" r=\"2.5\" />\n    </Mark>\n  );\n}\n\nexport function MarkTessellate(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <path d=\"M3 3h8.4v8.4H3V3Z\" />\n      <path d=\"M12.6 3H21v8.4h-8.4V3Z\" opacity=\"0.45\" />\n      <path d=\"M3 12.6h8.4V21H3v-8.4Z\" opacity=\"0.45\" />\n      <path d=\"M12.6 12.6H21V21h-8.4v-8.4Z\" />\n    </Mark>\n  );\n}\n\nexport function MarkBrightHarbour(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <path d=\"M12 2 21.5 11.5l-2.7 2.7L12 7.4l-6.8 6.8L2.5 11.5 12 2Z\" />\n      <path d=\"M12 11.8 19.2 19l-2.7 2.7L12 17.2l-4.5 4.5L4.8 19 12 11.8Z\" opacity=\"0.45\" />\n    </Mark>\n  );\n}\n\nexport function MarkNineYards(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      {[5, 12, 19].map((y) =>\n        [5, 12, 19].map((x) => (\n          <circle key={`${x}-${y}`} cx={x} cy={y} r={x === 12 && y === 12 ? 2.4 : 1.7} />\n        )),\n      )}\n    </Mark>\n  );\n}\n\nexport function MarkStudioMono(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <circle cx=\"12\" cy=\"12\" r=\"9\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.2\" />\n      <rect x=\"8.4\" y=\"8.4\" width=\"7.2\" height=\"7.2\" rx=\"2\" />\n    </Mark>\n  );\n}\n\nexport function MarkNorthwind(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <path d=\"M12 2 22 21.5 12 16.6 2 21.5 12 2Z\" />\n    </Mark>\n  );\n}\n\nexport function MarkCobalt(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M12 2 22 12 12 22 2 12 12 2Zm0 5.3L7.3 12l4.7 4.7 4.7-4.7L12 7.3Z\"\n      />\n    </Mark>\n  );\n}\n\nexport function MarkLinework(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <rect x=\"3\" y=\"9\" width=\"4\" height=\"12\" rx=\"2\" opacity=\"0.45\" />\n      <rect x=\"10\" y=\"3.5\" width=\"4\" height=\"17.5\" rx=\"2\" />\n      <rect x=\"17\" y=\"12.5\" width=\"4\" height=\"8.5\" rx=\"2\" opacity=\"0.45\" />\n    </Mark>\n  );\n}\n\nexport function MarkMeridian(props: MarkProps) {\n  return (\n    <Mark {...props}>\n      <circle cx=\"12\" cy=\"12\" r=\"9\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.2\" />\n      <path d=\"M3.4 9h17.2M3.4 15h17.2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.2\" />\n    </Mark>\n  );\n}\n\nexport const DEFAULT_CUSTOMERS: CustomerLogo[] = [\n  { name: 'Ridgeline', mark: MarkRidgeline },\n  { name: 'Postmark', mark: MarkPostmark },\n  { name: 'Calla', mark: MarkCalla },\n  { name: 'Hollow Creek', mark: MarkHollowCreek },\n  { name: 'Tessellate', mark: MarkTessellate },\n  { name: 'Bright Harbour', mark: MarkBrightHarbour },\n  { name: 'Nine Yards', mark: MarkNineYards },\n  { name: 'Studio Mono', mark: MarkStudioMono },\n  { name: 'Northwind', mark: MarkNorthwind },\n  { name: 'Cobalt', mark: MarkCobalt },\n  { name: 'Linework', mark: MarkLinework },\n  { name: 'Meridian', mark: MarkMeridian },\n];\n\nconst KEYFRAMES = `\n@keyframes su-proof-marquee { from { transform: translateX(0) } to { transform: translateX(-50%) } }\n@keyframes su-proof-cell { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } }\n`;\n\nexport type SocialProofFooterVariant = 'Marquee' | 'Grid';\n\nexport interface CustomerLogo {\n  name: string;\n  mark: React.ComponentType<SVGProps<SVGSVGElement>>;\n}\n\nexport interface ProofStat {\n  label: string;\n  value: number;\n  suffix?: string;\n}\n\nexport interface SocialProofFooterProps {\n  socials?: FooterSocial[];\n  brand: string;\n  customers?: CustomerLogo[];\n  stats: ProofStat[];\n  /** Link columns. A logo wall with no navigation under it is a banner. */\n  groups?: { title: string; links: { label: string; href: string }[] }[];\n  eyebrow?: string;\n  rating?: number;\n  ratingSource?: string;\n  links?: { label: string; href: string }[];\n  copyright?: string;\n  /** Seconds for one full pass of the marquee. Longer is calmer. */\n  speed?: number;\n  variant?: SocialProofFooterVariant;\n  className?: string;\n}\n\nconst REDUCED_MOTION = '(prefers-reduced-motion: reduce)';\n\nfunction subscribeToMotionPreference(onChange: () => void) {\n  const query = window.matchMedia(REDUCED_MOTION);\n  query.addEventListener('change', onChange);\n  return () => query.removeEventListener('change', onChange);\n}\n\nfunction usePrefersReducedMotion() {\n  return useSyncExternalStore(\n    subscribeToMotionPreference,\n    () => window.matchMedia(REDUCED_MOTION).matches,\n    () => false,\n  );\n}\n\nfunction useCountUp(target: number, run: boolean) {\n  const reduced = usePrefersReducedMotion();\n  const [value, setValue] = useState(0);\n  const frame = useRef<number | null>(null);\n\n  useEffect(() => {\n    if (!run || reduced) return;\n    const duration = 900;\n    let start: number | null = null;\n\n    function step(now: number) {\n      if (start === null) start = now;\n      const progress = Math.min(1, (now - start) / duration);\n      const eased = 1 - Math.pow(1 - progress, 3);\n      setValue(target * eased);\n      if (progress < 1) frame.current = requestAnimationFrame(step);\n    }\n\n    frame.current = requestAnimationFrame(step);\n    return () => {\n      if (frame.current !== null) cancelAnimationFrame(frame.current);\n    };\n  }, [reduced, run, target]);\n\n  return reduced && run ? target : value;\n}\n\nfunction StatValue({ stat, run }: { stat: ProofStat; run: boolean }) {\n  const value = useCountUp(stat.value, run);\n  const decimals = Number.isInteger(stat.value) ? 0 : 1;\n  return (\n    <span className=\"font-mono text-[26px] leading-none tabular-nums tracking-[-1px]\">\n      {value.toLocaleString('en-US', {\n        minimumFractionDigits: decimals,\n        maximumFractionDigits: decimals,\n      })}\n      {stat.suffix}\n    </span>\n  );\n}\n\n/** Mark plus wordmark, sized as one optical unit. */\nfunction Logo({ logo, className }: { logo: CustomerLogo; className?: string }) {\n  const Glyph = logo.mark;\n  return (\n    <span className={cn('inline-flex items-center gap-2.5 whitespace-nowrap', className)}>\n      <Glyph className=\"size-6 shrink-0\" />\n      <span className=\"text-[16px] font-semibold tracking-[-0.4px]\">{logo.name}</span>\n    </span>\n  );\n}\n\nexport function SocialProofFooter({\n  socials,\n  brand,\n  customers = DEFAULT_CUSTOMERS,\n  stats,\n  groups = [],\n  eyebrow = 'Trusted by product teams',\n  rating = 4.9,\n  ratingSource = 'G2 · 214 reviews',\n  links,\n  copyright,\n  speed = 42,\n  variant = 'Marquee',\n  className,\n}: SocialProofFooterProps) {\n  const [run, setRun] = useState(false);\n  const root = useRef<HTMLElement>(null);\n\n  useEffect(() => {\n    const node = root.current;\n    if (!node) return;\n    const observer = new IntersectionObserver(\n      (entries) => {\n        if (entries.some((entry) => entry.isIntersecting)) {\n          setRun(true);\n          observer.disconnect();\n        }\n      },\n      { rootMargin: '0px 0px -10% 0px' },\n    );\n    observer.observe(node);\n    return () => observer.disconnect();\n  }, []);\n\n  /* The track is the list twice over, and the keyframe travels exactly -50%, so\n     the seam lands where the second copy sits pixel-for-pixel on the first. */\n  const track = [...customers, ...customers];\n\n  return (\n    <footer\n      ref={root}\n      className={cn(\n        'w-full border-t border-black/[0.08] bg-white text-neutral-900 dark:border-white/[0.09] dark:bg-[#0A0A0B] dark:text-neutral-100',\n        className,\n      )}\n    >\n      <style dangerouslySetInnerHTML={{ __html: KEYFRAMES }} />\n\n      <div className=\"py-14\">\n        <div className=\"mx-auto w-full max-w-[1180px] px-6\">\n          <div className=\"flex flex-wrap items-center justify-between gap-4\">\n            <span className=\"font-mono text-[10.5px] font-medium uppercase tracking-[0.09em] text-neutral-500 dark:text-neutral-400\">\n              {eyebrow}\n            </span>\n            <span className=\"inline-flex items-center gap-2\">\n              <span aria-hidden className=\"flex gap-0.5\">\n                {[0, 1, 2, 3, 4].map((index) => (\n                  <IconStar\n                    key={index}\n                    className={cn(\n                      'size-3.5',\n                      index < Math.round(rating)\n                        ? 'text-amber-400'\n                        : 'text-neutral-300 dark:text-neutral-700',\n                    )}\n                  />\n                ))}\n              </span>\n              <span className=\"font-mono text-[11.5px] tabular-nums text-neutral-500 dark:text-neutral-400\">\n                {rating.toFixed(1)} · {ratingSource}\n              </span>\n            </span>\n          </div>\n        </div>\n\n        {variant === 'Marquee' ? (\n          /* The mask is what makes this read as a wall rather than a widget:\n             logos arrive and leave through a fade instead of being chopped by\n             the viewport edge. */\n          <div\n            className=\"group relative mt-8 overflow-hidden\"\n            style={{\n              WebkitMaskImage:\n                'linear-gradient(to right, transparent, #000 11%, #000 89%, transparent)',\n              maskImage: 'linear-gradient(to right, transparent, #000 11%, #000 89%, transparent)',\n            }}\n          >\n            <ul\n              aria-label=\"Customers\"\n              className=\"flex w-max animate-[su-proof-marquee_var(--su-speed)_linear_infinite] items-center gap-14 pr-14 group-hover:[animation-play-state:paused] motion-reduce:animate-none\"\n              style={{ ['--su-speed' as string]: `${speed}s` }}\n            >\n              {track.map((logo, index) => (\n                <li\n                  key={`${logo.name}-${index}`}\n                  aria-hidden={index >= customers.length}\n                  className=\"shrink-0 text-neutral-400 transition-[color,opacity] duration-150 hover:text-neutral-900 dark:text-neutral-500 dark:hover:text-neutral-100\"\n                >\n                  <Logo logo={logo} />\n                </li>\n              ))}\n            </ul>\n          </div>\n        ) : (\n          /* Plotted grid: one hairline between cells, drawn with insets rather\n             than per-cell borders so the outer edge stays open. */\n          <div className=\"mx-auto mt-8 w-full max-w-[1180px] px-6\">\n            <ul\n              aria-label=\"Customers\"\n              className=\"grid grid-cols-2 border-l border-t border-black/[0.07] dark:border-white/[0.08] sm:grid-cols-3 lg:grid-cols-4\"\n            >\n              {customers.map((logo, index) => (\n                <li\n                  key={logo.name}\n                  style={{ animationDelay: `${Math.min(index, 8) * 45}ms` }}\n                  className=\"flex animate-[su-proof-cell_420ms_cubic-bezier(0.2,0,0,1)_backwards] items-center justify-center border-b border-r border-black/[0.07] px-4 py-7 text-neutral-400 transition-[color] duration-150 hover:text-neutral-900 motion-reduce:animate-none dark:border-white/[0.08] dark:text-neutral-500 dark:hover:text-neutral-100\"\n                >\n                  <Logo logo={logo} />\n                </li>\n              ))}\n            </ul>\n          </div>\n        )}\n\n        <div className=\"mx-auto mt-12 w-full max-w-[1180px] px-6\">\n          <dl className=\"grid grid-cols-2 gap-6 border-y border-black/[0.07] py-8 dark:border-white/[0.08] lg:grid-cols-4\">\n            {stats.map((stat) => (\n              <div key={stat.label}>\n                <dd>\n                  <StatValue stat={stat} run={run} />\n                </dd>\n                <dt className=\"mt-2 font-mono text-[10.5px] uppercase tracking-[0.08em] text-neutral-500 dark:text-neutral-400\">\n                  {stat.label}\n                </dt>\n              </div>\n            ))}\n          </dl>\n\n          {groups.length > 0 && (\n            <nav aria-label=\"Footer\" className=\"mt-10 grid gap-8 sm:grid-cols-2 lg:grid-cols-4\">\n              {groups.map((group) => (\n                <div key={group.title} className=\"min-w-0\">\n                  <p className=\"font-mono text-[10.5px] font-medium uppercase tracking-[0.09em] text-neutral-500 dark:text-neutral-400\">\n                    {group.title}\n                  </p>\n                  <ul className=\"mt-4 space-y-2.5\">\n                    {group.links.map((link) => (\n                      <li key={link.label}>\n                        <a\n                          href={link.href}\n                          className=\"text-[13.5px] text-neutral-600 transition-colors duration-150 hover:text-neutral-950 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400 dark:text-neutral-400 dark:hover:text-neutral-50\"\n                        >\n                          {link.label}\n                        </a>\n                      </li>\n                    ))}\n                  </ul>\n                </div>\n              ))}\n            </nav>\n          )}\n\n          <FooterBar\n            brand={brand}\n            copyright={copyright}\n            links={links}\n            socials={socials}\n            className=\"mt-10\"\n          />\n        </div>\n      </div>\n    </footer>\n  );\n}\n\nexport default SocialProofFooter;\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/footers/social-proof-footer.tsx"
    },
    {
      "path": "components/spectrumui/blocks/footers/footer-kit.tsx",
      "content": "/**\n * Everything the footer blocks share: the icon set, the brand lockup, the\n * social row and the bottom bar.\n *\n * The earlier blocks inlined their own glyphs so each file copied out whole.\n * That stopped scaling once every footer needed the same wordmark, the same\n * three social marks and the same bottom bar — eighteen copies of the same\n * markup drift within a week. This ships alongside each block instead: the\n * registry item for `x-footer` lists this file too, so `npx shadcn add` writes\n * both and the block still installs in one command.\n *\n * Two icon styles live here on purpose:\n *\n *   - Utility icons — arrows, search, check, the rest — are **outline**. A\n *     filled arrow beside 13px text reads as a blob, and a filled chevron in a\n *     select looks like a warning triangle.\n *   - Social and brand marks are **solid**, because a logo drawn in outline is\n *     not that logo. These are the same paths the live site uses.\n */\n\nimport { cn } from '@/lib/utils';\n\ntype IconProps = React.SVGProps<SVGSVGElement>;\n\n/* ── Outline utility icons ──────────────────────────────────\n   One family, one grid, one stroke weight. 1.75 sits between the\n   1.5 that reads correctly next to regular text and the 2 that a\n   semibold label wants, which is the mix these footers run. */\n\nfunction Outline({ children, ...props }: IconProps & { children: React.ReactNode }) {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.75}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden=\"true\"\n      {...props}\n    >\n      {children}\n    </svg>\n  );\n}\n\nexport function IconArrowRight(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M4.5 12h15M13 5.5l6.5 6.5L13 18.5\" />\n    </Outline>\n  );\n}\n\nexport function IconArrowUpRight(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M7 17 17 7M8.5 7H17v8.5\" />\n    </Outline>\n  );\n}\n\nexport function IconArrowUp(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M12 19.5v-15M5.5 11 12 4.5l6.5 6.5\" />\n    </Outline>\n  );\n}\n\nexport function IconChevronDown(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"m6 9.5 6 6 6-6\" />\n    </Outline>\n  );\n}\n\nexport function IconChevronRight(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"m9.5 6 6 6-6 6\" />\n    </Outline>\n  );\n}\n\nexport function IconSearch(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <circle cx=\"11\" cy=\"11\" r=\"6.75\" />\n      <path d=\"m20 20-3.9-3.9\" />\n    </Outline>\n  );\n}\n\nexport function IconCheck(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"m5 12.5 4.5 4.5L19 7\" />\n    </Outline>\n  );\n}\n\nexport function IconCopy(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <rect x=\"8.5\" y=\"8.5\" width=\"12\" height=\"12\" rx=\"2.5\" />\n      <path d=\"M15.5 5.5A2 2 0 0 0 13.5 3.5h-8a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2\" />\n    </Outline>\n  );\n}\n\nexport function IconLocation(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M19.5 10.5c0 5.5-7.5 11-7.5 11s-7.5-5.5-7.5-11a7.5 7.5 0 0 1 15 0Z\" />\n      <circle cx=\"12\" cy=\"10.3\" r=\"2.6\" />\n    </Outline>\n  );\n}\n\nexport function IconMail(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <rect x=\"2.75\" y=\"4.75\" width=\"18.5\" height=\"14.5\" rx=\"2.5\" />\n      <path d=\"m3.5 8 7.4 4.6a2 2 0 0 0 2.2 0L20.5 8\" />\n    </Outline>\n  );\n}\n\nexport function IconPhone(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M21 16.9v2.6a2 2 0 0 1-2.2 2 19.5 19.5 0 0 1-8.5-3A19.2 19.2 0 0 1 4.4 13 19.5 19.5 0 0 1 1.4 4.4 2 2 0 0 1 3.4 2.2H6a2 2 0 0 1 2 1.7c.1 1 .3 1.9.6 2.8a2 2 0 0 1-.4 2.1L7 10a16 16 0 0 0 6 6l1.2-1.2a2 2 0 0 1 2.1-.4c.9.3 1.8.5 2.8.6a2 2 0 0 1 1.7 2Z\" />\n    </Outline>\n  );\n}\n\nexport function IconClock(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <circle cx=\"12\" cy=\"12\" r=\"8.75\" />\n      <path d=\"M12 6.8V12l3.4 2\" />\n    </Outline>\n  );\n}\n\nexport function IconShield(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M12 2.75 19.75 5.6v5.8c0 4.9-3.3 8.5-7.75 10.4C7.55 19.9 4.25 16.3 4.25 11.4V5.6L12 2.75Z\" />\n      <path d=\"m8.8 11.8 2.4 2.4 4.4-4.8\" />\n    </Outline>\n  );\n}\n\nexport function IconStar(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"m12 3.5 2.72 5.5 6.08.89-4.4 4.28 1.04 6.05L12 17.36 6.56 20.22l1.04-6.05L3.2 9.89 9.28 9 12 3.5Z\" />\n    </Outline>\n  );\n}\n\nexport function IconSparkle(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M12 3.2 13.9 9 19.7 10.9 13.9 12.8 12 18.6 10.1 12.8 4.3 10.9 10.1 9 12 3.2Z\" />\n    </Outline>\n  );\n}\n\nexport function IconEnter(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M9 10.5 5 14.5l4 4\" />\n      <path d=\"M5 14.5h10a4.5 4.5 0 0 0 4.5-4.5V5.5\" />\n    </Outline>\n  );\n}\n\n/* ── Brand and social marks ─────────────────────────────────\n   Solid, because a logo drawn in outline is not that logo. */\n\n/** The Spectrum UI flag. Same two paths the site header uses. */\nexport function SpectrumLogo(props: IconProps) {\n  return (\n    <svg viewBox=\"0 0 36 41\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n      <path d=\"M17.641 33.4291L11.563 27.3511C7.26395 23.052 0 26.091 0 32.169V40.1001H35.2821V15.7881L17.641 33.4291Z\" />\n      <path d=\"M17.641 6.67098L23.719 12.749C28.0181 17.0481 35.2821 14.0091 35.2821 7.93105V0H0V24.312L17.641 6.67098Z\" />\n    </svg>\n  );\n}\n\nexport function IconGitHub(props: IconProps) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n      <path d=\"M12 .5C5.73.5.98 5.24.98 11.5c0 4.86 3.15 8.98 7.52 10.44.55.1.75-.24.75-.53v-1.86c-3.06.67-3.71-1.48-3.71-1.48-.5-1.28-1.22-1.62-1.22-1.62-1-.68.08-.67.08-.67 1.1.08 1.69 1.14 1.69 1.14.98 1.69 2.58 1.2 3.21.92.1-.72.38-1.2.7-1.48-2.44-.28-5.01-1.22-5.01-5.45 0-1.2.43-2.19 1.14-2.96-.12-.28-.5-1.4.1-2.92 0 0 .93-.3 3.04 1.13a10.5 10.5 0 0 1 5.54 0c2.1-1.43 3.03-1.13 3.03-1.13.6 1.52.22 2.64.11 2.92.71.77 1.13 1.76 1.13 2.96 0 4.24-2.57 5.17-5.02 5.44.39.34.74 1.01.74 2.04v3.03c0 .29.2.64.76.53A11.03 11.03 0 0 0 23.02 11.5C23.02 5.24 18.27.5 12 .5Z\" />\n    </svg>\n  );\n}\n\nexport function IconX(props: IconProps) {\n  return (\n    <svg viewBox=\"0 0 1200 1227\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n      <path d=\"M714.163 519.284 1160.89 0H1055.03L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026Zm-145 168.544-47.468-67.894L144.011 79.694h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z\" />\n    </svg>\n  );\n}\n\nexport function IconLinkedIn(props: IconProps) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n      <path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.225 0z\" />\n    </svg>\n  );\n}\n\n/* ── Brand data ─────────────────────────────────────────────\n   Real routes, so the previews link somewhere rather than to `#`. */\n\nexport const SPECTRUM = {\n  name: 'Spectrum UI',\n  tagline: 'Animation-ready React components and blocks, with the source on the page.',\n  url: 'https://ui.spectrumhq.in',\n  copyright: `© ${new Date().getFullYear()} Spectrum UI`,\n};\n\nexport interface FooterSocial {\n  label: string;\n  href: string;\n  icon: React.ComponentType<IconProps>;\n}\n\nexport const SPECTRUM_SOCIALS: FooterSocial[] = [\n  { label: 'GitHub', href: 'https://github.com/arihantcodes/spectrum-ui', icon: IconGitHub },\n  { label: 'X', href: 'https://x.com/arihantcodes', icon: IconX },\n  { label: 'LinkedIn', href: 'https://www.linkedin.com/in/arihantcodes', icon: IconLinkedIn },\n];\n\nexport interface FooterLinkGroup {\n  title: string;\n  links: { label: string; href: string; badge?: string }[];\n}\n\nexport const SPECTRUM_GROUPS: FooterLinkGroup[] = [\n  {\n    title: 'Library',\n    links: [\n      { label: 'Components', href: '/docs/components' },\n      { label: 'Blocks', href: '/blocks' },\n      { label: 'Charts', href: '/blocks/charts' },\n      { label: 'Tables', href: '/blocks/tables' },\n      { label: 'Footers', href: '/blocks/footers', badge: 'New' },\n      { label: 'Colors', href: '/colors' },\n    ],\n  },\n  {\n    title: 'Docs',\n    links: [\n      { label: 'Introduction', href: '/docs' },\n      { label: 'Installation', href: '/docs/installation' },\n      { label: 'CLI', href: '/docs/cli' },\n      { label: 'MCP server', href: '/docs/mcp' },\n      { label: 'Theming', href: '/docs/theming' },\n      { label: 'Changelog', href: '/changelog' },\n    ],\n  },\n  {\n    title: 'Resources',\n    links: [\n      { label: 'Blog', href: '/blog' },\n      { label: 'Guides', href: '/docs/guides' },\n      { label: 'Comparisons', href: '/compare' },\n      { label: 'Templates', href: '/templates' },\n      { label: 'Brand kit', href: '/brandkit' },\n      { label: 'llms.txt', href: '/llm-info' },\n    ],\n  },\n  {\n    title: 'Project',\n    links: [\n      { label: 'GitHub', href: 'https://github.com/arihantcodes/spectrum-ui' },\n      { label: 'Sponsor', href: '/sponsor' },\n      { label: 'Pricing', href: '/pricing' },\n      { label: 'FAQs', href: '/faqs' },\n      { label: 'Founder story', href: '/founder-story' },\n      { label: 'Contact', href: 'https://x.com/arihantcodes' },\n    ],\n  },\n];\n\nexport const SPECTRUM_LEGAL = [\n  { label: 'Privacy', href: '/privacy-policy' },\n  { label: 'Terms', href: '/tos' },\n  { label: 'License', href: '/docs/license' },\n  { label: 'Status', href: '/blocks/footers' },\n];\n\n/* ── Shared pieces ──────────────────────────────────────────\n   `tone=\"dark\"` is for the blocks that are dark in both themes, where\n   `dark:` variants never fire and the light palette would be unreadable. */\n\nexport type FooterTone = 'auto' | 'dark';\n\n/** The mark and the wordmark, locked up. */\nexport function BrandLockup({\n  brand = SPECTRUM.name,\n  href = '/',\n  tone = 'auto',\n  className,\n}: {\n  brand?: string;\n  href?: string;\n  tone?: FooterTone;\n  className?: string;\n}) {\n  return (\n    <a\n      href={href}\n      className={cn(\n        'inline-flex items-center gap-2.5 rounded-md transition-opacity duration-150 hover:opacity-70 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400',\n        className,\n      )}\n    >\n      <span\n        className={cn(\n          'grid size-7 shrink-0 place-items-center rounded-[7px]',\n          tone === 'dark'\n            ? 'bg-neutral-100 text-neutral-900'\n            : 'bg-neutral-900 text-white dark:bg-neutral-100 dark:text-neutral-900',\n        )}\n      >\n        <SpectrumLogo className=\"h-3.5 w-auto\" />\n      </span>\n      <span className=\"whitespace-nowrap text-[14.5px] font-semibold tracking-[-0.3px]\">\n        {brand}\n      </span>\n    </a>\n  );\n}\n\n/** The social row. Solid marks, 32px hit area, one hover transition. */\nexport function FooterSocials({\n  socials = SPECTRUM_SOCIALS,\n  tone = 'auto',\n  className,\n}: {\n  socials?: FooterSocial[];\n  tone?: FooterTone;\n  className?: string;\n}) {\n  if (socials.length === 0) return null;\n  return (\n    <ul className={cn('flex items-center gap-0.5', className)}>\n      {socials.map(({ label, href, icon: Glyph }) => (\n        <li key={label}>\n          <a\n            href={href}\n            target=\"_blank\"\n            rel=\"noreferrer\"\n            aria-label={label}\n            className={cn(\n              'grid size-8 place-items-center rounded-lg transition-[color,background-color] duration-150 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400',\n              tone === 'dark'\n                ? 'text-neutral-400 hover:bg-white/[0.07] hover:text-neutral-100'\n                : 'text-neutral-400 hover:bg-black/[0.05] hover:text-neutral-900 dark:text-neutral-500 dark:hover:bg-white/[0.07] dark:hover:text-neutral-100',\n            )}\n          >\n            <Glyph className=\"size-[15px]\" />\n          </a>\n        </li>\n      ))}\n    </ul>\n  );\n}\n\n/**\n * The bottom bar every footer ends on: lockup and copyright left, utility links\n * and social marks right. One component rather than eighteen hand-rolled\n * copies, which is the only way they stay identical.\n */\nexport function FooterBar({\n  brand = SPECTRUM.name,\n  copyright,\n  links = SPECTRUM_LEGAL,\n  socials = SPECTRUM_SOCIALS,\n  tone = 'auto',\n  showLockup = true,\n  className,\n}: {\n  brand?: string;\n  copyright?: string;\n  links?: { label: string; href: string }[];\n  socials?: FooterSocial[];\n  tone?: FooterTone;\n  showLockup?: boolean;\n  className?: string;\n}) {\n  const muted = tone === 'dark' ? 'text-neutral-400' : 'text-neutral-500 dark:text-neutral-400';\n  const hover =\n    tone === 'dark'\n      ? 'hover:text-neutral-100'\n      : 'hover:text-neutral-900 dark:hover:text-neutral-100';\n  const rule =\n    tone === 'dark' ? 'border-white/[0.08]' : 'border-black/[0.07] dark:border-white/[0.08]';\n\n  return (\n    <div className={cn('border-t pt-6', rule, className)}>\n      <div className=\"flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between\">\n        <div className=\"flex flex-wrap items-center gap-x-5 gap-y-2\">\n          {showLockup && <BrandLockup brand={brand} tone={tone} />}\n          <p className={cn('text-[12px] tabular-nums', muted)}>{copyright ?? SPECTRUM.copyright}</p>\n        </div>\n\n        <div className=\"flex flex-wrap items-center gap-x-6 gap-y-3\">\n          <ul className={cn('flex flex-wrap items-center gap-x-5 gap-y-2 text-[12px]', muted)}>\n            {links.map((link) => (\n              <li key={link.label}>\n                <a\n                  href={link.href}\n                  className={cn(\n                    'transition-colors duration-150 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400',\n                    hover,\n                  )}\n                >\n                  {link.label}\n                </a>\n              </li>\n            ))}\n          </ul>\n          <FooterSocials socials={socials} tone={tone} className=\"-mr-2\" />\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/footers/footer-kit.tsx"
    }
  ],
  "type": "registry:block"
}
