{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fluted-glass-demo",
  "type": "registry:component",
  "dependencies": [
    "@paper-design/shaders-react"
  ],
  "registryDependencies": [
    "fluted-glass",
    "button",
    "input",
    "label",
    "select",
    "slider"
  ],
  "files": [
    {
      "path": "registry/default/example/fluted-glass-demo.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  flutedGlassPresets,\n  type FlutedGlassProps,\n} from \"@paper-design/shaders-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Slider } from \"@/components/ui/slider\"\nimport {\n  defaultFlutedGlassProps,\n  FlutedGlass,\n} from \"@/registry/default/ui/fluted-glass\"\n\nconst DEMO_IMAGES = [\n  {\n    id: \"flowers\",\n    label: \"Flowers\",\n    url: \"https://paper.design/flowers.webp\",\n  },\n  {\n    id: \"mountains\",\n    label: \"Mountains\",\n    url: \"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1280&q=80&fm=webp\",\n  },\n  {\n    id: \"abstract\",\n    label: \"Abstract\",\n    url: \"https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1280&q=80&fm=webp\",\n  },\n  {\n    id: \"forest\",\n    label: \"Forest\",\n    url: \"https://images.unsplash.com/photo-1448375240586-882707db888b?w=1280&q=80&fm=webp\",\n  },\n  {\n    id: \"ocean\",\n    label: \"Ocean\",\n    url: \"https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=1280&q=80&fm=webp\",\n  },\n] as const\n\nconst GRID_SHAPES = [\n  \"lines\",\n  \"linesIrregular\",\n  \"wave\",\n  \"zigzag\",\n  \"pattern\",\n] as const\n\nconst DISTORTION_SHAPES = [\n  \"prism\",\n  \"lens\",\n  \"contour\",\n  \"cascade\",\n  \"flat\",\n] as const\n\nconst FIT_OPTIONS = [\"cover\", \"contain\", \"none\"] as const\n\ntype DemoSettings = Pick<\n  FlutedGlassProps,\n  | \"distortion\"\n  | \"size\"\n  | \"shadows\"\n  | \"highlights\"\n  | \"angle\"\n  | \"edges\"\n  | \"blur\"\n  | \"stretch\"\n  | \"shift\"\n  | \"grainMixer\"\n  | \"grainOverlay\"\n  | \"margin\"\n  | \"shape\"\n  | \"distortionShape\"\n  | \"fit\"\n  | \"colorBack\"\n  | \"colorShadow\"\n  | \"colorHighlight\"\n>\n\nconst INITIAL_SETTINGS: DemoSettings = {\n  distortion: defaultFlutedGlassProps.distortion,\n  size: defaultFlutedGlassProps.size,\n  shadows: defaultFlutedGlassProps.shadows,\n  highlights: defaultFlutedGlassProps.highlights,\n  angle: defaultFlutedGlassProps.angle,\n  edges: defaultFlutedGlassProps.edges,\n  blur: defaultFlutedGlassProps.blur,\n  stretch: defaultFlutedGlassProps.stretch,\n  shift: defaultFlutedGlassProps.shift,\n  grainMixer: defaultFlutedGlassProps.grainMixer,\n  grainOverlay: defaultFlutedGlassProps.grainOverlay,\n  margin: defaultFlutedGlassProps.margin,\n  shape: defaultFlutedGlassProps.shape,\n  distortionShape: defaultFlutedGlassProps.distortionShape,\n  fit: defaultFlutedGlassProps.fit,\n  colorBack: defaultFlutedGlassProps.colorBack,\n  colorShadow: defaultFlutedGlassProps.colorShadow,\n  colorHighlight: defaultFlutedGlassProps.colorHighlight,\n}\n\nfunction SettingSlider({\n  label,\n  value,\n  onChange,\n  min,\n  max,\n  step,\n  format = (v: number) => v.toFixed(2),\n}: {\n  label: string\n  value: number\n  onChange: (v: number) => void\n  min: number\n  max: number\n  step: number\n  format?: (v: number) => string\n}) {\n  return (\n    <div className=\"space-y-2\" data-slot=\"fluted-glass-demo-slider\">\n      <div className=\"flex items-center justify-between gap-2\">\n        <Label className=\"text-muted-foreground\">{label}</Label>\n        <span className=\"tabular-nums text-muted-foreground text-xs\">\n          {format(value)}\n        </span>\n      </div>\n      <Slider\n        max={max}\n        min={min}\n        onValueChange={(v) =>\n          onChange(typeof v === \"number\" ? v : (v[0] ?? min))\n        }\n        step={step}\n        value={[value]}\n      />\n    </div>\n  )\n}\n\nfunction FlutedGlassDemo() {\n  const customUrlId = React.useId()\n  const colorBackId = React.useId()\n  const colorShadowId = React.useId()\n  const colorHighlightId = React.useId()\n  const [image, setImage] = React.useState<string>(DEMO_IMAGES[0].url)\n  const [customUrl, setCustomUrl] = React.useState(\"\")\n  const [settings, setSettings] = React.useState<DemoSettings>(INITIAL_SETTINGS)\n\n  const patch = React.useCallback(\n    <K extends keyof DemoSettings>(key: K, value: DemoSettings[K]) => {\n      setSettings((s) => ({ ...s, [key]: value }))\n    },\n    []\n  )\n\n  const applyPreset = React.useCallback((presetIndex: number) => {\n    const preset = flutedGlassPresets[presetIndex]\n    if (!preset) {\n      return\n    }\n    setSettings((prev) => ({\n      ...prev,\n      ...(preset.params as Partial<DemoSettings>),\n    }))\n  }, [])\n\n  const reset = React.useCallback(() => {\n    setImage(DEMO_IMAGES[0].url)\n    setCustomUrl(\"\")\n    setSettings(INITIAL_SETTINGS)\n  }, [])\n\n  const applyCustomUrl = React.useCallback(() => {\n    const trimmed = customUrl.trim()\n    if (!trimmed) {\n      return\n    }\n    setImage(trimmed)\n  }, [customUrl])\n\n  return (\n    <div\n      className=\"flex min-h-full flex-col gap-8 bg-background p-4 md:flex-row md:p-8\"\n      data-slot=\"fluted-glass-demo\"\n    >\n      <div className=\"flex w-full flex-col gap-4 md:max-w-[min(100%,800px)] md:flex-1\">\n        <div\n          className=\"relative aspect-video w-full overflow-hidden rounded-2xl border border-border bg-muted/30 shadow-sm\"\n          data-slot=\"fluted-glass-demo-stage\"\n        >\n          <FlutedGlass\n            key={image}\n            className=\"absolute inset-0 size-full\"\n            height={720}\n            image={image}\n            width={1280}\n            {...settings}\n          />\n        </div>\n\n        <div className=\"space-y-2\">\n          <Label className=\"text-muted-foreground text-xs\">Image</Label>\n          <div className=\"flex flex-wrap gap-2\">\n            {DEMO_IMAGES.map((item) => {\n              const active = image === item.url\n              return (\n                <button\n                  className={cn(\n                    \"relative aspect-video h-14 w-24 overflow-hidden rounded-lg border-2 transition-opacity\",\n                    active\n                      ? \"border-primary ring-2 ring-ring/40\"\n                      : \"border-transparent opacity-80 hover:opacity-100\"\n                  )}\n                  key={item.id}\n                  onClick={() => setImage(item.url)}\n                  title={item.label}\n                  type=\"button\"\n                >\n                  <img\n                    alt=\"\"\n                    className=\"size-full object-cover\"\n                    decoding=\"async\"\n                    src={item.url}\n                  />\n                  <span className=\"sr-only\">{item.label}</span>\n                </button>\n              )\n            })}\n          </div>\n        </div>\n\n        <div className=\"flex flex-col gap-2 sm:flex-row sm:items-end\">\n          <div className=\"flex-1 space-y-1\">\n            <Label\n              className=\"text-muted-foreground text-xs\"\n              htmlFor={customUrlId}\n            >\n              Custom image URL\n            </Label>\n            <Input\n              className=\"font-mono text-xs\"\n              id={customUrlId}\n              onKeyDown={(e) => {\n                if (e.key === \"Enter\") {\n                  applyCustomUrl()\n                }\n              }}\n              placeholder=\"https://…\"\n              value={customUrl}\n              onChange={(e) => setCustomUrl(e.target.value)}\n            />\n          </div>\n          <Button\n            className=\"shrink-0\"\n            onClick={applyCustomUrl}\n            type=\"button\"\n            variant=\"secondary\"\n          >\n            Load URL\n          </Button>\n        </div>\n      </div>\n\n      <div\n        className=\"flex w-full flex-col gap-5 md:max-h-[min(90vh,920px)] md:w-[380px] md:shrink-0 md:overflow-y-auto\"\n        data-slot=\"fluted-glass-demo-controls\"\n      >\n        <div className=\"flex flex-wrap items-center gap-2\">\n          <Button onClick={reset} size=\"sm\" type=\"button\" variant=\"outline\">\n            Reset defaults\n          </Button>\n        </div>\n\n        <div className=\"space-y-2\">\n          <Label className=\"text-muted-foreground text-xs\">\n            Library presets\n          </Label>\n          <div className=\"flex flex-wrap gap-2\">\n            {flutedGlassPresets.map((preset, i) => (\n              <Button\n                key={preset.name}\n                onClick={() => applyPreset(i)}\n                size=\"sm\"\n                type=\"button\"\n                variant=\"secondary\"\n              >\n                {preset.name}\n              </Button>\n            ))}\n          </div>\n        </div>\n\n        <div className=\"grid gap-3 sm:grid-cols-2\">\n          <div className=\"space-y-1\">\n            <Label className=\"text-muted-foreground text-xs\">Grid shape</Label>\n            <Select\n              onValueChange={(v) => patch(\"shape\", v as DemoSettings[\"shape\"])}\n              value={settings.shape}\n            >\n              <SelectTrigger className=\"w-full\">\n                <SelectValue />\n              </SelectTrigger>\n              <SelectContent>\n                {GRID_SHAPES.map((s) => (\n                  <SelectItem key={s} value={s}>\n                    {s}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n          <div className=\"space-y-1\">\n            <Label className=\"text-muted-foreground text-xs\">\n              Distortion shape\n            </Label>\n            <Select\n              onValueChange={(v) =>\n                patch(\"distortionShape\", v as DemoSettings[\"distortionShape\"])\n              }\n              value={settings.distortionShape}\n            >\n              <SelectTrigger className=\"w-full\">\n                <SelectValue />\n              </SelectTrigger>\n              <SelectContent>\n                {DISTORTION_SHAPES.map((s) => (\n                  <SelectItem key={s} value={s}>\n                    {s}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n          <div className=\"space-y-1 sm:col-span-2\">\n            <Label className=\"text-muted-foreground text-xs\">Fit</Label>\n            <Select\n              onValueChange={(v) => patch(\"fit\", v as DemoSettings[\"fit\"])}\n              value={settings.fit}\n            >\n              <SelectTrigger className=\"w-full\">\n                <SelectValue />\n              </SelectTrigger>\n              <SelectContent>\n                {FIT_OPTIONS.map((f) => (\n                  <SelectItem key={f} value={f}>\n                    {f}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n        </div>\n\n        <div className=\"grid gap-3\">\n          <div className=\"space-y-1\">\n            <Label\n              className=\"text-muted-foreground text-xs\"\n              htmlFor={colorBackId}\n            >\n              Background\n            </Label>\n            <Input\n              className=\"font-mono text-xs\"\n              id={colorBackId}\n              onChange={(e) => patch(\"colorBack\", e.target.value)}\n              value={settings.colorBack}\n            />\n          </div>\n          <div className=\"space-y-1\">\n            <Label\n              className=\"text-muted-foreground text-xs\"\n              htmlFor={colorShadowId}\n            >\n              Shadow tint\n            </Label>\n            <Input\n              className=\"font-mono text-xs\"\n              id={colorShadowId}\n              onChange={(e) => patch(\"colorShadow\", e.target.value)}\n              value={settings.colorShadow}\n            />\n          </div>\n          <div className=\"space-y-1\">\n            <Label\n              className=\"text-muted-foreground text-xs\"\n              htmlFor={colorHighlightId}\n            >\n              Highlight tint\n            </Label>\n            <Input\n              className=\"font-mono text-xs\"\n              id={colorHighlightId}\n              onChange={(e) => patch(\"colorHighlight\", e.target.value)}\n              value={settings.colorHighlight}\n            />\n          </div>\n        </div>\n\n        <div className=\"space-y-4\">\n          <SettingSlider\n            label=\"Distortion\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"distortion\", v)}\n            step={0.01}\n            value={settings.distortion ?? 0}\n          />\n          <SettingSlider\n            label=\"Stripe size\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"size\", v)}\n            step={0.01}\n            value={settings.size ?? 0}\n          />\n          <SettingSlider\n            label=\"Shadows\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"shadows\", v)}\n            step={0.01}\n            value={settings.shadows ?? 0}\n          />\n          <SettingSlider\n            label=\"Highlights\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"highlights\", v)}\n            step={0.01}\n            value={settings.highlights ?? 0}\n          />\n          <SettingSlider\n            format={(v) => `${Math.round(v)}°`}\n            label=\"Angle\"\n            max={180}\n            min={0}\n            onChange={(v) => patch(\"angle\", v)}\n            step={1}\n            value={settings.angle ?? 0}\n          />\n          <SettingSlider\n            label=\"Edges\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"edges\", v)}\n            step={0.01}\n            value={settings.edges ?? 0}\n          />\n          <SettingSlider\n            label=\"Blur\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"blur\", v)}\n            step={0.01}\n            value={settings.blur ?? 0}\n          />\n          <SettingSlider\n            label=\"Stretch\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"stretch\", v)}\n            step={0.01}\n            value={settings.stretch ?? 0}\n          />\n          <SettingSlider\n            label=\"Shift\"\n            max={1}\n            min={-1}\n            onChange={(v) => patch(\"shift\", v)}\n            step={0.01}\n            value={settings.shift ?? 0}\n          />\n          <SettingSlider\n            label=\"Margin\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"margin\", v)}\n            step={0.01}\n            value={settings.margin ?? 0}\n          />\n          <SettingSlider\n            label=\"Grain mixer\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"grainMixer\", v)}\n            step={0.01}\n            value={settings.grainMixer ?? 0}\n          />\n          <SettingSlider\n            label=\"Grain overlay\"\n            max={1}\n            min={0}\n            onChange={(v) => patch(\"grainOverlay\", v)}\n            step={0.01}\n            value={settings.grainOverlay ?? 0}\n          />\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport { FlutedGlassDemo }\nexport default FlutedGlassDemo\n",
      "type": "registry:component"
    }
  ]
}