{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "logo-cloud-settle",
  "title": "Logo Cloud Settle",
  "description": "A trust-wall grid of abstract, generated marks that physically settles into place: each tile drops in from lifted/shrunk/tilted with a per-item stagger and a slight spring overshoot, triggered on viewport entry and replayed if the wall leaves and re-enters view.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/logo-cloud-settle/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\n// ---------------------------------------------------------------------------\n// LogoCloudSettle — a trust wall of abstract marks that SETTLES onto its\n// grid rather than simply fading in: each tile starts lifted, shrunk and\n// tilted a few degrees, then drops into its resting transform on a per-item\n// stagger with a slight overshoot (a spring-approximating cubic-bezier, the\n// same shape this registry already uses for its digit/avatar settles) so it\n// reads as physically landing, not just appearing. This is a distinct job\n// from avatar-stack-flock: that component mills continuously as a boids\n// flock at rest and only resolves into a row on hover/focus; this cloud is\n// static at rest and settles ONCE per viewport entry via an\n// IntersectionObserver, replaying only if it leaves and re-enters view —\n// there is no continuous simulation here at all, just a CSS transition\n// driven by one boolean.\n//\n// Marks are abstract, generated geometric glyphs (no real company logos or\n// wordmarks), so there is nothing here with trademark exposure. Every color\n// is a Tailwind token utility (text-ns-muted / border-border / bg-surface),\n// never a literal, so both themes are correct without any getComputedStyle\n// bookkeeping — this is plain DOM, not canvas.\n// ---------------------------------------------------------------------------\n\ntype ShapeId = \"ring\" | \"diamond\" | \"triangle\" | \"plus\" | \"hex\" | \"venn\" | \"chevron\" | \"grid\";\n\nexport interface Mark {\n  id: string;\n  name: string;\n  shape: ShapeId;\n}\n\nconst DEFAULT_MARKS: Mark[] = [\n  { id: \"m1\", name: \"Nimbus\", shape: \"ring\" },\n  { id: \"m2\", name: \"Vectra\", shape: \"diamond\" },\n  { id: \"m3\", name: \"Solstice\", shape: \"triangle\" },\n  { id: \"m4\", name: \"Ashgrove\", shape: \"plus\" },\n  { id: \"m5\", name: \"Continuum\", shape: \"hex\" },\n  { id: \"m6\", name: \"Rivermark\", shape: \"venn\" },\n  { id: \"m7\", name: \"Anchorpoint\", shape: \"chevron\" },\n  { id: \"m8\", name: \"Greywolf\", shape: \"grid\" },\n];\n\nconst STAGGER_MS = 42;\nconst DURATION = 560;\nconst EASE = \"cubic-bezier(0.22, 1.7, 0.36, 1)\"; // spring-approximating overshoot, same family as this registry's other settles\nconst DROP_PX = 22;\n\nfunction MarkGlyph({ shape, className = \"\" }: { shape: ShapeId; className?: string }) {\n  const common = {\n    viewBox: \"0 0 24 24\",\n    className,\n    fill: \"none\",\n    stroke: \"currentColor\",\n    strokeWidth: 1.6,\n    strokeLinecap: \"round\" as const,\n    strokeLinejoin: \"round\" as const,\n    \"aria-hidden\": true,\n  };\n  switch (shape) {\n    case \"ring\":\n      return (\n        <svg {...common}>\n          <circle cx=\"12\" cy=\"12\" r=\"8\" />\n          <circle cx=\"12\" cy=\"12\" r=\"1.6\" fill=\"currentColor\" stroke=\"none\" />\n        </svg>\n      );\n    case \"diamond\":\n      return (\n        <svg {...common}>\n          <path d=\"M12 3 L20 12 L12 21 L4 12 Z\" />\n        </svg>\n      );\n    case \"triangle\":\n      return (\n        <svg {...common}>\n          <path d=\"M12 4 L20 19 L4 19 Z\" />\n          <line x1=\"6\" y1=\"19\" x2=\"18\" y2=\"19\" />\n        </svg>\n      );\n    case \"plus\":\n      return (\n        <svg {...common}>\n          <line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\" />\n          <line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\" />\n        </svg>\n      );\n    case \"hex\":\n      return (\n        <svg {...common}>\n          <path d=\"M12 3 L19.5 7.5 L19.5 16.5 L12 21 L4.5 16.5 L4.5 7.5 Z\" />\n        </svg>\n      );\n    case \"venn\":\n      return (\n        <svg {...common}>\n          <circle cx=\"9.5\" cy=\"12\" r=\"6\" />\n          <circle cx=\"14.5\" cy=\"12\" r=\"6\" />\n        </svg>\n      );\n    case \"chevron\":\n      return (\n        <svg {...common}>\n          <path d=\"M5 8 L12 14 L19 8\" />\n          <path d=\"M5 15 L12 21 L19 15\" />\n        </svg>\n      );\n    case \"grid\":\n      return (\n        <svg {...common}>\n          <rect x=\"4\" y=\"4\" width=\"6\" height=\"6\" />\n          <rect x=\"14\" y=\"4\" width=\"6\" height=\"6\" />\n          <rect x=\"4\" y=\"14\" width=\"6\" height=\"6\" />\n          <rect x=\"14\" y=\"14\" width=\"6\" height=\"6\" />\n        </svg>\n      );\n    default:\n      return null;\n  }\n}\n\nexport function LogoCloudSettle({\n  marks = DEFAULT_MARKS,\n  label = \"Trusted by teams shipping with ns-ui\",\n  className = \"\",\n}: {\n  /** the logos, settling into place */\n  marks?: Mark[];\n  /** accessible name for the logo group */\n  label?: string;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const [settled, setSettled] = useState(false);\n  const reducedRef = useRef(false);\n\n  useEffect(() => {\n    const root = rootRef.current;\n    if (!root) return;\n    reducedRef.current = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n    if (reducedRef.current) {\n      setSettled(true);\n      return;\n    }\n    const io = new IntersectionObserver(\n      (entries) => {\n        const hit = entries[0];\n        if (!hit) return;\n        setSettled(hit.isIntersecting);\n      },\n      { threshold: 0.2 }\n    );\n    io.observe(root);\n    return () => io.disconnect();\n  }, []);\n\n  return (\n    <div className={className}>\n      {label && (\n        <p className=\"mb-5 text-center font-mono text-xs uppercase tracking-widest text-ns-muted\">{label}</p>\n      )}\n      <div\n        ref={rootRef}\n        data-settled={settled}\n        role=\"list\"\n        aria-label={label}\n        className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\"\n      >\n        {marks.map((m, i) => (\n          <div\n            key={m.id}\n            role=\"listitem\"\n            className=\"flex flex-col items-center justify-center gap-2 rounded-md border border-border bg-surface px-4 py-6 text-ns-muted\"\n            style={{\n              transitionProperty: \"transform, opacity\",\n              transitionDuration: `${DURATION}ms`,\n              transitionTimingFunction: EASE,\n              transitionDelay: settled ? `${i * STAGGER_MS}ms` : \"0ms\",\n              transform: settled\n                ? \"translateY(0px) scale(1) rotate(0deg)\"\n                : `translateY(-${DROP_PX}px) scale(0.9) rotate(-4deg)`,\n              opacity: settled ? 1 : 0,\n              willChange: \"transform, opacity\",\n            }}\n          >\n            <MarkGlyph shape={m.shape} className=\"h-7 w-7\" />\n            <span className=\"font-mono text-[10px] uppercase tracking-widest\">{m.name}</span>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/logo-cloud-settle.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-ns-muted": "var(--ns-muted)",
      "color-surface": "var(--surface)"
    },
    "light": {
      "ns-muted": "#4d4d4d",
      "surface": "#fafafa"
    },
    "dark": {
      "ns-muted": "#8f8f8f",
      "surface": "#171717"
    }
  },
  "meta": {
    "collection": "core",
    "tags": [
      "logo-cloud",
      "trust-wall",
      "grid",
      "entrance",
      "spring"
    ],
    "instruction": "Renders `marks` (id/name/abstract-shape triples; eight generated geometric glyphs ship by default — ring, diamond, triangle, plus, hex, venn, chevron, dot-grid, all drawn as inline SVG with stroke=currentColor, zero real company logos or wordmarks) as a responsive grid (2 columns under sm, 4 above). At rest before its first reveal, and again any time it leaves the viewport, every tile sits lifted 22px above its slot, scaled to 0.9 and rotated -4deg with opacity 0. An IntersectionObserver (threshold 0.2) on the grid flips a single `settled` boolean the moment the wall enters view; while true, every tile transitions to translateY(0)/scale(1)/rotate(0)/opacity 1 over 560ms on a spring-approximating overshoot easing (cubic-bezier(0.22,1.7,0.36,1)), staggered 42ms per tile in DOM order, so the wall reads as physically DROPPING INTO and SETTLING onto its grid rather than fading in as a block. Leaving the viewport resets every tile instantly back to the lifted/tilted starting transform (no transition on the way out), so re-entering triggers the same settle again — this is a ONE-SHOT-PER-ENTRY transition driven by a boolean, not a continuous simulation: distinct from avatar-stack-flock, which mills continuously as a boids flock at rest and only resolves into a tidy row on hover/focus. `prefers-reduced-motion` renders every tile already settled on the first frame and never touches the IntersectionObserver. The grid is `role=list` with each tile `role=listitem` and the visible mark name as its label — this is a display-only trust wall with no interactive controls (no buttons, nothing to tab to), so nothing here is clickable. `data-settled` is exposed on the grid root for anyone probing its state. Props: `marks`, `label` (the caption above the grid, also the list's aria-label), className. Zero dependencies, plain DOM + CSS transitions, no canvas."
  },
  "type": "registry:ui"
}