{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glass-logo-reveal",
  "title": "Glass Logo Reveal",
  "description": "A WebGL2 logo sting: a capsule of thick glass carries your mark and name into view and lands on the real lockup.",
  "dependencies": [
    "clsx"
  ],
  "files": [
    {
      "path": "registry/glassyui/glass-logo-reveal.tsx",
      "content": "'use client';\n\n/* eslint-disable @next/next/no-img-element -- The registry also installs outside Next.js. */\nimport clsx from 'clsx';\nimport { forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';\nimport type { HTMLAttributes } from 'react';\nimport {\n  createLogoRevealRenderer,\n  LOGO_REVEAL_LENGTH,\n  paintLockup,\n  readColour,\n} from './lib/logo-reveal-renderer';\nimport type { LogoRevealRenderer } from './lib/logo-reveal-renderer';\nimport './styles/logo-reveal.css';\n\nexport type GlassLogoRevealPlay = 'visible' | 'mount';\n\ntype RevealState = 'waiting' | 'playing' | 'held' | 'ended';\n\nexport type GlassLogoRevealProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {\n  /** The mark, from your origin or served with CORS. An SVG stays sharp under the glass. */\n  markSrc: string;\n  /** The name set beside the mark, in the figure's font and colour. Also its accessible name. */\n  name: string;\n  /** Plays once: when the figure is mostly on screen, or as soon as it mounts. */\n  play?: GlassLogoRevealPlay;\n  /** Holds one moment, from 0 (the dark stage) to 1 (the lockup). Clearing it plays on. */\n  progress?: number;\n  /** Milliseconds from the dark stage to the lockup. */\n  duration?: number;\n  /** Called once the lockup has landed. */\n  onComplete?: () => void;\n};\n\nconst QUALITY = {\n  low: { ratio: 1, taps: 3 },\n  medium: { ratio: 1.5, taps: 5 },\n  high: { ratio: 2, taps: 7 },\n} as const;\n\n/** Reduced motion or transparency, from the system or the provider's preferences. */\nfunction stillness() {\n  const root = document.documentElement.dataset;\n\n  return (\n    root.lgReduceMotion === '1' ||\n    root.lgReduceTransparency === '1' ||\n    matchMedia('(prefers-reduced-motion: reduce)').matches ||\n    matchMedia('(prefers-reduced-transparency: reduce)').matches\n  );\n}\n\nfunction luminance([r, g, b, a]: readonly number[]) {\n  return a ? (0.2126 * r + 0.7152 * g + 0.0722 * b) / a : 0;\n}\n\n/**\n * A logo sting: a capsule of thick glass carries the mark and then the name into view, bending\n * and splitting them at its rim, and lands exactly on the real lockup, which stays in the page.\n */\nexport const GlassLogoReveal = forwardRef<HTMLElement, GlassLogoRevealProps>(\n  (\n    { className, duration = 2600, markSrc, name, onComplete, play = 'visible', progress, ...rest },\n    forwardedRef,\n  ) => {\n    const figureRef = useRef<HTMLElement>(null);\n    const canvasRef = useRef<HTMLCanvasElement>(null);\n    const markRef = useRef<HTMLImageElement>(null);\n    const nameRef = useRef<HTMLSpanElement>(null);\n    const baselineRef = useRef<HTMLSpanElement>(null);\n    const [state, setState] = useState<RevealState>('waiting');\n    const [error, setError] = useState<string>();\n    const latest = useRef({ duration, onComplete, play, progress });\n    const controls = useRef<{ paint: () => void; update: () => void } | null>(null);\n\n    useImperativeHandle(forwardedRef, () => figureRef.current!, []);\n\n    // The running reveal reads the latest props without starting over.\n    useLayoutEffect(() => {\n      latest.current = { duration, onComplete, play, progress };\n      controls.current?.update();\n    });\n\n    useLayoutEffect(() => {\n      controls.current?.paint();\n    }, [markSrc, name]);\n\n    useLayoutEffect(() => {\n      const figure = figureRef.current!;\n      const canvas = canvasRef.current!;\n      const mark = markRef.current!;\n      const parts = { figure, mark, name: nameRef.current!, baseline: baselineRef.current! };\n      let renderer: LogoRevealRenderer;\n\n      try {\n        renderer = createLogoRevealRenderer(canvas);\n      } catch {\n        // Without WebGL2 the lockup simply shows.\n        figure.dataset.renderer = 'none';\n        setState('ended');\n\n        return;\n      }\n\n      figure.dataset.renderer = 'webgl';\n\n      let alive = true;\n      let ready = false;\n      let visible = false;\n      let begun = false;\n      let completed = false;\n      // How far through the reveal it is, from 0 to 1.\n      let moment = 0;\n      let frame = 0;\n      let painting = 0;\n      let last = 0;\n\n      const draw = () => {\n        if (ready) renderer.draw(moment * LOGO_REVEAL_LENGTH);\n      };\n\n      const stop = () => {\n        cancelAnimationFrame(frame);\n        frame = 0;\n      };\n\n      const finish = () => {\n        stop();\n        moment = 1;\n        setState('ended');\n\n        if (!completed) {\n          completed = true;\n          latest.current.onComplete?.();\n        }\n      };\n\n      const tick = (now: number) => {\n        frame = 0;\n\n        if (!alive) return;\n\n        // A background tab pauses the reveal rather than skipping it.\n        const step = last ? Math.min(now - last, 50) : 0;\n\n        last = now;\n        moment = Math.min(moment + step / Math.max(latest.current.duration, 1), 1);\n        draw();\n\n        if (moment >= 1) finish();\n        else frame = requestAnimationFrame(tick);\n      };\n\n      const update = () => {\n        if (!alive) return;\n\n        const { play: when, progress: held } = latest.current;\n\n        // Reduced motion keeps the end state, even for a held moment.\n        if (stillness()) {\n          finish();\n\n          return;\n        }\n\n        if (held !== undefined) {\n          stop();\n          moment = Math.min(Math.max(held, 0), 1);\n          // Clearing the hold plays on from this moment.\n          begun = true;\n          setState(moment >= 1 ? 'ended' : 'held');\n          draw();\n\n          return;\n        }\n\n        if (moment >= 1) {\n          finish();\n\n          return;\n        }\n\n        if (!ready) return;\n\n        if (begun || when === 'mount' || visible) {\n          begun = true;\n          setState('playing');\n\n          if (!frame) {\n            last = 0;\n            frame = requestAnimationFrame(tick);\n          }\n\n          return;\n        }\n\n        setState('waiting');\n        draw();\n      };\n\n      // Draws the lockup as the page lays it out, in the figure's colours, at the device's\n      // resolution within the quality preference.\n      const paint = () => {\n        painting = 0;\n\n        if (!alive || !mark.complete || !mark.naturalWidth) return;\n\n        const width = figure.clientWidth;\n        const height = figure.clientHeight;\n\n        if (!width || !height) return;\n\n        const level = document.documentElement.dataset.lgQuality;\n        const quality = level === 'low' || level === 'medium' ? QUALITY[level] : QUALITY.high;\n\n        const ratio = Math.min(\n          window.devicePixelRatio || 1,\n          quality.ratio,\n          renderer.limit / Math.max(width, height),\n        );\n\n        const style = getComputedStyle(figure);\n        const backdrop = readColour(style.backgroundColor);\n\n        renderer.resize(width, height, ratio);\n        renderer.setTaps(quality.taps);\n        renderer.setStage({\n          backdrop,\n          // A clear stage takes its light from the ink: pale ink means a dark page.\n          dark:\n            backdrop[3] > 0.5\n              ? luminance(backdrop) < 0.5\n              : luminance(readColour(style.color)) >= 0.5,\n          thickness: Number.parseFloat(style.getPropertyValue('--lg-glass-thickness')) || 1,\n        });\n\n        try {\n          const lockup = paintLockup(parts, canvas.width, canvas.height);\n\n          renderer.setLockup(lockup.canvas, lockup.layout);\n        } catch {\n          setError('The mark needs permission to load across origins.');\n          finish();\n\n          return;\n        }\n\n        ready = true;\n        update();\n        draw();\n      };\n\n      const schedule = () => {\n        if (alive && !painting) painting = requestAnimationFrame(paint);\n      };\n\n      const onError = () => {\n        setError('The mark could not load.');\n        finish();\n      };\n\n      const onLost = () => {\n        ready = false;\n        finish();\n      };\n\n      const sight = new IntersectionObserver(\n        ([entry]) => {\n          visible = entry.isIntersecting;\n          update();\n        },\n        { threshold: 0.5 },\n      );\n\n      const resize = new ResizeObserver(schedule);\n\n      const preferences = new MutationObserver(() => {\n        schedule();\n        update();\n      });\n\n      const queries = [\n        '(prefers-reduced-motion: reduce)',\n        '(prefers-reduced-transparency: reduce)',\n        '(prefers-color-scheme: dark)',\n      ].map((query) => matchMedia(query));\n\n      const fonts = document.fonts;\n\n      controls.current = { paint: schedule, update };\n      sight.observe(figure);\n      resize.observe(figure);\n      preferences.observe(document.documentElement, {\n        attributes: true,\n        attributeFilter: [\n          'class',\n          'style',\n          'data-lg-theme',\n          'data-lg-quality',\n          'data-lg-reduce-motion',\n          'data-lg-reduce-transparency',\n        ],\n      });\n      queries.forEach((query) => query.addEventListener('change', schedule));\n      queries.forEach((query) => query.addEventListener('change', update));\n      mark.addEventListener('load', schedule);\n      mark.addEventListener('error', onError);\n      canvas.addEventListener('webglcontextlost', onLost);\n      fonts.addEventListener('loadingdone', schedule);\n\n      if (mark.complete && !mark.naturalWidth && mark.currentSrc) onError();\n\n      // Fonts that are still loading would change the name's shape.\n      void fonts.ready.then(schedule);\n      schedule();\n      update();\n\n      return () => {\n        alive = false;\n        controls.current = null;\n        stop();\n        cancelAnimationFrame(painting);\n        sight.disconnect();\n        resize.disconnect();\n        preferences.disconnect();\n        queries.forEach((query) => {\n          query.removeEventListener('change', schedule);\n          query.removeEventListener('change', update);\n        });\n        mark.removeEventListener('load', schedule);\n        mark.removeEventListener('error', onError);\n        canvas.removeEventListener('webglcontextlost', onLost);\n        fonts.removeEventListener('loadingdone', schedule);\n        renderer.dispose();\n      };\n    }, []);\n\n    return (\n      <figure\n        ref={figureRef}\n        aria-label={name}\n        className={clsx('lg-logo-reveal', className)}\n        data-state={state}\n        role=\"img\"\n        {...rest}\n      >\n        <canvas ref={canvasRef} aria-hidden className=\"lg-logo-reveal-canvas\" />\n        <span aria-hidden className=\"lg-logo-reveal-lockup\">\n          <img\n            ref={markRef}\n            alt=\"\"\n            className=\"lg-logo-reveal-mark\"\n            crossOrigin=\"anonymous\"\n            draggable={false}\n            src={markSrc}\n          />\n          <span ref={nameRef} className=\"lg-logo-reveal-name\">\n            <span ref={baselineRef} className=\"lg-logo-reveal-baseline\" />\n            {name}\n          </span>\n        </span>\n        {error ? <figcaption className=\"lg-logo-reveal-error\">{error}</figcaption> : null}\n      </figure>\n    );\n  },\n);\n\nGlassLogoReveal.displayName = 'GlassLogoReveal';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-logo-reveal.tsx"
    },
    {
      "path": "registry/glassyui/lib/logo-reveal-renderer.ts",
      "content": "// A capsule of thick glass carries the lockup into view, in three beats, drawn by one fragment\n// shader over the stage.\n//\n// Arrival: the stage is dark and the lockup is lit only where the glass is. The capsule glides in\n// over the mark on the library's ease-out, magnifying it a little and pulling the strokes nearest\n// its rim out into the bevel, where they split into colour.\n//\n// Pass: the capsule's leading end springs out across the name. While it moves, the end cap\n// stretches the letters it is reaching, split into colour, and lets them go as it passes.\n//\n// Settle: the glass relaxes to plain, one highlight runs along it, the stage lights come up\n// around it and its rim fades, leaving the lockup exactly where the page has it.\n//\n// Lengths are fractions of the capsule's height, which follows the lockup's, so the reveal keeps\n// its proportions at any size.\n\n/** Seconds the reveal takes at its regular pace. */\nexport const LOGO_REVEAL_LENGTH = 2.6;\n\nconst VERTEX = `#version 300 es\nvoid main() {\n  vec2 corner = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n  gl_Position = vec4(corner * 2.0 - 1.0, 0.0, 1.0);\n}`;\n\nconst FRAGMENT = `#version 300 es\nprecision highp float;\nout vec4 colour;\n\nuniform vec2 stage;\nuniform vec2 pixels;\nuniform vec4 backdrop;\nuniform float dark;\nuniform float seed;\nuniform sampler2D lockup;\nuniform int taps;\nuniform vec3 hues[8];\nuniform float shifts[8];\n\nuniform vec2 centre;\nuniform vec2 size;\nuniform float bevel;\nuniform float zoom;\nuniform float bend;\nuniform float smear;\nuniform float split;\nuniform float rims;\nuniform float haze;\nuniform float sweep;\nuniform float shine;\nuniform float inside;\nuniform float outside;\nuniform float soft;\nuniform float bloom;\n\nconst vec2 KEY = vec2(-0.5547, -0.8321);\n\nfloat px;\n\nvec4 over(vec4 content) {\n  return content + backdrop * (1.0 - content.a);\n}\n\nvec4 lockupAt(vec2 point, float bias) {\n  vec2 uv = point / stage;\n  vec2 within = step(vec2(0.0), uv) * step(uv, vec2(1.0));\n\n  return texture(lockup, uv, bias) * (within.x * within.y);\n}\n\n// Highlights add light on a dark stage and shade on a light one, as glass edges do.\nvec4 light(vec4 base, vec3 amount) {\n  vec3 lit = mix(base.rgb * (1.0 - 0.55 * amount), base.rgb + amount, dark);\n  float alpha = max(base.a, dark * min(max(amount.r, max(amount.g, amount.b)), 1.0));\n\n  return vec4(lit, alpha);\n}\n\nfloat capsule(vec2 p) {\n  vec2 q = abs(p - centre) - size + size.y;\n\n  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - size.y;\n}\n\nvoid main() {\n  px = stage.x / pixels.x;\n  vec2 p = vec2(gl_FragCoord.x, pixels.y - gl_FragCoord.y) * (stage / pixels);\n  vec2 e = vec2(0.5, 0.0);\n  vec2 slope = vec2(capsule(p + e.xy) - capsule(p - e.xy), capsule(p + e.yx) - capsule(p - e.yx));\n  vec2 normal = slope / max(length(slope), 1e-5);\n  float d = capsule(p);\n  float cover = clamp(0.5 - d / px, 0.0, 1.0);\n  vec4 plain = over(lockupAt(p, 0.0) * outside);\n  vec4 seen = plain;\n\n  if (cover > 0.0) {\n    // 0 at the rim, 1 where the bevel meets the flat face.\n    float depth = clamp(-d / bevel, 0.0, 1.0);\n    float rim = (1.0 - depth) * (1.0 - depth);\n    // The ends pull hardest, the long edges gently, and the leading end reaches further back\n    // while it moves.\n    float lead = smoothstep(0.2, 1.0, (p.x - centre.x) / max(size.x, 1.0));\n    float pull = bend * (0.35 + 0.65 * abs(normal.x)) + smear * lead * lead;\n    float bias = soft * (0.3 + 2.0 * rim);\n    // A capsule of glass is a rod: it magnifies across its spine and only rounds out at the ends,\n    // so type under it grows taller without sliding along it.\n    float run = max(size.x - size.y, 0.0);\n    vec2 spine = vec2(clamp(p.x, centre.x - run, centre.x + run), centre.y);\n    vec2 rel = p - spine;\n    vec3 sum = vec3(0.0);\n    float alpha = 0.0;\n\n    // The bevel pulls the strokes nearest the rim out into it; violet bends further than red.\n    for (int i = 0; i < 8; i++) {\n      if (i >= taps) break;\n      float k = 1.0 + split * shifts[i];\n      float magnify = 1.0 + (zoom - 1.0) * (1.0 + 0.3 * (k - 1.0));\n      vec2 point = spine + rel / magnify - normal * (pull * rim * k);\n      vec4 through = over(lockupAt(point, bias) * inside);\n      sum += hues[i] * through.rgb;\n      alpha += through.a;\n    }\n\n    // Thick glass scatters a little of what it magnifies, so it glows from within.\n    vec2 middle = spine + rel / zoom;\n    vec4 scatter = (lockupAt(middle, 3.5) * 0.4 + lockupAt(middle, 5.5) * 0.6) * inside;\n    vec4 glass = vec4(sum + scatter.rgb * haze * dark, alpha / float(taps));\n\n    // A darker band just inside the polished edge, where the bevel looks out at the dark.\n    float band = exp(-pow((d + 0.09 * size.y) / (0.07 * size.y), 2.0));\n    glass.rgb *= 1.0 - 0.45 * band * rims * dark;\n    seen = mix(plain, glass, cover);\n  }\n\n  float facing = max(dot(normal, KEY), 0.0);\n  float away = max(dot(normal, -KEY), 0.0);\n  float w = 0.012 * size.y + px;\n  float edge = exp(-pow((d + w) / w, 2.0));\n  float face = exp(-pow((d + bevel * 0.45) / (0.02 * size.y + px), 2.0));\n  float wall = exp(-pow((d - 0.06 * size.y) / (0.018 * size.y + px), 2.0));\n  float halo = exp(-max(d, 0.0) / (0.5 * size.y)) * (1.0 - cover);\n  // One slanted highlight runs along the glass as it settles.\n  float across = p.x - (centre.x - size.x + sweep * 2.0 * size.x) + 0.35 * (p.y - centre.y);\n  float sheen = exp(-pow(across / (0.9 * size.y), 2.0)) * shine * cover;\n\n  // Cool where the rim faces the light, warm on the far side.\n  vec3 tint = mix(vec3(1.0, 0.76, 0.46), vec3(0.62, 0.84, 1.0), facing);\n  vec3 rimLight = mix(vec3(1.0), tint, 0.45) * edge * (0.3 + 0.6 * pow(facing, 1.5) + 0.35 * pow(away, 2.0));\n  vec3 lines = vec3(0.86, 0.92, 1.0) * (face * (0.05 + 0.1 * facing) + wall * 0.06);\n  vec3 glow = vec3(0.6, 0.72, 0.9) * halo * 0.05;\n  // The lockup glows only where it can be seen: through the glass, just past its rim, or once\n  // the stage lights come up.\n  float shown = max(outside, inside * max(cover, exp(-max(d, 0.0) / (0.4 * size.y))));\n  vec3 amount = (rimLight + lines + glow) * rims + vec3(sheen * mix(0.12, 0.32, dark)) +\n    textureLod(lockup, p / stage, 4.5).rgb * bloom * shown;\n  vec4 c = light(seen, amount);\n\n  // Half a level of noise keeps the soft glows from banding while the glass is there.\n  float noise = fract(sin(dot(gl_FragCoord.xy + seed, vec2(12.9898, 78.233))) * 43758.5453);\n  c.rgb += (noise - 0.5) / 255.0 * c.a * step(0.001, rims + bloom + shine);\n  colour = clamp(c, 0.0, 1.0);\n}`;\n\n/** The finished lockup's box and its mark's horizontal extent, in stage pixels. */\ntype LogoRevealLayout = {\n  left: number;\n  right: number;\n  top: number;\n  bottom: number;\n  markLeft: number;\n  markRight: number;\n};\n\ntype LogoRevealStage = {\n  /** The stage colour, premultiplied, 0 to 1. */\n  backdrop: [number, number, number, number];\n  /** Whether highlights add light (a dark stage) or shade (a light one). */\n  dark: boolean;\n  /** The glass thickness preference: 1 is regular, thicker glass bends and splits more. */\n  thickness: number;\n};\n\nexport type LogoRevealRenderer = {\n  /** Draws the reveal `time` seconds in. */\n  draw: (time: number) => void;\n  /** The finished lockup at the canvas's own resolution. */\n  setLockup: (source: TexImageSource, layout: LogoRevealLayout) => void;\n  setStage: (stage: LogoRevealStage) => void;\n  /** Wavelengths sampled per pixel, from 3 to 8. */\n  setTaps: (taps: number) => void;\n  resize: (width: number, height: number, ratio: number) => void;\n  /** The largest canvas side the GPU accepts. */\n  limit: number;\n  dispose: () => void;\n};\n\nconst clamp = (value: number, low = 0, high = 1) => Math.min(Math.max(value, low), high);\n\n/** How far through [start, end] the time is, from 0 to 1. */\nconst span = (time: number, start: number, end: number) => clamp((time - start) / (end - start));\n\nconst mix = (a: number, b: number, x: number) => a + (b - a) * x;\n\nconst smooth = (x: number) => x * x * (3 - 2 * x);\n\nconst sineInOut = (x: number) => (1 - Math.cos(Math.PI * x)) / 2;\n\n/** A CSS cubic-bezier timing function, solved for x by bisection. */\nfunction bezier(x1: number, y1: number, x2: number, y2: number) {\n  const curve = (t: number, a: number, b: number) =>\n    3 * a * t * (1 - t) ** 2 + 3 * b * t * t * (1 - t) + t ** 3;\n\n  return (x: number) => {\n    let low = 0;\n    let high = 1;\n\n    for (let step = 0; step < 24; step++) {\n      const t = (low + high) / 2;\n\n      if (curve(t, x1, x2) < x) low = t;\n      else high = t;\n    }\n\n    return curve((low + high) / 2, y1, y2);\n  };\n}\n\n/** The library's arrival curve, --lg-ease-out. */\nconst easeOut = bezier(0.32, 0.72, 0, 1);\n\n/** A settle with one small overshoot, about 9%. */\nconst spring = (x: number) => (x >= 1 ? 1 : 1 - Math.exp(-6 * x) * Math.cos(8 * x));\n\n/**\n * Weights for `taps` wavelengths from red to violet. Each channel's weights sum to 1, so plain\n * glass passes white unchanged and only bending separates the colours.\n */\nfunction spectrum(taps: number) {\n  const bump = (x: number, middle: number, width: number) =>\n    Math.max(0, 1 - ((x - middle) / width) ** 2);\n\n  const raw = Array.from({ length: taps }, (_, index) => {\n    const x = index / (taps - 1);\n\n    return [bump(x, 0, 0.42) + 0.35 * bump(x, 1, 0.25), bump(x, 0.48, 0.3), bump(x, 0.85, 0.3)];\n  });\n\n  const totals = [0, 1, 2].map((channel) =>\n    raw.reduce((sum, weights) => sum + weights[channel], 0),\n  );\n\n  return {\n    hues: raw.map((weights) => weights.map((weight, channel) => weight / totals[channel])),\n    shifts: raw.map((_, index) => (index / (taps - 1)) * 2 - 1),\n  };\n}\n\n/** Every uniform for one moment of the reveal, for glass `thickness` times regular. */\nfunction revealFrame(time: number, height: number, layout: LogoRevealLayout, thickness: number) {\n  const t = clamp(time, 0, LOGO_REVEAL_LENGTH);\n  const tall = capsuleHeight(layout, height);\n  const half = tall / 2;\n  const middle = (layout.top + layout.bottom) / 2;\n  const mark = (layout.markLeft + layout.markRight) / 2;\n  // Long enough to carry the mark with a margin of glass on either side.\n  const pill = Math.max(tall * 1.25, layout.markRight - layout.markLeft + tall * 0.7);\n  const reach = layout.right + tall * 0.3;\n\n  // The arriving capsule's centre, then its leading end once it springs out across the name.\n  const arrival = mix(layout.left - tall * 1.4, mark, easeOut(span(t, 0.04, 0.95)));\n\n  const end = (at: number) =>\n    mix(mark + pill / 2, reach, spring(span(at, 0.82, 1.9))) + Math.min(arrival - mark, 0);\n\n  const left = arrival - pill / 2;\n  const right = Math.max(end(t), arrival + pill / 2);\n  const speed = Math.abs(end(t + 0.01) - end(t - 0.01)) / 0.02;\n  const settled = t >= LOGO_REVEAL_LENGTH;\n  const relax = sineInOut(span(t, 1.7, 2.3));\n\n  const pairs = {\n    centre: [(left + right) / 2, middle],\n    size: [(right - left) / 2, half],\n  } satisfies Record<string, readonly [number, number]>;\n\n  const floats = {\n    bevel: half * 0.5 * (0.75 + 0.25 * thickness),\n    zoom: settled ? 1 : 1 + 0.42 * (0.7 + 0.3 * thickness) * (1 - sineInOut(span(t, 0.35, 2.3))),\n    bend: settled ? 0 : half * 0.6 * (0.6 + 0.4 * thickness) * (1 - relax),\n    // The end cap reaches back hardest at the pass's fastest moment.\n    smear: settled\n      ? 0\n      : clamp(speed / (tall * 6)) * tall * 0.55 * (0.6 + 0.4 * thickness) * (1 - relax),\n    split: settled ? 0 : 0.14 * (0.5 + 0.5 * thickness) * (1 - sineInOut(span(t, 1.8, 2.3))),\n    rims: settled ? 0 : easeOut(span(t, 0, 0.3)) * (1 - span(t, 2.15, 2.55)),\n    haze: settled ? 0 : 0.3 * smooth(span(t, 0.1, 0.5)) * (1 - span(t, 1.6, 2.2)),\n    sweep: mix(-0.3, 1.3, span(t, 1.85, 2.45)),\n    shine: settled ? 0 : Math.sin(Math.PI * span(t, 1.85, 2.45)),\n    inside: smooth(span(t, 0.1, 0.45)),\n    outside: settled ? 1 : sineInOut(span(t, 1.75, 2.35)),\n    soft: settled ? 0 : 1 - span(t, 2.2, LOGO_REVEAL_LENGTH),\n    bloom: settled ? 0 : 0.12 * smooth(span(t, 0.3, 0.6)) * (1 - span(t, 1.9, 2.4)),\n  } satisfies Record<string, number>;\n\n  return { floats, pairs };\n}\n\n/** The capsule's height: a little over the lockup's, kept inside the stage. */\nfunction capsuleHeight(layout: LogoRevealLayout, height: number) {\n  return Math.max(Math.min((layout.bottom - layout.top) * 2, height * 0.86), 1);\n}\n\nfunction compile(gl: WebGL2RenderingContext, type: number, source: string) {\n  const shader = gl.createShader(type);\n\n  if (!shader) throw new Error('The logo reveal could not create a shader.');\n\n  try {\n    gl.shaderSource(shader, source);\n    gl.compileShader(shader);\n\n    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n      throw new Error(`The logo reveal shader did not compile: ${gl.getShaderInfoLog(shader)}`);\n    }\n\n    return shader;\n  } catch (error) {\n    gl.deleteShader(shader);\n    throw error;\n  }\n}\n\n/** One WebGL2 program over a full-stage triangle. Throws where WebGL2 is unavailable. */\nexport function createLogoRevealRenderer(canvas: HTMLCanvasElement): LogoRevealRenderer {\n  const gl = canvas.getContext('webgl2', {\n    alpha: true,\n    antialias: false,\n    depth: false,\n    premultipliedAlpha: true,\n    stencil: false,\n  });\n\n  if (!gl) throw new Error('The logo reveal needs WebGL2.');\n\n  const program = gl.createProgram();\n\n  if (!program) throw new Error('The logo reveal could not create a program.');\n\n  const shaders: WebGLShader[] = [];\n  let texture: WebGLTexture | null = null;\n  let vertices: WebGLVertexArrayObject | null = null;\n\n  const releaseShaders = () => {\n    const attached = gl.getAttachedShaders(program);\n\n    for (const shader of shaders) {\n      if (attached?.includes(shader)) gl.detachShader(program, shader);\n\n      gl.deleteShader(shader);\n    }\n\n    shaders.length = 0;\n  };\n\n  // Deletes what it made but keeps the context: React mounts effects twice in development, and a\n  // canvas whose context was lost on purpose never gets another.\n  const release = () => {\n    releaseShaders();\n    gl.useProgram(null);\n    gl.bindTexture(gl.TEXTURE_2D, null);\n    gl.bindVertexArray(null);\n\n    if (texture) gl.deleteTexture(texture);\n\n    if (vertices) gl.deleteVertexArray(vertices);\n\n    gl.deleteProgram(program);\n  };\n\n  try {\n    for (const [type, source] of [\n      [gl.VERTEX_SHADER, VERTEX],\n      [gl.FRAGMENT_SHADER, FRAGMENT],\n    ] as const) {\n      const shader = compile(gl, type, source);\n\n      shaders.push(shader);\n      gl.attachShader(program, shader);\n    }\n\n    gl.linkProgram(program);\n\n    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n      throw new Error(`The logo reveal program did not link: ${gl.getProgramInfoLog(program)}`);\n    }\n\n    releaseShaders();\n    gl.useProgram(program);\n    vertices = gl.createVertexArray();\n    gl.bindVertexArray(vertices);\n\n    const locations = new Map<string, WebGLUniformLocation | null>();\n\n    const location = (name: string) => {\n      if (!locations.has(name)) locations.set(name, gl.getUniformLocation(program, name));\n\n      return locations.get(name) ?? null;\n    };\n\n    const float = (name: string, value: number) => gl.uniform1f(location(name), value);\n\n    const pair = (name: string, [x, y]: readonly [number, number]) =>\n      gl.uniform2f(location(name), x, y);\n\n    texture = gl.createTexture();\n\n    if (!texture) throw new Error('The logo reveal could not create a texture.');\n\n    gl.activeTexture(gl.TEXTURE0);\n    gl.bindTexture(gl.TEXTURE_2D, texture);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n    // An empty pixel until the lockup is drawn.\n    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));\n    gl.generateMipmap(gl.TEXTURE_2D);\n    gl.uniform1i(location('lockup'), 0);\n\n    let height = 1;\n    let thickness = 1;\n\n    let layout: LogoRevealLayout = {\n      left: 0,\n      right: 0,\n      top: 0,\n      bottom: 0,\n      markLeft: 0,\n      markRight: 0,\n    };\n\n    const setTaps = (taps: number) => {\n      const count = Math.round(clamp(taps, 3, 8));\n      const { hues, shifts } = spectrum(count);\n\n      gl.uniform1i(location('taps'), count);\n      gl.uniform3fv(location('hues'), hues.flat());\n      gl.uniform1fv(location('shifts'), shifts);\n    };\n\n    setTaps(7);\n    gl.disable(gl.BLEND);\n\n    // SAFETY: WebGL2 defines MAX_TEXTURE_SIZE as a GLint parameter, which reads as a number.\n    const largest = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number;\n\n    return {\n      limit: Math.min(largest, 4096),\n      setLockup(source, next) {\n        layout = next;\n        gl.bindTexture(gl.TEXTURE_2D, texture);\n        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);\n        gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);\n        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);\n        gl.generateMipmap(gl.TEXTURE_2D);\n      },\n      setStage(stage) {\n        const [r, g, b, a] = stage.backdrop;\n\n        gl.uniform4f(location('backdrop'), r, g, b, a);\n        float('dark', stage.dark ? 1 : 0);\n        thickness = stage.thickness;\n      },\n      setTaps,\n      resize(width, nextHeight, ratio) {\n        height = nextHeight;\n        canvas.width = Math.max(1, Math.round(width * ratio));\n        canvas.height = Math.max(1, Math.round(nextHeight * ratio));\n        gl.viewport(0, 0, canvas.width, canvas.height);\n        pair('stage', [width, nextHeight]);\n        pair('pixels', [canvas.width, canvas.height]);\n      },\n      draw(time) {\n        if (gl.isContextLost()) return;\n\n        const { floats, pairs } = revealFrame(time, height, layout, thickness);\n\n        for (const [name, value] of Object.entries(floats)) float(name, value);\n\n        for (const [name, value] of Object.entries(pairs)) pair(name, value);\n\n        float('seed', (time * 61.7) % 97);\n        gl.drawArrays(gl.TRIANGLES, 0, 3);\n      },\n      dispose: release,\n    };\n  } catch (error) {\n    release();\n    throw error;\n  }\n}\n\n/** Reads any CSS colour as sRGB through a one-pixel canvas, premultiplied, 0 to 1. */\nexport function readColour(value: string): [number, number, number, number] {\n  const canvas = document.createElement('canvas');\n  canvas.width = canvas.height = 1;\n  const ink = canvas.getContext('2d', { willReadFrequently: true });\n\n  if (!ink) return [0, 0, 0, 0];\n\n  ink.fillStyle = value;\n  ink.fillRect(0, 0, 1, 1);\n\n  const [r, g, b, a] = ink.getImageData(0, 0, 1, 1).data;\n  const alpha = a / 255;\n\n  return [(r / 255) * alpha, (g / 255) * alpha, (b / 255) * alpha, alpha];\n}\n\ntype LogoRevealParts = {\n  figure: HTMLElement;\n  mark: HTMLImageElement;\n  /** The name's element. Its last child is the text. */\n  name: HTMLElement;\n  /** An empty inline block at the name's baseline. */\n  baseline: HTMLElement;\n};\n\n/**\n * The finished lockup as the page lays it out, drawn at the canvas's resolution: the mark where\n * its image sits and each character of the name where the browser placed it, so the last frame\n * lands on the real lockup.\n */\nexport function paintLockup(\n  { baseline, figure, mark, name }: LogoRevealParts,\n  pixelWidth: number,\n  pixelHeight: number,\n) {\n  const box = figure.getBoundingClientRect();\n  const canvas = document.createElement('canvas');\n  canvas.width = pixelWidth;\n  canvas.height = pixelHeight;\n  const ink = canvas.getContext('2d');\n\n  if (!ink || !box.width || !box.height)\n    throw new Error('The logo reveal could not draw its lockup.');\n\n  // Client rectangles are in the page's (possibly scaled) pixels; the stage uses the figure's own.\n  const toStage = figure.clientWidth / box.width;\n  const markBox = mark.getBoundingClientRect();\n  const nameBox = name.getBoundingClientRect();\n  const style = getComputedStyle(name);\n  const text = name.lastChild;\n  const rtl = style.direction === 'rtl';\n\n  ink.scale(pixelWidth / box.width, pixelHeight / box.height);\n  ink.drawImage(\n    mark,\n    markBox.left - box.left,\n    markBox.top - box.top,\n    markBox.width,\n    markBox.height,\n  );\n  ink.font = [\n    style.fontStyle,\n    style.fontVariantCaps === 'small-caps' ? 'small-caps' : '',\n    style.fontWeight,\n    style.fontSize,\n    style.fontFamily,\n  ]\n    .filter(Boolean)\n    .join(' ');\n  ink.fillStyle = style.color;\n  ink.textAlign = rtl ? 'right' : 'left';\n  ink.textBaseline = 'alphabetic';\n\n  // Browsers paint a line's text from whole device pixels, so the canvas does too.\n  const across = pixelWidth / box.width;\n  const down = pixelHeight / box.height;\n  const snapX = (x: number) => Math.round(x * across) / across;\n  const snapY = (y: number) => Math.round(y * down) / down;\n\n  if (text?.nodeType === Node.TEXT_NODE && text.textContent) {\n    const range = document.createRange();\n    const y = snapY(baseline.getBoundingClientRect().top - box.top);\n    let index = 0;\n\n    for (const { segment } of new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(\n      text.textContent,\n    )) {\n      range.setStart(text, index);\n      range.setEnd(text, index + segment.length);\n      index += segment.length;\n\n      const glyph = range.getClientRects()[0];\n\n      if (glyph && segment.trim())\n        ink.fillText(segment, snapX((rtl ? glyph.right : glyph.left) - box.left), y);\n    }\n  }\n\n  const stageX = (x: number) => (x - box.left) * toStage;\n  const stageY = (y: number) => (y - box.top) * toStage;\n\n  return {\n    canvas,\n    layout: {\n      left: stageX(Math.min(markBox.left, nameBox.left)),\n      right: stageX(Math.max(markBox.right, nameBox.right)),\n      top: stageY(Math.min(markBox.top, nameBox.top)),\n      bottom: stageY(Math.max(markBox.bottom, nameBox.bottom)),\n      markLeft: stageX(markBox.left),\n      markRight: stageX(markBox.right),\n    } satisfies LogoRevealLayout,\n  };\n}\n",
      "type": "registry:lib",
      "target": "components/glassyui/lib/logo-reveal-renderer.ts"
    },
    {
      "path": "registry/glassyui/styles/logo-reveal.css",
      "content": "/*\n  Glass Logo Reveal: the stage the reveal is drawn on and the lockup it lands on. The lockup is\n  the real one, laid out by the page; the canvas paints over it while the glass moves and steps\n  aside once it has landed. Size the lockup with font-size: the mark follows the name.\n*/\n\n/* Preserve Tailwind's order even when the reveal loads before global CSS. */\n@layer properties;\n@layer theme, base, components, utilities;\n\n@layer components {\n  .lg-logo-reveal {\n    --lg-logo-reveal-stage: #1c1c1e;\n    --lg-logo-reveal-ink: #f5f5f7;\n\n    position: relative;\n    display: grid;\n    place-items: center;\n    aspect-ratio: 16 / 9;\n    margin: 0;\n    overflow: hidden;\n    container-type: inline-size;\n    background-color: var(--lg-logo-reveal-stage);\n    color: var(--lg-logo-reveal-ink);\n  }\n\n  [data-lg-theme='light'] .lg-logo-reveal {\n    --lg-logo-reveal-stage: #faf9f7;\n    --lg-logo-reveal-ink: #222224;\n  }\n\n  .lg-logo-reveal-lockup {\n    display: flex;\n    align-items: center;\n    gap: 0.42em;\n    font-size: clamp(1rem, 6.5cqi, 4.5rem);\n    font-weight: 600;\n    letter-spacing: 0.012em;\n    line-height: 1;\n    white-space: nowrap;\n  }\n\n  .lg-logo-reveal-mark {\n    display: block;\n    width: auto;\n    height: 1.26em;\n  }\n\n  /* An empty inline block whose top is the name's baseline. */\n  .lg-logo-reveal-baseline {\n    display: inline-block;\n    width: 0;\n    height: 0;\n    vertical-align: baseline;\n  }\n\n  .lg-logo-reveal-canvas {\n    position: absolute;\n    inset: 0;\n    display: block;\n    width: 100%;\n    height: 100%;\n    pointer-events: none;\n    user-select: none;\n  }\n\n  .lg-logo-reveal[data-state='ended'] .lg-logo-reveal-canvas {\n    opacity: 0;\n    transition: opacity 240ms var(--lg-ease-out, cubic-bezier(0.32, 0.72, 0, 1));\n  }\n\n  /* With scripting the canvas draws everything until the glass has gone. */\n  @media (scripting: enabled) {\n    .lg-logo-reveal:not([data-state='ended']) .lg-logo-reveal-lockup {\n      visibility: hidden;\n    }\n  }\n\n  /* Reduced motion and reduced transparency keep the lockup and drop the glass. */\n  html[data-lg-reduce-motion='1'] .lg-logo-reveal-lockup,\n  html[data-lg-reduce-transparency='1'] .lg-logo-reveal-lockup {\n    visibility: visible;\n  }\n\n  html[data-lg-reduce-motion='1'] .lg-logo-reveal-canvas,\n  html[data-lg-reduce-transparency='1'] .lg-logo-reveal-canvas {\n    visibility: hidden;\n  }\n\n  @media (prefers-reduced-motion: reduce), (prefers-reduced-transparency: reduce) {\n    .lg-logo-reveal-lockup {\n      visibility: visible;\n    }\n\n    .lg-logo-reveal-canvas {\n      visibility: hidden;\n    }\n  }\n\n  .lg-logo-reveal-error {\n    position: absolute;\n    inset: auto 0 0;\n    padding: 12px;\n    font-size: 0.875rem;\n    text-align: center;\n  }\n}\n",
      "type": "registry:file",
      "target": "components/glassyui/styles/logo-reveal.css"
    }
  ],
  "meta": {
    "sourceModules": [
      "logo-reveal"
    ]
  },
  "type": "registry:ui"
}