{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dynamic-notification",
  "type": "registry:component",
  "title": "DynamicNotification",
  "description": "Dynamic Island-style AI notification pill with a thinking state that morphs into a reply bubble — token-driven surface, WebGL-enhanced backdrop blur, or real refractive liquid glass.",
  "dependencies": [],
  "registryDependencies": [
    "https://control-ui.dev/r/button.json",
    "https://control-ui.dev/r/core.json",
    "https://control-ui.dev/r/liquid-glass-optics.json"
  ],
  "files": [
    {
      "path": "src/registry/hooks/use-dynamic-notification.ts",
      "target": "@components/control-ui/hooks/use-dynamic-notification.ts",
      "type": "registry:hook",
      "content": "import type { ComponentProps, CSSProperties } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport type { FormSubmitEvent, OpenChangeEventDetails, OpenChangeReason } from \"@/components/control-ui/control-props\";\nimport type { DynamicNotificationKnobStyle } from \"@/components/control-ui/knob-contracts/dynamic-notification-knobs\";\n\nexport type DynamicNotificationVariant = \"surface\" | \"glass\" | \"liquid\";\n\nexport type DynamicNotificationReplyPayload = {\n  value: string;\n  clear: () => void;\n};\n\nexport type DynamicNotificationProps = Omit<ComponentProps<\"div\">, \"onChange\" | \"style\"> & {\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean, eventDetails: OpenChangeEventDetails) => void;\n  loading?: boolean;\n  replyValue?: string;\n  defaultReplyValue?: string;\n  onReplyValueChange?: (value: string) => void;\n  onReply?: (payload: DynamicNotificationReplyPayload) => void | Promise<void>;\n  variant?: DynamicNotificationVariant;\n  disabled?: boolean;\n  style?: CSSProperties & DynamicNotificationKnobStyle;\n};\n\n// No Base UI primitive backs island, so details are hand-built to same shape every other popup emits.\nfunction createOpenChangeEventDetails(reason: OpenChangeReason, event: Event, trigger: Element | undefined): OpenChangeEventDetails {\n  let canceled = false;\n  let propagationAllowed = false;\n  return {\n    reason,\n    event,\n    cancel() {\n      canceled = true;\n    },\n    allowPropagation() {\n      propagationAllowed = true;\n    },\n    get isCanceled() {\n      return canceled;\n    },\n    get isPropagationAllowed() {\n      return propagationAllowed;\n    },\n    trigger,\n  };\n}\n\nfunction useControllableText({\n  value,\n  defaultValue = \"\",\n  onValueChange,\n}: {\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n}) {\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const isControlled = value !== undefined;\n  const currentValue = isControlled ? value : internalValue;\n\n  const setValue = useMemo(\n    () => (nextValue: string) => {\n      if (!isControlled) setInternalValue(nextValue);\n      onValueChange?.(nextValue);\n    },\n    [isControlled, onValueChange],\n  );\n\n  return [currentValue, setValue] as const;\n}\n\nexport type DynamicNotificationController = {\n  open: boolean;\n  disabled: boolean;\n  setOpen: (nextOpen: boolean, reason: OpenChangeReason, event: Event, trigger?: Element) => void;\n  reply: string;\n  setReply: (nextValue: string) => void;\n  normalizedReply: string;\n  canSubmit: boolean;\n  clear: () => void;\n  submitReply: () => void;\n  handleReplySubmit: (event: FormSubmitEvent) => void;\n};\n\nexport function useDynamicNotification({\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  replyValue,\n  defaultReplyValue,\n  onReplyValueChange,\n  onReply,\n  disabled = false,\n}: Pick<\n  DynamicNotificationProps,\n  \"open\" | \"defaultOpen\" | \"onOpenChange\" | \"replyValue\" | \"defaultReplyValue\" | \"onReplyValueChange\" | \"onReply\" | \"disabled\"\n>): DynamicNotificationController {\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const isControlled = open !== undefined;\n  const isOpen = isControlled ? open : internalOpen;\n\n  const [reply, setReply] = useControllableText({\n    value: replyValue,\n    defaultValue: defaultReplyValue,\n    onValueChange: onReplyValueChange,\n  });\n  const normalizedReply = reply.trim();\n  const canSubmit = normalizedReply.length > 0 && !disabled;\n\n  // Stable actions keep split contexts isolated when installed without React Compiler.\n  const setOpen = useMemo(\n    () => (nextOpen: boolean, reason: OpenChangeReason, event: Event, trigger?: Element) => {\n      if (disabled || nextOpen === isOpen) return;\n      const details = createOpenChangeEventDetails(reason, event, trigger);\n      onOpenChange?.(nextOpen, details);\n      if (details.isCanceled) return;\n      if (!isControlled) setInternalOpen(nextOpen);\n    },\n    [disabled, isControlled, isOpen, onOpenChange],\n  );\n\n  const clear = useMemo(\n    () => () => {\n      setReply(\"\");\n    },\n    [setReply],\n  );\n\n  const submitReply = useMemo(\n    () => () => {\n      if (!canSubmit) return;\n      void onReply?.({ value: normalizedReply, clear });\n    },\n    [canSubmit, clear, normalizedReply, onReply],\n  );\n\n  const handleReplySubmit = useMemo(\n    () => (event: FormSubmitEvent) => {\n      event.preventDefault();\n      submitReply();\n    },\n    [submitReply],\n  );\n\n  return {\n    open: isOpen,\n    disabled,\n    setOpen,\n    reply,\n    setReply,\n    normalizedReply,\n    canSubmit,\n    clear,\n    submitReply,\n    handleReplySubmit,\n  };\n}\n"
    },
    {
      "path": "src/registry/knob-contracts/dynamic-notification-knobs.ts",
      "target": "@components/control-ui/knob-contracts/dynamic-notification-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/dynamic-notification.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const dynamicNotificationKnobs = [\n  \"--cui-dynamic-notification-content-easing\",\n  \"--cui-dynamic-notification-expanded-radius\",\n  \"--cui-dynamic-notification-morph-easing\",\n  \"--cui-dynamic-notification-glass-foreground\",\n  \"--cui-dynamic-notification-glass-ring-color\",\n  \"--cui-dynamic-notification-liquid-foreground\",\n  \"--cui-dynamic-notification-surface-background\",\n  \"--cui-dynamic-notification-surface-foreground\",\n  \"--cui-dynamic-notification-surface-ring-color\",\n  \"--cui-dynamic-notification-surface-shadow\",\n  \"--cui-dynamic-notification-indicator-end\",\n  \"--cui-dynamic-notification-indicator-middle\",\n  \"--cui-dynamic-notification-indicator-start\",\n] as const;\nexport type DynamicNotificationKnobStyle = Partial<Record<(typeof dynamicNotificationKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/sources/control-ui/dynamic-notification-glass.ts",
      "target": "@components/control-ui/dynamic-notification-glass.ts",
      "type": "registry:component",
      "content": "import { DYNAMIC_NOTIFICATION_SIRI_WAVE_GLSL } from \"@/components/control-ui/dynamic-notification-siri-wave\";\n\n// CSS paints gradient fallback under canvas, so absent or lost WebGL degrades to static material instead of hole.\nexport type DynamicNotificationGlassOptions = {\n  /** Aurora strength 0..1 (default 1). */\n  intensity?: number;\n  /** devicePixelRatio clamp (default 2) — island is small, 2x is visually lossless. */\n  maxDpr?: number;\n};\n\nconst VERTEX_SHADER = /* glsl */ `\nattribute vec2 a_position;\nvoid main() {\n  gl_Position = vec4(a_position, 0.0, 1.0);\n}\n`;\n\nconst FRAGMENT_SHADER = /* glsl */ `\nprecision highp float;\n\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_radius;\nuniform float u_intensity;\nuniform float u_aurora;\nuniform float u_reveal;\nuniform float u_settle;\nuniform float u_lift;\nuniform float u_ribbonHorizon;\n\nfloat sdRoundBox(vec2 p, vec2 b, float r) {\n  vec2 q = abs(p) - b + r;\n  return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;\n}\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);\n}\n\nfloat ridge(float y, float center, float sharpness) {\n  float delta = (y - center) * sharpness;\n  return exp(-delta * delta);\n}\n\n${DYNAMIC_NOTIFICATION_SIRI_WAVE_GLSL}\n\nvoid main() {\n  vec2 uv = gl_FragCoord.xy / u_resolution;\n  vec2 p = gl_FragCoord.xy - 0.5 * u_resolution;\n  vec2 halfSize = 0.5 * u_resolution;\n  float radius = min(u_radius, min(halfSize.x, halfSize.y));\n  float d = sdRoundBox(p, halfSize, radius);\n  float shape = 1.0 - smoothstep(-1.5, 0.5, d);\n  if (shape <= 0.0) {\n    gl_FragColor = vec4(0.0);\n    return;\n  }\n\n  /* gradient itself morphs, so pill never pops from opaque to translucent; expanded keeps a 0.4 floor for reply controls */\n  float gradThinking = mix(0.10, 0.97, smoothstep(u_ribbonHorizon - 0.10, u_ribbonHorizon + 0.10, uv.y));\n  float gradExpanded = mix(0.40, 0.97, smoothstep(0.00, 0.30, uv.y));\n  float grad = mix(gradThinking, gradExpanded, u_settle);\n  float alpha = mix(0.97, grad, u_reveal);\n  vec3 color = vec3(0.004, 0.004, 0.006) * mix(1.0, 0.4 + 0.6 * uv.y, u_reveal);\n\n  /* pale rainbow band across horizon */\n  float sheenY = u_ribbonHorizon + 0.02 * sin(u_time * 0.35);\n  float sheen = ridge(uv.y, sheenY, 9.0);\n  float spread = smoothstep(0.06, 0.4, uv.x) * smoothstep(0.94, 0.6, uv.x);\n  color += spread * 0.085 * vec3(\n    sheen * (0.9 + 0.35 * sin(uv.x * 8.0 + 1.6)),\n    ridge(uv.y, sheenY + 0.015, 9.5),\n    ridge(uv.y, sheenY - 0.02, 8.5) * 1.15\n  );\n\n  vec3 aurora = dynamicNotificationSiriWave(uv, u_resolution, u_time, u_ribbonHorizon, u_lift)\n    * u_intensity * u_aurora * (1.0 - u_lift);\n  float glow = max(aurora.r, max(aurora.g, aurora.b));\n  alpha = min(1.0, alpha + glow * 0.5);\n\n  /* inner rim light — glass thickness catching environment */\n  float rim = exp(-pow((d + 1.75) * 0.30, 2.0));\n  color += rim * vec3(0.82, 0.88, 1.0) * (0.05 + 0.10 * (1.0 - uv.y));\n\n  /* edge refraction stays narrow and mostly opaque on purpose: wide translucent band under backdrop blur reads as background melting into edge, not as glass */\n  float bevelW = max(4.0, u_radius * 0.32);\n  float bt = clamp(1.0 + d / bevelW, 0.0, 1.0);\n  float bevel = pow(bt, 2.4);\n  alpha *= 1.0 - 0.30 * bevel;\n  float seam = exp(-pow((bt - 0.42) * 7.0, 2.0));\n  color *= 1.0 - 0.25 * seam;\n  color += vec3(0.95, 0.97, 1.0) * bevel * bevel * (0.35 + 0.65 * uv.y) * 0.18;\n\n  /* dither so long dark gradient never bands */\n  color += (hash(gl_FragCoord.xy) - 0.5) / 255.0;\n\n  alpha *= shape;\n  gl_FragColor = vec4(color * alpha + aurora * shape, alpha);\n}\n`;\n\ntype GlassProgram = {\n  program: WebGLProgram;\n  buffer: WebGLBuffer;\n  resolution: WebGLUniformLocation | null;\n  time: WebGLUniformLocation | null;\n  radius: WebGLUniformLocation | null;\n  intensity: WebGLUniformLocation | null;\n  aurora: WebGLUniformLocation | null;\n  reveal: WebGLUniformLocation | null;\n  settle: WebGLUniformLocation | null;\n  lift: WebGLUniformLocation | null;\n  ribbonHorizon: WebGLUniformLocation | null;\n};\n\nfunction compileShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | null {\n  const shader = gl.createShader(type);\n  if (!shader) return null;\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    gl.deleteShader(shader);\n    return null;\n  }\n  return shader;\n}\n\nfunction buildProgram(gl: WebGLRenderingContext): GlassProgram | null {\n  const vertex = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);\n  const fragment = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);\n  if (!vertex || !fragment) return null;\n  const program = gl.createProgram();\n  const buffer = gl.createBuffer();\n  if (!program || !buffer) return null;\n  gl.attachShader(program, vertex);\n  gl.attachShader(program, fragment);\n  gl.linkProgram(program);\n  gl.deleteShader(vertex);\n  gl.deleteShader(fragment);\n  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return null;\n\n  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n  const position = gl.getAttribLocation(program, \"a_position\");\n  gl.enableVertexAttribArray(position);\n  gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);\n\n  return {\n    program,\n    buffer,\n    resolution: gl.getUniformLocation(program, \"u_resolution\"),\n    time: gl.getUniformLocation(program, \"u_time\"),\n    radius: gl.getUniformLocation(program, \"u_radius\"),\n    intensity: gl.getUniformLocation(program, \"u_intensity\"),\n    aurora: gl.getUniformLocation(program, \"u_aurora\"),\n    reveal: gl.getUniformLocation(program, \"u_reveal\"),\n    settle: gl.getUniformLocation(program, \"u_settle\"),\n    lift: gl.getUniformLocation(program, \"u_lift\"),\n    ribbonHorizon: gl.getUniformLocation(program, \"u_ribbonHorizon\"),\n  };\n}\n\n/* only while thinking — static pill and opened chat stay clean */\nfunction auroraTarget(state: string | undefined): number {\n  return state === \"thinking\" ? 1 : 0;\n}\n\n/* only collapsed pill is solid black */\nfunction revealTarget(state: string | undefined): number {\n  return state === \"collapsed\" ? 0 : 1;\n}\n\n/* picks ink ramp: thinking runs out to transparent, expanded keeps floor under its reply controls */\nfunction settleTarget(state: string | undefined): number {\n  return state === \"expanded\" ? 1 : 0;\n}\n\nexport function createDynamicNotificationGlass(canvas: HTMLCanvasElement, options: DynamicNotificationGlassOptions = {}): () => void {\n  const { intensity = 1, maxDpr = 2 } = options;\n  const gl = canvas.getContext(\"webgl\", { alpha: true, premultipliedAlpha: true, antialias: true });\n  if (!gl) return () => {};\n\n  // remount on same canvas hands back SAME live context, so cleanup must never loseContext():\n  // lost context returns null from getExtension, leaving no later instance able to restore it.\n  let contextLost = gl.isContextLost();\n  let glass = contextLost ? null : buildProgram(gl);\n  if (!contextLost && !glass) return () => {};\n\n  let destroyed = false;\n  let intersecting = true;\n  let pageVisible = !document.hidden;\n  let rafId = 0;\n  let dpr = 1;\n  let radius = 0;\n  let aurora = auroraTarget(canvas.parentElement?.dataset.state);\n  /* sends dying sheet toward top on exit; parked high whenever aurora is off */\n  let lift = 1 - aurora;\n  let reveal = revealTarget(canvas.parentElement?.dataset.state);\n  let settle = settleTarget(canvas.parentElement?.dataset.state);\n  let staticFrameDrawn = false;\n  const startedAt = performance.now();\n  const reducedMotionQuery = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\n  function reducedMotion(): boolean {\n    return reducedMotionQuery.matches || canvas.closest('[data-motion=\"reduced\"]') !== null;\n  }\n\n  function targetRadius(): number {\n    const host = canvas.parentElement;\n    if (!host) return 0;\n    const parsed = Number.parseFloat(getComputedStyle(host).borderTopLeftRadius);\n    return Number.isFinite(parsed) ? parsed * dpr : 0;\n  }\n\n  /* Called from draw(), never an observer: setting canvas.width clears the buffer, and rAF ticks run\n     before ResizeObserver callbacks, so an observer-side resize would wipe the drawn pixels before paint. */\n  function resize(): void {\n    // layout box, immune to the @starting-style scale at mount — getBoundingClientRect would bake that transform into backing size\n    dpr = Math.min(window.devicePixelRatio || 1, maxDpr);\n    const width = Math.max(1, Math.round(canvas.clientWidth * dpr));\n    const height = Math.max(1, Math.round(canvas.clientHeight * dpr));\n    if (canvas.width !== width || canvas.height !== height) {\n      canvas.width = width;\n      canvas.height = height;\n    }\n  }\n\n  function advanceMotion(reduced: boolean): void {\n    const target = targetRadius();\n    radius = reduced ? target : radius + (target - radius) * 0.25;\n\n    const auroraGoal = auroraTarget(canvas.parentElement?.dataset.state);\n    aurora = reduced ? auroraGoal : aurora + (auroraGoal - aurora) * 0.06;\n    /* linear exit so sheet clears surface before opening morph collapses */\n    const liftGoal = auroraGoal >= 0.5 ? 0 : 1;\n    if (reduced) lift = liftGoal;\n    else if (liftGoal === 1) lift = Math.min(1, lift + 0.036);\n    else lift *= 0.72;\n\n    const revealGoal = revealTarget(canvas.parentElement?.dataset.state);\n    reveal = reduced ? revealGoal : reveal + (revealGoal - reveal) * 0.07;\n    const settleGoal = settleTarget(canvas.parentElement?.dataset.state);\n    settle = reduced ? settleGoal : settle + (settleGoal - settle) * 0.07;\n  }\n\n  function uploadUniforms(activeGl: WebGLRenderingContext, activeGlass: GlassProgram, reduced: boolean): void {\n    activeGl.uniform2f(activeGlass.resolution, canvas.width, canvas.height);\n    activeGl.uniform1f(activeGlass.time, reduced ? 4.2 : (performance.now() - startedAt) / 1000);\n    activeGl.uniform1f(activeGlass.radius, radius);\n    activeGl.uniform1f(activeGlass.intensity, intensity);\n    activeGl.uniform1f(activeGlass.aurora, aurora);\n    activeGl.uniform1f(activeGlass.reveal, reveal);\n    activeGl.uniform1f(activeGlass.settle, settle);\n    activeGl.uniform1f(activeGlass.lift, lift);\n    activeGl.uniform1f(activeGlass.ribbonHorizon, 0.5);\n  }\n\n  function draw(): void {\n    if (!gl || !glass || contextLost) return;\n    resize();\n    gl.viewport(0, 0, canvas.width, canvas.height);\n    gl.clearColor(0, 0, 0, 0);\n    gl.clear(gl.COLOR_BUFFER_BIT);\n    // biome-ignore lint/correctness/useHookAtTopLevel: WebGL's useProgram, not a React hook.\n    gl.useProgram(glass.program);\n    const reduced = reducedMotion();\n    advanceMotion(reduced);\n    uploadUniforms(gl, glass, reduced);\n    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n    if (canvas.dataset.glassReady !== \"true\") canvas.dataset.glassReady = \"true\";\n  }\n\n  function visible(): boolean {\n    return intersecting && pageVisible;\n  }\n\n  function tick(): void {\n    rafId = 0;\n    if (destroyed || !visible() || contextLost) return;\n    draw();\n    if (reducedMotion()) {\n      staticFrameDrawn = true;\n      return;\n    }\n    staticFrameDrawn = false;\n    rafId = requestAnimationFrame(tick);\n  }\n\n  /* restarts loop, or repaints single static frame under reduced motion */\n  function invalidate(): void {\n    staticFrameDrawn = false;\n    if (destroyed || !visible() || contextLost || rafId !== 0) return;\n    rafId = requestAnimationFrame(tick);\n  }\n\n  function handleVisibility(): void {\n    pageVisible = !document.hidden;\n    invalidate();\n  }\n\n  function handleContextLost(event: Event): void {\n    // preventDefault asks for context back — browser then fires webglcontextrestored\n    event.preventDefault();\n    contextLost = true;\n    if (rafId !== 0) cancelAnimationFrame(rafId);\n    rafId = 0;\n  }\n\n  function handleContextRestored(): void {\n    // hoisted, so outer guard's narrowing does not flow in\n    if (!gl) return;\n    contextLost = false;\n    glass = buildProgram(gl);\n    invalidate();\n  }\n\n  const resizeObserver = new ResizeObserver(() => {\n    invalidate();\n  });\n  resizeObserver.observe(canvas);\n\n  /* pauses offscreen so previews below fold keep GPU idle */\n  const intersectionObserver = new IntersectionObserver(\n    (entries) => {\n      for (const entry of entries) intersecting = entry.isIntersecting;\n      invalidate();\n    },\n    { rootMargin: \"64px\" },\n  );\n  intersectionObserver.observe(canvas);\n\n  /* theme editor toggles data-motion on <html> live */\n  const motionObserver = new MutationObserver(() => {\n    if (!staticFrameDrawn || !reducedMotion()) invalidate();\n  });\n  motionObserver.observe(document.documentElement, { attributes: true, attributeFilter: [\"data-motion\"] });\n\n  function handleMotionPreference(): void {\n    invalidate();\n  }\n\n  document.addEventListener(\"visibilitychange\", handleVisibility);\n  reducedMotionQuery.addEventListener(\"change\", handleMotionPreference);\n  canvas.addEventListener(\"webglcontextlost\", handleContextLost);\n  canvas.addEventListener(\"webglcontextrestored\", handleContextRestored);\n\n  invalidate();\n\n  return () => {\n    destroyed = true;\n    if (rafId !== 0) cancelAnimationFrame(rafId);\n    resizeObserver.disconnect();\n    intersectionObserver.disconnect();\n    motionObserver.disconnect();\n    document.removeEventListener(\"visibilitychange\", handleVisibility);\n    reducedMotionQuery.removeEventListener(\"change\", handleMotionPreference);\n    canvas.removeEventListener(\"webglcontextlost\", handleContextLost);\n    canvas.removeEventListener(\"webglcontextrestored\", handleContextRestored);\n    delete canvas.dataset.glassReady;\n    if (glass && !gl.isContextLost()) {\n      gl.deleteProgram(glass.program);\n      gl.deleteBuffer(glass.buffer);\n    }\n    glass = null;\n  };\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/dynamic-notification-liquid.ts",
      "target": "@components/control-ui/dynamic-notification-liquid.ts",
      "type": "registry:component",
      "content": "import { DYNAMIC_NOTIFICATION_SIRI_WAVE_GLSL } from \"@/components/control-ui/dynamic-notification-siri-wave\";\nimport { LIQUID_GLASS_OPTICS_GLSL } from \"@/components/control-ui/lib/liquid-glass-optics\";\n\n/* Keep full-surface transmission separate from narrow edge-light field so refraction stays visible without halo. */\n\nexport type DynamicNotificationLiquidOptions = {\n  /** Virtual lens depth (default 0.85) applied to size-derived inner edge profile. */\n  refraction?: number;\n  /** Chromatic aberration (default 0) — optional RGB separation; Apple does not expose it as material property. */\n  chromaticAberration?: number;\n  /** Inner refraction band cap in css px (default 32). */\n  zRadius?: number;\n  /** Directional dark edge intensity (default 0.05). */\n  edgeDarkening?: number;\n  /** Opposed perimeter highlight intensity (default 0.11). */\n  specular?: number;\n  /** Grazing reflection intensity (default 0.35). */\n  fresnel?: number;\n  /** Edge highlight intensity (default 0.16). */\n  edgeHighlight?: number;\n  /** Frost factor for shader-native diffusion (default 4). centre remains sharp-dominant. */\n  frost?: number;\n  /** devicePixelRatio clamp (default 2). */\n  maxDpr?: number;\n};\n\nconst VERTEX_SHADER = /* glsl */ `\nattribute vec2 a_position;\nvarying vec2 v_uv;\nvoid main() {\n  /* v_uv is y-DOWN (v=0 = top) to match the 2D-canvas texture orientation */\n  v_uv = vec2(a_position.x * 0.5 + 0.5, 0.5 - a_position.y * 0.5);\n  gl_Position = vec4(a_position, 0.0, 1.0);\n}\n`;\n\n/* optical field runs in css px with y-down local frame centred on panel. */\nconst FRAGMENT_SHADER = /* glsl */ `\nprecision highp float;\n\nuniform sampler2D u_sharpTex;\nuniform sampler2D u_blurTex;\nuniform vec2 u_size;        /* panel size, css px */\nuniform vec2 u_panelOffset; /* panel top-left inside the scene, css px */\nuniform vec2 u_sceneSize;   /* scene size, css px */\nuniform float u_radius;     /* corner radius, css px */\nuniform float u_zRadius;    /* inner refraction band cap, css px */\nuniform float u_refract;\nuniform float u_chroma;\nuniform float u_edgeDarkening;\nuniform float u_spec;\nuniform float u_fresnel;\nuniform float u_edgeHL;\nuniform float u_time;\nuniform float u_aurora;\nuniform float u_reveal;\nuniform float u_settle;\nuniform float u_lift;\nuniform float u_ribbonHorizon;\n\nvarying vec2 v_uv;\n\n${LIQUID_GLASS_OPTICS_GLSL}\n${DYNAMIC_NOTIFICATION_SIRI_WAVE_GLSL}\n\nvec2 sceneUV(vec2 posCss) {\n  return clamp(posCss / u_sceneSize, 0.0, 1.0);\n}\n\nvoid main() {\n  vec2 local = (v_uv - 0.5) * u_size;\n  vec2 half_ = u_size * 0.5;\n  float r = min(u_radius, min(half_.x, half_.y));\n  float sdf = liquidSurfaceSDF(local, half_, r, 2.0);\n\n  /* mask clips only shape; resting Clear material has no separate shadow stage. */\n  float mask = 1.0 - smoothstep(-1.5, 0.5, sdf);\n  if (mask <= 0.0) {\n    gl_FragColor = vec4(0.0);\n    return;\n  }\n\n  float materialScale = smoothstep(40.0, 120.0, u_size.y);\n  float edgeWidth = liquidEdgeWidth(u_size, 0.36, u_zRadius);\n  float opticalInside = liquidOpticalInsideDistance(local, half_, r, 2.0);\n  float lens = liquidEdgeLens(opticalInside, edgeWidth);\n  vec2 boundaryNormal = liquidInwardOpticalNormal(local, half_, r, 2.0);\n\n  vec2 refrPx = liquidRefractedOffset(boundaryNormal, opticalInside, edgeWidth, u_refract);\n  vec3 N = liquidLensSurfaceNormal(boundaryNormal, opticalInside, edgeWidth);\n  vec2 keyDirection = normalize(vec2(0.707106, -0.707106));\n  float outerRim = 1.0 - smoothstep(0.35, 1.35, opticalInside);\n  float innerRim = exp(-pow((opticalInside - 2.0) / 0.9, 2.0));\n\n  float caS = u_chroma * 10.0 * lens;\n  vec2 caD = N.xy * caS;\n  vec2 base = u_panelOffset + v_uv * u_size + refrPx;\n\n  vec3 sharp = vec3(\n    texture2D(u_sharpTex, sceneUV(base + caD)).r,\n    texture2D(u_sharpTex, sceneUV(base)).g,\n    texture2D(u_sharpTex, sceneUV(base - caD)).b\n  );\n  vec3 blur = vec3(\n    texture2D(u_blurTex, sceneUV(base + caD)).r,\n    texture2D(u_blurTex, sceneUV(base)).g,\n    texture2D(u_blurTex, sceneUV(base - caD)).b\n  );\n  float diffusion = mix(0.08, 0.14, materialScale) * mix(0.25, 1.0, lens);\n  vec3 col = mix(sharp, blur, diffusion);\n\n  /* Map transmitted luminance to measured Clear-material endpoints while preserving hue. */\n  float transmittedLuma = dot(col, vec3(0.2126, 0.7152, 0.0722));\n  float environmentLuma = dot(blur, vec3(0.2126, 0.7152, 0.0722));\n  float lightEnvironment = smoothstep(0.38, 0.72, environmentLuma);\n  float blackEndpoint = mix(0.05, 0.20, lightEnvironment);\n  float whiteEndpoint = mix(0.80, 0.95, lightEnvironment);\n  float mappedLuma = mix(blackEndpoint, whiteEndpoint, transmittedLuma);\n  col = clamp(col + vec3(mappedLuma - transmittedLuma), 0.0, 1.0);\n\n  /* Ink and ribbon share refracted coordinate so their horizon stays joined at edges. */\n  vec2 auroraUv = clamp(v_uv + N.xy * lens * 0.012, vec2(0.0), vec2(1.0));\n  float yUp = 1.0 - auroraUv.y;\n  float inkThinking = mix(0.10, 0.985, smoothstep(u_ribbonHorizon - 0.10, u_ribbonHorizon + 0.10, yUp));\n  float inkExpanded = mix(0.24, 0.97, smoothstep(0.00, 0.30, yUp));\n  float ink = mix(0.985, mix(inkThinking, inkExpanded, u_settle), u_reveal);\n  vec3 materialColor = mix(col, vec3(0.004, 0.004, 0.006), ink);\n\n  float keyArc = pow(max(dot(boundaryNormal, keyDirection), 0.0), 6.0);\n  float fillArc = pow(max(dot(boundaryNormal, -keyDirection), 0.0), 8.0) * 0.28;\n  float shadowArc = pow(max(dot(boundaryNormal, -keyDirection), 0.0), 6.0);\n  materialColor *= 1.0 - innerRim * shadowArc * u_edgeDarkening;\n  float highlight = outerRim * u_edgeHL * 0.22 + innerRim * (keyArc * u_spec + fillArc * u_spec * 1.3);\n  float grazing = outerRim * u_fresnel * 0.025;\n  vec3 rimLight = mix(vec3(1.0), blur, 0.12);\n\n  vec3 auroraEmission = dynamicNotificationSiriWave(vec2(auroraUv.x, yUp), u_size, u_time, u_ribbonHorizon, u_lift)\n    * u_aurora * (1.0 - u_lift);\n\n  vec3 material = materialColor + rimLight * highlight + vec3(grazing) + auroraEmission;\n  gl_FragColor = vec4(material * mask, mask);\n}\n`;\n\ntype LiquidProgram = {\n  program: WebGLProgram;\n  buffer: WebGLBuffer;\n  sharpTexture: WebGLTexture;\n  blurTexture: WebGLTexture;\n  sharpTex: WebGLUniformLocation | null;\n  blurTex: WebGLUniformLocation | null;\n  size: WebGLUniformLocation | null;\n  panelOffset: WebGLUniformLocation | null;\n  sceneSize: WebGLUniformLocation | null;\n  radius: WebGLUniformLocation | null;\n  zRadius: WebGLUniformLocation | null;\n  refract: WebGLUniformLocation | null;\n  chroma: WebGLUniformLocation | null;\n  edgeDarkening: WebGLUniformLocation | null;\n  spec: WebGLUniformLocation | null;\n  fresnel: WebGLUniformLocation | null;\n  edgeHL: WebGLUniformLocation | null;\n  time: WebGLUniformLocation | null;\n  aurora: WebGLUniformLocation | null;\n  reveal: WebGLUniformLocation | null;\n  settle: WebGLUniformLocation | null;\n  lift: WebGLUniformLocation | null;\n  ribbonHorizon: WebGLUniformLocation | null;\n};\n\nfunction compileShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | null {\n  const shader = gl.createShader(type);\n  if (!shader) return null;\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    gl.deleteShader(shader);\n    return null;\n  }\n  return shader;\n}\n\nfunction createSceneTexture(gl: WebGLRenderingContext): WebGLTexture | null {\n  const texture = gl.createTexture();\n  if (!texture) return null;\n  gl.bindTexture(gl.TEXTURE_2D, texture);\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  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n  return texture;\n}\n\nfunction buildProgram(gl: WebGLRenderingContext): LiquidProgram | null {\n  const vertex = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);\n  const fragment = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);\n  if (!vertex || !fragment) return null;\n  const program = gl.createProgram();\n  const buffer = gl.createBuffer();\n  const sharpTexture = createSceneTexture(gl);\n  const blurTexture = createSceneTexture(gl);\n  if (!program || !buffer || !sharpTexture || !blurTexture) return null;\n  gl.attachShader(program, vertex);\n  gl.attachShader(program, fragment);\n  gl.linkProgram(program);\n  gl.deleteShader(vertex);\n  gl.deleteShader(fragment);\n  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return null;\n\n  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n  const position = gl.getAttribLocation(program, \"a_position\");\n  gl.enableVertexAttribArray(position);\n  gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);\n\n  return {\n    program,\n    buffer,\n    sharpTexture,\n    blurTexture,\n    sharpTex: gl.getUniformLocation(program, \"u_sharpTex\"),\n    blurTex: gl.getUniformLocation(program, \"u_blurTex\"),\n    size: gl.getUniformLocation(program, \"u_size\"),\n    panelOffset: gl.getUniformLocation(program, \"u_panelOffset\"),\n    sceneSize: gl.getUniformLocation(program, \"u_sceneSize\"),\n    radius: gl.getUniformLocation(program, \"u_radius\"),\n    zRadius: gl.getUniformLocation(program, \"u_zRadius\"),\n    refract: gl.getUniformLocation(program, \"u_refract\"),\n    chroma: gl.getUniformLocation(program, \"u_chroma\"),\n    edgeDarkening: gl.getUniformLocation(program, \"u_edgeDarkening\"),\n    spec: gl.getUniformLocation(program, \"u_spec\"),\n    fresnel: gl.getUniformLocation(program, \"u_fresnel\"),\n    edgeHL: gl.getUniformLocation(program, \"u_edgeHL\"),\n    time: gl.getUniformLocation(program, \"u_time\"),\n    aurora: gl.getUniformLocation(program, \"u_aurora\"),\n    reveal: gl.getUniformLocation(program, \"u_reveal\"),\n    settle: gl.getUniformLocation(program, \"u_settle\"),\n    lift: gl.getUniformLocation(program, \"u_lift\"),\n    ribbonHorizon: gl.getUniformLocation(program, \"u_ribbonHorizon\"),\n  };\n}\n\n/* only while thinking — static pill and opened chat stay clean */\nfunction auroraTarget(state: string | undefined): number {\n  return state === \"thinking\" ? 1 : 0;\n}\n\nfunction revealTarget(state: string | undefined): number {\n  return state === \"collapsed\" ? 0 : 1;\n}\n\nfunction settleTarget(state: string | undefined): number {\n  return state === \"expanded\" ? 1 : 0;\n}\n\nfunction approachAt60Hz(current: number, target: number, response: number, deltaMs: number): number {\n  const frameScale = Math.max(0, deltaMs) / (1000 / 60);\n  return current + (target - current) * (1 - (1 - response) ** frameScale);\n}\n\nconst EXCLUDE_SELECTOR = '[data-control-ui=\"dynamic-notification\"]';\n\ntype EmbeddedImages = ReadonlyMap<string, string>;\n\nfunction cssImageUrls(value: string): string[] {\n  return Array.from(value.matchAll(/url\\(([\"']?)(.*?)\\1\\)/g), (match) => match[2]?.trim()).filter((url): url is string =>\n    Boolean(url && !url.startsWith(\"data:\") && !url.startsWith(\"#\")),\n  );\n}\n\nfunction sceneImageUrls(scene: HTMLElement): Set<string> {\n  const urls = new Set<string>();\n  for (const element of [scene, ...scene.querySelectorAll(\"*\")]) {\n    if (element.closest(EXCLUDE_SELECTOR)) continue;\n    if (element instanceof HTMLImageElement && element.currentSrc && !element.currentSrc.startsWith(\"data:\")) {\n      urls.add(element.currentSrc);\n    }\n    const computed = getComputedStyle(element);\n    for (let index = 0; index < computed.length; index += 1) {\n      for (const url of cssImageUrls(computed.getPropertyValue(computed.item(index)))) urls.add(url);\n    }\n  }\n  return urls;\n}\n\nfunction blobDataUrl(blob: Blob): Promise<string | null> {\n  return new Promise((resolve) => {\n    const reader = new FileReader();\n    reader.addEventListener(\"load\", () => resolve(typeof reader.result === \"string\" ? reader.result : null), { once: true });\n    reader.addEventListener(\"error\", () => resolve(null), { once: true });\n    reader.readAsDataURL(blob);\n  });\n}\n\nasync function fetchImageDataUrl(url: string): Promise<string | null> {\n  try {\n    const resolved = new URL(url, document.baseURI);\n    const sameOrigin = resolved.origin === window.location.origin;\n    const response = await fetch(resolved, {\n      cache: \"force-cache\",\n      credentials: sameOrigin ? \"same-origin\" : \"omit\",\n      mode: sameOrigin ? \"same-origin\" : \"cors\",\n    });\n    if (!response.ok) return null;\n    return blobDataUrl(await response.blob());\n  } catch {\n    return null;\n  }\n}\n\nasync function embedSceneImages(scene: HTMLElement): Promise<EmbeddedImages | null> {\n  const urls = [...sceneImageUrls(scene)];\n  const dataUrls = await Promise.all(urls.map(fetchImageDataUrl));\n  const images = new Map<string, string>();\n  for (let index = 0; index < urls.length; index += 1) {\n    const url = urls[index];\n    const dataUrl = dataUrls[index];\n    if (!url || !dataUrl) return null;\n    images.set(url, dataUrl);\n  }\n  return images;\n}\n\nfunction embedCssImages(value: string, images: EmbeddedImages): string {\n  return value.replace(/url\\(([\"']?)(.*?)\\1\\)/g, (match, _quote: string, url: string) => {\n    const embedded = images.get(url.trim());\n    return embedded ? `url(\"${embedded}\")` : match;\n  });\n}\n\nfunction cloneCanvas(source: HTMLCanvasElement): HTMLImageElement | null {\n  try {\n    const snapshot = document.createElement(\"img\");\n    snapshot.src = source.toDataURL();\n    snapshot.setAttribute(\"style\", getComputedStyle(source).cssText);\n    return snapshot;\n  } catch {\n    return null;\n  }\n}\n\nfunction computedStyleText(source: Element, images: EmbeddedImages): string {\n  const computed = getComputedStyle(source);\n  const parts: string[] = [];\n  for (let index = 0; index < computed.length; index += 1) {\n    const property = computed.item(index);\n    parts.push(`${property}: ${embedCssImages(computed.getPropertyValue(property), images)};`);\n  }\n  return parts.join(\" \");\n}\n\nfunction appendClonedChildren(source: Element, target: Element, images: EmbeddedImages): void {\n  for (const child of source.childNodes) {\n    if (child instanceof Element) {\n      const cloned = cloneTree(child, images);\n      if (cloned) target.appendChild(cloned);\n    } else if (child.nodeType === Node.TEXT_NODE) {\n      target.appendChild(child.cloneNode(false));\n    }\n  }\n}\n\n/* Recursive clone with computed styles inlined — SVG-image sandbox can't reach stylesheets. */\nfunction cloneTree(source: Element, images: EmbeddedImages): Element | null {\n  if (source.matches(EXCLUDE_SELECTOR)) return null;\n\n  /* canvases can't paint inside SVG image: snapshot them into an <img> */\n  if (source instanceof HTMLCanvasElement) return cloneCanvas(source);\n\n  const node = source.cloneNode(false);\n  if (!(node instanceof Element)) return null;\n\n  node.setAttribute(\"style\", computedStyleText(source, images));\n  if (source instanceof HTMLImageElement && node instanceof HTMLImageElement) {\n    const embedded = images.get(source.currentSrc);\n    if (embedded) node.src = embedded;\n    node.removeAttribute(\"srcset\");\n    node.removeAttribute(\"sizes\");\n  }\n  if (source instanceof HTMLSourceElement && source.parentElement instanceof HTMLPictureElement) {\n    node.removeAttribute(\"srcset\");\n    node.removeAttribute(\"sizes\");\n  }\n  appendClonedChildren(source, node, images);\n  return node;\n}\n\ntype SceneCapture = { sharp: HTMLCanvasElement; frosted: HTMLCanvasElement };\ntype SceneCaptureDimensions = {\n  width: number;\n  height: number;\n  rasterWidth: number;\n  rasterHeight: number;\n};\n\nfunction scaleCanvas(source: HTMLCanvasElement, width: number, height: number): HTMLCanvasElement | null {\n  const step = document.createElement(\"canvas\");\n  step.width = width;\n  step.height = height;\n  const context = step.getContext(\"2d\");\n  if (!context) return null;\n  context.imageSmoothingEnabled = true;\n  context.imageSmoothingQuality = \"high\";\n  context.drawImage(source, 0, 0, width, height);\n  return step;\n}\n\nfunction sceneCaptureDimensions(scene: HTMLElement, scale: number): SceneCaptureDimensions {\n  const rect = scene.getBoundingClientRect();\n  const width = Math.max(1, Math.round(rect.width));\n  const height = Math.max(1, Math.round(rect.height));\n  return {\n    width,\n    height,\n    rasterWidth: Math.max(1, Math.round(width * scale)),\n    rasterHeight: Math.max(1, Math.round(height * scale)),\n  };\n}\n\nfunction prepareSceneClone(scene: HTMLElement, dimensions: SceneCaptureDimensions, images: EmbeddedImages): HTMLElement | null {\n  const clone = cloneTree(scene, images);\n  if (!(clone instanceof HTMLElement)) return null;\n\n  clone.setAttribute(\"xmlns\", \"http://www.w3.org/1999/xhtml\");\n  clone.style.width = `${dimensions.width}px`;\n  clone.style.height = `${dimensions.height}px`;\n  clone.style.margin = \"0\";\n  clone.style.boxSizing = \"border-box\";\n  return clone;\n}\n\nasync function decodeSceneImage(clone: HTMLElement, dimensions: SceneCaptureDimensions): Promise<HTMLImageElement | null> {\n  const markup = new XMLSerializer().serializeToString(clone);\n  const { width, height, rasterWidth, rasterHeight } = dimensions;\n  const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${rasterWidth}\" height=\"${rasterHeight}\" viewBox=\"0 0 ${width} ${height}\"><foreignObject width=\"${width}\" height=\"${height}\">${markup}</foreignObject></svg>`;\n  const image = new Image();\n  image.decoding = \"async\";\n  image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n\n  try {\n    await image.decode();\n    return image;\n  } catch {\n    return null;\n  }\n}\n\nfunction drawSharpScene(image: HTMLImageElement, dimensions: SceneCaptureDimensions): HTMLCanvasElement | null {\n  const sharp = document.createElement(\"canvas\");\n  sharp.width = dimensions.rasterWidth;\n  sharp.height = dimensions.rasterHeight;\n  const context = sharp.getContext(\"2d\");\n  if (!context) return null;\n  context.drawImage(image, 0, 0, sharp.width, sharp.height);\n  return sharp;\n}\n\nfunction downsampleForFrost(sharp: HTMLCanvasElement, frost: number): HTMLCanvasElement | null {\n  const targetWidth = Math.max(1, Math.round(sharp.width / frost));\n  const targetHeight = Math.max(1, Math.round(sharp.height / frost));\n  let small: HTMLCanvasElement | null = sharp;\n  let stepWidth = sharp.width;\n  let stepHeight = sharp.height;\n\n  while (small && stepWidth * 0.5 >= targetWidth) {\n    stepWidth = Math.max(targetWidth, Math.round(stepWidth * 0.5));\n    stepHeight = Math.max(targetHeight, Math.round(stepHeight * 0.5));\n    small = scaleCanvas(small, stepWidth, stepHeight);\n  }\n\n  if (small && (small.width !== targetWidth || small.height !== targetHeight)) {\n    small = scaleCanvas(small, targetWidth, targetHeight);\n  }\n  if (!small) return null;\n\n  const bounced = scaleCanvas(small, Math.max(1, Math.round(targetWidth / 2)), Math.max(1, Math.round(targetHeight / 2)));\n  return bounced ? (scaleCanvas(bounced, targetWidth, targetHeight) ?? small) : small;\n}\n\nfunction drawFrostedScene(sharp: HTMLCanvasElement, frost: number): HTMLCanvasElement | null {\n  const frosted = document.createElement(\"canvas\");\n  frosted.width = sharp.width;\n  frosted.height = sharp.height;\n  const context = frosted.getContext(\"2d\");\n  if (!context) return null;\n\n  if (frost <= 1) {\n    context.drawImage(sharp, 0, 0);\n    return frosted;\n  }\n\n  /* Raster-relative downsampling keeps roughly one texel per CSS pixel on high-density screens. */\n  const smoothed = downsampleForFrost(sharp, frost);\n  if (smoothed) {\n    context.imageSmoothingEnabled = true;\n    context.imageSmoothingQuality = \"high\";\n    context.drawImage(smoothed, 0, 0, frosted.width, frosted.height);\n  } else {\n    context.drawImage(sharp, 0, 0);\n  }\n  return frosted;\n}\n\n/* Rasterize the scene (minus islands) → sharp + frosted 2D canvases. The frost pass is a\n   downscale/upscale blur (portable approximate gaussian: 2D-context filters are missing in Safari). */\nasync function captureScene(scene: HTMLElement, scale: number, frost: number): Promise<SceneCapture | null> {\n  const images = await embedSceneImages(scene);\n  if (!images) return null;\n  const dimensions = sceneCaptureDimensions(scene, scale);\n  const clone = prepareSceneClone(scene, dimensions, images);\n  if (!clone) return null;\n  const image = await decodeSceneImage(clone, dimensions);\n  if (!image) return null;\n  const sharp = drawSharpScene(image, dimensions);\n  if (!sharp) return null;\n  const frosted = drawFrostedScene(sharp, frost);\n  if (!frosted) return null;\n  return { sharp, frosted };\n}\n\nexport function createDynamicNotificationLiquid(canvas: HTMLCanvasElement, options: DynamicNotificationLiquidOptions = {}): () => void {\n  const {\n    refraction = 0.85,\n    chromaticAberration = 0,\n    zRadius = 32,\n    edgeDarkening = 0.05,\n    specular = 0.11,\n    fresnel = 0.35,\n    edgeHighlight = 0.16,\n    frost = 4,\n    maxDpr = 2,\n  } = options;\n  const gl = canvas.getContext(\"webgl\", { alpha: true, premultipliedAlpha: true, antialias: true });\n  if (!gl) {\n    canvas.dataset.glassFailed = \"webgl\";\n    return () => {\n      delete canvas.dataset.glassFailed;\n    };\n  }\n\n  const closestScene = canvas.closest(\"[data-dn-scene]\");\n  const scene = closestScene instanceof HTMLElement ? closestScene : document.body;\n\n  /* Cleanup must never loseContext(): lost context returns null from getExtension, so remount on same canvas could never recover. */\n  let contextLost = gl.isContextLost();\n  let liquid = contextLost ? null : buildProgram(gl);\n  if (!contextLost && !liquid) {\n    canvas.dataset.glassFailed = \"shader\";\n    return () => {\n      delete canvas.dataset.glassFailed;\n    };\n  }\n\n  let destroyed = false;\n  let intersecting = true;\n  let pageVisible = !document.hidden;\n  let rafId = 0;\n  let dpr = 1;\n  let radius = 0;\n  const initialState = canvas.parentElement?.dataset.state;\n  let aurora = auroraTarget(initialState);\n  let reveal = revealTarget(initialState);\n  let settle = settleTarget(initialState);\n  /* sends dying sheet toward top on exit; parked high whenever aurora is off */\n  let lift = 1 - aurora;\n  let staticFrameDrawn = false;\n  /* kept for instant re-upload after context restore */\n  let captured: SceneCapture | null = null;\n  let textureFresh = false;\n  let captureToken = 0;\n  let captureTimer = 0;\n  const startedAt = performance.now();\n  let previousFrameAt = startedAt;\n  const reducedMotionQuery = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\n  function reducedMotion(): boolean {\n    return reducedMotionQuery.matches || canvas.closest('[data-motion=\"reduced\"]') !== null;\n  }\n\n  function targetRadius(host: HTMLElement): number {\n    const parsed = Number.parseFloat(getComputedStyle(host).borderTopLeftRadius);\n    return Number.isFinite(parsed) ? parsed : 0;\n  }\n\n  /* Called from draw(), never observer: setting canvas.width clears buffer, so separate resize task shows blank frame. */\n  function resize(): void {\n    // layout box, immune to the @starting-style scale at mount\n    dpr = Math.min(window.devicePixelRatio || 1, maxDpr);\n    const width = Math.max(1, Math.round(canvas.clientWidth * dpr));\n    const height = Math.max(1, Math.round(canvas.clientHeight * dpr));\n    if (canvas.width !== width || canvas.height !== height) {\n      canvas.width = width;\n      canvas.height = height;\n    }\n  }\n\n  function uploadTexture(): void {\n    if (!gl || !liquid || contextLost || !captured) return;\n    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);\n    gl.bindTexture(gl.TEXTURE_2D, liquid.sharpTexture);\n    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, captured.sharp);\n    gl.bindTexture(gl.TEXTURE_2D, liquid.blurTexture);\n    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, captured.frosted);\n    textureFresh = true;\n  }\n\n  function recapture(): void {\n    captureToken += 1;\n    const token = captureToken;\n    /* Supersample beyond dpr so sub-pixel edge optics stay clean without unbounded texture memory. */\n    const rect = scene.getBoundingClientRect();\n    const supersample = Math.min((window.devicePixelRatio || 1) * 1.5, 3, 4096 / Math.max(rect.width, rect.height, 1));\n    void captureScene(scene, Math.max(1, supersample), frost).then((result) => {\n      if (destroyed || token !== captureToken) return;\n      if (!result) {\n        captured = null;\n        textureFresh = false;\n        delete canvas.dataset.glassReady;\n        canvas.dataset.glassFailed = \"capture\";\n        return;\n      }\n      captured = result;\n      delete canvas.dataset.glassFailed;\n      uploadTexture();\n      invalidate();\n    });\n  }\n\n  function scheduleRecapture(): void {\n    window.clearTimeout(captureTimer);\n    captureTimer = window.setTimeout(recapture, 200);\n  }\n\n  type MotionTargets = {\n    radius: number;\n    aurora: number;\n    reveal: number;\n    settle: number;\n    lift: number;\n  };\n\n  function advanceMotion(host: HTMLElement, reduced: boolean, deltaMs: number): MotionTargets {\n    const state = host.dataset.state;\n    const targetAurora = auroraTarget(state);\n    const targets = {\n      radius: targetRadius(host),\n      aurora: targetAurora,\n      reveal: revealTarget(state),\n      settle: settleTarget(state),\n      lift: targetAurora >= 0.5 ? 0 : 1,\n    };\n\n    radius = reduced ? targets.radius : approachAt60Hz(radius, targets.radius, 0.25, deltaMs);\n    aurora = reduced ? targets.aurora : approachAt60Hz(aurora, targets.aurora, 0.06, deltaMs);\n    reveal = reduced ? targets.reveal : approachAt60Hz(reveal, targets.reveal, 0.07, deltaMs);\n    settle = reduced ? targets.settle : approachAt60Hz(settle, targets.settle, 0.07, deltaMs);\n    /* linear exit so aurora clears surface before it collapses */\n    if (reduced) lift = targets.lift;\n    else if (targets.lift === 1) lift = Math.min(1, lift + deltaMs * 0.00216);\n    else lift = approachAt60Hz(lift, 0, 0.28, deltaMs);\n\n    return targets;\n  }\n\n  function shouldAnimate(host: HTMLElement, reduced: boolean, targets: MotionTargets): boolean {\n    return (\n      !reduced &&\n      (host.dataset.state === \"thinking\" ||\n        Math.abs(radius - targets.radius) > 0.05 ||\n        Math.abs(aurora - targets.aurora) > 0.002 ||\n        Math.abs(reveal - targets.reveal) > 0.002 ||\n        Math.abs(settle - targets.settle) > 0.002 ||\n        Math.abs(lift - targets.lift) > 0.002)\n    );\n  }\n\n  function draw(): boolean {\n    if (!gl || !liquid || contextLost || !textureFresh) return false;\n    const host = canvas.parentElement;\n    if (!host) return false;\n    resize();\n    const hostRect = host.getBoundingClientRect();\n    const sceneRect = scene.getBoundingClientRect();\n    if (sceneRect.width < 1 || sceneRect.height < 1) return false;\n\n    gl.viewport(0, 0, canvas.width, canvas.height);\n    gl.clearColor(0, 0, 0, 0);\n    gl.clear(gl.COLOR_BUFFER_BIT);\n    // biome-ignore lint/correctness/useHookAtTopLevel: WebGL's useProgram, not a React hook.\n    gl.useProgram(liquid.program);\n    const reduced = reducedMotion();\n    const now = performance.now();\n    const deltaMs = Math.min(100, now - previousFrameAt);\n    previousFrameAt = now;\n    const targets = advanceMotion(host, reduced, deltaMs);\n\n    gl.activeTexture(gl.TEXTURE0);\n    gl.bindTexture(gl.TEXTURE_2D, liquid.sharpTexture);\n    gl.activeTexture(gl.TEXTURE1);\n    gl.bindTexture(gl.TEXTURE_2D, liquid.blurTexture);\n    gl.uniform1i(liquid.sharpTex, 0);\n    gl.uniform1i(liquid.blurTex, 1);\n    gl.uniform2f(liquid.size, Math.max(1, canvas.clientWidth), Math.max(1, canvas.clientHeight));\n    gl.uniform2f(liquid.panelOffset, hostRect.left - sceneRect.left, hostRect.top - sceneRect.top);\n    gl.uniform2f(liquid.sceneSize, sceneRect.width, sceneRect.height);\n    gl.uniform1f(liquid.radius, radius);\n    gl.uniform1f(liquid.zRadius, zRadius);\n    gl.uniform1f(liquid.refract, refraction);\n    gl.uniform1f(liquid.chroma, chromaticAberration);\n    gl.uniform1f(liquid.edgeDarkening, edgeDarkening);\n    gl.uniform1f(liquid.spec, specular);\n    gl.uniform1f(liquid.fresnel, fresnel);\n    gl.uniform1f(liquid.edgeHL, edgeHighlight);\n    gl.uniform1f(liquid.time, reduced ? 4.2 : (now - startedAt) / 1000);\n    gl.uniform1f(liquid.aurora, aurora);\n    gl.uniform1f(liquid.reveal, reveal);\n    gl.uniform1f(liquid.settle, settle);\n    gl.uniform1f(liquid.lift, lift);\n    gl.uniform1f(liquid.ribbonHorizon, 0.5);\n    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n    delete canvas.dataset.glassFailed;\n    if (canvas.dataset.glassReady !== \"true\") canvas.dataset.glassReady = \"true\";\n    return shouldAnimate(host, reduced, targets);\n  }\n\n  function visible(): boolean {\n    return intersecting && pageVisible;\n  }\n\n  function tick(): void {\n    rafId = 0;\n    if (destroyed || !visible() || contextLost) return;\n    const keepAnimating = draw();\n    if (!keepAnimating) {\n      staticFrameDrawn = true;\n      return;\n    }\n    staticFrameDrawn = false;\n    rafId = requestAnimationFrame(tick);\n  }\n\n  function invalidate(): void {\n    staticFrameDrawn = false;\n    if (destroyed || !visible() || contextLost || rafId !== 0) return;\n    rafId = requestAnimationFrame(tick);\n  }\n\n  function handleVisibility(): void {\n    pageVisible = !document.hidden;\n    invalidate();\n  }\n\n  function handleContextLost(event: Event): void {\n    event.preventDefault();\n    contextLost = true;\n    textureFresh = false;\n    delete canvas.dataset.glassReady;\n    canvas.dataset.glassFailed = \"context\";\n    if (rafId !== 0) cancelAnimationFrame(rafId);\n    rafId = 0;\n  }\n\n  function handleContextRestored(): void {\n    if (!gl) return;\n    contextLost = false;\n    liquid = buildProgram(gl);\n    if (!liquid) {\n      canvas.dataset.glassFailed = \"shader\";\n      return;\n    }\n    uploadTexture();\n    invalidate();\n  }\n\n  const resizeObserver = new ResizeObserver(() => {\n    invalidate();\n  });\n  resizeObserver.observe(canvas);\n\n  /* scene resizing changes capture geometry, not viewport */\n  const sceneResizeObserver = new ResizeObserver(() => {\n    scheduleRecapture();\n  });\n  sceneResizeObserver.observe(scene);\n\n  /* re-rasterize when the backdrop actually changes — mutations inside any island are the morph\n     itself animating and must NOT trigger captures (the island is excluded from the raster anyway) */\n  const sceneMutationObserver = new MutationObserver((mutations) => {\n    for (const mutation of mutations) {\n      const target = mutation.target;\n      const element = target instanceof Element ? target : target.parentElement;\n      if (element?.closest(EXCLUDE_SELECTOR)) continue;\n      scheduleRecapture();\n      return;\n    }\n  });\n  sceneMutationObserver.observe(scene, { attributes: true, characterData: true, childList: true, subtree: true });\n\n  const intersectionObserver = new IntersectionObserver(\n    (entries) => {\n      for (const entry of entries) intersecting = entry.isIntersecting;\n      invalidate();\n    },\n    { rootMargin: \"64px\" },\n  );\n  intersectionObserver.observe(canvas);\n\n  const motionObserver = new MutationObserver(() => {\n    if (!staticFrameDrawn || !reducedMotion()) invalidate();\n  });\n  motionObserver.observe(document.documentElement, { attributes: true, attributeFilter: [\"data-motion\"] });\n\n  function handleMotionPreference(): void {\n    invalidate();\n  }\n\n  document.addEventListener(\"visibilitychange\", handleVisibility);\n  reducedMotionQuery.addEventListener(\"change\", handleMotionPreference);\n  canvas.addEventListener(\"webglcontextlost\", handleContextLost);\n  canvas.addEventListener(\"webglcontextrestored\", handleContextRestored);\n\n  recapture();\n  invalidate();\n\n  return () => {\n    destroyed = true;\n    if (rafId !== 0) cancelAnimationFrame(rafId);\n    window.clearTimeout(captureTimer);\n    resizeObserver.disconnect();\n    sceneResizeObserver.disconnect();\n    sceneMutationObserver.disconnect();\n    intersectionObserver.disconnect();\n    motionObserver.disconnect();\n    document.removeEventListener(\"visibilitychange\", handleVisibility);\n    reducedMotionQuery.removeEventListener(\"change\", handleMotionPreference);\n    canvas.removeEventListener(\"webglcontextlost\", handleContextLost);\n    canvas.removeEventListener(\"webglcontextrestored\", handleContextRestored);\n    delete canvas.dataset.glassReady;\n    delete canvas.dataset.glassFailed;\n    if (liquid && !gl.isContextLost()) {\n      gl.deleteProgram(liquid.program);\n      gl.deleteBuffer(liquid.buffer);\n      gl.deleteTexture(liquid.sharpTexture);\n      gl.deleteTexture(liquid.blurTexture);\n    }\n    liquid = null;\n    captured = null;\n  };\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/dynamic-notification-siri-wave.ts",
      "target": "@components/control-ui/dynamic-notification-siri-wave.ts",
      "type": "registry:component",
      "content": "export const DYNAMIC_NOTIFICATION_SIRI_WAVE_GLSL = /* glsl */ `\nconst float SIRI_PI = 3.14159265359;\nconst float SIRI_AMPLITUDE = 0.32;\nconst float SIRI_FREQUENCY = 1.1;\nconst float SIRI_ABERRATION_FREQUENCY = 1.0;\nconst float SIRI_SPEED = 2.4;\nconst float SIRI_WAVE_SCALE = 0.6;\nconst float SIRI_ABERRATION = 2.6;\nconst float SIRI_THICKNESS = 3.0;\nconst float SIRI_INTENSITY = 2.0;\nconst float SIRI_FALLOFF = 1.7;\nconst float SIRI_EDGE_MASK = 0.4;\nconst float SIRI_BAND_FILL = 30000.0;\nconst float SIRI_BAND_THICKNESS = 0.08;\nconst float SIRI_SOFTNESS = 2.5;\nconst float SIRI_LOW_AMPLITUDE = 6.0;\nconst float SIRI_LOW_INTENSITY = 1.5;\nconst float SIRI_MID_ABERRATION = 0.8;\nconst float SIRI_MID_ABERRATION_AMPLITUDE = 0.05;\nconst float SIRI_MID_SOFTNESS = 0.4;\nconst float SIRI_HIGH_ABERRATION = 0.5;\nconst float SIRI_HIGH_ABERRATION_AMPLITUDE = 0.06;\n\nvec3 siriSpectrum(int strand) {\n  float x = float(strand);\n  return clamp(vec3(abs(x - 3.0) - 1.0, 2.0 - abs(x - 2.0), 2.0 - abs(x - 4.0)), 0.0, 1.0);\n}\n\nvec3 dynamicNotificationSiriWave(vec2 uv, vec2 resolution, float time, float horizon, float lift) {\n  float aspect = resolution.x / max(resolution.y, 1.0);\n  vec2 p = vec2((uv.x * 2.0 - 1.0) * aspect, (uv.y - horizon + lift * 0.55) * 2.0);\n  float screenY = p.y;\n  p /= max(SIRI_WAVE_SCALE, 0.1);\n\n  float low = clamp(0.45 + 0.45 * sin(time * 0.8) * sin(time * 0.37 + 1.0), 0.0, 1.0);\n  float mid = clamp(0.40 + 0.40 * sin(time * 1.7 + 2.0) * sin(time * 0.53), 0.0, 1.0);\n  float high = clamp(0.30 + 0.30 * sin(time * 2.9 + 4.0) * sin(time * 0.71 + 2.0), 0.0, 1.0);\n  float drift = mod(time, 20.0 * SIRI_PI) * SIRI_SPEED;\n\n  float horizontalPosition = uv.x * 2.0 - 1.0;\n  float envelope = cos(SIRI_PI * 0.5 * min(abs(0.9 * horizontalPosition), 1.0));\n  envelope *= envelope;\n\n  float primaryAmplitude = SIRI_AMPLITUDE + 0.01 * low * SIRI_LOW_AMPLITUDE;\n  float strandAmplitude = primaryAmplitude + mid * SIRI_MID_ABERRATION_AMPLITUDE + high * SIRI_HIGH_ABERRATION_AMPLITUDE;\n  float aberration = SIRI_ABERRATION + mid * SIRI_MID_ABERRATION + high * SIRI_HIGH_ABERRATION;\n  float thickness = 0.01 * SIRI_THICKNESS;\n  float intensity = 0.01 * (SIRI_INTENSITY + low * SIRI_LOW_INTENSITY);\n  float softness = 0.01 * max(0.0, SIRI_SOFTNESS + mid * SIRI_MID_SOFTNESS);\n  float primaryY = primaryAmplitude * envelope * sin(p.x * SIRI_FREQUENCY + drift);\n  float bandAmount = 1e-4 * SIRI_BAND_FILL * intensity;\n\n  vec3 numerator = vec3(0.0);\n  vec3 denominator = vec3(0.0);\n  for (int strand = 0; strand < 4; strand++) {\n    vec3 hue = siriSpectrum(strand);\n    denominator += hue;\n    float phase = mix(-aberration, aberration, float(strand) / 3.0);\n    float strandY = strandAmplitude * envelope * sin(p.x * SIRI_ABERRATION_FREQUENCY + drift + phase);\n    float distanceToStrand = abs(p.y - strandY);\n    float line = intensity / (sqrt(distanceToStrand * distanceToStrand + softness * softness) + thickness);\n    float bandLow = min(primaryY, strandY);\n    float bandHigh = max(primaryY, strandY);\n    float distanceToBand = max(0.0, max(p.y - bandHigh, bandLow - p.y));\n    float band = bandAmount / (distanceToBand + SIRI_BAND_THICKNESS);\n    numerator += hue * (line + band);\n  }\n\n  vec3 color = numerator / max(denominator, vec3(0.0001));\n  float primaryDistance = abs(p.y - primaryY);\n  color += 0.5 * intensity / (sqrt(primaryDistance * primaryDistance + softness * softness) + thickness);\n  color = pow(max(color, 0.0), vec3(1.5));\n\n  float edgeFadePosition = clamp((abs(screenY) - 1.0) / -SIRI_EDGE_MASK, 0.0, 1.0);\n  float edgeFade = edgeFadePosition * edgeFadePosition * (3.0 - 2.0 * edgeFadePosition);\n  color *= edgeFade * exp(-pow(horizontalPosition * SIRI_FALLOFF, 2.0));\n  return color;\n}\n`;\n"
    },
    {
      "path": "src/registry/sources/control-ui/dynamic-notification.tsx",
      "target": "@components/control-ui/dynamic-notification.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport type { ChangeEvent, ComponentProps, CSSProperties, KeyboardEvent, MouseEvent } from \"react\";\nimport { createContext, useContext, useEffect, useId, useMemo, useRef } from \"react\";\nimport type { FormSubmitEvent } from \"@/components/control-ui/control-props\";\nimport { createDynamicNotificationGlass } from \"@/components/control-ui/dynamic-notification-glass\";\nimport { createDynamicNotificationLiquid } from \"@/components/control-ui/dynamic-notification-liquid\";\nimport type { DynamicNotificationProps, DynamicNotificationVariant } from \"@/components/control-ui/hooks/use-dynamic-notification\";\nimport { type DynamicNotificationController, useDynamicNotification } from \"@/components/control-ui/hooks/use-dynamic-notification\";\nimport type { DynamicNotificationKnobStyle } from \"@/components/control-ui/knob-contracts/dynamic-notification-knobs\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\nimport { Button } from \"@/components/control-ui/ui/button\";\n\nexport type DynamicNotificationState = \"collapsed\" | \"thinking\" | \"expanded\";\n\ntype DynamicNotificationWordStyle = CSSProperties & { \"--_dynamic-notification-word-index\"?: string };\n\ntype DynamicNotificationShellContextValue = Pick<DynamicNotificationController, \"open\" | \"disabled\" | \"setOpen\"> & {\n  state: DynamicNotificationState;\n  variant: DynamicNotificationVariant;\n  contentId: string;\n};\n\ntype DynamicNotificationReplyContextValue = Pick<\n  DynamicNotificationController,\n  \"reply\" | \"setReply\" | \"normalizedReply\" | \"canSubmit\" | \"clear\" | \"submitReply\" | \"handleReplySubmit\"\n>;\n\nconst DynamicNotificationShellContext = createContext<DynamicNotificationShellContextValue | null>(null);\nconst DynamicNotificationReplyContext = createContext<DynamicNotificationReplyContextValue | null>(null);\n\nfunction resolveNotificationState(open: boolean, loading: boolean): DynamicNotificationState {\n  if (!open) return \"collapsed\";\n  if (loading) return \"thinking\";\n  return \"expanded\";\n}\n\nfunction useDynamicNotificationShellContext() {\n  const context = useContext(DynamicNotificationShellContext);\n  if (!context) throw new Error(\"DynamicNotification compound components must be rendered inside <DynamicNotification>.\");\n  return context;\n}\n\nfunction useDynamicNotificationReplyContext() {\n  const context = useContext(DynamicNotificationReplyContext);\n  if (!context) throw new Error(\"DynamicNotification reply components must be rendered inside <DynamicNotification>.\");\n  return context;\n}\n\nexport function useDynamicNotificationContext() {\n  const shell = useDynamicNotificationShellContext();\n  const reply = useDynamicNotificationReplyContext();\n  return { ...shell, ...reply };\n}\n\nexport function DynamicNotification({\n  open,\n  defaultOpen,\n  onOpenChange,\n  loading = false,\n  replyValue,\n  defaultReplyValue,\n  onReplyValueChange,\n  onReply,\n  variant = \"surface\",\n  disabled = false,\n  className,\n  children,\n  ...props\n}: DynamicNotificationProps) {\n  const notification = useDynamicNotification({\n    open,\n    defaultOpen,\n    onOpenChange,\n    replyValue,\n    defaultReplyValue,\n    onReplyValueChange,\n    onReply,\n    disabled,\n  });\n  const contentId = useId();\n  const state = resolveNotificationState(notification.open, loading);\n  const shellContext = useMemo(\n    () =>\n      ({\n        open: notification.open,\n        disabled: notification.disabled,\n        setOpen: notification.setOpen,\n        state,\n        variant,\n        contentId,\n      }) satisfies DynamicNotificationShellContextValue,\n    [notification.open, notification.disabled, notification.setOpen, state, variant, contentId],\n  );\n  const replyContext = useMemo(\n    () =>\n      ({\n        reply: notification.reply,\n        setReply: notification.setReply,\n        normalizedReply: notification.normalizedReply,\n        canSubmit: notification.canSubmit,\n        clear: notification.clear,\n        submitReply: notification.submitReply,\n        handleReplySubmit: notification.handleReplySubmit,\n      }) satisfies DynamicNotificationReplyContextValue,\n    [\n      notification.reply,\n      notification.setReply,\n      notification.normalizedReply,\n      notification.canSubmit,\n      notification.clear,\n      notification.submitReply,\n      notification.handleReplySubmit,\n    ],\n  );\n\n  return (\n    <DynamicNotificationShellContext.Provider value={shellContext}>\n      <DynamicNotificationReplyContext.Provider value={replyContext}>\n        <div\n          data-control-ui=\"dynamic-notification\"\n          data-control-family=\"dynamic-notification\"\n          data-slot=\"root\"\n          data-state={state}\n          data-variant={variant}\n          className={cn(\"relative flex w-full justify-center\", className)}\n          {...props}\n        >\n          {children}\n        </div>\n      </DynamicNotificationReplyContext.Provider>\n    </DynamicNotificationShellContext.Provider>\n  );\n}\n\nexport type DynamicNotificationIslandProps = ComponentProps<\"section\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\nexport function DynamicNotificationIsland({ className, onKeyDown, ...props }: DynamicNotificationIslandProps) {\n  const { open, setOpen, state, variant } = useDynamicNotificationShellContext();\n\n  function handleKeyDown(event: KeyboardEvent<HTMLElement>) {\n    onKeyDown?.(event);\n    if (event.defaultPrevented) return;\n    if (event.key === \"Escape\" && open) {\n      setOpen(false, \"escape-key\", event.nativeEvent, event.currentTarget);\n    }\n  }\n\n  return (\n    <section\n      aria-label=\"Assistant notification\"\n      aria-busy={state === \"thinking\" || undefined}\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"island\"\n      data-state={state}\n      data-variant={variant}\n      onKeyDown={handleKeyDown}\n      className={cn(\"relative isolate overflow-hidden\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type DynamicNotificationGlassProps = ComponentProps<\"canvas\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\n/** Optional upgrade for variant=\"glass\" — CSS fallback stays underneath. */\nexport function DynamicNotificationGlass({ className, ...props }: DynamicNotificationGlassProps) {\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    return createDynamicNotificationGlass(canvas);\n  }, []);\n\n  return (\n    <canvas\n      ref={canvasRef}\n      aria-hidden=\"true\"\n      tabIndex={-1}\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"glass\"\n      className={cn(\"pointer-events-none absolute inset-0 -z-10 size-full\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type DynamicNotificationLiquidProps = ComponentProps<\"canvas\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\n/** WebGL transmits nearest scene through surface while keeping distortion concentrated at its edge. */\nexport function DynamicNotificationLiquid({ className, ...props }: DynamicNotificationLiquidProps) {\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    return createDynamicNotificationLiquid(canvas);\n  }, []);\n\n  return (\n    <canvas\n      ref={canvasRef}\n      aria-hidden=\"true\"\n      tabIndex={-1}\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"liquid\"\n      className={cn(\"pointer-events-none absolute inset-0 -z-10 size-full\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type DynamicNotificationPillProps = ComponentProps<\"button\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\nexport function DynamicNotificationPill({ className, children, onClick, ...props }: DynamicNotificationPillProps) {\n  const { contentId, disabled, open, setOpen } = useDynamicNotificationShellContext();\n\n  function handleClick(event: MouseEvent<HTMLButtonElement>) {\n    onClick?.(event);\n    if (event.defaultPrevented) return;\n    setOpen(true, \"trigger-press\", event.nativeEvent, event.currentTarget);\n  }\n\n  return (\n    <button\n      type=\"button\"\n      aria-expanded={open}\n      aria-controls={contentId}\n      // inert, not CSS visibility: it lands with React commit, so tab order is right mid-morph instead of flipping halfway through\n      inert={open}\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"pill\"\n      onClick={handleClick}\n      disabled={disabled}\n      className={cn(\"absolute inset-0 flex cursor-pointer items-center justify-center gap-2 px-4\", className)}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n}\n\nexport type DynamicNotificationIndicatorProps = ComponentProps<\"span\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\n/** Breathing orb marking assistant activity; dynamic-notification.css animates it. */\nexport function DynamicNotificationIndicator({ className, ...props }: DynamicNotificationIndicatorProps) {\n  return (\n    <span\n      aria-hidden=\"true\"\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"indicator\"\n      className={cn(\"size-2 shrink-0\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type DynamicNotificationContentProps = ComponentProps<\"div\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\nexport function DynamicNotificationContent({ className, id, ...props }: DynamicNotificationContentProps) {\n  const { contentId, state } = useDynamicNotificationShellContext();\n\n  return (\n    <div\n      id={id ?? contentId}\n      inert={state !== \"expanded\"}\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"content\"\n      className={cn(\"flex flex-col gap-2.5 px-4 pt-3 pb-3.5\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type DynamicNotificationTitleProps = ComponentProps<\"div\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\nexport function DynamicNotificationTitle({ className, ...props }: DynamicNotificationTitleProps) {\n  return (\n    <div\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"title\"\n      className={cn(\"flex-1\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type DynamicNotificationMessageProps = Omit<ComponentProps<\"p\">, \"children\"> & {\n  children: string;\n} & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\nexport function DynamicNotificationMessage({ className, children, ...props }: DynamicNotificationMessageProps) {\n  return (\n    <p\n      aria-live=\"polite\"\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"message\"\n      className={className}\n      {...props}\n    >\n      <DynamicNotificationWords key={children} text={children} />\n    </p>\n  );\n}\n\nfunction DynamicNotificationWords({ text }: { text: string }) {\n  let wordIndex = 0;\n  return text.split(/(\\s+)/).map((part, position) => {\n    if (part.length === 0 || /^\\s+$/.test(part)) return part;\n    const style: DynamicNotificationWordStyle = { \"--_dynamic-notification-word-index\": `${wordIndex}` };\n    wordIndex += 1;\n    return (\n      // biome-ignore lint/suspicious/noArrayIndexKey: split positions are stable for a given text; the list remounts wholesale (key={text}) when the message changes.\n      <span key={position} data-control-ui=\"dynamic-notification\" data-control-family=\"dynamic-notification\" data-slot=\"word\" style={style}>\n        {part}\n      </span>\n    );\n  });\n}\n\nexport type DynamicNotificationReplyProps = ComponentProps<\"form\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\nexport function DynamicNotificationReply({ className, onSubmit, ...props }: DynamicNotificationReplyProps) {\n  const { handleReplySubmit } = useDynamicNotificationReplyContext();\n\n  function handleSubmit(event: FormSubmitEvent) {\n    onSubmit?.(event);\n    if (event.defaultPrevented) return;\n    handleReplySubmit(event);\n  }\n\n  return (\n    <form\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"reply\"\n      onSubmit={handleSubmit}\n      className={cn(\"flex items-center gap-2\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type DynamicNotificationReplyInputProps = ComponentProps<\"input\"> & { style?: CSSProperties & DynamicNotificationKnobStyle };\n\nexport function DynamicNotificationReplyInput({ className, onChange, disabled, ...props }: DynamicNotificationReplyInputProps) {\n  const { disabled: contextDisabled, state } = useDynamicNotificationShellContext();\n  const { reply, setReply } = useDynamicNotificationReplyContext();\n  const inputRef = useRef<HTMLInputElement | null>(null);\n\n  // waits out the \"thinking\" phase — content is inert until expanded, so focus would be dropped\n  useEffect(() => {\n    if (state !== \"expanded\") return;\n    const frame = requestAnimationFrame(() => inputRef.current?.focus({ preventScroll: true }));\n    return () => cancelAnimationFrame(frame);\n  }, [state]);\n\n  function handleChange(event: ChangeEvent<HTMLInputElement>) {\n    onChange?.(event);\n    if (!event.defaultPrevented) setReply(event.currentTarget.value);\n  }\n\n  return (\n    <input\n      ref={inputRef}\n      type=\"text\"\n      aria-label=\"Reply\"\n      data-control-ui=\"dynamic-notification\"\n      data-control-family=\"dynamic-notification\"\n      data-slot=\"reply-input\"\n      value={reply}\n      onChange={handleChange}\n      disabled={disabled ?? contextDisabled}\n      className={cn(\"h-9 min-w-0 flex-1 px-3.5 disabled:cursor-not-allowed\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type DynamicNotificationReplySubmitProps = ComponentProps<typeof Button>;\n\nexport function DynamicNotificationReplySubmit({ className, disabled, children, ...props }: DynamicNotificationReplySubmitProps) {\n  const { canSubmit } = useDynamicNotificationReplyContext();\n\n  return (\n    <Button\n      data-control-ui=\"dynamic-notification\"\n      data-dynamic-notification-submit=\"true\"\n      data-slot=\"reply-submit\"\n      type=\"submit\"\n      variant=\"solid\"\n      size=\"lg\"\n      iconOnly\n      shape=\"circle\"\n      aria-label=\"Send reply\"\n      disabled={disabled ?? !canSubmit}\n      className={cn(\"shrink-0\", className)}\n      {...props}\n    >\n      {children ?? (\n        <svg viewBox=\"0 0 16 16\" className=\"size-4\" aria-hidden=\"true\" fill=\"none\">\n          <path d=\"M8 12.5v-9M4 7l4-3.5L12 7\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n        </svg>\n      )}\n    </Button>\n  );\n}\n\nexport type DynamicNotificationCloseProps = ComponentProps<typeof Button>;\n\nexport function DynamicNotificationClose({ className, children, onClick, ...props }: DynamicNotificationCloseProps) {\n  const { setOpen } = useDynamicNotificationShellContext();\n\n  function handleClick(event: MouseEvent<HTMLButtonElement>) {\n    onClick?.(event);\n    if (event.defaultPrevented) return;\n    setOpen(false, \"close-press\", event.nativeEvent, event.currentTarget);\n  }\n\n  return (\n    <Button\n      data-control-ui=\"dynamic-notification\"\n      data-dynamic-notification-close=\"true\"\n      data-slot=\"close\"\n      variant=\"quiet\"\n      size=\"sm\"\n      iconOnly\n      shape=\"circle\"\n      aria-label=\"Dismiss\"\n      onClick={handleClick}\n      className={cn(\"-mr-1.5 shrink-0\", className)}\n      {...props}\n    >\n      {children ?? (\n        <svg viewBox=\"0 0 16 16\" className=\"size-3.5\" aria-hidden=\"true\" fill=\"none\">\n          <path d=\"M4 4 12 12M12 4 4 12\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\n        </svg>\n      )}\n    </Button>\n  );\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/dynamic-notification-input.css",
      "target": "@components/control-ui/styles/recipes/dynamic-notification-input.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"reply-input\"]) {\n    --_dynamic-notification-input-placeholder-paint: oklch(from var(--_dynamic-notification-active-foreground) l c h / 0.45);\n    border: 0;\n    border-radius: 9999px;\n    background: oklch(from var(--_dynamic-notification-active-foreground) l c h / 0.08);\n    color: var(--_dynamic-notification-active-foreground);\n    box-shadow: inset 0 0 0 1px oklch(from var(--_dynamic-notification-active-foreground) l c h / 0.1);\n    font-size: var(--text-body);\n    outline: none;\n    transition: box-shadow var(--duration-fast) var(--ease-standard);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"reply-input\"])::placeholder {\n    color: var(--_dynamic-notification-input-placeholder-paint);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"reply-input\"]:disabled) {\n    opacity: 0.5;\n  }\n\n  :where([data-control-family=\"button\"][data-dynamic-notification-close=\"true\"]) {\n    color: oklch(from var(--_dynamic-notification-active-foreground) l c h / 0.55);\n  }\n\n  :where([data-control-family=\"button\"][data-dynamic-notification-close=\"true\"]:hover) {\n    color: inherit;\n  }\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/dynamic-notification-motion.css",
      "target": "@components/control-ui/styles/recipes/dynamic-notification-motion.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"root\"]) {\n    --_dynamic-notification-word-stagger: calc(var(--duration-fast) * 0.35);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"root\"][data-state=\"thinking\"]) {\n    --_dynamic-notification-ink-plateau: calc(50% - 10%);\n    --_dynamic-notification-ink-mid-stop: calc(50% - 3%);\n    --_dynamic-notification-ink-low-stop: calc(50% + 3%);\n    --_dynamic-notification-ink-tail-stop: calc(50% + 10%);\n    --_dynamic-notification-ink-mid: oklch(0 0 0 / 0.72);\n    --_dynamic-notification-ink-low: oklch(0 0 0 / 0.3);\n    --_dynamic-notification-ink-tail: oklch(0 0 0 / 0.1);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"root\"][data-state=\"expanded\"]) {\n    --_dynamic-notification-ink-plateau: 70%;\n    --_dynamic-notification-ink-mid-stop: 82%;\n    --_dynamic-notification-ink-low-stop: 92%;\n    --_dynamic-notification-ink-tail-stop: 100%;\n    --_dynamic-notification-ink-mid: oklch(0 0 0 / 0.78);\n    --_dynamic-notification-ink-low: oklch(0 0 0 / 0.55);\n    --_dynamic-notification-ink-tail: oklch(0 0 0 / 0.4);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"]) {\n    interpolate-size: allow-keywords;\n    width: 8.5rem;\n    height: 2.5rem;\n    border-radius: calc(2.5rem / 2);\n    opacity: 1;\n    scale: 1;\n    translate: 0 0;\n    filter: blur(0);\n    transition:\n      width calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-morph-easing),\n      height calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-morph-easing),\n      border-radius calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-morph-easing),\n      box-shadow calc(var(--duration-slow) * 2) var(--ease-standard),\n      opacity var(--duration-slow) var(--ease-emphasized),\n      scale calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-morph-easing),\n      translate calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-morph-easing),\n      filter var(--duration-slow) var(--ease-emphasized),\n      --_dynamic-notification-ink-mid calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing),\n      --_dynamic-notification-ink-low calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing),\n      --_dynamic-notification-ink-tail calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing),\n      --_dynamic-notification-ink-plateau calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing),\n      --_dynamic-notification-ink-mid-stop calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing),\n      --_dynamic-notification-ink-low-stop calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing),\n      --_dynamic-notification-ink-tail-stop calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing);\n  }\n\n  @starting-style {\n    :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"]) {\n      opacity: 0;\n      scale: 0.85;\n      translate: 0 -0.4rem;\n      filter: blur(10px);\n    }\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"][data-state=\"thinking\"]) {\n    width: min(10rem, 100%);\n    height: 5.75rem;\n    border-radius: 2.75rem;\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"][data-state=\"expanded\"]) {\n    width: min(22.5rem, 100%);\n    height: auto;\n    border-radius: var(--cui-dynamic-notification-expanded-radius);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"pill\"]) {\n    opacity: 1;\n    filter: blur(0);\n    scale: 1;\n    transition:\n      opacity var(--duration-base) var(--ease-standard) calc(var(--duration-slow) * 0.55),\n      filter var(--duration-base) var(--ease-standard) calc(var(--duration-slow) * 0.55),\n      scale var(--duration-base) var(--ease-emphasized) calc(var(--duration-slow) * 0.55);\n  }\n\n  :where(\n    [data-control-family=\"dynamic-notification\"][data-slot=\"island\"]:not([data-state=\"collapsed\"])\n      :where([data-control-family=\"dynamic-notification\"][data-slot=\"pill\"])\n  ) {\n    opacity: 0;\n    filter: blur(6px);\n    scale: 0.8;\n    pointer-events: none;\n    transition-duration: var(--duration-fast);\n    transition-delay: calc(var(--duration-fast) * 0);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"content\"]) {\n    width: min(22.5rem, 100%);\n    opacity: 1;\n    filter: blur(0);\n    translate: 0 0;\n    scale: 1;\n    transform-origin: 50% 0%;\n    transition:\n      opacity calc(var(--duration-slow) * 1.4) var(--ease-emphasized) calc(var(--duration-fast) * 0.6),\n      filter calc(var(--duration-slow) * 1.4) var(--ease-emphasized) calc(var(--duration-fast) * 0.6),\n      translate calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing) calc(var(--duration-fast) * 0.6),\n      scale calc(var(--duration-slow) * 2) var(--cui-dynamic-notification-content-easing) calc(var(--duration-fast) * 0.6);\n  }\n\n  :where(\n    [data-control-family=\"dynamic-notification\"][data-slot=\"island\"]:not([data-state=\"expanded\"])\n      :where([data-control-family=\"dynamic-notification\"][data-slot=\"content\"])\n  ) {\n    opacity: 0;\n    filter: blur(10px);\n    translate: 0 -0.5rem;\n    scale: 0.94;\n    transition:\n      opacity var(--duration-base) var(--ease-standard),\n      filter var(--duration-base) var(--ease-standard),\n      translate var(--duration-base) var(--ease-standard),\n      scale var(--duration-base) var(--ease-standard);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"message\"] [data-control-family=\"dynamic-notification\"][data-slot=\"word\"]) {\n    display: inline-block;\n  }\n\n  :where(\n    [data-control-family=\"dynamic-notification\"][data-slot=\"island\"][data-state=\"expanded\"]\n      :where([data-control-family=\"dynamic-notification\"][data-slot=\"message\"])\n      [data-control-family=\"dynamic-notification\"][data-slot=\"word\"]\n  ) {\n    animation: dn-word-in calc(var(--duration-slow) * 1.8) var(--cui-dynamic-notification-content-easing) both;\n    animation-delay: calc(\n      var(--duration-base) *\n      0.8 +\n      var(--_dynamic-notification-word-index, 0) *\n      var(--_dynamic-notification-word-stagger)\n    );\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"indicator\"]) {\n    background:\n      radial-gradient(circle at 30% 30%, oklch(0.85 0.1 210 / 0.9), transparent 60%),\n      conic-gradient(\n        from 210deg,\n        var(--cui-dynamic-notification-indicator-start),\n        var(--cui-dynamic-notification-indicator-middle),\n        var(--cui-dynamic-notification-indicator-end),\n        var(--cui-dynamic-notification-indicator-start)\n      );\n    box-shadow: 0 0 8px oklch(0.7 0.17 250 / 0.55);\n    animation: dn-indicator-breathe calc(var(--duration-slow) * 5) var(--ease-standard) infinite;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"]) {\n      --_dynamic-notification-word-stagger: 0ms;\n    }\n  }\n\n  :where([data-motion=\"reduced\"] :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"])) {\n    --_dynamic-notification-word-stagger: 0ms;\n  }\n}\n\n@keyframes dn-word-in {\n  from {\n    opacity: 0;\n    filter: blur(8px);\n    translate: 0 0.4em;\n  }\n}\n\n@keyframes dn-indicator-breathe {\n  0%,\n  100% {\n    scale: 1;\n    opacity: 0.85;\n  }\n\n  50% {\n    scale: 1.35;\n    opacity: 1;\n  }\n}\n\n@property --_dynamic-notification-ink-mid {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: oklch(0 0 0 / 0.97);\n}\n\n@property --_dynamic-notification-ink-low {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: oklch(0 0 0 / 0.97);\n}\n\n@property --_dynamic-notification-ink-tail {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: oklch(0 0 0 / 0.97);\n}\n\n@property --_dynamic-notification-ink-plateau {\n  syntax: \"<percentage>\";\n  inherits: true;\n  initial-value: 100%;\n}\n\n@property --_dynamic-notification-ink-mid-stop {\n  syntax: \"<percentage>\";\n  inherits: true;\n  initial-value: 100%;\n}\n\n@property --_dynamic-notification-ink-low-stop {\n  syntax: \"<percentage>\";\n  inherits: true;\n  initial-value: 100%;\n}\n\n@property --_dynamic-notification-ink-tail-stop {\n  syntax: \"<percentage>\";\n  inherits: true;\n  initial-value: 100%;\n}\n\n@property --cui-dynamic-notification-content-easing {\n  syntax: \"*\";\n  inherits: true;\n  initial-value: linear(\n    0,\n    0.0553 4.55%,\n    0.1795 9.09%,\n    0.328 13.64%,\n    0.4745 18.18%,\n    0.6054 22.73%,\n    0.7148 27.27%,\n    0.8017 31.82%,\n    0.868 36.36%,\n    0.9166 40.91%,\n    0.9509 45.45%,\n    0.9741 50%,\n    0.9891 54.55%,\n    0.9982 59.09%,\n    1.0032 63.64%,\n    1.0056 68.18%,\n    1.0063 72.73%,\n    1.006 77.27%,\n    1.0053 81.82%,\n    1.0044 86.36%,\n    1.0034 90.91%,\n    1.0026 95.45%,\n    1\n  );\n}\n\n@property --cui-dynamic-notification-expanded-radius {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n@property --cui-dynamic-notification-morph-easing {\n  syntax: \"*\";\n  inherits: true;\n  initial-value: linear(\n    0,\n    0.0568 3.85%,\n    0.1888 7.69%,\n    0.351 11.54%,\n    0.5142 15.38%,\n    0.6608 19.23%,\n    0.7824 23.08%,\n    0.8767 26.92%,\n    0.9449 30.77%,\n    0.9908 34.62%,\n    1.0187 38.46%,\n    1.0332 42.31%,\n    1.0382 46.15%,\n    1.0372 50%,\n    1.0327 53.85%,\n    1.0266 57.69%,\n    1.0203 61.54%,\n    1.0145 65.38%,\n    1.0095 69.23%,\n    1.0056 73.08%,\n    1.0027 76.92%,\n    1.0008 80.77%,\n    0.9995 84.62%,\n    0.9988 88.46%,\n    0.9986 92.31%,\n    0.9985 96.15%,\n    1\n  );\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/dynamic-notification.css",
      "target": "@components/control-ui/styles/recipes/dynamic-notification.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"root\"]) {\n    --cui-dynamic-notification-content-easing: linear(\n      0,\n      0.0553 4.55%,\n      0.1795 9.09%,\n      0.328 13.64%,\n      0.4745 18.18%,\n      0.6054 22.73%,\n      0.7148 27.27%,\n      0.8017 31.82%,\n      0.868 36.36%,\n      0.9166 40.91%,\n      0.9509 45.45%,\n      0.9741 50%,\n      0.9891 54.55%,\n      0.9982 59.09%,\n      1.0032 63.64%,\n      1.0056 68.18%,\n      1.0063 72.73%,\n      1.006 77.27%,\n      1.0053 81.82%,\n      1.0044 86.36%,\n      1.0034 90.91%,\n      1.0026 95.45%,\n      1\n    );\n    --cui-dynamic-notification-expanded-radius: 1.65rem;\n    --cui-dynamic-notification-glass-foreground: white;\n    --cui-dynamic-notification-glass-ring-color: oklch(1 0 0 / 0.12);\n    --cui-dynamic-notification-indicator-end: oklch(0.7 0.17 250);\n    --cui-dynamic-notification-indicator-middle: oklch(0.75 0.15 70);\n    --cui-dynamic-notification-indicator-start: oklch(0.62 0.19 25);\n    --cui-dynamic-notification-liquid-foreground: white;\n    --cui-dynamic-notification-morph-easing: linear(\n      0,\n      0.0568 3.85%,\n      0.1888 7.69%,\n      0.351 11.54%,\n      0.5142 15.38%,\n      0.6608 19.23%,\n      0.7824 23.08%,\n      0.8767 26.92%,\n      0.9449 30.77%,\n      0.9908 34.62%,\n      1.0187 38.46%,\n      1.0332 42.31%,\n      1.0382 46.15%,\n      1.0372 50%,\n      1.0327 53.85%,\n      1.0266 57.69%,\n      1.0203 61.54%,\n      1.0145 65.38%,\n      1.0095 69.23%,\n      1.0056 73.08%,\n      1.0027 76.92%,\n      1.0008 80.77%,\n      0.9995 84.62%,\n      0.9988 88.46%,\n      0.9986 92.31%,\n      0.9985 96.15%,\n      1\n    );\n    --cui-dynamic-notification-surface-background: var(--popover);\n    --cui-dynamic-notification-surface-foreground: var(--popover-foreground);\n    --cui-dynamic-notification-surface-ring-color: var(--border);\n    --cui-dynamic-notification-surface-shadow: var(--shadow-pop);\n    --_dynamic-notification-active-foreground: var(--cui-dynamic-notification-surface-foreground);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"root\"][data-variant=\"glass\"]) {\n    --_dynamic-notification-active-foreground: var(--cui-dynamic-notification-glass-foreground);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"root\"][data-variant=\"liquid\"]) {\n    --_dynamic-notification-active-foreground: var(--cui-dynamic-notification-liquid-foreground);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"][data-variant=\"glass\"]) {\n    background: linear-gradient(\n      to bottom,\n      oklch(0 0 0 / 0.97) 0%,\n      oklch(0 0 0 / 0.97) var(--_dynamic-notification-ink-plateau),\n      var(--_dynamic-notification-ink-mid) var(--_dynamic-notification-ink-mid-stop),\n      var(--_dynamic-notification-ink-low) var(--_dynamic-notification-ink-low-stop),\n      var(--_dynamic-notification-ink-tail) var(--_dynamic-notification-ink-tail-stop),\n      var(--_dynamic-notification-ink-tail) 100%\n    );\n    backdrop-filter: blur(4px) saturate(1.4);\n    color: var(--cui-dynamic-notification-glass-foreground);\n    box-shadow:\n      inset 0 0 0 1px var(--cui-dynamic-notification-glass-ring-color),\n      var(--shadow-pop);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"][data-variant=\"liquid\"]) {\n    background: linear-gradient(\n      to bottom,\n      oklch(0 0 0 / 0.97) 0%,\n      oklch(0 0 0 / 0.97) var(--_dynamic-notification-ink-plateau),\n      var(--_dynamic-notification-ink-mid) var(--_dynamic-notification-ink-mid-stop),\n      var(--_dynamic-notification-ink-low) var(--_dynamic-notification-ink-low-stop),\n      var(--_dynamic-notification-ink-tail) var(--_dynamic-notification-ink-tail-stop),\n      var(--_dynamic-notification-ink-tail) 100%\n    );\n    color: var(--cui-dynamic-notification-liquid-foreground);\n    text-shadow: 0 1px 2px oklch(0 0 0 / 0.25);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"][data-variant=\"glass\"]:has([data-glass-ready=\"true\"])),\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"][data-variant=\"liquid\"]:has([data-glass-ready=\"true\"])) {\n    background: transparent;\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"glass\"]),\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"liquid\"]) {\n    opacity: 0;\n    transition: opacity var(--duration-base) var(--ease-standard);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"glass\"][data-glass-ready=\"true\"]),\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"liquid\"][data-glass-ready=\"true\"]) {\n    opacity: 1;\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"][data-variant=\"surface\"]) {\n    background: var(--cui-dynamic-notification-surface-background);\n    color: var(--cui-dynamic-notification-surface-foreground);\n    box-shadow:\n      inset 0 0 0 1px var(--cui-dynamic-notification-surface-ring-color),\n      var(--cui-dynamic-notification-surface-shadow);\n    backdrop-filter: blur(var(--backdrop-blur-popover));\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"pill\"]) {\n    font-size: var(--text-caption);\n    outline-style: none;\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"title\"]) {\n    font-size: var(--text-caption);\n    opacity: 0.55;\n    font-weight: var(--font-weight-medium);\n    letter-spacing: var(--tracking-wide);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"message\"]) {\n    font-size: var(--text-body-lg);\n    line-height: 1.375;\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"indicator\"]) {\n    border-radius: calc(infinity * 1px);\n  }\n\n  :where([data-control-family=\"dynamic-notification\"][data-slot=\"island\"]) {\n    outline-style: none;\n  }\n}\n\n@property --cui-dynamic-notification-glass-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-dynamic-notification-glass-ring-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-dynamic-notification-liquid-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-dynamic-notification-surface-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-dynamic-notification-surface-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-dynamic-notification-surface-ring-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-dynamic-notification-surface-shadow {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-dynamic-notification-indicator-end {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-dynamic-notification-indicator-middle {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-dynamic-notification-indicator-start {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/dynamic-notification.css\"": {},
    "@import \"../components/control-ui/styles/recipes/dynamic-notification-input.css\"": {},
    "@import \"../components/control-ui/styles/recipes/dynamic-notification-motion.css\"": {}
  },
  "meta": {}
}
