{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "layouts-floating-header",
  "title": "Floating Header",
  "description": "A scroll-driven floating header that morphs width, radius, and elevation as the user scrolls.",
  "dependencies": [
    "class-variance-authority",
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/layouts/floating-header/index.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n  motion,\n  useScroll,\n  useTransform,\n  useSpring,\n  useMotionTemplate,\n  type HTMLMotionProps,\n} from 'motion/react';\nimport * as React from 'react';\n\nconst floatingHeaderVariants = cva('sticky z-50 flex items-center mx-auto', {\n  variants: {\n    intent: {\n      primary: 'text-primary-foreground bg-primary border-primary',\n      accent: 'text-accent-foreground bg-accent border-accent',\n      secondary: 'text-secondary-foreground bg-secondary border-secondary',\n      success: 'text-success-foreground bg-success border-success',\n      warning: 'text-warning-foreground bg-warning border-warning',\n      error: 'text-error-foreground bg-error border-error',\n      info: 'text-info-foreground bg-info border-info',\n      neutral: 'text-foreground bg-background border-border',\n    },\n    styling: {\n      solid: 'border-none',\n      outline: 'border border-2 bg-background',\n      glass: 'backdrop-blur-md border-none',\n    },\n    size: {\n      sm: 'px-4 py-2 text-sm gap-2',\n      md: 'px-6 py-3 text-sm gap-3',\n      lg: 'px-8 py-4 text-base gap-4',\n    },\n  },\n  compoundVariants: [\n    { intent: 'primary', styling: 'outline', className: 'text-primary' },\n    { intent: 'accent', styling: 'outline', className: 'text-accent' },\n    { intent: 'secondary', styling: 'outline', className: 'text-secondary' },\n    { intent: 'success', styling: 'outline', className: 'text-success' },\n    { intent: 'warning', styling: 'outline', className: 'text-warning' },\n    { intent: 'error', styling: 'outline', className: 'text-error' },\n    { intent: 'info', styling: 'outline', className: 'text-info' },\n    { intent: 'neutral', styling: 'outline', className: 'text-foreground' },\n    { styling: 'glass', intent: 'primary', className: 'bg-primary/60' },\n    { styling: 'glass', intent: 'accent', className: 'bg-accent/60' },\n    { styling: 'glass', intent: 'secondary', className: 'bg-secondary/60' },\n    { styling: 'glass', intent: 'success', className: 'bg-success/60' },\n    { styling: 'glass', intent: 'warning', className: 'bg-warning/60' },\n    { styling: 'glass', intent: 'error', className: 'bg-error/60' },\n    { styling: 'glass', intent: 'info', className: 'bg-info/60' },\n    { styling: 'glass', intent: 'neutral', className: 'bg-background/60' },\n  ],\n  defaultVariants: {\n    intent: 'neutral',\n    styling: 'glass',\n    size: 'md',\n  },\n});\n\nconst SHAPE_RADII = {\n  rounded: 6,\n  pill: 9999,\n  sharp: 0,\n} as const;\n\nconst SHADOW_CONFIG = {\n  raised: {\n    layerOne: {\n      hOffset: [0, 0],\n      vOffset: [0, 4],\n      blur: [0, 6],\n      spread: [0, -1],\n      opacity: [0, 0.1],\n    },\n    layerTwo: {\n      hOffset: [0, 0],\n      vOffset: [0, 2],\n      blur: [0, 4],\n      spread: [0, -2],\n      opacity: [0, 0.1],\n    },\n  },\n  floating: {\n    layerOne: {\n      hOffset: [0, 0],\n      vOffset: [1, 10],\n      blur: [3, 15],\n      spread: [0, -3],\n      opacity: [0.05, 0.15],\n    },\n    layerTwo: {\n      hOffset: [0, 0],\n      vOffset: [1, 4],\n      blur: [2, 6],\n      spread: [-1, -4],\n      opacity: [0.05, 0.15],\n    },\n  },\n};\n\ntype FloatingHeaderShape = keyof typeof SHAPE_RADII;\ntype FloatingHeaderElevation = keyof typeof SHADOW_CONFIG;\n\ntype FloatingHeaderProps = Omit<HTMLMotionProps<'header'>, 'style'> &\n  VariantProps<typeof floatingHeaderVariants> & {\n    shape?: FloatingHeaderShape;\n    elevation?: FloatingHeaderElevation;\n    targetWidth?: number;\n    scrollRef?: React.RefObject<HTMLElement | null>;\n  };\n\nfunction FloatingHeader({\n  className,\n  children,\n  intent,\n  styling,\n  size,\n  shape = 'rounded',\n  elevation = 'raised',\n  targetWidth = 85,\n  scrollRef,\n  ...props\n}: FloatingHeaderProps) {\n  const resolvedElevation: FloatingHeaderElevation = elevation ?? 'raised';\n\n  const { scrollYProgress } = useScroll({\n    ...(scrollRef ? { container: scrollRef } : {}),\n    offset: ['start start', 'end start'],\n  });\n\n  const smoothProgress = useSpring(scrollYProgress, {\n    stiffness: 150,\n    damping: 15,\n    mass: 0.8,\n  });\n\n  const motionWidth = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    ['100%', `${targetWidth}%`],\n  );\n  const motionRadius = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    [0, SHAPE_RADII[shape]],\n  );\n  const motionY = useTransform(smoothProgress, [0, 0.2], ['0rem', '1rem']);\n  const motionScale = useTransform(smoothProgress, [0, 0.2], [1, 1.05]);\n\n  const config = SHADOW_CONFIG[resolvedElevation];\n  const shadowHOffset = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerOne.hOffset,\n  );\n  const shadowVOffset = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerOne.vOffset,\n  );\n  const shadowBlur = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerOne.blur,\n  );\n  const shadowSpread = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerOne.spread,\n  );\n  const shadowOpacity = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerOne.opacity,\n  );\n  const shadowHOffset2 = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerTwo.hOffset,\n  );\n  const shadowVOffset2 = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerTwo.vOffset,\n  );\n  const shadowBlur2 = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerTwo.blur,\n  );\n  const shadowSpread2 = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerTwo.spread,\n  );\n  const shadowOpacity2 = useTransform(\n    smoothProgress,\n    [0, 0.2],\n    config.layerTwo.opacity,\n  );\n  const motionShadow = useMotionTemplate`${shadowHOffset}px ${shadowVOffset}px ${shadowBlur}px ${shadowSpread}px oklch(from var(--primary) l c h / ${shadowOpacity}), ${shadowHOffset2}px ${shadowVOffset2}px ${shadowBlur2}px ${shadowSpread2}px oklch(from var(--primary) l c h / ${shadowOpacity2})`;\n\n  return (\n    <motion.header\n      role=\"banner\"\n      data-slot=\"floating-header\"\n      className={cn(\n        floatingHeaderVariants({ intent, styling, size }),\n        className,\n      )}\n      style={{\n        width: motionWidth,\n        borderRadius: motionRadius,\n        boxShadow: motionShadow,\n        top: motionY,\n        scale: motionScale,\n      }}\n      {...props}\n    >\n      {children}\n    </motion.header>\n  );\n}\n\nexport { FloatingHeader, floatingHeaderVariants, type FloatingHeaderProps };\n",
      "type": "registry:ui",
      "target": "layouts/azemmur/floating-header.tsx"
    }
  ],
  "type": "registry:ui"
}