{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "loader-iris",
  "title": "Loader Iris",
  "description": "An ambient loading glyph shaped as a six-blade camera-iris diaphragm breathing open and shut inside a fixed housing ring: one wedge shape repeated by rotation, its shared displacement keyframe overshooting each open and close before settling on a spring.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/loader-iris/component.tsx",
      "content": "\"use client\";\n\n// ---------------------------------------------------------------------------\n// BladeIris — an ambient loading glyph shaped as a six-blade camera-iris\n// diaphragm breathing open and shut inside a fixed housing ring. Each blade\n// is one wedge polygon, repeated six times via SVG <g rotate(60*i 50 50)>,\n// so only one shape is authored; the aperture itself is driven by a single\n// shared translateY keyframe every blade runs identically (retreating\n// toward the housing to open, sliding back toward center to close) — the\n// hex hole in the middle is purely the six wedges' tips moving in unison,\n// never a separately drawn shape. Both the open and the close leg ease in\n// fast then overshoot their target before correcting on the house spring\n// curve, so the aperture reads as spring-tensioned blades snapping to a\n// stop rather than a shape tweening between two states.\n// ---------------------------------------------------------------------------\n\nconst CSS = `\n.ns-bi-blade{\n  transform-box:fill-box;\n  transform-origin:center;\n  animation:ns-bi-breathe var(--ns-bi-period) infinite;\n}\n@keyframes ns-bi-breathe{\n  0%,10%{transform:translateY(0px);animation-timing-function:cubic-bezier(.16,1,.3,1)}\n  22%{transform:translateY(-9px);animation-timing-function:cubic-bezier(.34,1.56,.64,1)}\n  28%{transform:translateY(-7.5px)}\n  55%{transform:translateY(-7.5px);animation-timing-function:cubic-bezier(.16,1,.3,1)}\n  67%{transform:translateY(1px);animation-timing-function:cubic-bezier(.34,1.56,.64,1)}\n  73%,100%{transform:translateY(0px)}\n}\n@media (prefers-reduced-motion: reduce){\n  .ns-bi-blade{animation:none;transform:translateY(-4px);}\n}\n`;\n\nconst BLADE_D = \"M 50 47 L 37 12 L 63 12 Z\";\n\nexport interface BladeIrisProps {\n  /** glyph size in px */\n  size?: number;\n  /** ms for one full open-close breath */\n  periodMs?: number;\n  /** text announced via the component's own aria-live region */\n  label?: string;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}\n\nexport function BladeIris({\n  size = 40,\n  periodMs = 2600,\n  label = \"Loading\",\n  className = \"\",\n}: BladeIrisProps) {\n  return (\n    <span\n      role=\"status\"\n      aria-live=\"polite\"\n      data-loader-iris\n      className={`inline-flex shrink-0 items-center justify-center ${className}`}\n      style={{ width: size, height: size }}\n    >\n      <style>{CSS}</style>\n      <svg\n        viewBox=\"0 0 100 100\"\n        width={size}\n        height={size}\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        style={{ [\"--ns-bi-period\" as string]: `${periodMs}ms` }}\n      >\n        <circle cx={50} cy={50} r={46} fill=\"none\" stroke=\"var(--border)\" strokeWidth={1.5} />\n        {Array.from({ length: 6 }, (_, i) => (\n          <g key={i} transform={`rotate(${i * 60} 50 50)`}>\n            <path\n              className=\"ns-bi-blade\"\n              d={BLADE_D}\n              fill=\"color-mix(in srgb, var(--foreground) 82%, transparent)\"\n              stroke=\"var(--surface)\"\n              strokeWidth={1}\n              strokeLinejoin=\"round\"\n            />\n          </g>\n        ))}\n        <circle cx={50} cy={50} r={2.5} fill=\"var(--foreground)\" />\n      </svg>\n      <span className=\"sr-only\">{label}</span>\n    </span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/loader-iris.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-surface": "var(--surface)"
    },
    "light": {
      "surface": "#fafafa"
    },
    "dark": {
      "surface": "#171717"
    }
  },
  "meta": {
    "collection": "core",
    "tags": [
      "loader",
      "loading",
      "spinner",
      "indicator",
      "svg",
      "css-animation",
      "physics",
      "aria-live",
      "icon"
    ],
    "instruction": "A compact, always-on loading glyph (20-96px via a `size` prop) built as a six-blade iris diaphragm: one triangular wedge `<path>` authored once and repeated six times inside `<g transform=\"rotate(60*i 50 50)\">` around a 100x100 viewBox, so the hexagonal aperture in the middle is never a separately drawn shape — it's purely where the six wedge tips currently sit. Every blade runs the identical `translateY` keyframe (its own local 'up' axis, which the per-blade rotate then points radially outward), so the whole assembly opens and closes as one unit: tips retreat toward a static housing ring (`stroke:var(--border)`, drawn once, unrotated) on ease-out-expo, overshoot past the open position, correct on the house spring curve (cubic-bezier(.34,1.56,.64,1)); after a hold, the same pattern reverses to close — ease toward center, overshoot past fully-shut, spring back to rest — so both the opening and the closing read as blades under tension snapping to a stop, not a shape tweening between two states. Blade fill is `color-mix(in srgb, var(--foreground) 82%, transparent)` with a `var(--surface)` stroke so adjacent blade edges stay legible against each other in both themes; a small static center dot in `var(--foreground)` anchors the hub. The whole glyph is a single `role=\"status\" aria-live=\"polite\"` element; the SVG is `aria-hidden` and a visually hidden text node (the `label` prop, default 'Loading') is the only thing announced — nothing here is interactive, so it renders zero controls and is correctly exempt from the tabbability check. `prefers-reduced-motion` removes the animation entirely, leaving every blade retreated to a fixed mid-open position rather than either extreme, so the resting frame reads as a genuine aperture rather than a fully shut or fully open freeze-frame. Props: size (px, default 40), periodMs (full open-close breath, default 2600), label, className. Pure DOM + SVG + CSS, zero dependencies, no canvas, no framer-motion despite the source this was rebuilt from declaring it as a dependency it never actually imported."
  },
  "type": "registry:ui"
}