{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "optical-engine",
  "type": "registry:lib",
  "title": "Optical Engine",
  "description": "One shared WebGL2 context serving every optical instrument on the page, with an intersection-gated render loop, a frame-budget ladder, and permanent CSS fallback on context loss.",
  "dependencies": [],
  "files": [
    {
      "path": "components/optics/types.ts",
      "content": "/* Design tokens & architecture sourced from The Chromologium (https://chromologium.com) */\n/**\n * The Chromologium — Optical Engine Contracts\n *\n * An instrument never owns a GL context. It registers a host element and a\n * blit-target canvas, receives scissored frames from the shared engine, and\n * keeps its static CSS state underneath — so a lost context, a constrained\n * device, or a reduced-motion preference is the ordinary path, not an error.\n */\n\nimport type { OpticalTier } from \"@/lib/opticalPreference\";\n\nexport interface OpticalPointer {\n  /** Pointer position in surface-local CSS px. */\n  x: number;\n  y: number;\n  /** Velocity in CSS px/s, exponentially smoothed. */\n  vx: number;\n  vy: number;\n  down: boolean;\n  inside: boolean;\n}\n\nexport interface FrameInfo {\n  /** Seconds since the engine started. */\n  t: number;\n  /** Seconds since the previous frame (clamped to 0.1). */\n  dt: number;\n  tier: OpticalTier;\n  /** Default-framebuffer viewport for this surface: [x, y, w, h]. Multi-pass\n   *  renderers restore this after drawing into their own FBOs. */\n  viewport?: [number, number, number, number];\n}\n\nexport interface OpticalSurfaceState {\n  readonly id: number;\n  readonly host: HTMLElement;\n  readonly canvas: HTMLCanvasElement;\n  readonly ctx2d: CanvasRenderingContext2D;\n  /** Backing-store size in device px (post resolution-scale). */\n  width: number;\n  height: number;\n  /** Layout size in CSS px. */\n  cssWidth: number;\n  cssHeight: number;\n  pointer: OpticalPointer;\n  /** Read per frame; instrument-specific uniform values. */\n  getUniforms?: () => Record<string, number | number[]>;\n  /** Renderer-private storage for GL objects (programs, FBOs, textures). */\n  readonly store: Map<string, unknown>;\n}\n\nexport interface OpticalRenderer {\n  /**\n   * Compile programs and allocate per-surface resources. Called once before\n   * the first render, and again after a resize if `resizeReinit` is true.\n   * Throwing here drops the surface to its static CSS state.\n   */\n  init(gl: WebGL2RenderingContext, surface: OpticalSurfaceState): void;\n  /** Set when FBO-backed renderers must rebuild their targets on resize. */\n  resizeReinit?: boolean;\n  /** Draw one frame; the viewport and scissor are already set. */\n  render(\n    gl: WebGL2RenderingContext,\n    surface: OpticalSurfaceState,\n    frame: FrameInfo,\n  ): void;\n  /** Free per-surface GL resources. */\n  dispose(gl: WebGL2RenderingContext, surface: OpticalSurfaceState): void;\n}\n\nexport interface RegisterOptions {\n  host: HTMLElement;\n  canvas: HTMLCanvasElement;\n  tier: OpticalTier;\n  createRenderer: () => OpticalRenderer;\n  getUniforms?: () => Record<string, number | number[]>;\n  /** Attach pointer tracking listeners on the host element. */\n  interactive?: boolean;\n  /** Fired once after the first successful blit — fade the canvas in. */\n  onReady?: () => void;\n  /** Fired when the engine permanently degrades this surface to CSS. */\n  onStatic?: () => void;\n}\n",
      "type": "registry:lib",
      "target": "components/optics/types.ts"
    },
    {
      "path": "components/optics/glUtils.ts",
      "content": "/* Design tokens & architecture sourced from The Chromologium (https://chromologium.com) */\n/**\n * The Chromologium — Minimal WebGL2 Utilities\n *\n * Just enough plumbing for the optical instruments: program compilation with\n * readable error surfacing, a shared fullscreen-triangle vertex stage, and a\n * uniform uploader that accepts the plain records instruments produce.\n */\n\nexport const FULLSCREEN_VS = `#version 300 es\nprecision highp float;\nout vec2 v_uv;\nvoid main() {\n  // Fullscreen triangle — no buffers needed.\n  vec2 pos = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);\n  v_uv = pos;\n  gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);\n}`;\n\nexport function compileShader(\n  gl: WebGL2RenderingContext,\n  type: number,\n  source: string,\n): WebGLShader {\n  const shader = gl.createShader(type);\n  if (!shader) throw new Error(\"optics: createShader failed\");\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    const log = gl.getShaderInfoLog(shader);\n    gl.deleteShader(shader);\n    throw new Error(`optics: shader compile failed — ${log}`);\n  }\n  return shader;\n}\n\nexport function createProgram(\n  gl: WebGL2RenderingContext,\n  fragmentSource: string,\n  vertexSource: string = FULLSCREEN_VS,\n): WebGLProgram {\n  const vs = compileShader(gl, gl.VERTEX_SHADER, vertexSource);\n  const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n  const program = gl.createProgram();\n  if (!program) throw new Error(\"optics: createProgram failed\");\n  gl.attachShader(program, vs);\n  gl.attachShader(program, fs);\n  gl.linkProgram(program);\n  gl.deleteShader(vs);\n  gl.deleteShader(fs);\n  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n    const log = gl.getProgramInfoLog(program);\n    gl.deleteProgram(program);\n    throw new Error(`optics: program link failed — ${log}`);\n  }\n  return program;\n}\n\nexport function drawFullscreen(gl: WebGL2RenderingContext): void {\n  gl.drawArrays(gl.TRIANGLES, 0, 3);\n}\n\n/**\n * Uploads a record of numbers / vec2 / vec3 / vec4 values by uniform name.\n * Locations are memoized per program in the provided map.\n */\nexport function setUniforms(\n  gl: WebGL2RenderingContext,\n  program: WebGLProgram,\n  locations: Map<string, WebGLUniformLocation | null>,\n  values: Record<string, number | number[]>,\n): void {\n  for (const [name, value] of Object.entries(values)) {\n    let loc = locations.get(name);\n    if (loc === undefined) {\n      loc = gl.getUniformLocation(program, name);\n      locations.set(name, loc);\n    }\n    if (loc === null) continue;\n    if (typeof value === \"number\") {\n      gl.uniform1f(loc, value);\n    } else if (value.length === 2) {\n      gl.uniform2f(loc, value[0], value[1]);\n    } else if (value.length === 3) {\n      gl.uniform3f(loc, value[0], value[1], value[2]);\n    } else if (value.length === 4) {\n      gl.uniform4f(loc, value[0], value[1], value[2], value[3]);\n    }\n  }\n}\n\nexport interface PingPongTarget {\n  framebuffers: [WebGLFramebuffer, WebGLFramebuffer];\n  textures: [WebGLTexture, WebGLTexture];\n  /** Index of the texture currently holding state (read side). */\n  read: number;\n  width: number;\n  height: number;\n}\n\n/** Allocates a pair of float-friendly render targets for simulation state. */\nexport function createPingPong(\n  gl: WebGL2RenderingContext,\n  width: number,\n  height: number,\n): PingPongTarget {\n  const useFloat = !!gl.getExtension(\"EXT_color_buffer_float\");\n  const framebuffers: WebGLFramebuffer[] = [];\n  const textures: WebGLTexture[] = [];\n  for (let i = 0; i < 2; i++) {\n    const tex = gl.createTexture();\n    if (!tex) throw new Error(\"optics: createTexture failed\");\n    gl.bindTexture(gl.TEXTURE_2D, tex);\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    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    if (useFloat) {\n      gl.texImage2D(\n        gl.TEXTURE_2D,\n        0,\n        gl.RGBA16F,\n        width,\n        height,\n        0,\n        gl.RGBA,\n        gl.HALF_FLOAT,\n        null,\n      );\n    } else {\n      gl.texImage2D(\n        gl.TEXTURE_2D,\n        0,\n        gl.RGBA8,\n        width,\n        height,\n        0,\n        gl.RGBA,\n        gl.UNSIGNED_BYTE,\n        null,\n      );\n    }\n    const fbo = gl.createFramebuffer();\n    if (!fbo) throw new Error(\"optics: createFramebuffer failed\");\n    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\n    gl.framebufferTexture2D(\n      gl.FRAMEBUFFER,\n      gl.COLOR_ATTACHMENT0,\n      gl.TEXTURE_2D,\n      tex,\n      0,\n    );\n    gl.clearColor(0, 0, 0, 0);\n    gl.clear(gl.COLOR_BUFFER_BIT);\n    textures.push(tex);\n    framebuffers.push(fbo);\n  }\n  gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n  gl.bindTexture(gl.TEXTURE_2D, null);\n  return {\n    framebuffers: framebuffers as [WebGLFramebuffer, WebGLFramebuffer],\n    textures: textures as [WebGLTexture, WebGLTexture],\n    read: 0,\n    width,\n    height,\n  };\n}\n\nexport function disposePingPong(\n  gl: WebGL2RenderingContext,\n  target: PingPongTarget,\n): void {\n  for (const fbo of target.framebuffers) gl.deleteFramebuffer(fbo);\n  for (const tex of target.textures) gl.deleteTexture(tex);\n}\n",
      "type": "registry:lib",
      "target": "components/optics/glUtils.ts"
    },
    {
      "path": "components/optics/opticalEngine.ts",
      "content": "/* Design tokens & architecture sourced from The Chromologium (https://chromologium.com) */\n/**\n * The Chromologium — Shared Optical Engine\n *\n * One WebGL2 context on a detached canvas serves every optical instrument on\n * the page. Instruments register a host element and a blit-target 2D canvas;\n * the engine renders each visible surface into a scissored viewport and blits\n * the region out with `drawImage` in the same frame, so the page keeps its\n * normal compositing (backdrop-filters, blend modes) and the browser's\n * WebGL-context cap is never approached.\n *\n * Degradation ladder (all permanent for the session, logged once):\n * - context creation fails or `webglcontextlost` fires → every surface\n *   returns to its static CSS state\n * - rolling render cost > 20 ms → engine disables itself\n * - rolling render cost > 12 ms → backing resolution halves\n * The loop suspends entirely when the tab is hidden or no surface intersects\n * the viewport.\n */\n\nimport type { OpticalTier } from \"@/lib/opticalPreference\";\nimport { invalidateShaderTokens } from \"@/lib/shaderTokens\";\nimport type {\n  FrameInfo,\n  OpticalRenderer,\n  OpticalSurfaceState,\n  RegisterOptions,\n} from \"./types\";\n\ninterface EngineSurface extends OpticalSurfaceState {\n  renderer: OpticalRenderer;\n  visible: boolean;\n  initialized: boolean;\n  needsReinit: boolean;\n  readyFired: boolean;\n  onReady?: () => void;\n  onStatic?: () => void;\n  detachPointer?: () => void;\n}\n\nconst FRAME_WINDOW = 30;\nconst DEGRADE_MS = 12;\nconst DISABLE_MS = 20;\nconst MAX_DIMENSION = 2048;\n\nlet singleton: OpticalEngine | null = null;\n\nexport class OpticalEngine {\n  static get(): OpticalEngine {\n    if (!singleton) singleton = new OpticalEngine();\n    return singleton;\n  }\n\n  private glCanvas: HTMLCanvasElement | null = null;\n  private gl: WebGL2RenderingContext | null = null;\n  private surfaces = new Map<number, EngineSurface>();\n  private nextId = 1;\n  private disabled = false;\n  private resolutionScale = 1;\n  private tier: OpticalTier = \"full\";\n  private rafId: number | null = null;\n  private startTime = 0;\n  private lastTime = 0;\n  private frameCosts: number[] = [];\n  private intersection: IntersectionObserver | null = null;\n  private resize: ResizeObserver | null = null;\n  private tokenObserver: MutationObserver | null = null;\n  private hostToId = new WeakMap<Element, number>();\n\n  register(options: RegisterOptions): () => void {\n    if (this.disabled) {\n      options.onStatic?.();\n      return () => {};\n    }\n    this.tier = options.tier;\n    if (!this.ensureContext()) {\n      options.onStatic?.();\n      return () => {};\n    }\n\n    const ctx2d = options.canvas.getContext(\"2d\");\n    if (!ctx2d) {\n      options.onStatic?.();\n      return () => {};\n    }\n\n    const id = this.nextId++;\n    const surface: EngineSurface = {\n      id,\n      host: options.host,\n      canvas: options.canvas,\n      ctx2d,\n      width: 0,\n      height: 0,\n      cssWidth: 0,\n      cssHeight: 0,\n      pointer: { x: 0, y: 0, vx: 0, vy: 0, down: false, inside: false },\n      getUniforms: options.getUniforms,\n      store: new Map(),\n      renderer: options.createRenderer(),\n      visible: false,\n      initialized: false,\n      needsReinit: false,\n      readyFired: false,\n      onReady: options.onReady,\n      onStatic: options.onStatic,\n    };\n\n    this.sizeSurface(surface);\n    if (options.interactive) this.attachPointer(surface);\n\n    this.surfaces.set(id, surface);\n    this.hostToId.set(options.host, id);\n    this.observers().intersection.observe(options.host);\n    this.observers().resize.observe(options.host);\n\n    return () => this.unregister(id);\n  }\n\n  updateTier(tier: OpticalTier): void {\n    if (tier !== \"static\") this.tier = tier;\n  }\n\n  /**\n   * A read-only view of what the engine is actually doing, so the doctrine\n   * can be published rather than merely asserted. See /telemetry.\n   */\n  stats(): {\n    contexts: number;\n    surfaces: number;\n    visible: number;\n    disabled: number;\n    resolutionScale: number;\n    tier: OpticalTier;\n    meanFrameMs: number | null;\n    running: boolean;\n  } {\n    let visible = 0;\n    for (const s of this.surfaces.values()) if (s.visible) visible++;\n    const mean =\n      this.frameCosts.length > 0\n        ? this.frameCosts.reduce((a, b) => a + b, 0) / this.frameCosts.length\n        : null;\n    return {\n      contexts: this.gl ? 1 : 0,\n      surfaces: this.surfaces.size,\n      visible,\n      disabled: this.disabled ? 1 : 0,\n      resolutionScale: this.resolutionScale,\n      tier: this.tier,\n      meanFrameMs: mean,\n      running: this.rafId !== null,\n    };\n  }\n\n  /** True once the engine has permanently retired for this session. */\n  isDisabled(): boolean {\n    return this.disabled;\n  }\n\n  private unregister(id: number): void {\n    const surface = this.surfaces.get(id);\n    if (!surface) return;\n    this.surfaces.delete(id);\n    this.intersection?.unobserve(surface.host);\n    this.resize?.unobserve(surface.host);\n    surface.detachPointer?.();\n    if (this.gl && surface.initialized) {\n      try {\n        surface.renderer.dispose(this.gl, surface);\n      } catch {\n        // context may already be lost\n      }\n    }\n    if (this.surfaces.size === 0) this.stopLoop();\n  }\n\n  private ensureContext(): boolean {\n    if (this.gl) return true;\n    if (this.disabled) return false;\n    const canvas = document.createElement(\"canvas\");\n    const gl = canvas.getContext(\"webgl2\", {\n      alpha: true,\n      antialias: false,\n      depth: false,\n      stencil: false,\n      premultipliedAlpha: true,\n      powerPreference: \"low-power\",\n    });\n    if (!gl) {\n      this.degrade(\"webgl2-unavailable\");\n      return false;\n    }\n    canvas.addEventListener(\"webglcontextlost\", () => {\n      // Deliberate: no restore attempt. Static CSS is the ordinary path.\n      this.degrade(\"context-lost\");\n    });\n    this.glCanvas = canvas;\n    this.gl = gl;\n\n    this.tokenObserver = new MutationObserver(() => invalidateShaderTokens());\n    this.tokenObserver.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: [\"data-movement\", \"data-mode\", \"data-sheen\", \"style\"],\n    });\n    document.addEventListener(\"visibilitychange\", this.onVisibility);\n    return true;\n  }\n\n  private onVisibility = (): void => {\n    if (document.hidden) this.stopLoop();\n    else this.kickLoop();\n  };\n\n  private observers(): {\n    intersection: IntersectionObserver;\n    resize: ResizeObserver;\n  } {\n    if (!this.intersection) {\n      this.intersection = new IntersectionObserver(\n        (entries) => {\n          for (const entry of entries) {\n            const id = this.hostToId.get(entry.target);\n            const surface =\n              id !== undefined ? this.surfaces.get(id) : undefined;\n            if (surface) surface.visible = entry.isIntersecting;\n          }\n          this.kickLoop();\n        },\n        { rootMargin: \"64px\" },\n      );\n    }\n    if (!this.resize) {\n      this.resize = new ResizeObserver((entries) => {\n        for (const entry of entries) {\n          const id = this.hostToId.get(entry.target);\n          const surface = id !== undefined ? this.surfaces.get(id) : undefined;\n          if (surface) {\n            this.sizeSurface(surface);\n            if (surface.renderer.resizeReinit && surface.initialized) {\n              surface.needsReinit = true;\n            }\n          }\n        }\n        this.kickLoop();\n      });\n    }\n    return { intersection: this.intersection, resize: this.resize };\n  }\n\n  private devicePixelScale(): number {\n    const dprCap = this.tier === \"reduced\" ? 1 : 2;\n    return (\n      Math.min(window.devicePixelRatio || 1, dprCap) * this.resolutionScale\n    );\n  }\n\n  private sizeSurface(surface: EngineSurface): void {\n    const rect = surface.host.getBoundingClientRect();\n    surface.cssWidth = Math.max(1, Math.round(rect.width));\n    surface.cssHeight = Math.max(1, Math.round(rect.height));\n    const scale = this.devicePixelScale();\n    surface.width = Math.min(\n      MAX_DIMENSION,\n      Math.max(1, Math.round(surface.cssWidth * scale)),\n    );\n    surface.height = Math.min(\n      MAX_DIMENSION,\n      Math.max(1, Math.round(surface.cssHeight * scale)),\n    );\n    if (surface.canvas.width !== surface.width) {\n      surface.canvas.width = surface.width;\n    }\n    if (surface.canvas.height !== surface.height) {\n      surface.canvas.height = surface.height;\n    }\n  }\n\n  private attachPointer(surface: EngineSurface): void {\n    const host = surface.host;\n    let lastX = 0;\n    let lastY = 0;\n    let lastT = 0;\n\n    const onMove = (event: PointerEvent) => {\n      const rect = host.getBoundingClientRect();\n      const x = event.clientX - rect.left;\n      const y = event.clientY - rect.top;\n      const now = performance.now();\n      const dt = Math.max(1, now - lastT) / 1000;\n      const p = surface.pointer;\n      if (p.inside) {\n        // Exponential smoothing keeps flick velocities usable.\n        p.vx = p.vx * 0.7 + ((x - lastX) / dt) * 0.3;\n        p.vy = p.vy * 0.7 + ((y - lastY) / dt) * 0.3;\n      }\n      p.x = x;\n      p.y = y;\n      p.inside = true;\n      lastX = x;\n      lastY = y;\n      lastT = now;\n      this.kickLoop();\n    };\n    const onLeave = () => {\n      surface.pointer.inside = false;\n      surface.pointer.vx = 0;\n      surface.pointer.vy = 0;\n    };\n    const onDown = (event: PointerEvent) => {\n      surface.pointer.down = true;\n      onMove(event);\n    };\n    const onUp = () => {\n      surface.pointer.down = false;\n    };\n\n    host.addEventListener(\"pointermove\", onMove);\n    host.addEventListener(\"pointerdown\", onDown);\n    host.addEventListener(\"pointerleave\", onLeave);\n    window.addEventListener(\"pointerup\", onUp);\n    surface.detachPointer = () => {\n      host.removeEventListener(\"pointermove\", onMove);\n      host.removeEventListener(\"pointerdown\", onDown);\n      host.removeEventListener(\"pointerleave\", onLeave);\n      window.removeEventListener(\"pointerup\", onUp);\n    };\n  }\n\n  private kickLoop(): void {\n    if (this.disabled || this.rafId !== null || document.hidden) return;\n    let anyVisible = false;\n    for (const surface of this.surfaces.values()) {\n      if (surface.visible) {\n        anyVisible = true;\n        break;\n      }\n    }\n    if (!anyVisible) return;\n    if (this.startTime === 0) {\n      this.startTime = performance.now();\n      this.lastTime = this.startTime;\n    }\n    this.rafId = requestAnimationFrame(this.frame);\n  }\n\n  private stopLoop(): void {\n    if (this.rafId !== null) {\n      cancelAnimationFrame(this.rafId);\n      this.rafId = null;\n    }\n  }\n\n  private frame = (): void => {\n    this.rafId = null;\n    if (this.disabled) return;\n    const gl = this.gl;\n    const glCanvas = this.glCanvas;\n    if (!gl || !glCanvas) return;\n\n    const now = performance.now();\n    const frame: FrameInfo = {\n      t: (now - this.startTime) / 1000,\n      dt: Math.min(0.1, (now - this.lastTime) / 1000),\n      tier: this.tier,\n    };\n    this.lastTime = now;\n\n    // Size the shared canvas to the largest visible surface.\n    let maxW = 1;\n    let maxH = 1;\n    let anyVisible = false;\n    for (const surface of this.surfaces.values()) {\n      if (!surface.visible) continue;\n      anyVisible = true;\n      if (surface.width > maxW) maxW = surface.width;\n      if (surface.height > maxH) maxH = surface.height;\n    }\n    if (!anyVisible) return;\n    if (glCanvas.width < maxW) glCanvas.width = maxW;\n    if (glCanvas.height < maxH) glCanvas.height = maxH;\n\n    const cost0 = performance.now();\n    for (const surface of this.surfaces.values()) {\n      if (!surface.visible) continue;\n      try {\n        if (surface.needsReinit) {\n          surface.renderer.dispose(gl, surface);\n          surface.initialized = false;\n          surface.needsReinit = false;\n        }\n        if (!surface.initialized) {\n          surface.renderer.init(gl, surface);\n          surface.initialized = true;\n        }\n        const { width: w, height: h } = surface;\n        const vy = glCanvas.height - h;\n        gl.viewport(0, vy, w, h);\n        gl.enable(gl.SCISSOR_TEST);\n        gl.scissor(0, vy, w, h);\n        gl.clearColor(0, 0, 0, 0);\n        gl.clear(gl.COLOR_BUFFER_BIT);\n        // Scissor is only needed for the clear; fullscreen draws are\n        // confined by the viewport, and multi-pass renderers re-bind\n        // their own FBO viewports.\n        gl.disable(gl.SCISSOR_TEST);\n        frame.viewport = [0, vy, w, h];\n        surface.renderer.render(gl, surface, frame);\n\n        surface.ctx2d.clearRect(0, 0, w, h);\n        surface.ctx2d.drawImage(glCanvas, 0, 0, w, h, 0, 0, w, h);\n        if (!surface.readyFired) {\n          surface.readyFired = true;\n          surface.onReady?.();\n        }\n      } catch (error) {\n        // A single faulty instrument never takes the page down.\n        this.dropSurface(surface, error);\n      }\n    }\n\n    this.trackCost(performance.now() - cost0);\n    if (!this.disabled) this.kickLoop();\n  };\n\n  private trackCost(ms: number): void {\n    this.frameCosts.push(ms);\n    if (this.frameCosts.length < FRAME_WINDOW) return;\n    if (this.frameCosts.length > FRAME_WINDOW) this.frameCosts.shift();\n    const mean =\n      this.frameCosts.reduce((sum, v) => sum + v, 0) / this.frameCosts.length;\n    if (mean > DISABLE_MS) {\n      this.degrade(`frame-budget ${mean.toFixed(1)}ms`);\n    } else if (mean > DEGRADE_MS && this.resolutionScale > 0.5) {\n      this.resolutionScale = 0.5;\n      this.frameCosts = [];\n      for (const surface of this.surfaces.values()) {\n        this.sizeSurface(surface);\n        if (surface.renderer.resizeReinit && surface.initialized) {\n          surface.needsReinit = true;\n        }\n      }\n      console.warn(\n        \"[chromologium optics] frame budget pressure — halving resolution\",\n      );\n    }\n  }\n\n  private dropSurface(surface: EngineSurface, error: unknown): void {\n    console.warn(\n      \"[chromologium optics] instrument degraded to static CSS:\",\n      error,\n    );\n    this.unregister(surface.id);\n    surface.onStatic?.();\n  }\n\n  private degrade(reason: string): void {\n    if (this.disabled) return;\n    this.disabled = true;\n    console.warn(\n      `[chromologium optics] engine disabled (${reason}) — static CSS state active`,\n    );\n    this.stopLoop();\n    const gl = this.gl;\n    for (const surface of this.surfaces.values()) {\n      surface.detachPointer?.();\n      if (gl && surface.initialized) {\n        try {\n          surface.renderer.dispose(gl, surface);\n        } catch {\n          // context lost — nothing to free\n        }\n      }\n      surface.onStatic?.();\n    }\n    this.surfaces.clear();\n    this.intersection?.disconnect();\n    this.resize?.disconnect();\n    this.tokenObserver?.disconnect();\n    document.removeEventListener(\"visibilitychange\", this.onVisibility);\n    this.gl = null;\n    this.glCanvas = null;\n  }\n}\n",
      "type": "registry:lib",
      "target": "components/optics/opticalEngine.ts"
    },
    {
      "path": "components/optics/useOpticalSurface.ts",
      "content": "/* Design tokens & architecture sourced from The Chromologium (https://chromologium.com) */\n\"use client\";\n\n/**\n * The Chromologium — Optical Surface Hook\n *\n * The single way an instrument joins the shared optical engine. The engine\n * module is pulled in with a dynamic import inside the effect, so no GL code\n * reaches the initial bundle; under the `static` tier the import never runs\n * at all and the instrument stays on its CSS state.\n *\n * Usage:\n *   const { hostRef, canvasRef, ready } = useOpticalSurface({ createRenderer });\n *   <div ref={hostRef} className=\"optical-host\">\n *     {…static CSS state…}\n *     <canvas ref={canvasRef} className={ready ? \"optical-surface-canvas is-ready\" : \"optical-surface-canvas\"} aria-hidden />\n *   </div>\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useMovement } from \"../MovementProvider\";\nimport type { OpticalRenderer } from \"./types\";\n\nexport interface UseOpticalSurfaceOptions {\n  /** Stable factory — called once per registration. */\n  createRenderer: () => OpticalRenderer;\n  /** Read every frame; return instrument-specific uniform values. */\n  getUniforms?: () => Record<string, number | number[]>;\n  /** Track pointer position/velocity on the host element. */\n  interactive?: boolean;\n  /** Skip registration entirely (e.g. an instrument's own off switch). */\n  enabled?: boolean;\n}\n\nexport function useOpticalSurface(options: UseOpticalSurfaceOptions): {\n  hostRef: React.RefObject<HTMLDivElement | null>;\n  canvasRef: React.RefObject<HTMLCanvasElement | null>;\n  /** True once the first frame has been blitted — drives the fade-in. */\n  ready: boolean;\n  /** True while the surface is registered with a live engine. */\n  active: boolean;\n} {\n  const { opticalTier } = useMovement();\n  const hostRef = useRef<HTMLDivElement | null>(null);\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n  const [ready, setReady] = useState(false);\n  const [active, setActive] = useState(false);\n\n  // Options travel through a ref so identity churn in callers never\n  // re-registers the surface.\n  const optionsRef = useRef(options);\n  optionsRef.current = options;\n\n  const enabled = options.enabled !== false;\n\n  useEffect(() => {\n    if (opticalTier === \"static\" || !enabled) {\n      setReady(false);\n      setActive(false);\n      return;\n    }\n    const host = hostRef.current;\n    const canvas = canvasRef.current;\n    if (!host || !canvas) return;\n\n    let disposed = false;\n    let dispose: (() => void) | undefined;\n\n    import(\"./opticalEngine\").then(({ OpticalEngine }) => {\n      if (disposed) return;\n      const engine = OpticalEngine.get();\n      engine.updateTier(opticalTier);\n      // register() may invoke onStatic synchronously (no WebGL2, engine\n      // already degraded) — never mark the surface active in that case.\n      let degradedDuringRegister = false;\n      dispose = engine.register({\n        host,\n        canvas,\n        tier: opticalTier,\n        createRenderer: optionsRef.current.createRenderer,\n        getUniforms: () => optionsRef.current.getUniforms?.() ?? {},\n        interactive: optionsRef.current.interactive,\n        onReady: () => {\n          if (!disposed) setReady(true);\n        },\n        onStatic: () => {\n          degradedDuringRegister = true;\n          if (!disposed) {\n            setReady(false);\n            setActive(false);\n          }\n        },\n      });\n      if (!degradedDuringRegister) setActive(true);\n    });\n\n    return () => {\n      disposed = true;\n      dispose?.();\n      setReady(false);\n      setActive(false);\n    };\n  }, [opticalTier, enabled]);\n\n  return { hostRef, canvasRef, ready, active };\n}\n",
      "type": "registry:lib",
      "target": "components/optics/useOpticalSurface.ts"
    },
    {
      "path": "lib/shaderTokens.ts",
      "content": "/* Design tokens & architecture sourced from The Chromologium (https://chromologium.com) */\n/**\n * The Chromologium — Design Token → Shader Uniform Bridge\n *\n * Reads live `--ds-*` custom properties off `documentElement`, parses OKLCH /\n * hex / rgb() color strings, and returns sRGB or linear-light triples ready to\n * upload as GL uniforms. Reads are memoized; the optical engine calls\n * `invalidateShaderTokens()` from a MutationObserver watching the\n * data-movement / data-mode / data-sheen attributes so shaders never render a\n * stale movement.\n */\n\nexport type Rgb = [number, number, number];\n\nconst cache = new Map<string, Rgb>();\n\nexport function invalidateShaderTokens(): void {\n  cache.clear();\n}\n\n/** OKLab → linear sRGB (Björn Ottosson's reference matrices). */\nfunction oklabToLinearSrgb(L: number, a: number, b: number): Rgb {\n  const l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n  const m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n  const s_ = L - 0.0894841775 * a - 1.291485548 * b;\n  const l = l_ * l_ * l_;\n  const m = m_ * m_ * m_;\n  const s = s_ * s_ * s_;\n  return [\n    4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n    -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n    -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,\n  ];\n}\n\nfunction clamp01(v: number): number {\n  return v < 0 ? 0 : v > 1 ? 1 : v;\n}\n\nfunction linearToSrgbChannel(v: number): number {\n  const c = clamp01(v);\n  return c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;\n}\n\nfunction srgbToLinearChannel(v: number): number {\n  const c = clamp01(v);\n  return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n}\n\nfunction parseOklch(str: string): Rgb | null {\n  const m =\n    /oklch\\(\\s*([\\d.]+%?)\\s+([\\d.]+)\\s+([\\d.]+)(?:deg)?\\s*(?:\\/[^)]*)?\\)/i.exec(\n      str,\n    );\n  if (!m) return null;\n  let L = parseFloat(m[1]);\n  if (m[1].endsWith(\"%\")) L /= 100;\n  const C = parseFloat(m[2]);\n  const H = (parseFloat(m[3]) * Math.PI) / 180;\n  const linear = oklabToLinearSrgb(L, C * Math.cos(H), C * Math.sin(H));\n  return [\n    linearToSrgbChannel(linear[0]),\n    linearToSrgbChannel(linear[1]),\n    linearToSrgbChannel(linear[2]),\n  ];\n}\n\nfunction parseHex(str: string): Rgb | null {\n  const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(str.trim());\n  if (!m) return null;\n  const hex = m[1];\n  if (hex.length === 3) {\n    return [\n      parseInt(hex[0] + hex[0], 16) / 255,\n      parseInt(hex[1] + hex[1], 16) / 255,\n      parseInt(hex[2] + hex[2], 16) / 255,\n    ];\n  }\n  return [\n    parseInt(hex.slice(0, 2), 16) / 255,\n    parseInt(hex.slice(2, 4), 16) / 255,\n    parseInt(hex.slice(4, 6), 16) / 255,\n  ];\n}\n\nfunction parseRgbFunc(str: string): Rgb | null {\n  const m = /rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/i.exec(str);\n  if (!m) return null;\n  return [\n    parseFloat(m[1]) / 255,\n    parseFloat(m[2]) / 255,\n    parseFloat(m[3]) / 255,\n  ];\n}\n\nlet fallbackCtx: CanvasRenderingContext2D | null | undefined;\n\n/** Last-resort parse via the 2D canvas fillStyle normalizer (color-mix etc.). */\nfunction parseViaCanvas(str: string): Rgb | null {\n  if (fallbackCtx === undefined) {\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = 1;\n    canvas.height = 1;\n    fallbackCtx = canvas.getContext(\"2d\", { willReadFrequently: true });\n  }\n  if (!fallbackCtx) return null;\n  try {\n    fallbackCtx.fillStyle = \"#000\";\n    fallbackCtx.fillStyle = str;\n    fallbackCtx.clearRect(0, 0, 1, 1);\n    fallbackCtx.fillRect(0, 0, 1, 1);\n    const d = fallbackCtx.getImageData(0, 0, 1, 1).data;\n    return [d[0] / 255, d[1] / 255, d[2] / 255];\n  } catch {\n    return null;\n  }\n}\n\nexport function parseCssColor(str: string): Rgb | null {\n  const s = str.trim();\n  if (!s) return null;\n  return parseHex(s) ?? parseOklch(s) ?? parseRgbFunc(s) ?? parseViaCanvas(s);\n}\n\n/**\n * Reads a custom property (e.g. `--ds-accent`) from documentElement and\n * returns its gamma-encoded sRGB triple in 0–1. Memoized until invalidated.\n */\nexport function readTokenSrgb(\n  name: string,\n  fallback: Rgb = [0.5, 0.5, 0.5],\n): Rgb {\n  const cached = cache.get(name);\n  if (cached) return cached;\n  if (typeof window === \"undefined\") return fallback;\n  const raw = getComputedStyle(document.documentElement).getPropertyValue(name);\n  const parsed = parseCssColor(raw) ?? fallback;\n  cache.set(name, parsed);\n  return parsed;\n}\n\n/** Same as `readTokenSrgb` but linearized for physically-based shading math. */\nexport function readTokenLinear(name: string, fallback?: Rgb): Rgb {\n  const srgb = readTokenSrgb(name, fallback);\n  return [\n    srgbToLinearChannel(srgb[0]),\n    srgbToLinearChannel(srgb[1]),\n    srgbToLinearChannel(srgb[2]),\n  ];\n}\n",
      "type": "registry:lib",
      "target": "lib/shaderTokens.ts"
    },
    {
      "path": "lib/opticalPreference.ts",
      "content": "/* Design tokens & architecture sourced from The Chromologium (https://chromologium.com) */\n/**\n * The Chromologium — Optical Instrument Preference Spine\n *\n * Resolves whether GPU shader instruments may run, and at what fidelity,\n * from four signals combined in strict priority order:\n *\n * 1. `prefers-reduced-motion: reduce` → always `\"static\"`. The OS-level\n *    accessibility contract outranks every site-local switch, including an\n *    explicit \"on\".\n * 2. The visitor's explicit instrument switch (persisted by MovementProvider\n *    alongside density and sheen): \"off\" → `\"static\"`, \"on\" → skips the\n *    device heuristics, \"auto\" → applies them.\n * 3. `Save-Data` request hint → `\"static\"` under \"auto\".\n * 4. Device tier (`deviceMemory`, `hardwareConcurrency`) → `\"reduced\"`\n *    (half-resolution rendering) on constrained hardware under \"auto\".\n *\n * The resolved tier is stamped on `documentElement` as `data-optics` so both\n * CSS and the optical engine read one source of truth.\n */\n\nexport type OpticalMode = \"auto\" | \"on\" | \"off\";\n\nexport type OpticalTier = \"full\" | \"reduced\" | \"static\";\n\nexport const OPTICS_STORAGE_KEY = \"chromologium-optics\";\n\nconst REDUCED_MOTION_QUERY = \"(prefers-reduced-motion: reduce)\";\n\ninterface NetworkInformationLike {\n  saveData?: boolean;\n}\n\ninterface NavigatorLike extends Navigator {\n  connection?: NetworkInformationLike;\n  deviceMemory?: number;\n}\n\nexport function prefersReducedMotion(): boolean {\n  if (typeof window === \"undefined\" || !window.matchMedia) return false;\n  return window.matchMedia(REDUCED_MOTION_QUERY).matches;\n}\n\nexport function resolveOpticalTier(mode: OpticalMode): OpticalTier {\n  if (typeof window === \"undefined\") return \"static\";\n  if (prefersReducedMotion()) return \"static\";\n  if (mode === \"off\") return \"static\";\n  if (mode === \"on\") return \"full\";\n\n  // mode === \"auto\": consult device heuristics\n  const nav = navigator as NavigatorLike;\n  if (nav.connection?.saveData) return \"static\";\n\n  const memory = nav.deviceMemory;\n  const cores = nav.hardwareConcurrency;\n  if (\n    (memory !== undefined && memory <= 4) ||\n    (cores !== undefined && cores <= 4)\n  ) {\n    return \"reduced\";\n  }\n  return \"full\";\n}\n\n/**\n * Subscribes to the reduced-motion media query so the tier re-resolves live\n * when the OS preference flips. Returns an unsubscribe function.\n */\nexport function watchReducedMotion(onChange: () => void): () => void {\n  if (typeof window === \"undefined\" || !window.matchMedia) return () => {};\n  const query = window.matchMedia(REDUCED_MOTION_QUERY);\n  query.addEventListener(\"change\", onChange);\n  return () => query.removeEventListener(\"change\", onChange);\n}\n\nexport function readStoredOpticalMode(): OpticalMode {\n  if (typeof window === \"undefined\") return \"auto\";\n  const stored = localStorage.getItem(OPTICS_STORAGE_KEY);\n  return stored === \"on\" || stored === \"off\" ? stored : \"auto\";\n}\n",
      "type": "registry:lib",
      "target": "lib/opticalPreference.ts"
    },
    {
      "path": "lib/materialOptics.ts",
      "content": "/* Design tokens & architecture sourced from The Chromologium (https://chromologium.com) */\n/**\n * The Chromologium — Shared Material Optics Schema\n *\n * Numeric, shader-ready optical coefficients for every physical substrate\n * catalogued across the archive: Atlas sovereign entries carry explicit\n * per-entry blocks in `src/content/atlas.ts`, while System dossier material\n * substrates and Paint Chip finishes bind through the authored tables below,\n * so every optical instrument reads one uniform contract.\n *\n * Field conventions:\n * - scatterDepth · fraction of incident light re-emitted from beneath the\n *                  surface (0 = fully opaque, 1 = deep translucency)\n * - roughness    · micro-facet RMS roughness (0 = optically polished, 1 = raw)\n * - anisotropy   · directional grain of the specular lobe (0 = isotropic,\n *                  1 = fully brushed / woven)\n * - metalness    · dielectric-to-conductor blend (0 = dielectric, 1 = metal)\n * - ior          · index of refraction at 589 nm (sodium D line); for\n *                  metal-dominant substrates this describes the dielectric\n *                  coat or matrix, with reflectance carried by `metalness`\n * - patinaRate   · fraction of the surface acquiring visible patina per\n *                  simulated century (0–1)\n */\n\nexport interface MaterialOptics {\n  scatterDepth: number;\n  roughness: number;\n  anisotropy: number;\n  metalness: number;\n  ior: number;\n  patinaRate: number;\n}\n\n/**\n * Optical coefficients for the four archival paint-chip finishes\n * (`PaintChipPalette[\"finish\"]` in `src/content/paintChips.ts`).\n * Keys must remain in exact parity with that union.\n */\nexport const FINISH_OPTICS: Record<\n  \"Velvet Matte\" | \"Eggshell\" | \"High Gloss Lacquer\" | \"Chalk Emulsion\",\n  MaterialOptics\n> = {\n  \"Velvet Matte\": {\n    scatterDepth: 0.3,\n    roughness: 0.85,\n    anisotropy: 0.1,\n    metalness: 0,\n    ior: 1.5,\n    patinaRate: 0.2,\n  },\n  Eggshell: {\n    scatterDepth: 0.25,\n    roughness: 0.55,\n    anisotropy: 0.05,\n    metalness: 0,\n    ior: 1.52,\n    patinaRate: 0.16,\n  },\n  \"High Gloss Lacquer\": {\n    scatterDepth: 0.15,\n    roughness: 0.05,\n    anisotropy: 0.02,\n    metalness: 0,\n    ior: 1.62,\n    patinaRate: 0.1,\n  },\n  \"Chalk Emulsion\": {\n    scatterDepth: 0.4,\n    roughness: 0.95,\n    anisotropy: 0.05,\n    metalness: 0,\n    ior: 1.48,\n    patinaRate: 0.3,\n  },\n};\n\n/**\n * Optical coefficients for every System dossier material substrate\n * (`SystemMaterialSubstrate[\"name\"]` in `src/content/systems.ts`).\n * Values are authored per substrate against published optical references\n * (keratin fibre n≈1.55, borosilicate n≈1.47, obsidian n≈1.49, burnished\n * tadelakt lime n≈1.54), not derived heuristically.\n */\nexport const SUBSTRATE_OPTICS: Record<string, MaterialOptics> = {\n  \"Sartorial Super 150s Merino Wool\": {\n    scatterDepth: 0.45,\n    roughness: 0.9,\n    anisotropy: 0.6,\n    metalness: 0,\n    ior: 1.55,\n    patinaRate: 0.15,\n  },\n  \"Hand-Polished Copper Intaglio Plate\": {\n    scatterDepth: 0.02,\n    roughness: 0.06,\n    anisotropy: 0.15,\n    metalness: 0.95,\n    ior: 1.5,\n    patinaRate: 0.45,\n  },\n  \"Obsidian Architectural Mirror Glass\": {\n    scatterDepth: 0.05,\n    roughness: 0.02,\n    anisotropy: 0,\n    metalness: 0,\n    ior: 1.49,\n    patinaRate: 0.03,\n  },\n  \"Fluted Borosilicate Architectural Glass\": {\n    scatterDepth: 0.2,\n    roughness: 0.15,\n    anisotropy: 0.8,\n    metalness: 0,\n    ior: 1.47,\n    patinaRate: 0.02,\n  },\n  \"Living Forest Terrarium Bryophyte Moss\": {\n    scatterDepth: 0.65,\n    roughness: 1,\n    anisotropy: 0.2,\n    metalness: 0,\n    ior: 1.36,\n    patinaRate: 0.6,\n  },\n  \"Brushed Architectural Marine Brass\": {\n    scatterDepth: 0.02,\n    roughness: 0.3,\n    anisotropy: 0.85,\n    metalness: 0.9,\n    ior: 1.5,\n    patinaRate: 0.4,\n  },\n  \"Hand-Thrown Unglazed Terracotta Clay\": {\n    scatterDepth: 0.35,\n    roughness: 0.9,\n    anisotropy: 0.05,\n    metalness: 0,\n    ior: 1.55,\n    patinaRate: 0.35,\n  },\n  \"Natural Slaked Lime Plaster (Tadelakt)\": {\n    scatterDepth: 0.45,\n    roughness: 0.25,\n    anisotropy: 0.1,\n    metalness: 0,\n    ior: 1.54,\n    patinaRate: 0.25,\n  },\n  \"Quarter-Sawn White Oak Timber\": {\n    scatterDepth: 0.4,\n    roughness: 0.5,\n    anisotropy: 0.7,\n    metalness: 0,\n    ior: 1.53,\n    patinaRate: 0.28,\n  },\n};\n\n/** Neutral fallback for surfaces without an authored table entry. */\nexport const DEFAULT_OPTICS: MaterialOptics = {\n  scatterDepth: 0.25,\n  roughness: 0.5,\n  anisotropy: 0.1,\n  metalness: 0,\n  ior: 1.52,\n  patinaRate: 0.2,\n};\n\nexport function opticsForFinish(\n  finish: keyof typeof FINISH_OPTICS,\n): MaterialOptics {\n  return FINISH_OPTICS[finish];\n}\n\nexport function opticsForSubstrate(name: string): MaterialOptics {\n  return SUBSTRATE_OPTICS[name] ?? DEFAULT_OPTICS;\n}\n",
      "type": "registry:lib",
      "target": "lib/materialOptics.ts"
    }
  ],
  "docs": "Wrap instruments with useOpticalSurface(). The engine mounts lazily and never runs under prefers-reduced-motion. See https://chromologium.com/monographs/the-instrument-and-the-substrate",
  "author": "The Chromologium",
  "homepage": "https://chromologium.com",
  "license": "MIT"
}
