{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dither-shine",
  "title": "Dither shine",
  "description": "A two-color Bayer-dithered band that sweeps corner to corner at a stepped frame rate.",
  "registryDependencies": [
    "@ui95/theme",
    "@ui95/shader-mount"
  ],
  "files": [
    {
      "path": "lib/shaders/dither-shine.ts",
      "content": "import type { ShaderColorParam, ShaderMotionParams, ShaderPreset } from \"./types\"\n\nexport const ditherShineFragmentShader = `#version 300 es\nprecision highp float;\n\nuniform float u_time;\nuniform vec2 u_resolution;\nuniform float u_pxSize;\nuniform vec4 u_colorBack;\nuniform vec4 u_colorFront;\nuniform float u_type;\nuniform float u_bandWidth;\nuniform float u_sweepDuration;\nuniform float u_pauseDuration;\n\nout vec4 fragColor;\n\nfloat readBayer2(vec2 cell) {\n  vec2 corner = floor(cell);\n  return fract(corner.x * 0.5 + corner.y * corner.y * 0.75);\n}\n\nfloat readBayer(vec2 cell, float size) {\n  float fine = readBayer2(cell);\n  if (size < 3.0) {\n    return fine;\n  }\n  float medium = readBayer2(cell * 0.5) * 0.25 + fine;\n  if (size < 6.0) {\n    return medium;\n  }\n  return (readBayer2(cell * 0.25) * 0.25 + readBayer2(cell * 0.5)) * 0.25 + fine;\n}\n\nfloat readBandProfile(float offset) {\n  float band = step(abs(offset), 0.5) * clamp(0.25 + (0.4375 - abs(offset)) * 2.0, 0.0, 1.0);\n  float glintOffset = abs(offset + 0.9375);\n  float glint = step(glintOffset, 0.1875) * (1.0 - glintOffset / 0.375);\n  return max(band, glint);\n}\n\nvoid main() {\n  vec2 fragment = vec2(gl_FragCoord.x, u_resolution.y - gl_FragCoord.y);\n  vec2 cell = floor(fragment / u_pxSize);\n  vec2 grid = max(ceil(u_resolution / u_pxSize) - 1.0, vec2(1.0));\n  float diagonal = (cell.x + cell.y) / (grid.x + grid.y);\n\n  float bandWidth = max(u_bandWidth, 0.001);\n  float sweepDuration = max(u_sweepDuration, 0.001);\n  float cycle = sweepDuration + max(u_pauseDuration, 0.0);\n  float progress = clamp(mod(u_time, cycle) / sweepDuration, 0.0, 1.0);\n  float center = mix(-0.5 * bandWidth, 1.0 + 1.125 * bandWidth, progress);\n  float intensity = readBandProfile((diagonal - center) / bandWidth);\n\n  vec4 color = intensity > readBayer(cell, u_type) ? u_colorFront : u_colorBack;\n  fragColor = vec4(color.rgb * color.a, color.a);\n}\n`\n\nexport const DitherShineTypes = {\n  \"2x2\": 2,\n  \"4x4\": 4,\n  \"8x8\": 8,\n} as const\n\nexport type DitherShineType = keyof typeof DitherShineTypes\n\nexport type DitherShineUniforms = {\n  u_type: number\n  u_bandWidth: number\n  u_sweepDuration: number\n  u_pauseDuration: number\n}\n\nexport type DitherShineColors = {\n  u_colorBack: ShaderColorParam\n  u_colorFront: ShaderColorParam\n}\n\nexport type DitherShineParams = Required<ShaderMotionParams> & {\n  colorBack: ShaderColorParam\n  colorFront: ShaderColorParam\n  type: DitherShineType\n  bandWidth: number\n  sweepDuration: number\n  pauseDuration: number\n}\n\nexport const defaultPreset: ShaderPreset<DitherShineParams> = {\n  name: \"Default\",\n  params: {\n    speed: 1,\n    frame: 0,\n    fps: 10,\n    colorBack: \"--primary\",\n    colorFront: \"--primary-foreground\",\n    type: \"4x4\",\n    bandWidth: 0.35,\n    sweepDuration: 1.2,\n    pauseDuration: 1.2,\n  },\n}\n\nexport const glintPreset: ShaderPreset<DitherShineParams> = {\n  name: \"Glint\",\n  params: {\n    ...defaultPreset.params,\n    type: \"2x2\",\n    bandWidth: 0.14,\n    sweepDuration: 0.6,\n    pauseDuration: 1.8,\n  },\n}\n\nexport const slowPreset: ShaderPreset<DitherShineParams> = {\n  name: \"Slow\",\n  params: {\n    ...defaultPreset.params,\n    type: \"8x8\",\n    bandWidth: 0.6,\n    sweepDuration: 2.4,\n    pauseDuration: 1.6,\n  },\n}\n\nexport const ditherShinePresets: readonly ShaderPreset<DitherShineParams>[] = [\n  defaultPreset,\n  glintPreset,\n  slowPreset,\n]\n",
      "type": "registry:lib",
      "target": "@lib/shaders/dither-shine.ts"
    },
    {
      "path": "components/ui/dither-shine.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { ShaderMount, type ShaderMountProps } from \"@/components/ui/shader-mount\"\nimport {\n  defaultPreset,\n  type DitherShineColors,\n  ditherShineFragmentShader,\n  type DitherShineParams,\n  DitherShineTypes,\n  type DitherShineUniforms,\n} from \"@/lib/shaders/dither-shine\"\n\ntype DitherShineProps = Omit<ShaderMountProps, \"fragmentShader\" | \"uniforms\" | \"colors\"> &\n  Partial<DitherShineParams>\n\nconst DitherShine = React.memo(function DitherShine({\n  speed = defaultPreset.params.speed,\n  frame = defaultPreset.params.frame,\n  fps = defaultPreset.params.fps,\n  colorBack = defaultPreset.params.colorBack,\n  colorFront = defaultPreset.params.colorFront,\n  type = defaultPreset.params.type,\n  bandWidth = defaultPreset.params.bandWidth,\n  sweepDuration = defaultPreset.params.sweepDuration,\n  pauseDuration = defaultPreset.params.pauseDuration,\n  ...props\n}: DitherShineProps) {\n  const uniforms = React.useMemo<DitherShineUniforms>(\n    () => ({\n      u_type: DitherShineTypes[type],\n      u_bandWidth: bandWidth,\n      u_sweepDuration: sweepDuration,\n      u_pauseDuration: pauseDuration,\n    }),\n    [type, bandWidth, sweepDuration, pauseDuration]\n  )\n  const colors = React.useMemo<DitherShineColors>(\n    () => ({ u_colorBack: colorBack, u_colorFront: colorFront }),\n    [colorBack, colorFront]\n  )\n\n  return (\n    <ShaderMount\n      data-slot=\"dither-shine\"\n      {...props}\n      speed={speed}\n      frame={frame}\n      fps={fps}\n      fragmentShader={ditherShineFragmentShader}\n      uniforms={uniforms}\n      colors={colors}\n    />\n  )\n})\n\nexport { DitherShine }\nexport type { DitherShineProps }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "ui"
  ],
  "type": "registry:ui"
}