{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "louver",
  "title": "Louver",
  "author": "Visant Labs <visantsupply@gmail.com>",
  "description": "Persiana em lâminas: rolar gira as lâminas em cinco paradas, e as faixas de luz na parede abrem e fecham junto.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/visant/ui/louver.tsx",
      "content": "\"use client\";\n\n/*\n * LOUVER: scrolling turns the slats of a window blind, and the bars of sunlight\n * they let onto the wall widen and narrow.\n *\n * The scroller has five native snap detents (CSS scroll-snap), one per 22.5° of\n * slat. Edge-on, a closed slat stands upright and an open one lies flat; the\n * light through each gap is 1 − cos θ of the pitch, so the first turn barely\n * lets anything in and the last one floods the wall. Under reduced motion the\n * slats and the light land on the nearest detent instead of travelling with\n * the finger.\n */\nimport * as React from \"react\";\nimport { motion, useMotionValue, useReducedMotion, useTransform, type MotionValue } from \"motion/react\";\n\nconst STEP = 48; // px of scroll per detent (h-12)\nconst DETENTS = 5; // 0°, 22.5°, 45°, 67.5°, 90°\nconst PER = 90 / (DETENTS - 1);\nconst BORN = 3; // born at 67.5°: the bars already read in a screenshot\nconst SLATS = 9;\nconst BANDS = 7;\n\nfunction Slat({ theta }: { theta: MotionValue<number> }) {\n  // Edge-on: 0° stands upright (closed), 90° lies flat (open).\n  const rotate = useTransform(theta, (t) => 90 - t);\n  return <motion.span style={{ rotate }} className=\"block h-px w-4 rounded-full bg-muted-foreground\" />;\n}\n\nfunction Band({ open }: { open: MotionValue<number> }) {\n  const scaleY = useTransform(open, (o) => Math.max(o, 0.02));\n  const opacity = useTransform(open, (o) => 0.35 + o * 0.65);\n  return (\n    <motion.div\n      style={{ scaleY, opacity }}\n      className=\"h-full w-full rounded-[2px] blur-[3px] [background:linear-gradient(90deg,transparent,color-mix(in_srgb,var(--foreground)_14%,transparent)_30%,color-mix(in_srgb,var(--foreground)_8%,transparent))]\"\n    />\n  );\n}\n\nexport function Louver() {\n  const reduce = useReducedMotion();\n  const scroller = React.useRef<HTMLDivElement>(null);\n  const theta = useMotionValue(BORN * PER);\n  const open = useTransform(theta, (t) => 1 - Math.cos((t * Math.PI) / 180));\n\n  React.useLayoutEffect(() => {\n    if (scroller.current) scroller.current.scrollTop = BORN * STEP;\n  }, []);\n\n  const onScroll = (e: React.UIEvent<HTMLDivElement>) => {\n    const d = Math.max(0, Math.min(DETENTS - 1, e.currentTarget.scrollTop / STEP));\n    theta.set((reduce ? Math.round(d) : d) * PER);\n  };\n\n  return (\n    <div className=\"relative isolate h-full w-full select-none overflow-hidden rounded-[var(--r-surface)] bg-card\">\n      {/* The wall: bars of light, cast at the sun's slant. */}\n      <div aria-hidden className=\"pointer-events-none absolute -inset-x-6 inset-y-6 -z-10 flex -skew-y-6 flex-col justify-between\">\n        {Array.from({ length: BANDS }, (_, i) => (\n          <div key={i} className=\"h-[9%]\">\n            <Band open={open} />\n          </div>\n        ))}\n      </div>\n      {/* The blind, edge-on, down the left side. */}\n      <div aria-hidden className=\"pointer-events-none absolute inset-y-6 left-5 flex flex-col justify-between\">\n        {Array.from({ length: SLATS }, (_, i) => (\n          <Slat key={i} theta={theta} />\n        ))}\n      </div>\n      {/* The input: a short native scroller with five snap detents, so wheel,\n          trackpad and finger all work and the page takes over at either end. */}\n      <div\n        ref={scroller}\n        onScroll={onScroll}\n        className=\"absolute inset-0 snap-y snap-mandatory touch-pan-y overflow-y-scroll [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n      >\n        {Array.from({ length: DETENTS - 1 }, (_, i) => (\n          <div key={i} aria-hidden className=\"h-12 snap-start\" />\n        ))}\n        <div aria-hidden className=\"h-full snap-start\" />\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "uso": {
      "quando": "Escolher um nível em poucas paradas com um gesto de rolar: brilho, intensidade, privacidade",
      "evite": "Valor contínuo e preciso: scrub-input; ligar e desligar: toggle-lever"
    }
  },
  "categories": [
    "controles",
    "persiana",
    "luz",
    "rolagem",
    "paradas",
    "laminas"
  ],
  "type": "registry:ui"
}