{"version":3,"sources":["jsdelivr-header.js","/npm/chartgpu@0.3.2/src/core/GPUContext.ts","/npm/chartgpu@0.3.2/src/data/cartesianData.ts","/npm/chartgpu@0.3.2/src/data/createDataStore.ts","/npm/chartgpu@0.3.2/src/data/lttbSample.ts","/npm/chartgpu@0.3.2/src/data/sampleSeries.ts","/npm/chartgpu@0.3.2/src/data/ohlcSample.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/utils/canvasUtils.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/data/computeVisibleSlice.ts","/npm/chartgpu@0.3.2/src/utils/colors.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/utils/dataPointUtils.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/utils/axisUtils.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/utils/timeAxisUtils.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/axis/computeAxisTicks.ts","/npm/chartgpu@0.3.2/src/utils/axisLabelStyling.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/render/renderAxisLabels.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/render/renderAnnotationLabels.ts","/npm/chartgpu@0.3.2/src/interaction/findNearestPoint.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/render/renderOverlays.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/annotations/processAnnotations.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/animation/animationHelpers.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/render/renderSeries.ts","/npm/chartgpu@0.3.2/src/shaders/grid.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/rendererUtils.ts","/npm/chartgpu@0.3.2/src/renderers/createAxisRenderer.ts","/npm/chartgpu@0.3.2/src/renderers/createGridRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/area.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createAreaRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/line.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createLineRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/scatter.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createScatterRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/scatterDensityBinning.wgsl?raw","/npm/chartgpu@0.3.2/src/shaders/scatterDensityColormap.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createScatterDensityRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/pie.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createPieRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/candlestick.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createCandlestickRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/bar.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createBarRenderer.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/renderers/rendererPool.ts","/npm/chartgpu@0.3.2/src/core/renderCoordinator/gpu/textureManager.ts","/npm/chartgpu@0.3.2/src/shaders/crosshair.wgsl?raw","/npm/chartgpu@0.3.2/src/data/createStreamBuffer.ts","/npm/chartgpu@0.3.2/src/renderers/createCrosshairRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/highlight.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createHighlightRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/referenceLine.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createReferenceLineRenderer.ts","/npm/chartgpu@0.3.2/src/shaders/annotationMarker.wgsl?raw","/npm/chartgpu@0.3.2/src/renderers/createAnnotationMarkerRenderer.ts","/npm/chartgpu@0.3.2/src/interaction/createEventManager.ts","/npm/chartgpu@0.3.2/src/interaction/createInsideZoom.ts","/npm/chartgpu@0.3.2/src/interaction/createZoomState.ts","/npm/chartgpu@0.3.2/src/interaction/findPointsAtX.ts","/npm/chartgpu@0.3.2/src/interaction/findCandlestick.ts","/npm/chartgpu@0.3.2/src/interaction/findPieSlice.ts","/npm/chartgpu@0.3.2/src/utils/scales.ts","/npm/chartgpu@0.3.2/src/components/createTextOverlay.ts","/npm/chartgpu@0.3.2/src/components/createLegend.ts","/npm/chartgpu@0.3.2/src/components/createTooltip.ts","/npm/chartgpu@0.3.2/src/components/formatTooltip.ts","/npm/chartgpu@0.3.2/src/core/createAnimationController.ts","/npm/chartgpu@0.3.2/src/utils/easing.ts","/npm/chartgpu@0.3.2/src/core/createRenderCoordinator.ts","/npm/chartgpu@0.3.2/src/config/defaults.ts","/npm/chartgpu@0.3.2/src/themes/darkTheme.ts","/npm/chartgpu@0.3.2/src/themes/lightTheme.ts","/npm/chartgpu@0.3.2/src/themes/index.ts","/npm/chartgpu@0.3.2/src/config/OptionResolver.ts","/npm/chartgpu@0.3.2/src/components/createDataZoomSlider.ts","/npm/chartgpu@0.3.2/src/components/createZoomResetButton.ts","/npm/chartgpu@0.3.2/src/utils/checkWebGPU.ts","/npm/chartgpu@0.3.2/src/ChartGPU.ts","/npm/chartgpu@0.3.2/src/interaction/createChartSync.ts","/npm/chartgpu@0.3.2/src/interaction/createAnnotationHitTester.ts","/npm/chartgpu@0.3.2/src/interaction/createAnnotationDragHandler.ts","/npm/chartgpu@0.3.2/src/components/createAnnotationConfigDialog.ts","/npm/chartgpu@0.3.2/src/interaction/createAnnotationAuthoring.ts","/npm/chartgpu@0.3.2/src/core/RenderScheduler.ts","/npm/chartgpu@0.3.2/src/core/PipelineCache.ts","/npm/chartgpu@0.3.2/src/core/createPipelineCache.ts","/npm/chartgpu@0.3.2/src/index.ts"],"names":[],"mappings":"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","file":"/npm/chartgpu@0.3.2/dist/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/chartgpu@0.3.2/dist/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","/**\n * GPUContext - WebGPU device and adapter management\n * \n * Handles WebGPU initialization, adapter selection, and device creation\n * following WebGPU best practices for resource management and error handling.\n * \n * This module provides both functional and class-based APIs for maximum flexibility.\n */\n\n/** Canvas types supported by GPUContext. */\nexport type SupportedCanvas = HTMLCanvasElement;\n\n// Baseline requirements for ChartGPU when injecting a shared GPUDevice.\n// These are intentionally conservative so ChartGPU fails early with a descriptive error\n// rather than failing later during large uploads/allocations.\nconst MIN_REQUIRED_MAX_BUFFER_SIZE_BYTES = 32 * 1024 * 1024; // 32 MiB\nconst MIN_REQUIRED_MAX_STORAGE_BUFFER_BINDING_SIZE_BYTES = 32 * 1024 * 1024; // 32 MiB\n\n// Internal ownership tracking.\n// AC-2 requires ownership tracking, but this must remain internal (not a public API surface).\nconst ownsDeviceKey: unique symbol = Symbol('GPUContext.ownsDevice');\ntype GPUContextStateInternal = GPUContextState & { readonly [ownsDeviceKey]: boolean };\nconst getOwnsDevice = (context: GPUContextState): boolean => (context as GPUContextStateInternal)[ownsDeviceKey] ?? true;\n\n/** Options for GPU context initialization. */\nexport interface GPUContextOptions {\n  /** DPR for high-DPI displays. Auto-detects from `window.devicePixelRatio`, defaults to 1.0. */\n  readonly devicePixelRatio?: number;\n  /** Canvas alpha mode. Default: 'opaque' (faster, no transparency). */\n  readonly alphaMode?: 'opaque' | 'premultiplied';\n  /** GPU power preference for adapter selection. */\n  readonly powerPreference?: 'low-power' | 'high-performance';\n  /**\n   * Optional WebGPU adapter. When both device and adapter are provided, initialization skips\n   * requestAdapter/requestDevice and uses these objects (shared device mode). The caller owns\n   * the device lifecycle; destroyGPUContext will not call device.destroy().\n   */\n  readonly adapter?: GPUAdapter;\n  /**\n   * Optional WebGPU device. When both device and adapter are provided, initialization skips\n   * requestAdapter/requestDevice and uses these objects (shared device mode). The caller owns\n   * the device lifecycle; destroyGPUContext will not call device.destroy().\n   */\n  readonly device?: GPUDevice;\n}\n\n/**\n * Represents the state of a GPU context.\n * All properties are readonly to ensure immutability.\n */\nexport interface GPUContextState {\n  readonly adapter: GPUAdapter | null;\n  readonly device: GPUDevice | null;\n  readonly initialized: boolean;\n  readonly canvas: HTMLCanvasElement | null;\n  readonly canvasContext: GPUCanvasContext | null;\n  readonly preferredFormat: GPUTextureFormat | null;\n  readonly devicePixelRatio: number;\n  readonly alphaMode: 'opaque' | 'premultiplied';\n  readonly powerPreference: 'low-power' | 'high-performance';\n}\n\n/** Reliable type guard for DOM canvases (safe when DOM globals are absent). */\nexport function isHTMLCanvasElement(canvas: HTMLCanvasElement): canvas is HTMLCanvasElement {\n  return typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement;\n}\n\n/** Gets display dimensions - clientWidth/Height for HTMLCanvasElement */\nfunction getCanvasDimensions(canvas: HTMLCanvasElement): { width: number; height: number } {\n  // Prefer clientWidth/clientHeight (CSS pixels) for HTMLCanvasElement as they reflect actual display size\n  // Fall back to canvas.width/height (device pixels) if client dimensions are 0 or invalid\n  const width = canvas.clientWidth || canvas.width || 0;\n  const height = canvas.clientHeight || canvas.height || 0;\n  \n  // Validate dimensions are finite and non-negative\n  // Note: 0 dimensions are allowed here - they'll be clamped to 1 during GPUContext initialization\n  if (!Number.isFinite(width) || !Number.isFinite(height)) {\n    throw new Error(\n      `GPUContext: Invalid canvas dimensions detected: width=${canvas.clientWidth || canvas.width}, ` +\n      `height=${canvas.clientHeight || canvas.height}. ` +\n      `Canvas must have finite dimensions. Ensure canvas is properly sized before initialization.`\n    );\n  }\n  \n  return { width, height };\n\n}\n\n/**\n * Creates a new GPUContext state with initial values.\n * \n * @param canvas - Optional canvas element (HTMLCanvasElement) to configure for WebGPU rendering\n * @param options - Optional configuration for device pixel ratio, alpha mode, and power preference\n * @returns A new GPUContextState instance\n */\nexport function createGPUContext(\n  canvas?: HTMLCanvasElement,\n  options?: GPUContextOptions\n): GPUContextState {\n  // Auto-detect DPR when `window` is available; otherwise default to 1.0.\n  const dprRaw =\n    options?.devicePixelRatio ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1.0);\n  // Be resilient: callers may pass 0/NaN/Infinity. Fall back to 1 instead of throwing.\n  const dpr = Number.isFinite(dprRaw) && dprRaw > 0 ? dprRaw : 1.0;\n  const alphaMode = options?.alphaMode ?? 'opaque';\n  const powerPreference = options?.powerPreference ?? 'high-performance';\n  \n  // Only use injected device/adapter when BOTH are provided (shared device mode)\n  const hasInjected = !!(options?.device && options?.adapter);\n  const injectedAdapter = hasInjected ? options!.adapter! : null;\n  const injectedDevice = hasInjected ? options!.device! : null;\n  const ownsDevice = !hasInjected;\n\n  return {\n    adapter: injectedAdapter,\n    device: injectedDevice,\n    initialized: false,\n    canvas: canvas || null,\n    canvasContext: null,\n    preferredFormat: null,\n    devicePixelRatio: dpr,\n    alphaMode,\n    powerPreference,\n    [ownsDeviceKey]: ownsDevice,\n  } as GPUContextStateInternal;\n}\n\n/**\n * Initializes the WebGPU context by requesting an adapter and device.\n * Returns a new state object with initialized values.\n * \n * @param context - The GPU context state to initialize\n * @returns A new GPUContextState with initialized adapter and device\n * @throws {Error} If WebGPU is not available in the browser\n * @throws {Error} If adapter request fails\n * @throws {Error} If device request fails\n * @throws {Error} If already initialized\n */\nexport async function initializeGPUContext(\n  context: GPUContextState\n): Promise<GPUContextState> {\n  if (context.initialized) {\n    throw new Error('GPUContext: already initialized. Call destroyGPUContext() before reinitializing.');\n  }\n\n  // Be resilient: callers may construct GPUContextState manually.\n  const sanitizedDevicePixelRatio =\n    Number.isFinite(context.devicePixelRatio) && context.devicePixelRatio > 0 ? context.devicePixelRatio : 1.0;\n\n  // Check for WebGPU support\n  if (!navigator.gpu) {\n    throw new Error(\n      'WebGPU is not available in this browser. ' +\n      'Please use a browser that supports WebGPU (Chrome 113+, Edge 113+, or Safari 18+). ' +\n      'Ensure WebGPU is enabled in browser flags if needed.'\n    );\n  }\n\n  let device: GPUDevice | null = null;\n  let adapter: GPUAdapter | null = null;\n  let ownsDevice = getOwnsDevice(context);\n\n  try {\n    const useInjectedDevice = context.adapter && context.device;\n\n    if (useInjectedDevice) {\n      // Shared device mode: use provided adapter and device, skip requestAdapter/requestDevice\n      adapter = context.adapter;\n      device = context.device;\n      ownsDevice = false;\n\n      // AC-8: Best-effort validation that getPreferredCanvasFormat is usable for canvas config.\n      // WebGPU does not expose format compatibility checks on the adapter/device; validate API\n      // availability and that the preferred format is one of the mandated canvas formats.\n      if (typeof navigator.gpu?.getPreferredCanvasFormat !== 'function') {\n        throw new Error(\n          'GPUContext: Shared device requires navigator.gpu.getPreferredCanvasFormat() for canvas ' +\n          'format selection, but it is not available in this environment. Use a browser with full WebGPU support.'\n        );\n      }\n\n      // Validate preferred canvas format is in the mandated set for WebGPU canvases.\n      // (This is as close as we can get to \"adapter supports format\" with current WebGPU APIs.)\n      const preferred = navigator.gpu.getPreferredCanvasFormat();\n      if (preferred !== 'bgra8unorm' && preferred !== 'rgba8unorm') {\n        throw new Error(\n          `GPUContext: Shared device preferred canvas format is not supported by ChartGPU. ` +\n            `Received navigator.gpu.getPreferredCanvasFormat()=\"${preferred}\". ` +\n            `Supported formats: \"bgra8unorm\", \"rgba8unorm\".`\n        );\n      }\n\n      // AC-5: Validate injected device limits against ChartGPU minimum requirements.\n      const actualMaxBufferSize = device.limits.maxBufferSize;\n      if (actualMaxBufferSize < MIN_REQUIRED_MAX_BUFFER_SIZE_BYTES) {\n        throw new Error(\n          `GPUContext: Injected device.limits.maxBufferSize is insufficient. ` +\n            `Required >= ${MIN_REQUIRED_MAX_BUFFER_SIZE_BYTES} bytes, actual=${actualMaxBufferSize} bytes.`\n        );\n      }\n\n      const actualMaxStorageBinding = device.limits.maxStorageBufferBindingSize;\n      if (actualMaxStorageBinding < MIN_REQUIRED_MAX_STORAGE_BUFFER_BINDING_SIZE_BYTES) {\n        throw new Error(\n          `GPUContext: Injected device.limits.maxStorageBufferBindingSize is insufficient. ` +\n            `Required >= ${MIN_REQUIRED_MAX_STORAGE_BUFFER_BINDING_SIZE_BYTES} bytes, actual=${actualMaxStorageBinding} bytes.`\n        );\n      }\n    } else {\n      // Normal mode: request adapter and device\n      const requestedAdapter = await navigator.gpu.requestAdapter({\n        powerPreference: context.powerPreference,\n      });\n\n      if (!requestedAdapter) {\n        throw new Error(\n          'GPUContext: Failed to request WebGPU adapter. ' +\n          'No compatible adapter found. This may occur if no GPU is available or WebGPU is disabled.'\n        );\n      }\n\n      const requestedDevice = await requestedAdapter.requestDevice();\n\n      if (!requestedDevice) {\n        throw new Error('GPUContext: Failed to request WebGPU device from adapter.');\n      }\n\n      adapter = requestedAdapter;\n      device = requestedDevice;\n      ownsDevice = true;\n\n      // AC-4 (CGPU-SHARED-DEVICE): Add uncapturederror handler only for owned devices.\n      // Rationale: Injected devices are caller-managed; avoid adding handlers to avoid conflicts\n      // with caller's error handling strategy. Owned devices are fully managed internally.\n      device.addEventListener('uncapturederror', (event: GPUUncapturedErrorEvent) => {\n        console.error('WebGPU uncaptured error:', event.error);\n      });\n    }\n\n    let canvasContext: GPUCanvasContext | null = null;\n    let preferredFormat: GPUTextureFormat | null = null;\n\n    // Configure canvas if provided\n    if (context.canvas) {\n      const webgpuContext = context.canvas.getContext('webgpu') as GPUCanvasContext | null;\n      \n      if (!webgpuContext) {\n        // Clean up device before throwing (only if we own it)\n        if (ownsDevice && device) {\n          try {\n            device.destroy();\n          } catch (error) {\n            console.warn('Error destroying device during canvas setup failure:', error);\n          }\n        }\n        throw new Error('GPUContext: Failed to get WebGPU context from canvas.');\n      }\n\n      // Use DPR from context state (set at context creation)\n      const { width, height } = getCanvasDimensions(context.canvas);\n      const dpr = sanitizedDevicePixelRatio;\n      \n      // Calculate target dimensions in device pixels\n      // Note: width/height from getCanvasDimensions are in CSS pixels for HTMLCanvasElement,\n      // or device pixels (already set by main thread)\n      const targetWidth = Math.floor(width * dpr);\n      const targetHeight = Math.floor(height * dpr);\n\n      // Clamp to device limits (must happen after device creation)\n      const maxDim = device.limits.maxTextureDimension2D;\n      // AC-5: For injected devices, validate the device supports the requested backing size at DPR.\n      if (!ownsDevice && (targetWidth > maxDim || targetHeight > maxDim)) {\n        const required = Math.max(targetWidth, targetHeight);\n        throw new Error(\n          `GPUContext: Injected device.limits.maxTextureDimension2D is insufficient. ` +\n            `Required >= ${required} (for ${targetWidth}x${targetHeight} at devicePixelRatio=${dpr}), ` +\n            `actual=${maxDim}.`\n        );\n      }\n      const finalWidth = Math.max(1, Math.min(targetWidth, maxDim));\n      const finalHeight = Math.max(1, Math.min(targetHeight, maxDim));\n      \n      context.canvas.width = finalWidth;\n      context.canvas.height = finalHeight;\n\n      // Get preferred format from navigator.gpu, fallback to bgra8unorm\n      preferredFormat = navigator.gpu.getPreferredCanvasFormat?.() || 'bgra8unorm';\n\n      // Configure the canvas context with alpha mode from context state\n      webgpuContext.configure({\n        device: device,\n        format: preferredFormat,\n        alphaMode: context.alphaMode,\n      });\n\n      canvasContext = webgpuContext;\n    }\n\n    return {\n      adapter,\n      device,\n      initialized: true,\n      canvas: context.canvas,\n      canvasContext,\n      preferredFormat,\n      devicePixelRatio: sanitizedDevicePixelRatio,\n      alphaMode: context.alphaMode,\n      powerPreference: context.powerPreference,\n      [ownsDeviceKey]: ownsDevice,\n    } as GPUContextStateInternal;\n  } catch (error) {\n    // If we created the device and initialization failed, destroy it to avoid leaks.\n    if (ownsDevice && device) {\n      try {\n        device.destroy();\n      } catch (destroyError) {\n        console.warn('Error destroying device during initialization failure:', destroyError);\n      }\n    }\n    if (error instanceof Error) {\n      throw error;\n    }\n    throw new Error(`Failed to initialize GPUContext: ${String(error)}`);\n  }\n}\n\n/**\n * Gets the current texture from the canvas context.\n * \n * @param context - The GPU context state\n * @returns The current canvas texture\n * @throws {Error} If canvas is not configured or context is not initialized\n * \n * @example\n * ```typescript\n * const texture = getCanvasTexture(context);\n * // Use texture in render pass\n * ```\n */\nexport function getCanvasTexture(context: GPUContextState): GPUTexture {\n  if (!context.canvas) {\n    throw new Error('GPUContext: Canvas is not configured. Provide a canvas element when creating the context.');\n  }\n\n  if (!context.initialized || !context.canvasContext) {\n    throw new Error('GPUContext: not initialized. Call initializeGPUContext() first.');\n  }\n\n  return context.canvasContext.getCurrentTexture();\n}\n\n/**\n * Clears the canvas to a solid color.\n * Creates a command encoder, begins a render pass with the specified clear color,\n * ends the pass, and submits it to the queue.\n * \n * @param context - The GPU context state\n * @param r - Red component (0.0 to 1.0)\n * @param g - Green component (0.0 to 1.0)\n * @param b - Blue component (0.0 to 1.0)\n * @param a - Alpha component (0.0 to 1.0)\n * @throws {Error} If canvas is not configured or context is not initialized\n * @throws {Error} If device is not available\n * \n * @example\n * ```typescript\n * // Clear to dark purple (#1a1a2e)\n * clearScreen(context, 0x1a / 255, 0x1a / 255, 0x2e / 255, 1.0);\n * ```\n */\nexport function clearScreen(\n  context: GPUContextState,\n  r: number,\n  g: number,\n  b: number,\n  a: number\n): void {\n  // Validate color component ranges\n  if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1 || a < 0 || a > 1) {\n    throw new Error('GPUContext: Color components must be in the range [0.0, 1.0]');\n  }\n\n  if (!context.canvas) {\n    throw new Error('GPUContext: Canvas is not configured. Provide a canvas element when creating the context.');\n  }\n\n  if (!context.initialized || !context.device || !context.canvasContext) {\n    throw new Error('GPUContext: not initialized. Call initializeGPUContext() first.');\n  }\n\n  // Get the current texture from the canvas\n  const texture = getCanvasTexture(context);\n\n  // Create command encoder\n  const encoder = context.device.createCommandEncoder();\n\n  // Begin render pass with clear color\n  const renderPass = encoder.beginRenderPass({\n    colorAttachments: [\n      {\n        view: texture.createView(),\n        clearValue: { r, g, b, a },\n        loadOp: 'clear',\n        storeOp: 'store',\n      },\n    ],\n  });\n\n  // End render pass\n  renderPass.end();\n\n  // Submit command buffer to queue\n  context.device.queue.submit([encoder.finish()]);\n}\n\n/**\n * Destroys the WebGPU device and cleans up resources.\n * \n * AC-3 (CGPU-SHARED-DEVICE): Dispose semantics\n * - **Always** unconfigures the canvas context (releases textures from getCurrentTexture)\n * - **Conditionally** calls device.destroy():\n *   - Owned devices (created internally): destroyed\n *   - Shared devices (injected via options): NOT destroyed (caller owns lifecycle)\n * \n * AC-7 (CGPU-SHARED-DEVICE): Backwards compatibility\n * - Missing ownership metadata is treated as \"owned\" (preserves legacy behavior)\n * \n * Returns a new state object with reset values.\n * After calling this, the context must be reinitialized before use.\n *\n * @param context - The GPU context state to destroy\n * @returns A new GPUContextState with reset values\n */\nexport function destroyGPUContext(context: GPUContextState): GPUContextState {\n  // Always unconfigure canvas context (releases textures from getCurrentTexture)\n  if (context.canvasContext) {\n    try {\n      context.canvasContext.unconfigure();\n    } catch (error) {\n      console.warn('Error unconfiguring GPU canvas context:', error);\n    }\n  }\n\n  // Only destroy device if we own it (created internally); shared devices are caller-owned.\n  // Backwards compat: missing ownership metadata treated as \"owned\" (legacy behavior).\n  if (getOwnsDevice(context) !== false && context.device) {\n    try {\n      context.device.destroy();\n    } catch (error) {\n      console.warn('Error destroying GPU device:', error);\n    }\n  }\n\n  return {\n    adapter: null,\n    device: null,\n    initialized: false,\n    canvas: context.canvas,\n    canvasContext: null,\n    preferredFormat: null,\n    devicePixelRatio: context.devicePixelRatio,\n    alphaMode: context.alphaMode,\n    powerPreference: context.powerPreference,\n    [ownsDeviceKey]: false,\n  } as GPUContextStateInternal;\n}\n\n/**\n * Convenience function that creates and initializes a GPU context in one step.\n * \n * @param canvas - Optional canvas element (HTMLCanvasElement) to configure for WebGPU rendering\n * @param options - Optional configuration for device pixel ratio, alpha mode, and power preference\n * @returns A fully initialized GPUContextState\n * @throws {Error} If initialization fails\n * \n * @example\n * ```typescript\n * const context = await createGPUContextAsync();\n * const device = context.device;\n * ```\n * \n * @example\n * ```typescript\n * const canvas = document.querySelector('canvas');\n * const context = await createGPUContextAsync(canvas);\n * const texture = getCanvasTexture(context);\n * ```\n */\nexport async function createGPUContextAsync(\n  canvas?: HTMLCanvasElement,\n  options?: GPUContextOptions\n): Promise<GPUContextState> {\n  const context = createGPUContext(canvas, options);\n  return initializeGPUContext(context);\n}\n\n/**\n * GPUContext class wrapper for backward compatibility.\n * \n * This class provides a class-based API that internally uses the functional implementation.\n * Use the functional API directly for better type safety and immutability.\n */\nexport class GPUContext {\n  private _state: GPUContextState;\n\n  /**\n   * Gets the WebGPU adapter, or null if not initialized.\n   */\n  get adapter(): GPUAdapter | null {\n    return this._state.adapter;\n  }\n\n  /**\n   * Gets the WebGPU device, or null if not initialized.\n   */\n  get device(): GPUDevice | null {\n    return this._state.device;\n  }\n\n  /**\n   * Checks if the context has been initialized.\n   */\n  get initialized(): boolean {\n    return this._state.initialized;\n  }\n\n  /**\n   * Gets the canvas element, or null if not provided.\n   */\n  get canvas(): SupportedCanvas | null {\n    return this._state.canvas;\n  }\n\n  /**\n   * Gets the WebGPU canvas context, or null if canvas is not configured.\n   */\n  get canvasContext(): GPUCanvasContext | null {\n    return this._state.canvasContext;\n  }\n\n  /**\n   * Gets the preferred canvas format, or null if canvas is not configured.\n   */\n  get preferredFormat(): GPUTextureFormat | null {\n    return this._state.preferredFormat;\n  }\n\n  /**\n   * Gets the device pixel ratio used for canvas sizing.\n   */\n  get devicePixelRatio(): number {\n    return this._state.devicePixelRatio;\n  }\n\n  /**\n   * Gets the canvas alpha mode.\n   */\n  get alphaMode(): 'opaque' | 'premultiplied' {\n    return this._state.alphaMode;\n  }\n\n  /**\n   * Gets the GPU power preference.\n   */\n  get powerPreference(): 'low-power' | 'high-performance' {\n    return this._state.powerPreference;\n  }\n\n  /**\n   * Creates a new GPUContext instance.\n   * \n   * @param canvas - Optional canvas element (HTMLCanvasElement) to configure for WebGPU rendering\n   * @param options - Optional configuration for device pixel ratio, alpha mode, and power preference\n   */\n  constructor(canvas?: HTMLCanvasElement, options?: GPUContextOptions) {\n    this._state = createGPUContext(canvas, options);\n  }\n\n  /**\n   * Initializes the WebGPU context by requesting an adapter and device.\n   * \n   * @throws {Error} If WebGPU is not available in the browser\n   * @throws {Error} If adapter request fails\n   * @throws {Error} If device request fails\n   * @throws {Error} If already initialized\n   */\n  async initialize(): Promise<void> {\n    this._state = await initializeGPUContext(this._state);\n  }\n\n  /**\n   * Static factory method to create and initialize a GPUContext instance.\n   * \n   * @param canvas - Optional canvas element (HTMLCanvasElement) to configure for WebGPU rendering\n   * @param options - Optional configuration for device pixel ratio, alpha mode, and power preference\n   * @returns A fully initialized GPUContext instance\n   * @throws {Error} If initialization fails\n   * \n   * @example\n   * ```typescript\n   * const context = await GPUContext.create();\n   * const device = context.device;\n   * ```\n   * \n   * @example\n   * ```typescript\n   * const canvas = document.querySelector('canvas');\n   * const context = await GPUContext.create(canvas);\n   * const texture = context.getCanvasTexture();\n   * ```\n   */\n  static async create(canvas?: HTMLCanvasElement, options?: GPUContextOptions): Promise<GPUContext> {\n    const context = new GPUContext(canvas, options);\n    await context.initialize();\n    return context;\n  }\n\n  /**\n   * Gets the current texture from the canvas context.\n   * \n   * @returns The current canvas texture\n   * @throws {Error} If canvas is not configured or context is not initialized\n   * \n   * @example\n   * ```typescript\n   * const texture = context.getCanvasTexture();\n   * // Use texture in render pass\n   * ```\n   */\n  getCanvasTexture(): GPUTexture {\n    return getCanvasTexture(this._state);\n  }\n\n  /**\n   * Clears the canvas to a solid color.\n   * Creates a command encoder, begins a render pass with the specified clear color,\n   * ends the pass, and submits it to the queue.\n   * \n   * @param r - Red component (0.0 to 1.0)\n   * @param g - Green component (0.0 to 1.0)\n   * @param b - Blue component (0.0 to 1.0)\n   * @param a - Alpha component (0.0 to 1.0)\n   * @throws {Error} If canvas is not configured or context is not initialized\n   * @throws {Error} If device is not available\n   * \n   * @example\n   * ```typescript\n   * // Clear to dark purple (#1a1a2e)\n   * context.clearScreen(0x1a / 255, 0x1a / 255, 0x2e / 255, 1.0);\n   * ```\n   */\n  clearScreen(r: number, g: number, b: number, a: number): void {\n    clearScreen(this._state, r, g, b, a);\n  }\n\n  /**\n   * Destroys the WebGPU device and cleans up resources.\n   * After calling destroy(), the context must be reinitialized before use.\n   */\n  destroy(): void {\n    this._state = destroyGPUContext(this._state);\n  }\n}\n","/**\n * Internal cartesian data abstraction for CartesianSeriesData.\n *\n * Provides high-performance, allocation-minimizing primitives to support all three cartesian formats:\n * - ReadonlyArray<DataPoint> (tuple or object)\n * - XYArraysData (separate x/y/size arrays)\n * - InterleavedXYData (typed array view with [x0,y0,x1,y1,...] layout)\n *\n * DO NOT export from public entrypoint (src/index.ts). This is internal-only.\n *\n * @module cartesianData\n * @internal\n */\n\nimport type { CartesianSeriesData, DataPoint, XYArraysData, InterleavedXYData } from '../config/types';\n\n/**\n * Bounds type for min/max x and y values.\n */\nexport type Bounds = Readonly<{ xMin: number; xMax: number; yMin: number; yMax: number }>;\n\n/**\n * Type for typed arrays with numeric indexing (excluding DataView and BigInt arrays).\n * BigInt arrays are excluded because coordinates must be numbers, not bigints.\n */\ntype TypedArray = \n  | Int8Array\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array;\n\n/**\n * Type guard for XYArraysData format.\n */\nfunction isXYArraysData(data: CartesianSeriesData): data is XYArraysData {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    !Array.isArray(data) &&\n    'x' in data &&\n    'y' in data &&\n    typeof (data as any).x === 'object' &&\n    typeof (data as any).y === 'object' &&\n    'length' in (data as any).x &&\n    'length' in (data as any).y\n  );\n}\n\n/**\n * Type guard for InterleavedXYData format (ArrayBufferView).\n */\nfunction isInterleavedXYData(data: CartesianSeriesData): data is InterleavedXYData {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    !Array.isArray(data) &&\n    ArrayBuffer.isView(data)\n  );\n}\n\n/**\n * Type guard for tuple DataPoint format.\n */\nfunction isTupleDataPoint(p: DataPoint): p is readonly [number, number, number?] {\n  return Array.isArray(p);\n}\n\n/**\n * Returns the number of points in the CartesianSeriesData.\n */\nexport function getPointCount(data: CartesianSeriesData): number {\n  if (isXYArraysData(data)) {\n    // Use minimum of x and y array lengths for safety\n    return Math.min(data.x.length, data.y.length);\n  }\n  \n  if (isInterleavedXYData(data)) {\n    // DataView is unsupported - throw clear error\n    if (data instanceof DataView) {\n      throw new Error('DataView is not supported for InterleavedXYData. Use typed arrays (Float32Array, Float64Array, etc.).');\n    }\n    // Interpret pointCount as floor(length/2), tolerant of odd length\n    // Cast to typed array with numeric indexing after DataView check\n    const arr = data as TypedArray;\n    return Math.floor(arr.length / 2);\n  }\n  \n  // ReadonlyArray<DataPoint>\n  return data.length;\n}\n\n/**\n * Returns the x-coordinate of the point at index i.\n * Returns NaN if the point is undefined, null, or non-object (for DataPoint[] format).\n * This allows callers using `Number.isFinite()` to naturally skip missing points.\n */\nexport function getX(data: CartesianSeriesData, i: number): number {\n  if (isXYArraysData(data)) {\n    return data.x[i]!;\n  }\n  \n  if (isInterleavedXYData(data)) {\n    if (data instanceof DataView) {\n      throw new Error('DataView is not supported for InterleavedXYData. Use typed arrays (Float32Array, Float64Array, etc.).');\n    }\n    const arr = data as TypedArray;\n    return arr[i * 2]!;\n  }\n  \n  // ReadonlyArray<DataPoint>\n  const p = data[i];\n  // Guard against undefined/null/non-object entries (sparse arrays, holes)\n  if (p === undefined || p === null || typeof p !== 'object') {\n    return NaN;\n  }\n  return isTupleDataPoint(p) ? p[0] : p.x;\n}\n\n/**\n * Returns the y-coordinate of the point at index i.\n * Returns NaN if the point is undefined, null, or non-object (for DataPoint[] format).\n * This allows callers using `Number.isFinite()` to naturally skip missing points.\n */\nexport function getY(data: CartesianSeriesData, i: number): number {\n  if (isXYArraysData(data)) {\n    return data.y[i]!;\n  }\n  \n  if (isInterleavedXYData(data)) {\n    if (data instanceof DataView) {\n      throw new Error('DataView is not supported for InterleavedXYData. Use typed arrays (Float32Array, Float64Array, etc.).');\n    }\n    const arr = data as TypedArray;\n    return arr[i * 2 + 1]!;\n  }\n  \n  // ReadonlyArray<DataPoint>\n  const p = data[i];\n  // Guard against undefined/null/non-object entries (sparse arrays, holes)\n  if (p === undefined || p === null || typeof p !== 'object') {\n    return NaN;\n  }\n  return isTupleDataPoint(p) ? p[1] : p.y;\n}\n\n/**\n * Returns the size value of the point at index i, or undefined if not available.\n * Returns undefined if the point is undefined, null, or non-object (for DataPoint[] format).\n * Note: InterleavedXYData does NOT support interleaved size (use XYArraysData.size if needed).\n */\nexport function getSize(data: CartesianSeriesData, i: number): number | undefined {\n  if (isXYArraysData(data)) {\n    return data.size?.[i];\n  }\n  \n  if (isInterleavedXYData(data)) {\n    // Size is not interleaved in InterleavedXYData format\n    return undefined;\n  }\n  \n  // ReadonlyArray<DataPoint>\n  const p = data[i];\n  // Guard against undefined/null/non-object entries (sparse arrays, holes)\n  if (p === undefined || p === null || typeof p !== 'object') {\n    return undefined;\n  }\n  return isTupleDataPoint(p) ? p[2] : p.size;\n}\n\n/**\n * Packs XY coordinates from CartesianSeriesData into a Float32Array in interleaved layout.\n * \n * Writes `pointCount` points starting at `srcPointOffset` in the source data\n * into `out` starting at `outFloatOffset` (measured in float32 elements, not bytes).\n * \n * Each point writes 2 floats: [x - xOffset, y].\n * Size dimension is NOT packed (use getSize() separately if needed).\n * \n * @param out - Target Float32Array to write into\n * @param outFloatOffset - Starting offset in `out` (in float32 elements)\n * @param src - Source CartesianSeriesData\n * @param srcPointOffset - Starting point index in source\n * @param pointCount - Number of points to pack\n * @param xOffset - Value to subtract from x coordinates (for Float32 precision preservation)\n */\nexport function packXYInto(\n  out: Float32Array,\n  outFloatOffset: number,\n  src: CartesianSeriesData,\n  srcPointOffset: number,\n  pointCount: number,\n  xOffset: number\n): void {\n  const availablePoints = getPointCount(src) - srcPointOffset;\n  const actualPointCount = Math.min(pointCount, availablePoints);\n  \n  if (actualPointCount <= 0) return;\n  \n  // Validate output buffer capacity\n  const requiredOutLength = outFloatOffset + actualPointCount * 2;\n  if (requiredOutLength > out.length) {\n    throw new Error(\n      `packXYInto: output buffer too small (need ${requiredOutLength} floats, have ${out.length})`\n    );\n  }\n  \n  if (isXYArraysData(src)) {\n    // Fast path: bulk copy with xOffset adjustment\n    for (let i = 0; i < actualPointCount; i++) {\n      const srcIdx = srcPointOffset + i;\n      const outIdx = outFloatOffset + i * 2;\n      out[outIdx] = src.x[srcIdx]! - xOffset;\n      out[outIdx + 1] = src.y[srcIdx]!;\n    }\n    return;\n  }\n  \n  if (isInterleavedXYData(src)) {\n    if (src instanceof DataView) {\n      throw new Error('DataView is not supported for InterleavedXYData. Use typed arrays (Float32Array, Float64Array, etc.).');\n    }\n    \n    const arr = src as TypedArray;\n    \n    // Fast path: bulk copy with xOffset adjustment\n    for (let i = 0; i < actualPointCount; i++) {\n      const srcIdx = (srcPointOffset + i) * 2;\n      const outIdx = outFloatOffset + i * 2;\n      out[outIdx] = arr[srcIdx]! - xOffset;\n      out[outIdx + 1] = arr[srcIdx + 1]!;\n    }\n    return;\n  }\n  \n  // ReadonlyArray<DataPoint> path\n  for (let i = 0; i < actualPointCount; i++) {\n    const srcIdx = srcPointOffset + i;\n    const outIdx = outFloatOffset + i * 2;\n    const p = src[srcIdx];\n    \n    // Guard against undefined/null/non-object entries (sparse arrays, holes)\n    if (p === undefined || p === null || typeof p !== 'object') {\n      out[outIdx] = NaN;\n      out[outIdx + 1] = NaN;\n      continue;\n    }\n    \n    const x = isTupleDataPoint(p) ? p[0] : p.x;\n    const y = isTupleDataPoint(p) ? p[1] : p.y;\n    \n    out[outIdx] = x - xOffset;\n    out[outIdx + 1] = y;\n  }\n}\n\n/**\n * Computes xMin/xMax/yMin/yMax bounds from CartesianSeriesData.\n * Skips non-finite x or y values. Returns null if no finite points found.\n * Ensures xMin !== xMax and yMin !== yMax for scale derivation (expands max by +1 if needed).\n *\n * @param data - CartesianSeriesData in any supported format\n * @returns Bounds object or null if no finite points\n */\nexport function computeRawBoundsFromCartesianData(data: CartesianSeriesData): Bounds | null {\n  let xMin = Number.POSITIVE_INFINITY;\n  let xMax = Number.NEGATIVE_INFINITY;\n  let yMin = Number.POSITIVE_INFINITY;\n  let yMax = Number.NEGATIVE_INFINITY;\n  \n  // Hoist type detection outside loop to avoid per-point type checks\n  if (isXYArraysData(data)) {\n    // Fast path for XYArraysData\n    const count = Math.min(data.x.length, data.y.length);\n    for (let i = 0; i < count; i++) {\n      const x = data.x[i]!;\n      const y = data.y[i]!;\n      \n      if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n      \n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  } else if (isInterleavedXYData(data)) {\n    // Fast path for InterleavedXYData\n    if (data instanceof DataView) {\n      throw new Error('DataView is not supported for InterleavedXYData. Use typed arrays (Float32Array, Float64Array, etc.).');\n    }\n    \n    const arr = data as TypedArray;\n    const count = Math.floor(arr.length / 2);\n    \n    for (let i = 0; i < count; i++) {\n      const x = arr[i * 2]!;\n      const y = arr[i * 2 + 1]!;\n      \n      if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n      \n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  } else {\n    // Array<DataPoint> path: use helper functions\n    const count = data.length;\n    for (let i = 0; i < count; i++) {\n      const x = getX(data, i);\n      const y = getY(data, i);\n      \n      if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n      \n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  }\n  \n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || !Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    return null;\n  }\n  \n  // Preserve existing behavior: if min==max, expand max by +1 for usability\n  if (xMin === xMax) xMax = xMin + 1;\n  if (yMin === yMax) yMax = yMin + 1;\n  \n  return { xMin, xMax, yMin, yMax };\n}\n\n/**\n * Returns true if a CartesianSeriesData array contains any null entries (gap markers).\n * Only applies to ReadonlyArray<DataPoint | null> format — XYArraysData and\n * InterleavedXYData cannot contain null entries and always return false.\n */\nexport function hasNullGaps(data: CartesianSeriesData): boolean {\n  if (!Array.isArray(data)) return false;\n  return data.includes(null);\n}\n\n/**\n * Removes null entries from a DataPoint array.\n * Used by connectNulls to strip gap markers before GPU upload,\n * so the line/area draws through gaps instead of breaking.\n */\nexport function filterNullGaps(data: ReadonlyArray<DataPoint | null>): ReadonlyArray<DataPoint> {\n  return data.filter((p): p is DataPoint => p !== null);\n}\n\n/**\n * Removes gap entries (null or NaN) from any CartesianSeriesData format.\n *\n * Null entries in DataPoint[] arrays are a direct gap marker. NaN x/y values\n * in XYArraysData and InterleavedXYData arise when cartesianDataToMutableColumns\n * converts null DataPoint entries into NaN pairs for the columnar format.\n *\n * Used by connectNulls to strip all gap markers regardless of data format,\n * so the line/area draws through gaps instead of breaking.\n *\n * Returns a DataPointTuple[] with only finite-coordinate points.\n */\nexport function filterGaps(data: CartesianSeriesData): ReadonlyArray<DataPoint> {\n  if (Array.isArray(data)) {\n    // ReadonlyArray<DataPoint | null> — filter nulls and NaN entries\n    return (data as ReadonlyArray<DataPoint | null>).filter((p): p is DataPoint => {\n      if (p === null || p === undefined) return false;\n      if (typeof p !== 'object') return false;\n      const x = isTupleDataPoint(p) ? p[0] : p.x;\n      const y = isTupleDataPoint(p) ? p[1] : p.y;\n      return Number.isFinite(x) && Number.isFinite(y);\n    });\n  }\n\n  // XYArraysData or InterleavedXYData — filter out indices where x or y is NaN\n  const count = getPointCount(data);\n  const result: DataPoint[] = [];\n  for (let i = 0; i < count; i++) {\n    const x = getX(data, i);\n    const y = getY(data, i);\n    if (Number.isFinite(x) && Number.isFinite(y)) {\n      result.push([x, y]);\n    }\n  }\n  return result;\n}\n","import type { CartesianSeriesData } from '../config/types';\nimport { getPointCount, packXYInto } from './cartesianData';\n\nexport interface DataStore {\n  setSeries(\n    index: number,\n    data: CartesianSeriesData,\n    options?: Readonly<{ xOffset?: number }>\n  ): void;\n  /**\n   * Appends new points to an existing series without re-uploading the entire buffer when possible.\n   *\n   * - Reuses the same geometric growth policy as `setSeries`.\n   * - When no reallocation is needed, writes only the appended byte range via `queue.writeBuffer(...)`.\n   * - Maintains `pointCount` for render path queries.\n   *\n   * Throws if the series has not been set yet.\n   */\n  appendSeries(index: number, newPoints: CartesianSeriesData): void;\n  removeSeries(index: number): void;\n  getSeriesBuffer(index: number): GPUBuffer;\n  /**\n   * Returns the number of points last set for the given series index.\n   *\n   * Throws if the series has not been set yet.\n   */\n  getSeriesPointCount(index: number): number;\n  dispose(): void;\n}\n\ntype SeriesEntry = {\n  readonly buffer: GPUBuffer;\n  readonly capacityBytes: number;\n  readonly pointCount: number;\n  readonly hash32: number;\n  /**\n   * X-origin subtracted during packing to preserve Float32 precision for large-magnitude domains\n   * (e.g. epoch-ms time axes). Stored so appendSeries can pack consistently.\n   */\n  readonly xOffset: number;\n  /**\n   * Growable staging buffer for interleaved Float32 x,y data.\n   * Maintained to enable efficient incremental append without repacking all data.\n   */\n  readonly stagingBuffer: Float32Array;\n};\n\nconst MIN_BUFFER_BYTES = 4;\n\nfunction roundUpToMultipleOf4(bytes: number): number {\n  return (bytes + 3) & ~3;\n}\n\nfunction nextPow2(bytes: number): number {\n  if (!Number.isFinite(bytes) || bytes <= 0) return 1;\n  const n = Math.ceil(bytes);\n  return 2 ** Math.ceil(Math.log2(n));\n}\n\nfunction computeGrownCapacityBytes(currentCapacityBytes: number, requiredBytes: number): number {\n  // Grow geometrically to reduce buffer churn (power-of-two policy).\n  // Enforce 4-byte alignment via MIN_BUFFER_BYTES (>= 4) and power-of-two growth.\n  const required = Math.max(MIN_BUFFER_BYTES, roundUpToMultipleOf4(requiredBytes));\n  const grown = Math.max(MIN_BUFFER_BYTES, nextPow2(required));\n  return Math.max(currentCapacityBytes, grown);\n}\n\nfunction fnv1aUpdate(hash: number, words: Uint32Array): number {\n  let h = hash >>> 0;\n  for (let i = 0; i < words.length; i++) {\n    h ^= words[i]!;\n    h = Math.imul(h, 0x01000193) >>> 0; // FNV prime\n  }\n  return h >>> 0;\n}\n\n/**\n * Computes a stable 32-bit hash of the Float32 contents using their IEEE-754\n * bit patterns (not numeric equality), to cheaply detect changes.\n */\nfunction hashFloat32ArrayBits(data: Float32Array): number {\n  const u32 = new Uint32Array(data.buffer, data.byteOffset, data.byteLength / 4);\n  return fnv1aUpdate(0x811c9dc5, u32); // FNV-1a offset basis\n}\n\nexport function createDataStore(device: GPUDevice): DataStore {\n  const series = new Map<number, SeriesEntry>();\n  let disposed = false;\n\n  /**\n   * Packs CartesianSeriesData into an interleaved Float32Array using packXYInto.\n   * Returns a view-safe Float32Array suitable for GPU upload.\n   */\n  const packCartesianData = (data: CartesianSeriesData, xOffset: number): Float32Array => {\n    const pointCount = getPointCount(data);\n    if (pointCount === 0) return new Float32Array(0);\n\n    const buffer = new ArrayBuffer(pointCount * 2 * 4);\n    const f32 = new Float32Array(buffer);\n\n    packXYInto(f32, 0, data, 0, pointCount, xOffset);\n\n    return f32;\n  };\n\n  const assertNotDisposed = (): void => {\n    if (disposed) {\n      throw new Error('DataStore is disposed.');\n    }\n  };\n\n  const getSeriesEntry = (index: number): SeriesEntry => {\n    assertNotDisposed();\n    const entry = series.get(index);\n    if (!entry) {\n      throw new Error(`Series ${index} has no data. Call setSeries(${index}, data) first.`);\n    }\n    return entry;\n  };\n\n  const setSeries = (index: number, data: CartesianSeriesData, options?: Readonly<{ xOffset?: number }>): void => {\n    assertNotDisposed();\n\n    const xOffset = options?.xOffset ?? 0;\n    const pointCount = getPointCount(data);\n    const packed = packCartesianData(data, xOffset);\n    const hash32 = hashFloat32ArrayBits(packed);\n\n    const requiredBytes = roundUpToMultipleOf4(packed.byteLength);\n    const targetBytes = Math.max(MIN_BUFFER_BYTES, requiredBytes);\n\n    const existing = series.get(index);\n    const unchanged = existing && existing.pointCount === pointCount && existing.hash32 === hash32;\n    if (unchanged) return;\n\n    let buffer = existing?.buffer ?? null;\n    let capacityBytes = existing?.capacityBytes ?? 0;\n\n    if (!buffer || targetBytes > capacityBytes) {\n      const maxBufferSize = device.limits.maxBufferSize;\n      if (targetBytes > maxBufferSize) {\n        throw new Error(\n          `DataStore.setSeries(${index}): required buffer size ${targetBytes} exceeds device.limits.maxBufferSize (${maxBufferSize}).`\n        );\n      }\n\n      if (buffer) {\n        try {\n          buffer.destroy();\n        } catch {\n          // Ignore destroy errors; we are replacing the buffer anyway.\n        }\n      }\n\n      const grownCapacityBytes = computeGrownCapacityBytes(capacityBytes, targetBytes);\n      if (grownCapacityBytes > maxBufferSize) {\n        // If geometric growth would exceed the limit, fall back to the exact required size.\n        // (Still no shrink: if current capacity was already larger, we'd keep it above.)\n        // NOTE: targetBytes is already checked against maxBufferSize above.\n        capacityBytes = targetBytes;\n      } else {\n        capacityBytes = grownCapacityBytes;\n      }\n\n      buffer = device.createBuffer({\n        size: capacityBytes,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    // View-safe GPU upload: explicitly pass byteOffset and byteLength\n    if (packed.byteLength > 0) {\n      device.queue.writeBuffer(buffer, 0, packed.buffer, packed.byteOffset, packed.byteLength);\n    }\n\n    // Create staging buffer matching the packed data for efficient append\n    const stagingBuffer = new Float32Array(capacityBytes / 4);\n    stagingBuffer.set(packed);\n\n    series.set(index, {\n      buffer,\n      capacityBytes,\n      pointCount,\n      hash32,\n      xOffset,\n      stagingBuffer,\n    });\n  };\n\n  const appendSeries = (index: number, newPoints: CartesianSeriesData): void => {\n    assertNotDisposed();\n    const newPointCount = getPointCount(newPoints);\n    if (newPointCount === 0) return;\n\n    const existing = getSeriesEntry(index);\n    const prevPointCount = existing.pointCount;\n    const nextPointCount = prevPointCount + newPointCount;\n\n    // Each point is 2 floats (x, y) = 8 bytes.\n    const requiredBytes = roundUpToMultipleOf4(nextPointCount * 2 * 4);\n    const targetBytes = Math.max(MIN_BUFFER_BYTES, requiredBytes);\n\n    let buffer = existing.buffer;\n    let capacityBytes = existing.capacityBytes;\n    let stagingBuffer = existing.stagingBuffer;\n\n    const maxBufferSize = device.limits.maxBufferSize;\n\n    if (targetBytes > capacityBytes) {\n      if (targetBytes > maxBufferSize) {\n        throw new Error(\n          `DataStore.appendSeries(${index}): required buffer size ${targetBytes} exceeds device.limits.maxBufferSize (${maxBufferSize}).`\n        );\n      }\n\n      // Replace buffer (no shrink). This is the slow path; we re-upload the full series.\n      try {\n        buffer.destroy();\n      } catch {\n        // Ignore destroy errors; we are replacing the buffer anyway.\n      }\n\n      const grownCapacityBytes = computeGrownCapacityBytes(capacityBytes, targetBytes);\n      capacityBytes = grownCapacityBytes > maxBufferSize ? targetBytes : grownCapacityBytes;\n\n      buffer = device.createBuffer({\n        size: capacityBytes,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n      });\n\n      // Create new staging buffer with grown capacity\n      const newStagingBuffer = new Float32Array(capacityBytes / 4);\n      // Copy old data\n      newStagingBuffer.set(stagingBuffer.subarray(0, prevPointCount * 2));\n      // Pack new data directly into staging buffer\n      packXYInto(newStagingBuffer, prevPointCount * 2, newPoints, 0, newPointCount, existing.xOffset);\n      \n      const fullPacked = newStagingBuffer.subarray(0, nextPointCount * 2);\n      if (fullPacked.byteLength > 0) {\n        device.queue.writeBuffer(buffer, 0, fullPacked.buffer, fullPacked.byteOffset, fullPacked.byteLength);\n      }\n\n      series.set(index, {\n        buffer,\n        capacityBytes,\n        pointCount: nextPointCount,\n        hash32: hashFloat32ArrayBits(fullPacked),\n        xOffset: existing.xOffset,\n        stagingBuffer: newStagingBuffer,\n      });\n      return;\n    }\n\n    // Fast path: pack directly into existing staging buffer and upload only the appended range.\n    packXYInto(stagingBuffer, prevPointCount * 2, newPoints, 0, newPointCount, existing.xOffset);\n    \n    const appendedView = stagingBuffer.subarray(prevPointCount * 2, nextPointCount * 2);\n    if (appendedView.byteLength > 0) {\n      const byteOffset = prevPointCount * 2 * 4;\n      device.queue.writeBuffer(buffer, byteOffset, appendedView.buffer, appendedView.byteOffset, appendedView.byteLength);\n    }\n\n    // Incremental FNV-1a update over the appended IEEE-754 bit patterns.\n    const appendWords = new Uint32Array(appendedView.buffer, appendedView.byteOffset, appendedView.byteLength / 4);\n    const nextHash32 = fnv1aUpdate(existing.hash32, appendWords);\n\n    series.set(index, {\n      buffer,\n      capacityBytes,\n      pointCount: nextPointCount,\n      hash32: nextHash32,\n      xOffset: existing.xOffset,\n      stagingBuffer,\n    });\n  };\n\n  const removeSeries = (index: number): void => {\n    assertNotDisposed();\n\n    const entry = series.get(index);\n    if (!entry) return;\n\n    try {\n      entry.buffer.destroy();\n    } catch {\n      // Ignore destroy errors; removal should be best-effort.\n    }\n    series.delete(index);\n  };\n\n  const getSeriesBuffer = (index: number): GPUBuffer => {\n    return getSeriesEntry(index).buffer;\n  };\n\n  const getSeriesPointCount = (index: number): number => {\n    return getSeriesEntry(index).pointCount;\n  };\n\n  const dispose = (): void => {\n    if (disposed) return;\n    disposed = true;\n\n    for (const entry of series.values()) {\n      try {\n        entry.buffer.destroy();\n      } catch {\n        // Ignore destroy errors; disposal should be best-effort.\n      }\n    }\n    series.clear();\n  };\n\n  return {\n    setSeries,\n    appendSeries,\n    removeSeries,\n    getSeriesBuffer,\n    getSeriesPointCount,\n    dispose,\n  };\n}\n\n","import type { DataPoint, DataPointTuple } from '../config/types';\n\nfunction isTupleDataPoint(point: DataPoint): point is DataPointTuple {\n  // `DataPoint` uses a readonly tuple; `Array.isArray` doesn't narrow it well without a predicate.\n  return Array.isArray(point);\n}\n\nfunction lttbIndicesForInterleavedXY(data: Float32Array, targetPoints: number): Int32Array {\n  const n = data.length >>> 1; // floor(length / 2)\n  const lastIndex = n - 1;\n\n  if (targetPoints <= 0 || n === 0) return new Int32Array(0);\n  if (targetPoints === 1) return new Int32Array([0]);\n  if (targetPoints === 2) return n >= 2 ? new Int32Array([0, lastIndex]) : new Int32Array([0]);\n  if (n <= targetPoints) {\n    const indices = new Int32Array(n);\n    for (let i = 0; i < n; i++) indices[i] = i;\n    return indices;\n  }\n\n  const indices = new Int32Array(targetPoints);\n  indices[0] = 0;\n  indices[targetPoints - 1] = lastIndex;\n\n  const bucketSize = (n - 2) / (targetPoints - 2);\n\n  let a = 0;\n  let out = 1;\n\n  const lastX = data[lastIndex * 2 + 0];\n  const lastY = data[lastIndex * 2 + 1];\n\n  for (let bucket = 0; bucket < targetPoints - 2; bucket++) {\n    // Current bucket: candidate points are [rangeStart, rangeEndExclusive) and never include lastIndex.\n    let rangeStart = Math.floor(bucketSize * bucket) + 1;\n    let rangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 1)) + 1, lastIndex);\n    if (rangeStart >= rangeEndExclusive) {\n      // Defensive: ensure at least one candidate point.\n      rangeStart = Math.min(rangeStart, lastIndex - 1);\n      rangeEndExclusive = Math.min(rangeStart + 1, lastIndex);\n    }\n\n    // Next bucket for average: [nextRangeStart, nextRangeEndExclusive)\n    const nextRangeStart = Math.floor(bucketSize * (bucket + 1)) + 1;\n    const nextRangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 2)) + 1, lastIndex);\n\n    // If there are no points in the next bucket, use the last point as the average.\n    let avgX = lastX;\n    let avgY = lastY;\n    if (nextRangeStart < nextRangeEndExclusive) {\n      let sumX = 0;\n      let sumY = 0;\n      let avgCount = 0;\n      for (let i = nextRangeStart; i < nextRangeEndExclusive; i++) {\n        sumX += data[i * 2 + 0];\n        sumY += data[i * 2 + 1];\n        avgCount++;\n      }\n      if (avgCount > 0) {\n        avgX = sumX / avgCount;\n        avgY = sumY / avgCount;\n      }\n    }\n\n    const ax = data[a * 2 + 0];\n    const ay = data[a * 2 + 1];\n\n    let maxArea = -1;\n    let maxIndex = rangeStart;\n    for (let i = rangeStart; i < rangeEndExclusive; i++) {\n      const bx = data[i * 2 + 0];\n      const by = data[i * 2 + 1];\n      const area2 = (ax - avgX) * (by - ay) - (ax - bx) * (avgY - ay);\n      const absArea2 = area2 < 0 ? -area2 : area2;\n      if (absArea2 > maxArea) {\n        maxArea = absArea2;\n        maxIndex = i;\n      }\n    }\n\n    indices[out++] = maxIndex;\n    a = maxIndex;\n  }\n\n  return indices;\n}\n\nfunction lttbIndicesForDataPoints(data: ReadonlyArray<DataPoint>, targetPoints: number): Int32Array {\n  const n = data.length;\n  const lastIndex = n - 1;\n\n  if (targetPoints <= 0 || n === 0) return new Int32Array(0);\n  if (targetPoints === 1) return new Int32Array([0]);\n  if (targetPoints === 2) return n >= 2 ? new Int32Array([0, lastIndex]) : new Int32Array([0]);\n  if (n <= targetPoints) {\n    const indices = new Int32Array(n);\n    for (let i = 0; i < n; i++) indices[i] = i;\n    return indices;\n  }\n\n  const indices = new Int32Array(targetPoints);\n  indices[0] = 0;\n  indices[targetPoints - 1] = lastIndex;\n\n  const bucketSize = (n - 2) / (targetPoints - 2);\n\n  let a = 0;\n  let out = 1;\n\n  const pLast = data[lastIndex]!;\n  const lastX = isTupleDataPoint(pLast) ? pLast[0] : pLast.x;\n  const lastY = isTupleDataPoint(pLast) ? pLast[1] : pLast.y;\n\n  for (let bucket = 0; bucket < targetPoints - 2; bucket++) {\n    // Current bucket: candidate points are [rangeStart, rangeEndExclusive) and never include lastIndex.\n    let rangeStart = Math.floor(bucketSize * bucket) + 1;\n    let rangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 1)) + 1, lastIndex);\n    if (rangeStart >= rangeEndExclusive) {\n      // Defensive: ensure at least one candidate point.\n      rangeStart = Math.min(rangeStart, lastIndex - 1);\n      rangeEndExclusive = Math.min(rangeStart + 1, lastIndex);\n    }\n\n    // Next bucket for average: [nextRangeStart, nextRangeEndExclusive)\n    const nextRangeStart = Math.floor(bucketSize * (bucket + 1)) + 1;\n    const nextRangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 2)) + 1, lastIndex);\n\n    // If there are no points in the next bucket, use the last point as the average.\n    let avgX = lastX;\n    let avgY = lastY;\n    if (nextRangeStart < nextRangeEndExclusive) {\n      let sumX = 0;\n      let sumY = 0;\n      let avgCount = 0;\n      for (let i = nextRangeStart; i < nextRangeEndExclusive; i++) {\n        const p = data[i]!;\n        const x = isTupleDataPoint(p) ? p[0] : p.x;\n        const y = isTupleDataPoint(p) ? p[1] : p.y;\n        sumX += x;\n        sumY += y;\n        avgCount++;\n      }\n      if (avgCount > 0) {\n        avgX = sumX / avgCount;\n        avgY = sumY / avgCount;\n      }\n    }\n\n    const pa = data[a]!;\n    const ax = isTupleDataPoint(pa) ? pa[0] : pa.x;\n    const ay = isTupleDataPoint(pa) ? pa[1] : pa.y;\n\n    let maxArea = -1;\n    let maxIndex = rangeStart;\n    for (let i = rangeStart; i < rangeEndExclusive; i++) {\n      const pb = data[i]!;\n      const bx = isTupleDataPoint(pb) ? pb[0] : pb.x;\n      const by = isTupleDataPoint(pb) ? pb[1] : pb.y;\n      const area2 = (ax - avgX) * (by - ay) - (ax - bx) * (avgY - ay);\n      const absArea2 = area2 < 0 ? -area2 : area2;\n      if (absArea2 > maxArea) {\n        maxArea = absArea2;\n        maxIndex = i;\n      }\n    }\n\n    indices[out++] = maxIndex;\n    a = maxIndex;\n  }\n\n  return indices;\n}\n\nexport function lttbSample(data: Float32Array, targetPoints: number): Float32Array;\nexport function lttbSample(data: DataPoint[], targetPoints: number): DataPoint[];\nexport function lttbSample(data: ReadonlyArray<DataPoint>, targetPoints: number): ReadonlyArray<DataPoint>;\nexport function lttbSample(\n  data: ReadonlyArray<DataPoint> | Float32Array,\n  targetPoints: number\n): ReadonlyArray<DataPoint> | Float32Array {\n  const threshold = Math.floor(targetPoints);\n\n  if (data instanceof Float32Array) {\n    const n = data.length >>> 1;\n    if (threshold <= 0 || n === 0) return new Float32Array(0);\n\n    // If we're already under the target, avoid copying.\n    if (n <= threshold) return data;\n\n    const indices = lttbIndicesForInterleavedXY(data, threshold);\n    const out = new Float32Array(indices.length * 2);\n    for (let i = 0; i < indices.length; i++) {\n      const idx = indices[i]!;\n      out[i * 2 + 0] = data[idx * 2 + 0];\n      out[i * 2 + 1] = data[idx * 2 + 1];\n    }\n    return out;\n  }\n\n  const n = data.length;\n  if (threshold <= 0 || n === 0) return [];\n\n  // Story requirement: when data is shorter than the target, return original.\n  if (n <= threshold) return data;\n\n  const indices = lttbIndicesForDataPoints(data, threshold);\n  const out = new Array<DataPoint>(indices.length);\n  for (let i = 0; i < indices.length; i++) {\n    out[i] = data[indices[i]!]!;\n  }\n  return out;\n}\n\n","import type { CartesianSeriesData, DataPoint, DataPointTuple, SeriesSampling } from '../config/types';\nimport { lttbSample } from './lttbSample';\nimport { getPointCount, getX, getY, getSize as getPointSize } from './cartesianData';\n\n\nfunction clampTargetPoints(targetPoints: number): number {\n  const t = Math.floor(targetPoints);\n  return Number.isFinite(t) ? t : 0;\n}\n\n/**\n * Type guard for XYArraysData format.\n */\nfunction isXYArraysData(data: CartesianSeriesData): data is import('../config/types').XYArraysData {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    !Array.isArray(data) &&\n    'x' in data &&\n    'y' in data &&\n    typeof (data as any).x === 'object' &&\n    typeof (data as any).y === 'object' &&\n    'length' in (data as any).x &&\n    'length' in (data as any).y\n  );\n}\n\n/**\n * Type guard for InterleavedXYData format (ArrayBufferView).\n */\nfunction isInterleavedXYData(data: CartesianSeriesData): data is import('../config/types').InterleavedXYData {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    !Array.isArray(data) &&\n    ArrayBuffer.isView(data)\n  );\n}\n\n/**\n * Packs CartesianSeriesData into a Float32Array for LTTB sampling.\n * Returns the packed Float32Array.\n */\nfunction packToFloat32Array(data: CartesianSeriesData): Float32Array {\n  const count = getPointCount(data);\n  const out = new Float32Array(count * 2);\n  \n  for (let i = 0; i < count; i++) {\n    out[i * 2] = getX(data, i);\n    out[i * 2 + 1] = getY(data, i);\n  }\n  \n  return out;\n}\n\ntype BucketMode = 'average' | 'max' | 'min';\n\n/**\n * Samples CartesianSeriesData using bucket-based strategies (average, max, min).\n * Always returns DataPointTuple[] for newly allocated data.\n * Preserves size semantics when available.\n */\nfunction sampleByBucketsFromCartesian(\n  data: CartesianSeriesData,\n  targetPoints: number,\n  mode: BucketMode\n): DataPointTuple[] {\n  const n = getPointCount(data);\n  const threshold = clampTargetPoints(targetPoints);\n\n  if (threshold <= 0 || n === 0) return [];\n  if (threshold === 1) {\n    const x = getX(data, 0);\n    const y = getY(data, 0);\n    const size = getPointSize(data, 0);\n    return size !== undefined ? [[x, y, size]] : [[x, y]];\n  }\n  if (threshold === 2) {\n    if (n >= 2) {\n      const x0 = getX(data, 0);\n      const y0 = getY(data, 0);\n      const size0 = getPointSize(data, 0);\n      const xLast = getX(data, n - 1);\n      const yLast = getY(data, n - 1);\n      const sizeLast = getPointSize(data, n - 1);\n      return [\n        size0 !== undefined ? [x0, y0, size0] : [x0, y0],\n        sizeLast !== undefined ? [xLast, yLast, sizeLast] : [xLast, yLast],\n      ];\n    } else {\n      const x = getX(data, 0);\n      const y = getY(data, 0);\n      const size = getPointSize(data, 0);\n      return size !== undefined ? [[x, y, size]] : [[x, y]];\n    }\n  }\n\n  const lastIndex = n - 1;\n  const out: DataPointTuple[] = new Array(threshold);\n  \n  // First and last points\n  {\n    const x0 = getX(data, 0);\n    const y0 = getY(data, 0);\n    const size0 = getPointSize(data, 0);\n    out[0] = size0 !== undefined ? [x0, y0, size0] : [x0, y0];\n    \n    const xLast = getX(data, lastIndex);\n    const yLast = getY(data, lastIndex);\n    const sizeLast = getPointSize(data, lastIndex);\n    out[threshold - 1] = sizeLast !== undefined ? [xLast, yLast, sizeLast] : [xLast, yLast];\n  }\n\n  const bucketSize = (n - 2) / (threshold - 2);\n\n  for (let bucket = 0; bucket < threshold - 2; bucket++) {\n    let rangeStart = Math.floor(bucketSize * bucket) + 1;\n    let rangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 1)) + 1, lastIndex);\n\n    if (rangeStart >= rangeEndExclusive) {\n      rangeStart = Math.min(rangeStart, lastIndex - 1);\n      rangeEndExclusive = Math.min(rangeStart + 1, lastIndex);\n    }\n\n    let chosen: DataPointTuple | null = null;\n\n    if (mode === 'average') {\n      let sumX = 0;\n      let sumY = 0;\n      let sumSize = 0;\n      let count = 0;\n      let sizeCount = 0;\n      for (let i = rangeStart; i < rangeEndExclusive; i++) {\n        const x = getX(data, i);\n        const y = getY(data, i);\n        if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n        sumX += x;\n        sumY += y;\n        count++;\n\n        const size = getPointSize(data, i);\n        if (typeof size === 'number' && Number.isFinite(size)) {\n          sumSize += size;\n          sizeCount++;\n        }\n      }\n\n      if (count > 0) {\n        const avgX = sumX / count;\n        const avgY = sumY / count;\n        if (sizeCount > 0) {\n          chosen = [avgX, avgY, sumSize / sizeCount];\n        } else {\n          chosen = [avgX, avgY];\n        }\n      }\n    } else {\n      let bestY = mode === 'max' ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;\n      let bestIndex = rangeStart;\n      for (let i = rangeStart; i < rangeEndExclusive; i++) {\n        const y = getY(data, i);\n        if (!Number.isFinite(y)) continue;\n        if (mode === 'max') {\n          if (y > bestY) {\n            bestY = y;\n            bestIndex = i;\n          }\n        } else {\n          if (y < bestY) {\n            bestY = y;\n            bestIndex = i;\n          }\n        }\n      }\n      // Return the best point found\n      const x = getX(data, bestIndex);\n      const y = getY(data, bestIndex);\n      const size = getPointSize(data, bestIndex);\n      chosen = size !== undefined ? [x, y, size] : [x, y];\n    }\n\n    if (chosen === null) {\n      // Fallback to first point in range\n      const x = getX(data, rangeStart);\n      const y = getY(data, rangeStart);\n      const size = getPointSize(data, rangeStart);\n      chosen = size !== undefined ? [x, y, size] : [x, y];\n    }\n\n    out[bucket + 1] = chosen;\n  }\n\n  return out;\n}\n\n\n/**\n * Samples CartesianSeriesData using the specified sampling strategy.\n * \n * Returns the ORIGINAL data reference when:\n * - `sampling === 'none'`\n * - `samplingThreshold` is invalid/non-positive\n * - Point count <= threshold\n * \n * When sampling occurs:\n * - For `lttb`:\n *   - Float32Array interleaved → returns sampled Float32Array\n *   - Other interleaved typed array → packs to Float32Array, returns sampled Float32Array\n *   - DataPoint[] → returns sampled DataPoint[]\n *   - XYArraysData → packs to Float32Array, returns sampled Float32Array\n * - For `average`/`max`/`min`:\n *   - Returns DataPointTuple[] for all input formats\n */\nexport function sampleSeriesDataPoints(\n  data: CartesianSeriesData,\n  sampling: SeriesSampling,\n  samplingThreshold: number\n): CartesianSeriesData {\n  const threshold = clampTargetPoints(samplingThreshold);\n  const pointCount = getPointCount(data);\n\n  // Disabled or already under threshold: keep original reference (avoid extra allocations).\n  if (sampling === 'none') return data;\n  if (!(threshold > 0)) return data;\n  if (pointCount <= threshold) return data;\n\n  switch (sampling) {\n    case 'lttb': {\n      // Float32Array fast path\n      if (data instanceof Float32Array) {\n        return lttbSample(data, threshold);\n      }\n      \n      // Other interleaved typed arrays: pack to Float32Array and sample\n      if (isInterleavedXYData(data)) {\n        const packed = packToFloat32Array(data);\n        return lttbSample(packed, threshold);\n      }\n      \n      // XYArraysData: pack to Float32Array and sample\n      if (isXYArraysData(data)) {\n        const packed = packToFloat32Array(data);\n        return lttbSample(packed, threshold);\n      }\n      \n      // DataPoint[] path — filter nulls before LTTB sampling.\n      // Nulls represent line-segmentation gaps and will be handled by gap detection\n      // in later pipeline stages; LTTB only operates on concrete data points.\n      const nonNullData = (data as ReadonlyArray<DataPoint | null>).filter(\n        (p): p is DataPoint => p !== null\n      );\n      return lttbSample(nonNullData, threshold);\n    }\n    \n    case 'average':\n      return sampleByBucketsFromCartesian(data, threshold, 'average');\n    \n    case 'max':\n      return sampleByBucketsFromCartesian(data, threshold, 'max');\n    \n    case 'min':\n      return sampleByBucketsFromCartesian(data, threshold, 'min');\n    \n    default: {\n      // Defensive for JS callers / widened types.\n      return data;\n    }\n  }\n}\n\n","import type { OHLCDataPoint, OHLCDataPointTuple } from '../config/types';\n\nfunction isTupleOHLCDataPoint(point: OHLCDataPoint): point is OHLCDataPointTuple {\n  return Array.isArray(point);\n}\n\n/**\n * Downsamples OHLC (candlestick) data to a target number of points using bucket aggregation.\n *\n * Each bucket aggregates candles preserving OHLC semantics:\n * - timestamp and open from the first candle in the bucket\n * - close from the last candle in the bucket\n * - high as the maximum of all highs in the bucket\n * - low as the minimum of all lows in the bucket\n *\n * @param data - Array of OHLC data points (tuples or objects)\n * @param targetPoints - Desired number of output points\n * @returns Downsampled array; same reference if no sampling needed\n *\n * Edge cases:\n * - If `data.length <= targetPoints` or `targetPoints < 2`, returns the original array (same reference)\n * - First and last candles are always preserved exactly (same element references)\n * - Output shape matches input shape (tuples → tuples, objects → objects)\n */\nexport function ohlcSample(\n  data: ReadonlyArray<OHLCDataPoint>,\n  targetPoints: number,\n): ReadonlyArray<OHLCDataPoint> {\n  const threshold = Math.floor(targetPoints);\n  const n = data.length;\n\n  // Return original if already under target or insufficient target.\n  if (threshold < 2 || n <= threshold) return data;\n\n  const out = new Array<OHLCDataPoint>(threshold);\n\n  // Preserve first and last candles exactly.\n  out[0] = data[0]!;\n  out[threshold - 1] = data[n - 1]!;\n\n  if (threshold === 2) return out;\n\n  // Hoist tuple-vs-object detection once (assume homogeneous arrays).\n  const isTuple = isTupleOHLCDataPoint(data[0]!);\n\n  // Bucket size for interior points: (n - 2) interior input points → (threshold - 2) interior output points.\n  const bucketSize = (n - 2) / (threshold - 2);\n\n  if (isTuple) {\n    // Tuple format path: [timestamp, open, close, low, high]\n    const dataAsTuples = data as ReadonlyArray<OHLCDataPointTuple>;\n\n    for (let bucket = 0; bucket < threshold - 2; bucket++) {\n      // Bucket range: [rangeStart, rangeEndExclusive)\n      let rangeStart = Math.floor(bucketSize * bucket) + 1;\n      let rangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 1)) + 1, n - 1);\n\n      // Defensive: ensure at least one candidate point.\n      if (rangeStart >= rangeEndExclusive) {\n        rangeStart = Math.min(rangeStart, n - 2);\n        rangeEndExclusive = Math.min(rangeStart + 1, n - 1);\n      }\n\n      // Extract first and last candles in bucket.\n      const firstCandle = dataAsTuples[rangeStart]!;\n      const lastCandle = dataAsTuples[rangeEndExclusive - 1]!;\n\n      const timestamp = firstCandle[0];\n      const open = firstCandle[1];\n      const close = lastCandle[2];\n\n      // Aggregate high and low across the bucket.\n      let high = -Infinity;\n      let low = Infinity;\n      for (let i = rangeStart; i < rangeEndExclusive; i++) {\n        const candle = dataAsTuples[i]!;\n        const candleLow = candle[3];\n        const candleHigh = candle[4];\n        if (candleHigh > high) high = candleHigh;\n        if (candleLow < low) low = candleLow;\n      }\n\n      out[bucket + 1] = [timestamp, open, close, low, high];\n    }\n  } else {\n    // Object format path: { timestamp, open, close, low, high }\n    const dataAsObjects = data as ReadonlyArray<Exclude<OHLCDataPoint, OHLCDataPointTuple>>;\n\n    for (let bucket = 0; bucket < threshold - 2; bucket++) {\n      // Bucket range: [rangeStart, rangeEndExclusive)\n      let rangeStart = Math.floor(bucketSize * bucket) + 1;\n      let rangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 1)) + 1, n - 1);\n\n      // Defensive: ensure at least one candidate point.\n      if (rangeStart >= rangeEndExclusive) {\n        rangeStart = Math.min(rangeStart, n - 2);\n        rangeEndExclusive = Math.min(rangeStart + 1, n - 1);\n      }\n\n      // Extract first and last candles in bucket.\n      const firstCandle = dataAsObjects[rangeStart]!;\n      const lastCandle = dataAsObjects[rangeEndExclusive - 1]!;\n\n      const timestamp = firstCandle.timestamp;\n      const open = firstCandle.open;\n      const close = lastCandle.close;\n\n      // Aggregate high and low across the bucket.\n      let high = -Infinity;\n      let low = Infinity;\n      for (let i = rangeStart; i < rangeEndExclusive; i++) {\n        const candle = dataAsObjects[i]!;\n        const candleHigh = candle.high;\n        const candleLow = candle.low;\n        if (candleHigh > high) high = candleHigh;\n        if (candleLow < low) low = candleLow;\n      }\n\n      out[bucket + 1] = { timestamp, open, close, low, high };\n    }\n  }\n\n  return out;\n}\n","/**\n * Canvas sizing and measurement utilities for the RenderCoordinator.\n *\n * These pure functions handle canvas dimension retrieval with special handling\n * for device pixel ratio and GPU overlay coordinate conversions.\n *\n * @module canvasUtils\n */\n\n/**\n * Gets canvas CSS width - clientWidth for HTMLCanvasElement.\n *\n * @param canvas - The canvas element to measure, or null\n * @returns CSS width in pixels, or 0 if canvas is null\n */\nexport function getCanvasCssWidth(canvas: HTMLCanvasElement | null): number {\n  if (!canvas) {\n    return 0;\n  }\n\n  return canvas.clientWidth;\n}\n\n/**\n * Gets canvas CSS height - clientHeight for HTMLCanvasElement.\n *\n * @param canvas - The canvas element to measure, or null\n * @returns CSS height in pixels, or 0 if canvas is null\n */\nexport function getCanvasCssHeight(canvas: HTMLCanvasElement | null): number {\n  if (!canvas) {\n    return 0;\n  }\n\n  return canvas.clientHeight;\n}\n\n/**\n * Gets canvas CSS size derived strictly from device-pixel dimensions and DPR.\n *\n * This is intentionally different from `getCanvasCssWidth/Height(...)`:\n * - HTMLCanvasElement: `clientWidth/clientHeight` reflect DOM layout and can diverge (rounding, zoom, async resize)\n *   from the WebGPU render target size (`canvas.width/height` in device pixels).\n * - For GPU overlays that round-trip CSS↔device pixels in-shader, we must derive CSS size from\n *   `canvas.width/height` + DPR to keep transforms consistent with the render target.\n *\n * NOTE: Use this for GPU overlay coordinate conversion only (reference lines, markers).\n * Keep DOM overlays (labels/tooltips) using `clientWidth/clientHeight` for layout correctness.\n *\n * @param canvas - The canvas element to measure, or null\n * @param devicePixelRatio - The device pixel ratio (defaults to window.devicePixelRatio or 1)\n * @returns Object with width and height in CSS pixels derived from device pixels\n */\nexport function getCanvasCssSizeFromDevicePixels(\n  canvas: HTMLCanvasElement | null,\n  devicePixelRatio: number = typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n): Readonly<{ width: number; height: number }> {\n  if (!canvas) return { width: 0, height: 0 };\n  const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;\n  // HTMLCanvasElement exposes `.width/.height` in device pixels.\n  return { width: canvas.width / dpr, height: canvas.height / dpr };\n}\n\n/**\n * Clamps a value to an integer within [lo, hi] range.\n *\n * @param v - Value to clamp\n * @param lo - Lower bound (inclusive)\n * @param hi - Upper bound (inclusive)\n * @returns Clamped integer value\n */\nexport function clampInt(v: number, lo: number, hi: number): number {\n  return Math.min(hi, Math.max(lo, v | 0));\n}\n","/**\n * Visible Slice Computation Utilities\n *\n * Provides efficient data slicing for zoom operations using binary search\n * when data is monotonic, with fallback to linear filtering.\n *\n * Key features:\n * - Binary search slicing for O(log n) performance on sorted data\n * - WeakMap caching of monotonicity checks to avoid O(n) scans\n * - Separate implementations for cartesian (x-based) and OHLC (timestamp-based) data\n * - Support for DataPoint[], XYArraysData, and InterleavedXYData formats\n */\n\nimport type {\n  CartesianSeriesData,\n  DataPoint,\n  OHLCDataPoint,\n  OHLCDataPointTuple,\n  OHLCDataPointObject,\n  XYArraysData,\n  InterleavedXYData,\n} from '../../../config/types';\nimport { getPointCount, getX, getY } from '../../../data/cartesianData';\nimport { clampInt } from '../utils/canvasUtils';\n\n// Type guards for OHLC data\nexport function isTupleOHLCDataPoint(p: OHLCDataPoint): p is OHLCDataPointTuple {\n  return Array.isArray(p);\n}\n\n// Cache monotonicity checks to avoid O(n) scans on every zoom operation\n// WeakMap works for arrays, typed arrays, and object references (XYArraysData)\nconst monotonicXCache = new WeakMap<object, boolean>();\nconst monotonicTimestampCache = new WeakMap<ReadonlyArray<OHLCDataPoint>, boolean>();\n\n/**\n * Checks if cartesian data is monotonic non-decreasing by X coordinate with all finite values.\n * Results are cached in a WeakMap to avoid repeated O(n) scans.\n * \n * Supports all CartesianSeriesData formats: DataPoint[], XYArraysData, InterleavedXYData.\n */\nexport function isMonotonicNonDecreasingFiniteX(data: CartesianSeriesData): boolean {\n  // For primitive arrays and typed arrays, we can cache by object reference\n  // For XYArraysData, we cache by the object itself\n  const cacheKey = typeof data === 'object' && data !== null ? data : null;\n  if (cacheKey) {\n    const cached = monotonicXCache.get(cacheKey);\n    if (cached !== undefined) return cached;\n  }\n\n  let prevX = Number.NEGATIVE_INFINITY;\n  const n = getPointCount(data);\n\n  for (let i = 0; i < n; i++) {\n    const x = getX(data, i);\n    if (!Number.isFinite(x)) {\n      if (cacheKey) monotonicXCache.set(cacheKey, false);\n      return false;\n    }\n    if (x < prevX) {\n      if (cacheKey) monotonicXCache.set(cacheKey, false);\n      return false;\n    }\n    prevX = x;\n  }\n\n  if (cacheKey) monotonicXCache.set(cacheKey, true);\n  return true;\n}\n\n/**\n * Checks if OHLC data is monotonic non-decreasing by timestamp with all finite values.\n * Results are cached in a WeakMap to avoid repeated O(n) scans.\n */\nexport function isMonotonicNonDecreasingFiniteTimestamp(data: ReadonlyArray<OHLCDataPoint>): boolean {\n  const cached = monotonicTimestampCache.get(data);\n  if (cached !== undefined) return cached;\n\n  let prevTimestamp = Number.NEGATIVE_INFINITY;\n\n  for (let i = 0; i < data.length; i++) {\n    const p = data[i]!;\n    const timestamp = isTupleOHLCDataPoint(p) ? p[0] : p.timestamp;\n    if (!Number.isFinite(timestamp)) {\n      monotonicTimestampCache.set(data, false);\n      return false;\n    }\n    if (timestamp < prevTimestamp) {\n      monotonicTimestampCache.set(data, false);\n      return false;\n    }\n    prevTimestamp = timestamp;\n  }\n  monotonicTimestampCache.set(data, true);\n  return true;\n}\n\n// Binary search: lower bound (first element >= target) for CartesianSeriesData\nfunction lowerBoundX(data: CartesianSeriesData, xTarget: number): number {\n  let lo = 0;\n  let hi = getPointCount(data);\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const x = getX(data, mid);\n    if (x < xTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n}\n\n// Binary search: upper bound (first element > target) for CartesianSeriesData\nfunction upperBoundX(data: CartesianSeriesData, xTarget: number): number {\n  let lo = 0;\n  let hi = getPointCount(data);\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const x = getX(data, mid);\n    if (x <= xTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n}\n\nfunction lowerBoundTimestampTuple(data: ReadonlyArray<OHLCDataPointTuple>, timestampTarget: number): number {\n  let lo = 0;\n  let hi = data.length;\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const timestamp = data[mid][0];\n    if (timestamp < timestampTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n}\n\nfunction upperBoundTimestampTuple(data: ReadonlyArray<OHLCDataPointTuple>, timestampTarget: number): number {\n  let lo = 0;\n  let hi = data.length;\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const timestamp = data[mid][0];\n    if (timestamp <= timestampTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n}\n\nfunction lowerBoundTimestampObject(data: ReadonlyArray<OHLCDataPointObject>, timestampTarget: number): number {\n  let lo = 0;\n  let hi = data.length;\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const timestamp = data[mid].timestamp;\n    if (timestamp < timestampTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n}\n\nfunction upperBoundTimestampObject(data: ReadonlyArray<OHLCDataPointObject>, timestampTarget: number): number {\n  let lo = 0;\n  let hi = data.length;\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const timestamp = data[mid].timestamp;\n    if (timestamp <= timestampTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n}\n\n/**\n * Helper: Check if data is XYArraysData format.\n */\nfunction isXYArraysData(data: CartesianSeriesData): data is XYArraysData {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    !Array.isArray(data) &&\n    'x' in data &&\n    'y' in data &&\n    typeof (data as any).x === 'object' &&\n    typeof (data as any).y === 'object' &&\n    'length' in (data as any).x &&\n    'length' in (data as any).y\n  );\n}\n\n/**\n * Helper: Check if data is InterleavedXYData format (ArrayBufferView).\n */\nfunction isInterleavedXYData(data: CartesianSeriesData): data is InterleavedXYData {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    !Array.isArray(data) &&\n    ArrayBuffer.isView(data)\n  );\n}\n\n/**\n * Helper: Slice CartesianSeriesData to index range [start, end).\n * Returns appropriate view/slice for each format.\n */\nfunction sliceCartesianData(\n  data: CartesianSeriesData,\n  start: number,\n  end: number\n): CartesianSeriesData {\n  // Clamp indices\n  const n = getPointCount(data);\n  const s = Math.max(0, Math.min(start, n));\n  const e = Math.max(s, Math.min(end, n));\n\n  if (s === 0 && e === n) return data;\n  if (e <= s) {\n    // Return empty data in appropriate format\n    if (isXYArraysData(data)) {\n      return { x: [], y: [], ...(data.size ? { size: [] } : {}) };\n    }\n    if (isInterleavedXYData(data)) {\n      // Return empty view of same type\n      if (data instanceof DataView) {\n        throw new Error('DataView is not supported for InterleavedXYData');\n      }\n      const TypedArrayConstructor = (data as any).constructor;\n      return new TypedArrayConstructor(0);\n    }\n    return [];\n  }\n\n  // XYArraysData: slice x, y, and optional size arrays\n  if (isXYArraysData(data)) {\n    const xSliced = Array.isArray(data.x)\n      ? data.x.slice(s, e)\n      : 'subarray' in data.x\n      ? (data.x as any).subarray(s, e)\n      : Array.from(data.x).slice(s, e);\n\n    const ySliced = Array.isArray(data.y)\n      ? data.y.slice(s, e)\n      : 'subarray' in data.y\n      ? (data.y as any).subarray(s, e)\n      : Array.from(data.y).slice(s, e);\n\n    const result: XYArraysData = { x: xSliced, y: ySliced };\n\n    if (data.size) {\n      const sizeSliced = Array.isArray(data.size)\n        ? data.size.slice(s, e)\n        : 'subarray' in data.size\n        ? (data.size as any).subarray(s, e)\n        : Array.from(data.size).slice(s, e);\n      (result as any).size = sizeSliced;\n    }\n\n    return result;\n  }\n\n  // InterleavedXYData: return subarray view (start*2, end*2)\n  if (isInterleavedXYData(data)) {\n    if (data instanceof DataView) {\n      throw new Error('DataView is not supported for InterleavedXYData');\n    }\n    return (data as any).subarray(s * 2, e * 2);\n  }\n\n  // ReadonlyArray<DataPoint>: standard slice\n  return (data as ReadonlyArray<DataPoint>).slice(s, e);\n}\n\n/**\n * Slices cartesian data to the visible X range [xMin, xMax].\n *\n * Uses binary search (O(log n)) when data is monotonic by X;\n * otherwise falls back to linear filtering (O(n)).\n *\n * @param data - Cartesian data in any supported format\n * @param xMin - Minimum X value (inclusive)\n * @param xMax - Maximum X value (inclusive)\n * @returns Sliced data in the same format as input\n */\nexport function sliceVisibleRangeByX(\n  data: CartesianSeriesData,\n  xMin: number,\n  xMax: number\n): CartesianSeriesData {\n  const n = getPointCount(data);\n  if (n === 0) return data;\n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax)) return data;\n\n  const canBinarySearch = isMonotonicNonDecreasingFiniteX(data);\n\n  if (canBinarySearch) {\n    const lo = lowerBoundX(data, xMin);\n    const hi = upperBoundX(data, xMax);\n\n    if (lo <= 0 && hi >= n) return data;\n    return sliceCartesianData(data, lo, hi);\n  }\n\n  // Safe fallback: linear filter (preserves order, ignores non-finite x)\n  // For non-monotonic data, we must return a filtered array\n  const out: DataPoint[] = [];\n  for (let i = 0; i < n; i++) {\n    const x = getX(data, i);\n    if (!Number.isFinite(x)) continue;\n    if (x >= xMin && x <= xMax) {\n      const y = getY(data, i);\n      out.push([x, y]);\n    }\n  }\n  return out;\n}\n\n/**\n * Finds the index range of visible points in cartesian data.\n *\n * Returns { start, end } indices suitable for slicing or iteration.\n * Only works correctly when data is monotonic; returns full range otherwise.\n *\n * @param data - Cartesian data in any supported format\n * @param xMin - Minimum X value (inclusive)\n * @param xMax - Maximum X value (inclusive)\n * @returns Index range { start, end } for visible data\n */\nexport function findVisibleRangeIndicesByX(\n  data: CartesianSeriesData,\n  xMin: number,\n  xMax: number\n): { readonly start: number; readonly end: number } {\n  const n = getPointCount(data);\n  if (n === 0) return { start: 0, end: 0 };\n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax)) return { start: 0, end: n };\n\n  const canBinarySearch = isMonotonicNonDecreasingFiniteX(data);\n  if (!canBinarySearch) {\n    // Data is not monotonic by x; we can't represent the visible set as a contiguous index range\n    // Fall back to processing the full series for correctness\n    return { start: 0, end: n };\n  }\n\n  const start = lowerBoundX(data, xMin);\n  const end = upperBoundX(data, xMax);\n\n  const s = clampInt(start, 0, n);\n  const e = clampInt(end, 0, n);\n  return e <= s ? { start: s, end: s } : { start: s, end: e };\n}\n\n/**\n * Slices OHLC/candlestick data to the visible timestamp range [xMin, xMax].\n *\n * Uses binary search (O(log n)) when timestamps are monotonic;\n * otherwise falls back to linear filtering (O(n)).\n *\n * @param data - OHLC data points (tuple or object format)\n * @param xMin - Minimum timestamp (inclusive)\n * @param xMax - Maximum timestamp (inclusive)\n * @returns Sliced data array containing only points within [xMin, xMax]\n */\nexport function sliceVisibleRangeByOHLC(\n  data: ReadonlyArray<OHLCDataPoint>,\n  xMin: number,\n  xMax: number\n): ReadonlyArray<OHLCDataPoint> {\n  const n = data.length;\n  if (n === 0) return data;\n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax)) return data;\n\n  const canBinarySearch = isMonotonicNonDecreasingFiniteTimestamp(data);\n  const isTuple = n > 0 && isTupleOHLCDataPoint(data[0]!);\n\n  if (canBinarySearch) {\n    const lo = isTuple\n      ? lowerBoundTimestampTuple(data as ReadonlyArray<OHLCDataPointTuple>, xMin)\n      : lowerBoundTimestampObject(data as ReadonlyArray<OHLCDataPointObject>, xMin);\n    const hi = isTuple\n      ? upperBoundTimestampTuple(data as ReadonlyArray<OHLCDataPointTuple>, xMax)\n      : upperBoundTimestampObject(data as ReadonlyArray<OHLCDataPointObject>, xMax);\n\n    if (lo <= 0 && hi >= n) return data;\n    if (hi <= lo) return [];\n    return data.slice(lo, hi);\n  }\n\n  // Safe fallback: linear filter (preserves order, ignores non-finite timestamp)\n  const out: OHLCDataPoint[] = [];\n  for (let i = 0; i < n; i++) {\n    const p = data[i]!;\n    const timestamp = isTupleOHLCDataPoint(p) ? p[0] : p.timestamp;\n    if (!Number.isFinite(timestamp)) continue;\n    if (timestamp >= xMin && timestamp <= xMax) out.push(p);\n  }\n  return out;\n}\n","export type Rgba01 = readonly [r: number, g: number, b: number, a: number];\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst clamp255 = (v: number): number => Math.min(255, Math.max(0, v));\n\nconst parseHexNibble = (hex: string): number => {\n  const n = Number.parseInt(hex, 16);\n  return Number.isFinite(n) ? n : 0;\n};\n\nconst parseHexByte = (hex: string): number => {\n  const n = Number.parseInt(hex, 16);\n  return Number.isFinite(n) ? n : 0;\n};\n\nconst parseHexColorToRgba01 = (color: string): Rgba01 | null => {\n  const c = color.trim();\n  if (!c.startsWith('#')) return null;\n\n  const hex = c.slice(1);\n\n  // #rgb\n  if (hex.length === 3) {\n    const r = parseHexNibble(hex[0]);\n    const g = parseHexNibble(hex[1]);\n    const b = parseHexNibble(hex[2]);\n    return [(r * 17) / 255, (g * 17) / 255, (b * 17) / 255, 1];\n  }\n\n  // #rgba\n  if (hex.length === 4) {\n    const r = parseHexNibble(hex[0]);\n    const g = parseHexNibble(hex[1]);\n    const b = parseHexNibble(hex[2]);\n    const a = parseHexNibble(hex[3]);\n    return [(r * 17) / 255, (g * 17) / 255, (b * 17) / 255, (a * 17) / 255];\n  }\n\n  // #rrggbb\n  if (hex.length === 6) {\n    const r = parseHexByte(hex.slice(0, 2));\n    const g = parseHexByte(hex.slice(2, 4));\n    const b = parseHexByte(hex.slice(4, 6));\n    return [r / 255, g / 255, b / 255, 1];\n  }\n\n  // #rrggbbaa\n  if (hex.length === 8) {\n    const r = parseHexByte(hex.slice(0, 2));\n    const g = parseHexByte(hex.slice(2, 4));\n    const b = parseHexByte(hex.slice(4, 6));\n    const a = parseHexByte(hex.slice(6, 8));\n    return [r / 255, g / 255, b / 255, a / 255];\n  }\n\n  return null;\n};\n\nconst parseRgbNumberOrPercent = (token: string): number | null => {\n  const t = token.trim();\n  if (t.length === 0) return null;\n\n  if (t.endsWith('%')) {\n    const n = Number.parseFloat(t.slice(0, -1));\n    if (!Number.isFinite(n)) return null;\n    return clamp255((n / 100) * 255);\n  }\n\n  const n = Number.parseFloat(t);\n  if (!Number.isFinite(n)) return null;\n  return clamp255(n);\n};\n\nconst parseAlphaNumberOrPercent = (token: string): number | null => {\n  const t = token.trim();\n  if (t.length === 0) return null;\n\n  if (t.endsWith('%')) {\n    const n = Number.parseFloat(t.slice(0, -1));\n    if (!Number.isFinite(n)) return null;\n    return clamp01(n / 100);\n  }\n\n  const n = Number.parseFloat(t);\n  if (!Number.isFinite(n)) return null;\n  return clamp01(n);\n};\n\nconst parseRgbFuncToRgba01 = (color: string): Rgba01 | null => {\n  const c = color.trim();\n  const m = /^(rgba?|RGBA?)\\(\\s*([^\\)]*)\\s*\\)$/.exec(c);\n  if (!m) return null;\n\n  const fn = m[1].toLowerCase();\n  const argsRaw = m[2];\n\n  // Requirement scope: support comma-separated rgb()/rgba().\n  // (We intentionally do not attempt full CSS Color 4 space-separated syntax here.)\n  const parts = argsRaw.split(',').map((p) => p.trim());\n  if (fn === 'rgb') {\n    if (parts.length !== 3) return null;\n    const r = parseRgbNumberOrPercent(parts[0]);\n    const g = parseRgbNumberOrPercent(parts[1]);\n    const b = parseRgbNumberOrPercent(parts[2]);\n    if (r == null || g == null || b == null) return null;\n    return [r / 255, g / 255, b / 255, 1];\n  }\n\n  if (fn === 'rgba') {\n    if (parts.length !== 4) return null;\n    const r = parseRgbNumberOrPercent(parts[0]);\n    const g = parseRgbNumberOrPercent(parts[1]);\n    const b = parseRgbNumberOrPercent(parts[2]);\n    const a = parseAlphaNumberOrPercent(parts[3]);\n    if (r == null || g == null || b == null || a == null) return null;\n    return [r / 255, g / 255, b / 255, a];\n  }\n\n  return null;\n};\n\n/**\n * Parse a CSS color string into RGBA floats in [0..1].\n *\n * Supported:\n * - #rgb / #rgba / #rrggbb / #rrggbbaa\n * - rgb(r,g,b)\n * - rgba(r,g,b,a)\n *\n * Returns null when parsing fails.\n */\nexport const parseCssColorToRgba01 = (color: string): Rgba01 | null => {\n  if (typeof color !== 'string') return null;\n  const c = color.trim();\n  if (c.length === 0) return null;\n\n  const hex = parseHexColorToRgba01(c);\n  if (hex) return hex;\n\n  const rgb = parseRgbFuncToRgba01(c);\n  if (rgb) return rgb;\n\n  return null;\n};\n\nexport const parseCssColorToGPUColor = (\n  color: string,\n  fallback: GPUColor = { r: 0, g: 0, b: 0, a: 1 }\n): GPUColor => {\n  const rgba = parseCssColorToRgba01(color);\n  if (!rgba) return fallback;\n  const [r, g, b, a] = rgba;\n  return { r, g, b, a };\n};\n\n","/**\n * Data point type guards and utilities for the RenderCoordinator.\n *\n * These pure functions handle the dual data format system (tuple vs object)\n * and provide type-safe access to point coordinates.\n *\n * @module dataPointUtils\n */\n\nimport type { DataPoint, DataPointTuple, OHLCDataPoint, OHLCDataPointTuple } from '../../../config/types';\n\n/**\n * Validates that a number is finite, returning the number or null.\n *\n * @param v - Value to validate\n * @returns The number if finite, otherwise null\n */\nexport const finiteOrNull = (v: number | null | undefined): number | null =>\n  typeof v === 'number' && Number.isFinite(v) ? v : null;\n\n/**\n * Validates that a number is finite, returning the number or undefined.\n *\n * @param v - Value to validate\n * @returns The number if finite, otherwise undefined\n */\nexport const finiteOrUndefined = (v: number | undefined): number | undefined =>\n  typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n\n/**\n * Compile-time exhaustiveness check for error handling.\n * Used in switch statements to ensure all cases are handled.\n *\n * @param value - The value that should be of type `never` if all cases are handled\n * @throws Always throws an error if called\n */\nexport const assertUnreachable = (value: never): never => {\n  // Intentionally minimal message: this is used for compile-time exhaustiveness.\n  throw new Error(`RenderCoordinator: unreachable value: ${String(value)}`);\n};\n\n/**\n * Type guard: checks if a DataPoint is in tuple form `[x, y]`.\n *\n * @param p - The data point to check\n * @returns True if the point is a tuple, false if it's an object\n */\nexport const isTupleDataPoint = (p: DataPoint): p is DataPointTuple => Array.isArray(p);\n\n/**\n * Extracts x,y coordinates from either tuple or object point format.\n *\n * @param p - The data point (either tuple or object format)\n * @returns Object with x and y properties\n */\nexport const getPointXY = (p: DataPoint): { readonly x: number; readonly y: number } => {\n  if (isTupleDataPoint(p)) return { x: p[0], y: p[1] };\n  return { x: p.x, y: p.y };\n};\n\n/**\n * Type guard: checks if a data point is in tuple OHLC form `[timestamp, open, high, low, close]`.\n *\n * @param p - The OHLC data point to check\n * @returns True if the point is a tuple, false if it's an object\n */\nexport const isTupleOHLCDataPoint = (p: OHLCDataPoint): p is OHLCDataPointTuple => Array.isArray(p);\n\n/**\n * Type guard: checks if a data point is in tuple form `[x, y]` (for individual points).\n *\n * @param p - The point to check\n * @returns True if the point is a tuple array\n */\nexport const isTuplePoint = (p: unknown): p is readonly [number, number] => Array.isArray(p);\n\n/**\n * Type guard: checks if entire data array is in tuple format.\n * Only checks the first element for performance.\n *\n * @param data - Array of data points to check\n * @returns True if first element (and therefore likely all) is a tuple\n */\nexport const isTupleDataArray = (data: ReadonlyArray<DataPoint>): data is ReadonlyArray<DataPointTuple> =>\n  data.length > 0 && isTupleDataPoint(data[0]);\n","/**\n * Axis and grid utilities for the RenderCoordinator.\n *\n * These pure functions handle coordinate transformations between different spaces:\n * - CSS pixels (DOM layout)\n * - Device pixels (canvas.width/height)\n * - Normalized device coordinates / clip space (WebGPU [-1, 1])\n *\n * @module axisUtils\n */\n\nimport type { GPUContextLike } from '../types';\nimport type { ResolvedChartGPUOptions } from '../../../config/OptionResolver';\nimport type { GridArea } from '../../../renderers/createGridRenderer';\nimport { parseCssColorToRgba01 } from '../../../utils/colors';\nimport { normalizeDomain } from './boundsComputation';\nimport { clampInt } from './canvasUtils';\n\n/**\n * Computes grid area with margins and canvas dimensions for rendering layout.\n * GridArea uses:\n * - Margins (left, right, top, bottom) in CSS pixels\n * - Canvas dimensions (canvasWidth, canvasHeight) in DEVICE pixels\n * - devicePixelRatio for CSS-to-device conversion\n *\n * @param gpuContext - GPU context with canvas and device pixel ratio\n * @param options - Resolved chart options with grid margins\n * @returns GridArea object with margins, canvas dimensions, and DPR\n * @throws If canvas is null or has invalid dimensions\n */\nexport const computeGridArea = (gpuContext: GPUContextLike, options: ResolvedChartGPUOptions): GridArea => {\n  const canvas = gpuContext.canvas;\n  if (!canvas) throw new Error('RenderCoordinator: gpuContext.canvas is required.');\n\n  const dpr = gpuContext.devicePixelRatio ?? 1;\n  const devicePixelRatio = (Number.isFinite(dpr) && dpr > 0) ? dpr : 1;\n\n  // Validate and sanitize canvas dimensions (device pixels)\n  const rawCanvasWidth = canvas.width;\n  const rawCanvasHeight = canvas.height;\n\n  if (!Number.isFinite(rawCanvasWidth) || !Number.isFinite(rawCanvasHeight)) {\n    throw new Error(\n      `RenderCoordinator: Invalid canvas dimensions: width=${rawCanvasWidth}, height=${rawCanvasHeight}. ` +\n      `Canvas must be initialized with finite dimensions before rendering.`\n    );\n  }\n\n  // Be resilient: charts may be mounted into 0-sized containers (e.g. display:none during init).\n  // Renderers guard internally; clamping avoids hard crashes and allows future resize to recover.\n  const canvasWidth = Math.max(1, Math.floor(rawCanvasWidth));\n  const canvasHeight = Math.max(1, Math.floor(rawCanvasHeight));\n\n  // Validate and sanitize grid margins (CSS pixels)\n  const left = Number.isFinite(options.grid.left) ? options.grid.left : 0;\n  const right = Number.isFinite(options.grid.right) ? options.grid.right : 0;\n  const top = Number.isFinite(options.grid.top) ? options.grid.top : 0;\n  const bottom = Number.isFinite(options.grid.bottom) ? options.grid.bottom : 0;\n\n  // Ensure margins are non-negative (negative margins could cause rendering issues)\n  const sanitizedLeft = Math.max(0, left);\n  const sanitizedRight = Math.max(0, right);\n  const sanitizedTop = Math.max(0, top);\n  const sanitizedBottom = Math.max(0, bottom);\n\n  return {\n    left: sanitizedLeft,\n    right: sanitizedRight,\n    top: sanitizedTop,\n    bottom: sanitizedBottom,\n    canvasWidth,                      // Device pixels (clamped above)\n    canvasHeight,                     // Device pixels (clamped above)\n    devicePixelRatio,                 // Explicit DPR (validated above)\n  };\n};\n\n/**\n * Converts RGBA normalized [0-1] values to CSS rgba() string.\n *\n * @param rgba - Array of [r, g, b, a] in range [0, 1]\n * @returns CSS rgba() string\n */\nexport const rgba01ToCssRgba = (rgba: readonly [number, number, number, number]): string => {\n  const r = Math.max(0, Math.min(255, Math.round(rgba[0] * 255)));\n  const g = Math.max(0, Math.min(255, Math.round(rgba[1] * 255)));\n  const b = Math.max(0, Math.min(255, Math.round(rgba[2] * 255)));\n  const a = Math.max(0, Math.min(1, rgba[3]));\n  return `rgba(${r},${g},${b},${a})`;\n};\n\n/**\n * Applies alpha multiplier to CSS color.\n * Parses color, multiplies alpha channel, and returns new CSS rgba() string.\n *\n * @param cssColor - CSS color string\n * @param alphaMultiplier - Alpha multiplier in range [0, 1]\n * @returns CSS rgba() string with modified alpha, or original color if parse fails\n */\nexport const withAlpha = (cssColor: string, alphaMultiplier: number): string => {\n  const parsed = parseCssColorToRgba01(cssColor);\n  if (!parsed) return cssColor;\n  const a = Math.max(0, Math.min(1, parsed[3] * alphaMultiplier));\n  return rgba01ToCssRgba([parsed[0], parsed[1], parsed[2], a]);\n};\n\n/**\n * Converts grid margins to normalized device clip coordinates for WebGPU.\n * Output is in WebGPU clip space: [-1, 1] for both x and y.\n * Y-axis is flipped (top is positive, bottom is negative).\n *\n * @param gridArea - Grid area with margins and canvas dimensions\n * @returns Clip rect with left, right, top, bottom in range [-1, 1]\n */\nexport const computePlotClipRect = (\n  gridArea: GridArea\n): { readonly left: number; readonly right: number; readonly top: number; readonly bottom: number } => {\n  const { left, right, top, bottom, canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeft = left * devicePixelRatio;\n  const plotRight = canvasWidth - right * devicePixelRatio;\n  const plotTop = top * devicePixelRatio;\n  const plotBottom = canvasHeight - bottom * devicePixelRatio;\n\n  const plotLeftClip = (plotLeft / canvasWidth) * 2.0 - 1.0;\n  const plotRightClip = (plotRight / canvasWidth) * 2.0 - 1.0;\n  const plotTopClip = 1.0 - (plotTop / canvasHeight) * 2.0; // flip Y\n  const plotBottomClip = 1.0 - (plotBottom / canvasHeight) * 2.0; // flip Y\n\n  return {\n    left: plotLeftClip,\n    right: plotRightClip,\n    top: plotTopClip,\n    bottom: plotBottomClip,\n  };\n};\n\n/**\n * Clamps value to [0, 1] range.\n *\n * @param v - Value to clamp\n * @returns Clamped value\n */\nexport const clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\n\n/**\n * Linear interpolation between two values with clamped t.\n *\n * @param a - Start value\n * @param b - End value\n * @param t01 - Interpolation parameter in range [0, 1]\n * @returns Interpolated value\n */\nexport const lerp = (a: number, b: number, t01: number): number => a + (b - a) * clamp01(t01);\n\n/**\n * Interpolates between two domains (min/max pairs).\n * Ensures result is a valid domain (min ≤ max, both finite).\n *\n * @param from - Start domain\n * @param to - End domain\n * @param t01 - Interpolation parameter in range [0, 1]\n * @returns Interpolated domain\n */\nexport const lerpDomain = (\n  from: { readonly min: number; readonly max: number },\n  to: { readonly min: number; readonly max: number },\n  t01: number\n): { readonly min: number; readonly max: number } => {\n  return normalizeDomain(lerp(from.min, to.min, t01), lerp(from.max, to.max, t01));\n};\n\n/**\n * Computes scissor rect in device pixels from grid margins.\n * Used for WebGPU scissor testing to clip rendering to plot area.\n *\n * @param gridArea - Grid area with margins and canvas dimensions\n * @returns Scissor rect with x, y, w, h in device pixels\n */\nexport const computePlotScissorDevicePx = (\n  gridArea: GridArea\n): { readonly x: number; readonly y: number; readonly w: number; readonly h: number } => {\n  const { canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeftDevice = gridArea.left * devicePixelRatio;\n  const plotRightDevice = canvasWidth - gridArea.right * devicePixelRatio;\n  const plotTopDevice = gridArea.top * devicePixelRatio;\n  const plotBottomDevice = canvasHeight - gridArea.bottom * devicePixelRatio;\n\n  const scissorX = clampInt(Math.floor(plotLeftDevice), 0, Math.max(0, canvasWidth));\n  const scissorY = clampInt(Math.floor(plotTopDevice), 0, Math.max(0, canvasHeight));\n  const scissorR = clampInt(Math.ceil(plotRightDevice), 0, Math.max(0, canvasWidth));\n  const scissorB = clampInt(Math.ceil(plotBottomDevice), 0, Math.max(0, canvasHeight));\n  const scissorW = Math.max(0, scissorR - scissorX);\n  const scissorH = Math.max(0, scissorB - scissorY);\n\n  return { x: scissorX, y: scissorY, w: scissorW, h: scissorH };\n};\n\n/**\n * Converts clip-space X to canvas CSS pixels (from normalized [-1, 1]).\n *\n * @param xClip - X coordinate in clip space [-1, 1]\n * @param canvasCssWidth - Canvas width in CSS pixels\n * @returns X coordinate in canvas CSS pixels\n */\nexport const clipXToCanvasCssPx = (xClip: number, canvasCssWidth: number): number => ((xClip + 1) / 2) * canvasCssWidth;\n\n/**\n * Converts clip-space Y to canvas CSS pixels (from normalized [-1, 1]).\n * Y-axis is flipped (1 is top, -1 is bottom).\n *\n * @param yClip - Y coordinate in clip space [-1, 1]\n * @param canvasCssHeight - Canvas height in CSS pixels\n * @returns Y coordinate in canvas CSS pixels\n */\nexport const clipYToCanvasCssPx = (yClip: number, canvasCssHeight: number): number => ((1 - yClip) / 2) * canvasCssHeight;\n","/**\n * Time axis and formatting utilities for the RenderCoordinator.\n *\n * These pure functions handle time-based tick generation, adaptive label formatting,\n * and number/percentage parsing for pie chart configuration.\n *\n * @module timeAxisUtils\n */\n\nimport type { LinearScale } from '../../../utils/scales';\nimport type { TextOverlayAnchor } from '../../../components/createTextOverlay';\nimport type { PieCenter, PieRadius } from '../../../config/types';\nimport { clipXToCanvasCssPx } from './axisUtils';\nimport { finiteOrNull } from './dataPointUtils';\n\n/**\n * Time constants for axis formatting decisions.\n */\nexport const MS_PER_DAY = 24 * 60 * 60 * 1000;\nexport const MS_PER_MONTH_APPROX = 30 * MS_PER_DAY;\nexport const MS_PER_YEAR_APPROX = 365 * MS_PER_DAY;\n\n/**\n * Tick configuration constants.\n */\nexport const MAX_TIME_X_TICK_COUNT = 9;\nexport const MIN_TIME_X_TICK_COUNT = 1;\nexport const MIN_X_LABEL_GAP_CSS_PX = 6;\nexport const DEFAULT_MAX_TICK_FRACTION_DIGITS = 6;\nexport const DEFAULT_TICK_COUNT = 5;\n\n/**\n * English month abbreviations for time axis labels.\n */\nexport const MONTH_SHORT_EN: readonly string[] = [\n  'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\n  'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',\n];\n\n/**\n * Parses value as number or percentage string, returns null if invalid.\n * Used for pie chart center and radius configuration.\n *\n * @param value - Number or percentage string (e.g. \"50%\", \"120\", 120)\n * @param basis - Basis value for percentage calculation\n * @returns Parsed number or null if invalid\n */\nexport const parseNumberOrPercent = (value: number | string, basis: number): number | null => {\n  if (typeof value === 'number') return Number.isFinite(value) ? value : null;\n  if (typeof value !== 'string') return null;\n\n  const s = value.trim();\n  if (s.length === 0) return null;\n\n  if (s.endsWith('%')) {\n    const pct = Number.parseFloat(s.slice(0, -1));\n    if (!Number.isFinite(pct)) return null;\n    return (pct / 100) * basis;\n  }\n\n  // Be permissive: allow numeric strings like \"120\".\n  const n = Number.parseFloat(s);\n  return Number.isFinite(n) ? n : null;\n};\n\n/**\n * Resolves pie center from mixed number/string/percent format.\n * Defaults to center of plot area (50%, 50%).\n *\n * @param center - Pie center configuration or undefined\n * @param plotWidthCss - Plot area width in CSS pixels\n * @param plotHeightCss - Plot area height in CSS pixels\n * @returns Resolved center coordinates in CSS pixels\n */\nexport const resolvePieCenterPlotCss = (\n  center: PieCenter | undefined,\n  plotWidthCss: number,\n  plotHeightCss: number\n): { readonly x: number; readonly y: number } => {\n  const xRaw = center?.[0] ?? '50%';\n  const yRaw = center?.[1] ?? '50%';\n\n  const x = parseNumberOrPercent(xRaw, plotWidthCss);\n  const y = parseNumberOrPercent(yRaw, plotHeightCss);\n\n  return {\n    x: Number.isFinite(x) ? x! : plotWidthCss * 0.5,\n    y: Number.isFinite(y) ? y! : plotHeightCss * 0.5,\n  };\n};\n\n/**\n * Type guard for pie radius tuple format `[inner, outer]`.\n *\n * @param radius - Pie radius configuration\n * @returns True if radius is a tuple\n */\nexport const isPieRadiusTuple = (\n  radius: PieRadius\n): radius is readonly [inner: number | string, outer: number | string] => Array.isArray(radius);\n\n/**\n * Resolves pie inner/outer radii with defaults, bounds checking.\n * Default outer radius is 70% of max, inner radius is 0 (full pie).\n *\n * @param radius - Pie radius configuration or undefined\n * @param maxRadiusCss - Maximum radius in CSS pixels\n * @returns Resolved inner and outer radii in CSS pixels\n */\nexport const resolvePieRadiiCss = (\n  radius: PieRadius | undefined,\n  maxRadiusCss: number\n): { readonly inner: number; readonly outer: number } => {\n  // Default similar to common chart libs (mirrors `createPieRenderer.ts`).\n  if (radius == null) return { inner: 0, outer: maxRadiusCss * 0.7 };\n\n  if (isPieRadiusTuple(radius)) {\n    const inner = parseNumberOrPercent(radius[0], maxRadiusCss);\n    const outer = parseNumberOrPercent(radius[1], maxRadiusCss);\n    const innerCss = Math.max(0, Number.isFinite(inner) ? inner! : 0);\n    const outerCss = Math.max(innerCss, Number.isFinite(outer) ? outer! : maxRadiusCss * 0.7);\n    return { inner: innerCss, outer: Math.min(maxRadiusCss, outerCss) };\n  }\n\n  const outer = parseNumberOrPercent(radius, maxRadiusCss);\n  const outerCss = Math.max(0, Number.isFinite(outer) ? outer! : maxRadiusCss * 0.7);\n  return { inner: 0, outer: Math.min(maxRadiusCss, outerCss) };\n};\n\n/**\n * Calculates decimal precision needed for clean tick formatting from tick step.\n * Prefers \"clean\" decimal representations (e.g. 2.5, 0.25, 0.125) without relying on magnitude alone.\n *\n * @param tickStep - Step size between ticks\n * @param cap - Maximum fraction digits to return (default 6)\n * @returns Number of fraction digits for formatting\n */\nexport const computeMaxFractionDigitsFromStep = (tickStep: number, cap: number = DEFAULT_MAX_TICK_FRACTION_DIGITS): number => {\n  const stepAbs = Math.abs(tickStep);\n  if (!Number.isFinite(stepAbs) || stepAbs === 0) return 0;\n\n  // Prefer \"clean\" decimal representations (e.g. 2.5, 0.25, 0.125) without relying on magnitude alone.\n  // We accept floating-point noise and cap the search to keep formatting reasonable.\n  for (let d = 0; d <= cap; d++) {\n    const scaled = stepAbs * 10 ** d;\n    const rounded = Math.round(scaled);\n    const err = Math.abs(scaled - rounded);\n    const tol = 1e-9 * Math.max(1, Math.abs(scaled));\n    if (err <= tol) return d;\n  }\n\n  // Fallback for repeating decimals (e.g. 1/3): show a small number of digits based on magnitude.\n  // The +1 nudges values like 0.333.. towards 2 decimals rather than 1.\n  return Math.max(0, Math.min(cap, Math.ceil(-Math.log10(stepAbs)) + 1));\n};\n\n/**\n * Creates Intl.NumberFormat instance for consistent tick formatting.\n * Automatically computes appropriate fraction digits from tick step.\n *\n * @param tickStep - Step size between ticks\n * @returns NumberFormat instance\n */\nexport const createTickFormatter = (tickStep: number): Intl.NumberFormat => {\n  const maximumFractionDigits = computeMaxFractionDigitsFromStep(tickStep);\n  return new Intl.NumberFormat(undefined, { maximumFractionDigits });\n};\n\n/**\n * Formats numeric value using NumberFormat, handles -0 and NaN edge cases.\n *\n * @param nf - NumberFormat instance\n * @param v - Value to format\n * @returns Formatted string or null if invalid\n */\nexport const formatTickValue = (nf: Intl.NumberFormat, v: number): string | null => {\n  if (!Number.isFinite(v)) return null;\n  // Avoid displaying \"-0\" from floating-point artifacts.\n  const normalized = Math.abs(v) < 1e-12 ? 0 : v;\n  const formatted = nf.format(normalized);\n  // Guard against unexpected output like \"NaN\" even after the finite check (defensive).\n  return formatted === 'NaN' ? null : formatted;\n};\n\n/**\n * Pads single-digit numbers with leading zero (used by time formatting).\n *\n * @param n - Number to pad\n * @returns Zero-padded string (minimum 2 digits)\n */\nexport const pad2 = (n: number): string => String(Math.trunc(n)).padStart(2, '0');\n\n/**\n * Formats millisecond timestamps with adaptive precision based on visible range.\n * Format tiers:\n * - < 1 day: HH:mm\n * - 1-7 days: MM/DD HH:mm\n * - 1-12 weeks (up to ~3 months): MM/DD\n * - 3-12 months: MMM DD\n * - > 1 year: YYYY/MM\n *\n * @param timestampMs - Timestamp in milliseconds\n * @param visibleRangeMs - Visible range width in milliseconds\n * @returns Formatted time string or null if invalid\n */\nexport const formatTimeTickValue = (timestampMs: number, visibleRangeMs: number): string | null => {\n  if (!Number.isFinite(timestampMs)) return null;\n  if (!Number.isFinite(visibleRangeMs) || visibleRangeMs < 0) visibleRangeMs = 0;\n\n  const d = new Date(timestampMs);\n  // Guard against out-of-range timestamps that produce an invalid Date.\n  if (!Number.isFinite(d.getTime())) return null;\n  const yyyy = d.getFullYear();\n  const mm = d.getMonth() + 1; // 1-12\n  const dd = d.getDate();\n  const hh = d.getHours();\n  const min = d.getMinutes();\n\n  if (visibleRangeMs < MS_PER_DAY) {\n    return `${pad2(hh)}:${pad2(min)}`;\n  }\n  // Treat the 7-day boundary as inclusive for the \"1–7 days\" tier.\n  if (visibleRangeMs <= 7 * MS_PER_DAY) {\n    return `${pad2(mm)}/${pad2(dd)} ${pad2(hh)}:${pad2(min)}`;\n  }\n  // Keep short calendar dates until the visible range reaches ~3 months.\n  if (visibleRangeMs < 3 * MS_PER_MONTH_APPROX) {\n    return `${pad2(mm)}/${pad2(dd)}`;\n  }\n  if (visibleRangeMs <= MS_PER_YEAR_APPROX) {\n    const mmm = MONTH_SHORT_EN[d.getMonth()] ?? pad2(mm);\n    return `${mmm} ${pad2(dd)}`;\n  }\n  return `${yyyy}/${pad2(mm)}`;\n};\n\n/**\n * Generates evenly-spaced tick values across domain.\n *\n * @param domainMin - Domain minimum value\n * @param domainMax - Domain maximum value\n * @param tickCount - Number of ticks to generate\n * @returns Array of tick values\n */\nexport const generateLinearTicks = (domainMin: number, domainMax: number, tickCount: number): number[] => {\n  const count = Math.max(1, Math.floor(tickCount));\n  const ticks: number[] = new Array(count);\n  for (let i = 0; i < count; i++) {\n    const t = count === 1 ? 0.5 : i / (count - 1);\n    ticks[i] = domainMin + t * (domainMax - domainMin);\n  }\n  return ticks;\n};\n\n/**\n * Computes optimal tick count + values to avoid label overlap on time x-axis.\n * Uses text measurement context to test label widths.\n * Tries tick counts from MAX (9) down to MIN (1) until labels fit without overlap.\n *\n * @param params - Configuration object with axis, scale, canvas, and measurement settings\n * @returns Object with tickCount and tickValues\n */\nexport const computeAdaptiveTimeXAxisTicks = (params: {\n  readonly axisMin: number | null;\n  readonly axisMax: number | null;\n  readonly xScale: LinearScale;\n  readonly plotClipLeft: number;\n  readonly plotClipRight: number;\n  readonly canvasCssWidth: number;\n  readonly visibleRangeMs: number;\n  readonly measureCtx: CanvasRenderingContext2D | null;\n  readonly measureCache?: Map<string, number>;\n  readonly fontSize: number;\n  readonly fontFamily: string;\n  readonly tickFormatter?: (value: number) => string | null;\n}): { readonly tickCount: number; readonly tickValues: readonly number[] } => {\n  const {\n    axisMin,\n    axisMax,\n    xScale,\n    plotClipLeft,\n    plotClipRight,\n    canvasCssWidth,\n    visibleRangeMs,\n    measureCtx,\n    measureCache,\n    fontSize,\n    fontFamily,\n    tickFormatter,\n  } = params;\n\n  // Domain fallback matches `createAxisRenderer` (use explicit min/max when provided).\n  const domainMin = finiteOrNull(axisMin) ?? xScale.invert(plotClipLeft);\n  const domainMax = finiteOrNull(axisMax) ?? xScale.invert(plotClipRight);\n\n  if (!measureCtx || canvasCssWidth <= 0) {\n    return { tickCount: DEFAULT_TICK_COUNT, tickValues: generateLinearTicks(domainMin, domainMax, DEFAULT_TICK_COUNT) };\n  }\n\n  // Ensure the measurement font matches the overlay labels.\n  measureCtx.font = `${fontSize}px ${fontFamily}`;\n  if (measureCache && measureCache.size > 2000) measureCache.clear();\n\n  // Pre-construct the font part of the cache key to avoid repeated concatenation.\n  const cacheKeyPrefix = measureCache ? `${fontSize}px ${fontFamily}@@` : null;\n\n  for (let tickCount = MAX_TIME_X_TICK_COUNT; tickCount >= MIN_TIME_X_TICK_COUNT; tickCount--) {\n    const tickValues = generateLinearTicks(domainMin, domainMax, tickCount);\n\n    // Compute label extents in *canvas-local CSS px* and ensure adjacent labels don't overlap.\n    let prevRight = Number.NEGATIVE_INFINITY;\n    let ok = true;\n\n    for (let i = 0; i < tickValues.length; i++) {\n      const v = tickValues[i]!;\n      const label = tickFormatter ? tickFormatter(v) : formatTimeTickValue(v, visibleRangeMs);\n      if (label == null) continue;\n\n      const w = (() => {\n        if (!cacheKeyPrefix) return measureCtx.measureText(label).width;\n        const key = cacheKeyPrefix + label;\n        const cached = measureCache!.get(key);\n        if (cached != null) return cached;\n        const measured = measureCtx.measureText(label).width;\n        measureCache!.set(key, measured);\n        return measured;\n      })();\n      const xClip = xScale.scale(v);\n      const xCss = clipXToCanvasCssPx(xClip, canvasCssWidth);\n\n      const anchor: TextOverlayAnchor =\n        tickCount === 1 ? 'middle' : i === 0 ? 'start' : i === tickValues.length - 1 ? 'end' : 'middle';\n\n      const left = anchor === 'start' ? xCss : anchor === 'end' ? xCss - w : xCss - w * 0.5;\n      const right = anchor === 'start' ? xCss + w : anchor === 'end' ? xCss : xCss + w * 0.5;\n\n      if (left < prevRight + MIN_X_LABEL_GAP_CSS_PX) {\n        ok = false;\n        break;\n      }\n      prevRight = right;\n    }\n\n    if (ok) {\n      return { tickCount, tickValues };\n    }\n  }\n\n  return { tickCount: MIN_TIME_X_TICK_COUNT, tickValues: generateLinearTicks(domainMin, domainMax, MIN_TIME_X_TICK_COUNT) };\n};\n","/**\n * Axis tick computation and formatting.\n *\n * Generates tick values and formatting for linear axes. Handles decimal precision\n * determination based on tick step size and provides number formatting utilities.\n *\n * @module computeAxisTicks\n */\n\n/**\n * Default maximum fraction digits for tick formatting.\n */\nconst DEFAULT_MAX_TICK_FRACTION_DIGITS = 8;\n\n/**\n * Generates evenly-spaced tick values between domain min and max.\n *\n * @param domainMin - Minimum value of the domain\n * @param domainMax - Maximum value of the domain\n * @param tickCount - Number of ticks to generate (must be >= 1)\n * @returns Array of tick values\n */\nexport function generateLinearTicks(domainMin: number, domainMax: number, tickCount: number): number[] {\n  const count = Math.max(1, Math.floor(tickCount));\n  const ticks: number[] = new Array(count);\n  for (let i = 0; i < count; i++) {\n    const t = count === 1 ? 0.5 : i / (count - 1);\n    const v = domainMin + t * (domainMax - domainMin);\n    ticks[i] = v;\n  }\n  return ticks;\n}\n\n/**\n * Computes the maximum number of decimal places needed to display a tick step cleanly.\n *\n * Prefers \"clean\" decimal representations (e.g., 2.5, 0.25, 0.125) without relying on\n * magnitude alone. Accepts floating-point noise and caps the search to keep formatting\n * reasonable.\n *\n * @param tickStep - The step size between ticks\n * @param cap - Maximum number of decimal places to consider (default: 8)\n * @returns Number of decimal places (0 to cap)\n */\nexport function computeMaxFractionDigitsFromStep(tickStep: number, cap: number = DEFAULT_MAX_TICK_FRACTION_DIGITS): number {\n  const stepAbs = Math.abs(tickStep);\n  if (!Number.isFinite(stepAbs) || stepAbs === 0) return 0;\n\n  // Prefer \"clean\" decimal representations (e.g. 2.5, 0.25, 0.125) without relying on magnitude alone.\n  // We accept floating-point noise and cap the search to keep formatting reasonable.\n  for (let d = 0; d <= cap; d++) {\n    const scaled = stepAbs * 10 ** d;\n    const rounded = Math.round(scaled);\n    const err = Math.abs(scaled - rounded);\n    const tol = 1e-9 * Math.max(1, Math.abs(scaled));\n    if (err <= tol) return d;\n  }\n\n  // Fallback for repeating decimals (e.g. 1/3): show a small number of digits based on magnitude.\n  // The +1 nudges values like 0.333.. towards 2 decimals rather than 1.\n  return Math.max(0, Math.min(cap, 1 - Math.floor(Math.log10(stepAbs)) + 1));\n}\n\n/**\n * Creates an Intl.NumberFormat for tick value formatting.\n *\n * Automatically determines the appropriate number of decimal places based on the\n * tick step size using `computeMaxFractionDigitsFromStep()`.\n *\n * @param tickStep - The step size between ticks\n * @returns Intl.NumberFormat configured for tick formatting\n */\nexport function createTickFormatter(tickStep: number): Intl.NumberFormat {\n  const maximumFractionDigits = computeMaxFractionDigitsFromStep(tickStep);\n  return new Intl.NumberFormat(undefined, { maximumFractionDigits });\n}\n\n/**\n * Formats a numeric tick value using the provided number formatter.\n *\n * Handles edge cases:\n * - Non-finite values return null\n * - Values near zero (< 1e-12) are normalized to 0 to avoid \"-0\" display\n * - Unexpected \"NaN\" output from formatter is guarded against\n *\n * @param nf - Intl.NumberFormat to use for formatting\n * @param v - Numeric value to format\n * @returns Formatted string or null if value cannot be formatted\n */\nexport function formatTickValue(nf: Intl.NumberFormat, v: number): string | null {\n  if (!Number.isFinite(v)) return null;\n  // Avoid displaying \"-0\" from floating-point artifacts.\n  const normalized = Math.abs(v) < 1e-12 ? 0 : v;\n  const formatted = nf.format(normalized);\n  // Guard against unexpected output like \"NaN\" even after the finite check (defensive).\n  return formatted === 'NaN' ? null : formatted;\n}\n","/**\n * Shared axis label styling utilities.\n */\n\n/**\n * Theme configuration for axis labels.\n */\nexport interface AxisLabelThemeConfig {\n  readonly fontSize: number;\n  readonly fontFamily: string;\n  readonly textColor: string;\n}\n\n/**\n * Calculates the font size for axis titles (larger than regular tick labels).\n */\nexport function getAxisTitleFontSize(baseFontSize: number): number {\n  return Math.max(\n    baseFontSize + 1,\n    Math.round(baseFontSize * 1.15)\n  );\n}\n\n/**\n * Applies consistent styling to an axis label span element.\n */\nexport function styleAxisLabelSpan(\n  span: HTMLSpanElement,\n  isTitle: boolean,\n  theme: AxisLabelThemeConfig\n): void {\n  // Set inline styles\n  span.dir = 'auto';\n  span.style.fontFamily = theme.fontFamily;\n\n  // Axis titles are bold\n  if (isTitle) {\n    span.style.fontWeight = '600';\n  }\n}\n\n","/**\n * Axis Label Rendering Utilities\n *\n * Generates DOM-based axis labels and titles for cartesian charts.\n * Labels are positioned using canvas-local CSS coordinates and rendered\n * into a text overlay element.\n *\n * @module renderAxisLabels\n */\n\nimport type { ResolvedChartGPUOptions } from '../../../config/OptionResolver';\nimport type { LinearScale } from '../../../utils/scales';\nimport type { TextOverlay, TextOverlayAnchor } from '../../../components/createTextOverlay';\nimport { getCanvasCssWidth, getCanvasCssHeight } from '../utils/canvasUtils';\nimport { formatTimeTickValue } from '../utils/timeAxisUtils';\nimport { formatTickValue, createTickFormatter } from '../axis/computeAxisTicks';\nimport { finiteOrUndefined } from '../utils/dataPointUtils';\nimport { getAxisTitleFontSize } from '../../../utils/axisLabelStyling';\n\nconst DEFAULT_TICK_LENGTH_CSS_PX = 6;\nconst LABEL_PADDING_CSS_PX = 4;\nconst DEFAULT_TICK_COUNT = 5;\n\nexport interface AxisLabelRenderContext {\n  gpuContext: {\n    canvas: HTMLCanvasElement | null;\n  };\n  currentOptions: ResolvedChartGPUOptions;\n  xScale: LinearScale;\n  yScale: LinearScale;\n  xTickValues: ReadonlyArray<number>;\n  plotClipRect: {\n    left: number;\n    right: number;\n    top: number;\n    bottom: number;\n  };\n  visibleXRangeMs: number;\n}\n\nfunction clipXToCanvasCssPx(xClip: number, canvasCssWidth: number): number {\n  return ((xClip + 1) / 2) * canvasCssWidth;\n}\n\nfunction clipYToCanvasCssPx(yClip: number, canvasCssHeight: number): number {\n  return ((1 - yClip) / 2) * canvasCssHeight;\n}\n\nfunction styleAxisLabelSpan(span: HTMLSpanElement, isTitle: boolean, theme: ResolvedChartGPUOptions['theme']): void {\n  span.style.fontFamily = theme.fontFamily;\n  span.style.fontWeight = isTitle ? '500' : '400';\n  span.style.userSelect = 'none';\n  span.style.pointerEvents = 'none';\n}\n\n/**\n * Renders axis labels and titles to the text overlay.\n *\n * Generates X and Y axis tick labels with appropriate formatting,\n * and renders axis titles if configured.\n *\n * @param axisLabelOverlay - Text overlay for rendering labels\n * @param overlayContainer - DOM container for overlay positioning\n * @param context - Rendering context with scales, options, and layout\n */\nexport function renderAxisLabels(\n  axisLabelOverlay: TextOverlay | null,\n  overlayContainer: HTMLElement | null,\n  context: AxisLabelRenderContext\n): void {\n  const { gpuContext, currentOptions, xScale, yScale, xTickValues, plotClipRect, visibleXRangeMs } = context;\n\n  const hasCartesianSeries = currentOptions.series.some((s) => s.type !== 'pie');\n  if (!hasCartesianSeries || !axisLabelOverlay || !overlayContainer) {\n    return;\n  }\n\n  const canvas = gpuContext.canvas;\n  if (!canvas) return;\n\n  // Get canvas dimensions\n  const canvasCssWidth = getCanvasCssWidth(canvas as HTMLCanvasElement);\n  const canvasCssHeight = getCanvasCssHeight(canvas as HTMLCanvasElement);\n  if (canvasCssWidth <= 0 || canvasCssHeight <= 0) return;\n\n  // Calculate offsets (only for HTMLCanvasElement with DOM)\n  const offsetX = (canvas as HTMLCanvasElement).offsetLeft || 0;\n  const offsetY = (canvas as HTMLCanvasElement).offsetTop || 0;\n\n  const plotLeftCss = clipXToCanvasCssPx(plotClipRect.left, canvasCssWidth);\n  const plotRightCss = clipXToCanvasCssPx(plotClipRect.right, canvasCssWidth);\n  const plotTopCss = clipYToCanvasCssPx(plotClipRect.top, canvasCssHeight);\n  const plotBottomCss = clipYToCanvasCssPx(plotClipRect.bottom, canvasCssHeight);\n\n  // Clear axis label overlay\n  axisLabelOverlay.clear();\n\n  // X-axis tick labels\n  const xTickLengthCssPx = currentOptions.xAxis.tickLength ?? DEFAULT_TICK_LENGTH_CSS_PX;\n  const xLabelY = plotBottomCss + xTickLengthCssPx + LABEL_PADDING_CSS_PX + currentOptions.theme.fontSize * 0.5;\n  const isTimeXAxis = currentOptions.xAxis.type === 'time';\n  const xFormatter = (() => {\n    if (isTimeXAxis) return null;\n    const xDomainMin = finiteOrUndefined(currentOptions.xAxis.min) ?? xScale.invert(plotClipRect.left);\n    const xDomainMax = finiteOrUndefined(currentOptions.xAxis.max) ?? xScale.invert(plotClipRect.right);\n    const xTickCount = xTickValues.length;\n    const xTickStep = xTickCount === 1 ? 0 : (xDomainMax - xDomainMin) / (xTickCount - 1);\n    return createTickFormatter(xTickStep);\n  })();\n\n  const xTickFormatter = currentOptions.xAxis.tickFormatter;\n  for (let i = 0; i < xTickValues.length; i++) {\n    const v = xTickValues[i]!;\n    const xClip = xScale.scale(v);\n    const xCss = clipXToCanvasCssPx(xClip, canvasCssWidth);\n\n    const anchor: TextOverlayAnchor =\n      xTickValues.length === 1 ? 'middle' : i === 0 ? 'start' : i === xTickValues.length - 1 ? 'end' : 'middle';\n    const label = xTickFormatter\n      ? xTickFormatter(v)\n      : isTimeXAxis\n        ? formatTimeTickValue(v, visibleXRangeMs)\n        : formatTickValue(xFormatter!, v);\n    if (label == null) continue;\n\n    const span = axisLabelOverlay.addLabel(label, offsetX + xCss, offsetY + xLabelY, {\n      fontSize: currentOptions.theme.fontSize,\n      color: currentOptions.theme.textColor,\n      anchor,\n    });\n    styleAxisLabelSpan(span, false, currentOptions.theme);\n  }\n\n  // Y-axis tick labels\n  const yTickCount = DEFAULT_TICK_COUNT;\n  const yTickLengthCssPx = currentOptions.yAxis.tickLength ?? DEFAULT_TICK_LENGTH_CSS_PX;\n  const yDomainMin = finiteOrUndefined(currentOptions.yAxis.min) ?? yScale.invert(plotClipRect.bottom);\n  const yDomainMax = finiteOrUndefined(currentOptions.yAxis.max) ?? yScale.invert(plotClipRect.top);\n  const yTickStep = yTickCount <= 1 ? 0 : (yDomainMax - yDomainMin) / (yTickCount - 1);\n  const yFormatter = createTickFormatter(yTickStep);\n  const yLabelX = plotLeftCss - yTickLengthCssPx - LABEL_PADDING_CSS_PX;\n  const ySpans: HTMLSpanElement[] = [];\n\n  const yTickFormatter = currentOptions.yAxis.tickFormatter;\n  for (let i = 0; i < yTickCount; i++) {\n    const t = yTickCount <= 1 ? 0.5 : i / (yTickCount - 1);\n    const v = yDomainMin + t * (yDomainMax - yDomainMin);\n    const yClip = yScale.scale(v);\n    const yCss = clipYToCanvasCssPx(yClip, canvasCssHeight);\n\n    const label = yTickFormatter ? yTickFormatter(v) : formatTickValue(yFormatter, v);\n    if (label == null) continue;\n\n    const span = axisLabelOverlay.addLabel(label, offsetX + yLabelX, offsetY + yCss, {\n      fontSize: currentOptions.theme.fontSize,\n      color: currentOptions.theme.textColor,\n      anchor: 'end',\n    });\n    styleAxisLabelSpan(span, false, currentOptions.theme);\n    ySpans.push(span);\n  }\n\n  // X-axis title\n  const axisNameFontSize = getAxisTitleFontSize(currentOptions.theme.fontSize);\n  const xAxisName = currentOptions.xAxis.name?.trim() ?? '';\n  if (xAxisName.length > 0) {\n    const xCenter = (plotLeftCss + plotRightCss) / 2;\n    const xTickLabelsBottom = xLabelY + currentOptions.theme.fontSize * 0.5;\n    const hasSliderZoom = currentOptions.dataZoom?.some((z) => z?.type === 'slider') ?? false;\n    const sliderTrackHeightCssPx = 32;\n    const bottomLimitCss = hasSliderZoom ? canvasCssHeight - sliderTrackHeightCssPx : canvasCssHeight;\n    const xTitleY = (xTickLabelsBottom + bottomLimitCss) / 2;\n\n    const span = axisLabelOverlay.addLabel(xAxisName, offsetX + xCenter, offsetY + xTitleY, {\n      fontSize: axisNameFontSize,\n      color: currentOptions.theme.textColor,\n      anchor: 'middle',\n    });\n    styleAxisLabelSpan(span, true, currentOptions.theme);\n  }\n\n  // Y-axis title\n  const yAxisName = currentOptions.yAxis.name?.trim() ?? '';\n  if (yAxisName.length > 0) {\n    // Measure actual rendered label widths from DOM\n    const maxTickLabelWidth =\n      ySpans.length === 0 ? 0 : ySpans.reduce((max, s) => Math.max(max, s.getBoundingClientRect().width), 0);\n\n    const yCenter = (plotTopCss + plotBottomCss) / 2;\n    const yTickLabelLeft = yLabelX - maxTickLabelWidth;\n    const yTitleX = yTickLabelLeft - LABEL_PADDING_CSS_PX - axisNameFontSize * 0.5;\n\n    const span = axisLabelOverlay.addLabel(yAxisName, offsetX + yTitleX, offsetY + yCenter, {\n      fontSize: axisNameFontSize,\n      color: currentOptions.theme.textColor,\n      anchor: 'middle',\n      rotation: -90,\n    });\n    styleAxisLabelSpan(span, true, currentOptions.theme);\n  }\n}\n","/**\n * Annotation Label Rendering Utilities\n *\n * Generates DOM-based annotation labels for cartesian charts.\n * Handles template rendering, coordinate transformations, and styling\n * for lineX, lineY, point, and text annotations.\n *\n * @module renderAnnotationLabels\n */\n\nimport type { ResolvedChartGPUOptions } from '../../../config/OptionResolver';\nimport type { LinearScale } from '../../../utils/scales';\nimport type { TextOverlay, TextOverlayAnchor } from '../../../components/createTextOverlay';\nimport { parseCssColorToRgba01 } from '../../../utils/colors';\nimport { clamp01 } from '../utils/axisUtils';\nimport { assertUnreachable } from '../utils/dataPointUtils';\n\nexport interface AnnotationLabelRenderContext {\n  currentOptions: ResolvedChartGPUOptions;\n  xScale: LinearScale;\n  yScale: LinearScale;\n  canvasCssWidthForAnnotations: number;\n  canvasCssHeightForAnnotations: number;\n  plotLeftCss: number;\n  plotTopCss: number;\n  plotWidthCss: number;\n  plotHeightCss: number;\n  canvas: HTMLCanvasElement;\n}\n\ninterface AnnotationLabelData {\n  text: string;\n  x: number;\n  y: number;\n  anchor: TextOverlayAnchor;\n  color: string;\n  fontSize: number;\n  background?: {\n    backgroundColor: string;\n    padding?: readonly [number, number, number, number];\n    borderRadius?: number;\n  };\n}\n\nfunction isHTMLCanvasElement(canvas: HTMLCanvasElement): canvas is HTMLCanvasElement {\n  return 'offsetLeft' in canvas;\n}\n\nfunction clipXToCanvasCssPx(xClip: number, canvasCssWidth: number): number {\n  return ((xClip + 1) / 2) * canvasCssWidth;\n}\n\nfunction clipYToCanvasCssPx(yClip: number, canvasCssHeight: number): number {\n  return ((1 - yClip) / 2) * canvasCssHeight;\n}\n\nfunction toCssRgba(color: string, opacity01: number): string {\n  const base = parseCssColorToRgba01(color) ?? ([0, 0, 0, 1] as const);\n  const a = clamp01(base[3] * clamp01(opacity01));\n  const r = Math.round(clamp01(base[0]) * 255);\n  const g = Math.round(clamp01(base[1]) * 255);\n  const b = Math.round(clamp01(base[2]) * 255);\n  return `rgba(${r}, ${g}, ${b}, ${a})`;\n}\n\nfunction formatNumber(n: number, decimals?: number): string {\n  if (!Number.isFinite(n)) return '';\n  if (decimals == null) return String(n);\n  const d = Math.min(20, Math.max(0, Math.floor(decimals)));\n  return n.toFixed(d);\n}\n\n// PERFORMANCE: Cache regex pattern (compiled once per render, reused for all templates)\nconst templateRegex = /\\{(x|y|value|name)\\}/g;\n\nfunction renderTemplate(\n  template: string,\n  values: Readonly<{ x?: number; y?: number; value?: number; name?: string }>,\n  decimals?: number\n): string {\n  // PERFORMANCE: Reset regex lastIndex to ensure consistent behavior\n  templateRegex.lastIndex = 0;\n  return template.replace(templateRegex, (_m, key) => {\n    if (key === 'name') return values.name ?? '';\n    const v = (values as any)[key] as number | undefined;\n    return v == null ? '' : formatNumber(v, decimals);\n  });\n}\n\nfunction mapAnchor(anchor: 'start' | 'center' | 'end' | undefined): TextOverlayAnchor {\n  switch (anchor) {\n    case 'center':\n      return 'middle';\n    case 'end':\n      return 'end';\n    case 'start':\n    default:\n      return 'start';\n  }\n}\n\n/**\n * Renders annotation labels to the text overlay.\n *\n * Processes annotations and generates DOM labels with template support,\n * coordinate transformations, and background styling.\n *\n * @param annotationOverlay - Text overlay for rendering labels\n * @param overlayContainer - DOM container for overlay positioning\n * @param context - Rendering context with scales, options, and layout\n */\nexport function renderAnnotationLabels(\n  annotationOverlay: TextOverlay | null,\n  overlayContainer: HTMLElement | null,\n  context: AnnotationLabelRenderContext\n): void {\n  const {\n    currentOptions,\n    xScale,\n    yScale,\n    canvasCssWidthForAnnotations,\n    canvasCssHeightForAnnotations,\n    plotLeftCss,\n    plotTopCss,\n    plotWidthCss,\n    plotHeightCss,\n    canvas,\n  } = context;\n\n  const hasCartesianSeries = currentOptions.series.some((s) => s.type !== 'pie');\n  if (!hasCartesianSeries || !annotationOverlay || !overlayContainer) {\n    return;\n  }\n\n  // Validate canvas dimensions\n  if (\n    !canvas ||\n    canvasCssWidthForAnnotations <= 0 ||\n    canvasCssHeightForAnnotations <= 0 ||\n    plotWidthCss <= 0 ||\n    plotHeightCss <= 0\n  ) {\n    annotationOverlay.clear();\n    return;\n  }\n\n  const offsetX = isHTMLCanvasElement(canvas) ? canvas.offsetLeft : 0;\n  const offsetY = isHTMLCanvasElement(canvas) ? canvas.offsetTop : 0;\n\n  annotationOverlay.clear();\n\n  const annotations = currentOptions.annotations ?? [];\n  if (annotations.length === 0) {\n    return;\n  }\n\n  const labelsOut: AnnotationLabelData[] = [];\n\n  for (let i = 0; i < annotations.length; i++) {\n    const a = annotations[i]!;\n\n    const labelCfg = a.label;\n    const wantsLabel = labelCfg != null || a.type === 'text';\n    if (!wantsLabel) continue;\n\n    // Compute anchor point (canvas-local CSS px)\n    let anchorXCss: number | null = null;\n    let anchorYCss: number | null = null;\n    let values: { x?: number; y?: number; value?: number; name?: string } = { name: a.id ?? '' };\n\n    switch (a.type) {\n      case 'lineX': {\n        const xClip = xScale.scale(a.x);\n        const xCss = clipXToCanvasCssPx(xClip, canvasCssWidthForAnnotations);\n        anchorXCss = xCss;\n        anchorYCss = plotTopCss;\n        values = { ...values, x: a.x, value: a.x };\n        break;\n      }\n      case 'lineY': {\n        const yClip = yScale.scale(a.y);\n        const yCss = clipYToCanvasCssPx(yClip, canvasCssHeightForAnnotations);\n        anchorXCss = plotLeftCss;\n        // Offset label 8px above the horizontal line (negative Y = upward)\n        anchorYCss = yCss - 8;\n        values = { ...values, y: a.y, value: a.y };\n        break;\n      }\n      case 'point': {\n        const xClip = xScale.scale(a.x);\n        const yClip = yScale.scale(a.y);\n        const xCss = clipXToCanvasCssPx(xClip, canvasCssWidthForAnnotations);\n        const yCss = clipYToCanvasCssPx(yClip, canvasCssHeightForAnnotations);\n        anchorXCss = xCss;\n        anchorYCss = yCss;\n        values = { ...values, x: a.x, y: a.y, value: a.y };\n        break;\n      }\n      case 'text': {\n        if (a.position.space === 'data') {\n          const xClip = xScale.scale(a.position.x);\n          const yClip = yScale.scale(a.position.y);\n          const xCss = clipXToCanvasCssPx(xClip, canvasCssWidthForAnnotations);\n          const yCss = clipYToCanvasCssPx(yClip, canvasCssHeightForAnnotations);\n          anchorXCss = xCss;\n          anchorYCss = yCss;\n          values = { ...values, x: a.position.x, y: a.position.y, value: a.position.y };\n        } else {\n          const xCss = plotLeftCss + a.position.x * plotWidthCss;\n          const yCss = plotTopCss + a.position.y * plotHeightCss;\n          anchorXCss = xCss;\n          anchorYCss = yCss;\n          values = { ...values, x: a.position.x, y: a.position.y, value: a.position.y };\n        }\n        break;\n      }\n      default:\n        assertUnreachable(a);\n    }\n\n    if (anchorXCss == null || anchorYCss == null || !Number.isFinite(anchorXCss) || !Number.isFinite(anchorYCss)) {\n      continue;\n    }\n\n    // Cull annotations that are far outside the visible plot area (performance + visual cleanup)\n    const cullMargin = 200; // px margin for labels near edges\n    if (\n      anchorXCss < plotLeftCss - cullMargin ||\n      anchorXCss > plotLeftCss + plotWidthCss + cullMargin ||\n      anchorYCss < plotTopCss - cullMargin ||\n      anchorYCss > plotTopCss + plotHeightCss + cullMargin\n    ) {\n      continue;\n    }\n\n    const dx = labelCfg?.offset?.[0] ?? 0;\n    const dy = labelCfg?.offset?.[1] ?? 0;\n    const x = anchorXCss + dx;\n    const y = anchorYCss + dy;\n\n    // Label text selection (explicit > template > defaults)\n    const text =\n      labelCfg?.text ??\n      (labelCfg?.template\n        ? renderTemplate(labelCfg.template, values, labelCfg.decimals)\n        : labelCfg\n          ? (() => {\n              const defaultTemplate =\n                a.type === 'lineX'\n                  ? 'x={x}'\n                  : a.type === 'lineY'\n                    ? 'y={y}'\n                    : a.type === 'point'\n                      ? '({x}, {y})'\n                      : a.type === 'text'\n                        ? a.text\n                        : '';\n              return defaultTemplate.includes('{')\n                ? renderTemplate(defaultTemplate, values, labelCfg.decimals)\n                : defaultTemplate;\n            })()\n          : a.type === 'text'\n            ? a.text\n            : '');\n\n    const trimmed = typeof text === 'string' ? text.trim() : '';\n    if (trimmed.length === 0) continue;\n\n    const anchor = mapAnchor(labelCfg?.anchor);\n    const color = a.style?.color ?? currentOptions.theme.textColor;\n    const fontSize = currentOptions.theme.fontSize;\n\n    const bg = labelCfg?.background;\n    const bgColor = bg?.color != null ? toCssRgba(bg.color, bg.opacity ?? 1) : undefined;\n    const padding = (() => {\n      const p = bg?.padding;\n      if (typeof p === 'number' && Number.isFinite(p)) return [p, p, p, p] as const;\n      if (Array.isArray(p) && p.length === 4 && p.every((n) => typeof n === 'number' && Number.isFinite(n))) {\n        return [p[0], p[1], p[2], p[3]] as const;\n      }\n      return bg ? ([2, 4, 2, 4] as const) : undefined;\n    })();\n    const borderRadius =\n      typeof bg?.borderRadius === 'number' && Number.isFinite(bg.borderRadius) ? bg.borderRadius : undefined;\n\n    const labelData: AnnotationLabelData = {\n      text: trimmed,\n      x: offsetX + x,\n      y: offsetY + y,\n      anchor,\n      color,\n      fontSize,\n      ...(bgColor\n        ? {\n            background: {\n              backgroundColor: bgColor,\n              ...(padding ? { padding } : {}),\n              ...(borderRadius != null ? { borderRadius } : {}),\n            },\n          }\n        : {}),\n    };\n\n    labelsOut.push(labelData);\n\n    // Render label to DOM\n    const span = annotationOverlay.addLabel(trimmed, labelData.x, labelData.y, {\n      fontSize,\n      color,\n      anchor,\n    });\n\n    if (labelData.background) {\n      span.style.backgroundColor = labelData.background.backgroundColor;\n      span.style.display = 'inline-block';\n      span.style.boxSizing = 'border-box';\n      if (labelData.background.padding) {\n        const [t, r, b, l] = labelData.background.padding;\n        span.style.padding = `${t}px ${r}px ${b}px ${l}px`;\n      }\n      if (labelData.background.borderRadius != null) {\n        span.style.borderRadius = `${labelData.background.borderRadius}px`;\n      }\n    }\n  }\n}\n","import type { DataPoint, CartesianSeriesData, DataPointTuple, ScatterPointTuple } from '../config/types';\nimport type {\n  ResolvedBarSeriesConfig,\n  ResolvedScatterSeriesConfig,\n  ResolvedSeriesConfig,\n} from '../config/OptionResolver';\nimport type { LinearScale } from '../utils/scales';\nimport { getPointCount, getX, getY, getSize } from '../data/cartesianData';\nimport { isMonotonicNonDecreasingFiniteX } from '../core/renderCoordinator/data/computeVisibleSlice';\n\nconst DEFAULT_MAX_DISTANCE_PX = 20;\nconst DEFAULT_BAR_GAP = 0.01; // Minimal gap between bars within a group (was 0.1)\nconst DEFAULT_BAR_CATEGORY_GAP = 0.2;\nconst DEFAULT_SCATTER_RADIUS_CSS_PX = 4;\n\n/**\n * Binary search: finds the lower bound index (first element >= target) in monotonic cartesian data.\n * Returns index in range [0, n] where n = point count.\n */\nfunction lowerBoundX(data: CartesianSeriesData, xTarget: number): number {\n  let lo = 0;\n  let hi = getPointCount(data);\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const x = getX(data, mid);\n    if (x < xTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n}\n\nexport type NearestPointMatch = Readonly<{\n  seriesIndex: number;\n  dataIndex: number;\n  point: DataPoint;\n  /** Euclidean distance in range units. */\n  distance: number;\n}>;\n\nexport type BarBounds = { left: number; right: number; top: number; bottom: number };\n\nexport function isPointInBar(x: number, y: number, barBounds: BarBounds): boolean {\n  // Inclusive bounds.\n  // Note: stacked bar segments can share edges; tie-breaking is handled by the caller.\n  return (\n    x >= barBounds.left &&\n    x <= barBounds.right &&\n    y >= barBounds.top &&\n    y <= barBounds.bottom\n  );\n}\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\n\nconst parsePercent = (value: string): number | null => {\n  const m = value.trim().match(/^(\\d+(?:\\.\\d+)?)%$/);\n  if (!m) return null;\n  const p = Number(m[1]) / 100;\n  return Number.isFinite(p) ? p : null;\n};\n\nconst normalizeStackId = (stack: unknown): string => {\n  if (typeof stack !== 'string') return '';\n  const trimmed = stack.trim();\n  return trimmed.length > 0 ? trimmed : '';\n};\n\nconst isTupleDataPoint = (p: DataPoint): p is DataPointTuple => Array.isArray(p);\n\nconst getPointSizeCssPx = (p: DataPoint): number | null => {\n  if (isTupleDataPoint(p)) {\n    const s = p[2];\n    return typeof s === 'number' && Number.isFinite(s) ? s : null;\n  }\n  const s = p.size;\n  return typeof s === 'number' && Number.isFinite(s) ? s : null;\n};\n\nconst toScatterTuple = (p: DataPoint): ScatterPointTuple => {\n  if (isTupleDataPoint(p)) return p;\n  return [p.x, p.y, p.size] as const;\n};\n\nconst safeCallSymbolSize = (\n  fn: (value: ScatterPointTuple) => number,\n  value: ScatterPointTuple,\n): number | null => {\n  try {\n    const v = fn(value);\n    return typeof v === 'number' && Number.isFinite(v) ? v : null;\n  } catch {\n    return null;\n  }\n};\n\nconst getScatterRadiusCssPx = (seriesCfg: ResolvedScatterSeriesConfig, p: DataPoint): number => {\n  // Mirrors `createScatterRenderer.ts` size semantics (but stays in CSS px):\n  // point.size -> series.symbolSize -> default 4px.\n  const perPoint = getPointSizeCssPx(p);\n  if (perPoint != null) return Math.max(0, perPoint);\n\n  const seriesSymbolSize = seriesCfg.symbolSize;\n  if (typeof seriesSymbolSize === 'number') {\n    return Number.isFinite(seriesSymbolSize)\n      ? Math.max(0, seriesSymbolSize)\n      : DEFAULT_SCATTER_RADIUS_CSS_PX;\n  }\n  if (typeof seriesSymbolSize === 'function') {\n    const v = safeCallSymbolSize(seriesSymbolSize, toScatterTuple(p));\n    return v == null ? DEFAULT_SCATTER_RADIUS_CSS_PX : Math.max(0, v);\n  }\n\n  return DEFAULT_SCATTER_RADIUS_CSS_PX;\n};\n\n\n// Note: we intentionally do NOT compute “nearest bar by distance”.\n// Bars are only considered a match when the cursor is inside their rect bounds.\n\nexport type BarClusterSlots = Readonly<{\n  clusterIndexBySeries: ReadonlyArray<number>;\n  clusterCount: number;\n  stackIdBySeries: ReadonlyArray<string>;\n}>;\n\nexport function computeBarClusterSlots(\n  seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n): BarClusterSlots {\n  // Cluster slots (mirrors `createBarRenderer.ts`):\n  // - Each unique non-empty stackId gets a single cluster slot.\n  // - Each unstacked series gets its own cluster slot.\n  const stackIdToClusterIndex = new Map<string, number>();\n  const clusterIndexBySeries: number[] = new Array(seriesConfigs.length);\n  const stackIdBySeries: string[] = new Array(seriesConfigs.length);\n\n  let clusterCount = 0;\n  for (let i = 0; i < seriesConfigs.length; i++) {\n    const stackId = normalizeStackId(seriesConfigs[i].stack);\n    stackIdBySeries[i] = stackId;\n\n    if (stackId !== '') {\n      const existing = stackIdToClusterIndex.get(stackId);\n      if (existing !== undefined) {\n        clusterIndexBySeries[i] = existing;\n      } else {\n        const idx = clusterCount++;\n        stackIdToClusterIndex.set(stackId, idx);\n        clusterIndexBySeries[i] = idx;\n      }\n    } else {\n      clusterIndexBySeries[i] = clusterCount++;\n    }\n  }\n\n  return {\n    clusterIndexBySeries,\n    clusterCount: Math.max(1, clusterCount),\n    stackIdBySeries,\n  };\n}\n\nexport function computeBarCategoryStep(seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>): number {\n  const xs: number[] = [];\n  for (let s = 0; s < seriesConfigs.length; s++) {\n    const data = seriesConfigs[s].data as CartesianSeriesData;\n    const n = getPointCount(data);\n    for (let i = 0; i < n; i++) {\n      const x = getX(data, i);\n      if (Number.isFinite(x)) xs.push(x);\n    }\n  }\n\n  if (xs.length < 2) return 1;\n  xs.sort((a, b) => a - b);\n\n  let minStep = Number.POSITIVE_INFINITY;\n  for (let i = 1; i < xs.length; i++) {\n    const d = xs[i] - xs[i - 1];\n    if (d > 0 && d < minStep) minStep = d;\n  }\n  return Number.isFinite(minStep) && minStep > 0 ? minStep : 1;\n}\n\nexport function computeCategoryWidthPx(\n  seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n  xScale: LinearScale,\n  categoryStep: number,\n): number {\n  // Primary path (mirrors renderer): derive width from domain step via scale().\n  if (Number.isFinite(categoryStep) && categoryStep > 0) {\n    const x0 = 0;\n    const p0 = xScale.scale(x0);\n    const p1 = xScale.scale(x0 + categoryStep);\n    const w = Math.abs(p1 - p0);\n    if (Number.isFinite(w) && w > 0) return w;\n  }\n\n  // Fallback: compute min positive delta in *scaled* x positions.\n  const sx: number[] = [];\n  for (let s = 0; s < seriesConfigs.length; s++) {\n    const data = seriesConfigs[s].data as CartesianSeriesData;\n    const n = getPointCount(data);\n    for (let i = 0; i < n; i++) {\n      const x = getX(data, i);\n      if (!Number.isFinite(x)) continue;\n      const px = xScale.scale(x);\n      if (Number.isFinite(px)) sx.push(px);\n    }\n  }\n  if (sx.length < 2) return 0;\n  sx.sort((a, b) => a - b);\n\n  let minDx = Number.POSITIVE_INFINITY;\n  for (let i = 1; i < sx.length; i++) {\n    const d = sx[i] - sx[i - 1];\n    if (d > 0 && d < minDx) minDx = d;\n  }\n\n  return Number.isFinite(minDx) && minDx > 0 ? minDx : 0;\n}\n\ntype BarSharedLayout = Readonly<{\n  barWidth?: number | string;\n  barGap?: number;\n  barCategoryGap?: number;\n}>;\n\nconst computeSharedBarLayout = (\n  seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n): BarSharedLayout => {\n  let barWidth: number | string | undefined = undefined;\n  let barGap: number | undefined = undefined;\n  let barCategoryGap: number | undefined = undefined;\n\n  for (let i = 0; i < seriesConfigs.length; i++) {\n    const s = seriesConfigs[i];\n    if (barWidth === undefined && s.barWidth !== undefined) barWidth = s.barWidth;\n    if (barGap === undefined && s.barGap !== undefined) barGap = s.barGap;\n    if (barCategoryGap === undefined && s.barCategoryGap !== undefined) barCategoryGap = s.barCategoryGap;\n  }\n\n  return { barWidth, barGap, barCategoryGap };\n};\n\nexport type BarLayoutPx = Readonly<{\n  categoryStep: number;\n  categoryWidthPx: number;\n  barWidthPx: number;\n  gapPx: number;\n  clusterWidthPx: number;\n  clusterSlots: BarClusterSlots;\n}>;\n\nexport function computeBarLayoutPx(\n  seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n  xScale: LinearScale,\n): BarLayoutPx {\n  const clusterSlots = computeBarClusterSlots(seriesConfigs);\n  const clusterCount = clusterSlots.clusterCount;\n\n  const categoryStep = computeBarCategoryStep(seriesConfigs);\n  const categoryWidthPx = computeCategoryWidthPx(seriesConfigs, xScale, categoryStep);\n\n  const layout = computeSharedBarLayout(seriesConfigs);\n  const barGap = clamp01(layout.barGap ?? DEFAULT_BAR_GAP);\n  const barCategoryGap = clamp01(layout.barCategoryGap ?? DEFAULT_BAR_CATEGORY_GAP);\n\n  const categoryInnerWidthPx = Math.max(0, categoryWidthPx * (1 - barCategoryGap));\n  const denom = clusterCount + Math.max(0, clusterCount - 1) * barGap;\n  const maxBarWidthPx = denom > 0 ? categoryInnerWidthPx / denom : 0;\n\n  let barWidthPx = 0;\n  const rawBarWidth = layout.barWidth;\n  if (typeof rawBarWidth === 'number') {\n    barWidthPx = Math.max(0, rawBarWidth);\n    barWidthPx = Math.min(barWidthPx, maxBarWidthPx);\n  } else if (typeof rawBarWidth === 'string') {\n    const p = parsePercent(rawBarWidth);\n    barWidthPx = p == null ? 0 : maxBarWidthPx * clamp01(p);\n  }\n\n  if (!(barWidthPx > 0)) {\n    // Auto-width: max per-bar width that still avoids overlap (given clusterCount and barGap).\n    barWidthPx = maxBarWidthPx;\n  }\n\n  const gapPx = barWidthPx * barGap;\n  const clusterWidthPx = clusterCount * barWidthPx + Math.max(0, clusterCount - 1) * gapPx;\n\n  return {\n    categoryStep,\n    categoryWidthPx,\n    barWidthPx,\n    gapPx,\n    clusterWidthPx,\n    clusterSlots,\n  };\n}\n\nconst computeBaselineForBarsFromData = (seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>): number => {\n  let yMin = Number.POSITIVE_INFINITY;\n  let yMax = Number.NEGATIVE_INFINITY;\n\n  for (let s = 0; s < seriesConfigs.length; s++) {\n    const data = seriesConfigs[s].data as CartesianSeriesData;\n    const n = getPointCount(data);\n    for (let i = 0; i < n; i++) {\n      const y = getY(data, i);\n      if (!Number.isFinite(y)) continue;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  }\n\n  if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) return 0;\n  if (yMin <= 0 && 0 <= yMax) return 0;\n  return Math.abs(yMin) < Math.abs(yMax) ? yMin : yMax;\n};\n\nexport function inferPlotHeightPxForBarHitTesting(\n  seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n  yScale: LinearScale,\n): number {\n  // We don't have direct access to the scale range endpoints, so infer the plot height in range-space.\n  // In the common ChartGPU interaction setup, yScale.range(plotHeightCss, 0), so max(scaledY) should\n  // approximate plotHeightCss (or be <= plotHeightCss if axis min/max are overridden).\n  let maxY = 0;\n  for (let s = 0; s < seriesConfigs.length; s++) {\n    const data = seriesConfigs[s].data as CartesianSeriesData;\n    const n = getPointCount(data);\n    for (let i = 0; i < n; i++) {\n      const y = getY(data, i);\n      if (!Number.isFinite(y)) continue;\n      const py = yScale.scale(y);\n      if (Number.isFinite(py) && py > maxY) maxY = py;\n    }\n  }\n  return Math.max(0, maxY);\n}\n\nexport function computeBaselineDomainAndPx(\n  seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n  yScale: LinearScale,\n  plotHeightPx: number,\n): Readonly<{ baselineDomain: number; baselinePx: number }> {\n  // Axis-aware baseline logic (mirrors `createBarRenderer.ts`, but in px-space):\n  // Determine visible y-domain from yScale via invert(bottom/top) where top=0 and bottom=plotHeightPx.\n  const yDomainA = yScale.invert(plotHeightPx);\n  const yDomainB = yScale.invert(0);\n  const yMin = Math.min(yDomainA, yDomainB);\n  const yMax = Math.max(yDomainA, yDomainB);\n\n  let baselineDomain: number;\n  if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    baselineDomain = computeBaselineForBarsFromData(seriesConfigs);\n  } else if (yMin <= 0 && 0 <= yMax) {\n    baselineDomain = 0;\n  } else if (yMin > 0) {\n    baselineDomain = yMin;\n  } else if (yMax < 0) {\n    baselineDomain = yMax;\n  } else {\n    baselineDomain = computeBaselineForBarsFromData(seriesConfigs);\n  }\n\n  let baselinePx = yScale.scale(baselineDomain);\n  if (!Number.isFinite(baselinePx)) {\n    baselineDomain = computeBaselineForBarsFromData(seriesConfigs);\n    baselinePx = yScale.scale(baselineDomain);\n  }\n  if (!Number.isFinite(baselinePx)) {\n    baselineDomain = 0;\n    baselinePx = yScale.scale(0);\n  }\n\n  return { baselineDomain, baselinePx };\n}\n\nexport function bucketStackedXKey(\n  xCenterPx: number,\n  categoryWidthPx: number,\n  xDomain: number,\n  categoryStep: number,\n): number {\n  // Match renderer intent:\n  // - Prefer bucketing in *range-space* to avoid float-equality issues in domain-x.\n  // - Requirement: Math.round(xCenterPx / categoryWidthPx) (grid-local).\n  if (Number.isFinite(categoryWidthPx) && categoryWidthPx > 0 && Number.isFinite(xCenterPx)) {\n    return Math.round(xCenterPx / categoryWidthPx);\n  }\n  if (Number.isFinite(categoryStep) && categoryStep > 0 && Number.isFinite(xDomain)) {\n    return Math.round(xDomain / categoryStep);\n  }\n  return Math.round(xDomain * 1e6);\n}\n\n/**\n * Finds the nearest data point to the given cursor position across all series.\n *\n * Coordinate system contract:\n * - `x`/`y` MUST be in the same units as `xScale`/`yScale` **range**.\n * - If you pass **grid-local CSS pixels** (e.g. `payload.gridX` / `payload.gridY` from `createEventManager`),\n *   then `xScale.range()` / `yScale.range()` must also be in **CSS pixels**.\n * - If your scales are in **clip space** (e.g. \\([-1, 1]\\)), pass cursor coordinates in clip space too.\n *\n * DPR/WebGPU note:\n * - Pointer events are naturally in CSS pixels; WebGPU rendering often uses device pixels or clip space.\n *   This helper stays agnostic and only computes Euclidean distance in the provided **range-space**.\n *\n * Performance notes:\n * - Assumes each series is sorted by increasing x in domain space.\n * - Uses per-series lower-bound binary search on x, then expands outward while x-distance alone can still win.\n * - Uses squared distance comparisons and computes `sqrt` only for the final match.\n * - Skips non-finite points and any points whose scaled coordinates are NaN.\n */\nexport function findNearestPoint(\n  series: ReadonlyArray<ResolvedSeriesConfig>,\n  x: number,\n  y: number,\n  xScale: LinearScale,\n  yScale: LinearScale,\n  maxDistance: number = DEFAULT_MAX_DISTANCE_PX,\n): NearestPointMatch | null {\n  if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n\n  const md = Number.isFinite(maxDistance)\n    ? Math.max(0, maxDistance)\n    : DEFAULT_MAX_DISTANCE_PX;\n  const maxDistSq = md * md;\n\n  const xTarget = xScale.invert(x);\n  if (!Number.isFinite(xTarget)) return null;\n\n  let bestSeriesIndex = -1;\n  let bestDataIndex = -1;\n  let bestPoint: DataPoint | null = null;\n  let bestDistSq = Number.POSITIVE_INFINITY;\n\n  // Story 4.6: Bar hit-testing (range-space bounds).\n  // - Only counts as a match when cursor is inside a bar rect.\n  // - For stacked bars, uses the same stacking bucket logic as the bar renderer (xKey bucketing).\n  // - If multiple segments match (shared edges), prefer visually topmost (smallest `top` in CSS px).\n  //   If still tied, prefer larger `seriesIndex` for determinism.\n  const barSeriesConfigs: ResolvedBarSeriesConfig[] = [];\n  const barSeriesIndexByBar: number[] = [];\n  for (let s = 0; s < series.length; s++) {\n    const cfg = series[s];\n    if (cfg?.type === 'bar' && cfg.visible !== false) {\n      barSeriesConfigs.push(cfg);\n      barSeriesIndexByBar.push(s);\n    }\n  }\n\n  if (barSeriesConfigs.length > 0) {\n    const layoutPx = computeBarLayoutPx(barSeriesConfigs, xScale);\n    if (layoutPx.barWidthPx > 0 && layoutPx.clusterWidthPx >= 0) {\n      const plotHeightPx = inferPlotHeightPxForBarHitTesting(barSeriesConfigs, yScale);\n      const { baselineDomain, baselinePx } = computeBaselineDomainAndPx(barSeriesConfigs, yScale, plotHeightPx);\n\n      const { clusterSlots, barWidthPx, gapPx, clusterWidthPx, categoryWidthPx, categoryStep } = layoutPx;\n      const stackSumsByStackId = new Map<string, Map<number, { posSum: number; negSum: number }>>();\n\n      let bestBarHit:\n        | {\n            readonly seriesIndex: number;\n            readonly dataIndex: number;\n            readonly top: number;\n          }\n        | null = null;\n\n      for (let b = 0; b < barSeriesConfigs.length; b++) {\n        const seriesCfg = barSeriesConfigs[b];\n        const originalSeriesIndex = barSeriesIndexByBar[b] ?? -1;\n        if (originalSeriesIndex < 0) continue;\n\n        const data = seriesCfg.data as CartesianSeriesData;\n        const n = getPointCount(data);\n        const clusterIndex = clusterSlots.clusterIndexBySeries[b] ?? 0;\n        const stackId = clusterSlots.stackIdBySeries[b] ?? '';\n\n        for (let i = 0; i < n; i++) {\n          const xDomain = getX(data, i);\n          const yDomain = getY(data, i);\n          if (!Number.isFinite(xDomain) || !Number.isFinite(yDomain)) continue;\n\n          const xCenterPx = xScale.scale(xDomain);\n          if (!Number.isFinite(xCenterPx)) continue;\n\n          const left = xCenterPx - clusterWidthPx / 2 + clusterIndex * (barWidthPx + gapPx);\n          const right = left + barWidthPx;\n\n          let baseDomain = baselineDomain;\n          let topDomain = yDomain;\n\n          if (stackId !== '') {\n            let sumsForX = stackSumsByStackId.get(stackId);\n            if (!sumsForX) {\n              sumsForX = new Map<number, { posSum: number; negSum: number }>();\n              stackSumsByStackId.set(stackId, sumsForX);\n            }\n\n            const xKey = bucketStackedXKey(xCenterPx, categoryWidthPx, xDomain, categoryStep);\n            let sums = sumsForX.get(xKey);\n            if (!sums) {\n              sums = { posSum: baselineDomain, negSum: baselineDomain };\n              sumsForX.set(xKey, sums);\n            }\n\n            if (yDomain >= 0) {\n              baseDomain = sums.posSum;\n              topDomain = baseDomain + yDomain;\n              sums.posSum = topDomain;\n            } else {\n              baseDomain = sums.negSum;\n              topDomain = baseDomain + yDomain;\n              sums.negSum = topDomain;\n            }\n          } else {\n            baseDomain = baselineDomain;\n            topDomain = yDomain;\n          }\n\n          const basePx = stackId !== '' ? yScale.scale(baseDomain) : baselinePx;\n          const topPx = yScale.scale(topDomain);\n          if (!Number.isFinite(basePx) || !Number.isFinite(topPx)) continue;\n\n          const bounds: BarBounds = {\n            left,\n            right,\n            top: Math.min(basePx, topPx),\n            bottom: Math.max(basePx, topPx),\n          };\n\n          if (!isPointInBar(x, y, bounds)) continue;\n\n          const isBetter =\n            bestBarHit === null ||\n            bounds.top < bestBarHit.top ||\n            (bounds.top === bestBarHit.top && originalSeriesIndex > bestBarHit.seriesIndex);\n\n          if (isBetter) {\n            bestBarHit = { seriesIndex: originalSeriesIndex, dataIndex: i, top: bounds.top };\n          }\n        }\n      }\n\n      if (bestBarHit) {\n        const seriesData = series[bestBarHit.seriesIndex]?.data as CartesianSeriesData | undefined;\n        if (seriesData) {\n          const x = getX(seriesData, bestBarHit.dataIndex);\n          const y = getY(seriesData, bestBarHit.dataIndex);\n          const size = getSize(seriesData, bestBarHit.dataIndex);\n          const point: DataPoint = size !== undefined ? [x, y, size] : [x, y];\n          return {\n            seriesIndex: bestBarHit.seriesIndex,\n            dataIndex: bestBarHit.dataIndex,\n            point,\n            distance: 0,\n          };\n        }\n      }\n    }\n  }\n\n  // Build index mapping for non-bar cartesian series (scatter, line, area) to preserve original series indices\n  // after filtering for visibility, matching the pattern used for bar series above.\n  const cartesianSeriesConfigs: ResolvedSeriesConfig[] = [];\n  const cartesianSeriesIndexMap: number[] = [];\n  for (let s = 0; s < series.length; s++) {\n    const seriesCfg = series[s];\n    // Pie and candlestick series are non-cartesian (or not yet implemented); they don't participate in x/y nearest-point hit-testing.\n    if (seriesCfg.type === 'pie' || seriesCfg.type === 'candlestick') continue;\n\n    // Skip invisible series (matches bar series visibility check above).\n    if (seriesCfg.visible === false) continue;\n\n    cartesianSeriesConfigs.push(seriesCfg);\n    cartesianSeriesIndexMap.push(s);\n  }\n\n  for (let s = 0; s < cartesianSeriesConfigs.length; s++) {\n    const seriesCfg = cartesianSeriesConfigs[s];\n    const originalSeriesIndex = cartesianSeriesIndexMap[s] ?? -1;\n    if (originalSeriesIndex < 0) continue;\n\n    const data = seriesCfg.data as CartesianSeriesData;\n    const n = getPointCount(data);\n    if (n === 0) continue;\n\n    const isScatter = seriesCfg.type === 'scatter';\n    const scatterCfg = isScatter ? (seriesCfg as ResolvedScatterSeriesConfig) : null;\n\n    // Check if data is monotonic for O(log n) fast path\n    const canBinarySearch = isMonotonicNonDecreasingFiniteX(data);\n\n    if (canBinarySearch) {\n      // Fast path: binary search + expand outward\n      // Find the index where data[i].x >= xTarget (lower bound)\n      const startIdx = lowerBoundX(data, xTarget);\n\n      // Expand outward from startIdx while points could still be closer\n      // Check right side first (startIdx onwards)\n      for (let i = startIdx; i < n; i++) {\n        const px = getX(data, i);\n        const py = getY(data, i);\n        if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n\n        const sx = xScale.scale(px);\n        const sy = yScale.scale(py);\n        if (!Number.isFinite(sx) || !Number.isFinite(sy)) continue;\n\n        const dx = sx - x;\n        const dy = sy - y;\n        const distSq = dx * dx + dy * dy;\n\n        // Early exit: if x-distance alone exceeds current best, no point can be closer (monotonic x)\n        const dxSq = dx * dx;\n        if (dxSq > bestDistSq) break;\n\n        // Check scatter radius if applicable\n        let allowedSq = maxDistSq;\n        if (scatterCfg) {\n          const size = getSize(data, i);\n          const p: DataPoint = size !== undefined ? [px, py, size] : [px, py];\n          const r = getScatterRadiusCssPx(scatterCfg, p);\n          const allowed = md + r;\n          allowedSq = allowed * allowed;\n        }\n\n        if (distSq > allowedSq) continue;\n\n        const isBetter =\n          distSq < bestDistSq ||\n          (distSq === bestDistSq &&\n            (bestPoint === null ||\n              originalSeriesIndex < bestSeriesIndex ||\n              (originalSeriesIndex === bestSeriesIndex && i < bestDataIndex)));\n\n        if (isBetter) {\n          bestDistSq = distSq;\n          bestSeriesIndex = originalSeriesIndex;\n          bestDataIndex = i;\n          const size = getSize(data, i);\n          bestPoint = size !== undefined ? [px, py, size] : [px, py];\n        }\n      }\n\n      // Check left side (before startIdx)\n      for (let i = startIdx - 1; i >= 0; i--) {\n        const px = getX(data, i);\n        const py = getY(data, i);\n        if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n\n        const sx = xScale.scale(px);\n        const sy = yScale.scale(py);\n        if (!Number.isFinite(sx) || !Number.isFinite(sy)) continue;\n\n        const dx = sx - x;\n        const dy = sy - y;\n        const distSq = dx * dx + dy * dy;\n\n        // Early exit: if x-distance alone exceeds current best, no point can be closer\n        const dxSq = dx * dx;\n        if (dxSq > bestDistSq) break;\n\n        // Check scatter radius if applicable\n        let allowedSq = maxDistSq;\n        if (scatterCfg) {\n          const size = getSize(data, i);\n          const p: DataPoint = size !== undefined ? [px, py, size] : [px, py];\n          const r = getScatterRadiusCssPx(scatterCfg, p);\n          const allowed = md + r;\n          allowedSq = allowed * allowed;\n        }\n\n        if (distSq > allowedSq) continue;\n\n        const isBetter =\n          distSq < bestDistSq ||\n          (distSq === bestDistSq &&\n            (bestPoint === null ||\n              originalSeriesIndex < bestSeriesIndex ||\n              (originalSeriesIndex === bestSeriesIndex && i < bestDataIndex)));\n\n        if (isBetter) {\n          bestDistSq = distSq;\n          bestSeriesIndex = originalSeriesIndex;\n          bestDataIndex = i;\n          const size = getSize(data, i);\n          bestPoint = size !== undefined ? [px, py, size] : [px, py];\n        }\n      }\n    } else {\n      // Fallback: linear scan for non-monotonic data\n      for (let i = 0; i < n; i++) {\n        const px = getX(data, i);\n        const py = getY(data, i);\n        if (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n\n        const sx = xScale.scale(px);\n        const sy = yScale.scale(py);\n        if (!Number.isFinite(sx) || !Number.isFinite(sy)) continue;\n\n        const dx = sx - x;\n        const dy = sy - y;\n        const distSq = dx * dx + dy * dy;\n\n        // Check scatter radius if applicable\n        let allowedSq = maxDistSq;\n        if (scatterCfg) {\n          const size = getSize(data, i);\n          const p: DataPoint = size !== undefined ? [px, py, size] : [px, py];\n          const r = getScatterRadiusCssPx(scatterCfg, p);\n          const allowed = md + r;\n          allowedSq = allowed * allowed;\n        }\n\n        if (distSq > allowedSq) continue;\n\n        const isBetter =\n          distSq < bestDistSq ||\n          (distSq === bestDistSq &&\n            (bestPoint === null ||\n              originalSeriesIndex < bestSeriesIndex ||\n              (originalSeriesIndex === bestSeriesIndex && i < bestDataIndex)));\n\n        if (isBetter) {\n          bestDistSq = distSq;\n          bestSeriesIndex = originalSeriesIndex;\n          bestDataIndex = i;\n          const size = getSize(data, i);\n          bestPoint = size !== undefined ? [px, py, size] : [px, py];\n        }\n      }\n    }\n  }\n\n  if (bestPoint === null) return null;\n  if (!Number.isFinite(bestDistSq)) return null;\n\n  return {\n    seriesIndex: bestSeriesIndex,\n    dataIndex: bestDataIndex,\n    point: bestPoint,\n    distance: Math.sqrt(bestDistSq),\n  };\n}\n\n","/**\n * Overlay Rendering Utilities\n *\n * Prepares and renders GPU-based chart overlays (grid, axes, crosshair, highlight).\n * These overlays are rendered on top of the main chart series.\n *\n * @module renderOverlays\n */\n\nimport type { ResolvedChartGPUOptions } from '../../../config/OptionResolver';\nimport type { LinearScale } from '../../../utils/scales';\nimport type { GridRenderer } from '../../../renderers/createGridRenderer';\nimport type { AxisRenderer } from '../../../renderers/createAxisRenderer';\nimport type { CrosshairRenderer, CrosshairRenderOptions } from '../../../renderers/createCrosshairRenderer';\nimport type { HighlightRenderer, HighlightPoint } from '../../../renderers/createHighlightRenderer';\nimport type { GridArea } from '../../../renderers/createGridRenderer';\nimport { findNearestPoint } from '../../../interaction/findNearestPoint';\nimport { getPointXY } from '../utils/dataPointUtils';\nimport { computePlotScissorDevicePx } from '../utils/axisUtils';\n\nconst DEFAULT_TICK_COUNT = 5;\nconst DEFAULT_CROSSHAIR_LINE_WIDTH_CSS_PX = 1;\nconst DEFAULT_HIGHLIGHT_SIZE_CSS_PX = 4;\n\nexport interface OverlayRenderers {\n  gridRenderer: GridRenderer;\n  xAxisRenderer: AxisRenderer;\n  yAxisRenderer: AxisRenderer;\n  crosshairRenderer: CrosshairRenderer;\n  highlightRenderer: HighlightRenderer;\n}\n\nexport interface OverlayPrepareContext {\n  currentOptions: ResolvedChartGPUOptions;\n  xScale: LinearScale;\n  yScale: LinearScale;\n  gridArea: GridArea;\n  xTickCount: number;\n  hasCartesianSeries: boolean;\n  effectivePointer: {\n    hasPointer: boolean;\n    isInGrid: boolean;\n    source: 'mouse' | 'sync';\n    x: number;\n    y: number;\n    gridX: number;\n    gridY: number;\n  };\n  interactionScales: {\n    xScale: LinearScale;\n    yScale: LinearScale;\n  } | null;\n  seriesForRender: ReadonlyArray<any>;\n  withAlpha: (color: string, alpha: number) => string;\n}\n\nexport interface OverlayRenderContext {\n  mainPass: GPURenderPassEncoder;\n  topOverlayPass: GPURenderPassEncoder;\n  hasCartesianSeries: boolean;\n}\n\n/**\n * Prepares all overlay renderers with current frame data.\n *\n * This includes grid lines, axes, crosshair, and point highlights.\n *\n * @param renderers - Overlay renderer instances\n * @param context - Rendering context with scales, options, and pointer state\n */\nexport function prepareOverlays(renderers: OverlayRenderers, context: OverlayPrepareContext): void {\n  const {\n    currentOptions,\n    xScale,\n    yScale,\n    gridArea,\n    xTickCount,\n    hasCartesianSeries,\n    effectivePointer,\n    interactionScales,\n    seriesForRender,\n    withAlpha,\n  } = context;\n\n  // Grid preparation - always prepare so hidden grids don't render stale geometry.\n  const gridLinesConfig = currentOptions.gridLines;\n  const horizontalCount = gridLinesConfig.show && gridLinesConfig.horizontal.show ? gridLinesConfig.horizontal.count : 0;\n  const verticalCount = gridLinesConfig.show && gridLinesConfig.vertical.show ? gridLinesConfig.vertical.count : 0;\n\n  // Clear grid when hidden (or when both counts are zero).\n  if (horizontalCount === 0 && verticalCount === 0) {\n    renderers.gridRenderer.prepare(gridArea, { lineCount: { horizontal: 0, vertical: 0 } });\n  } else if (\n    horizontalCount > 0 &&\n    verticalCount > 0 &&\n    gridLinesConfig.horizontal.color !== gridLinesConfig.vertical.color\n  ) {\n    // Per-direction colors: render two batches (horizontal then vertical).\n    renderers.gridRenderer.prepare(gridArea, {\n      lineCount: { horizontal: horizontalCount, vertical: 0 },\n      color: gridLinesConfig.horizontal.color,\n    });\n    renderers.gridRenderer.prepare(gridArea, {\n      lineCount: { horizontal: 0, vertical: verticalCount },\n      color: gridLinesConfig.vertical.color,\n      append: true,\n    });\n  } else {\n    // Single color (either both directions share a color, or only one direction is enabled).\n    const color = horizontalCount > 0 ? gridLinesConfig.horizontal.color : gridLinesConfig.vertical.color;\n    renderers.gridRenderer.prepare(gridArea, {\n      lineCount: { horizontal: horizontalCount, vertical: verticalCount },\n      color,\n    });\n  }\n\n  // Axes preparation (cartesian only)\n  if (hasCartesianSeries) {\n    renderers.xAxisRenderer.prepare(\n      currentOptions.xAxis,\n      xScale,\n      'x',\n      gridArea,\n      currentOptions.theme.axisLineColor,\n      currentOptions.theme.axisTickColor,\n      xTickCount\n    );\n    renderers.yAxisRenderer.prepare(\n      currentOptions.yAxis,\n      yScale,\n      'y',\n      gridArea,\n      currentOptions.theme.axisLineColor,\n      currentOptions.theme.axisTickColor,\n      DEFAULT_TICK_COUNT\n    );\n  }\n\n  // Crosshair preparation (when pointer is in grid)\n  if (effectivePointer.hasPointer && effectivePointer.isInGrid) {\n    const crosshairOptions: CrosshairRenderOptions = {\n      showX: true,\n      // Sync has no meaningful y, so avoid horizontal line.\n      showY: effectivePointer.source !== 'sync',\n      color: withAlpha(currentOptions.theme.axisLineColor, 0.6),\n      lineWidth: DEFAULT_CROSSHAIR_LINE_WIDTH_CSS_PX,\n    };\n    renderers.crosshairRenderer.prepare(effectivePointer.x, effectivePointer.y, gridArea, crosshairOptions);\n    renderers.crosshairRenderer.setVisible(true);\n  } else {\n    renderers.crosshairRenderer.setVisible(false);\n  }\n\n  // Highlight preparation (on hover, find nearest point)\n  if (effectivePointer.source === 'mouse' && effectivePointer.hasPointer && effectivePointer.isInGrid) {\n    if (interactionScales) {\n      // findNearestPoint handles visibility filtering internally\n      const match = findNearestPoint(\n        seriesForRender,\n        effectivePointer.gridX,\n        effectivePointer.gridY,\n        interactionScales.xScale,\n        interactionScales.yScale\n      );\n\n      if (match) {\n        const { x, y } = getPointXY(match.point);\n        const xGridCss = interactionScales.xScale.scale(x);\n        const yGridCss = interactionScales.yScale.scale(y);\n\n        if (Number.isFinite(xGridCss) && Number.isFinite(yGridCss)) {\n          const centerCssX = gridArea.left + xGridCss;\n          const centerCssY = gridArea.top + yGridCss;\n\n          const plotScissor = computePlotScissorDevicePx(gridArea);\n          const point: HighlightPoint = {\n            centerDeviceX: centerCssX * gridArea.devicePixelRatio,\n            centerDeviceY: centerCssY * gridArea.devicePixelRatio,\n            devicePixelRatio: gridArea.devicePixelRatio,\n            canvasWidth: gridArea.canvasWidth,\n            canvasHeight: gridArea.canvasHeight,\n            scissor: plotScissor,\n          };\n\n          const seriesColor = currentOptions.series[match.seriesIndex]?.color ?? '#888';\n          renderers.highlightRenderer.prepare(point, seriesColor, DEFAULT_HIGHLIGHT_SIZE_CSS_PX);\n          renderers.highlightRenderer.setVisible(true);\n        } else {\n          renderers.highlightRenderer.setVisible(false);\n        }\n      } else {\n        renderers.highlightRenderer.setVisible(false);\n      }\n    } else {\n      renderers.highlightRenderer.setVisible(false);\n    }\n  } else {\n    renderers.highlightRenderer.setVisible(false);\n  }\n}\n\n/**\n * Renders all overlay elements to the appropriate render passes.\n *\n * Grid is rendered in the main pass (background).\n * Highlight, axes, and crosshair are rendered in the top overlay pass (foreground).\n *\n * @param renderers - Overlay renderer instances\n * @param context - Render pass context\n */\nexport function renderOverlays(renderers: OverlayRenderers, context: OverlayRenderContext): void {\n  const { mainPass, topOverlayPass, hasCartesianSeries } = context;\n\n  // Grid renders in main pass (background)\n  if (renderers.gridRenderer) {\n    renderers.gridRenderer.render(mainPass);\n  }\n\n  // Highlight, axes, crosshair render in top overlay pass (foreground)\n  renderers.highlightRenderer.render(topOverlayPass);\n  if (hasCartesianSeries) {\n    renderers.xAxisRenderer.render(topOverlayPass);\n    renderers.yAxisRenderer.render(topOverlayPass);\n  }\n  renderers.crosshairRenderer.render(topOverlayPass);\n}\n","/**\n * Annotation processing for the RenderCoordinator.\n *\n * Processes annotation configurations into GPU-renderable instances (reference lines,\n * markers) and DOM label data. Supports layering (above/below series) and multiple\n * annotation types (lineX, lineY, point, text).\n *\n * @module processAnnotations\n */\n\nimport type { AnnotationConfig } from '../../../config/types';\nimport type { LinearScale } from '../../../utils/scales';\nimport type { ThemeConfig } from '../../../themes/types';\nimport type { ReferenceLineInstance } from '../../../renderers/createReferenceLineRenderer';\nimport type { AnnotationMarkerInstance } from '../../../renderers/createAnnotationMarkerRenderer';\nimport type { TextOverlayAnchor } from '../../../components/createTextOverlay';\nimport { parseCssColorToRgba01 } from '../../../utils/colors';\nimport { clipXToCanvasCssPx, clipYToCanvasCssPx, clamp01 } from '../utils/axisUtils';\nimport { assertUnreachable } from '../utils/dataPointUtils';\n\n/**\n * Internal type for annotation label data (DOM overlay).\n */\nexport interface AnnotationLabelData {\n  readonly text: string;\n  readonly x: number;\n  readonly y: number;\n  readonly anchor?: 'start' | 'middle' | 'end';\n  readonly color?: string;\n  readonly fontSize?: number;\n  readonly background?: Readonly<{\n    readonly backgroundColor: string;\n    readonly padding?: readonly [number, number, number, number];\n    readonly borderRadius?: number;\n  }>;\n}\n\n/**\n * Plot bounds in CSS pixels.\n */\nexport interface PlotBounds {\n  readonly leftCss: number;\n  readonly rightCss: number;\n  readonly topCss: number;\n  readonly bottomCss: number;\n  readonly widthCss: number;\n  readonly heightCss: number;\n}\n\n/**\n * Context for annotation processing.\n */\nexport interface AnnotationContext {\n  readonly annotations: ReadonlyArray<AnnotationConfig>;\n  readonly xScale: LinearScale;\n  readonly yScale: LinearScale;\n  readonly plotBounds: PlotBounds;\n  readonly canvasCssWidth: number;\n  readonly canvasCssHeight: number;\n  readonly theme: ThemeConfig;\n  readonly offsetX?: number;\n  readonly offsetY?: number;\n}\n\n/**\n * Result of annotation processing.\n */\nexport interface AnnotationResult {\n  readonly linesBelow: ReferenceLineInstance[];\n  readonly linesAbove: ReferenceLineInstance[];\n  readonly markersBelow: AnnotationMarkerInstance[];\n  readonly markersAbove: AnnotationMarkerInstance[];\n  readonly labels: AnnotationLabelData[];\n}\n\n/**\n * Resolves annotation RGBA color with opacity.\n *\n * @param color - CSS color string or undefined\n * @param opacity - Opacity value [0, 1] or undefined\n * @param defaultColor - Default color (typically theme text color)\n * @returns RGBA tuple [r, g, b, a] in range [0, 1]\n */\nfunction resolveAnnotationRgba(\n  color: string | undefined,\n  opacity: number | undefined,\n  defaultColor: string\n): readonly [number, number, number, number] {\n  const base =\n    parseCssColorToRgba01(color ?? defaultColor) ??\n    parseCssColorToRgba01(defaultColor) ??\n    ([1, 1, 1, 1] as const);\n  const o = opacity == null ? 1 : clamp01(opacity);\n  return [clamp01(base[0]), clamp01(base[1]), clamp01(base[2]), clamp01(base[3] * o)] as const;\n}\n\n/**\n * Converts color and opacity to CSS rgba() string.\n *\n * @param color - CSS color string\n * @param opacity01 - Opacity in range [0, 1]\n * @returns CSS rgba() string\n */\nfunction toCssRgba(color: string, opacity01: number): string {\n  const base = parseCssColorToRgba01(color) ?? ([0, 0, 0, 1] as const);\n  const a = clamp01(base[3] * clamp01(opacity01));\n  const r = Math.round(clamp01(base[0]) * 255);\n  const g = Math.round(clamp01(base[1]) * 255);\n  const b = Math.round(clamp01(base[2]) * 255);\n  return `rgba(${r}, ${g}, ${b}, ${a})`;\n}\n\n/**\n * Formats number with optional decimal precision.\n *\n * @param n - Number to format\n * @param decimals - Number of decimal places (optional)\n * @returns Formatted string\n */\nfunction formatNumber(n: number, decimals?: number): string {\n  if (!Number.isFinite(n)) return '';\n  if (decimals == null) return String(n);\n  const d = Math.min(20, Math.max(0, Math.floor(decimals)));\n  return n.toFixed(d);\n}\n\n/**\n * Renders template string with value substitution.\n * Supports {x}, {y}, {value}, and {name} placeholders.\n *\n * @param template - Template string with placeholders\n * @param values - Values for substitution\n * @param decimals - Decimal precision for numbers\n * @returns Rendered string\n */\nfunction renderTemplate(\n  template: string,\n  values: Readonly<{ x?: number; y?: number; value?: number; name?: string }>,\n  decimals?: number\n): string {\n  // PERFORMANCE: Create regex per call (cached by engine in hot path)\n  const templateRegex = /\\{(x|y|value|name)\\}/g;\n  return template.replace(templateRegex, (_m, key) => {\n    if (key === 'name') return values.name ?? '';\n    const v = (values as any)[key] as number | undefined;\n    return v == null ? '' : formatNumber(v, decimals);\n  });\n}\n\n/**\n * Maps annotation anchor to text overlay anchor.\n *\n * @param anchor - Annotation anchor or undefined\n * @returns Text overlay anchor\n */\nfunction mapAnchor(anchor: 'start' | 'center' | 'end' | undefined): TextOverlayAnchor {\n  switch (anchor) {\n    case 'center':\n      return 'middle';\n    case 'end':\n      return 'end';\n    case 'start':\n    default:\n      return 'start';\n  }\n}\n\n/**\n * Processes annotations into GPU-renderable instances and DOM labels.\n *\n * **Annotation Types:**\n * - `lineX`: Vertical reference line at x coordinate\n * - `lineY`: Horizontal reference line at y coordinate\n * - `point`: Marker at (x, y) coordinate\n * - `text`: Text annotation with flexible positioning\n *\n * **Layering:**\n * - `belowSeries`: Rendered before series (occluded by data)\n * - `aboveSeries`: Rendered after series (always visible, default)\n *\n * **Labels:**\n * - Optional text labels for all annotation types\n * - Template support with {x}, {y}, {value}, {name} placeholders\n * - Background styling with padding and border radius\n *\n * @param context - Annotation processing context\n * @returns Annotation result with lines, markers, and labels\n */\nexport function processAnnotations(context: AnnotationContext): AnnotationResult {\n  const {\n    annotations,\n    xScale,\n    yScale,\n    plotBounds,\n    canvasCssWidth,\n    canvasCssHeight,\n    theme,\n    offsetX = 0,\n    offsetY = 0,\n  } = context;\n\n  const { leftCss: plotLeftCss, topCss: plotTopCss, widthCss: plotWidthCss, heightCss: plotHeightCss } = plotBounds;\n\n  // Output arrays (reused across calls for performance)\n  const linesBelow: ReferenceLineInstance[] = [];\n  const linesAbove: ReferenceLineInstance[] = [];\n  const markersBelow: AnnotationMarkerInstance[] = [];\n  const markersAbove: AnnotationMarkerInstance[] = [];\n  const labels: AnnotationLabelData[] = [];\n\n  // PERFORMANCE: Early exit if no annotations or invalid canvas dimensions\n  if (annotations.length === 0 || canvasCssWidth <= 0 || canvasCssHeight <= 0 || plotWidthCss <= 0 || plotHeightCss <= 0) {\n    return { linesBelow, linesAbove, markersBelow, markersAbove, labels };\n  }\n\n  // Process each annotation\n  for (let i = 0; i < annotations.length; i++) {\n    const a = annotations[i]!;\n    const layer = a.layer ?? 'aboveSeries';\n    const targetLines = layer === 'belowSeries' ? linesBelow : linesAbove;\n    const targetMarkers = layer === 'belowSeries' ? markersBelow : markersAbove;\n\n    // Resolve annotation styling\n    const styleColor = a.style?.color;\n    const styleOpacity = a.style?.opacity;\n    const lineWidth = typeof a.style?.lineWidth === 'number' && Number.isFinite(a.style.lineWidth) ? Math.max(0, a.style.lineWidth) : 1;\n    const lineDash = a.style?.lineDash;\n    const rgba = resolveAnnotationRgba(styleColor, styleOpacity, theme.textColor);\n\n    // Process annotation by type (GPU rendering)\n    switch (a.type) {\n      case 'lineX': {\n        const xClip = xScale.scale(a.x);\n        const xCss = clipXToCanvasCssPx(xClip, canvasCssWidth);\n        if (!Number.isFinite(xCss)) break;\n        targetLines.push({\n          axis: 'vertical',\n          positionCssPx: xCss,\n          lineWidth,\n          lineDash,\n          rgba,\n        });\n        break;\n      }\n      case 'lineY': {\n        const yClip = yScale.scale(a.y);\n        const yCss = clipYToCanvasCssPx(yClip, canvasCssHeight);\n        if (!Number.isFinite(yCss)) break;\n        targetLines.push({\n          axis: 'horizontal',\n          positionCssPx: yCss,\n          lineWidth,\n          lineDash,\n          rgba,\n        });\n        break;\n      }\n      case 'point': {\n        const xClip = xScale.scale(a.x);\n        const yClip = yScale.scale(a.y);\n        const xCss = clipXToCanvasCssPx(xClip, canvasCssWidth);\n        const yCss = clipYToCanvasCssPx(yClip, canvasCssHeight);\n        if (!Number.isFinite(xCss) || !Number.isFinite(yCss)) break;\n\n        const markerSize =\n          typeof a.marker?.size === 'number' && Number.isFinite(a.marker.size) ? Math.max(1, a.marker.size) : 6;\n        const markerColor = a.marker?.style?.color ?? a.style?.color;\n        const markerOpacity = a.marker?.style?.opacity ?? a.style?.opacity;\n        const fillRgba = resolveAnnotationRgba(markerColor, markerOpacity, theme.textColor);\n\n        targetMarkers.push({\n          xCssPx: xCss,\n          yCssPx: yCss,\n          sizeCssPx: markerSize,\n          fillRgba,\n        });\n        break;\n      }\n      case 'text': {\n        // Text annotations are handled via DOM overlays (labels), not GPU\n        break;\n      }\n      default:\n        assertUnreachable(a);\n    }\n\n    // Process annotation label (DOM overlay)\n    const labelCfg = a.label;\n    const wantsLabel = labelCfg != null || a.type === 'text';\n    if (!wantsLabel) continue;\n\n    // Compute anchor point (canvas-local CSS px)\n    let anchorXCss: number | null = null;\n    let anchorYCss: number | null = null;\n    let values: { x?: number; y?: number; value?: number; name?: string } = { name: a.id ?? '' };\n\n    switch (a.type) {\n      case 'lineX': {\n        const xClip = xScale.scale(a.x);\n        const xCss = clipXToCanvasCssPx(xClip, canvasCssWidth);\n        anchorXCss = xCss;\n        anchorYCss = plotTopCss;\n        values = { ...values, x: a.x, value: a.x };\n        break;\n      }\n      case 'lineY': {\n        const yClip = yScale.scale(a.y);\n        const yCss = clipYToCanvasCssPx(yClip, canvasCssHeight);\n        anchorXCss = plotLeftCss;\n        // Offset label 8px above the horizontal line (negative Y = upward)\n        anchorYCss = yCss - 8;\n        values = { ...values, y: a.y, value: a.y };\n        break;\n      }\n      case 'point': {\n        const xClip = xScale.scale(a.x);\n        const yClip = yScale.scale(a.y);\n        const xCss = clipXToCanvasCssPx(xClip, canvasCssWidth);\n        const yCss = clipYToCanvasCssPx(yClip, canvasCssHeight);\n        anchorXCss = xCss;\n        anchorYCss = yCss;\n        values = { ...values, x: a.x, y: a.y, value: a.y };\n        break;\n      }\n      case 'text': {\n        if (a.position.space === 'data') {\n          const xClip = xScale.scale(a.position.x);\n          const yClip = yScale.scale(a.position.y);\n          const xCss = clipXToCanvasCssPx(xClip, canvasCssWidth);\n          const yCss = clipYToCanvasCssPx(yClip, canvasCssHeight);\n          anchorXCss = xCss;\n          anchorYCss = yCss;\n          values = { ...values, x: a.position.x, y: a.position.y, value: a.position.y };\n        } else {\n          const xCss = plotLeftCss + a.position.x * plotWidthCss;\n          const yCss = plotTopCss + a.position.y * plotHeightCss;\n          anchorXCss = xCss;\n          anchorYCss = yCss;\n          values = { ...values, x: a.position.x, y: a.position.y, value: a.position.y };\n        }\n        break;\n      }\n      default:\n        assertUnreachable(a);\n    }\n\n    if (anchorXCss == null || anchorYCss == null || !Number.isFinite(anchorXCss) || !Number.isFinite(anchorYCss)) {\n      continue;\n    }\n\n    // Cull labels that are far outside the visible plot area\n    const cullMargin = 200;\n    if (\n      anchorXCss < plotBounds.leftCss - cullMargin ||\n      anchorXCss > plotBounds.leftCss + plotBounds.widthCss + cullMargin ||\n      anchorYCss < plotBounds.topCss - cullMargin ||\n      anchorYCss > plotBounds.topCss + plotBounds.heightCss + cullMargin\n    ) {\n      continue;\n    }\n\n    // Apply label offset\n    const dx = labelCfg?.offset?.[0] ?? 0;\n    const dy = labelCfg?.offset?.[1] ?? 0;\n    const x = anchorXCss + dx;\n    const y = anchorYCss + dy;\n\n    // Label text selection (explicit > template > defaults)\n    const text =\n      labelCfg?.text ??\n      (labelCfg?.template\n        ? renderTemplate(labelCfg.template, values, labelCfg.decimals)\n        : labelCfg\n          ? (() => {\n              const defaultTemplate =\n                a.type === 'lineX'\n                  ? 'x={x}'\n                  : a.type === 'lineY'\n                    ? 'y={y}'\n                    : a.type === 'point'\n                      ? '({x}, {y})'\n                      : a.type === 'text'\n                        ? a.text\n                        : '';\n              return defaultTemplate.includes('{')\n                ? renderTemplate(defaultTemplate, values, labelCfg.decimals)\n                : defaultTemplate;\n            })()\n          : a.type === 'text'\n            ? a.text\n            : '');\n\n    const trimmed = typeof text === 'string' ? text.trim() : '';\n    if (trimmed.length === 0) continue;\n\n    // Label styling\n    const anchor = mapAnchor(labelCfg?.anchor);\n    const color = a.style?.color ?? theme.textColor;\n    const fontSize = theme.fontSize;\n\n    // Background styling\n    const bg = labelCfg?.background;\n    const bgColor = bg?.color != null ? toCssRgba(bg.color, bg.opacity ?? 1) : undefined;\n    const padding = (() => {\n      const p = bg?.padding;\n      if (typeof p === 'number' && Number.isFinite(p)) return [p, p, p, p] as const;\n      if (Array.isArray(p) && p.length === 4 && p.every((n) => typeof n === 'number' && Number.isFinite(n))) {\n        return [p[0], p[1], p[2], p[3]] as const;\n      }\n      return bg ? ([2, 4, 2, 4] as const) : undefined;\n    })();\n    const borderRadius =\n      typeof bg?.borderRadius === 'number' && Number.isFinite(bg.borderRadius) ? bg.borderRadius : undefined;\n\n    // Create label data\n    const labelData: AnnotationLabelData = {\n      text: trimmed,\n      x: offsetX + x,\n      y: offsetY + y,\n      anchor,\n      color,\n      fontSize,\n      ...(bgColor\n        ? {\n            background: {\n              backgroundColor: bgColor,\n              ...(padding ? { padding } : {}),\n              ...(borderRadius != null ? { borderRadius } : {}),\n            },\n          }\n        : {}),\n    };\n\n    labels.push(labelData);\n  }\n\n  return {\n    linesBelow,\n    linesAbove,\n    markersBelow,\n    markersAbove,\n    labels,\n  };\n}\n","/**\n * Animation helper utilities for intro and update animations.\n *\n * Provides pure functions for animation config resolution, easing transformations,\n * series interpolation, and animation state management. These utilities support\n * both intro animations (initial reveal) and update animations (smooth transitions).\n *\n * @module animationHelpers\n */\n\nimport type { AnimationConfig, DataPoint } from '../../../config/types';\nimport type { ResolvedPieSeriesConfig } from '../../../config/OptionResolver';\nimport type { EasingFunction } from '../../../utils/easing';\nimport { getPointXY, isTupleDataPoint } from '../utils/dataPointUtils';\n\n/**\n * Intro animation phase state machine.\n */\nexport type IntroPhase = 'pending' | 'running' | 'done';\n\n/**\n * Domain boundaries with min and max values.\n */\nexport interface DomainBounds {\n  readonly min: number;\n  readonly max: number;\n}\n\n/**\n * Resolved animation configuration with timing and easing.\n */\nexport interface ResolvedAnimationConfig {\n  readonly delayMs: number;\n  readonly durationMs: number;\n  readonly easing: EasingFunction;\n}\n\n/**\n * Series configuration type that supports all series types.\n */\nexport type AnySeriesConfig =\n  | { readonly type: 'line'; readonly data: ReadonlyArray<DataPoint> }\n  | { readonly type: 'area'; readonly data: ReadonlyArray<DataPoint> }\n  | { readonly type: 'bar'; readonly data: ReadonlyArray<DataPoint> }\n  | { readonly type: 'scatter'; readonly data: ReadonlyArray<DataPoint> }\n  | { readonly type: 'candlestick'; readonly data: ReadonlyArray<any> }\n  | ResolvedPieSeriesConfig;\n\n/**\n * Clamps a value between 0 and 1.\n *\n * @param value - Value to clamp\n * @returns Clamped value in [0, 1]\n */\nexport function clamp01(value: number): number {\n  return Math.max(0, Math.min(1, value));\n}\n\n/**\n * Resolves animation configuration from options.\n *\n * Returns null if animation is disabled (false or null).\n * Returns default config if animation is true or an empty object.\n * Converts duration/delay from user config to milliseconds.\n *\n * @param animation - Animation options from chart config\n * @param getEasingFn - Function to resolve easing by name (to avoid circular deps)\n * @returns Resolved animation config or null if disabled\n */\nexport function resolveAnimationConfig(\n  animation: boolean | AnimationConfig | null | undefined,\n  getEasingFn: (name: string) => EasingFunction\n): ResolvedAnimationConfig | null {\n  if (animation === false || animation == null) return null;\n\n  const cfg: AnimationConfig | null = animation === true ? {} : animation;\n  if (!cfg) return null;\n\n  // Extract duration and delay with defaults\n  const durationMsRaw = cfg.duration ?? 300;\n  const delayMsRaw = cfg.delay ?? 0;\n\n  const durationMs = Number.isFinite(durationMsRaw) ? Math.max(0, durationMsRaw) : 300;\n  const delayMs = Number.isFinite(delayMsRaw) ? Math.max(0, delayMsRaw) : 0;\n\n  // Resolve easing (string name or function)\n  const easingConfig = cfg.easing ?? 'cubicOut';\n  const easing =\n    typeof easingConfig === 'string' ? getEasingFn(easingConfig) : easingConfig;\n\n  return {\n    durationMs,\n    delayMs,\n    easing,\n  };\n}\n\n/**\n * Creates an easing function that incorporates delay.\n *\n * The returned function maps t ∈ [0, 1] to an output considering both delay\n * and duration:\n * - t in [0, delay]: output = 0 (delay phase)\n * - t in [delay, delay+duration]: output = easing((t-delay)/duration)\n * - t > delay+duration: output = 1 (complete)\n *\n * @param delayMs - Delay before animation starts (milliseconds)\n * @param durationMs - Animation duration after delay (milliseconds)\n * @param easing - Base easing function to apply after delay\n * @returns Easing function with delay incorporated\n */\nexport function createEasingWithDelay(\n  delayMs: number,\n  durationMs: number,\n  easing: EasingFunction\n): EasingFunction {\n  return (t01: number): number => {\n    const t = clamp01(t01);\n    const totalMs = delayMs + durationMs;\n\n    if (!(totalMs > 0)) return 1;\n\n    const elapsedMs = t * totalMs;\n    if (elapsedMs < delayMs) return 0;\n\n    if (!(durationMs > 0)) return 1;\n    const innerT = (elapsedMs - delayMs) / durationMs;\n    return easing(innerT);\n  };\n}\n\n/**\n * Checks if a series configuration has drawable marks.\n *\n * Returns true if the series has data that will produce visible marks:\n * - Pie: at least one slice with value > 0\n * - Cartesian (line/area/bar/scatter/candlestick): at least one data point\n *\n * @param series - Series configuration to check\n * @returns True if series has drawable content\n */\nexport function hasDrawableMarks(series: AnySeriesConfig): boolean {\n  switch (series.type) {\n    case 'pie': {\n      return series.data.some(\n        (it: any) => typeof it?.value === 'number' && Number.isFinite(it.value) && it.value > 0\n      );\n    }\n    case 'line':\n    case 'area':\n    case 'bar':\n    case 'scatter':\n    case 'candlestick': {\n      return series.data.length > 0;\n    }\n    default: {\n      return false;\n    }\n  }\n}\n\n/**\n * Checks if any series in the list has drawable marks.\n *\n * @param seriesList - Array of series configurations\n * @returns True if at least one series has drawable marks\n */\nexport function hasAnyDrawableMarks(seriesList: ReadonlyArray<AnySeriesConfig>): boolean {\n  for (let i = 0; i < seriesList.length; i++) {\n    if (hasDrawableMarks(seriesList[i]!)) {\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Linearly interpolates between two domain bounds.\n *\n * @param from - Starting domain\n * @param to - Ending domain\n * @param t - Interpolation progress [0, 1]\n * @returns Interpolated domain\n */\nexport function lerpDomain(from: DomainBounds, to: DomainBounds, t: number): DomainBounds {\n  const t01 = clamp01(t);\n  return {\n    min: from.min + (to.min - from.min) * t01,\n    max: from.max + (to.max - from.max) * t01,\n  };\n}\n\n/**\n * Linearly interpolates between two numbers.\n *\n * @param from - Starting value\n * @param to - Ending value\n * @param t - Interpolation progress [0, 1]\n * @returns Interpolated value\n */\nexport function lerp(from: number, to: number, t: number): number {\n  return from + (to - from) * clamp01(t);\n}\n\n/**\n * Interpolates cartesian series data between from and to states.\n *\n * Returns null if array lengths don't match (can't interpolate mismatched arrays).\n * Reuses cache array if provided and same length.\n *\n * @param fromData - Starting data points\n * @param toData - Ending data points\n * @param t - Interpolation progress [0, 1]\n * @param cache - Optional cache array to reuse\n * @returns Interpolated data points or null if lengths mismatch\n */\nexport function interpolateCartesianData(\n  fromData: ReadonlyArray<DataPoint>,\n  toData: ReadonlyArray<DataPoint>,\n  t: number,\n  cache: DataPoint[] | null\n): DataPoint[] | null {\n  if (fromData.length !== toData.length) return null;\n\n  const n = toData.length;\n  if (n === 0) return cache ?? [];\n\n  const out = cache && cache.length === n ? cache : new Array<DataPoint>(n);\n  const t01 = clamp01(t);\n\n  // Determine format from first element\n  const isTuple = isTupleDataPoint(toData[0]!);\n\n  for (let i = 0; i < n; i++) {\n    const fromPt = fromData[i]!;\n    const toPt = toData[i]!;\n\n    const fromXY = getPointXY(fromPt);\n    const toXY = getPointXY(toPt);\n\n    const x = lerp(fromXY.x, toXY.x, t01);\n    const y = lerp(fromXY.y, toXY.y, t01);\n\n    if (isTuple) {\n      out[i] = [x, y] as DataPoint;\n    } else {\n      out[i] = { x, y } as DataPoint;\n    }\n  }\n\n  return out;\n}\n\n/**\n * Interpolates pie series data between from and to states.\n *\n * Returns the toSeries unchanged if data array lengths don't match.\n * Only interpolates the value property; angles are recomputed by the renderer.\n *\n * @param fromSeries - Starting pie series\n * @param toSeries - Ending pie series\n * @param t - Interpolation progress [0, 1]\n * @param cache - Optional cache array to reuse\n * @returns Interpolated pie series\n */\nexport function interpolatePieData(\n  fromSeries: ResolvedPieSeriesConfig,\n  toSeries: ResolvedPieSeriesConfig,\n  t: number,\n  cache: ResolvedPieSeriesConfig['data'] | null\n): ResolvedPieSeriesConfig {\n  const fromData = fromSeries.data;\n  const toData = toSeries.data;\n\n  if (fromData.length !== toData.length) return toSeries;\n\n  const n = toData.length;\n  if (n === 0) return toSeries;\n\n  // Create or reuse cache array\n  const out =\n    cache && cache.length === n\n      ? cache\n      : (() => {\n          const created: any[] = new Array(n);\n          for (let i = 0; i < n; i++) {\n            // Preserve name/color from \"to\"; patch value per frame\n            created[i] = { ...toData[i]!, value: 0 };\n          }\n          return created as ResolvedPieSeriesConfig['data'];\n        })();\n\n  const t01 = clamp01(t);\n\n  for (let i = 0; i < n; i++) {\n    const vFrom = (fromData[i] as any)?.value;\n    const vTo = (toData[i] as any)?.value;\n\n    // Interpolate value if both are valid numbers\n    const nextValue =\n      typeof vFrom === 'number' && typeof vTo === 'number' && Number.isFinite(vFrom) && Number.isFinite(vTo)\n        ? Math.max(0, lerp(vFrom, vTo, t01))\n        : typeof vTo === 'number' && Number.isFinite(vTo)\n          ? vTo\n          : 0;\n\n    (out[i] as any).value = nextValue;\n  }\n\n  return { ...toSeries, data: out };\n}\n\n/**\n * Checks if two domain bounds are equal.\n *\n * @param a - First domain\n * @param b - Second domain\n * @returns True if domains have identical min and max\n */\nexport function isDomainEqual(a: DomainBounds, b: DomainBounds): boolean {\n  return a.min === b.min && a.max === b.max;\n}\n\n/**\n * Determines the next intro phase based on current state and conditions.\n *\n * State machine transitions:\n * - pending → running: when has drawable marks and animation enabled\n * - running → done: when animation completes\n * - done → pending: when retriggering (e.g., visibility change)\n *\n * @param currentPhase - Current intro phase\n * @param hasDrawable - Whether series have drawable marks\n * @param animationEnabled - Whether animation is enabled\n * @param retrigger - Whether to retrigger from done state\n * @returns Next intro phase\n */\nexport function computeNextIntroPhase(\n  currentPhase: IntroPhase,\n  hasDrawable: boolean,\n  animationEnabled: boolean,\n  retrigger: boolean = false\n): IntroPhase {\n  if (retrigger && currentPhase === 'done') {\n    return 'pending';\n  }\n\n  if (currentPhase === 'pending' && hasDrawable && animationEnabled) {\n    return 'running';\n  }\n\n  return currentPhase;\n}\n\n/**\n * Applies intro animation progress to create an animated bar Y scale.\n *\n * During intro, bars grow from the zero line (or domain min if no zero).\n * This creates a scale that compresses bars based on progress.\n *\n * @param baseYScale - Original Y scale\n * @param yMin - Domain minimum\n * @param yMax - Domain maximum\n * @param progress - Animation progress [0, 1]\n * @returns Y coordinate adjusted for intro animation\n */\nexport function applyBarIntroProgress(\n  baseY: number,\n  yMin: number,\n  yMax: number,\n  progress: number\n): number {\n  const p = clamp01(progress);\n\n  // Find zero line or use domain min as anchor\n  const zeroLine = yMin <= 0 && yMax >= 0 ? 0 : yMin;\n\n  // Interpolate from zero line to actual value\n  return lerp(zeroLine, baseY, p);\n}\n","/**\n * Series Rendering Utilities\n *\n * Prepares and renders all chart series types (area, line, bar, scatter, candlestick, pie).\n * Handles intro animations, GPU buffer management, and multi-pass rendering with proper layering.\n *\n * @module renderSeries\n */\n\nimport type { ResolvedChartGPUOptions, ResolvedSeriesConfig, ResolvedBarSeriesConfig, ResolvedAreaSeriesConfig, ResolvedPieSeriesConfig } from '../../../config/OptionResolver';\nimport type { DataPoint } from '../../../config/types';\nimport type { LinearScale } from '../../../utils/scales';\nimport type { GridArea } from '../../../renderers/createGridRenderer';\nimport type { LineRenderer } from '../../../renderers/createLineRenderer';\nimport type { AreaRenderer } from '../../../renderers/createAreaRenderer';\nimport type { BarRenderer } from '../../../renderers/createBarRenderer';\nimport type { ScatterRenderer } from '../../../renderers/createScatterRenderer';\nimport type { ScatterDensityRenderer } from '../../../renderers/createScatterDensityRenderer';\nimport type { PieRenderer } from '../../../renderers/createPieRenderer';\nimport type { CandlestickRenderer } from '../../../renderers/createCandlestickRenderer';\nimport type { ReferenceLineRenderer } from '../../../renderers/createReferenceLineRenderer';\nimport type { AnnotationMarkerRenderer } from '../../../renderers/createAnnotationMarkerRenderer';\nimport type { DataStore } from '../../../data/createDataStore';\nimport { clampInt } from '../utils/canvasUtils';\nimport { clamp01 } from '../animation/animationHelpers';\nimport { findVisibleRangeIndicesByX } from '../data/computeVisibleSlice';\nimport { resolvePieRadiiCss } from '../utils/timeAxisUtils';\nimport { getPointCount, getX, filterGaps } from '../../../data/cartesianData';\n\nexport interface SeriesRenderers {\n  readonly lineRenderers: ReadonlyArray<LineRenderer>;\n  readonly areaRenderers: ReadonlyArray<AreaRenderer>;\n  readonly barRenderer: BarRenderer;\n  readonly scatterRenderers: ReadonlyArray<ScatterRenderer>;\n  readonly scatterDensityRenderers: ReadonlyArray<ScatterDensityRenderer>;\n  readonly pieRenderers: ReadonlyArray<PieRenderer>;\n  readonly candlestickRenderers: ReadonlyArray<CandlestickRenderer>;\n}\n\nexport interface AnnotationRenderers {\n  referenceLineRenderer: ReferenceLineRenderer;\n  referenceLineRendererMsaa: ReferenceLineRenderer;\n  annotationMarkerRenderer: AnnotationMarkerRenderer;\n  annotationMarkerRendererMsaa: AnnotationMarkerRenderer;\n}\n\nexport interface SeriesPrepareContext {\n  currentOptions: ResolvedChartGPUOptions;\n  seriesForRender: ReadonlyArray<ResolvedSeriesConfig>;\n  xScale: LinearScale;\n  yScale: LinearScale;\n  gridArea: GridArea;\n  dataStore: DataStore;\n  appendedGpuThisFrame: Set<number>;\n  gpuSeriesKindByIndex: Array<'fullRawLine' | 'other' | 'unknown'>;\n  zoomState: { getRange(): { start: number; end: number } | null } | null;\n  visibleXDomain: { min: number; max: number };\n  introPhase: 'pending' | 'running' | 'done';\n  introProgress01: number;\n  withAlpha: (color: string, alpha: number) => string;\n  maxRadiusCss: number;\n}\n\nexport interface SeriesRenderContext {\n  hasCartesianSeries: boolean;\n  gridArea: GridArea;\n  mainPass: GPURenderPassEncoder;\n  plotScissor: { x: number; y: number; w: number; h: number };\n  introPhase: 'pending' | 'running' | 'done';\n  introProgress01: number;\n  referenceLineBelowCount: number;\n  markerBelowCount: number;\n}\n\nexport interface AboveSeriesAnnotationContext {\n  hasCartesianSeries: boolean;\n  gridArea: GridArea;\n  overlayPass: GPURenderPassEncoder;\n  plotScissor: { x: number; y: number; w: number; h: number };\n  referenceLineBelowCount: number;\n  referenceLineAboveCount: number;\n  markerBelowCount: number;\n  markerAboveCount: number;\n}\n\nexport interface SeriesPreparationResult {\n  visibleSeriesForRender: ReadonlyArray<{ series: ResolvedSeriesConfig; originalIndex: number }>;\n  barSeriesConfigs: ResolvedBarSeriesConfig[];\n  visibleBarSeriesConfigs: ResolvedBarSeriesConfig[];\n}\n\n/**\n * Helper: determines if an area should be rendered for a series.\n * Line series with areaStyle should render as area.\n */\nfunction shouldRenderArea(series: ResolvedSeriesConfig): boolean {\n  return series.type === 'area' || (series.type === 'line' && !!series.areaStyle);\n}\n\n/**\n * Prepares all series renderers with current frame data.\n *\n * This loop prepares ALL series (including hidden) to maintain correct renderer indices.\n * Visibility filtering happens after preparation for rendering.\n *\n * @param renderers - Series renderer instances\n * @param context - Preparation context with scales, options, and state\n * @returns Preparation result with visibility-filtered series arrays\n */\nexport function prepareSeries(\n  renderers: SeriesRenderers,\n  context: SeriesPrepareContext\n): SeriesPreparationResult {\n  const {\n    currentOptions,\n    seriesForRender,\n    xScale,\n    yScale,\n    gridArea,\n    dataStore,\n    appendedGpuThisFrame,\n    gpuSeriesKindByIndex,\n    zoomState,\n    visibleXDomain,\n    introPhase,\n    introProgress01,\n    withAlpha,\n    maxRadiusCss,\n  } = context;\n\n  const defaultBaseline = currentOptions.yAxis.min ?? (currentOptions.yAxis.min ?? 0);\n  const barSeriesConfigs: ResolvedBarSeriesConfig[] = [];\n\n  const introP = introPhase === 'running' ? clamp01(introProgress01) : 1;\n\n  // Preparation loop: prepare ALL series (including hidden) to maintain correct indices\n  for (let i = 0; i < seriesForRender.length; i++) {\n    const s = seriesForRender[i];\n    switch (s.type) {\n      case 'area': {\n        const baseline = s.baseline ?? defaultBaseline;\n        // When connectNulls is true, strip null/NaN gap entries so the area draws through gaps.\n        // Uses filterGaps which handles all CartesianSeriesData formats (Array, XYArraysData,\n        // InterleavedXYData), not just Array — important because recomputeRuntimeBaseSeries\n        // may convert the data to MutableXYColumns (XYArraysData-like) with NaN gap markers.\n        const areaData = s.connectNulls\n          ? filterGaps(s.data)\n          : s.data;\n        renderers.areaRenderers[i].prepare(s, areaData, xScale, yScale, baseline);\n        break;\n      }\n      case 'line': {\n        // Always prepare the line stroke.\n        // If we already appended into the DataStore this frame (fast-path), avoid a full re-upload.\n        // For time axes (epoch-ms), subtract an x-origin before packing to Float32 to avoid precision loss\n        // (Float32 ulp at ~1e12 is ~2e5), which can manifest as stroke shimmer during zoom.\n        const xOffset = (() => {\n          if (currentOptions.xAxis.type !== 'time') return 0;\n          const d = s.data;\n          const count = getPointCount(d);\n          for (let k = 0; k < count; k++) {\n            const x = getX(d, k);\n            if (Number.isFinite(x)) return x;\n          }\n          return 0;\n        })();\n        // When connectNulls is true, strip null/NaN gap entries so the line draws through gaps.\n        // Uses filterGaps which handles all CartesianSeriesData formats (Array, XYArraysData,\n        // InterleavedXYData), not just Array — important because recomputeRuntimeBaseSeries\n        // may convert the data to MutableXYColumns (XYArraysData-like) with NaN gap markers.\n        const uploadData = s.connectNulls\n          ? filterGaps(s.data)\n          : s.data;\n        if (!appendedGpuThisFrame.has(i)) {\n          dataStore.setSeries(i, uploadData as ReadonlyArray<DataPoint>, { xOffset });\n        }\n        const buffer = dataStore.getSeriesBuffer(i);\n        // Pass filtered data to the renderer so point count matches the GPU buffer.\n        const lineSeriesForRenderer = uploadData !== s.data\n          ? { ...s, data: uploadData }\n          : s;\n        renderers.lineRenderers[i].prepare(\n          lineSeriesForRenderer, buffer, xScale, yScale, xOffset,\n          gridArea.devicePixelRatio,\n          gridArea.canvasWidth,\n          gridArea.canvasHeight,\n        );\n\n        // Track the GPU buffer kind for future append fast-path decisions.\n        const zoomRange = zoomState?.getRange() ?? null;\n        const isFullSpanZoom =\n          zoomRange == null ||\n          (Number.isFinite(zoomRange.start) &&\n            Number.isFinite(zoomRange.end) &&\n            zoomRange.start <= 0 &&\n            zoomRange.end >= 100);\n        if (isFullSpanZoom && s.sampling === 'none') {\n          gpuSeriesKindByIndex[i] = 'fullRawLine';\n        } else {\n          gpuSeriesKindByIndex[i] = 'other';\n        }\n\n        // If `areaStyle` is provided on a line series, render a fill behind it.\n        if (s.areaStyle) {\n          const areaLike: ResolvedAreaSeriesConfig = {\n            type: 'area',\n            name: s.name,\n            rawData: s.data,\n            data: uploadData,\n            color: s.areaStyle.color,\n            areaStyle: s.areaStyle,\n            sampling: s.sampling,\n            samplingThreshold: s.samplingThreshold,\n            connectNulls: s.connectNulls,\n          };\n\n          renderers.areaRenderers[i].prepare(areaLike, areaLike.data, xScale, yScale, defaultBaseline);\n        }\n\n        break;\n      }\n      case 'bar': {\n        barSeriesConfigs.push(s);\n        break;\n      }\n      case 'scatter': {\n        // Scatter renderer sets/resets its own scissor. Animate intro via alpha fade.\n        if (s.mode === 'density') {\n          // Density mode bins raw (unsampled) data for correctness, but limits compute to the visible\n          // range when x is monotonic.\n          const rawData = (s.rawData ?? s.data) as ReadonlyArray<DataPoint>;\n          const visible = findVisibleRangeIndicesByX(rawData, visibleXDomain.min, visibleXDomain.max);\n\n          // Upload full raw data for compute. DataStore hashing makes this a cheap no-op when unchanged.\n          if (!appendedGpuThisFrame.has(i)) {\n            dataStore.setSeries(i, rawData);\n          }\n          const buffer = dataStore.getSeriesBuffer(i);\n          const pointCount = dataStore.getSeriesPointCount(i);\n\n          renderers.scatterDensityRenderers[i].prepare(\n            s,\n            buffer,\n            pointCount,\n            visible.start,\n            visible.end,\n            xScale,\n            yScale,\n            gridArea,\n            s.rawBounds\n          );\n          // Density mode keeps its own compute path; treat as non-fast-path for append heuristics.\n          gpuSeriesKindByIndex[i] = 'other';\n        } else {\n          const animated = introP < 1 ? ({ ...s, color: withAlpha(s.color, introP) } as const) : s;\n          renderers.scatterRenderers[i].prepare(animated, s.data, xScale, yScale, gridArea);\n        }\n        break;\n      }\n      case 'pie': {\n        // Pie renderer sets/resets its own scissor. Animate intro via radius scale (CSS px).\n        if (introP < 1 && maxRadiusCss > 0) {\n          const radiiCss = resolvePieRadiiCss(s.radius, maxRadiusCss);\n          const inner = Math.max(0, radiiCss.inner) * introP;\n          const outer = Math.max(inner, radiiCss.outer) * introP;\n          const animated: ResolvedPieSeriesConfig = { ...s, radius: [inner, outer] as const };\n          renderers.pieRenderers[i].prepare(animated, gridArea);\n          break;\n        }\n        renderers.pieRenderers[i].prepare(s, gridArea);\n        break;\n      }\n      case 'candlestick': {\n        // Candlestick renderer handles clipping internally, no intro animation for now.\n        renderers.candlestickRenderers[i].prepare(s, s.data, xScale, yScale, gridArea, currentOptions.theme.backgroundColor);\n        break;\n      }\n      default: {\n        // Exhaustive check for unhandled series types\n        const _exhaustive: never = s;\n        throw new Error(`Unhandled series type: ${(_exhaustive as any).type}`);\n      }\n    }\n  }\n\n  // Filter series by visibility for rendering (after preparation)\n  const visibleSeriesForRender = seriesForRender\n    .map((s, i) => ({ series: s, originalIndex: i }))\n    .filter(({ series }) => series.visible !== false);\n\n  // Bars are collected but prepared separately by coordinator (needs yScaleForBars which depends on visibleBarSeriesConfigs)\n  const visibleBarSeriesConfigs = barSeriesConfigs.filter(s => s.visible !== false);\n\n  return {\n    visibleSeriesForRender,\n    barSeriesConfigs,\n    visibleBarSeriesConfigs,\n  };\n}\n\n/**\n * Encodes scatter density compute passes before rendering.\n *\n * Must be called before beginRenderPass() for the main pass.\n *\n * @param renderers - Series renderer instances\n * @param seriesForRender - All series configurations\n * @param encoder - Command encoder for compute passes\n */\nexport function encodeScatterDensityCompute(\n  renderers: SeriesRenderers,\n  seriesForRender: ReadonlyArray<ResolvedSeriesConfig>,\n  encoder: GPUCommandEncoder\n): void {\n  for (let i = 0; i < seriesForRender.length; i++) {\n    const s = seriesForRender[i];\n    if (s.visible !== false && s.type === 'scatter' && s.mode === 'density') {\n      renderers.scatterDensityRenderers[i].encodeCompute(encoder);\n    }\n  }\n}\n\n/**\n * Renders all series to the main render pass with proper layering.\n *\n * Render order (from back to front):\n * 1. Pies (non-cartesian, behind cartesian series)\n * 2. Annotations below series (reference lines, markers)\n * 3. Area fills\n * 4. Bars\n * 5. Candlesticks\n * 6. Scatter points\n * 7. Line strokes\n *\n * @param renderers - Series renderer instances\n * @param annotationRenderers - Annotation renderer instances\n * @param context - Render pass context with pass encoders and state\n */\nexport function renderSeries(\n  renderers: SeriesRenderers,\n  annotationRenderers: AnnotationRenderers,\n  context: SeriesRenderContext,\n  prepResult: SeriesPreparationResult\n): void {\n  const {\n    hasCartesianSeries,\n    gridArea,\n    mainPass,\n    plotScissor,\n    introPhase,\n    introProgress01,\n    referenceLineBelowCount,\n    markerBelowCount,\n  } = context;\n\n  const { visibleSeriesForRender } = prepResult;\n  const introP = introPhase === 'running' ? clamp01(introProgress01) : 1;\n\n  // Render pies first (non-cartesian, visible behind cartesian series)\n  for (let idx = 0; idx < visibleSeriesForRender.length; idx++) {\n    const { series, originalIndex } = visibleSeriesForRender[idx];\n    if (series.type === 'pie') {\n      renderers.pieRenderers[originalIndex].render(mainPass);\n    }\n  }\n\n  // Annotations (below series): clipped to plot scissor.\n  if (hasCartesianSeries && plotScissor.w > 0 && plotScissor.h > 0) {\n    const hasBelow = referenceLineBelowCount > 0 || markerBelowCount > 0;\n    if (hasBelow) {\n      mainPass.setScissorRect(plotScissor.x, plotScissor.y, plotScissor.w, plotScissor.h);\n      if (referenceLineBelowCount > 0) {\n        annotationRenderers.referenceLineRenderer.render(mainPass, 0, referenceLineBelowCount);\n      }\n      if (markerBelowCount > 0) {\n        annotationRenderers.annotationMarkerRenderer.render(mainPass, 0, markerBelowCount);\n      }\n      mainPass.setScissorRect(0, 0, gridArea.canvasWidth, gridArea.canvasHeight);\n    }\n  }\n\n  // Render area fills\n  for (let idx = 0; idx < visibleSeriesForRender.length; idx++) {\n    const { series, originalIndex } = visibleSeriesForRender[idx];\n    if (shouldRenderArea(series)) {\n      // Line/area intro reveal: left-to-right plot scissor.\n      if (introP < 1) {\n        const w = clampInt(Math.floor(plotScissor.w * introP), 0, plotScissor.w);\n        if (w > 0 && plotScissor.h > 0) {\n          mainPass.setScissorRect(plotScissor.x, plotScissor.y, w, plotScissor.h);\n          renderers.areaRenderers[originalIndex].render(mainPass);\n          mainPass.setScissorRect(0, 0, gridArea.canvasWidth, gridArea.canvasHeight);\n        }\n      } else {\n        mainPass.setScissorRect(plotScissor.x, plotScissor.y, plotScissor.w, plotScissor.h);\n        renderers.areaRenderers[originalIndex].render(mainPass);\n        mainPass.setScissorRect(0, 0, gridArea.canvasWidth, gridArea.canvasHeight);\n      }\n    }\n  }\n\n  // Clip bars to the plot grid (mirrors area/line scissor usage).\n  if (plotScissor.w > 0 && plotScissor.h > 0) {\n    mainPass.setScissorRect(plotScissor.x, plotScissor.y, plotScissor.w, plotScissor.h);\n    renderers.barRenderer.render(mainPass);\n    mainPass.setScissorRect(0, 0, gridArea.canvasWidth, gridArea.canvasHeight);\n  }\n\n  // Render candlesticks\n  for (let idx = 0; idx < visibleSeriesForRender.length; idx++) {\n    const { series, originalIndex } = visibleSeriesForRender[idx];\n    if (series.type === 'candlestick') {\n      renderers.candlestickRenderers[originalIndex].render(mainPass);\n    }\n  }\n\n  // Render scatter points\n  for (let idx = 0; idx < visibleSeriesForRender.length; idx++) {\n    const { series, originalIndex } = visibleSeriesForRender[idx];\n    if (series.type !== 'scatter') continue;\n    if (series.mode === 'density') {\n      renderers.scatterDensityRenderers[originalIndex].render(mainPass);\n    } else {\n      renderers.scatterRenderers[originalIndex].render(mainPass);\n    }\n  }\n\n  // Render line strokes\n  for (let idx = 0; idx < visibleSeriesForRender.length; idx++) {\n    const { series, originalIndex } = visibleSeriesForRender[idx];\n    if (series.type === 'line') {\n      // Line intro reveal: left-to-right plot scissor.\n      if (introP < 1) {\n        const w = clampInt(Math.floor(plotScissor.w * introP), 0, plotScissor.w);\n        if (w > 0 && plotScissor.h > 0) {\n          mainPass.setScissorRect(plotScissor.x, plotScissor.y, w, plotScissor.h);\n          renderers.lineRenderers[originalIndex].render(mainPass);\n          mainPass.setScissorRect(0, 0, gridArea.canvasWidth, gridArea.canvasHeight);\n        }\n      } else {\n        mainPass.setScissorRect(plotScissor.x, plotScissor.y, plotScissor.w, plotScissor.h);\n        renderers.lineRenderers[originalIndex].render(mainPass);\n        mainPass.setScissorRect(0, 0, gridArea.canvasWidth, gridArea.canvasHeight);\n      }\n    }\n  }\n}\n\n/**\n * Renders above-series annotations to the MSAA overlay pass.\n *\n * Must be called during the MSAA overlay pass (after blit).\n *\n * @param annotationRenderers - Annotation renderer instances\n * @param context - Render pass context with overlay pass and state\n */\nexport function renderAboveSeriesAnnotations(\n  annotationRenderers: AnnotationRenderers,\n  context: AboveSeriesAnnotationContext\n): void {\n  const {\n    hasCartesianSeries,\n    gridArea,\n    overlayPass,\n    plotScissor,\n    referenceLineBelowCount,\n    referenceLineAboveCount,\n    markerBelowCount,\n    markerAboveCount,\n  } = context;\n\n  // Annotations (above series): reference lines then markers, clipped to plot scissor.\n  if (hasCartesianSeries && plotScissor.w > 0 && plotScissor.h > 0) {\n    const hasAbove = referenceLineAboveCount > 0 || markerAboveCount > 0;\n    if (hasAbove) {\n      const firstLine = referenceLineBelowCount;\n      const firstMarker = markerBelowCount;\n      overlayPass.setScissorRect(plotScissor.x, plotScissor.y, plotScissor.w, plotScissor.h);\n      if (referenceLineAboveCount > 0) {\n        annotationRenderers.referenceLineRendererMsaa.render(overlayPass, firstLine, referenceLineAboveCount);\n      }\n      if (markerAboveCount > 0) {\n        annotationRenderers.annotationMarkerRendererMsaa.render(overlayPass, firstMarker, markerAboveCount);\n      }\n      overlayPass.setScissorRect(0, 0, gridArea.canvasWidth, gridArea.canvasHeight);\n    }\n  }\n}\n","export default \"// grid.wgsl\\n// Minimal grid line shader:\\n// - Vertex input: vec2<f32> position in clip-space coordinates\\n// - Uniforms: identity transform + solid RGBA color\\n\\nstruct VSUniforms {\\n  transform: mat4x4<f32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;\\n\\nstruct FSUniforms {\\n  color: vec4<f32>,\\n};\\n\\n@group(0) @binding(1) var<uniform> fsUniforms: FSUniforms;\\n\\nstruct VSIn {\\n  @location(0) position: vec2<f32>,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition: vec4<f32>,\\n};\\n\\n@vertex\\nfn vsMain(in: VSIn) -> VSOut {\\n  var out: VSOut;\\n  out.clipPosition = vsUniforms.transform * vec4<f32>(in.position, 0.0, 1.0);\\n  return out;\\n}\\n\\n@fragment\\nfn fsMain() -> @location(0) vec4<f32> {\\n  return fsUniforms.color;\\n}\\n\"","/**\n * Shared renderer utilities.\n *\n * Minimal, library-friendly helpers for common WebGPU boilerplate:\n * - shader module creation\n * - render pipeline creation (ergonomic config + sensible defaults)\n * - uniform buffer creation + updates\n *\n * Notes:\n * - All helpers are pure functions; they create resources but do not mutate external state.\n * - First argument is always `device: GPUDevice`.\n */\n\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport type ShaderStageModuleSource =\n  | {\n      /** Use an existing module. */\n      readonly module: GPUShaderModule;\n      readonly entryPoint?: string;\n      readonly constants?: Record<string, GPUPipelineConstantValue>;\n    }\n  | {\n      /** Provide WGSL code to compile. */\n      readonly code: string;\n      readonly label?: string;\n      readonly entryPoint?: string;\n      readonly constants?: Record<string, GPUPipelineConstantValue>;\n    };\n\nexport type VertexStageConfig = ShaderStageModuleSource & {\n  readonly buffers?: readonly GPUVertexBufferLayout[];\n};\n\nexport type FragmentStageConfig = ShaderStageModuleSource & {\n  /**\n   * Provide full color target states directly (most flexible).\n   * If omitted, `formats` must be provided.\n   */\n  readonly targets?: readonly GPUColorTargetState[];\n  /**\n   * Convenience: provide one or more target formats and optionally a shared blend/writeMask.\n   * Ignored if `targets` is provided.\n   */\n  readonly formats?: GPUTextureFormat | readonly GPUTextureFormat[];\n  readonly blend?: GPUBlendState;\n  readonly writeMask?: GPUColorWriteFlags;\n};\n\nexport type RenderPipelineConfig =\n  | (RenderPipelineConfigBase & { readonly fragment: FragmentStageConfig })\n  | (RenderPipelineConfigBase & { readonly fragment?: undefined });\n\nexport interface RenderPipelineConfigBase {\n  readonly label?: string;\n\n  /**\n   * Defaults to `'auto'`.\n   *\n   * If you provide `bindGroupLayouts`, a pipeline layout will be created for you.\n   * If both are provided, `layout` wins.\n   */\n  readonly layout?: GPUPipelineLayout | 'auto';\n  readonly bindGroupLayouts?: readonly GPUBindGroupLayout[];\n\n  readonly vertex: VertexStageConfig;\n\n  readonly primitive?: GPUPrimitiveState;\n  readonly depthStencil?: GPUDepthStencilState;\n  readonly multisample?: GPUMultisampleState;\n}\n\nconst DEFAULT_VERTEX_ENTRY = 'vsMain';\nconst DEFAULT_FRAGMENT_ENTRY = 'fsMain';\n\nconst isPowerOfTwo = (n: number): boolean => Number.isInteger(n) && n > 0 && (n & (n - 1)) === 0;\n\nconst alignTo = (value: number, alignment: number): number => {\n  if (!Number.isFinite(value) || value < 0) {\n    throw new Error(`alignTo(value): value must be a finite non-negative number. Received: ${String(value)}`);\n  }\n  if (!isPowerOfTwo(alignment)) {\n    throw new Error(`alignTo(alignment): alignment must be a positive power of two. Received: ${String(alignment)}`);\n  }\n  const v = Math.floor(value);\n  return (v + alignment - 1) & ~(alignment - 1);\n};\n\nconst getStageModule = (\n  device: GPUDevice,\n  stage: ShaderStageModuleSource,\n  pipelineCache?: PipelineCache\n): { readonly module: GPUShaderModule; readonly entryPoint: string; readonly constants?: Record<string, GPUPipelineConstantValue> } => {\n  // Validate pipelineCache device match early (before any shader module creation).\n  if (pipelineCache && pipelineCache.device !== device) {\n    throw new Error('getStageModule(pipelineCache): cache.device must match the provided GPUDevice.');\n  }\n\n  if ('module' in stage) {\n    return {\n      module: stage.module,\n      entryPoint: stage.entryPoint || '',\n      constants: stage.constants,\n    };\n  }\n\n  return {\n    module: createShaderModule(device, stage.code, stage.label, pipelineCache),\n    entryPoint: stage.entryPoint || '',\n    constants: stage.constants,\n  };\n};\n\n/**\n * Creates a shader module from WGSL source.\n */\nexport function createShaderModule(device: GPUDevice, code: string, label?: string, pipelineCache?: PipelineCache): GPUShaderModule {\n  if (typeof code !== 'string' || code.length === 0) {\n    throw new Error('createShaderModule(code): WGSL code must be a non-empty string.');\n  }\n  if (pipelineCache) {\n    if (pipelineCache.device !== device) {\n      throw new Error('createShaderModule(pipelineCache): cache.device must match the provided GPUDevice.');\n    }\n    return pipelineCache.getOrCreateShaderModule(code, label);\n  }\n  return device.createShaderModule({ code, label });\n}\n\n/**\n * Creates a render pipeline with reduced boilerplate and sensible defaults.\n *\n * Defaults:\n * - `layout: 'auto'`\n * - `vertex.entryPoint: 'vsMain'`\n * - `fragment.entryPoint: 'fsMain'` (if fragment present)\n * - `primitive.topology: 'triangle-list'`\n * - `multisample.count: 1`\n */\nexport function createRenderPipeline(device: GPUDevice, config: RenderPipelineConfig, pipelineCache?: PipelineCache): GPURenderPipeline {\n  if (pipelineCache && pipelineCache.device !== device) {\n    throw new Error('createRenderPipeline(pipelineCache): cache.device must match the provided GPUDevice.');\n  }\n\n  // Resolve stages first (shader modules may be cached).\n  const vertexStage = getStageModule(device, config.vertex, pipelineCache);\n  const vertexEntryPoint = vertexStage.entryPoint || DEFAULT_VERTEX_ENTRY;\n\n  let fragment: GPUFragmentState | undefined = undefined;\n  if (config.fragment) {\n    const fragmentStageResolved = getStageModule(device, config.fragment, pipelineCache);\n    const fragmentEntryPoint = fragmentStageResolved.entryPoint || DEFAULT_FRAGMENT_ENTRY;\n\n    // Avoid double-cloning target arrays: if we synthesize targets, we already own the array.\n    let targets: GPUColorTargetState[];\n    if (config.fragment.targets) {\n      targets = [...config.fragment.targets];\n    } else {\n      const formats = config.fragment.formats;\n      if (!formats) {\n        throw new Error(\n          \"createRenderPipeline(fragment): provide either `fragment.targets` or `fragment.formats` when a fragment stage is present.\"\n        );\n      }\n      if (typeof formats === 'string') {\n        targets = [\n          {\n            format: formats,\n            blend: config.fragment.blend,\n            writeMask: config.fragment.writeMask,\n          },\n        ];\n      } else {\n        targets = new Array(formats.length);\n        for (let i = 0; i < formats.length; i++) {\n          targets[i] = {\n            format: formats[i]!,\n            blend: config.fragment.blend,\n            writeMask: config.fragment.writeMask,\n          };\n        }\n      }\n    }\n\n    fragment = {\n      module: fragmentStageResolved.module,\n      entryPoint: fragmentEntryPoint,\n      targets,\n      constants: fragmentStageResolved.constants,\n    };\n  }\n\n  const primitive: GPUPrimitiveState = config.primitive ?? { topology: 'triangle-list' };\n  const multisample: GPUMultisampleState = config.multisample ?? { count: 1 };\n\n  // Layout selection:\n  // - Default is `'auto'`\n  // - If `bindGroupLayouts` are provided, create an explicit pipeline layout.\n  //   NOTE: We always create an explicit layout when bindGroupLayouts are given,\n  //   even with pipelineCache. Using 'auto' with pipelineCache would improve\n  //   pipeline dedup (structurally identical layouts from different charts would\n  //   share one cached pipeline), BUT it breaks bind group compatibility:\n  //   bind groups created with manually-created GPUBindGroupLayout objects are\n  //   NOT compatible with auto-inferred pipeline layouts. Shader module caching\n  //   (the most expensive part) still works regardless of layout strategy.\n  let layout: GPUPipelineLayout | 'auto';\n  if (config.layout != null) {\n    layout = config.layout;\n  } else if (config.bindGroupLayouts) {\n    layout = device.createPipelineLayout({ bindGroupLayouts: [...config.bindGroupLayouts] });\n  } else {\n    layout = 'auto';\n  }\n\n  const descriptor: GPURenderPipelineDescriptor = {\n    label: config.label,\n    layout,\n    vertex: {\n      module: vertexStage.module,\n      entryPoint: vertexEntryPoint,\n      buffers: config.vertex.buffers ? [...config.vertex.buffers] : [],\n      constants: vertexStage.constants,\n    },\n    fragment,\n    primitive,\n    depthStencil: config.depthStencil,\n    multisample,\n  };\n\n  if (pipelineCache) {\n    return pipelineCache.getOrCreateRenderPipeline(descriptor);\n  }\n\n  return device.createRenderPipeline(descriptor);\n}\n\n/**\n * Creates a compute pipeline, optionally using a pipeline cache for deduplication.\n *\n * Unlike render pipelines, compute pipelines typically use explicit layouts (not 'auto'),\n * so we do NOT force layout: 'auto' when caching.\n */\nexport function createComputePipeline(\n  device: GPUDevice,\n  descriptor: GPUComputePipelineDescriptor,\n  pipelineCache?: PipelineCache\n): GPUComputePipeline {\n  if (pipelineCache && pipelineCache.device !== device) {\n    throw new Error('createComputePipeline(pipelineCache): cache.device must match the provided GPUDevice.');\n  }\n  if (pipelineCache) {\n    return pipelineCache.getOrCreateComputePipeline(descriptor);\n  }\n  return device.createComputePipeline(descriptor);\n}\n\n/**\n * Creates a uniform buffer suitable for `@group/@binding` uniform bindings.\n *\n * Notes:\n * - WebGPU's `queue.writeBuffer()` requires `byteLength` and offsets to be multiples of 4.\n * - Uniform data layout in WGSL is typically aligned to 16 bytes; we default to a 16-byte size alignment.\n * - If you plan to use this buffer with *dynamic offsets*, you must additionally align offsets to\n *   `device.limits.minUniformBufferOffsetAlignment` (commonly 256). This helper does not enforce that.\n */\nexport function createUniformBuffer(\n  device: GPUDevice,\n  size: number,\n  options?: { readonly label?: string; readonly alignment?: number }\n): GPUBuffer {\n  if (!Number.isFinite(size) || size <= 0) {\n    throw new Error(`createUniformBuffer(size): size must be a positive number. Received: ${String(size)}`);\n  }\n\n  const alignment = options?.alignment ?? 16;\n  const alignedSize = alignTo(size, Math.max(4, alignment));\n\n  const maxSize = device.limits.maxUniformBufferBindingSize;\n  if (alignedSize > maxSize) {\n    throw new Error(\n      `createUniformBuffer(size): requested size ${alignedSize} exceeds device.limits.maxUniformBufferBindingSize (${maxSize}).`\n    );\n  }\n\n  return device.createBuffer({\n    label: options?.label,\n    size: alignedSize,\n    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n  });\n}\n\n/**\n * Writes CPU data into a uniform buffer (default offset 0).\n *\n * `data` must be a `BufferSource`:\n * - `ArrayBuffer` or `ArrayBufferView` (TypedArray/DataView)\n *\n * Important WebGPU constraint:\n * - `queue.writeBuffer()` requires write size (and offsets) to be multiples of 4 bytes.\n */\nexport function writeUniformBuffer(device: GPUDevice, buffer: GPUBuffer, data: BufferSource): void {\n  const src =\n    data instanceof ArrayBuffer\n      ? { arrayBuffer: data, offset: 0, size: data.byteLength }\n      : { arrayBuffer: data.buffer, offset: data.byteOffset, size: data.byteLength };\n\n  if (src.size === 0) return;\n\n  if ((src.offset & 3) !== 0 || (src.size & 3) !== 0) {\n    throw new Error(\n      `writeUniformBuffer(data): data byteOffset (${src.offset}) and byteLength (${src.size}) must be multiples of 4 for queue.writeBuffer().`\n    );\n  }\n\n  if (src.size > buffer.size) {\n    throw new Error(`writeUniformBuffer(data): data byteLength (${src.size}) exceeds buffer.size (${buffer.size}).`);\n  }\n\n  device.queue.writeBuffer(buffer, 0, src.arrayBuffer, src.offset, src.size);\n}\n","import gridWgsl from '../shaders/grid.wgsl?raw';\nimport type { AxisConfig } from '../config/types';\nimport type { LinearScale } from '../utils/scales';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport type { GridArea } from './createGridRenderer';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface AxisRenderer {\n  prepare(\n    axisConfig: AxisConfig,\n    scale: LinearScale,\n    orientation: 'x' | 'y',\n    gridArea: GridArea,\n    axisLineColor?: string,\n    axisTickColor?: string,\n    tickCount?: number\n  ): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface AxisRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_TICK_COUNT = 5;\nconst DEFAULT_TICK_LENGTH_CSS_PX = 6;\nconst DEFAULT_AXIS_RGBA: readonly [number, number, number, number] = [1, 1, 1, 0.8];\n\nconst createIdentityMat4Buffer = (): ArrayBuffer => {\n  // Column-major identity mat4x4\n  const buffer = new ArrayBuffer(16 * 4);\n  new Float32Array(buffer).set([\n    1, 0, 0, 0, // col0\n    0, 1, 0, 0, // col1\n    0, 0, 1, 0, // col2\n    0, 0, 0, 1, // col3\n  ]);\n  return buffer;\n};\n\nconst isFiniteGridArea = (gridArea: GridArea): boolean =>\n  Number.isFinite(gridArea.left) &&\n  Number.isFinite(gridArea.right) &&\n  Number.isFinite(gridArea.top) &&\n  Number.isFinite(gridArea.bottom) &&\n  Number.isFinite(gridArea.canvasWidth) &&\n  Number.isFinite(gridArea.canvasHeight);\n\nconst finiteOrUndefined = (v: number | undefined): number | undefined => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);\n\nconst normalizeDomain = (minCandidate: number, maxCandidate: number): { readonly min: number; readonly max: number } => {\n  let min = minCandidate;\n  let max = maxCandidate;\n\n  if (!Number.isFinite(min) || !Number.isFinite(max)) {\n    min = 0;\n    max = 1;\n  }\n\n  if (min === max) {\n    max = min + 1;\n  } else if (min > max) {\n    const t = min;\n    min = max;\n    max = t;\n  }\n\n  return { min, max };\n};\n\nconst generateAxisVertices = (\n  axisConfig: AxisConfig,\n  scale: LinearScale,\n  orientation: 'x' | 'y',\n  gridArea: GridArea,\n  tickCountOverride?: number\n): Float32Array => {\n  const { left, right, top, bottom, canvasWidth, canvasHeight } = gridArea;\n  // Be resilient: older call sites may omit/incorrectly pass DPR. Defaulting avoids hard crashes.\n  const devicePixelRatio =\n    Number.isFinite(gridArea.devicePixelRatio) && gridArea.devicePixelRatio > 0 ? gridArea.devicePixelRatio : 1;\n\n  if (!isFiniteGridArea(gridArea)) {\n    throw new Error('AxisRenderer.prepare: gridArea dimensions must be finite numbers.');\n  }\n  if (canvasWidth <= 0 || canvasHeight <= 0) {\n    throw new Error('AxisRenderer.prepare: canvas dimensions must be positive.');\n  }\n  if (left < 0 || right < 0 || top < 0 || bottom < 0) {\n    throw new Error('AxisRenderer.prepare: gridArea margins must be non-negative.');\n  }\n\n  const plotLeft = left * devicePixelRatio;\n  const plotRight = canvasWidth - right * devicePixelRatio;\n  const plotTop = top * devicePixelRatio;\n  const plotBottom = canvasHeight - bottom * devicePixelRatio;\n\n  const plotLeftClip = (plotLeft / canvasWidth) * 2.0 - 1.0;\n  const plotRightClip = (plotRight / canvasWidth) * 2.0 - 1.0;\n  const plotTopClip = 1.0 - (plotTop / canvasHeight) * 2.0; // flip Y\n  const plotBottomClip = 1.0 - (plotBottom / canvasHeight) * 2.0; // flip Y\n\n  const tickLengthCssPx = axisConfig.tickLength ?? DEFAULT_TICK_LENGTH_CSS_PX;\n  if (!Number.isFinite(tickLengthCssPx) || tickLengthCssPx < 0) {\n    throw new Error('AxisRenderer.prepare: tickLength must be a finite non-negative number.');\n  }\n\n  const tickCountRaw = tickCountOverride ?? DEFAULT_TICK_COUNT;\n  const tickCount = Math.max(1, Math.floor(tickCountRaw));\n  if (!Number.isFinite(tickCountRaw) || tickCount < 1) {\n    throw new Error('AxisRenderer.prepare: tickCount must be a finite number >= 1.');\n  }\n  const tickLengthDevicePx = tickLengthCssPx * devicePixelRatio;\n  const tickDeltaClipX = (tickLengthDevicePx / canvasWidth) * 2.0;\n  const tickDeltaClipY = (tickLengthDevicePx / canvasHeight) * 2.0;\n\n  // IMPORTANT: ignore non-finite overrides to keep GPU ticks consistent with the render coordinator\n  // (which also treats min/max as “unset” when non-finite).\n  const domainMinRaw =\n    finiteOrUndefined(axisConfig.min) ??\n    (orientation === 'x' ? scale.invert(plotLeftClip) : scale.invert(plotBottomClip));\n  const domainMaxRaw =\n    finiteOrUndefined(axisConfig.max) ??\n    (orientation === 'x' ? scale.invert(plotRightClip) : scale.invert(plotTopClip));\n  const domain = normalizeDomain(domainMinRaw, domainMaxRaw);\n  const domainMin = domain.min;\n  const domainMax = domain.max;\n\n  // Line-list segments:\n  // - 1 baseline segment\n  // - tickCount tick segments\n  const totalSegments = 1 + tickCount;\n  const vertices = new Float32Array(totalSegments * 2 * 2); // segments * 2 vertices * vec2<f32>\n\n  let idx = 0;\n\n  if (orientation === 'x') {\n    // Baseline along bottom edge of plot rect.\n    vertices[idx++] = plotLeftClip;\n    vertices[idx++] = plotBottomClip;\n    vertices[idx++] = plotRightClip;\n    vertices[idx++] = plotBottomClip;\n\n    // Ticks extend downward (outside plot).\n    const y0 = plotBottomClip;\n    const y1 = y0 - tickDeltaClipY;\n\n    for (let i = 0; i < tickCount; i++) {\n      const t = tickCount === 1 ? 0.5 : i / (tickCount - 1);\n      const v = domainMin + t * (domainMax - domainMin);\n      const x = scale.scale(v);\n\n      vertices[idx++] = x;\n      vertices[idx++] = y0;\n      vertices[idx++] = x;\n      vertices[idx++] = y1;\n    }\n  } else {\n    // Baseline along left edge of plot rect.\n    vertices[idx++] = plotLeftClip;\n    vertices[idx++] = plotBottomClip;\n    vertices[idx++] = plotLeftClip;\n    vertices[idx++] = plotTopClip;\n\n    // Ticks extend left (outside plot).\n    const x0 = plotLeftClip;\n    const x1 = x0 - tickDeltaClipX;\n\n    for (let i = 0; i < tickCount; i++) {\n      const t = tickCount === 1 ? 0.5 : i / (tickCount - 1);\n      const v = domainMin + t * (domainMax - domainMin);\n      const y = scale.scale(v);\n\n      vertices[idx++] = x0;\n      vertices[idx++] = y;\n      vertices[idx++] = x1;\n      vertices[idx++] = y;\n    }\n  }\n\n  return vertices;\n};\n\nexport function createAxisRenderer(device: GPUDevice, options?: AxisRendererOptions): AxisRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n      { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n    ],\n  });\n\n  const vsUniformBuffer = createUniformBuffer(device, 64, { label: 'axisRenderer/vsUniforms' });\n  const fsUniformBufferLine = createUniformBuffer(device, 16, { label: 'axisRenderer/fsUniformsLine' });\n  const fsUniformBufferTick = createUniformBuffer(device, 16, { label: 'axisRenderer/fsUniformsTick' });\n\n  const bindGroupLine = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [\n      { binding: 0, resource: { buffer: vsUniformBuffer } },\n      { binding: 1, resource: { buffer: fsUniformBufferLine } },\n    ],\n  });\n\n  const bindGroupTick = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [\n      { binding: 0, resource: { buffer: vsUniformBuffer } },\n      { binding: 1, resource: { buffer: fsUniformBufferTick } },\n    ],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'axisRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: gridWgsl,\n        label: 'grid.wgsl',\n        buffers: [\n          {\n            arrayStride: 8,\n            stepMode: 'vertex',\n            attributes: [{ shaderLocation: 0, format: 'float32x2', offset: 0 }],\n          },\n        ],\n      },\n      fragment: {\n        code: gridWgsl,\n        label: 'grid.wgsl',\n        formats: targetFormat,\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'line-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let vertexBuffer: GPUBuffer | null = null;\n  let vertexCount = 0;\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('AxisRenderer is disposed.');\n  };\n\n  const prepare: AxisRenderer['prepare'] = (\n    axisConfig,\n    scale,\n    orientation,\n    gridArea,\n    axisLineColor,\n    axisTickColor,\n    tickCount\n  ) => {\n    assertNotDisposed();\n\n    if (orientation !== 'x' && orientation !== 'y') {\n      throw new Error(\"AxisRenderer.prepare: orientation must be 'x' or 'y'.\");\n    }\n\n    const vertices = generateAxisVertices(axisConfig, scale, orientation, gridArea, tickCount);\n    const requiredSize = vertices.byteLength;\n    const bufferSize = Math.max(4, requiredSize);\n\n    if (!vertexBuffer || vertexBuffer.size < bufferSize) {\n      if (vertexBuffer) {\n        try {\n          vertexBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n      vertexBuffer = device.createBuffer({\n        label: 'axisRenderer/vertexBuffer',\n        size: bufferSize,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    device.queue.writeBuffer(vertexBuffer, 0, vertices.buffer, 0, vertices.byteLength);\n    vertexCount = vertices.length / 2;\n\n    // Identity transform (vertices already in clip-space).\n    writeUniformBuffer(device, vsUniformBuffer, createIdentityMat4Buffer());\n\n    // Separate colors for baseline vs ticks.\n    // Gracefully fall back to legacy (slightly brighter than grid) when parsing fails.\n    const axisLineColorString = axisLineColor ?? 'rgba(255,255,255,0.8)';\n    const axisTickColorString = axisTickColor ?? axisLineColorString;\n\n    const axisLineRgba = parseCssColorToRgba01(axisLineColorString) ?? DEFAULT_AXIS_RGBA;\n    const axisTickRgba = parseCssColorToRgba01(axisTickColorString) ?? axisLineRgba;\n\n    const lineColorBuffer = new ArrayBuffer(4 * 4);\n    new Float32Array(lineColorBuffer).set([\n      axisLineRgba[0],\n      axisLineRgba[1],\n      axisLineRgba[2],\n      axisLineRgba[3],\n    ]);\n    writeUniformBuffer(device, fsUniformBufferLine, lineColorBuffer);\n\n    const tickColorBuffer = new ArrayBuffer(4 * 4);\n    new Float32Array(tickColorBuffer).set([\n      axisTickRgba[0],\n      axisTickRgba[1],\n      axisTickRgba[2],\n      axisTickRgba[3],\n    ]);\n    writeUniformBuffer(device, fsUniformBufferTick, tickColorBuffer);\n  };\n\n  const render: AxisRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (vertexCount === 0 || !vertexBuffer) return;\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setVertexBuffer(0, vertexBuffer);\n\n    // Baseline: first 2 vertices.\n    passEncoder.setBindGroup(0, bindGroupLine);\n    passEncoder.draw(Math.min(2, vertexCount));\n\n    // Ticks: remaining vertices.\n    if (vertexCount > 2) {\n      passEncoder.setBindGroup(0, bindGroupTick);\n      passEncoder.draw(vertexCount - 2, 1, 2, 0);\n    }\n  };\n\n  const dispose: AxisRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    try {\n      fsUniformBufferLine.destroy();\n    } catch {\n      // best-effort\n    }\n    try {\n      fsUniformBufferTick.destroy();\n    } catch {\n      // best-effort\n    }\n    if (vertexBuffer) {\n      try {\n        vertexBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n\n    vertexBuffer = null;\n    vertexCount = 0;\n  };\n\n  return { prepare, render, dispose };\n}\n\n","import gridWgsl from '../shaders/grid.wgsl?raw';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface GridRenderer {\n  /**\n   * Backward compatible:\n   * - `prepare(gridArea, lineCount)` where `lineCount` is `{ horizontal?, vertical? }`\n   *\n   * Preferred:\n   * - `prepare(gridArea, { lineCount, color })`\n   */\n  prepare(gridArea: GridArea, lineCountOrOptions?: GridLineCount | GridPrepareOptions): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface GridArea {\n  readonly left: number;        // Left margin in CSS pixels\n  readonly right: number;       // Right margin in CSS pixels\n  readonly top: number;         // Top margin in CSS pixels\n  readonly bottom: number;      // Bottom margin in CSS pixels\n  readonly canvasWidth: number;  // Canvas width in device pixels (canvas.width)\n  readonly canvasHeight: number; // Canvas height in device pixels (canvas.height)\n  readonly devicePixelRatio: number; // Device pixel ratio for CSS-to-device conversion\n}\n\nexport interface GridLineCount {\n  readonly horizontal?: number;  // Default: 5\n  readonly vertical?: number;    // Default: 6\n}\n\nexport interface GridPrepareOptions {\n  readonly lineCount?: GridLineCount;\n  /**\n   * CSS color string used for grid lines.\n   *\n   * Expected formats: `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb(r,g,b)`, `rgba(r,g,b,a)`.\n   */\n  readonly color?: string;\n  /**\n   * When true, appends additional grid line geometry to the existing prepared\n   * batch instead of replacing it. This enables rendering multiple grid batches\n   * (e.g. different colors for horizontal vs vertical lines).\n   *\n   * Backward compatible: call sites that don't use `append` continue to replace\n   * the prepared geometry each frame.\n   */\n  readonly append?: boolean;\n}\n\nexport interface GridRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_HORIZONTAL_LINES = 5;\nconst DEFAULT_VERTICAL_LINES = 6;\nconst DEFAULT_GRID_COLOR = 'rgba(255,255,255,0.15)';\nconst DEFAULT_GRID_RGBA: readonly [number, number, number, number] = [1, 1, 1, 0.15];\n\nconst createIdentityMat4Buffer = (): ArrayBuffer => {\n  // Column-major identity mat4x4\n  const buffer = new ArrayBuffer(16 * 4);\n  new Float32Array(buffer).set([\n    1, 0, 0, 0, // col0\n    0, 1, 0, 0, // col1\n    0, 0, 1, 0, // col2\n    0, 0, 0, 1, // col3\n  ]);\n  return buffer;\n};\n\nconst generateGridVertices = (gridArea: GridArea, horizontal: number, vertical: number): Float32Array => {\n  const { left, right, top, bottom, canvasWidth, canvasHeight } = gridArea;\n  // Be resilient: older call sites may omit/incorrectly pass DPR. Defaulting avoids hard crashes.\n  const devicePixelRatio =\n    Number.isFinite(gridArea.devicePixelRatio) && gridArea.devicePixelRatio > 0 ? gridArea.devicePixelRatio : 1;\n\n  // Calculate plot area in device pixels using explicit DPR\n  const plotLeft = left * devicePixelRatio;\n  const plotRight = canvasWidth - right * devicePixelRatio;\n  const plotTop = top * devicePixelRatio;\n  const plotBottom = canvasHeight - bottom * devicePixelRatio;\n\n  const plotWidth = plotRight - plotLeft;\n  const plotHeight = plotBottom - plotTop;\n\n  // Total vertices: (horizontal + vertical) * 2 vertices per line\n  const totalLines = horizontal + vertical;\n  const vertices = new Float32Array(totalLines * 2 * 2); // 2 vertices * 2 floats per vertex\n\n  let idx = 0;\n\n  // Generate horizontal lines (constant Y, varying X)\n  for (let i = 0; i < horizontal; i++) {\n    // Calculate t parameter for even spacing\n    const t = horizontal === 1 ? 0.5 : i / (horizontal - 1);\n    const yDevice = plotTop + t * plotHeight;\n\n    // Convert to clip space\n    const xClipLeft = (plotLeft / canvasWidth) * 2.0 - 1.0;\n    const xClipRight = (plotRight / canvasWidth) * 2.0 - 1.0;\n    const yClip = 1.0 - (yDevice / canvasHeight) * 2.0; // Flip Y-axis\n\n    // First vertex (left edge)\n    vertices[idx++] = xClipLeft;\n    vertices[idx++] = yClip;\n\n    // Second vertex (right edge)\n    vertices[idx++] = xClipRight;\n    vertices[idx++] = yClip;\n  }\n\n  // Generate vertical lines (constant X, varying Y)\n  for (let i = 0; i < vertical; i++) {\n    // Calculate t parameter for even spacing\n    const t = vertical === 1 ? 0.5 : i / (vertical - 1);\n    const xDevice = plotLeft + t * plotWidth;\n\n    // Convert to clip space\n    const xClip = (xDevice / canvasWidth) * 2.0 - 1.0;\n    const yClipTop = 1.0 - (plotTop / canvasHeight) * 2.0; // Flip Y-axis\n    const yClipBottom = 1.0 - (plotBottom / canvasHeight) * 2.0; // Flip Y-axis\n\n    // First vertex (top edge)\n    vertices[idx++] = xClip;\n    vertices[idx++] = yClipTop;\n\n    // Second vertex (bottom edge)\n    vertices[idx++] = xClip;\n    vertices[idx++] = yClipBottom;\n  }\n\n  return vertices;\n};\n\nexport function createGridRenderer(device: GPUDevice, options?: GridRendererOptions): GridRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n      { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n    ],\n  });\n\n  const vsUniformBuffer = createUniformBuffer(device, 64, { label: 'gridRenderer/vsUniforms' });\n  const fsUniformBuffer = createUniformBuffer(device, 16, { label: 'gridRenderer/fsUniforms' });\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [\n      { binding: 0, resource: { buffer: vsUniformBuffer } },\n      { binding: 1, resource: { buffer: fsUniformBuffer } },\n    ],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'gridRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: gridWgsl,\n        label: 'grid.wgsl',\n        buffers: [\n          {\n            arrayStride: 8, // vec2<f32> = 2 * 4 bytes\n            stepMode: 'vertex',\n            attributes: [{ shaderLocation: 0, format: 'float32x2', offset: 0 }],\n          },\n        ],\n      },\n      fragment: {\n        code: gridWgsl,\n        label: 'grid.wgsl',\n        formats: targetFormat,\n        // Enable standard alpha blending so `fsUniforms.color.a` behaves as expected\n        // (blends into the cleared background instead of making the canvas pixels transparent).\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'line-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let vertexBuffer: GPUBuffer | null = null;\n  let combinedVertices: Float32Array | null = null;\n  let batches: Array<{\n    readonly vertexOffsetBytes: number;\n    readonly vertexCount: number;\n    readonly rgba: readonly [number, number, number, number];\n  }> = [];\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('GridRenderer is disposed.');\n  };\n\n  const prepare: GridRenderer['prepare'] = (gridArea, lineCountOrOptions) => {\n    assertNotDisposed();\n\n    const isOptionsObject =\n      lineCountOrOptions != null &&\n      typeof lineCountOrOptions === 'object' &&\n      ('lineCount' in lineCountOrOptions || 'color' in lineCountOrOptions || 'append' in lineCountOrOptions);\n\n    const options: GridPrepareOptions | undefined = isOptionsObject\n      ? (lineCountOrOptions as GridPrepareOptions)\n      : undefined;\n\n    const lineCount: GridLineCount | undefined = isOptionsObject\n      ? options?.lineCount\n      : (lineCountOrOptions as GridLineCount | undefined);\n\n    const horizontal = lineCount?.horizontal ?? DEFAULT_HORIZONTAL_LINES;\n    const vertical = lineCount?.vertical ?? DEFAULT_VERTICAL_LINES;\n    const colorString = options?.color ?? DEFAULT_GRID_COLOR;\n    const append = options?.append === true;\n\n    // Validate inputs\n    if (horizontal < 0 || vertical < 0) {\n      throw new Error('GridRenderer.prepare: line counts must be non-negative.');\n    }\n    if (\n      !Number.isFinite(gridArea.left) ||\n      !Number.isFinite(gridArea.right) ||\n      !Number.isFinite(gridArea.top) ||\n      !Number.isFinite(gridArea.bottom) ||\n      !Number.isFinite(gridArea.canvasWidth) ||\n      !Number.isFinite(gridArea.canvasHeight)\n    ) {\n      throw new Error('GridRenderer.prepare: gridArea dimensions must be finite numbers.');\n    }\n    if (gridArea.canvasWidth <= 0 || gridArea.canvasHeight <= 0) {\n      throw new Error('GridRenderer.prepare: canvas dimensions must be positive.');\n    }\n\n    // Early return if no lines to draw. If we're not appending, also clear any\n    // previously prepared geometry so subsequent renders draw nothing.\n    if (horizontal === 0 && vertical === 0) {\n      if (!append) {\n        combinedVertices = null;\n        batches = [];\n      }\n      return;\n    }\n\n    // Generate vertices for this batch\n    const vertices = generateGridVertices(gridArea, horizontal, vertical);\n    const newBatchVertexCount = (horizontal + vertical) * 2;\n\n    // Parse color for this batch (fallbacks to internal default)\n    const rgba = parseCssColorToRgba01(colorString) ?? DEFAULT_GRID_RGBA;\n\n    // Append or replace prepared geometry\n    let vertexOffsetBytes = 0;\n    if (append && combinedVertices && combinedVertices.byteLength > 0 && batches.length > 0) {\n      vertexOffsetBytes = combinedVertices.byteLength;\n      const combined = new Float32Array(combinedVertices.length + vertices.length);\n      combined.set(combinedVertices, 0);\n      combined.set(vertices, combinedVertices.length);\n      combinedVertices = combined;\n      batches = batches.concat([{ vertexOffsetBytes, vertexCount: newBatchVertexCount, rgba }]);\n    } else {\n      combinedVertices = vertices;\n      batches = [{ vertexOffsetBytes: 0, vertexCount: newBatchVertexCount, rgba }];\n    }\n\n    const requiredSize = combinedVertices.byteLength;\n    // Ensure minimum buffer size of 4 bytes\n    const bufferSize = Math.max(4, requiredSize);\n\n    // Create or recreate vertex buffer if needed\n    if (!vertexBuffer || vertexBuffer.size < bufferSize) {\n      if (vertexBuffer) {\n        try {\n          vertexBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n\n      vertexBuffer = device.createBuffer({\n        label: 'gridRenderer/vertexBuffer',\n        size: bufferSize,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    // Write combined vertex data\n    device.queue.writeBuffer(vertexBuffer, 0, combinedVertices.buffer, 0, combinedVertices.byteLength);\n\n    // Write uniforms\n    // VS uniform: identity transform (vertices already in clip space)\n    const transformBuffer = createIdentityMat4Buffer();\n    writeUniformBuffer(device, vsUniformBuffer, transformBuffer);\n  };\n\n  const render: GridRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (batches.length === 0 || !vertexBuffer) return;\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n\n    for (const batch of batches) {\n      // FS uniform: per-batch color\n      const colorBuffer = new ArrayBuffer(4 * 4);\n      new Float32Array(colorBuffer).set([batch.rgba[0], batch.rgba[1], batch.rgba[2], batch.rgba[3]]);\n      writeUniformBuffer(device, fsUniformBuffer, colorBuffer);\n\n      passEncoder.setVertexBuffer(0, vertexBuffer, batch.vertexOffsetBytes);\n      passEncoder.draw(batch.vertexCount);\n    }\n  };\n\n  const dispose: GridRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    try {\n      fsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    if (vertexBuffer) {\n      try {\n        vertexBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n\n    vertexBuffer = null;\n    combinedVertices = null;\n    batches = [];\n  };\n\n  return { prepare, render, dispose };\n}\n","export default \"// area.wgsl\\n// Minimal area-fill shader (triangle-strip):\\n// - Vertex input: vec2<f32> position in data coords\\n// - Uniforms: clip-space transform + baseline value + solid RGBA color\\n// - Topology: triangle-strip\\n// - CPU duplicates vertices as p0,p0,p1,p1,... and we use vertex_index parity:\\n//   even index -> \\\"top\\\" vertex (original y)\\n//   odd index  -> \\\"baseline\\\" vertex (uniform baseline)\\n\\nstruct VSUniforms {\\n  transform: mat4x4<f32>,\\n  baseline: f32,\\n  // Pad to 16-byte multiple (uniform buffer layout requirements).\\n  _pad0: vec3<f32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;\\n\\nstruct FSUniforms {\\n  color: vec4<f32>,\\n};\\n\\n@group(0) @binding(1) var<uniform> fsUniforms: FSUniforms;\\n\\nstruct VSIn {\\n  @location(0) position: vec2<f32>,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition: vec4<f32>,\\n};\\n\\n@vertex\\nfn vsMain(in: VSIn, @builtin(vertex_index) vertexIndex: u32) -> VSOut {\\n  var out: VSOut;\\n  let useBaseline = (vertexIndex & 1u) == 1u;\\n  let y = select(in.position.y, vsUniforms.baseline, useBaseline);\\n  let pos = vec2<f32>(in.position.x, y);\\n  out.clipPosition = vsUniforms.transform * vec4<f32>(pos, 0.0, 1.0);\\n  return out;\\n}\\n\\n@fragment\\nfn fsMain() -> @location(0) vec4<f32> {\\n  return fsUniforms.color;\\n}\\n\\n\"","import areaWgsl from '../shaders/area.wgsl?raw';\nimport type { ResolvedAreaSeriesConfig } from '../config/OptionResolver';\nimport type { CartesianSeriesData } from '../config/types';\nimport type { LinearScale } from '../utils/scales';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport { getPointCount, getX, getY, computeRawBoundsFromCartesianData } from '../data/cartesianData';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface AreaRenderer {\n  prepare(\n    seriesConfig: ResolvedAreaSeriesConfig,\n    data: CartesianSeriesData,\n    xScale: LinearScale,\n    yScale: LinearScale,\n    baseline?: number\n  ): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface AreaRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\ntype Rgba = readonly [r: number, g: number, b: number, a: number];\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst parseSeriesColorToRgba01 = (color: string): Rgba =>\n  parseCssColorToRgba01(color) ?? ([0, 0, 0, 1] as const);\n\n\nconst computeClipAffineFromScale = (\n  scale: LinearScale,\n  v0: number,\n  v1: number\n): { readonly a: number; readonly b: number } => {\n  const p0 = scale.scale(v0);\n  const p1 = scale.scale(v1);\n\n  // If the domain sample is degenerate or non-finite, fall back to constant output.\n  if (!Number.isFinite(v0) || !Number.isFinite(v1) || v0 === v1 || !Number.isFinite(p0) || !Number.isFinite(p1)) {\n    return { a: 0, b: Number.isFinite(p0) ? p0 : 0 };\n  }\n\n  const a = (p1 - p0) / (v1 - v0);\n  const b = p0 - a * v0;\n  return { a: Number.isFinite(a) ? a : 0, b: Number.isFinite(b) ? b : 0 };\n};\n\nconst writeTransformMat4F32 = (out: Float32Array, ax: number, bx: number, ay: number, by: number): void => {\n  // Column-major mat4x4 for: clip = M * vec4(x, y, 0, 1)\n  out[0] = ax;\n  out[1] = 0;\n  out[2] = 0;\n  out[3] = 0; // col0\n  out[4] = 0;\n  out[5] = ay;\n  out[6] = 0;\n  out[7] = 0; // col1\n  out[8] = 0;\n  out[9] = 0;\n  out[10] = 1;\n  out[11] = 0; // col2\n  out[12] = bx;\n  out[13] = by;\n  out[14] = 0;\n  out[15] = 1; // col3\n};\n\nconst createAreaVertices = (data: CartesianSeriesData): Float32Array => {\n  // Triangle-strip expects duplicated vertices:\n  // p0,p0,p1,p1,... and WGSL uses vertex_index parity to swap y to baseline for odd indices.\n  //\n  // For null gaps: when a point has NaN values, emit (0,0) degenerate vertices.\n  // This creates zero-area triangles at gap boundaries, visually breaking the fill.\n  const n = getPointCount(data);\n  const out = new Float32Array(n * 2 * 2); // n * 2 vertices * vec2<f32>\n\n  let idx = 0;\n  for (let i = 0; i < n; i++) {\n    const x = getX(data, i);\n    const y = getY(data, i);\n    if (!Number.isFinite(x) || !Number.isFinite(y)) {\n      // Degenerate: collapse to (0,0) — produces zero-area triangles in strip\n      out[idx++] = 0;\n      out[idx++] = 0;\n      out[idx++] = 0;\n      out[idx++] = 0;\n    } else {\n      out[idx++] = x;\n      out[idx++] = y;\n      out[idx++] = x;\n      out[idx++] = y;\n    }\n  }\n\n  return out;\n};\n\nexport function createAreaRenderer(device: GPUDevice, options?: AreaRendererOptions): AreaRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n      { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n    ],\n  });\n\n  const vsUniformBuffer = createUniformBuffer(device, 96, { label: 'areaRenderer/vsUniforms' });\n  const fsUniformBuffer = createUniformBuffer(device, 16, { label: 'areaRenderer/fsUniforms' });\n\n  // Reused CPU-side staging for uniform writes (avoid per-frame allocations).\n  const vsUniformScratchBuffer = new ArrayBuffer(96);\n  const vsUniformScratchF32 = new Float32Array(vsUniformScratchBuffer);\n  const fsUniformScratchF32 = new Float32Array(4);\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [\n      { binding: 0, resource: { buffer: vsUniformBuffer } },\n      { binding: 1, resource: { buffer: fsUniformBuffer } },\n    ],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'areaRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: areaWgsl,\n        label: 'area.wgsl',\n        buffers: [\n          {\n            arrayStride: 8,\n            stepMode: 'vertex',\n            attributes: [{ shaderLocation: 0, format: 'float32x2', offset: 0 }],\n          },\n        ],\n      },\n      fragment: {\n        code: areaWgsl,\n        label: 'area.wgsl',\n        formats: targetFormat,\n        // Enable standard alpha blending so `areaStyle.opacity` behaves correctly.\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-strip', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let vertexBuffer: GPUBuffer | null = null;\n  let vertexCount = 0;\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('AreaRenderer is disposed.');\n  };\n\n  const writeVsUniforms = (ax: number, bx: number, ay: number, by: number, baseline: number): void => {\n    // VSUniforms:\n    // - mat4x4<f32> (64 bytes)\n    // - baseline: f32 (4 bytes)\n    // - (implicit padding to next 16B boundary) (12 bytes)\n    // - _pad0: vec3<f32> (occupies 16 bytes in a uniform buffer)\n    // Total: 96 bytes.\n    //\n    // Layout details (uniform address space):\n    // - transform at byte offset 0\n    // - baseline at byte offset 64 (f32[16])\n    // - _pad0 at byte offset 80 (f32[20..22]) with trailing 4B padding\n    writeTransformMat4F32(vsUniformScratchF32, ax, bx, ay, by);\n    vsUniformScratchF32[16] = baseline;\n    vsUniformScratchF32[17] = 0;\n    vsUniformScratchF32[18] = 0;\n    vsUniformScratchF32[19] = 0;\n    vsUniformScratchF32[20] = 0;\n    vsUniformScratchF32[21] = 0;\n    vsUniformScratchF32[22] = 0;\n    vsUniformScratchF32[23] = 0;\n    writeUniformBuffer(device, vsUniformBuffer, vsUniformScratchBuffer);\n  };\n\n  const prepare: AreaRenderer['prepare'] = (seriesConfig, data, xScale, yScale, baseline) => {\n    assertNotDisposed();\n\n    const vertices = createAreaVertices(data);\n    const requiredSize = vertices.byteLength;\n    const bufferSize = Math.max(4, requiredSize);\n\n    if (!vertexBuffer || vertexBuffer.size < bufferSize) {\n      if (vertexBuffer) {\n        try {\n          vertexBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n      vertexBuffer = device.createBuffer({\n        label: 'areaRenderer/vertexBuffer',\n        size: bufferSize,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    if (vertices.byteLength > 0) {\n      device.queue.writeBuffer(vertexBuffer, 0, vertices.buffer, 0, vertices.byteLength);\n    }\n    vertexCount = vertices.length / 2;\n\n    const bounds = computeRawBoundsFromCartesianData(data);\n    const { xMin, xMax, yMin, yMax } = bounds ?? { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };\n    const { a: ax, b: bx } = computeClipAffineFromScale(xScale, xMin, xMax);\n    const { a: ay, b: by } = computeClipAffineFromScale(yScale, yMin, yMax);\n\n    const baselineValue =\n      Number.isFinite(baseline ?? Number.NaN) ? (baseline as number) : Number.isFinite(yMin) ? yMin : 0;\n\n    writeVsUniforms(ax, bx, ay, by, baselineValue);\n\n    // Use the resolved fill color from areaStyle.color (not seriesConfig.color).\n    const [r, g, b, a] = parseSeriesColorToRgba01(seriesConfig.areaStyle.color);\n    const opacity = clamp01(seriesConfig.areaStyle.opacity);\n    fsUniformScratchF32[0] = r;\n    fsUniformScratchF32[1] = g;\n    fsUniformScratchF32[2] = b;\n    fsUniformScratchF32[3] = clamp01(a * opacity);\n    writeUniformBuffer(device, fsUniformBuffer, fsUniformScratchF32);\n  };\n\n  const render: AreaRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (!vertexBuffer || vertexCount < 4) return;\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n    passEncoder.setVertexBuffer(0, vertexBuffer);\n    passEncoder.draw(vertexCount);\n  };\n\n  const dispose: AreaRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    if (vertexBuffer) {\n      try {\n        vertexBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    vertexBuffer = null;\n    vertexCount = 0;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    try {\n      fsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n  };\n\n  return { prepare, render, dispose };\n}\n\n","export default \"// line.wgsl — Screen-space quad expansion with SDF-based anti-aliasing.\\n//\\n// Each \\\"instance\\\" draws one line segment (point[i] → point[i+1]).\\n// 6 vertices per instance (2 triangles = 1 quad per segment).\\n//\\n// The vertex shader:\\n//   1. Reads endpoints from a storage buffer.\\n//   2. Transforms both to clip space using the mat4x4 transform.\\n//   3. Converts clip→screen (NDC * canvasSize * 0.5).\\n//   4. Computes the perpendicular direction in screen space.\\n//   5. Offsets vertices by ±(halfWidth + AA_PADDING) along the perpendicular.\\n//   6. Converts back to clip space.\\n//   7. Outputs `acrossDevice` varying for SDF-based AA.\\n//\\n// The fragment shader applies smoothstep AA on the distance-from-edge.\\n\\nconst AA_PADDING: f32 = 1.5;\\n\\nstruct VSUniforms {\\n  transform       : mat4x4<f32>,  // 64 bytes: data-coord → clip-space\\n  canvasSize      : vec2<f32>,     //  8 bytes: device pixels (width, height)\\n  devicePixelRatio: f32,           //  4 bytes\\n  lineWidthCssPx  : f32,           //  4 bytes: line width in CSS pixels\\n};\\n// Total: 80 bytes (aligned to 16).\\n\\n@group(0) @binding(0) var<uniform> vsUniforms : VSUniforms;\\n\\nstruct FSUniforms {\\n  color : vec4<f32>,\\n};\\n\\n@group(0) @binding(1) var<uniform> fsUniforms : FSUniforms;\\n\\n@group(0) @binding(2) var<storage, read> points : array<vec2<f32>>;\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition : vec4<f32>,\\n  @location(0) acrossDevice       : f32,\\n  @location(1) @interpolate(flat) widthDevice : f32,\\n};\\n\\n// Returns UV for the 6 vertices of a quad (2 triangles):\\n//   uv.x: 0 → endpoint A, 1 → endpoint B\\n//   uv.y: 0 → +side, 1 → −side\\nfn quadUv(vid : u32) -> vec2<f32> {\\n  switch (vid) {\\n    case 0u: { return vec2<f32>(0.0, 0.0); }\\n    case 1u: { return vec2<f32>(1.0, 0.0); }\\n    case 2u: { return vec2<f32>(0.0, 1.0); }\\n    case 3u: { return vec2<f32>(0.0, 1.0); }\\n    case 4u: { return vec2<f32>(1.0, 0.0); }\\n    default: { return vec2<f32>(1.0, 1.0); }\\n  }\\n}\\n\\n@vertex\\nfn vsMain(\\n  @builtin(vertex_index) vid : u32,\\n  @builtin(instance_index) iid : u32,\\n) -> VSOut {\\n  let uv = quadUv(vid);\\n\\n  // Read segment endpoints in data coordinates.\\n  let pA_data = points[iid];\\n  let pB_data = points[iid + 1u];\\n\\n  // ── Gap detection ──────────────────────────────────────────────\\n  // Null entries in the data array are packed as NaN by the CPU.\\n  // Collapse the quad to a degenerate point so the rasterizer discards it.\\n  // WGSL has no isnan(); use the IEEE 754 property that NaN != NaN.\\n  if (pA_data.x != pA_data.x || pA_data.y != pA_data.y ||\\n      pB_data.x != pB_data.x || pB_data.y != pB_data.y) {\\n    var out: VSOut;\\n    out.clipPosition = vec4<f32>(0.0, 0.0, 0.0, 0.0);\\n    out.acrossDevice = 0.0;\\n    out.widthDevice = 0.0;\\n    return out;\\n  }\\n\\n  // Transform to clip space.\\n  let clipA = vsUniforms.transform * vec4<f32>(pA_data, 0.0, 1.0);\\n  let clipB = vsUniforms.transform * vec4<f32>(pB_data, 0.0, 1.0);\\n\\n  // Convert clip → screen (device pixels). \\n  // screen = (ndc * 0.5 + 0.5) * canvasSize, but Y is flipped.\\n  let ndcA = clipA.xy / clipA.w;\\n  let ndcB = clipB.xy / clipB.w;\\n  let screenA = vec2<f32>(\\n    (ndcA.x * 0.5 + 0.5) * vsUniforms.canvasSize.x,\\n    (1.0 - (ndcA.y * 0.5 + 0.5)) * vsUniforms.canvasSize.y,\\n  );\\n  let screenB = vec2<f32>(\\n    (ndcB.x * 0.5 + 0.5) * vsUniforms.canvasSize.x,\\n    (1.0 - (ndcB.y * 0.5 + 0.5)) * vsUniforms.canvasSize.y,\\n  );\\n\\n  // Segment direction and perpendicular in screen space.\\n  let delta = screenB - screenA;\\n  let segLen = length(delta);\\n\\n  // Degenerate segment: collapse quad to a degenerate triangle.\\n  if (segLen < 1e-6) {\\n    var out : VSOut;\\n    out.clipPosition = clipA;\\n    out.acrossDevice = 0.0;\\n    out.widthDevice = 0.0;\\n    return out;\\n  }\\n\\n  let dir = delta / segLen;\\n  // Perpendicular: rotate 90° CW → (dy, -dx).\\n  let perp = vec2<f32>(dir.y, -dir.x);\\n\\n  // Compute line width in device pixels + AA padding.\\n  let dpr = max(vsUniforms.devicePixelRatio, 1e-6);\\n  let widthDevice = max(1.0, vsUniforms.lineWidthCssPx * dpr);\\n  let halfExtent = widthDevice * 0.5 + AA_PADDING;\\n\\n  // Select endpoint: uv.x=0 → A, uv.x=1 → B.\\n  let baseScreen = mix(screenA, screenB, uv.x);\\n\\n  // Offset perpendicular: uv.y selects +side (0) vs −side (1).\\n  let side = mix(1.0, -1.0, uv.y);\\n  let screenPos = baseScreen + perp * halfExtent * side;\\n\\n  // acrossDevice: 0 at −side edge, widthDevice at +side edge.\\n  // Map from [−halfExtent, +halfExtent] to [0, widthDevice + 2*AA_PADDING].\\n  let totalExtent = 2.0 * halfExtent;\\n  let acrossDevice = (side * halfExtent + halfExtent) / totalExtent * totalExtent;\\n  // Simplified: acrossDevice = halfExtent * (1 + side) = halfExtent + halfExtent * side\\n  // But for the fragment shader we want [0, totalExtent]:\\n  // Let's define it properly:\\n  // At side=+1: screenPos is at +halfExtent from center → acrossDevice = totalExtent\\n  // At side=-1: screenPos is at -halfExtent from center → acrossDevice = 0\\n  let acrossDeviceVal = halfExtent * (1.0 + side);\\n\\n  // Convert screen → clip.\\n  let clipX = (screenPos.x / vsUniforms.canvasSize.x) * 2.0 - 1.0;\\n  let clipY = 1.0 - (screenPos.y / vsUniforms.canvasSize.y) * 2.0;\\n\\n  var out : VSOut;\\n  out.clipPosition = vec4<f32>(clipX, clipY, 0.0, 1.0);\\n  out.acrossDevice = acrossDeviceVal;\\n  out.widthDevice = widthDevice;\\n  return out;\\n}\\n\\n@fragment\\nfn fsMain(in : VSOut) -> @location(0) vec4<f32> {\\n  let totalExtent = in.widthDevice + 2.0 * AA_PADDING;\\n  let edgeDist = min(in.acrossDevice, totalExtent - in.acrossDevice);\\n\\n  // Smooth step from 0 to AA zone for anti-aliased edges.\\n  let aa = max(fwidth(in.acrossDevice), 1e-3) * 1.25;\\n  let edgeCoverage = smoothstep(0.0, aa, edgeDist);\\n\\n  // Also fade out in the AA_PADDING region (beyond the nominal half-width).\\n  // The padding zone is [0, AA_PADDING] at each edge.\\n  // Distance from the nominal edge = edgeDist - AA_PADDING (negative means inside).\\n  // Actually, remap: the nominal line occupies [AA_PADDING, AA_PADDING + widthDevice].\\n  let nominalDist = min(in.acrossDevice - AA_PADDING, (AA_PADDING + in.widthDevice) - in.acrossDevice);\\n  let paddingCoverage = smoothstep(0.0, aa, nominalDist);\\n\\n  // Combine: paddingCoverage handles the SDF fade, edgeCoverage handles the outer trim.\\n  // For thin lines (< 1 device px), paddingCoverage alone provides the desired fade.\\n  let coverage = min(edgeCoverage, paddingCoverage);\\n\\n  var color = fsUniforms.color;\\n  color = vec4<f32>(color.rgb, color.a * coverage);\\n  return color;\\n}\\n\"","import lineWgsl from '../shaders/line.wgsl?raw';\nimport type { ResolvedLineSeriesConfig } from '../config/OptionResolver';\nimport type { LinearScale } from '../utils/scales';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport { getPointCount, computeRawBoundsFromCartesianData } from '../data/cartesianData';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface LineRenderer {\n  prepare(\n    seriesConfig: ResolvedLineSeriesConfig,\n    dataBuffer: GPUBuffer,\n    xScale: LinearScale,\n    yScale: LinearScale,\n    xOffset?: number,\n    devicePixelRatio?: number,\n    canvasWidthDevicePx?: number,\n    canvasHeightDevicePx?: number,\n  ): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface LineRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   * Opt-in only: if omitted, behavior is identical to the uncached path.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\ntype Rgba = readonly [r: number, g: number, b: number, a: number];\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_LINE_WIDTH_CSS_PX = 2;\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst parseSeriesColorToRgba01 = (color: string): Rgba =>\n  parseCssColorToRgba01(color) ?? ([0, 0, 0, 1] as const);\n\n\nconst computeClipAffineFromScale = (\n  scale: LinearScale,\n  v0: number,\n  v1: number\n): { readonly a: number; readonly b: number } => {\n  const p0 = scale.scale(v0);\n  const p1 = scale.scale(v1);\n\n  // If the domain sample is degenerate or non-finite, fall back to constant output.\n  if (!Number.isFinite(v0) || !Number.isFinite(v1) || v0 === v1 || !Number.isFinite(p0) || !Number.isFinite(p1)) {\n    return { a: 0, b: Number.isFinite(p0) ? p0 : 0 };\n  }\n\n  const a = (p1 - p0) / (v1 - v0);\n  const b = p0 - a * v0;\n  return { a: Number.isFinite(a) ? a : 0, b: Number.isFinite(b) ? b : 0 };\n};\n\nconst writeTransformMat4F32 = (out: Float32Array, ax: number, bx: number, ay: number, by: number): void => {\n  // Column-major mat4x4 for: clip = M * vec4(x, y, 0, 1)\n  out[0] = ax;\n  out[1] = 0;\n  out[2] = 0;\n  out[3] = 0; // col0\n  out[4] = 0;\n  out[5] = ay;\n  out[6] = 0;\n  out[7] = 0; // col1\n  out[8] = 0;\n  out[9] = 0;\n  out[10] = 1;\n  out[11] = 0; // col2\n  out[12] = bx;\n  out[13] = by;\n  out[14] = 0;\n  out[15] = 1; // col3\n};\n\nexport function createLineRenderer(device: GPUDevice, options?: LineRendererOptions): LineRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n      { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n      { binding: 2, visibility: GPUShaderStage.VERTEX, buffer: { type: 'read-only-storage' } },\n    ],\n  });\n\n  // VS uniforms: mat4x4 (64) + canvasSize (8) + dpr (4) + lineWidthCssPx (4) = 80 bytes.\n  const vsUniformBuffer = createUniformBuffer(device, 80, { label: 'lineRenderer/vsUniforms' });\n  const fsUniformBuffer = createUniformBuffer(device, 16, { label: 'lineRenderer/fsUniforms' });\n\n  // Reused CPU-side staging for uniform writes (avoid per-frame allocations).\n  const vsUniformScratchBuffer = new ArrayBuffer(80);\n  const vsUniformScratchF32 = new Float32Array(vsUniformScratchBuffer);\n  const fsUniformScratchF32 = new Float32Array(4);\n\n  // Bind group is recreated per-frame because the storage buffer (data buffer) changes per series.\n  let currentBindGroup: GPUBindGroup | null = null;\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'lineRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: lineWgsl,\n        label: 'line.wgsl',\n        buffers: [], // No vertex buffers — points are read from storage buffer.\n      },\n      fragment: {\n        code: lineWgsl,\n        label: 'line.wgsl',\n        formats: targetFormat,\n        // Enable standard alpha blending so per-series `lineStyle.opacity` and AA transparency work.\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let currentPointCount = 0;\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('LineRenderer is disposed.');\n  };\n\n  const prepare: LineRenderer['prepare'] = (\n    seriesConfig,\n    dataBuffer,\n    xScale,\n    yScale,\n    xOffset = 0,\n    devicePixelRatio = 1,\n    canvasWidthDevicePx = 1,\n    canvasHeightDevicePx = 1,\n  ) => {\n    assertNotDisposed();\n\n    currentPointCount = getPointCount(seriesConfig.data);\n\n    const bounds = computeRawBoundsFromCartesianData(seriesConfig.data);\n    const { xMin, xMax, yMin, yMax } = bounds ?? { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };\n    const { a: ax, b: bx } = computeClipAffineFromScale(xScale, xMin, xMax);\n    const { a: ay, b: by } = computeClipAffineFromScale(yScale, yMin, yMax);\n\n    // When the vertex buffer packs x as (x - xOffset) (to preserve Float32 precision for large\n    // domains like epoch-ms), fold the offset back into the affine's intercept in f64 on CPU:\n    // clipX = ax * (x - xOffset) + (bx + ax * xOffset)\n    const bxAdjusted = bx + ax * xOffset;\n\n    // Write VS uniforms: mat4x4 (16 floats) + canvasSize (2 floats) + dpr (1 float) + lineWidth (1 float).\n    writeTransformMat4F32(vsUniformScratchF32, ax, bxAdjusted, ay, by);\n    const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;\n    const canvasW = Number.isFinite(canvasWidthDevicePx) && canvasWidthDevicePx > 0 ? canvasWidthDevicePx : 1;\n    const canvasH = Number.isFinite(canvasHeightDevicePx) && canvasHeightDevicePx > 0 ? canvasHeightDevicePx : 1;\n    const lineWidthCss = Number.isFinite(seriesConfig.lineStyle.width) && seriesConfig.lineStyle.width > 0\n      ? seriesConfig.lineStyle.width\n      : DEFAULT_LINE_WIDTH_CSS_PX;\n\n    vsUniformScratchF32[16] = canvasW;\n    vsUniformScratchF32[17] = canvasH;\n    vsUniformScratchF32[18] = dpr;\n    vsUniformScratchF32[19] = lineWidthCss;\n    writeUniformBuffer(device, vsUniformBuffer, vsUniformScratchBuffer);\n\n    const [r, g, b, a] = parseSeriesColorToRgba01(seriesConfig.color);\n    const opacity = clamp01(seriesConfig.lineStyle.opacity);\n    fsUniformScratchF32[0] = r;\n    fsUniformScratchF32[1] = g;\n    fsUniformScratchF32[2] = b;\n    fsUniformScratchF32[3] = clamp01(a * opacity);\n    writeUniformBuffer(device, fsUniformBuffer, fsUniformScratchF32);\n\n    // Recreate bind group with the current data buffer.\n    currentBindGroup = device.createBindGroup({\n      layout: bindGroupLayout,\n      entries: [\n        { binding: 0, resource: { buffer: vsUniformBuffer } },\n        { binding: 1, resource: { buffer: fsUniformBuffer } },\n        { binding: 2, resource: { buffer: dataBuffer } },\n      ],\n    });\n  };\n\n  const render: LineRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    // Need at least 2 points to form 1 segment.\n    if (!currentBindGroup || currentPointCount < 2) return;\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, currentBindGroup);\n    // 6 vertices per instance (quad), (pointCount - 1) instances (segments).\n    passEncoder.draw(6, currentPointCount - 1);\n  };\n\n  const dispose: LineRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    currentBindGroup = null;\n    currentPointCount = 0;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    try {\n      fsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n  };\n\n  return { prepare, render, dispose };\n}\n","export default \"// scatter.wgsl\\n// Instanced anti-aliased circle shader (SDF):\\n// - Per-instance vertex input:\\n//   - center   = vec2<f32> point center (transformed by VSUniforms.transform)\\n//   - radiusPx = f32 circle radius in pixels\\n// - Draw call: draw(6, instanceCount) using triangle-list expansion in VS\\n// - Uniforms:\\n//   - @group(0) @binding(0): VSUniforms { transform, viewportPx }\\n//   - @group(0) @binding(1): FSUniforms { color }\\n//\\n// Notes:\\n// - `viewportPx` is the current render target size in pixels (width, height).\\n// - The quad is expanded in clip space using `radiusPx` and `viewportPx`.\\n\\nstruct VSUniforms {\\n  transform: mat4x4<f32>,\\n  viewportPx: vec2<f32>,\\n  // Pad to 16-byte alignment (mat4x4 is 64B; vec2 adds 8B; pad to 80B).\\n  _pad0: vec2<f32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;\\n\\nstruct FSUniforms {\\n  color: vec4<f32>,\\n};\\n\\n@group(0) @binding(1) var<uniform> fsUniforms: FSUniforms;\\n\\nstruct VSIn {\\n  @location(0) center: vec2<f32>,\\n  @location(1) radiusPx: f32,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition: vec4<f32>,\\n  @location(0) localPx: vec2<f32>,\\n  @location(1) radiusPx: f32,\\n};\\n\\n@vertex\\nfn vsMain(in: VSIn, @builtin(vertex_index) vertexIndex: u32) -> VSOut {\\n  // Fixed local corners for 2 triangles (triangle-list).\\n  // `localNdc` is a quad in [-1, 1]^2; we convert it to pixel offsets via radiusPx.\\n  let localNdc = array<vec2<f32>, 6>(\\n    vec2<f32>(-1.0, -1.0),\\n    vec2<f32>( 1.0, -1.0),\\n    vec2<f32>(-1.0,  1.0),\\n    vec2<f32>(-1.0,  1.0),\\n    vec2<f32>( 1.0, -1.0),\\n    vec2<f32>( 1.0,  1.0)\\n  );\\n\\n  let corner = localNdc[vertexIndex];\\n  let localPx = corner * in.radiusPx;\\n\\n  // Convert pixel offset to clip-space offset.\\n  // Clip space spans [-1, 1] across the viewport, so px -> clip is (2 / viewportPx).\\n  let localClip = localPx * (2.0 / vsUniforms.viewportPx);\\n\\n  let centerClip = (vsUniforms.transform * vec4<f32>(in.center, 0.0, 1.0)).xy;\\n\\n  var out: VSOut;\\n  out.clipPosition = vec4<f32>(centerClip + localClip, 0.0, 1.0);\\n  out.localPx = localPx;\\n  out.radiusPx = in.radiusPx;\\n  return out;\\n}\\n\\n@fragment\\nfn fsMain(in: VSOut) -> @location(0) vec4<f32> {\\n  // Signed distance to the circle boundary (negative inside).\\n  let dist = length(in.localPx) - in.radiusPx;\\n\\n  // Analytic-ish AA: smooth edge based on derivative of dist in screen space.\\n  let w = fwidth(dist);\\n  let a = 1.0 - smoothstep(0.0, w, dist);\\n\\n  // Discard fully outside to avoid unnecessary blending work.\\n  if (a <= 0.0) {\\n    discard;\\n  }\\n\\n  return vec4<f32>(fsUniforms.color.rgb, fsUniforms.color.a * a);\\n}\\n\\n\"","import scatterWgsl from '../shaders/scatter.wgsl?raw';\nimport type { ResolvedScatterSeriesConfig } from '../config/OptionResolver';\nimport type { CartesianSeriesData } from '../config/types';\nimport type { LinearScale } from '../utils/scales';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport type { GridArea } from './createGridRenderer';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport { getPointCount, getX, getY, getSize, computeRawBoundsFromCartesianData } from '../data/cartesianData';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface ScatterRenderer {\n  prepare(\n    seriesConfig: ResolvedScatterSeriesConfig,\n    data: CartesianSeriesData,\n    xScale: LinearScale,\n    yScale: LinearScale,\n    gridArea?: GridArea\n  ): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface ScatterRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\ntype Rgba = readonly [r: number, g: number, b: number, a: number];\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_SCATTER_RADIUS_CSS_PX = 4;\nconst INSTANCE_STRIDE_BYTES = 16; // center.xy, radiusPx, pad\nconst INSTANCE_STRIDE_FLOATS = INSTANCE_STRIDE_BYTES / 4;\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst clampInt = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v | 0));\n\nconst parseSeriesColorToRgba01 = (color: string): Rgba =>\n  parseCssColorToRgba01(color) ?? ([0, 0, 0, 1] as const);\n\nconst nextPow2 = (v: number): number => {\n  if (!Number.isFinite(v) || v <= 0) return 1;\n  const n = Math.ceil(v);\n  return 2 ** Math.ceil(Math.log2(n));\n};\n\n\nconst computeClipAffineFromScale = (\n  scale: LinearScale,\n  v0: number,\n  v1: number\n): { readonly a: number; readonly b: number } => {\n  const p0 = scale.scale(v0);\n  const p1 = scale.scale(v1);\n\n  // If the domain sample is degenerate or non-finite, fall back to constant output.\n  if (!Number.isFinite(v0) || !Number.isFinite(v1) || v0 === v1 || !Number.isFinite(p0) || !Number.isFinite(p1)) {\n    return { a: 0, b: Number.isFinite(p0) ? p0 : 0 };\n  }\n\n  const a = (p1 - p0) / (v1 - v0);\n  const b = p0 - a * v0;\n  return { a: Number.isFinite(a) ? a : 0, b: Number.isFinite(b) ? b : 0 };\n};\n\nconst writeTransformMat4F32 = (out: Float32Array, ax: number, bx: number, ay: number, by: number): void => {\n  // Column-major mat4x4 for: clip = M * vec4(x, y, 0, 1)\n  out[0] = ax;\n  out[1] = 0;\n  out[2] = 0;\n  out[3] = 0; // col0\n  out[4] = 0;\n  out[5] = ay;\n  out[6] = 0;\n  out[7] = 0; // col1\n  out[8] = 0;\n  out[9] = 0;\n  out[10] = 1;\n  out[11] = 0; // col2\n  out[12] = bx;\n  out[13] = by;\n  out[14] = 0;\n  out[15] = 1; // col3\n};\n\nconst computePlotScissorDevicePx = (\n  gridArea: GridArea\n): { readonly x: number; readonly y: number; readonly w: number; readonly h: number } => {\n  const { canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeftDevice = gridArea.left * devicePixelRatio;\n  const plotRightDevice = canvasWidth - gridArea.right * devicePixelRatio;\n  const plotTopDevice = gridArea.top * devicePixelRatio;\n  const plotBottomDevice = canvasHeight - gridArea.bottom * devicePixelRatio;\n\n  const scissorX = clampInt(Math.floor(plotLeftDevice), 0, Math.max(0, canvasWidth));\n  const scissorY = clampInt(Math.floor(plotTopDevice), 0, Math.max(0, canvasHeight));\n  const scissorR = clampInt(Math.ceil(plotRightDevice), 0, Math.max(0, canvasWidth));\n  const scissorB = clampInt(Math.ceil(plotBottomDevice), 0, Math.max(0, canvasHeight));\n  const scissorW = Math.max(0, scissorR - scissorX);\n  const scissorH = Math.max(0, scissorB - scissorY);\n\n  return { x: scissorX, y: scissorY, w: scissorW, h: scissorH };\n};\n\nexport function createScatterRenderer(device: GPUDevice, options?: ScatterRendererOptions): ScatterRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n      { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n    ],\n  });\n\n  // VSUniforms: mat4x4 (64) + viewportPx vec2 (8) + pad vec2 (8) = 80 bytes.\n  const vsUniformBuffer = createUniformBuffer(device, 80, { label: 'scatterRenderer/vsUniforms' });\n  const fsUniformBuffer = createUniformBuffer(device, 16, { label: 'scatterRenderer/fsUniforms' });\n\n  // Reused CPU-side staging for uniform writes (avoid per-frame allocations).\n  const vsUniformScratchBuffer = new ArrayBuffer(80);\n  const vsUniformScratchF32 = new Float32Array(vsUniformScratchBuffer);\n  const fsUniformScratchF32 = new Float32Array(4);\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [\n      { binding: 0, resource: { buffer: vsUniformBuffer } },\n      { binding: 1, resource: { buffer: fsUniformBuffer } },\n    ],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'scatterRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: scatterWgsl,\n        label: 'scatter.wgsl',\n        buffers: [\n          {\n            arrayStride: INSTANCE_STRIDE_BYTES,\n            stepMode: 'instance',\n            attributes: [\n              { shaderLocation: 0, format: 'float32x2', offset: 0 },\n              { shaderLocation: 1, format: 'float32', offset: 8 },\n            ],\n          },\n        ],\n      },\n      fragment: {\n        code: scatterWgsl,\n        label: 'scatter.wgsl',\n        formats: targetFormat,\n        // Standard alpha blending (circle AA uses alpha, and series color may be translucent).\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let instanceBuffer: GPUBuffer | null = null;\n  let instanceCount = 0;\n  let cpuInstanceStagingBuffer = new ArrayBuffer(0);\n  let cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n\n  let lastCanvasWidth = 0;\n  let lastCanvasHeight = 0;\n  let lastViewportPx: readonly [number, number] = [1, 1];\n  let lastScissor: { readonly x: number; readonly y: number; readonly w: number; readonly h: number } | null = null;\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('ScatterRenderer is disposed.');\n  };\n\n  const ensureCpuInstanceCapacityFloats = (requiredFloats: number): void => {\n    if (requiredFloats <= cpuInstanceStagingF32.length) return;\n    const nextFloats = Math.max(8, nextPow2(requiredFloats));\n    cpuInstanceStagingBuffer = new ArrayBuffer(nextFloats * 4);\n    cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n  };\n\n  const writeVsUniforms = (\n    ax: number,\n    bx: number,\n    ay: number,\n    by: number,\n    viewportW: number,\n    viewportH: number\n  ): void => {\n    const w = Number.isFinite(viewportW) && viewportW > 0 ? viewportW : 1;\n    const h = Number.isFinite(viewportH) && viewportH > 0 ? viewportH : 1;\n\n    writeTransformMat4F32(vsUniformScratchF32, ax, bx, ay, by);\n    vsUniformScratchF32[16] = w;\n    vsUniformScratchF32[17] = h;\n    vsUniformScratchF32[18] = 0;\n    vsUniformScratchF32[19] = 0;\n    writeUniformBuffer(device, vsUniformBuffer, vsUniformScratchBuffer);\n\n    lastViewportPx = [w, h];\n  };\n\n  const prepare: ScatterRenderer['prepare'] = (seriesConfig, data, xScale, yScale, gridArea) => {\n    assertNotDisposed();\n\n    const bounds = computeRawBoundsFromCartesianData(data);\n    const { xMin, xMax, yMin, yMax } = bounds ?? { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };\n    const { a: ax, b: bx } = computeClipAffineFromScale(xScale, xMin, xMax);\n    const { a: ay, b: by } = computeClipAffineFromScale(yScale, yMin, yMax);\n\n    if (gridArea) {\n      lastCanvasWidth = gridArea.canvasWidth;\n      lastCanvasHeight = gridArea.canvasHeight;\n      writeVsUniforms(ax, bx, ay, by, gridArea.canvasWidth, gridArea.canvasHeight);\n      lastScissor = computePlotScissorDevicePx(gridArea);\n    } else {\n      // Backward-compatible: keep rendering with the last known viewport (or safe default).\n      writeVsUniforms(ax, bx, ay, by, lastViewportPx[0], lastViewportPx[1]);\n      lastScissor = null;\n    }\n\n    const [r, g, b, a] = parseSeriesColorToRgba01(seriesConfig.color);\n    fsUniformScratchF32[0] = r;\n    fsUniformScratchF32[1] = g;\n    fsUniformScratchF32[2] = b;\n    fsUniformScratchF32[3] = clamp01(a);\n    writeUniformBuffer(device, fsUniformBuffer, fsUniformScratchF32);\n\n    const dpr = gridArea?.devicePixelRatio ?? 1;\n    const hasValidDpr = dpr > 0 && Number.isFinite(dpr);\n\n    const seriesSymbolSize = seriesConfig.symbolSize;\n    // Scratch tuple for symbolSize function: reuse to avoid per-point allocations\n    const scratchTuple: [number, number, number | undefined] = [0, 0, undefined];\n    \n    const getSeriesSizeCssPx =\n      typeof seriesSymbolSize === 'function'\n        ? (x: number, y: number, size: number | undefined): number => {\n            scratchTuple[0] = x;\n            scratchTuple[1] = y;\n            scratchTuple[2] = size;\n            const v = seriesSymbolSize(scratchTuple);\n            return typeof v === 'number' && Number.isFinite(v) ? v : DEFAULT_SCATTER_RADIUS_CSS_PX;\n          }\n        : typeof seriesSymbolSize === 'number' && Number.isFinite(seriesSymbolSize)\n          ? (_x: number, _y: number, _size: number | undefined): number => seriesSymbolSize\n          : (_x: number, _y: number, _size: number | undefined): number => DEFAULT_SCATTER_RADIUS_CSS_PX;\n\n    const count = getPointCount(data);\n    ensureCpuInstanceCapacityFloats(count * INSTANCE_STRIDE_FLOATS);\n    const f32 = cpuInstanceStagingF32;\n    let outFloats = 0;\n\n    for (let i = 0; i < count; i++) {\n      const x = getX(data, i);\n      const y = getY(data, i);\n      if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n\n      // Per-point size from data overrides series.symbolSize\n      const pointSize = getSize(data, i);\n      const sizeCss = pointSize ?? getSeriesSizeCssPx(x, y, pointSize);\n      const radiusCss = Number.isFinite(sizeCss) ? Math.max(0, sizeCss) : DEFAULT_SCATTER_RADIUS_CSS_PX;\n      const radiusDevicePx = hasValidDpr ? radiusCss * dpr : radiusCss;\n      if (!(radiusDevicePx > 0)) continue;\n\n      f32[outFloats + 0] = x;\n      f32[outFloats + 1] = y;\n      f32[outFloats + 2] = radiusDevicePx;\n      f32[outFloats + 3] = 0; // pad\n      outFloats += INSTANCE_STRIDE_FLOATS;\n    }\n\n    instanceCount = outFloats / INSTANCE_STRIDE_FLOATS;\n    const requiredBytes = Math.max(4, instanceCount * INSTANCE_STRIDE_BYTES);\n\n    if (!instanceBuffer || instanceBuffer.size < requiredBytes) {\n      const grownBytes = Math.max(Math.max(4, nextPow2(requiredBytes)), instanceBuffer ? instanceBuffer.size : 0);\n      if (instanceBuffer) {\n        try {\n          instanceBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n      instanceBuffer = device.createBuffer({\n        label: 'scatterRenderer/instanceBuffer',\n        size: grownBytes,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    if (instanceBuffer && instanceCount > 0) {\n      device.queue.writeBuffer(instanceBuffer, 0, cpuInstanceStagingBuffer, 0, instanceCount * INSTANCE_STRIDE_BYTES);\n    }\n  };\n\n  const render: ScatterRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (!instanceBuffer || instanceCount === 0) return;\n\n    // Clip to plot area when available.\n    if (lastScissor && lastCanvasWidth > 0 && lastCanvasHeight > 0) {\n      passEncoder.setScissorRect(lastScissor.x, lastScissor.y, lastScissor.w, lastScissor.h);\n    }\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n    passEncoder.setVertexBuffer(0, instanceBuffer);\n    passEncoder.draw(6, instanceCount);\n\n    // Reset scissor to full canvas to avoid impacting later renderers.\n    if (lastScissor && lastCanvasWidth > 0 && lastCanvasHeight > 0) {\n      passEncoder.setScissorRect(0, 0, lastCanvasWidth, lastCanvasHeight);\n    }\n  };\n\n  const dispose: ScatterRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    if (instanceBuffer) {\n      try {\n        instanceBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    instanceBuffer = null;\n    instanceCount = 0;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    try {\n      fsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n\n    lastCanvasWidth = 0;\n    lastCanvasHeight = 0;\n    lastViewportPx = [1, 1];\n    lastScissor = null;\n  };\n\n  return { prepare, render, dispose };\n}\n\n","export default \"struct ComputeUniforms {\\n  transform: mat4x4<f32>,\\n  viewportPx: vec2f,\\n  _pad0: vec2f,\\n  plotOriginPx: vec2<u32>,\\n  plotSizePx: vec2<u32>,\\n  binSizePx: u32,\\n  binCountX: u32,\\n  binCountY: u32,\\n  visibleStart: u32,\\n  visibleEnd: u32,\\n  normalization: u32,\\n  _pad1: vec2<u32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> u: ComputeUniforms;\\n@group(0) @binding(1) var<storage, read> points: array<vec2f>;\\n@group(0) @binding(2) var<storage, read_write> bins: array<atomic<u32>>;\\n\\nstruct MaxBuffer {\\n  value: atomic<u32>,\\n};\\n@group(0) @binding(3) var<storage, read_write> maxBuf: MaxBuffer;\\n\\nfn clipToDevicePx(clip: vec2f) -> vec2f {\\n  // clip in [-1,1] -> device pixel in [0, viewport]\\n  return vec2f(\\n    (clip.x * 0.5 + 0.5) * u.viewportPx.x,\\n    (-clip.y * 0.5 + 0.5) * u.viewportPx.y\\n  );\\n}\\n\\n@compute @workgroup_size(256)\\nfn binPoints(@builtin(global_invocation_id) gid: vec3<u32>) {\\n  let idx = u.visibleStart + gid.x;\\n  if (idx >= u.visibleEnd) {\\n    return;\\n  }\\n\\n  let p = points[idx];\\n  let clip4 = u.transform * vec4f(p.x, p.y, 0.0, 1.0);\\n  let clip = clip4.xy / max(1e-9, clip4.w);\\n  let px = clipToDevicePx(clip);\\n\\n  // Scissor bounds in device px\\n  let left = f32(u.plotOriginPx.x);\\n  let top = f32(u.plotOriginPx.y);\\n  let right = left + f32(u.plotSizePx.x);\\n  let bottom = top + f32(u.plotSizePx.y);\\n\\n  if (px.x < left || px.x >= right || px.y < top || px.y >= bottom) {\\n    return;\\n  }\\n\\n  let localX = u32((px.x - left) / f32(u.binSizePx));\\n  let localY = u32((px.y - top) / f32(u.binSizePx));\\n  if (localX >= u.binCountX || localY >= u.binCountY) {\\n    return;\\n  }\\n\\n  let binIndex = localY * u.binCountX + localX;\\n  atomicAdd(&bins[binIndex], 1u);\\n}\\n\\n@compute @workgroup_size(256)\\nfn reduceMax(@builtin(global_invocation_id) gid: vec3<u32>) {\\n  let binTotal = u.binCountX * u.binCountY;\\n  let i = gid.x;\\n  if (i >= binTotal) {\\n    return;\\n  }\\n\\n  let v = atomicLoad(&bins[i]);\\n  atomicMax(&maxBuf.value, v);\\n}\\n\\n\"","export default \"struct RenderUniforms {\\n  plotOriginPx: vec2<u32>,\\n  plotSizePx: vec2<u32>,\\n  binSizePx: u32,\\n  binCountX: u32,\\n  binCountY: u32,\\n  normalization: u32,\\n  _pad: vec2<u32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> u: RenderUniforms;\\n@group(0) @binding(1) var<storage, read> bins: array<u32>;\\n@group(0) @binding(2) var<storage, read> maxBuf: array<u32>;\\n@group(0) @binding(3) var lutTex: texture_2d<f32>;\\n\\nstruct VsOut {\\n  @builtin(position) position: vec4f,\\n};\\n\\n@vertex\\nfn vsMain(@builtin(vertex_index) vid: u32) -> VsOut {\\n  // Fullscreen triangle (covers clip space).\\n  // (0,0)->(-1,-1), (2,0)->(3,-1), (0,2)->(-1,3)\\n  var pos = array<vec2f, 3>(\\n    vec2f(-1.0, -1.0),\\n    vec2f(3.0, -1.0),\\n    vec2f(-1.0, 3.0)\\n  );\\n  var out: VsOut;\\n  out.position = vec4f(pos[vid], 0.0, 1.0);\\n  return out;\\n}\\n\\nfn applyNormalization(count: f32, maxCount: f32, mode: u32) -> f32 {\\n  if (maxCount <= 0.0) {\\n    return 0.0;\\n  }\\n  let t = clamp(count / maxCount, 0.0, 1.0);\\n  if (mode == 1u) { // sqrt\\n    return sqrt(t);\\n  }\\n  if (mode == 2u) { // log\\n    // log1p(count) / log1p(max)\\n    return clamp(log(1.0 + count) / max(1e-9, log(1.0 + maxCount)), 0.0, 1.0);\\n  }\\n  return t; // linear\\n}\\n\\n@fragment\\nfn fsMain(@builtin(position) pos: vec4f) -> @location(0) vec4f {\\n  // pos.xy is framebuffer pixel coords (device px) with origin top-left.\\n  let x = pos.x;\\n  let y = pos.y;\\n\\n  let left = f32(u.plotOriginPx.x);\\n  let top = f32(u.plotOriginPx.y);\\n  // plot scissor also applied on CPU; keep a guard anyway.\\n  if (x < left || y < top) {\\n    return vec4f(0.0);\\n  }\\n\\n  let localX = u32((x - left) / f32(u.binSizePx));\\n  let localY = u32((y - top) / f32(u.binSizePx));\\n  if (localX >= u.binCountX || localY >= u.binCountY) {\\n    return vec4f(0.0);\\n  }\\n\\n  let idx = localY * u.binCountX + localX;\\n  let c = f32(bins[idx]);\\n  let maxC = f32(maxBuf[0]);\\n\\n  let t = applyNormalization(c, maxC, u.normalization);\\n  let lutX = i32(round(t * 255.0));\\n  let lut = textureLoad(lutTex, vec2<i32>(lutX, 0), 0);\\n  return vec4f(lut.rgb, 1.0);\\n}\\n\\n\"","import scatterDensityBinningWgsl from '../shaders/scatterDensityBinning.wgsl?raw';\nimport scatterDensityColormapWgsl from '../shaders/scatterDensityColormap.wgsl?raw';\nimport type { RawBounds, ResolvedScatterSeriesConfig } from '../config/OptionResolver';\nimport type { LinearScale } from '../utils/scales';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport type { GridArea } from './createGridRenderer';\nimport { createRenderPipeline, createShaderModule, createUniformBuffer, writeUniformBuffer, createComputePipeline } from './rendererUtils';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface ScatterDensityRenderer {\n  prepare(\n    seriesConfig: ResolvedScatterSeriesConfig,\n    pointBuffer: GPUBuffer,\n    pointCount: number,\n    visibleStartIndex: number,\n    visibleEndIndex: number,\n    xScale: LinearScale,\n    yScale: LinearScale,\n    gridArea: GridArea,\n    rawBounds?: RawBounds\n  ): void;\n  encodeCompute(encoder: GPUCommandEncoder): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface ScatterDensityRendererOptions {\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst clampInt = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v | 0));\n\nconst nextPow2 = (v: number): number => {\n  if (!Number.isFinite(v) || v <= 0) return 1;\n  const n = Math.ceil(v);\n  return 2 ** Math.ceil(Math.log2(n));\n};\n\nconst computeClipAffineFromScale = (\n  scale: LinearScale,\n  v0: number,\n  v1: number\n): { readonly a: number; readonly b: number } => {\n  const p0 = scale.scale(v0);\n  const p1 = scale.scale(v1);\n\n  if (!Number.isFinite(v0) || !Number.isFinite(v1) || v0 === v1 || !Number.isFinite(p0) || !Number.isFinite(p1)) {\n    return { a: 0, b: Number.isFinite(p0) ? p0 : 0 };\n  }\n\n  const a = (p1 - p0) / (v1 - v0);\n  const b = p0 - a * v0;\n  return { a: Number.isFinite(a) ? a : 0, b: Number.isFinite(b) ? b : 0 };\n};\n\nconst writeTransformMat4F32 = (out: Float32Array, ax: number, bx: number, ay: number, by: number): void => {\n  // Column-major mat4x4 for: clip = M * vec4(x, y, 0, 1)\n  out[0] = ax;\n  out[1] = 0;\n  out[2] = 0;\n  out[3] = 0;\n  out[4] = 0;\n  out[5] = ay;\n  out[6] = 0;\n  out[7] = 0;\n  out[8] = 0;\n  out[9] = 0;\n  out[10] = 1;\n  out[11] = 0;\n  out[12] = bx;\n  out[13] = by;\n  out[14] = 0;\n  out[15] = 1;\n};\n\nconst computePlotScissorDevicePx = (\n  gridArea: GridArea\n): { readonly x: number; readonly y: number; readonly w: number; readonly h: number } => {\n  const { canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeftDevice = gridArea.left * devicePixelRatio;\n  const plotRightDevice = canvasWidth - gridArea.right * devicePixelRatio;\n  const plotTopDevice = gridArea.top * devicePixelRatio;\n  const plotBottomDevice = canvasHeight - gridArea.bottom * devicePixelRatio;\n\n  const scissorX = clampInt(Math.floor(plotLeftDevice), 0, Math.max(0, canvasWidth));\n  const scissorY = clampInt(Math.floor(plotTopDevice), 0, Math.max(0, canvasHeight));\n  const scissorR = clampInt(Math.ceil(plotRightDevice), 0, Math.max(0, canvasWidth));\n  const scissorB = clampInt(Math.ceil(plotBottomDevice), 0, Math.max(0, canvasHeight));\n  const scissorW = Math.max(0, scissorR - scissorX);\n  const scissorH = Math.max(0, scissorB - scissorY);\n\n  return { x: scissorX, y: scissorY, w: scissorW, h: scissorH };\n};\n\ntype Rgba01 = readonly [r: number, g: number, b: number, a: number];\n\nconst lerp = (a: number, b: number, t: number): number => a + (b - a) * t;\nconst lerpRgba = (a: Rgba01, b: Rgba01, t: number): Rgba01 =>\n  [lerp(a[0], b[0], t), lerp(a[1], b[1], t), lerp(a[2], b[2], t), lerp(a[3], b[3], t)] as const;\n\nconst parseColorStop = (css: string): Rgba01 => parseCssColorToRgba01(css) ?? ([0, 0, 0, 1] as const);\n\nconst getNamedStops = (name: 'viridis' | 'plasma' | 'inferno'): readonly string[] => {\n  // Compact stop lists (interpolated to 256 entries). These are standard-ish anchors.\n  if (name === 'plasma') {\n    return ['#0d0887', '#6a00a8', '#b12a90', '#e16462', '#fca636', '#f0f921'] as const;\n  }\n  if (name === 'inferno') {\n    return ['#000004', '#420a68', '#932667', '#dd513a', '#fca50a', '#fcffa4'] as const;\n  }\n  // viridis\n  return ['#440154', '#3b528b', '#21918c', '#5ec962', '#fde725'] as const;\n};\n\nconst buildLutRGBA8 = (colormap: ResolvedScatterSeriesConfig['densityColormap']): Uint8Array<ArrayBuffer> => {\n  const stopsCss =\n    typeof colormap === 'string'\n      ? getNamedStops(colormap)\n      : Array.isArray(colormap) && colormap.length > 0\n        ? colormap\n        : (getNamedStops('viridis') as readonly string[]);\n\n  const stops = stopsCss.map(parseColorStop);\n  const n = Math.max(2, stops.length);\n\n  // Ensure the underlying buffer is a plain ArrayBuffer (not SharedArrayBuffer) for WebGPU typings.\n  const out: Uint8Array<ArrayBuffer> = new Uint8Array(new ArrayBuffer(256 * 4));\n  for (let i = 0; i < 256; i++) {\n    const t = i / 255;\n    const x = t * (n - 1);\n    const seg = Math.min(n - 2, Math.max(0, Math.floor(x)));\n    const localT = x - seg;\n    const c = lerpRgba(stops[seg]!, stops[seg + 1]!, localT);\n\n    out[i * 4 + 0] = clampInt(Math.round(clamp01(c[0]) * 255), 0, 255);\n    out[i * 4 + 1] = clampInt(Math.round(clamp01(c[1]) * 255), 0, 255);\n    out[i * 4 + 2] = clampInt(Math.round(clamp01(c[2]) * 255), 0, 255);\n    out[i * 4 + 3] = clampInt(Math.round(clamp01(c[3]) * 255), 0, 255);\n  }\n  return out;\n};\n\nconst colormapKey = (colormap: ResolvedScatterSeriesConfig['densityColormap']): string => {\n  if (typeof colormap === 'string') return colormap;\n  try {\n    return JSON.stringify(colormap);\n  } catch {\n    return 'custom';\n  }\n};\n\nconst normalizationToU32 = (n: ResolvedScatterSeriesConfig['densityNormalization']): number => {\n  // Must match shader:\n  // 0: linear, 1: sqrt, 2: log\n  if (n === 'sqrt') return 1;\n  if (n === 'log') return 2;\n  return 0;\n};\n\n// Pre-allocated zero buffer for clearing the max reduction buffer.\n// Avoids `new Uint32Array([0]).buffer` allocation per compute dispatch.\nconst ZERO_U32_BUFFER: ArrayBuffer = new Uint32Array([0]).buffer;\n\nexport function createScatterDensityRenderer(\n  device: GPUDevice,\n  options?: ScatterDensityRendererOptions\n): ScatterDensityRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const computeBindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },\n      { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },\n      { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n      { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },\n    ],\n  });\n\n  const renderBindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n      // `scatterDensityColormap.wgsl` declares these as `var<storage, read>`, so they must be read-only-storage.\n      { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } },\n      { binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } },\n      { binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'unfilterable-float' } },\n    ],\n  });\n\n  // Compute uniforms:\n  // transform(64) + viewportPx(8)+pad(8)=80\n  // plotOriginPx u32x2(8) + plotSizePx u32x2(8) = 16 => 96\n  // binSize/binCountX/binCountY/start/end/norm u32*6 (24) + pad u32x2 (8) => 128\n  const computeUniformBuffer = createUniformBuffer(device, 128, { label: 'scatterDensity/computeUniforms' });\n  const computeUniformScratch = new ArrayBuffer(128);\n  const computeUniformF32 = new Float32Array(computeUniformScratch, 0, 20); // first 80 bytes (20 f32)\n  const computeUniformU32 = new Uint32Array(computeUniformScratch);\n\n  // Render uniforms: plotOriginPx(8)+plotSizePx(8)=16; u32*4 (16) + padding to 48.\n  const renderUniformBuffer = createUniformBuffer(device, 48, { label: 'scatterDensity/renderUniforms' });\n  const renderUniformScratch = new ArrayBuffer(48);\n  const renderUniformU32 = new Uint32Array(renderUniformScratch);\n\n  const binningModule = createShaderModule(\n    device,\n    scatterDensityBinningWgsl,\n    'scatterDensityBinning.wgsl',\n    pipelineCache\n  );\n  const computeLayout = device.createPipelineLayout({ bindGroupLayouts: [computeBindGroupLayout] });\n  const binPointsPipeline = createComputePipeline(device, {\n    label: 'scatterDensity/binPointsPipeline',\n    layout: computeLayout,\n    compute: { module: binningModule, entryPoint: 'binPoints' },\n  }, pipelineCache);\n  const reduceMaxPipeline = createComputePipeline(device, {\n    label: 'scatterDensity/reduceMaxPipeline',\n    layout: computeLayout,\n    compute: { module: binningModule, entryPoint: 'reduceMax' },\n  }, pipelineCache);\n\n  const renderPipeline = createRenderPipeline(\n    device,\n    {\n      label: 'scatterDensity/renderPipeline',\n      bindGroupLayouts: [renderBindGroupLayout],\n      vertex: { code: scatterDensityColormapWgsl, label: 'scatterDensityColormap.wgsl' },\n      fragment: {\n        code: scatterDensityColormapWgsl,\n        label: 'scatterDensityColormap.wgsl',\n        formats: targetFormat,\n        blend: undefined,\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let binsBuffer: GPUBuffer | null = null;\n  let maxBuffer: GPUBuffer | null = null;\n  let binsCapacityU32 = 0;\n\n  let lutTexture: GPUTexture | null = null;\n  let lutView: GPUTextureView | null = null;\n  let lastColormapKey = '';\n\n  let computeBindGroup: GPUBindGroup | null = null;\n  let renderBindGroup: GPUBindGroup | null = null;\n\n  // Cached state to decide when to recompute.\n  let lastPointBuffer: GPUBuffer | null = null;\n  let lastPointCount = -1;\n  let lastVisibleStart = 0;\n  let lastVisibleEnd = 0;\n  let lastBinSizePx = 0;\n  let lastBinCountX = 0;\n  let lastBinCountY = 0;\n  let lastPlotScissor: { readonly x: number; readonly y: number; readonly w: number; readonly h: number } | null = null;\n  let lastCanvasWidth = 0;\n  let lastCanvasHeight = 0;\n  let lastNormalizationU32 = 2; // default 'log'\n\n  let computeDirty = true;\n  let hasPrepared = false;\n\n  // Zero staging for fast clear (reallocated to match bins size).\n  let zeroBinsStaging = new Uint32Array(0);\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('ScatterDensityRenderer is disposed.');\n  };\n\n  const ensureLut = (seriesConfig: ResolvedScatterSeriesConfig): void => {\n    const key = colormapKey(seriesConfig.densityColormap);\n    if (!lutTexture) {\n      lutTexture = device.createTexture({\n        label: 'scatterDensity/lutTexture',\n        size: { width: 256, height: 1, depthOrArrayLayers: 1 },\n        format: 'rgba8unorm',\n        usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n      });\n      lutView = lutTexture.createView();\n      lastColormapKey = '';\n    }\n    if (key === lastColormapKey) return;\n\n    const data = buildLutRGBA8(seriesConfig.densityColormap);\n    device.queue.writeTexture(\n      { texture: lutTexture! },\n      data,\n      { bytesPerRow: 256 * 4, rowsPerImage: 1 },\n      { width: 256, height: 1, depthOrArrayLayers: 1 }\n    );\n    lastColormapKey = key;\n  };\n\n  const ensureBins = (binCountX: number, binCountY: number): void => {\n    const n = Math.max(1, binCountX | 0) * Math.max(1, binCountY | 0);\n    if (binsBuffer && maxBuffer && n <= binsCapacityU32) return;\n\n    const requiredU32 = Math.max(1, n);\n    const grownU32 = Math.max(256, nextPow2(requiredU32));\n    binsCapacityU32 = grownU32;\n\n    if (binsBuffer) {\n      try {\n        binsBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n      binsBuffer = null;\n    }\n    if (maxBuffer) {\n      try {\n        maxBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n      maxBuffer = null;\n    }\n\n    binsBuffer = device.createBuffer({\n      label: 'scatterDensity/binsBuffer',\n      size: binsCapacityU32 * 4,\n      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n    });\n    maxBuffer = device.createBuffer({\n      label: 'scatterDensity/maxBuffer',\n      size: 4,\n      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n    });\n\n    // Refresh clear staging.\n    zeroBinsStaging = new Uint32Array(binsCapacityU32);\n\n    // Bind groups depend on buffers, so force re-create.\n    computeBindGroup = null;\n    renderBindGroup = null;\n    computeDirty = true;\n  };\n\n  const ensureBindGroups = (): void => {\n    if (!binsBuffer || !maxBuffer || !lutView || !lastPointBuffer) return;\n    if (!computeBindGroup) {\n      computeBindGroup = device.createBindGroup({\n        label: 'scatterDensity/computeBindGroup',\n        layout: computeBindGroupLayout,\n        entries: [\n          { binding: 0, resource: { buffer: computeUniformBuffer } },\n          { binding: 1, resource: { buffer: lastPointBuffer } },\n          { binding: 2, resource: { buffer: binsBuffer } },\n          { binding: 3, resource: { buffer: maxBuffer } },\n        ],\n      });\n    }\n    if (!renderBindGroup) {\n      renderBindGroup = device.createBindGroup({\n        label: 'scatterDensity/renderBindGroup',\n        layout: renderBindGroupLayout,\n        entries: [\n          { binding: 0, resource: { buffer: renderUniformBuffer } },\n          { binding: 1, resource: { buffer: binsBuffer } },\n          { binding: 2, resource: { buffer: maxBuffer } },\n          { binding: 3, resource: lutView },\n        ],\n      });\n    }\n  };\n\n  const prepare: ScatterDensityRenderer['prepare'] = (\n    seriesConfig,\n    pointBuffer,\n    pointCount,\n    visibleStartIndex,\n    visibleEndIndex,\n    xScale,\n    yScale,\n    gridArea,\n    rawBounds\n  ) => {\n    assertNotDisposed();\n    hasPrepared = true;\n\n    const plotScissor = computePlotScissorDevicePx(gridArea);\n    const dpr = gridArea.devicePixelRatio;\n    const binSizeCss = Number.isFinite(seriesConfig.binSize) ? Math.max(1e-6, seriesConfig.binSize) : 2;\n    const binSizePx = Math.max(1, Math.round(binSizeCss * (Number.isFinite(dpr) && dpr > 0 ? dpr : 1)));\n\n    const binCountX = Math.max(1, Math.ceil(plotScissor.w / binSizePx));\n    const binCountY = Math.max(1, Math.ceil(plotScissor.h / binSizePx));\n\n    ensureBins(binCountX, binCountY);\n    ensureLut(seriesConfig);\n\n    const normU32 = normalizationToU32(seriesConfig.densityNormalization);\n\n    // Dirty detection.\n    if (lastPointBuffer !== pointBuffer) {\n      lastPointBuffer = pointBuffer;\n      computeBindGroup = null;\n      renderBindGroup = null;\n      computeDirty = true;\n    }\n    if (lastPointCount !== pointCount) {\n      lastPointCount = pointCount;\n      computeDirty = true;\n    }\n    if (lastVisibleStart !== visibleStartIndex || lastVisibleEnd !== visibleEndIndex) {\n      lastVisibleStart = visibleStartIndex;\n      lastVisibleEnd = visibleEndIndex;\n      computeDirty = true;\n    }\n    if (lastBinSizePx !== binSizePx || lastBinCountX !== binCountX || lastBinCountY !== binCountY) {\n      lastBinSizePx = binSizePx;\n      lastBinCountX = binCountX;\n      lastBinCountY = binCountY;\n      computeDirty = true;\n    }\n    if (\n      !lastPlotScissor ||\n      lastPlotScissor.x !== plotScissor.x ||\n      lastPlotScissor.y !== plotScissor.y ||\n      lastPlotScissor.w !== plotScissor.w ||\n      lastPlotScissor.h !== plotScissor.h\n    ) {\n      lastPlotScissor = plotScissor;\n      computeDirty = true;\n    }\n    if (lastCanvasWidth !== gridArea.canvasWidth || lastCanvasHeight !== gridArea.canvasHeight) {\n      lastCanvasWidth = gridArea.canvasWidth;\n      lastCanvasHeight = gridArea.canvasHeight;\n      computeDirty = true;\n    }\n    if (lastNormalizationU32 !== normU32) {\n      lastNormalizationU32 = normU32;\n      computeDirty = true;\n    }\n\n    // Write uniforms.\n    const rb = rawBounds;\n    const xMin = rb?.xMin ?? 0;\n    const xMax = rb?.xMax ?? 1;\n    const yMin = rb?.yMin ?? 0;\n    const yMax = rb?.yMax ?? 1;\n\n    const { a: ax, b: bx } = computeClipAffineFromScale(xScale, xMin, xMax);\n    const { a: ay, b: by } = computeClipAffineFromScale(yScale, yMin, yMax);\n\n    writeTransformMat4F32(computeUniformF32, ax, bx, ay, by);\n    computeUniformF32[16] = gridArea.canvasWidth > 0 ? gridArea.canvasWidth : 1;\n    computeUniformF32[17] = gridArea.canvasHeight > 0 ? gridArea.canvasHeight : 1;\n    computeUniformF32[18] = 0;\n    computeUniformF32[19] = 0;\n\n    computeUniformU32[20] = plotScissor.x >>> 0;\n    computeUniformU32[21] = plotScissor.y >>> 0;\n    computeUniformU32[22] = plotScissor.w >>> 0;\n    computeUniformU32[23] = plotScissor.h >>> 0;\n    computeUniformU32[24] = binSizePx >>> 0;\n    computeUniformU32[25] = binCountX >>> 0;\n    computeUniformU32[26] = binCountY >>> 0;\n    computeUniformU32[27] = (Math.max(0, visibleStartIndex) | 0) >>> 0;\n    computeUniformU32[28] = (Math.max(0, visibleEndIndex) | 0) >>> 0;\n    computeUniformU32[29] = normU32 >>> 0;\n\n    writeUniformBuffer(device, computeUniformBuffer, computeUniformScratch);\n\n    renderUniformU32[0] = plotScissor.x >>> 0;\n    renderUniformU32[1] = plotScissor.y >>> 0;\n    renderUniformU32[2] = plotScissor.w >>> 0;\n    renderUniformU32[3] = plotScissor.h >>> 0;\n    renderUniformU32[4] = binSizePx >>> 0;\n    renderUniformU32[5] = binCountX >>> 0;\n    renderUniformU32[6] = binCountY >>> 0;\n    renderUniformU32[7] = normU32 >>> 0;\n    writeUniformBuffer(device, renderUniformBuffer, renderUniformScratch);\n\n    ensureBindGroups();\n  };\n\n  const encodeCompute: ScatterDensityRenderer['encodeCompute'] = (encoder) => {\n    assertNotDisposed();\n    if (!hasPrepared) return;\n    if (!computeDirty) return;\n    if (!binsBuffer || !maxBuffer || !computeBindGroup || lastPointCount <= 0) {\n      computeDirty = false;\n      return;\n    }\n    if (!lastPlotScissor || lastPlotScissor.w <= 0 || lastPlotScissor.h <= 0) {\n      computeDirty = false;\n      return;\n    }\n\n    // Clear bins + max.\n    device.queue.writeBuffer(binsBuffer, 0, zeroBinsStaging.buffer, 0, binsCapacityU32 * 4);\n    device.queue.writeBuffer(maxBuffer, 0, ZERO_U32_BUFFER);\n\n    const binTotal = (lastBinCountX * lastBinCountY) | 0;\n    const visibleCount = Math.max(0, (lastVisibleEnd - lastVisibleStart) | 0);\n\n    const pass = encoder.beginComputePass({ label: 'scatterDensity/computePass' });\n    pass.setBindGroup(0, computeBindGroup);\n\n    pass.setPipeline(binPointsPipeline);\n    const wg = 256;\n    const groupsPoints = Math.ceil(visibleCount / wg);\n    if (groupsPoints > 0) pass.dispatchWorkgroups(groupsPoints);\n\n    pass.setPipeline(reduceMaxPipeline);\n    const groupsBins = Math.ceil(binTotal / wg);\n    if (groupsBins > 0) pass.dispatchWorkgroups(groupsBins);\n\n    pass.end();\n    computeDirty = false;\n  };\n\n  const render: ScatterDensityRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (!hasPrepared) return;\n    if (!renderBindGroup || !lastPlotScissor || !lutView) return;\n    if (lastPlotScissor.w <= 0 || lastPlotScissor.h <= 0) return;\n\n    passEncoder.setScissorRect(lastPlotScissor.x, lastPlotScissor.y, lastPlotScissor.w, lastPlotScissor.h);\n    passEncoder.setPipeline(renderPipeline);\n    passEncoder.setBindGroup(0, renderBindGroup);\n    passEncoder.draw(3);\n\n    if (lastCanvasWidth > 0 && lastCanvasHeight > 0) {\n      passEncoder.setScissorRect(0, 0, lastCanvasWidth, lastCanvasHeight);\n    }\n  };\n\n  const dispose: ScatterDensityRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      computeUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    try {\n      renderUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n\n    if (binsBuffer) {\n      try {\n        binsBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    if (maxBuffer) {\n      try {\n        maxBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    binsBuffer = null;\n    maxBuffer = null;\n    binsCapacityU32 = 0;\n\n    if (lutTexture) {\n      try {\n        lutTexture.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    lutTexture = null;\n    lutView = null;\n\n    computeBindGroup = null;\n    renderBindGroup = null;\n    lastPointBuffer = null;\n  };\n\n  return { prepare, encodeCompute, render, dispose };\n}\n\n","export default \"// pie.wgsl\\n// Instanced anti-aliased pie-slice shader (instanced quad + SDF mask).\\n//\\n// - Per-instance vertex input:\\n//   - center        = vec2<f32> slice center (transformed by VSUniforms.transform)\\n//   - startAngleRad = f32 start angle in radians\\n//   - endAngleRad   = f32 end angle in radians\\n//   - radiiPx       = vec2<f32>(innerRadiusPx, outerRadiusPx) in *device pixels*\\n//   - color         = vec4<f32> RGBA color in [0..1]\\n//\\n// - Draw call: draw(6, instanceCount) using triangle-list expansion in VS\\n//\\n// - Uniforms:\\n//   - @group(0) @binding(0): VSUniforms { transform, viewportPx }\\n//\\n// Notes:\\n// - The quad is expanded in clip space using `radiusPx` and `viewportPx`.\\n// - Fragment uses an SDF mask for the circle boundary + an angular wedge mask.\\n// - Fully outside fragments are discarded to avoid unnecessary blending work.\\n//\\n// Conventions: matches other shaders in this repo (vsMain/fsMain, group 0 bindings,\\n// and explicit uniform padding/alignment where needed).\\n\\nconst PI: f32 = 3.141592653589793;\\nconst TAU: f32 = 6.283185307179586; // 2*pi\\n\\nstruct VSUniforms {\\n  transform: mat4x4<f32>,\\n  viewportPx: vec2<f32>,\\n  // Pad to 16-byte alignment (mat4x4 is 64B; vec2 adds 8B; pad to 80B).\\n  _pad0: vec2<f32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;\\n\\nstruct VSIn {\\n  @location(0) center: vec2<f32>,\\n  @location(1) startAngleRad: f32,\\n  @location(2) endAngleRad: f32,\\n  @location(3) radiiPx: vec2<f32>, // (innerPx, outerPx)\\n  @location(4) color: vec4<f32>,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition: vec4<f32>,\\n  @location(0) localPx: vec2<f32>,\\n  @location(1) startAngleRad: f32,\\n  @location(2) endAngleRad: f32,\\n  @location(3) radiiPx: vec2<f32>,\\n  @location(4) color: vec4<f32>,\\n};\\n\\n@vertex\\nfn vsMain(in: VSIn, @builtin(vertex_index) vertexIndex: u32) -> VSOut {\\n  // Fixed local corners for 2 triangles (triangle-list).\\n  // `localNdc` is a quad in [-1, 1]^2; we convert it to pixel offsets via radiusPx.\\n  let localNdc = array<vec2<f32>, 6>(\\n    vec2<f32>(-1.0, -1.0),\\n    vec2<f32>( 1.0, -1.0),\\n    vec2<f32>(-1.0,  1.0),\\n    vec2<f32>(-1.0,  1.0),\\n    vec2<f32>( 1.0, -1.0),\\n    vec2<f32>( 1.0,  1.0)\\n  );\\n\\n  let corner = localNdc[vertexIndex];\\n  let outerPx = in.radiiPx.y;\\n  let localPx = corner * outerPx;\\n\\n  // Convert pixel offset to clip-space offset.\\n  // Clip space spans [-1, 1] across the viewport, so px -> clip is (2 / viewportPx).\\n  let localClip = localPx * (2.0 / vsUniforms.viewportPx);\\n\\n  let centerClip = (vsUniforms.transform * vec4<f32>(in.center, 0.0, 1.0)).xy;\\n\\n  var out: VSOut;\\n  out.clipPosition = vec4<f32>(centerClip + localClip, 0.0, 1.0);\\n  out.localPx = localPx;\\n  out.startAngleRad = in.startAngleRad;\\n  out.endAngleRad = in.endAngleRad;\\n  out.radiiPx = in.radiiPx;\\n  out.color = in.color;\\n  return out;\\n}\\n\\nfn wrapToTau(theta: f32) -> f32 {\\n  // Maps theta to [0, TAU). (Input often comes from atan2 in [-PI, PI].)\\n  return select(theta, theta + TAU, theta < 0.0);\\n}\\n\\n@fragment\\nfn fsMain(in: VSOut) -> @location(0) vec4<f32> {\\n  let p = in.localPx;\\n  let r = length(p);\\n\\n  let innerPx = in.radiiPx.x;\\n  let outerPx = in.radiiPx.y;\\n\\n  // --- Radial mask: ring between inner and outer radii (inner==0 => pie) ---\\n  // Positive inside the ring, negative outside.\\n  let radialDist = min(r - innerPx, outerPx - r);\\n  let radialW = fwidth(radialDist);\\n  let radialA = smoothstep(-radialW, radialW, radialDist);\\n\\n  if (radialA <= 0.0) {\\n    discard;\\n  }\\n\\n  // Compute fragment angle in [0, TAU).\\n  let angle = wrapToTau(atan2(p.y, p.x));\\n\\n  // --- Angular mask: wedge between start/end angles with wrap ---\\n  let start = in.startAngleRad;\\n  let end = in.endAngleRad;\\n\\n  // Compute span in [0, 2π) with wrap.\\n  var span = end - start;\\n  span = span + select(0.0, TAU, span < 0.0);\\n\\n  // Compute rel in [0, 2π) with wrap.\\n  var rel = angle - start;\\n  rel = rel + select(0.0, TAU, rel < 0.0);\\n\\n  let inside = rel <= span;\\n\\n  // Signed angular distance (in radians) to nearest boundary.\\n  // - Inside: +min(rel, span-rel)\\n  // - Outside: -min(rel-span, 2π-rel)\\n  let dIn = min(rel, max(span - rel, 0.0));\\n  let dOutA = max(rel - span, 0.0);\\n  let dOutB = max(TAU - rel, 0.0);\\n  let dOut = min(dOutA, dOutB);\\n\\n  let signedAngleDist = select(-dOut, dIn, inside);\\n\\n  // Convert to approximate pixel distance to the boundary ray.\\n  // (For small angles, perpendicular distance to a ray ≈ r * angle.)\\n  let angleDistPx = signedAngleDist * max(r, 1.0);\\n\\n  let angW = fwidth(angleDistPx);\\n  let angularA = smoothstep(-angW, angW, angleDistPx);\\n\\n  let aOut = radialA * angularA;\\n  if (aOut <= 0.0) {\\n    discard;\\n  }\\n\\n  return vec4<f32>(in.color.rgb, in.color.a * aOut);\\n}\\n\\n\"","import pieWgsl from '../shaders/pie.wgsl?raw';\nimport type { ResolvedPieSeriesConfig } from '../config/OptionResolver';\nimport type { PieCenter, PieRadius } from '../config/types';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport type { GridArea } from './createGridRenderer';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface PieRenderer {\n  prepare(seriesConfig: ResolvedPieSeriesConfig, gridArea: GridArea): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface PieRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\ntype Rgba = readonly [r: number, g: number, b: number, a: number];\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\n\n// Instance layout (must match `pie.wgsl` locations):\n// @location(0) center: vec2<f32>\n// @location(1) startAngleRad: f32\n// @location(2) endAngleRad: f32\n// @location(3) radiiPx: vec2<f32> (innerPx, outerPx) in device pixels\n// @location(4) color: vec4<f32>\nconst INSTANCE_STRIDE_BYTES = 40;\nconst INSTANCE_STRIDE_FLOATS = INSTANCE_STRIDE_BYTES / 4;\n\nconst TAU = Math.PI * 2;\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst clampInt = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v | 0));\n\nconst nextPow2 = (v: number): number => {\n  if (!Number.isFinite(v) || v <= 0) return 1;\n  const n = Math.ceil(v);\n  return 2 ** Math.ceil(Math.log2(n));\n};\n\nconst wrapToTau = (thetaRad: number): number => {\n  if (!Number.isFinite(thetaRad)) return 0;\n  const t = thetaRad % TAU;\n  return t < 0 ? t + TAU : t;\n};\n\nconst parseColor = (cssColor: string, fallbackCssColor: string): Rgba => {\n  const parsed = parseCssColorToRgba01(cssColor);\n  if (parsed) return [parsed[0], parsed[1], parsed[2], clamp01(parsed[3])] as const;\n\n  const fb = parseCssColorToRgba01(fallbackCssColor);\n  if (fb) return [fb[0], fb[1], fb[2], clamp01(fb[3])] as const;\n\n  return [0, 0, 0, 1] as const;\n};\n\nconst parseNumberOrPercent = (value: number | string, basis: number): number | null => {\n  if (typeof value === 'number') return Number.isFinite(value) ? value : null;\n  if (typeof value !== 'string') return null;\n\n  const s = value.trim();\n  if (s.length === 0) return null;\n\n  if (s.endsWith('%')) {\n    const pct = Number.parseFloat(s.slice(0, -1));\n    if (!Number.isFinite(pct)) return null;\n    return (pct / 100) * basis;\n  }\n\n  // Be permissive: allow numeric strings like \"120\" even though the public type primarily documents percent strings.\n  const n = Number.parseFloat(s);\n  return Number.isFinite(n) ? n : null;\n};\n\nconst resolveCenterPlotCss = (\n  center: PieCenter | undefined,\n  plotWidthCss: number,\n  plotHeightCss: number\n): { readonly x: number; readonly y: number } => {\n  const xRaw = center?.[0] ?? '50%';\n  const yRaw = center?.[1] ?? '50%';\n\n  const x = parseNumberOrPercent(xRaw, plotWidthCss);\n  const y = parseNumberOrPercent(yRaw, plotHeightCss);\n\n  return {\n    x: Number.isFinite(x) ? x! : plotWidthCss * 0.5,\n    y: Number.isFinite(y) ? y! : plotHeightCss * 0.5,\n  };\n};\n\nconst isRadiusTuple = (\n  radius: PieRadius\n): radius is readonly [inner: number | string, outer: number | string] => Array.isArray(radius);\n\nconst resolveRadiiCss = (\n  radius: PieRadius | undefined,\n  maxRadiusCss: number\n): { readonly inner: number; readonly outer: number } => {\n  // Default similar to common chart libs.\n  if (radius == null) return { inner: 0, outer: maxRadiusCss * 0.7 };\n\n  if (isRadiusTuple(radius)) {\n    const inner = parseNumberOrPercent(radius[0], maxRadiusCss);\n    const outer = parseNumberOrPercent(radius[1], maxRadiusCss);\n    const innerCss = Math.max(0, Number.isFinite(inner) ? inner! : 0);\n    const outerCss = Math.max(innerCss, Number.isFinite(outer) ? outer! : maxRadiusCss * 0.7);\n    return { inner: innerCss, outer: Math.min(maxRadiusCss, outerCss) };\n  }\n\n  const outer = parseNumberOrPercent(radius, maxRadiusCss);\n  const outerCss = Math.max(0, Number.isFinite(outer) ? outer! : maxRadiusCss * 0.7);\n  return { inner: 0, outer: Math.min(maxRadiusCss, outerCss) };\n};\n\nconst computePlotScissorDevicePx = (\n  gridArea: GridArea\n): { readonly x: number; readonly y: number; readonly w: number; readonly h: number } => {\n  const { canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeftDevice = gridArea.left * devicePixelRatio;\n  const plotRightDevice = canvasWidth - gridArea.right * devicePixelRatio;\n  const plotTopDevice = gridArea.top * devicePixelRatio;\n  const plotBottomDevice = canvasHeight - gridArea.bottom * devicePixelRatio;\n\n  const scissorX = clampInt(Math.floor(plotLeftDevice), 0, Math.max(0, canvasWidth));\n  const scissorY = clampInt(Math.floor(plotTopDevice), 0, Math.max(0, canvasHeight));\n  const scissorR = clampInt(Math.ceil(plotRightDevice), 0, Math.max(0, canvasWidth));\n  const scissorB = clampInt(Math.ceil(plotBottomDevice), 0, Math.max(0, canvasHeight));\n  const scissorW = Math.max(0, scissorR - scissorX);\n  const scissorH = Math.max(0, scissorB - scissorY);\n\n  return { x: scissorX, y: scissorY, w: scissorW, h: scissorH };\n};\n\nconst IDENTITY_MAT4_F32 = new Float32Array([\n  1, 0, 0, 0, // col0\n  0, 1, 0, 0, // col1\n  0, 0, 1, 0, // col2\n  0, 0, 0, 1, // col3\n]);\n\nexport function createPieRenderer(device: GPUDevice, options?: PieRendererOptions): PieRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } }],\n  });\n\n  // VSUniforms in `pie.wgsl`: mat4x4 (64) + viewportPx vec2 (8) + pad vec2 (8) = 80 bytes.\n  const vsUniformBuffer = createUniformBuffer(device, 80, { label: 'pieRenderer/vsUniforms' });\n\n  // Reused CPU-side staging for uniform writes (avoid per-frame allocations).\n  const vsUniformScratchBuffer = new ArrayBuffer(80);\n  const vsUniformScratchF32 = new Float32Array(vsUniformScratchBuffer);\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [{ binding: 0, resource: { buffer: vsUniformBuffer } }],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'pieRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: pieWgsl,\n        label: 'pie.wgsl',\n        buffers: [\n          {\n            arrayStride: INSTANCE_STRIDE_BYTES,\n            stepMode: 'instance',\n            attributes: [\n              { shaderLocation: 0, format: 'float32x2', offset: 0 }, // center\n              { shaderLocation: 1, format: 'float32', offset: 8 }, // startAngleRad\n              { shaderLocation: 2, format: 'float32', offset: 12 }, // endAngleRad\n              { shaderLocation: 3, format: 'float32x2', offset: 16 }, // radiiPx\n              { shaderLocation: 4, format: 'float32x4', offset: 24 }, // color\n            ],\n          },\n        ],\n      },\n      fragment: {\n        code: pieWgsl,\n        label: 'pie.wgsl',\n        formats: targetFormat,\n        // Standard alpha blending for AA edges and translucent slice colors.\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let instanceBuffer: GPUBuffer | null = null;\n  let instanceCount = 0;\n  let cpuInstanceStagingBuffer = new ArrayBuffer(0);\n  let cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n\n  let lastCanvasWidth = 0;\n  let lastCanvasHeight = 0;\n  let lastScissor: { readonly x: number; readonly y: number; readonly w: number; readonly h: number } | null = null;\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('PieRenderer is disposed.');\n  };\n\n  const ensureCpuInstanceCapacityFloats = (requiredFloats: number): void => {\n    if (requiredFloats <= cpuInstanceStagingF32.length) return;\n    const nextFloats = Math.max(8, nextPow2(requiredFloats));\n    cpuInstanceStagingBuffer = new ArrayBuffer(nextFloats * 4);\n    cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n  };\n\n  const writeVsUniforms = (viewportWDevicePx: number, viewportHDevicePx: number): void => {\n    const w = Number.isFinite(viewportWDevicePx) && viewportWDevicePx > 0 ? viewportWDevicePx : 1;\n    const h = Number.isFinite(viewportHDevicePx) && viewportHDevicePx > 0 ? viewportHDevicePx : 1;\n\n    vsUniformScratchF32.set(IDENTITY_MAT4_F32, 0);\n    vsUniformScratchF32[16] = w;\n    vsUniformScratchF32[17] = h;\n    vsUniformScratchF32[18] = 0;\n    vsUniformScratchF32[19] = 0;\n    writeUniformBuffer(device, vsUniformBuffer, vsUniformScratchBuffer);\n  };\n\n  const prepare: PieRenderer['prepare'] = (seriesConfig, gridArea) => {\n    assertNotDisposed();\n\n    const dprRaw = gridArea.devicePixelRatio;\n    const dpr = dprRaw > 0 && Number.isFinite(dprRaw) ? dprRaw : 1;\n\n    lastCanvasWidth = gridArea.canvasWidth;\n    lastCanvasHeight = gridArea.canvasHeight;\n    writeVsUniforms(gridArea.canvasWidth, gridArea.canvasHeight);\n    lastScissor = computePlotScissorDevicePx(gridArea);\n\n    const canvasCssWidth = gridArea.canvasWidth / dpr;\n    const canvasCssHeight = gridArea.canvasHeight / dpr;\n    if (!(canvasCssWidth > 0) || !(canvasCssHeight > 0)) {\n      instanceCount = 0;\n      return;\n    }\n\n    const plotWidthCss = canvasCssWidth - gridArea.left - gridArea.right;\n    const plotHeightCss = canvasCssHeight - gridArea.top - gridArea.bottom;\n    if (!(plotWidthCss > 0) || !(plotHeightCss > 0)) {\n      instanceCount = 0;\n      return;\n    }\n\n    const maxRadiusCss = 0.5 * Math.min(plotWidthCss, plotHeightCss);\n    if (!(maxRadiusCss > 0)) {\n      instanceCount = 0;\n      return;\n    }\n\n    // Center specified in plot-local CSS px (or %), then shifted by GridArea CSS margins.\n    const centerPlotCss = resolveCenterPlotCss(seriesConfig.center, plotWidthCss, plotHeightCss);\n    const centerCanvasCssX = gridArea.left + centerPlotCss.x;\n    const centerCanvasCssY = gridArea.top + centerPlotCss.y;\n\n    // Instance center is in clip space; VS transform is identity.\n    const centerClipX = (centerCanvasCssX / canvasCssWidth) * 2 - 1;\n    const centerClipY = 1 - (centerCanvasCssY / canvasCssHeight) * 2;\n    if (!Number.isFinite(centerClipX) || !Number.isFinite(centerClipY)) {\n      instanceCount = 0;\n      return;\n    }\n\n    // Radii specified in CSS px (or % of max radius), converted to device px for the shader.\n    const radiiCss = resolveRadiiCss(seriesConfig.radius, maxRadiusCss);\n    const innerCss = Math.max(0, Math.min(radiiCss.inner, radiiCss.outer));\n    const outerCss = Math.max(innerCss, radiiCss.outer);\n    const innerPx = innerCss * dpr;\n    const outerPx = outerCss * dpr;\n    if (!(outerPx > 0)) {\n      instanceCount = 0;\n      return;\n    }\n\n    // Total positive value for angle allocation (exclude hidden slices).\n    let total = 0;\n    let validCount = 0;\n    for (let i = 0; i < seriesConfig.data.length; i++) {\n      const item = seriesConfig.data[i];\n      const v = item?.value;\n      if (typeof v === 'number' && Number.isFinite(v) && v > 0 && item.visible !== false) {\n        total += v;\n        validCount++;\n      }\n    }\n    if (!(total > 0) || validCount === 0) {\n      instanceCount = 0;\n      return;\n    }\n\n    ensureCpuInstanceCapacityFloats(validCount * INSTANCE_STRIDE_FLOATS);\n    const f32 = cpuInstanceStagingF32;\n\n    // IMPORTANT: shader assumes start/end are already wrapped to [0, 2π) (it only adds TAU once).\n    const startDeg =\n      typeof seriesConfig.startAngle === 'number' && Number.isFinite(seriesConfig.startAngle) ? seriesConfig.startAngle : 90;\n    let current = wrapToTau((startDeg * Math.PI) / 180);\n\n    // Make the last slice close the circle (reduces float drift).\n    let accumulated = 0;\n    let outFloats = 0;\n    let emitted = 0;\n\n    for (let i = 0; i < seriesConfig.data.length; i++) {\n      const item = seriesConfig.data[i];\n      const v = item?.value;\n      if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) continue;\n      // Skip hidden slices\n      if (item.visible === false) continue;\n\n      emitted++;\n      const isLast = emitted === validCount;\n\n      const frac = v / total;\n      let span = frac * TAU;\n      if (isLast) {\n        span = Math.max(0, TAU - accumulated);\n      } else {\n        // Keep accumulated stable and avoid pathological spans from weird inputs.\n        span = Math.max(0, Math.min(TAU, span));\n      }\n      accumulated += span;\n      if (!(span > 0)) continue;\n\n      const startRad = current;\n      // When there's only one visible slice, it should span the full circle (0 to TAU).\n      // Don't wrap the end angle in this case, as wrapping (start + TAU) gives start again.\n      const endRad = validCount === 1 ? current + TAU : wrapToTau(current + span);\n      current = wrapToTau(current + span);\n\n      const [r, g, b, a] = parseColor(item.color, seriesConfig.color);\n\n      f32[outFloats + 0] = centerClipX;\n      f32[outFloats + 1] = centerClipY;\n      f32[outFloats + 2] = startRad;\n      f32[outFloats + 3] = endRad;\n      f32[outFloats + 4] = innerPx;\n      f32[outFloats + 5] = outerPx;\n      f32[outFloats + 6] = r;\n      f32[outFloats + 7] = g;\n      f32[outFloats + 8] = b;\n      f32[outFloats + 9] = a;\n      outFloats += INSTANCE_STRIDE_FLOATS;\n    }\n\n    instanceCount = outFloats / INSTANCE_STRIDE_FLOATS;\n    const requiredBytes = Math.max(4, instanceCount * INSTANCE_STRIDE_BYTES);\n\n    if (!instanceBuffer || instanceBuffer.size < requiredBytes) {\n      const grownBytes = Math.max(Math.max(4, nextPow2(requiredBytes)), instanceBuffer ? instanceBuffer.size : 0);\n      if (instanceBuffer) {\n        try {\n          instanceBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n      instanceBuffer = device.createBuffer({\n        label: 'pieRenderer/instanceBuffer',\n        size: grownBytes,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    if (instanceBuffer && instanceCount > 0) {\n      device.queue.writeBuffer(instanceBuffer, 0, cpuInstanceStagingBuffer, 0, instanceCount * INSTANCE_STRIDE_BYTES);\n    }\n  };\n\n  const render: PieRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (!instanceBuffer || instanceCount === 0) return;\n\n    // Clip to plot area (scissor is in device pixels).\n    if (lastScissor && lastCanvasWidth > 0 && lastCanvasHeight > 0) {\n      passEncoder.setScissorRect(lastScissor.x, lastScissor.y, lastScissor.w, lastScissor.h);\n    }\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n    passEncoder.setVertexBuffer(0, instanceBuffer);\n    passEncoder.draw(6, instanceCount);\n\n    // Reset scissor to full canvas to avoid impacting later renderers.\n    if (lastScissor && lastCanvasWidth > 0 && lastCanvasHeight > 0) {\n      passEncoder.setScissorRect(0, 0, lastCanvasWidth, lastCanvasHeight);\n    }\n  };\n\n  const dispose: PieRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    if (instanceBuffer) {\n      try {\n        instanceBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    instanceBuffer = null;\n    instanceCount = 0;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n\n    lastCanvasWidth = 0;\n    lastCanvasHeight = 0;\n    lastScissor = null;\n  };\n\n  return { prepare, render, dispose };\n}\n\n","export default \"// candlestick.wgsl\\n// Instanced candlestick shader (bodies + wicks):\\n// - Per-instance vertex input:\\n//   - xClip, openClip, closeClip, lowClip, highClip, bodyWidthClip (6 floats)\\n//   - bodyColor rgba (4 floats)\\n// - Draw call: draw(18, instanceCount) using triangle-list expansion in VS\\n//   - vertices 0-5: body quad (2 triangles)\\n//   - vertices 6-11: upper wick (2 triangles)\\n//   - vertices 12-17: lower wick (2 triangles)\\n// - Uniforms:\\n//   - @group(0) @binding(0): VSUniforms { transform, wickWidthClip }\\n\\nstruct VSUniforms {\\n  transform: mat4x4<f32>,\\n  wickWidthClip: f32,\\n  _pad0: f32,\\n  _pad1: f32,\\n  _pad2: f32,\\n};\\n\\n@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;\\n\\nstruct VSIn {\\n  @location(0) xClip: f32,\\n  @location(1) openClip: f32,\\n  @location(2) closeClip: f32,\\n  @location(3) lowClip: f32,\\n  @location(4) highClip: f32,\\n  @location(5) bodyWidthClip: f32,\\n  @location(6) bodyColor: vec4<f32>,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition: vec4<f32>,\\n  @location(0) color: vec4<f32>,\\n};\\n\\n@vertex\\nfn vsMain(in: VSIn, @builtin(vertex_index) vertexIndex: u32) -> VSOut {\\n  // Compute body bounds\\n  let bodyTop = max(in.openClip, in.closeClip);\\n  let bodyBottom = min(in.openClip, in.closeClip);\\n  let bodyLeft = in.xClip - in.bodyWidthClip * 0.5;\\n  let bodyRight = in.xClip + in.bodyWidthClip * 0.5;\\n\\n  // Wick bounds\\n  let wickLeft = in.xClip - vsUniforms.wickWidthClip * 0.5;\\n  let wickRight = in.xClip + vsUniforms.wickWidthClip * 0.5;\\n\\n  var pos: vec2<f32>;\\n\\n  if (vertexIndex < 6u) {\\n    // Body quad (vertices 0-5)\\n    let corners = array<vec2<f32>, 6>(\\n      vec2<f32>(0.0, 0.0),\\n      vec2<f32>(1.0, 0.0),\\n      vec2<f32>(0.0, 1.0),\\n      vec2<f32>(0.0, 1.0),\\n      vec2<f32>(1.0, 0.0),\\n      vec2<f32>(1.0, 1.0)\\n    );\\n    let corner = corners[vertexIndex];\\n    let bodyMin = vec2<f32>(bodyLeft, bodyBottom);\\n    let bodyMax = vec2<f32>(bodyRight, bodyTop);\\n    pos = bodyMin + corner * (bodyMax - bodyMin);\\n  } else if (vertexIndex < 12u) {\\n    // Upper wick (vertices 6-11): from bodyTop to highClip\\n    let idx = vertexIndex - 6u;\\n    let corners = array<vec2<f32>, 6>(\\n      vec2<f32>(0.0, 0.0),\\n      vec2<f32>(1.0, 0.0),\\n      vec2<f32>(0.0, 1.0),\\n      vec2<f32>(0.0, 1.0),\\n      vec2<f32>(1.0, 0.0),\\n      vec2<f32>(1.0, 1.0)\\n    );\\n    let corner = corners[idx];\\n    let wickMin = vec2<f32>(wickLeft, bodyTop);\\n    let wickMax = vec2<f32>(wickRight, in.highClip);\\n    pos = wickMin + corner * (wickMax - wickMin);\\n  } else {\\n    // Lower wick (vertices 12-17): from lowClip to bodyBottom\\n    let idx = vertexIndex - 12u;\\n    let corners = array<vec2<f32>, 6>(\\n      vec2<f32>(0.0, 0.0),\\n      vec2<f32>(1.0, 0.0),\\n      vec2<f32>(0.0, 1.0),\\n      vec2<f32>(0.0, 1.0),\\n      vec2<f32>(1.0, 0.0),\\n      vec2<f32>(1.0, 1.0)\\n    );\\n    let corner = corners[idx];\\n    let wickMin = vec2<f32>(wickLeft, in.lowClip);\\n    let wickMax = vec2<f32>(wickRight, bodyBottom);\\n    pos = wickMin + corner * (wickMax - wickMin);\\n  }\\n\\n  var out: VSOut;\\n  out.clipPosition = vsUniforms.transform * vec4<f32>(pos, 0.0, 1.0);\\n  out.color = in.bodyColor;\\n  return out;\\n}\\n\\n@fragment\\nfn fsMain(in: VSOut) -> @location(0) vec4<f32> {\\n  return in.color;\\n}\\n\"","import candlestickWgsl from '../shaders/candlestick.wgsl?raw';\nimport type { ResolvedCandlestickSeriesConfig } from '../config/OptionResolver';\nimport type { OHLCDataPoint, OHLCDataPointTuple } from '../config/types';\nimport type { LinearScale } from '../utils/scales';\nimport type { GridArea } from './createGridRenderer';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface CandlestickRenderer {\n  prepare(\n    series: ResolvedCandlestickSeriesConfig,\n    data: ResolvedCandlestickSeriesConfig['data'],\n    xScale: LinearScale,\n    yScale: LinearScale,\n    gridArea: GridArea,\n    backgroundColor?: string\n  ): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface CandlestickRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\ntype Rgba = readonly [r: number, g: number, b: number, a: number];\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_WICK_WIDTH_CSS_PX = 1;\nconst INSTANCE_STRIDE_BYTES = 40; // 6 floats + vec4 color\nconst INSTANCE_STRIDE_FLOATS = INSTANCE_STRIDE_BYTES / 4;\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst clampInt = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v | 0));\n\nconst parseSeriesColorToRgba01 = (color: string): Rgba =>\n  parseCssColorToRgba01(color) ?? ([0, 0, 0, 1] as const);\n\nconst nextPow2 = (v: number): number => {\n  if (!Number.isFinite(v) || v <= 0) return 1;\n  const n = Math.ceil(v);\n  return 2 ** Math.ceil(Math.log2(n));\n};\n\nconst parsePercent = (value: string): number | null => {\n  const m = value.trim().match(/^(\\d+(?:\\.\\d+)?)%$/);\n  if (!m) return null;\n  const p = Number(m[1]) / 100;\n  return Number.isFinite(p) ? p : null;\n};\n\nconst isTupleDataPoint = (p: OHLCDataPoint): p is OHLCDataPointTuple => Array.isArray(p);\n\nconst getOHLC = (\n  p: OHLCDataPoint\n): { readonly timestamp: number; readonly open: number; readonly close: number; readonly low: number; readonly high: number } => {\n  if (isTupleDataPoint(p)) {\n    return { timestamp: p[0], open: p[1], close: p[2], low: p[3], high: p[4] };\n  }\n  return { timestamp: p.timestamp, open: p.open, close: p.close, low: p.low, high: p.high };\n};\n\nconst computePlotSizeCssPx = (gridArea: GridArea): { readonly plotWidthCss: number; readonly plotHeightCss: number } | null => {\n  const dpr = gridArea.devicePixelRatio;\n  if (!(dpr > 0)) return null;\n  const canvasCssWidth = gridArea.canvasWidth / dpr;\n  const canvasCssHeight = gridArea.canvasHeight / dpr;\n  const plotWidthCss = canvasCssWidth - gridArea.left - gridArea.right;\n  const plotHeightCss = canvasCssHeight - gridArea.top - gridArea.bottom;\n  if (!(plotWidthCss > 0) || !(plotHeightCss > 0)) return null;\n  return { plotWidthCss, plotHeightCss };\n};\n\nconst computePlotClipRect = (\n  gridArea: GridArea\n): { readonly left: number; readonly right: number; readonly top: number; readonly bottom: number; readonly width: number; readonly height: number } => {\n  const { left, right, top, bottom, canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeft = left * devicePixelRatio;\n  const plotRight = canvasWidth - right * devicePixelRatio;\n  const plotTop = top * devicePixelRatio;\n  const plotBottom = canvasHeight - bottom * devicePixelRatio;\n\n  const plotLeftClip = (plotLeft / canvasWidth) * 2.0 - 1.0;\n  const plotRightClip = (plotRight / canvasWidth) * 2.0 - 1.0;\n  const plotTopClip = 1.0 - (plotTop / canvasHeight) * 2.0; // flip Y\n  const plotBottomClip = 1.0 - (plotBottom / canvasHeight) * 2.0; // flip Y\n\n  return {\n    left: plotLeftClip,\n    right: plotRightClip,\n    top: plotTopClip,\n    bottom: plotBottomClip,\n    width: plotRightClip - plotLeftClip,\n    height: plotTopClip - plotBottomClip,\n  };\n};\n\nconst computePlotScissorDevicePx = (\n  gridArea: GridArea\n): { readonly x: number; readonly y: number; readonly w: number; readonly h: number } => {\n  const { canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeftDevice = gridArea.left * devicePixelRatio;\n  const plotRightDevice = canvasWidth - gridArea.right * devicePixelRatio;\n  const plotTopDevice = gridArea.top * devicePixelRatio;\n  const plotBottomDevice = canvasHeight - gridArea.bottom * devicePixelRatio;\n\n  const scissorX = clampInt(Math.floor(plotLeftDevice), 0, Math.max(0, canvasWidth));\n  const scissorY = clampInt(Math.floor(plotTopDevice), 0, Math.max(0, canvasHeight));\n  const scissorR = clampInt(Math.ceil(plotRightDevice), 0, Math.max(0, canvasWidth));\n  const scissorB = clampInt(Math.ceil(plotBottomDevice), 0, Math.max(0, canvasHeight));\n  const scissorW = Math.max(0, scissorR - scissorX);\n  const scissorH = Math.max(0, scissorB - scissorY);\n\n  return { x: scissorX, y: scissorY, w: scissorW, h: scissorH };\n};\n\nconst computeCategoryStep = (data: ReadonlyArray<OHLCDataPoint>): number => {\n  const timestamps: number[] = [];\n  for (let i = 0; i < data.length; i++) {\n    const { timestamp } = getOHLC(data[i]);\n    if (Number.isFinite(timestamp)) timestamps.push(timestamp);\n  }\n\n  if (timestamps.length < 2) return 1;\n  timestamps.sort((a, b) => a - b);\n\n  let minStep = Number.POSITIVE_INFINITY;\n  for (let i = 1; i < timestamps.length; i++) {\n    const d = timestamps[i] - timestamps[i - 1];\n    if (d > 0 && d < minStep) minStep = d;\n  }\n  return Number.isFinite(minStep) && minStep > 0 ? minStep : 1;\n};\n\nconst computeCategoryWidthClip = (\n  xScale: LinearScale,\n  categoryStep: number,\n  plotClipRect: Readonly<{ width: number }>,\n  fallbackCategoryCount: number\n): number => {\n  if (Number.isFinite(categoryStep) && categoryStep > 0) {\n    const x0 = 0;\n    const p0 = xScale.scale(x0);\n    const p1 = xScale.scale(x0 + categoryStep);\n    const w = Math.abs(p1 - p0);\n    if (Number.isFinite(w) && w > 0) return w;\n  }\n\n  const clipWidth = Math.abs(plotClipRect.width);\n  if (!(clipWidth > 0)) return 0;\n  const n = Math.max(1, Math.floor(fallbackCategoryCount));\n  return clipWidth / n;\n};\n\nconst createIdentityMat4Buffer = (): ArrayBuffer => {\n  // Column-major identity mat4x4\n  const buffer = new ArrayBuffer(16 * 4);\n  new Float32Array(buffer).set([\n    1, 0, 0, 0, // col0\n    0, 1, 0, 0, // col1\n    0, 0, 1, 0, // col2\n    0, 0, 0, 1, // col3\n  ]);\n  return buffer;\n};\n\nexport function createCandlestickRenderer(device: GPUDevice, options?: CandlestickRendererOptions): CandlestickRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } }],\n  });\n\n  // VSUniforms: mat4x4 (64 bytes) + wickWidthClip f32 (4 bytes) + pad (12 bytes) = 80 bytes\n  const vsUniformBuffer = createUniformBuffer(device, 80, { label: 'candlestickRenderer/vsUniforms' });\n  writeUniformBuffer(device, vsUniformBuffer, createIdentityMat4Buffer()); // Default to identity\n\n  const vsUniformScratchBuffer = new ArrayBuffer(80);\n  const vsUniformScratchF32 = new Float32Array(vsUniformScratchBuffer);\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [{ binding: 0, resource: { buffer: vsUniformBuffer } }],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'candlestickRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: candlestickWgsl,\n        label: 'candlestick.wgsl',\n        buffers: [\n          {\n            arrayStride: INSTANCE_STRIDE_BYTES,\n            stepMode: 'instance',\n            attributes: [\n              { shaderLocation: 0, format: 'float32', offset: 0 },\n              { shaderLocation: 1, format: 'float32', offset: 4 },\n              { shaderLocation: 2, format: 'float32', offset: 8 },\n              { shaderLocation: 3, format: 'float32', offset: 12 },\n              { shaderLocation: 4, format: 'float32', offset: 16 },\n              { shaderLocation: 5, format: 'float32', offset: 20 },\n              { shaderLocation: 6, format: 'float32x4', offset: 24 },\n            ],\n          },\n        ],\n      },\n      fragment: {\n        code: candlestickWgsl,\n        label: 'candlestick.wgsl',\n        formats: targetFormat,\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let instanceBuffer: GPUBuffer | null = null;\n  let instanceCount = 0;\n  let cpuInstanceStagingBuffer = new ArrayBuffer(0);\n  let cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n\n  let lastCanvasWidth = 0;\n  let lastCanvasHeight = 0;\n  let lastScissor: { readonly x: number; readonly y: number; readonly w: number; readonly h: number } | null = null;\n\n  // Hollow mode state\n  let hollowMode = false;\n  let hollowInstanceBuffer: GPUBuffer | null = null;\n  let hollowInstanceCount = 0;\n  let cpuHollowStagingBuffer = new ArrayBuffer(0);\n  let cpuHollowStagingF32 = new Float32Array(cpuHollowStagingBuffer);\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('CandlestickRenderer is disposed.');\n  };\n\n  const ensureCpuInstanceCapacityFloats = (requiredFloats: number): void => {\n    if (requiredFloats <= cpuInstanceStagingF32.length) return;\n    const nextFloats = Math.max(8, nextPow2(requiredFloats));\n    cpuInstanceStagingBuffer = new ArrayBuffer(nextFloats * 4);\n    cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n  };\n\n  const ensureCpuHollowCapacityFloats = (requiredFloats: number): void => {\n    if (requiredFloats <= cpuHollowStagingF32.length) return;\n    const nextFloats = Math.max(8, nextPow2(requiredFloats));\n    cpuHollowStagingBuffer = new ArrayBuffer(nextFloats * 4);\n    cpuHollowStagingF32 = new Float32Array(cpuHollowStagingBuffer);\n  };\n\n  const prepare: CandlestickRenderer['prepare'] = (series, data, xScale, yScale, gridArea, backgroundColor) => {\n    assertNotDisposed();\n\n    if (data.length === 0) {\n      instanceCount = 0;\n      hollowInstanceCount = 0;\n      return;\n    }\n\n    const plotSize = computePlotSizeCssPx(gridArea);\n    if (!plotSize) {\n      instanceCount = 0;\n      hollowInstanceCount = 0;\n      return;\n    }\n\n    const plotClipRect = computePlotClipRect(gridArea);\n    const clipPerCssX = plotSize.plotWidthCss > 0 ? plotClipRect.width / plotSize.plotWidthCss : 0;\n\n    lastCanvasWidth = gridArea.canvasWidth;\n    lastCanvasHeight = gridArea.canvasHeight;\n    lastScissor = computePlotScissorDevicePx(gridArea);\n\n    // Compute category step and width\n    const categoryStep = computeCategoryStep(data);\n    const categoryWidthClip = computeCategoryWidthClip(xScale, categoryStep, plotClipRect, data.length);\n\n    // Compute body width in clip space\n    let bodyWidthClip = 0;\n    const rawBarWidth = series.barWidth;\n    if (typeof rawBarWidth === 'number') {\n      bodyWidthClip = Math.max(0, rawBarWidth) * clipPerCssX;\n    } else if (typeof rawBarWidth === 'string') {\n      const p = parsePercent(rawBarWidth);\n      bodyWidthClip = p == null ? 0 : categoryWidthClip * clamp01(p);\n    }\n\n    // Apply min/max width constraints (CSS pixels converted to clip space)\n    const minWidthClip = series.barMinWidth * clipPerCssX;\n    const maxWidthClip = series.barMaxWidth * clipPerCssX;\n    bodyWidthClip = Math.min(Math.max(bodyWidthClip, minWidthClip), maxWidthClip);\n\n    // Compute wick width in clip space (default 1px CSS)\n    const wickWidthCssPx = series.itemStyle.borderWidth ?? DEFAULT_WICK_WIDTH_CSS_PX;\n    const wickWidthClip = Math.max(0, wickWidthCssPx) * clipPerCssX;\n\n    // Write VS uniforms (identity transform + wick width)\n    vsUniformScratchF32.set([\n      1, 0, 0, 0, // col0\n      0, 1, 0, 0, // col1\n      0, 0, 1, 0, // col2\n      0, 0, 0, 1, // col3\n      wickWidthClip,\n      0,\n      0,\n      0,\n    ]);\n    writeUniformBuffer(device, vsUniformBuffer, vsUniformScratchBuffer);\n\n    // Parse colors\n    const upColor = parseSeriesColorToRgba01(series.itemStyle.upColor);\n    const downColor = parseSeriesColorToRgba01(series.itemStyle.downColor);\n    const upBorderColor = parseSeriesColorToRgba01(series.itemStyle.upBorderColor);\n    const downBorderColor = parseSeriesColorToRgba01(series.itemStyle.downBorderColor);\n    const bgColor = backgroundColor ? parseSeriesColorToRgba01(backgroundColor) : ([0, 0, 0, 1] as const);\n\n    hollowMode = series.style === 'hollow';\n\n    ensureCpuInstanceCapacityFloats(data.length * INSTANCE_STRIDE_FLOATS);\n    const f32 = cpuInstanceStagingF32;\n    let outFloats = 0;\n\n    if (hollowMode) {\n      ensureCpuHollowCapacityFloats(data.length * INSTANCE_STRIDE_FLOATS);\n    }\n    const hollowF32 = cpuHollowStagingF32;\n    let hollowOutFloats = 0;\n\n    for (let i = 0; i < data.length; i++) {\n      const { timestamp, open, close, low, high } = getOHLC(data[i]);\n      if (!Number.isFinite(timestamp) || !Number.isFinite(open) || !Number.isFinite(close) || !Number.isFinite(low) || !Number.isFinite(high)) {\n        continue;\n      }\n\n      const xClip = xScale.scale(timestamp);\n      const openClip = yScale.scale(open);\n      const closeClip = yScale.scale(close);\n      const lowClip = yScale.scale(low);\n      const highClip = yScale.scale(high);\n\n      if (!Number.isFinite(xClip) || !Number.isFinite(openClip) || !Number.isFinite(closeClip) || !Number.isFinite(lowClip) || !Number.isFinite(highClip)) {\n        continue;\n      }\n\n      const isUp = close > open;\n\n      if (hollowMode) {\n        // Pass 1: Draw all candles with border colors (body + wicks)\n        const borderColor = isUp ? upBorderColor : downBorderColor;\n        f32[outFloats + 0] = xClip;\n        f32[outFloats + 1] = openClip;\n        f32[outFloats + 2] = closeClip;\n        f32[outFloats + 3] = lowClip;\n        f32[outFloats + 4] = highClip;\n        f32[outFloats + 5] = bodyWidthClip;\n        f32[outFloats + 6] = borderColor[0];\n        f32[outFloats + 7] = borderColor[1];\n        f32[outFloats + 8] = borderColor[2];\n        f32[outFloats + 9] = borderColor[3];\n        outFloats += INSTANCE_STRIDE_FLOATS;\n\n        // Pass 2: For UP candles only, draw body inset with background color to punch out interior\n        if (isUp) {\n          const borderWidthClip = series.itemStyle.borderWidth * clipPerCssX;\n          const insetBodyWidthClip = Math.max(0, bodyWidthClip - 2 * borderWidthClip);\n\n          hollowF32[hollowOutFloats + 0] = xClip;\n          hollowF32[hollowOutFloats + 1] = openClip;\n          hollowF32[hollowOutFloats + 2] = closeClip;\n          hollowF32[hollowOutFloats + 3] = lowClip; // Not used for body-only draw, but keep for consistency\n          hollowF32[hollowOutFloats + 4] = highClip; // Not used for body-only draw\n          hollowF32[hollowOutFloats + 5] = insetBodyWidthClip;\n          hollowF32[hollowOutFloats + 6] = bgColor[0];\n          hollowF32[hollowOutFloats + 7] = bgColor[1];\n          hollowF32[hollowOutFloats + 8] = bgColor[2];\n          hollowF32[hollowOutFloats + 9] = bgColor[3];\n          hollowOutFloats += INSTANCE_STRIDE_FLOATS;\n        }\n      } else {\n        // Classic mode: draw candles with fill colors\n        const fillColor = isUp ? upColor : downColor;\n        f32[outFloats + 0] = xClip;\n        f32[outFloats + 1] = openClip;\n        f32[outFloats + 2] = closeClip;\n        f32[outFloats + 3] = lowClip;\n        f32[outFloats + 4] = highClip;\n        f32[outFloats + 5] = bodyWidthClip;\n        f32[outFloats + 6] = fillColor[0];\n        f32[outFloats + 7] = fillColor[1];\n        f32[outFloats + 8] = fillColor[2];\n        f32[outFloats + 9] = fillColor[3];\n        outFloats += INSTANCE_STRIDE_FLOATS;\n      }\n    }\n\n    instanceCount = outFloats / INSTANCE_STRIDE_FLOATS;\n    hollowInstanceCount = hollowOutFloats / INSTANCE_STRIDE_FLOATS;\n\n    // Upload primary instance buffer\n    const requiredBytes = Math.max(4, instanceCount * INSTANCE_STRIDE_BYTES);\n    if (!instanceBuffer || instanceBuffer.size < requiredBytes) {\n      const grownBytes = Math.max(Math.max(4, nextPow2(requiredBytes)), instanceBuffer ? instanceBuffer.size : 0);\n      if (instanceBuffer) {\n        try {\n          instanceBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n      instanceBuffer = device.createBuffer({\n        label: 'candlestickRenderer/instanceBuffer',\n        size: grownBytes,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    if (instanceCount > 0) {\n      device.queue.writeBuffer(instanceBuffer, 0, cpuInstanceStagingBuffer, 0, instanceCount * INSTANCE_STRIDE_BYTES);\n    }\n\n    // Upload hollow mode buffer (second pass)\n    if (hollowMode && hollowInstanceCount > 0) {\n      const hollowRequiredBytes = Math.max(4, hollowInstanceCount * INSTANCE_STRIDE_BYTES);\n      if (!hollowInstanceBuffer || hollowInstanceBuffer.size < hollowRequiredBytes) {\n        const grownBytes = Math.max(Math.max(4, nextPow2(hollowRequiredBytes)), hollowInstanceBuffer ? hollowInstanceBuffer.size : 0);\n        if (hollowInstanceBuffer) {\n          try {\n            hollowInstanceBuffer.destroy();\n          } catch {\n            // best-effort\n          }\n        }\n        hollowInstanceBuffer = device.createBuffer({\n          label: 'candlestickRenderer/hollowInstanceBuffer',\n          size: grownBytes,\n          usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n        });\n      }\n      device.queue.writeBuffer(hollowInstanceBuffer, 0, cpuHollowStagingBuffer, 0, hollowInstanceCount * INSTANCE_STRIDE_BYTES);\n    }\n  };\n\n  const render: CandlestickRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n\n    if (!instanceBuffer || instanceCount === 0) return;\n\n    // Apply scissor rect to clip to plot area\n    if (lastScissor && lastCanvasWidth > 0 && lastCanvasHeight > 0) {\n      passEncoder.setScissorRect(lastScissor.x, lastScissor.y, lastScissor.w, lastScissor.h);\n    }\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n\n    // Pass 1: Draw all candles (18 vertices per instance)\n    passEncoder.setVertexBuffer(0, instanceBuffer);\n    passEncoder.draw(18, instanceCount);\n\n    // Pass 2: For hollow mode, draw body-only punch-out for UP candles\n    if (hollowMode && hollowInstanceBuffer && hollowInstanceCount > 0) {\n      passEncoder.setVertexBuffer(0, hollowInstanceBuffer);\n      // Draw only body vertices (0-5) by drawing 6 vertices per instance\n      passEncoder.draw(6, hollowInstanceCount);\n    }\n\n    // Reset scissor to full canvas\n    if (lastScissor && lastCanvasWidth > 0 && lastCanvasHeight > 0) {\n      passEncoder.setScissorRect(0, 0, lastCanvasWidth, lastCanvasHeight);\n    }\n  };\n\n  const dispose: CandlestickRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    if (instanceBuffer) {\n      try {\n        instanceBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    instanceBuffer = null;\n    instanceCount = 0;\n\n    if (hollowInstanceBuffer) {\n      try {\n        hollowInstanceBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    hollowInstanceBuffer = null;\n    hollowInstanceCount = 0;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n\n    lastCanvasWidth = 0;\n    lastCanvasHeight = 0;\n    lastScissor = null;\n  };\n\n  return { prepare, render, dispose };\n}\n","export default \"// bar.wgsl\\n// Instanced bar/rect shader:\\n// - Per-instance vertex input:\\n//   - rect  = vec4<f32>(x, y, width, height) in CLIP space\\n//   - color = vec4<f32>(r, g, b, a) in [0..1]\\n// - Draw call: draw(6, instanceCount) using triangle-list expansion in VS\\n// - Uniforms:\\n//   - @group(0) @binding(0): VSUniforms { transform }\\n\\nstruct VSUniforms {\\n  transform: mat4x4<f32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;\\n\\nstruct VSIn {\\n  // rect.xy = origin, rect.zw = size (width, height)\\n  @location(0) rect: vec4<f32>,\\n  @location(1) color: vec4<f32>,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition: vec4<f32>,\\n  @location(0) color: vec4<f32>,\\n};\\n\\n@vertex\\nfn vsMain(in: VSIn, @builtin(vertex_index) vertexIndex: u32) -> VSOut {\\n  // Fixed local corners for 2 triangles (triangle-list).\\n  let corners = array<vec2<f32>, 6>(\\n    vec2<f32>(0.0, 0.0),\\n    vec2<f32>(1.0, 0.0),\\n    vec2<f32>(0.0, 1.0),\\n    vec2<f32>(0.0, 1.0),\\n    vec2<f32>(1.0, 0.0),\\n    vec2<f32>(1.0, 1.0)\\n  );\\n\\n  // Normalize negative width/height by computing min/max extents.\\n  let p0 = in.rect.xy;\\n  let p1 = in.rect.xy + in.rect.zw;\\n  let rectMin = min(p0, p1);\\n  let rectMax = max(p0, p1);\\n  let rectSize = rectMax - rectMin;\\n\\n  let corner = corners[vertexIndex];\\n  let pos = rectMin + corner * rectSize;\\n\\n  var out: VSOut;\\n  out.clipPosition = vsUniforms.transform * vec4<f32>(pos, 0.0, 1.0);\\n  out.color = in.color;\\n  return out;\\n}\\n\\n@fragment\\nfn fsMain(in: VSOut) -> @location(0) vec4<f32> {\\n  return in.color;\\n}\\n\\n\"","import barWgsl from '../shaders/bar.wgsl?raw';\nimport type { ResolvedBarSeriesConfig } from '../config/OptionResolver';\nimport type { LinearScale } from '../utils/scales';\nimport type { GridArea } from './createGridRenderer';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport type { DataStore } from '../data/createDataStore';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport { getPointCount, getX, getY } from '../data/cartesianData';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface BarRenderer {\n  prepare(\n    seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n    dataStore: DataStore,\n    xScale: LinearScale,\n    yScale: LinearScale,\n    gridArea: GridArea\n  ): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  dispose(): void;\n}\n\nexport interface BarRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\ntype Rgba = readonly [r: number, g: number, b: number, a: number];\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_BAR_GAP = 0.01; // Minimal gap between bars within a group (was 0.1)\nconst DEFAULT_BAR_CATEGORY_GAP = 0.2;\nconst INSTANCE_STRIDE_BYTES = 32; // rect vec4 + color vec4\nconst INSTANCE_STRIDE_FLOATS = INSTANCE_STRIDE_BYTES / 4;\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst parseSeriesColorToRgba01 = (color: string): Rgba =>\n  parseCssColorToRgba01(color) ?? ([0, 0, 0, 1] as const);\n\nconst nextPow2 = (v: number): number => {\n  if (!Number.isFinite(v) || v <= 0) return 1;\n  const n = Math.ceil(v);\n  return 2 ** Math.ceil(Math.log2(n));\n};\n\nconst createIdentityMat4Buffer = (): ArrayBuffer => {\n  // Column-major identity mat4x4\n  const buffer = new ArrayBuffer(16 * 4);\n  new Float32Array(buffer).set([\n    1, 0, 0, 0, // col0\n    0, 1, 0, 0, // col1\n    0, 0, 1, 0, // col2\n    0, 0, 0, 1, // col3\n  ]);\n  return buffer;\n};\n\nconst parsePercent = (value: string): number | null => {\n  const m = value.trim().match(/^(\\d+(?:\\.\\d+)?)%$/);\n  if (!m) return null;\n  const p = Number(m[1]) / 100;\n  return Number.isFinite(p) ? p : null;\n};\n\nconst normalizeStackId = (stack: unknown): string => {\n  if (typeof stack !== 'string') return '';\n  const trimmed = stack.trim();\n  return trimmed.length > 0 ? trimmed : '';\n};\n\nconst computePlotSizeCssPx = (gridArea: GridArea): { readonly plotWidthCss: number; readonly plotHeightCss: number } | null => {\n  const dpr = gridArea.devicePixelRatio;\n  if (!(dpr > 0)) return null;\n  const canvasCssWidth = gridArea.canvasWidth / dpr;\n  const canvasCssHeight = gridArea.canvasHeight / dpr;\n  const plotWidthCss = canvasCssWidth - gridArea.left - gridArea.right;\n  const plotHeightCss = canvasCssHeight - gridArea.top - gridArea.bottom;\n  if (!(plotWidthCss > 0) || !(plotHeightCss > 0)) return null;\n  return { plotWidthCss, plotHeightCss };\n};\n\nconst computePlotClipRect = (\n  gridArea: GridArea\n): { readonly left: number; readonly right: number; readonly top: number; readonly bottom: number } => {\n  const { left, right, top, bottom, canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeft = left * devicePixelRatio;\n  const plotRight = canvasWidth - right * devicePixelRatio;\n  const plotTop = top * devicePixelRatio;\n  const plotBottom = canvasHeight - bottom * devicePixelRatio;\n\n  const plotLeftClip = (plotLeft / canvasWidth) * 2.0 - 1.0;\n  const plotRightClip = (plotRight / canvasWidth) * 2.0 - 1.0;\n  const plotTopClip = 1.0 - (plotTop / canvasHeight) * 2.0; // flip Y\n  const plotBottomClip = 1.0 - (plotBottom / canvasHeight) * 2.0; // flip Y\n\n  return { left: plotLeftClip, right: plotRightClip, top: plotTopClip, bottom: plotBottomClip };\n};\n\nconst computeCategoryWidthClip = (\n  xScale: LinearScale,\n  categoryStep: number,\n  plotClipRect: Readonly<{ left: number; right: number }>,\n  fallbackCategoryCount: number\n): number => {\n  if (Number.isFinite(categoryStep) && categoryStep > 0) {\n    const x0 = 0;\n    const p0 = xScale.scale(x0);\n    const p1 = xScale.scale(x0 + categoryStep);\n    const w = Math.abs(p1 - p0);\n    if (Number.isFinite(w) && w > 0) return w;\n  }\n\n  const clipWidth = Math.abs(plotClipRect.right - plotClipRect.left);\n  if (!(clipWidth > 0)) return 0;\n  const n = Math.max(1, Math.floor(fallbackCategoryCount));\n  return clipWidth / n;\n};\n\nexport function createBarRenderer(device: GPUDevice, options?: BarRendererOptions): BarRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n    ],\n  });\n\n  const vsUniformBuffer = createUniformBuffer(device, 64, { label: 'barRenderer/vsUniforms' });\n  // Default to identity: we upload rects in clip-space.\n  writeUniformBuffer(device, vsUniformBuffer, createIdentityMat4Buffer());\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [\n      { binding: 0, resource: { buffer: vsUniformBuffer } },\n    ],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'barRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: barWgsl,\n        label: 'bar.wgsl',\n        buffers: [\n          {\n            arrayStride: INSTANCE_STRIDE_BYTES, // rect vec4 + color vec4\n            stepMode: 'instance',\n            attributes: [\n              { shaderLocation: 0, format: 'float32x4', offset: 0 },\n              { shaderLocation: 1, format: 'float32x4', offset: 16 },\n            ],\n          },\n        ],\n      },\n      fragment: {\n        code: barWgsl,\n        label: 'bar.wgsl',\n        formats: targetFormat,\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let instanceBuffer: GPUBuffer | null = null;\n  let instanceCount = 0;\n  let cpuInstanceStagingBuffer = new ArrayBuffer(0);\n  let cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n  const categoryXScratch: number[] = [];\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('BarRenderer is disposed.');\n  };\n\n  const ensureCpuInstanceCapacityFloats = (requiredFloats: number): void => {\n    if (requiredFloats <= cpuInstanceStagingF32.length) return;\n    // Grow geometrically (power-of-two) to reduce churn.\n    const nextFloats = Math.max(8, nextPow2(requiredFloats));\n    cpuInstanceStagingBuffer = new ArrayBuffer(nextFloats * 4);\n    cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n  };\n\n  const computeBarCategoryStep = (seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>): number => {\n    categoryXScratch.length = 0;\n    for (let s = 0; s < seriesConfigs.length; s++) {\n      const data = seriesConfigs[s].data;\n      const count = getPointCount(data);\n      for (let i = 0; i < count; i++) {\n        const x = getX(data, i);\n        if (Number.isFinite(x)) categoryXScratch.push(x);\n      }\n    }\n\n    if (categoryXScratch.length < 2) return 1;\n    categoryXScratch.sort((a, b) => a - b);\n\n    let minStep = Number.POSITIVE_INFINITY;\n    for (let i = 1; i < categoryXScratch.length; i++) {\n      const d = categoryXScratch[i] - categoryXScratch[i - 1];\n      if (d > 0 && d < minStep) minStep = d;\n    }\n    return Number.isFinite(minStep) && minStep > 0 ? minStep : 1;\n  };\n\n  const computeSharedBarLayout = (\n    seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>\n  ): { readonly barWidth?: number | string; readonly barGap?: number; readonly barCategoryGap?: number } => {\n    let barWidth: number | string | undefined = undefined;\n    let barGap: number | undefined = undefined;\n    let barCategoryGap: number | undefined = undefined;\n\n    for (let i = 0; i < seriesConfigs.length; i++) {\n      const s = seriesConfigs[i];\n      if (barWidth === undefined && s.barWidth !== undefined) barWidth = s.barWidth;\n      if (barGap === undefined && s.barGap !== undefined) barGap = s.barGap;\n      if (barCategoryGap === undefined && s.barCategoryGap !== undefined) barCategoryGap = s.barCategoryGap;\n    }\n\n    return { barWidth, barGap, barCategoryGap };\n  };\n\n  const computeBaselineForBarsFromData = (seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>): number => {\n    let yMin = Number.POSITIVE_INFINITY;\n    let yMax = Number.NEGATIVE_INFINITY;\n\n    for (let s = 0; s < seriesConfigs.length; s++) {\n      const data = seriesConfigs[s].data;\n      const count = getPointCount(data);\n      for (let i = 0; i < count; i++) {\n        const y = getY(data, i);\n        if (!Number.isFinite(y)) continue;\n        if (y < yMin) yMin = y;\n        if (y > yMax) yMax = y;\n      }\n    }\n\n    if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) return 0;\n    if (yMin <= 0 && 0 <= yMax) return 0;\n    return Math.abs(yMin) < Math.abs(yMax) ? yMin : yMax;\n  };\n\n  const computeBaselineForBarsFromAxis = (\n    seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n    yScale: LinearScale,\n    plotClipRect: Readonly<{ top: number; bottom: number }>\n  ): number => {\n    // Determine the visible y-domain from the yScale + plot clip rect (clip-space).\n    const yDomainA = yScale.invert(plotClipRect.bottom);\n    const yDomainB = yScale.invert(plotClipRect.top);\n    const yMin = Math.min(yDomainA, yDomainB);\n    const yMax = Math.max(yDomainA, yDomainB);\n\n    // If scale/range is degenerate, fall back.\n    if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n      return computeBaselineForBarsFromData(seriesConfigs);\n    }\n\n    if (yMin <= 0 && 0 <= yMax) return 0;\n    if (yMin > 0) return yMin;\n    if (yMax < 0) return yMax;\n\n    // Should be unreachable with finite min/max, but keep a safe fallback.\n    return computeBaselineForBarsFromData(seriesConfigs);\n  };\n\n  const prepare: BarRenderer['prepare'] = (seriesConfigs, dataStore, xScale, yScale, gridArea) => {\n    assertNotDisposed();\n    void dataStore;\n\n    if (seriesConfigs.length === 0) {\n      instanceCount = 0;\n      return;\n    }\n\n    const plotSize = computePlotSizeCssPx(gridArea);\n    if (!plotSize) {\n      instanceCount = 0;\n      return;\n    }\n\n    const plotClipRect = computePlotClipRect(gridArea);\n    const plotClipWidth = plotClipRect.right - plotClipRect.left;\n    const plotClipHeight = plotClipRect.top - plotClipRect.bottom;\n    const clipPerCssX = plotSize.plotWidthCss > 0 ? plotClipWidth / plotSize.plotWidthCss : 0;\n    void plotClipHeight; // reserved for future y-size conversions (e.g. border radius)\n\n    // Cluster slots:\n    // - Each unique non-empty stackId gets a single cluster slot.\n    // - Each unstacked series gets its own cluster slot.\n    const stackIdToClusterIndex = new Map<string, number>();\n    const clusterIndexBySeries: number[] = new Array(seriesConfigs.length);\n    let clusterCount = 0;\n    for (let i = 0; i < seriesConfigs.length; i++) {\n      const stackId = normalizeStackId(seriesConfigs[i].stack);\n      if (stackId !== '') {\n        const existing = stackIdToClusterIndex.get(stackId);\n        if (existing !== undefined) {\n          clusterIndexBySeries[i] = existing;\n        } else {\n          const idx = clusterCount++;\n          stackIdToClusterIndex.set(stackId, idx);\n          clusterIndexBySeries[i] = idx;\n        }\n      } else {\n        clusterIndexBySeries[i] = clusterCount++;\n      }\n    }\n    clusterCount = Math.max(1, clusterCount);\n\n    const categoryStep = computeBarCategoryStep(seriesConfigs);\n    const layout = computeSharedBarLayout(seriesConfigs);\n    const barGap = clamp01(layout.barGap ?? DEFAULT_BAR_GAP);\n    const barCategoryGap = clamp01(layout.barCategoryGap ?? DEFAULT_BAR_CATEGORY_GAP);\n\n    let fallbackCategoryCount = 1;\n    for (let s = 0; s < seriesConfigs.length; s++) {\n      const dataLength = getPointCount(seriesConfigs[s].data);\n      fallbackCategoryCount = Math.max(fallbackCategoryCount, Math.floor(dataLength));\n    }\n\n    const categoryWidthClip = computeCategoryWidthClip(xScale, categoryStep, plotClipRect, fallbackCategoryCount);\n    const categoryInnerWidthClip = Math.max(0, categoryWidthClip * (1 - barCategoryGap));\n\n    const denom = clusterCount + Math.max(0, clusterCount - 1) * barGap;\n    const maxBarWidthClip = denom > 0 ? categoryInnerWidthClip / denom : 0;\n\n    let barWidthClip = 0;\n    const rawBarWidth = layout.barWidth;\n    if (typeof rawBarWidth === 'number') {\n      barWidthClip = Math.max(0, rawBarWidth) * clipPerCssX;\n      barWidthClip = Math.min(barWidthClip, maxBarWidthClip);\n    } else if (typeof rawBarWidth === 'string') {\n      const p = parsePercent(rawBarWidth);\n      barWidthClip = p == null ? 0 : maxBarWidthClip * clamp01(p);\n    }\n\n    if (!(barWidthClip > 0)) {\n      // Auto-width: max per-bar width that still avoids overlap (given clusterCount and barGap).\n      barWidthClip = maxBarWidthClip;\n    }\n\n    const gapClip = barWidthClip * barGap;\n    const clusterWidthClip = clusterCount * barWidthClip + Math.max(0, clusterCount - 1) * gapClip;\n\n    let baselineDomain = computeBaselineForBarsFromAxis(seriesConfigs, yScale, plotClipRect);\n    let baselineClip = yScale.scale(baselineDomain);\n    if (!Number.isFinite(baselineClip)) {\n      // Fallback for pathological scales: revert to data-derived baseline, then 0.\n      const fallbackBaselineDomain = computeBaselineForBarsFromData(seriesConfigs);\n      baselineDomain = fallbackBaselineDomain;\n      baselineClip = yScale.scale(fallbackBaselineDomain);\n      if (!Number.isFinite(baselineClip)) {\n        baselineDomain = 0;\n        baselineClip = yScale.scale(0);\n      }\n      if (!Number.isFinite(baselineClip)) {\n        instanceCount = 0;\n        return;\n      }\n    }\n\n    let maxBars = 0;\n    for (let s = 0; s < seriesConfigs.length; s++) {\n      maxBars += Math.max(0, getPointCount(seriesConfigs[s].data));\n    }\n\n    ensureCpuInstanceCapacityFloats(maxBars * INSTANCE_STRIDE_FLOATS);\n    const f32 = cpuInstanceStagingF32;\n    let outFloats = 0;\n\n    // Per-stack, per-x running sums in domain units (supports negative stacking too).\n    const stackSumsByStackId = new Map<string, Map<number, { posSum: number; negSum: number }>>();\n\n    for (let seriesIndex = 0; seriesIndex < seriesConfigs.length; seriesIndex++) {\n      const series = seriesConfigs[seriesIndex];\n      const data = series.data;\n      const [r, g, b, a] = parseSeriesColorToRgba01(series.color);\n      const stackId = normalizeStackId(series.stack);\n      const clusterIndex = clusterIndexBySeries[seriesIndex] ?? 0;\n\n      const count = getPointCount(data);\n      for (let i = 0; i < count; i++) {\n        const x = getX(data, i);\n        const y = getY(data, i);\n        const xClipCenter = xScale.scale(x);\n        if (!Number.isFinite(xClipCenter) || !Number.isFinite(y)) continue;\n\n        const left = xClipCenter - clusterWidthClip / 2 + clusterIndex * (barWidthClip + gapClip);\n\n        let baseClip = baselineClip;\n        let height = 0;\n\n        if (stackId !== '') {\n          let sumsForX = stackSumsByStackId.get(stackId);\n          if (!sumsForX) {\n            sumsForX = new Map<number, { posSum: number; negSum: number }>();\n            stackSumsByStackId.set(stackId, sumsForX);\n          }\n\n          // NOTE: Never key stacks by raw `x` (float equality is fragile). Instead, compute a stable\n          // integer \"category\" key so visually-equivalent bars stack together even with tiny noise.\n          let xKey: number;\n          if (Number.isFinite(categoryWidthClip) && categoryWidthClip > 0 && Number.isFinite(xClipCenter)) {\n            xKey = Math.round((xClipCenter - plotClipRect.left) / categoryWidthClip);\n          } else if (Number.isFinite(categoryStep) && categoryStep > 0) {\n            xKey = Math.round(x / categoryStep);\n          } else {\n            // Last-resort: stable-ish quantization in domain space.\n            xKey = Math.round(x * 1e6);\n          }\n\n          let sums = sumsForX.get(xKey);\n          if (!sums) {\n            sums = { posSum: baselineDomain, negSum: baselineDomain };\n            sumsForX.set(xKey, sums);\n          }\n\n          // Stack upward for y>=0, downward for y<0 (domain units).\n          let baseDomain: number;\n          let topDomain: number;\n          if (y >= 0) {\n            baseDomain = sums.posSum;\n            topDomain = baseDomain + y;\n            sums.posSum = topDomain;\n          } else {\n            baseDomain = sums.negSum;\n            topDomain = baseDomain + y;\n            sums.negSum = topDomain;\n          }\n\n          const bClip = yScale.scale(baseDomain);\n          const tClip = yScale.scale(topDomain);\n          if (!Number.isFinite(bClip) || !Number.isFinite(tClip)) continue;\n          baseClip = bClip;\n          height = tClip - bClip;\n        } else {\n          const yClip = yScale.scale(y);\n          if (!Number.isFinite(yClip)) continue;\n          height = yClip - baselineClip;\n        }\n\n        f32[outFloats + 0] = left;\n        f32[outFloats + 1] = baseClip;\n        f32[outFloats + 2] = barWidthClip;\n        f32[outFloats + 3] = height;\n        f32[outFloats + 4] = r;\n        f32[outFloats + 5] = g;\n        f32[outFloats + 6] = b;\n        f32[outFloats + 7] = a;\n        outFloats += INSTANCE_STRIDE_FLOATS;\n      }\n    }\n\n    // If we skipped invalid points, resize the effective instance count.\n    instanceCount = outFloats / INSTANCE_STRIDE_FLOATS;\n    const requiredBytes = Math.max(4, instanceCount * INSTANCE_STRIDE_BYTES);\n\n    if (!instanceBuffer || instanceBuffer.size < requiredBytes) {\n      const grownBytes = Math.max(Math.max(4, nextPow2(requiredBytes)), instanceBuffer ? instanceBuffer.size : 0);\n      if (instanceBuffer) {\n        try {\n          instanceBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n      instanceBuffer = device.createBuffer({\n        label: 'barRenderer/instanceBuffer',\n        size: grownBytes,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    if (instanceCount > 0) {\n      device.queue.writeBuffer(instanceBuffer, 0, cpuInstanceStagingBuffer, 0, instanceCount * INSTANCE_STRIDE_BYTES);\n    }\n  };\n\n  const render: BarRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (!instanceBuffer || instanceCount === 0) return;\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n    passEncoder.setVertexBuffer(0, instanceBuffer);\n    passEncoder.draw(6, instanceCount);\n  };\n\n  const dispose: BarRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    if (instanceBuffer) {\n      try {\n        instanceBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    instanceBuffer = null;\n    instanceCount = 0;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n  };\n\n  return { prepare, render, dispose };\n}\n\n","/**\n * Renderer pool management for the RenderCoordinator.\n *\n * Manages dynamic arrays of chart renderers with lazy instantiation and proper disposal.\n * Each chart type (line, area, scatter, etc.) maintains a pool of renderer instances\n * that grows/shrinks based on the number of series.\n *\n * @module rendererPool\n */\n\nimport { createAreaRenderer } from '../../../renderers/createAreaRenderer';\nimport { createLineRenderer } from '../../../renderers/createLineRenderer';\nimport { createScatterRenderer } from '../../../renderers/createScatterRenderer';\nimport { createScatterDensityRenderer } from '../../../renderers/createScatterDensityRenderer';\nimport { createPieRenderer } from '../../../renderers/createPieRenderer';\nimport { createCandlestickRenderer } from '../../../renderers/createCandlestickRenderer';\nimport { createBarRenderer } from '../../../renderers/createBarRenderer';\nimport type { PipelineCache } from '../../PipelineCache';\n\n/**\n * Configuration for renderer pool creation.\n */\nexport interface RendererPoolConfig {\n  readonly device: GPUDevice;\n  readonly targetFormat: GPUTextureFormat;\n  readonly pipelineCache?: PipelineCache;\n  /**\n   * Multisample count for all renderer pipelines.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n}\n\n/**\n * Renderer pool state exposed to the render coordinator.\n */\nexport interface RendererPoolState {\n  readonly areaRenderers: ReadonlyArray<ReturnType<typeof createAreaRenderer>>;\n  readonly lineRenderers: ReadonlyArray<ReturnType<typeof createLineRenderer>>;\n  readonly scatterRenderers: ReadonlyArray<ReturnType<typeof createScatterRenderer>>;\n  readonly scatterDensityRenderers: ReadonlyArray<ReturnType<typeof createScatterDensityRenderer>>;\n  readonly pieRenderers: ReadonlyArray<ReturnType<typeof createPieRenderer>>;\n  readonly candlestickRenderers: ReadonlyArray<ReturnType<typeof createCandlestickRenderer>>;\n  readonly barRenderer: ReturnType<typeof createBarRenderer>;\n}\n\n/**\n * Renderer pool interface returned by factory function.\n */\nexport interface RendererPool {\n  /**\n   * Ensures area renderer count matches the given count.\n   * Grows or shrinks the pool as needed, disposing excess renderers.\n   *\n   * @param count - Desired number of area renderers\n   */\n  ensureAreaRendererCount(count: number): void;\n\n  /**\n   * Ensures line renderer count matches the given count.\n   * Grows or shrinks the pool as needed, disposing excess renderers.\n   *\n   * @param count - Desired number of line renderers\n   */\n  ensureLineRendererCount(count: number): void;\n\n  /**\n   * Ensures scatter renderer count matches the given count.\n   * Grows or shrinks the pool as needed, disposing excess renderers.\n   *\n   * @param count - Desired number of scatter renderers\n   */\n  ensureScatterRendererCount(count: number): void;\n\n  /**\n   * Ensures scatter density renderer count matches the given count.\n   * Grows or shrinks the pool as needed, disposing excess renderers.\n   *\n   * @param count - Desired number of scatter density renderers\n   */\n  ensureScatterDensityRendererCount(count: number): void;\n\n  /**\n   * Ensures pie renderer count matches the given count.\n   * Grows or shrinks the pool as needed, disposing excess renderers.\n   *\n   * @param count - Desired number of pie renderers\n   */\n  ensurePieRendererCount(count: number): void;\n\n  /**\n   * Ensures candlestick renderer count matches the given count.\n   * Grows or shrinks the pool as needed, disposing excess renderers.\n   *\n   * @param count - Desired number of candlestick renderers\n   */\n  ensureCandlestickRendererCount(count: number): void;\n\n  /**\n   * Gets current renderer pool state for rendering.\n   * Returns readonly arrays to prevent external mutation.\n   *\n   * @returns Current state with all renderer arrays\n   */\n  getState(): RendererPoolState;\n\n  /**\n   * Disposes all renderers in the pool.\n   * Clears all arrays and destroys GPU resources.\n   */\n  dispose(): void;\n}\n\n/**\n * Creates a renderer pool for dynamic renderer management.\n *\n * The renderer pool uses lazy instantiation: renderers are only created when\n * the pool grows, and are disposed when the pool shrinks. This allows the\n * render coordinator to efficiently handle varying numbers of series.\n *\n * **Architecture:**\n * - Each chart type has a dedicated renderer array\n * - Bar renderer is a singleton (not pooled)\n * - Renderers are disposed when removed from the pool\n * - Arrays are cleared to release references\n *\n * @param config - Configuration with device and target format\n * @returns Renderer pool instance\n */\nexport function createRendererPool(config: RendererPoolConfig): RendererPool {\n  const { device, targetFormat, pipelineCache, sampleCount } = config;\n\n  // Mutable renderer arrays (exposed as readonly externally)\n  const areaRenderers: Array<ReturnType<typeof createAreaRenderer>> = [];\n  const lineRenderers: Array<ReturnType<typeof createLineRenderer>> = [];\n  const scatterRenderers: Array<ReturnType<typeof createScatterRenderer>> = [];\n  const scatterDensityRenderers: Array<ReturnType<typeof createScatterDensityRenderer>> = [];\n  const pieRenderers: Array<ReturnType<typeof createPieRenderer>> = [];\n  const candlestickRenderers: Array<ReturnType<typeof createCandlestickRenderer>> = [];\n\n  // Bar renderer is a singleton (one instance handles all bar series)\n  const barRenderer = createBarRenderer(device, { targetFormat, pipelineCache, sampleCount });\n\n  /**\n   * Ensures area renderer count matches the given count.\n   * Shrinks pool by popping and disposing excess renderers.\n   * Grows pool by pushing new renderer instances.\n   */\n  function ensureAreaRendererCount(count: number): void {\n    while (areaRenderers.length > count) {\n      const r = areaRenderers.pop();\n      r?.dispose();\n    }\n    while (areaRenderers.length < count) {\n      areaRenderers.push(createAreaRenderer(device, { targetFormat, pipelineCache, sampleCount }));\n    }\n  }\n\n  /**\n   * Ensures line renderer count matches the given count.\n   * Shrinks pool by popping and disposing excess renderers.\n   * Grows pool by pushing new renderer instances.\n   */\n  function ensureLineRendererCount(count: number): void {\n    while (lineRenderers.length > count) {\n      const r = lineRenderers.pop();\n      r?.dispose();\n    }\n    while (lineRenderers.length < count) {\n      lineRenderers.push(createLineRenderer(device, { targetFormat, pipelineCache, sampleCount }));\n    }\n  }\n\n  /**\n   * Ensures scatter renderer count matches the given count.\n   * Shrinks pool by popping and disposing excess renderers.\n   * Grows pool by pushing new renderer instances.\n   */\n  function ensureScatterRendererCount(count: number): void {\n    while (scatterRenderers.length > count) {\n      const r = scatterRenderers.pop();\n      r?.dispose();\n    }\n    while (scatterRenderers.length < count) {\n      scatterRenderers.push(createScatterRenderer(device, { targetFormat, pipelineCache, sampleCount }));\n    }\n  }\n\n  /**\n   * Ensures scatter density renderer count matches the given count.\n   * Shrinks pool by popping and disposing excess renderers.\n   * Grows pool by pushing new renderer instances.\n   */\n  function ensureScatterDensityRendererCount(count: number): void {\n    while (scatterDensityRenderers.length > count) {\n      const r = scatterDensityRenderers.pop();\n      r?.dispose();\n    }\n    while (scatterDensityRenderers.length < count) {\n      scatterDensityRenderers.push(createScatterDensityRenderer(device, { targetFormat, pipelineCache, sampleCount }));\n    }\n  }\n\n  /**\n   * Ensures pie renderer count matches the given count.\n   * Shrinks pool by popping and disposing excess renderers.\n   * Grows pool by pushing new renderer instances.\n   */\n  function ensurePieRendererCount(count: number): void {\n    while (pieRenderers.length > count) {\n      const r = pieRenderers.pop();\n      r?.dispose();\n    }\n    while (pieRenderers.length < count) {\n      pieRenderers.push(createPieRenderer(device, { targetFormat, pipelineCache, sampleCount }));\n    }\n  }\n\n  /**\n   * Ensures candlestick renderer count matches the given count.\n   * Shrinks pool by popping and disposing excess renderers.\n   * Grows pool by pushing new renderer instances.\n   */\n  function ensureCandlestickRendererCount(count: number): void {\n    while (candlestickRenderers.length > count) {\n      const r = candlestickRenderers.pop();\n      r?.dispose();\n    }\n    while (candlestickRenderers.length < count) {\n      candlestickRenderers.push(createCandlestickRenderer(device, { targetFormat, pipelineCache, sampleCount }));\n    }\n  }\n\n  // Cached state object to avoid per-frame allocations.\n  // Since the arrays are mutated in-place (push/pop), the cached object's\n  // readonly references remain valid — we only need one allocation.\n  let cachedState: RendererPoolState | null = null;\n\n  /**\n   * Gets current renderer pool state.\n   * Returns a cached object with readonly array references to prevent\n   * per-frame object allocations. The object is created once and reused\n   * because the underlying arrays are mutated in-place.\n   */\n  function getState(): RendererPoolState {\n    if (!cachedState) {\n      cachedState = {\n        areaRenderers,\n        lineRenderers,\n        scatterRenderers,\n        scatterDensityRenderers,\n        pieRenderers,\n        candlestickRenderers,\n        barRenderer,\n      };\n    }\n    return cachedState;\n  }\n\n  /**\n   * Disposes all renderers and clears arrays.\n   * IMPORTANT: Also disposes scatterDensityRenderers which was missing in original code.\n   */\n  function dispose(): void {\n    // Dispose area renderers\n    for (let i = 0; i < areaRenderers.length; i++) {\n      areaRenderers[i].dispose();\n    }\n    areaRenderers.length = 0;\n\n    // Dispose line renderers\n    for (let i = 0; i < lineRenderers.length; i++) {\n      lineRenderers[i].dispose();\n    }\n    lineRenderers.length = 0;\n\n    // Dispose scatter renderers\n    for (let i = 0; i < scatterRenderers.length; i++) {\n      scatterRenderers[i].dispose();\n    }\n    scatterRenderers.length = 0;\n\n    // Dispose scatter density renderers (BUGFIX: was missing in original code)\n    for (let i = 0; i < scatterDensityRenderers.length; i++) {\n      scatterDensityRenderers[i].dispose();\n    }\n    scatterDensityRenderers.length = 0;\n\n    // Dispose pie renderers\n    for (let i = 0; i < pieRenderers.length; i++) {\n      pieRenderers[i].dispose();\n    }\n    pieRenderers.length = 0;\n\n    // Dispose candlestick renderers\n    for (let i = 0; i < candlestickRenderers.length; i++) {\n      candlestickRenderers[i].dispose();\n    }\n    candlestickRenderers.length = 0;\n\n    // Dispose bar renderer (singleton)\n    barRenderer.dispose();\n  }\n\n  return {\n    ensureAreaRendererCount,\n    ensureLineRendererCount,\n    ensureScatterRendererCount,\n    ensureScatterDensityRendererCount,\n    ensurePieRendererCount,\n    ensureCandlestickRendererCount,\n    getState,\n    dispose,\n  };\n}\n","/**\n * GPU texture management for the RenderCoordinator.\n *\n * Handles lazy allocation of render target textures and MSAA overlay management.\n * Uses a multi-pass rendering strategy:\n * 1. Main scene → 4x MSAA texture, resolved to single-sample mainResolveTexture\n * 2. Blit resolved main scene to MSAA overlay target + draw annotations (MSAA overlay pass)\n * 3. Draw UI overlays on resolved swapchain (single-sample)\n *\n * @module textureManager\n */\n\nimport { createRenderPipeline } from '../../../renderers/rendererUtils';\nimport type { PipelineCache } from '../../PipelineCache';\n\n/**\n * MSAA sample count for the main scene render pass.\n * All series renderers (line, area, bar, scatter, etc.) and the grid\n * must create pipelines with this sample count.\n */\nexport const MAIN_SCENE_MSAA_SAMPLE_COUNT = 4;\n\n/**\n * MSAA sample count for annotation overlay pass.\n * Higher values reduce aliasing but increase memory/performance cost.\n */\nexport const ANNOTATION_OVERLAY_MSAA_SAMPLE_COUNT = 4;\n\n/**\n * Blit shader for copying main color texture to MSAA target.\n * Uses textureLoad (no filtering) for pixel-exact copy.\n */\nconst OVERLAY_BLIT_WGSL = `\nstruct VSOut { @builtin(position) pos: vec4f };\n\n@vertex\nfn vsMain(@builtin(vertex_index) i: u32) -> VSOut {\n  var positions = array<vec2f, 3>(\n    vec2f(-1.0, -1.0),\n    vec2f( 3.0, -1.0),\n    vec2f(-1.0,  3.0)\n  );\n  var o: VSOut;\n  o.pos = vec4f(positions[i], 0.0, 1.0);\n  return o;\n}\n\n// Using textureLoad (no filtering) for pixel-exact blit into the MSAA overlay pass.\n@group(0) @binding(0) var srcTex: texture_2d<f32>;\n\n@fragment\nfn fsMain(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n  let xy = vec2<i32>(pos.xy);\n  return textureLoad(srcTex, xy, 0);\n}\n`;\n\n/**\n * Texture manager state exposed to the render coordinator.\n */\nexport interface TextureManagerState {\n  readonly mainColorView: GPUTextureView | null;\n  /** Single-sample resolve target for the MSAA main pass. Used by the overlay blit. */\n  readonly mainResolveView: GPUTextureView | null;\n  readonly overlayMsaaView: GPUTextureView | null;\n  readonly overlayBlitBindGroup: GPUBindGroup | null;\n  readonly overlayBlitPipeline: GPURenderPipeline;\n  readonly msaaSampleCount: number;\n  /** MSAA sample count for the main scene render pass. */\n  readonly mainSceneMsaaSampleCount: number;\n}\n\n/**\n * Internal mutable state for texture management.\n */\ninterface InternalState {\n  mainColorTexture: GPUTexture | null;\n  mainColorView: GPUTextureView | null;\n  /** Single-sample resolve target for the MSAA main pass. */\n  mainResolveTexture: GPUTexture | null;\n  mainResolveView: GPUTextureView | null;\n  overlayMsaaTexture: GPUTexture | null;\n  overlayMsaaView: GPUTextureView | null;\n  overlayBlitBindGroup: GPUBindGroup | null;\n  overlayTargetsWidth: number;\n  overlayTargetsHeight: number;\n  overlayTargetsFormat: GPUTextureFormat | null;\n}\n\n/**\n * Configuration for texture manager creation.\n */\nexport interface TextureManagerConfig {\n  readonly device: GPUDevice;\n  readonly targetFormat: GPUTextureFormat;\n  readonly pipelineCache?: PipelineCache;\n}\n\n/**\n * Texture manager interface returned by factory function.\n */\nexport interface TextureManager {\n  /**\n   * Ensures textures are allocated for the given dimensions.\n   * Reallocates if size or format changes.\n   *\n   * @param width - Canvas width in device pixels\n   * @param height - Canvas height in device pixels\n   */\n  ensureTextures(width: number, height: number): void;\n\n  /**\n   * Gets current texture manager state for rendering.\n   *\n   * @returns Current state with texture views and bind groups\n   */\n  getState(): TextureManagerState;\n\n  /**\n   * Disposes all GPU resources.\n   * Textures, views, and bind groups are destroyed.\n   */\n  dispose(): void;\n}\n\n/**\n * Safely destroys a GPU texture.\n * Best-effort: swallows exceptions if texture is already destroyed or invalid.\n *\n * @param tex - Texture to destroy or null\n */\nfunction destroyTexture(tex: GPUTexture | null): void {\n  if (!tex) return;\n  try {\n    tex.destroy();\n  } catch {\n    // best-effort: texture may already be destroyed or invalid\n  }\n}\n\n/**\n * Creates a texture manager for render target allocation and management.\n *\n * The texture manager uses lazy allocation: textures are only created when\n * first requested via ensureTextures(), and are reallocated if dimensions\n * or format change.\n *\n * **Architecture:**\n * - Main color texture: 4x MSAA render target for main scene\n * - Main resolve texture: Single-sample resolve target (read by overlay blit)\n * - Overlay MSAA texture: Multi-sample render target for annotations\n * - Blit pipeline: Copies resolved main scene to MSAA target for overlay pass\n *\n * @param config - Configuration with device and target format\n * @returns Texture manager instance\n */\nexport function createTextureManager(config: TextureManagerConfig): TextureManager {\n  const { device, targetFormat } = config;\n\n  // Internal mutable state\n  const state: InternalState = {\n    mainColorTexture: null,\n    mainColorView: null,\n    mainResolveTexture: null,\n    mainResolveView: null,\n    overlayMsaaTexture: null,\n    overlayMsaaView: null,\n    overlayBlitBindGroup: null,\n    overlayTargetsWidth: 0,\n    overlayTargetsHeight: 0,\n    overlayTargetsFormat: null,\n  };\n\n  // Create bind group layout for blit pipeline\n  const overlayBlitBindGroupLayout = device.createBindGroupLayout({\n    entries: [{\n      binding: 0,\n      visibility: GPUShaderStage.FRAGMENT,\n      texture: { sampleType: 'float', viewDimension: '2d' }\n    }],\n  });\n\n  // Create blit pipeline for copying main color to MSAA target\n  const overlayBlitPipeline = createRenderPipeline(device, {\n    label: 'textureManager/overlayBlitPipeline',\n    bindGroupLayouts: [overlayBlitBindGroupLayout],\n    vertex: { code: OVERLAY_BLIT_WGSL, label: 'textureManager/overlayBlit.wgsl' },\n    fragment: { code: OVERLAY_BLIT_WGSL, label: 'textureManager/overlayBlit.wgsl', formats: targetFormat },\n    primitive: { topology: 'triangle-list', cullMode: 'none' },\n    multisample: { count: ANNOTATION_OVERLAY_MSAA_SAMPLE_COUNT },\n  }, config.pipelineCache);\n\n  /**\n   * Ensures overlay textures are allocated for the given dimensions.\n   * Reallocates if size or format changes.\n   */\n  function ensureTextures(canvasWidthDevicePx: number, canvasHeightDevicePx: number): void {\n    // Clamp dimensions to valid range [1, infinity)\n    const w = Number.isFinite(canvasWidthDevicePx) ? Math.max(1, Math.floor(canvasWidthDevicePx)) : 1;\n    const h = Number.isFinite(canvasHeightDevicePx) ? Math.max(1, Math.floor(canvasHeightDevicePx)) : 1;\n\n    // Check if textures are already allocated with correct dimensions and format\n    if (\n      state.mainColorTexture &&\n      state.mainResolveTexture &&\n      state.overlayMsaaTexture &&\n      state.overlayBlitBindGroup &&\n      state.overlayTargetsWidth === w &&\n      state.overlayTargetsHeight === h &&\n      state.overlayTargetsFormat === targetFormat\n    ) {\n      return; // Textures are already correct\n    }\n\n    // Destroy old textures before allocating new ones\n    destroyTexture(state.mainColorTexture);\n    destroyTexture(state.mainResolveTexture);\n    destroyTexture(state.overlayMsaaTexture);\n\n    // Allocate main color texture (MSAA render target).\n    // RENDER_ATTACHMENT only — multisampled textures cannot have TEXTURE_BINDING.\n    state.mainColorTexture = device.createTexture({\n      label: 'textureManager/mainColorTexture',\n      size: { width: w, height: h },\n      sampleCount: MAIN_SCENE_MSAA_SAMPLE_COUNT,\n      format: targetFormat,\n      usage: GPUTextureUsage.RENDER_ATTACHMENT,\n    });\n    state.mainColorView = state.mainColorTexture.createView();\n\n    // Allocate single-sample resolve target for the MSAA main pass.\n    // This receives the resolved result and is read by the overlay blit pipeline.\n    state.mainResolveTexture = device.createTexture({\n      label: 'textureManager/mainResolveTexture',\n      size: { width: w, height: h },\n      format: targetFormat,\n      usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,\n    });\n    state.mainResolveView = state.mainResolveTexture.createView();\n\n    // Allocate MSAA overlay texture (multi-sample)\n    state.overlayMsaaTexture = device.createTexture({\n      label: 'textureManager/annotationOverlayMsaaTexture',\n      size: { width: w, height: h },\n      sampleCount: ANNOTATION_OVERLAY_MSAA_SAMPLE_COUNT,\n      format: targetFormat,\n      usage: GPUTextureUsage.RENDER_ATTACHMENT,\n    });\n    state.overlayMsaaView = state.overlayMsaaTexture.createView();\n\n    // Create bind group for blit pipeline — reads from the resolved (single-sample) texture.\n    state.overlayBlitBindGroup = device.createBindGroup({\n      label: 'textureManager/overlayBlitBindGroup',\n      layout: overlayBlitBindGroupLayout,\n      entries: [{ binding: 0, resource: state.mainResolveView }],\n    });\n\n    // Update cached dimensions and format\n    state.overlayTargetsWidth = w;\n    state.overlayTargetsHeight = h;\n    state.overlayTargetsFormat = targetFormat;\n\n    // Invalidate cached getState snapshot after reallocation.\n    cachedState = null;\n  }\n\n  // Cached getState snapshot — avoids per-frame object allocation.\n  // Invalidated only when ensureTextures() reallocates resources.\n  let cachedState: TextureManagerState | null = null;\n\n  /**\n   * Gets current texture manager state for rendering.\n   * Returns a cached object to avoid per-frame allocations.\n   * The cache is invalidated when ensureTextures() reallocates.\n   */\n  function getState(): TextureManagerState {\n    if (!cachedState) {\n      cachedState = {\n        mainColorView: state.mainColorView,\n        mainResolveView: state.mainResolveView,\n        overlayMsaaView: state.overlayMsaaView,\n        overlayBlitBindGroup: state.overlayBlitBindGroup,\n        overlayBlitPipeline,\n        msaaSampleCount: ANNOTATION_OVERLAY_MSAA_SAMPLE_COUNT,\n        mainSceneMsaaSampleCount: MAIN_SCENE_MSAA_SAMPLE_COUNT,\n      };\n    }\n    return cachedState;\n  }\n\n  /**\n   * Disposes all GPU resources.\n   */\n  function dispose(): void {\n    destroyTexture(state.mainColorTexture);\n    destroyTexture(state.mainResolveTexture);\n    destroyTexture(state.overlayMsaaTexture);\n\n    // Clear references\n    state.mainColorTexture = null;\n    state.mainColorView = null;\n    state.mainResolveTexture = null;\n    state.mainResolveView = null;\n    state.overlayMsaaTexture = null;\n    state.overlayMsaaView = null;\n    state.overlayBlitBindGroup = null;\n    state.overlayTargetsWidth = 0;\n    state.overlayTargetsHeight = 0;\n    state.overlayTargetsFormat = null;\n    cachedState = null;\n  }\n\n  return {\n    ensureTextures,\n    getState,\n    dispose,\n  };\n}\n","export default \"// crosshair.wgsl\\n// Minimal crosshair line shader:\\n// - Vertex input: vec2<f32> position in clip-space coordinates\\n// - VS uniform: transform mat4 (identity)\\n// - FS uniform: solid RGBA color\\n\\nstruct VSUniforms {\\n  transform: mat4x4<f32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;\\n\\nstruct FSUniforms {\\n  color: vec4<f32>,\\n};\\n\\n@group(0) @binding(1) var<uniform> fsUniforms: FSUniforms;\\n\\nstruct VSIn {\\n  @location(0) position: vec2<f32>,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition: vec4<f32>,\\n};\\n\\n@vertex\\nfn vsMain(in: VSIn) -> VSOut {\\n  var out: VSOut;\\n  out.clipPosition = vsUniforms.transform * vec4<f32>(in.position, 0.0, 1.0);\\n  return out;\\n}\\n\\n@fragment\\nfn fsMain() -> @location(0) vec4<f32> {\\n  return fsUniforms.color;\\n}\\n\\n\"","export interface StreamBuffer {\n  /**\n   * Writes a new vertex payload into the streaming buffer.\n   *\n   * Notes:\n   * - `data` is interpreted as interleaved `vec2<f32>` vertices: `[x0, y0, x1, y1, ...]`.\n   * - Uses double buffering (alternates GPU buffers each write) to avoid writing into the same\n   *   buffer the GPU might still be reading from the prior frame.\n   * - Uses a per-buffer CPU mirror (Uint32 bit patterns) to compute partial updates.\n   */\n  write(data: Float32Array): void;\n  /** Returns the GPUBuffer that contains the most recently written data. */\n  getBuffer(): GPUBuffer;\n  /** Returns the vertex count for the most recently written data. */\n  getVertexCount(): number;\n  /** Destroys GPU resources (best-effort). Safe to call multiple times. */\n  dispose(): void;\n}\n\nconst align4 = (n: number): number => (n + 3) & ~3;\n\n// Small payloads are cheaper to just full-write (avoid diff overhead).\nconst SMALL_FULL_WRITE_MAX_BYTES = 1024;\n\n// Heuristic guard against pathological alternating-word diffs which would produce many tiny ranges.\n// If exceeded, we do a single full-range write for the used bytes.\nconst MAX_DIFF_RANGES_BEFORE_FULL_WRITE = 128;\nconst MAX_CHANGED_WORDS_BEFORE_FULL_WRITE = 16_384;\n\nconst toU32View = (data: Float32Array): Uint32Array => {\n  if ((data.byteOffset & 3) !== 0) {\n    // This should never happen for Float32Array, but keep it explicit for correctness.\n    throw new Error('createStreamBuffer.write: data.byteOffset must be 4-byte aligned.');\n  }\n  return new Uint32Array(data.buffer, data.byteOffset, data.byteLength >>> 2);\n};\n\nexport function createStreamBuffer(device: GPUDevice, maxSize: number): StreamBuffer {\n  if (!Number.isFinite(maxSize) || maxSize <= 0) {\n    throw new Error(`createStreamBuffer(maxSize): maxSize (bytes) must be a positive number. Received: ${String(maxSize)}`);\n  }\n\n  const clamped = Math.max(4, Math.floor(maxSize));\n  const capacityBytes = align4(clamped);\n\n  const limit = device.limits.maxBufferSize;\n  if (capacityBytes > limit) {\n    throw new Error(\n      `createStreamBuffer(maxSize): requested size ${capacityBytes} bytes exceeds device.limits.maxBufferSize (${limit}).`\n    );\n  }\n\n  const capacityWords = capacityBytes >>> 2;\n\n  const createSlot = (label: string): { readonly buffer: GPUBuffer; readonly mirror: Uint32Array } => ({\n    buffer: device.createBuffer({\n      label,\n      size: capacityBytes,\n      usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n    }),\n    mirror: new Uint32Array(capacityWords),\n  });\n\n  const slots = [createSlot('streamBuffer/a'), createSlot('streamBuffer/b')] as const;\n\n  let disposed = false;\n  let currentIndex = 0; // getBuffer() returns slots[currentIndex]\n  let vertexCount = 0;\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('createStreamBuffer: StreamBuffer is disposed.');\n  };\n\n  const writeFull = (slotIndex: number, newWords: Uint32Array, usedWords: number): void => {\n    const slot = slots[slotIndex];\n    const mirror = slot.mirror;\n\n    if (usedWords < 0 || usedWords > newWords.length) {\n      throw new Error('createStreamBuffer.write: internal error (invalid usedWords).');\n    }\n    if (usedWords === 0) return;\n\n    const usedBytes = usedWords << 2;\n    device.queue.writeBuffer(slot.buffer, 0, newWords.buffer, newWords.byteOffset, usedBytes);\n    mirror.set(newWords.subarray(0, usedWords), 0);\n  };\n\n  const writeRangesByDiff = (slotIndex: number, newWords: Uint32Array, usedWords: number): void => {\n    const slot = slots[slotIndex];\n    const mirror = slot.mirror;\n\n    // Guard against programming errors.\n    if (usedWords < 0 || usedWords > newWords.length) {\n      throw new Error('createStreamBuffer.write: internal error (invalid usedWords).');\n    }\n\n    // Small-buffer fast path: diffing overhead dominates.\n    const usedBytes = usedWords << 2;\n    if (usedBytes > 0 && usedBytes <= SMALL_FULL_WRITE_MAX_BYTES) {\n      writeFull(slotIndex, newWords, usedWords);\n      return;\n    }\n\n    // First pass: collect ranges and decide whether we should fall back to a full write.\n    const ranges: Array<[start: number, end: number]> = [];\n    let rangeCount = 0;\n    let changedWords = 0;\n\n    let i = 0;\n    while (i < usedWords) {\n      // Find first differing word.\n      while (i < usedWords && mirror[i] === newWords[i]) i++;\n      if (i >= usedWords) break;\n\n      const start = i;\n      i++;\n      // Extend to contiguous run of differing words.\n      while (i < usedWords && mirror[i] !== newWords[i]) i++;\n      const end = i;\n\n      ranges.push([start, end]);\n      rangeCount++;\n      changedWords += end - start;\n\n      // Pathological case guard: alternating changes can create many tiny ranges.\n      if (rangeCount > MAX_DIFF_RANGES_BEFORE_FULL_WRITE || changedWords > MAX_CHANGED_WORDS_BEFORE_FULL_WRITE) {\n        writeFull(slotIndex, newWords, usedWords);\n        return;\n      }\n    }\n\n    // Second pass: apply range writes.\n    for (let r = 0; r < ranges.length; r++) {\n      const [start, end] = ranges[r];\n      const byteOffset = start << 2;\n      const byteSize = (end - start) << 2;\n\n      // WebGPU requires offsets/sizes to be multiples of 4 bytes (satisfied by word addressing).\n      device.queue.writeBuffer(slot.buffer, byteOffset, newWords.buffer, newWords.byteOffset + byteOffset, byteSize);\n      mirror.set(newWords.subarray(start, end), start);\n    }\n  };\n\n  const write: StreamBuffer['write'] = (data) => {\n    assertNotDisposed();\n\n    if (data.length & 1) {\n      throw new Error('createStreamBuffer.write: data length must be even (vec2<f32> vertices).');\n    }\n\n    const bytes = data.byteLength;\n    if (bytes > capacityBytes) {\n      throw new Error(\n        `createStreamBuffer.write: data.byteLength (${bytes}) exceeds capacity (${capacityBytes}). Increase maxSize.`\n      );\n    }\n\n    const nextVertexCount = data.length >>> 1;\n    if (bytes === 0) {\n      // Avoid swapping buffers for empty payloads.\n      vertexCount = nextVertexCount;\n      return;\n    }\n\n    const words = toU32View(data);\n    const nextIndex = 1 - currentIndex;\n\n    // Only swap after the write succeeds so we never expose a partially-updated \"current\" buffer.\n    writeRangesByDiff(nextIndex, words, words.length);\n    currentIndex = nextIndex;\n    vertexCount = nextVertexCount;\n  };\n\n  const getBuffer: StreamBuffer['getBuffer'] = () => {\n    assertNotDisposed();\n    return slots[currentIndex].buffer;\n  };\n\n  const getVertexCount: StreamBuffer['getVertexCount'] = () => {\n    assertNotDisposed();\n    return vertexCount;\n  };\n\n  const dispose: StreamBuffer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n    vertexCount = 0;\n\n    for (const slot of slots) {\n      try {\n        slot.buffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n  };\n\n  return { write, getBuffer, getVertexCount, dispose };\n}\n\n","import crosshairWgsl from '../shaders/crosshair.wgsl?raw';\nimport { createStreamBuffer } from '../data/createStreamBuffer';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport type { GridArea } from './createGridRenderer';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport interface CrosshairRenderOptions {\n  /** Whether to render the vertical crosshair line. */\n  readonly showX: boolean;\n  /** Whether to render the horizontal crosshair line. */\n  readonly showY: boolean;\n  /** CSS color string for the crosshair lines. */\n  readonly color: string;\n  /**\n   * Desired line width in CSS pixels.\n   *\n   * Note: WebGPU wide lines are not reliably supported; the renderer emulates thickness by\n   * drawing multiple 1px lines in device-pixel offsets (best-effort, deterministic).\n   */\n  readonly lineWidth: number;\n}\n\nexport interface CrosshairRenderer {\n  /**\n   * Positions the crosshair for rendering.\n   *\n   * Coordinate contract:\n   * - `x`, `y` are CANVAS-LOCAL CSS pixels (e.g. eventManager payload x/y)\n   * - `gridArea` margins are CSS pixels; `gridArea.canvasWidth/Height` are device pixels\n   */\n  prepare(x: number, y: number, gridArea: GridArea, options: CrosshairRenderOptions): void;\n  /** Draws the crosshair (if visible) clipped to the plot rect. */\n  render(passEncoder: GPURenderPassEncoder): void;\n  /** Shows/hides the crosshair without destroying GPU resources. */\n  setVisible(visible: boolean): void;\n  /** Cleans up GPU resources (best-effort). */\n  dispose(): void;\n}\n\nexport interface CrosshairRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_CROSSHAIR_RGBA: readonly [number, number, number, number] = [1, 1, 1, 0.8];\n\nconst MAX_THICKNESS_DEVICE_PX = 8;\nconst DASH_ON_DEVICE_PX = 6;\nconst DASH_OFF_DEVICE_PX = 4;\n\n// Hard cap to keep CPU-side dash segmentation inexpensive/deterministic.\nconst MAX_VERTICES = 8192; // vec2<f32> vertices, i.e. floats/2.\n\nconst createIdentityMat4Buffer = (): ArrayBuffer => {\n  // Column-major identity mat4x4\n  const buffer = new ArrayBuffer(16 * 4);\n  new Float32Array(buffer).set([\n    1, 0, 0, 0, // col0\n    0, 1, 0, 0, // col1\n    0, 0, 1, 0, // col2\n    0, 0, 0, 1, // col3\n  ]);\n  return buffer;\n};\n\nconst isFiniteGridArea = (gridArea: GridArea): boolean =>\n  Number.isFinite(gridArea.left) &&\n  Number.isFinite(gridArea.right) &&\n  Number.isFinite(gridArea.top) &&\n  Number.isFinite(gridArea.bottom) &&\n  Number.isFinite(gridArea.canvasWidth) &&\n  Number.isFinite(gridArea.canvasHeight);\n\nconst clampInt = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v | 0));\n\nconst computeThicknessOffsetsDevicePx = (lineWidthCssPx: number, dpr: number): readonly number[] => {\n  if (!Number.isFinite(lineWidthCssPx) || lineWidthCssPx < 0) {\n    throw new Error('CrosshairRenderer.prepare: lineWidth must be a finite non-negative number.');\n  }\n  if (lineWidthCssPx === 0) return [];\n\n  // Convert to device px, then clamp to a small deterministic maximum.\n  const widthDevicePx = lineWidthCssPx * dpr;\n  const thickness = Math.max(1, Math.min(MAX_THICKNESS_DEVICE_PX, Math.round(widthDevicePx)));\n\n  // Symmetric offsets around center (even thickness yields ±0.5 style offsets).\n  const mid = (thickness - 1) / 2;\n  const out: number[] = [];\n  for (let i = 0; i < thickness; i++) out.push(i - mid);\n  return out;\n};\n\nconst devicePxToClipX = (xDevicePx: number, canvasWidthDevicePx: number): number =>\n  (xDevicePx / canvasWidthDevicePx) * 2.0 - 1.0;\nconst devicePxToClipY = (yDevicePx: number, canvasHeightDevicePx: number): number =>\n  1.0 - (yDevicePx / canvasHeightDevicePx) * 2.0;\n\ntype Segment2D = readonly [x0: number, y0: number, x1: number, y1: number];\n\nconst appendSegmentVerticesClip = (out: number[], seg: Segment2D): void => {\n  out.push(seg[0], seg[1], seg[2], seg[3]);\n};\n\nconst generateDashedSegmentsAxisAligned = (start: number, end: number): readonly [number, number][] => {\n  // Returns a list of [a,b] segments in *device* space along a single axis.\n  if (!Number.isFinite(start) || !Number.isFinite(end)) return [];\n\n  const a0 = Math.min(start, end);\n  const a1 = Math.max(start, end);\n  if (a1 <= a0) return [];\n\n  const on = DASH_ON_DEVICE_PX;\n  const off = DASH_OFF_DEVICE_PX;\n  const period = on + off;\n  if (period <= 0 || !Number.isFinite(period)) return [];\n\n  // Conservative cap: if this many segments would exceed MAX_VERTICES after thickness expansion,\n  // the caller will fall back to a single solid segment.\n  const approxSegments = Math.ceil((a1 - a0) / period);\n  if (!Number.isFinite(approxSegments) || approxSegments <= 0) return [];\n\n  const segments: Array<[number, number]> = [];\n  let t = a0;\n  while (t < a1) {\n    const s0 = t;\n    const s1 = Math.min(t + on, a1);\n    if (s1 > s0) segments.push([s0, s1]);\n    t += period;\n  }\n  return segments;\n};\n\nconst generateCrosshairVertices = (\n  xCssPx: number,\n  yCssPx: number,\n  gridArea: GridArea,\n  options: CrosshairRenderOptions\n): {\n  readonly vertices: Float32Array;\n  readonly scissor: { readonly x: number; readonly y: number; readonly w: number; readonly h: number };\n} => {\n  if (!Number.isFinite(xCssPx) || !Number.isFinite(yCssPx)) {\n    throw new Error('CrosshairRenderer.prepare: x and y must be finite numbers.');\n  }\n  if (!isFiniteGridArea(gridArea)) {\n    throw new Error('CrosshairRenderer.prepare: gridArea dimensions must be finite numbers.');\n  }\n  if (gridArea.canvasWidth <= 0 || gridArea.canvasHeight <= 0) {\n    throw new Error('CrosshairRenderer.prepare: canvas dimensions must be positive.');\n  }\n  if (gridArea.left < 0 || gridArea.right < 0 || gridArea.top < 0 || gridArea.bottom < 0) {\n    throw new Error('CrosshairRenderer.prepare: gridArea margins must be non-negative.');\n  }\n\n  const { canvasWidth, canvasHeight } = gridArea;\n  // Be resilient: older call sites may omit/incorrectly pass DPR. Defaulting avoids hard crashes.\n  const devicePixelRatio =\n    Number.isFinite(gridArea.devicePixelRatio) && gridArea.devicePixelRatio > 0 ? gridArea.devicePixelRatio : 1;\n\n  const plotLeftDevice = gridArea.left * devicePixelRatio;\n  const plotRightDevice = canvasWidth - gridArea.right * devicePixelRatio;\n  const plotTopDevice = gridArea.top * devicePixelRatio;\n  const plotBottomDevice = canvasHeight - gridArea.bottom * devicePixelRatio;\n\n  const scissorX = clampInt(Math.floor(plotLeftDevice), 0, Math.max(0, canvasWidth));\n  const scissorY = clampInt(Math.floor(plotTopDevice), 0, Math.max(0, canvasHeight));\n  const scissorR = clampInt(Math.ceil(plotRightDevice), 0, Math.max(0, canvasWidth));\n  const scissorB = clampInt(Math.ceil(plotBottomDevice), 0, Math.max(0, canvasHeight));\n  const scissorW = Math.max(0, scissorR - scissorX);\n  const scissorH = Math.max(0, scissorB - scissorY);\n\n  // Convert the requested position from CSS px (canvas-local) to device px.\n  const xDevice = xCssPx * devicePixelRatio;\n  const yDevice = yCssPx * devicePixelRatio;\n\n  const thicknessOffsets = computeThicknessOffsetsDevicePx(options.lineWidth, devicePixelRatio);\n  if (thicknessOffsets.length === 0 || (!options.showX && !options.showY)) {\n    return {\n      vertices: new Float32Array(0),\n      scissor: { x: scissorX, y: scissorY, w: scissorW, h: scissorH },\n    };\n  }\n\n  const floats: number[] = [];\n\n  // Compute how many dashed segments we *might* generate and fall back to solid if too many.\n  const dashSegmentsY = options.showX ? generateDashedSegmentsAxisAligned(plotTopDevice, plotBottomDevice) : [];\n  const dashSegmentsX = options.showY ? generateDashedSegmentsAxisAligned(plotLeftDevice, plotRightDevice) : [];\n\n  const segmentsPerThickness =\n    (options.showX ? dashSegmentsY.length : 0) + (options.showY ? dashSegmentsX.length : 0);\n  const projectedVertexCount = segmentsPerThickness * thicknessOffsets.length * 2; // 2 vertices per segment\n\n  const useDashed = projectedVertexCount > 0 && projectedVertexCount <= MAX_VERTICES;\n\n  const addVerticalSolid = (xDevicePx: number): void => {\n    const xClip = devicePxToClipX(xDevicePx, canvasWidth);\n    const y0 = devicePxToClipY(plotTopDevice, canvasHeight);\n    const y1 = devicePxToClipY(plotBottomDevice, canvasHeight);\n    appendSegmentVerticesClip(floats, [xClip, y0, xClip, y1]);\n  };\n\n  const addHorizontalSolid = (yDevicePx: number): void => {\n    const yClip = devicePxToClipY(yDevicePx, canvasHeight);\n    const x0 = devicePxToClipX(plotLeftDevice, canvasWidth);\n    const x1 = devicePxToClipX(plotRightDevice, canvasWidth);\n    appendSegmentVerticesClip(floats, [x0, yClip, x1, yClip]);\n  };\n\n  if (options.showX) {\n    for (let i = 0; i < thicknessOffsets.length; i++) {\n      const xd = xDevice + thicknessOffsets[i];\n      if (!useDashed) {\n        addVerticalSolid(xd);\n        continue;\n      }\n\n      const xClip = devicePxToClipX(xd, canvasWidth);\n      for (let s = 0; s < dashSegmentsY.length; s++) {\n        const [ya, yb] = dashSegmentsY[s];\n        const y0 = devicePxToClipY(ya, canvasHeight);\n        const y1 = devicePxToClipY(yb, canvasHeight);\n        appendSegmentVerticesClip(floats, [xClip, y0, xClip, y1]);\n      }\n    }\n  }\n\n  if (options.showY) {\n    for (let i = 0; i < thicknessOffsets.length; i++) {\n      const yd = yDevice + thicknessOffsets[i];\n      if (!useDashed) {\n        addHorizontalSolid(yd);\n        continue;\n      }\n\n      const yClip = devicePxToClipY(yd, canvasHeight);\n      for (let s = 0; s < dashSegmentsX.length; s++) {\n        const [xa, xb] = dashSegmentsX[s];\n        const x0 = devicePxToClipX(xa, canvasWidth);\n        const x1 = devicePxToClipX(xb, canvasWidth);\n        appendSegmentVerticesClip(floats, [x0, yClip, x1, yClip]);\n      }\n    }\n  }\n\n  const vertices = new Float32Array(floats);\n  return { vertices, scissor: { x: scissorX, y: scissorY, w: scissorW, h: scissorH } };\n};\n\nexport function createCrosshairRenderer(device: GPUDevice, options?: CrosshairRendererOptions): CrosshairRenderer {\n  let disposed = false;\n  let visible = true;\n\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [\n      { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },\n      { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },\n    ],\n  });\n\n  const vsUniformBuffer = createUniformBuffer(device, 64, { label: 'crosshairRenderer/vsUniforms' });\n  const fsUniformBuffer = createUniformBuffer(device, 16, { label: 'crosshairRenderer/fsUniforms' });\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [\n      { binding: 0, resource: { buffer: vsUniformBuffer } },\n      { binding: 1, resource: { buffer: fsUniformBuffer } },\n    ],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'crosshairRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: crosshairWgsl,\n        label: 'crosshair.wgsl',\n        buffers: [\n          {\n            arrayStride: 8,\n            stepMode: 'vertex',\n            attributes: [{ shaderLocation: 0, format: 'float32x2', offset: 0 }],\n          },\n        ],\n      },\n      fragment: {\n        code: crosshairWgsl,\n        label: 'crosshair.wgsl',\n        formats: targetFormat,\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'line-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  const stream = createStreamBuffer(device, MAX_VERTICES * 8);\n  let vertexCount = 0;\n  let lastCanvasWidth = 0;\n  let lastCanvasHeight = 0;\n  let lastScissor = { x: 0, y: 0, w: 0, h: 0 };\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('CrosshairRenderer is disposed.');\n  };\n\n  const prepare: CrosshairRenderer['prepare'] = (x, y, gridArea, renderOptions) => {\n    assertNotDisposed();\n\n    // Validate options up-front for deterministic behavior.\n    if (typeof renderOptions.showX !== 'boolean' || typeof renderOptions.showY !== 'boolean') {\n      throw new Error('CrosshairRenderer.prepare: showX/showY must be boolean.');\n    }\n    if (typeof renderOptions.color !== 'string') {\n      throw new Error('CrosshairRenderer.prepare: color must be a string.');\n    }\n    if (!Number.isFinite(renderOptions.lineWidth) || renderOptions.lineWidth < 0) {\n      throw new Error('CrosshairRenderer.prepare: lineWidth must be a finite non-negative number.');\n    }\n\n    const { vertices, scissor } = generateCrosshairVertices(x, y, gridArea, renderOptions);\n    if (vertices.byteLength === 0) {\n      vertexCount = 0;\n    } else {\n      stream.write(vertices);\n      vertexCount = stream.getVertexCount();\n    }\n\n    // Identity transform (vertices are already in clip-space).\n    writeUniformBuffer(device, vsUniformBuffer, createIdentityMat4Buffer());\n\n    // Color.\n    const rgba = parseCssColorToRgba01(renderOptions.color) ?? DEFAULT_CROSSHAIR_RGBA;\n    const colorBuffer = new ArrayBuffer(4 * 4);\n    new Float32Array(colorBuffer).set([rgba[0], rgba[1], rgba[2], rgba[3]]);\n    writeUniformBuffer(device, fsUniformBuffer, colorBuffer);\n\n    lastCanvasWidth = gridArea.canvasWidth;\n    lastCanvasHeight = gridArea.canvasHeight;\n    lastScissor = scissor;\n  };\n\n  const render: CrosshairRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (!visible) return;\n    if (vertexCount === 0) return;\n    if (lastCanvasWidth <= 0 || lastCanvasHeight <= 0) return;\n\n    // Clip to plot area (device pixels).\n    passEncoder.setScissorRect(lastScissor.x, lastScissor.y, lastScissor.w, lastScissor.h);\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n    passEncoder.setVertexBuffer(0, stream.getBuffer());\n    passEncoder.draw(vertexCount);\n\n    // Reset scissor to full canvas (avoid affecting subsequent renderers).\n    passEncoder.setScissorRect(0, 0, lastCanvasWidth, lastCanvasHeight);\n  };\n\n  const setVisible: CrosshairRenderer['setVisible'] = (v) => {\n    assertNotDisposed();\n    visible = Boolean(v);\n  };\n\n  const dispose: CrosshairRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    try {\n      fsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    stream.dispose();\n\n    vertexCount = 0;\n    lastCanvasWidth = 0;\n    lastCanvasHeight = 0;\n    lastScissor = { x: 0, y: 0, w: 0, h: 0 };\n  };\n\n  return { prepare, render, setVisible, dispose };\n}\n\n","export default \"// highlight.wgsl\\n// Draws an anti-aliased ring highlight around a point.\\n//\\n// Contract:\\n// - `@builtin(position)` in the fragment stage is framebuffer-space pixels.\\n// - The renderer supplies `center` and ring sizes in *device pixels*.\\n\\nstruct Uniforms {\\n  center: vec2<f32>,\\n  radius: f32,\\n  thickness: f32,\\n  color: vec4<f32>,\\n  outlineColor: vec4<f32>,\\n};\\n\\n@group(0) @binding(0) var<uniform> u: Uniforms;\\n\\nstruct VSOut {\\n  @builtin(position) position: vec4<f32>,\\n};\\n\\n@vertex\\nfn vsMain(@builtin(vertex_index) vertexIndex: u32) -> VSOut {\\n  // Fullscreen triangle.\\n  // Covers clip-space [-1,1] with 3 verts: (-1,-1), (3,-1), (-1,3)\\n  let positions = array<vec2<f32>, 3>(\\n    vec2<f32>(-1.0, -1.0),\\n    vec2<f32>(3.0, -1.0),\\n    vec2<f32>(-1.0, 3.0)\\n  );\\n\\n  var out: VSOut;\\n  out.position = vec4<f32>(positions[vertexIndex], 0.0, 1.0);\\n  return out;\\n}\\n\\nfn ringCoverage(distancePx: f32, radiusPx: f32, thicknessPx: f32) -> f32 {\\n  let aa = 1.0; // ~1px antialias band (device pixels)\\n  let halfT = max(0.5, thicknessPx * 0.5);\\n  let a0 = smoothstep(radiusPx - halfT - aa, radiusPx - halfT + aa, distancePx);\\n  let a1 = smoothstep(radiusPx + halfT - aa, radiusPx + halfT + aa, distancePx);\\n  return clamp(a0 - a1, 0.0, 1.0);\\n}\\n\\n@fragment\\nfn fsMain(@builtin(position) fragPos: vec4<f32>) -> @location(0) vec4<f32> {\\n  let d = distance(fragPos.xy, u.center);\\n\\n  let ring = ringCoverage(d, u.radius, u.thickness);\\n  let outline = ringCoverage(d, u.radius, u.thickness + 2.0);\\n\\n  let cover = max(ring, outline);\\n  if (cover <= 0.0) {\\n    discard;\\n  }\\n\\n  // Blend between outline and ring color based on relative coverage,\\n  // then apply total coverage as alpha.\\n  let t = clamp(select(0.0, ring / cover, cover > 0.0), 0.0, 1.0);\\n  let rgb = mix(u.outlineColor.rgb, u.color.rgb, t);\\n  let a = mix(u.outlineColor.a, u.color.a, t) * cover;\\n  return vec4<f32>(rgb, a);\\n}\\n\\n\"","import highlightWgsl from '../shaders/highlight.wgsl?raw';\nimport { parseCssColorToRgba01 } from '../utils/colors';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport type HighlightPoint = Readonly<{\n  /** Center in *device pixels* (same coordinate space as fragment `@builtin(position)`). */\n  centerDeviceX: number;\n  centerDeviceY: number;\n\n  /** Device pixel ratio used for CSS→device conversion. */\n  devicePixelRatio: number;\n\n  /** Canvas dimensions in *device pixels* (used to reset scissor). */\n  canvasWidth: number;\n  canvasHeight: number;\n\n  /** Plot scissor rect in *device pixels*. */\n  scissor: Readonly<{ x: number; y: number; w: number; h: number }>;\n}>;\n\nexport interface HighlightRenderer {\n  /**\n   * Prepares the highlight ring.\n   *\n   * Coordinate contract:\n   * - `point.centerDeviceX/Y` are device pixels in the same space as fragment `@builtin(position)`.\n   * - `size` is specified in CSS pixels; the renderer will scale it by `point.devicePixelRatio`.\n   */\n  prepare(point: HighlightPoint, color: string, size: number): void;\n  render(passEncoder: GPURenderPassEncoder): void;\n  setVisible(visible: boolean): void;\n  dispose(): void;\n}\n\nexport interface HighlightRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_RGBA: readonly [number, number, number, number] = [1, 1, 1, 1];\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst clampInt = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v | 0));\n\nconst isFiniteScissor = (s: HighlightPoint['scissor']): boolean =>\n  Number.isFinite(s.x) && Number.isFinite(s.y) && Number.isFinite(s.w) && Number.isFinite(s.h);\n\nconst brighten = (rgba: readonly [number, number, number, number], factor: number): readonly [number, number, number, number] => {\n  const f = Number.isFinite(factor) ? factor : 1;\n  return [clamp01(rgba[0] * f), clamp01(rgba[1] * f), clamp01(rgba[2] * f), clamp01(rgba[3])] as const;\n};\n\nconst luminance = (rgba: readonly [number, number, number, number]): number =>\n  0.2126 * rgba[0] + 0.7152 * rgba[1] + 0.0722 * rgba[2];\n\nexport function createHighlightRenderer(device: GPUDevice, options?: HighlightRendererOptions): HighlightRenderer {\n  let disposed = false;\n  let visible = true;\n\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } }],\n  });\n\n  // Uniform layout (WGSL):\n  // center.xy, radius, thickness, color.rgba, outlineColor.rgba\n  // = 12 floats = 48 bytes\n  const uniformBuffer = createUniformBuffer(device, 48, { label: 'highlightRenderer/uniforms' });\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'highlightRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: { code: highlightWgsl, label: 'highlight.wgsl' },\n      fragment: {\n        code: highlightWgsl,\n        label: 'highlight.wgsl',\n        formats: targetFormat,\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let lastCanvasWidth = 0;\n  let lastCanvasHeight = 0;\n  let lastScissor = { x: 0, y: 0, w: 0, h: 0 };\n  let hasPrepared = false;\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('HighlightRenderer is disposed.');\n  };\n\n  const prepare: HighlightRenderer['prepare'] = (point, cssColor, sizeCssPx) => {\n    assertNotDisposed();\n\n    if (!Number.isFinite(point.centerDeviceX) || !Number.isFinite(point.centerDeviceY)) {\n      throw new Error('HighlightRenderer.prepare: point center must be finite.');\n    }\n    if (!Number.isFinite(point.canvasWidth) || !Number.isFinite(point.canvasHeight) || point.canvasWidth <= 0 || point.canvasHeight <= 0) {\n      throw new Error('HighlightRenderer.prepare: canvasWidth/canvasHeight must be positive finite numbers.');\n    }\n    if (!isFiniteScissor(point.scissor)) {\n      throw new Error('HighlightRenderer.prepare: scissor must be finite.');\n    }\n    if (!Number.isFinite(sizeCssPx) || sizeCssPx < 0) {\n      throw new Error('HighlightRenderer.prepare: size must be a finite non-negative number.');\n    }\n\n    const dprRaw = point.devicePixelRatio;\n    const dpr = Number.isFinite(dprRaw) && dprRaw > 0 ? dprRaw : 1;\n    const baseRadiusDevicePx = sizeCssPx * dpr;\n\n    // Slightly larger than the implied \"normal\" point size.\n    const radius = Math.max(1, baseRadiusDevicePx * 1.5);\n    const thickness = Math.max(1, Math.round(Math.max(2, radius * 0.25)));\n\n    const seriesRgba = parseCssColorToRgba01(cssColor) ?? DEFAULT_RGBA;\n    const ringRgba = brighten(seriesRgba, 1.25);\n    const useDarkOutline = luminance(seriesRgba) > 0.7;\n    const outlineRgba: readonly [number, number, number, number] = useDarkOutline ? [0, 0, 0, 0.9] : [1, 1, 1, 0.9];\n\n    const buf = new ArrayBuffer(12 * 4);\n    new Float32Array(buf).set([\n      point.centerDeviceX,\n      point.centerDeviceY,\n      radius,\n      thickness,\n      ringRgba[0],\n      ringRgba[1],\n      ringRgba[2],\n      1.0,\n      outlineRgba[0],\n      outlineRgba[1],\n      outlineRgba[2],\n      outlineRgba[3],\n    ]);\n    writeUniformBuffer(device, uniformBuffer, buf);\n\n    lastCanvasWidth = point.canvasWidth;\n    lastCanvasHeight = point.canvasHeight;\n\n    // Clamp scissor to valid canvas bounds (defensive).\n    const x0 = clampInt(Math.floor(point.scissor.x), 0, Math.max(0, point.canvasWidth));\n    const y0 = clampInt(Math.floor(point.scissor.y), 0, Math.max(0, point.canvasHeight));\n    const x1 = clampInt(Math.ceil(point.scissor.x + point.scissor.w), 0, Math.max(0, point.canvasWidth));\n    const y1 = clampInt(Math.ceil(point.scissor.y + point.scissor.h), 0, Math.max(0, point.canvasHeight));\n    lastScissor = { x: x0, y: y0, w: Math.max(0, x1 - x0), h: Math.max(0, y1 - y0) };\n\n    hasPrepared = true;\n  };\n\n  const render: HighlightRenderer['render'] = (passEncoder) => {\n    assertNotDisposed();\n    if (!visible) return;\n    if (!hasPrepared) return;\n    if (lastCanvasWidth <= 0 || lastCanvasHeight <= 0) return;\n    if (lastScissor.w === 0 || lastScissor.h === 0) return;\n\n    passEncoder.setScissorRect(lastScissor.x, lastScissor.y, lastScissor.w, lastScissor.h);\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n    passEncoder.draw(3);\n    passEncoder.setScissorRect(0, 0, lastCanvasWidth, lastCanvasHeight);\n  };\n\n  const setVisible: HighlightRenderer['setVisible'] = (v) => {\n    assertNotDisposed();\n    visible = Boolean(v);\n  };\n\n  const dispose: HighlightRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      uniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n\n    lastCanvasWidth = 0;\n    lastCanvasHeight = 0;\n    lastScissor = { x: 0, y: 0, w: 0, h: 0 };\n    hasPrepared = false;\n  };\n\n  return { prepare, render, setVisible, dispose };\n}\n\n","export default \"// Reference line renderer (axis-aligned, instanced quads).\\n//\\n// Coordinate conventions:\\n// - Instance position is provided in CANVAS-LOCAL CSS pixels (same coordinate space as pointer events).\\n// - Plot rect is provided in DEVICE pixels (computed from grid margins + DPR).\\n// - Line width and dash lengths are provided in CSS pixels and converted in-shader using DPR.\\n//\\n// Scissoring/clipping:\\n// - The render coordinator is expected to set a scissor rect for the plot area before drawing.\\n// - This shader simply draws full-height/full-width quads; clipping is handled by scissor.\\n//\\n// Dash semantics:\\n// - lineDash is a repeating on/off sequence in CSS pixels, starting with \\\"on\\\" at t=0.\\n// - Up to 8 dash entries are supported per line (truncated on CPU).\\n//\\n// Performance:\\n// - Vertex stage expands each instance into a quad (2 triangles, 6 vertices).\\n// - We intentionally avoid snapping to integer device pixels to prevent visible stepping/jiggle\\n//   while zooming; edge AA is handled in the fragment stage.\\n\\nstruct VSUniforms {\\n  canvasSize : vec2<f32>,     // device pixels (canvas.width, canvas.height)\\n  plotOrigin : vec2<f32>,     // device pixels (plotLeft, plotTop)\\n  plotSize : vec2<f32>,       // device pixels (plotWidth, plotHeight)\\n  devicePixelRatio : f32,\\n  _pad0 : f32,\\n};\\n\\n@group(0) @binding(0) var<uniform> u : VSUniforms;\\n\\nstruct VSIn {\\n  // axisPos.x = axis (0 = vertical, 1 = horizontal)\\n  // axisPos.y = position in CANVAS-LOCAL CSS pixels (x for vertical, y for horizontal)\\n  @location(0) axisPos : vec2<f32>,\\n\\n  // widthDashCount.x = lineWidth in CSS px\\n  // widthDashCount.y = dashCount (float, cast to u32)\\n  @location(1) widthDashCount : vec2<f32>,\\n\\n  // dashMeta.x = dashTotal (CSS px)\\n  // dashMeta.y = reserved (unused)\\n  @location(2) dashMeta : vec2<f32>,\\n\\n  @location(3) dash0_3 : vec4<f32>,\\n  @location(4) dash4_7 : vec4<f32>,\\n\\n  // Premultiplied or straight alpha is fine; blending is handled by pipeline state.\\n  @location(5) color : vec4<f32>,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) position : vec4<f32>,\\n\\n  // Distance along the line in CSS pixels (0..plotLengthCss).\\n  @location(0) alongCss : f32,\\n\\n  // Packed dash metadata to avoid extra varyings.\\n  // dashInfo.x = dashCount (float, cast to u32)\\n  // dashInfo.y = dashTotal (CSS px)\\n  @location(1) @interpolate(flat) dashInfo : vec2<f32>,\\n\\n  @location(2) @interpolate(flat) dash0_3 : vec4<f32>,\\n  @location(3) @interpolate(flat) dash4_7 : vec4<f32>,\\n  @location(4) @interpolate(flat) color : vec4<f32>,\\n\\n  // Axis-aligned quad anti-aliasing (device pixels).\\n  // acrossDevice ranges [0..widthDevice] across the stroke thickness.\\n  @location(5) acrossDevice : f32,\\n  @location(6) @interpolate(flat) widthDevice : f32,\\n};\\n\\nfn quadUv(vid : u32) -> vec2<f32> {\\n  // Two triangles covering [0,1]x[0,1].\\n  // 0: (0,0) 1:(1,0) 2:(0,1) 3:(0,1) 4:(1,0) 5:(1,1)\\n  switch (vid) {\\n    case 0u: { return vec2<f32>(0.0, 0.0); }\\n    case 1u: { return vec2<f32>(1.0, 0.0); }\\n    case 2u: { return vec2<f32>(0.0, 1.0); }\\n    case 3u: { return vec2<f32>(0.0, 1.0); }\\n    case 4u: { return vec2<f32>(1.0, 0.0); }\\n    default: { return vec2<f32>(1.0, 1.0); }\\n  }\\n}\\n\\n@vertex\\nfn vsMain(in : VSIn, @builtin(vertex_index) vid : u32) -> VSOut {\\n  let uv = quadUv(vid);\\n  let dpr = max(1e-6, u.devicePixelRatio);\\n  // IMPORTANT: Do NOT snap reference lines to integer device pixels.\\n  // Snapping looks crisp at rest but causes visible \\\"jiggle\\\" / stepping while zooming because\\n  // the line position is continuously changing (data-space → screen-space), and rounding\\n  // quantizes that motion to adjacent pixels. We rely on analytic AA in the fragment stage\\n  // to keep strokes stable and reasonably crisp across DPRs.\\n\\n  let axis = in.axisPos.x;\\n  let posCss = in.axisPos.y;\\n  let widthCss = max(0.0, in.widthDashCount.x);\\n  let widthDevice = max(1.0, widthCss * dpr);\\n\\n  var xDevice : f32;\\n  var yDevice : f32;\\n  var alongCss : f32;\\n  var acrossDevice : f32;\\n\\n  if (axis < 0.5) {\\n    // Vertical line at x = posCss (canvas-local CSS px), spanning plot height.\\n    let centerX = posCss * dpr;\\n    let startX = centerX - 0.5 * widthDevice;\\n    xDevice = startX + uv.x * widthDevice;\\n    yDevice = u.plotOrigin.y + uv.y * u.plotSize.y;\\n    alongCss = (uv.y * u.plotSize.y) / dpr;\\n    acrossDevice = uv.x * widthDevice;\\n  } else {\\n    // Horizontal line at y = posCss (canvas-local CSS px), spanning plot width.\\n    let centerY = posCss * dpr;\\n    let startY = centerY - 0.5 * widthDevice;\\n    xDevice = u.plotOrigin.x + uv.x * u.plotSize.x;\\n    yDevice = startY + uv.y * widthDevice;\\n    alongCss = (uv.x * u.plotSize.x) / dpr;\\n    acrossDevice = uv.y * widthDevice;\\n  }\\n\\n  let clipX = (xDevice / u.canvasSize.x) * 2.0 - 1.0;\\n  let clipY = 1.0 - (yDevice / u.canvasSize.y) * 2.0;\\n\\n  var out : VSOut;\\n  out.position = vec4<f32>(clipX, clipY, 0.0, 1.0);\\n  out.alongCss = alongCss;\\n  out.dashInfo = vec2<f32>(in.widthDashCount.y, in.dashMeta.x);\\n  out.dash0_3 = in.dash0_3;\\n  out.dash4_7 = in.dash4_7;\\n  out.color = in.color;\\n  out.acrossDevice = acrossDevice;\\n  out.widthDevice = widthDevice;\\n  return out;\\n}\\n\\nfn dashValue(i : u32, d0 : vec4<f32>, d1 : vec4<f32>) -> f32 {\\n  switch (i) {\\n    case 0u: { return d0.x; }\\n    case 1u: { return d0.y; }\\n    case 2u: { return d0.z; }\\n    case 3u: { return d0.w; }\\n    case 4u: { return d1.x; }\\n    case 5u: { return d1.y; }\\n    case 6u: { return d1.z; }\\n    default: { return d1.w; }\\n  }\\n}\\n\\n@fragment\\nfn fsMain(in : VSOut) -> @location(0) vec4<f32> {\\n  // Analytic edge anti-aliasing for axis-aligned quads (reduces shimmering during zoom).\\n  // This is a lightweight alternative to full MSAA for thin strokes.\\n  let edgeDist = min(in.acrossDevice, in.widthDevice - in.acrossDevice);\\n  // Slightly widen AA to reduce temporal shimmer on moving 1-2px strokes.\\n  // Keep conservative so lines remain reasonably crisp.\\n  let aa = max(fwidth(in.acrossDevice), 1e-3) * 1.25;\\n  let edgeCoverage = smoothstep(0.0, aa, edgeDist);\\n  var color = in.color;\\n  color.a = color.a * edgeCoverage;\\n\\n  let dashCount = u32(round(in.dashInfo.x));\\n  let dashTotal = in.dashInfo.y;\\n\\n  // IMPORTANT: derivative ops (fwidth) must execute in uniform control flow.\\n  // So compute the dash parameterization unconditionally (using a safe total) BEFORE any early-return.\\n  let dashTotalSafe = max(dashTotal, 1.0);\\n  let t = in.alongCss - floor(in.alongCss / dashTotalSafe) * dashTotalSafe;\\n  // Anti-alias dash edges along the line axis (CSS pixels).\\n  // This reduces shimmer during zoom for dashed reference lines without requiring MSAA.\\n  let dashAa = max(fwidth(t), 1e-3);\\n\\n  // Solid line (no dash pattern).\\n  if (dashCount == 0u || dashTotal <= 0.0) {\\n    return color;\\n  }\\n\\n  var acc = 0.0;\\n  var on = true;\\n\\n  for (var i : u32 = 0u; i < 8u; i = i + 1u) {\\n    if (i >= dashCount) { break; }\\n    let seg = dashValue(i, in.dash0_3, in.dash4_7);\\n    if (seg <= 0.0) { continue; }\\n\\n    if (t < acc + seg) {\\n      // IMPORTANT: Avoid `discard` for off segments.\\n      // Discard can cause temporal popping on moving dashed edges; prefer a smooth alpha mask.\\n      //\\n      // Fade in/out near dash boundaries for smooth edges. This produces coverage in [0..1]\\n      // within the current segment, going to 0 at segment boundaries.\\n      let inFromStart = smoothstep(0.0, dashAa, t - acc);\\n      let inFromEnd = smoothstep(0.0, dashAa, (acc + seg) - t);\\n      let segCoverage = min(inFromStart, inFromEnd);\\n\\n      // On segments contribute alpha; off segments contribute 0 alpha (no discard).\\n      let dashMask = select(0.0, segCoverage, on);\\n      color.a = color.a * dashMask;\\n      return color;\\n    }\\n\\n    acc = acc + seg;\\n    on = !on;\\n  }\\n\\n  // Defensive fallback if the dash list is degenerate.\\n  // If we didn't find a segment (shouldn't happen), default to transparent (safer than solid).\\n  color.a = 0.0;\\n  return color;\\n}\\n\"","import referenceLineWgsl from '../shaders/referenceLine.wgsl?raw';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport type { GridArea } from './createGridRenderer';\nimport type { PipelineCache } from '../core/PipelineCache';\n\n/**\n * Maximum dash entries supported per line instance.\n *\n * WGSL requires fixed-size arrays/varyings; we cap and truncate for deterministic perf.\n * If you need more, increase this (and update the shader accordingly).\n */\nconst MAX_DASH_VALUES = 8;\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\n\nexport type ReferenceLineAxis = 'vertical' | 'horizontal';\n\nexport interface ReferenceLineInstance {\n  /**\n   * Axis alignment.\n   * - `'vertical'`: a line spanning the plot height at a fixed X position\n   * - `'horizontal'`: a line spanning the plot width at a fixed Y position\n   */\n  readonly axis: ReferenceLineAxis;\n\n  /**\n   * Position in **CANVAS-LOCAL CSS pixels**.\n   *\n   * This is the same coordinate space as pointer event payloads:\n   * - For vertical lines: canvas-local X in CSS px\n   * - For horizontal lines: canvas-local Y in CSS px\n   *\n   * The shader converts CSS px → device px using DPR and relies on analytic AA for stable\n   * strokes during zoom (no integer device-pixel snapping).\n   */\n  readonly positionCssPx: number;\n\n  /**\n   * Desired line width in **CSS pixels**.\n   *\n   * The renderer emulates thickness using a quad (two triangles) and converts CSS px to\n   * device px using `gridArea.devicePixelRatio`.\n   */\n  readonly lineWidth: number;\n\n  /**\n   * Dash pattern in **CSS pixels**, matching the semantics of Canvas2D/SVG:\n   * `[dash, gap, dash, gap, ...]`, repeating, starting with an \"on\" dash.\n   *\n   * - `undefined` / `[]` renders a solid line.\n   * - Non-finite / non-positive entries are ignored.\n   * - If the list length is odd, it is duplicated (CSS behavior) before truncation.\n   * - The pattern is truncated to `MAX_DASH_VALUES`.\n   */\n  readonly lineDash?: ReadonlyArray<number>;\n\n  /**\n   * Line color as RGBA in 0..1.\n   *\n   * `rgba[3]` is the final opacity (i.e. you can pre-multiply any \"opacity\" control into alpha).\n   */\n  readonly rgba: readonly [number, number, number, number];\n}\n\nexport interface ReferenceLineRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\nexport interface ReferenceLineRenderer {\n  /**\n   * Prepares GPU buffers and uniforms for drawing.\n   *\n   * Coordinate contract:\n   * - Line positions are CANVAS-LOCAL CSS pixels.\n   * - `gridArea` margins are CSS pixels; `gridArea.canvasWidth/Height` are device pixels.\n   */\n  prepare(gridArea: GridArea, lines: ReadonlyArray<ReferenceLineInstance>): void;\n  /**\n   * Draws all prepared reference lines.\n   *\n   * Important: This renderer does NOT set scissor state. The render coordinator is expected\n   * to set a scissor rect for the plot area before calling `render()`.\n   */\n  render(passEncoder: GPURenderPassEncoder, firstInstance?: number, instanceCount?: number): void;\n  /** Cleans up GPU resources (best-effort). */\n  dispose(): void;\n}\n\nconst isFiniteGridArea = (gridArea: GridArea): boolean =>\n  Number.isFinite(gridArea.left) &&\n  Number.isFinite(gridArea.right) &&\n  Number.isFinite(gridArea.top) &&\n  Number.isFinite(gridArea.bottom) &&\n  Number.isFinite(gridArea.canvasWidth) &&\n  Number.isFinite(gridArea.canvasHeight);\n\ntype PackedDash = {\n  readonly dashCount: number;\n  readonly dashTotal: number;\n  readonly values: readonly number[]; // length MAX_DASH_VALUES\n};\n\nconst normalizeDash = (lineDash?: ReadonlyArray<number>): PackedDash => {\n  if (!lineDash || lineDash.length === 0) {\n    return { dashCount: 0, dashTotal: 0, values: new Array<number>(MAX_DASH_VALUES).fill(0) };\n  }\n\n  const cleaned: number[] = [];\n  for (let i = 0; i < lineDash.length; i++) {\n    const v = lineDash[i];\n    if (typeof v === 'number' && Number.isFinite(v) && v > 0) cleaned.push(v);\n  }\n\n  if (cleaned.length === 0) {\n    return { dashCount: 0, dashTotal: 0, values: new Array<number>(MAX_DASH_VALUES).fill(0) };\n  }\n\n  // CSS behavior: odd-length dash arrays are repeated to make even length.\n  const normalized = cleaned.length % 2 === 1 ? cleaned.concat(cleaned) : cleaned;\n\n  const dashCount = Math.min(MAX_DASH_VALUES, normalized.length);\n  const values = new Array<number>(MAX_DASH_VALUES).fill(0);\n  let dashTotal = 0;\n  for (let i = 0; i < dashCount; i++) {\n    values[i] = normalized[i];\n    dashTotal += normalized[i];\n  }\n\n  if (!Number.isFinite(dashTotal) || dashTotal <= 0) {\n    return { dashCount: 0, dashTotal: 0, values: new Array<number>(MAX_DASH_VALUES).fill(0) };\n  }\n\n  return { dashCount, dashTotal, values };\n};\n\nexport function createReferenceLineRenderer(device: GPUDevice, options?: ReferenceLineRendererOptions): ReferenceLineRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } }],\n  });\n\n  // VSUniforms:\n  // - canvasSize (vec2f)\n  // - plotOrigin (vec2f)\n  // - plotSize (vec2f)\n  // - devicePixelRatio (f32)\n  // - pad (f32)\n  const vsUniformBuffer = createUniformBuffer(device, 32, { label: 'referenceLineRenderer/vsUniforms' });\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [{ binding: 0, resource: { buffer: vsUniformBuffer } }],\n  });\n\n  const INSTANCE_STRIDE_BYTES = 72;\n  const INSTANCE_STRIDE_FLOATS = INSTANCE_STRIDE_BYTES / 4;\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'referenceLineRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: referenceLineWgsl,\n        label: 'referenceLine.wgsl',\n        buffers: [\n          {\n            arrayStride: INSTANCE_STRIDE_BYTES,\n            stepMode: 'instance',\n            attributes: [\n              { shaderLocation: 0, format: 'float32x2', offset: 0 }, // axisPos\n              { shaderLocation: 1, format: 'float32x2', offset: 8 }, // widthDashCount\n              { shaderLocation: 2, format: 'float32x2', offset: 16 }, // dashMeta\n              { shaderLocation: 3, format: 'float32x4', offset: 24 }, // dash0_3\n              { shaderLocation: 4, format: 'float32x4', offset: 40 }, // dash4_7\n              { shaderLocation: 5, format: 'float32x4', offset: 56 }, // color\n            ],\n          },\n        ],\n      },\n      fragment: {\n        code: referenceLineWgsl,\n        label: 'referenceLine.wgsl',\n        formats: targetFormat,\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let instanceBuffer: GPUBuffer | null = null;\n  let instanceCapacity = 0;\n  let instanceCount = 0;\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('ReferenceLineRenderer is disposed.');\n  };\n\n  const prepare: ReferenceLineRenderer['prepare'] = (gridArea, lines) => {\n    assertNotDisposed();\n\n    if (!Array.isArray(lines)) {\n      throw new Error('ReferenceLineRenderer.prepare: lines must be an array.');\n    }\n    if (!isFiniteGridArea(gridArea)) {\n      throw new Error('ReferenceLineRenderer.prepare: gridArea dimensions must be finite numbers.');\n    }\n    if (gridArea.canvasWidth <= 0 || gridArea.canvasHeight <= 0) {\n      throw new Error('ReferenceLineRenderer.prepare: canvas dimensions must be positive.');\n    }\n    if (gridArea.left < 0 || gridArea.right < 0 || gridArea.top < 0 || gridArea.bottom < 0) {\n      throw new Error('ReferenceLineRenderer.prepare: gridArea margins must be non-negative.');\n    }\n\n    // Be resilient: older call sites may omit/incorrectly pass DPR. Defaulting avoids hard crashes.\n    const dpr =\n      Number.isFinite(gridArea.devicePixelRatio) && gridArea.devicePixelRatio > 0 ? gridArea.devicePixelRatio : 1;\n\n    const plotLeftDevice = gridArea.left * dpr;\n    const plotTopDevice = gridArea.top * dpr;\n    const plotRightDevice = gridArea.canvasWidth - gridArea.right * dpr;\n    const plotBottomDevice = gridArea.canvasHeight - gridArea.bottom * dpr;\n    const plotWidthDevice = plotRightDevice - plotLeftDevice;\n    const plotHeightDevice = plotBottomDevice - plotTopDevice;\n\n    if (!(plotWidthDevice > 0) || !(plotHeightDevice > 0)) {\n      instanceCount = 0;\n      return;\n    }\n\n    // Write uniforms.\n    const uniforms = new Float32Array(8);\n    uniforms[0] = gridArea.canvasWidth;\n    uniforms[1] = gridArea.canvasHeight;\n    uniforms[2] = plotLeftDevice;\n    uniforms[3] = plotTopDevice;\n    uniforms[4] = plotWidthDevice;\n    uniforms[5] = plotHeightDevice;\n    uniforms[6] = dpr;\n    uniforms[7] = 0;\n    writeUniformBuffer(device, vsUniformBuffer, uniforms);\n\n    // Early out: no instances.\n    if (lines.length === 0) {\n      instanceCount = 0;\n      return;\n    }\n\n    // Ensure instance buffer capacity.\n    if (!instanceBuffer || instanceCapacity < lines.length) {\n      const nextCapacity = Math.max(1, Math.ceil(lines.length * 1.5));\n      const size = Math.max(4, nextCapacity * INSTANCE_STRIDE_BYTES);\n\n      if (instanceBuffer) {\n        try {\n          instanceBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n\n      instanceBuffer = device.createBuffer({\n        label: 'referenceLineRenderer/instanceBuffer',\n        size,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n      instanceCapacity = nextCapacity;\n    }\n\n    const data = new Float32Array(lines.length * INSTANCE_STRIDE_FLOATS);\n\n    for (let i = 0; i < lines.length; i++) {\n      const line = lines[i];\n      const base = i * INSTANCE_STRIDE_FLOATS;\n\n      if (line.axis !== 'vertical' && line.axis !== 'horizontal') {\n        throw new Error(\"ReferenceLineRenderer.prepare: line.axis must be 'vertical' or 'horizontal'.\");\n      }\n      if (!Number.isFinite(line.positionCssPx)) {\n        throw new Error('ReferenceLineRenderer.prepare: line.positionCssPx must be a finite number.');\n      }\n      if (!Number.isFinite(line.lineWidth) || line.lineWidth < 0) {\n        throw new Error('ReferenceLineRenderer.prepare: line.lineWidth must be a finite non-negative number.');\n      }\n\n      const rgba = line.rgba;\n      if (!Array.isArray(rgba) || rgba.length !== 4) {\n        throw new Error('ReferenceLineRenderer.prepare: line.rgba must be a tuple [r,g,b,a].');\n      }\n\n      const dash = normalizeDash(line.lineDash);\n\n      // axisPos\n      data[base + 0] = line.axis === 'vertical' ? 0 : 1;\n      data[base + 1] = line.positionCssPx;\n\n      // widthDashCount\n      data[base + 2] = line.lineWidth;\n      data[base + 3] = dash.dashCount;\n\n      // dashMeta\n      data[base + 4] = dash.dashTotal;\n      data[base + 5] = 0;\n\n      // dash0_3 + dash4_7\n      for (let d = 0; d < MAX_DASH_VALUES; d++) {\n        data[base + 6 + d] = dash.values[d];\n      }\n\n      // color\n      data[base + 14] = rgba[0];\n      data[base + 15] = rgba[1];\n      data[base + 16] = rgba[2];\n      data[base + 17] = rgba[3];\n    }\n\n    device.queue.writeBuffer(instanceBuffer, 0, data.buffer, data.byteOffset, data.byteLength);\n    instanceCount = lines.length;\n  };\n\n  const render: ReferenceLineRenderer['render'] = (passEncoder, firstInstance = 0, requestedCount) => {\n    assertNotDisposed();\n    if (instanceCount === 0 || !instanceBuffer) return;\n\n    const first = Number.isFinite(firstInstance) ? Math.max(0, Math.floor(firstInstance)) : 0;\n    const available = Math.max(0, instanceCount - first);\n    const count =\n      requestedCount == null\n        ? available\n        : Number.isFinite(requestedCount)\n          ? Math.max(0, Math.min(available, Math.floor(requestedCount)))\n          : available;\n    if (count === 0) return;\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n    passEncoder.setVertexBuffer(0, instanceBuffer);\n    passEncoder.draw(6, count, 0, first);\n  };\n\n  const dispose: ReferenceLineRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n    if (instanceBuffer) {\n      try {\n        instanceBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n\n    instanceBuffer = null;\n    instanceCapacity = 0;\n    instanceCount = 0;\n  };\n\n  return { prepare, render, dispose };\n}\n\n","export default \"// annotationMarker.wgsl\\n// Instanced annotation marker shader (circle SDF with optional stroke).\\n//\\n// Coordinate contract:\\n// - Instance center is CANVAS-LOCAL CSS pixels (xCssPx, yCssPx)\\n// - Instance size is diameter in CSS pixels (sizeCssPx)\\n// - Uniform provides render target size in *device* pixels and DPR for CSS→device conversion.\\n//\\n// Draw call: draw(6, instanceCount) using triangle-list quad expansion in VS.\\n\\nstruct VSUniforms {\\n  viewportPx: vec2<f32>, // render target size in device pixels (width, height)\\n  dpr: f32,              // device pixel ratio (CSS px -> device px)\\n  _pad0: f32,\\n};\\n\\n@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;\\n\\nstruct VSIn {\\n  // Center in CANVAS-LOCAL CSS pixels.\\n  @location(0) centerCssPx: vec2<f32>,\\n  // Marker diameter in CSS pixels.\\n  @location(1) sizeCssPx: f32,\\n  // Stroke width in CSS pixels (0 disables stroke).\\n  @location(2) strokeWidthCssPx: f32,\\n  // Colors are straight-alpha RGBA in 0..1.\\n  @location(3) fillRgba: vec4<f32>,\\n  @location(4) strokeRgba: vec4<f32>,\\n};\\n\\nstruct VSOut {\\n  @builtin(position) clipPosition: vec4<f32>,\\n  // Local quad coordinates in [-1, 1]^2 (used for circle SDF).\\n  @location(0) local: vec2<f32>,\\n  // Half-size in device pixels (radius in screen space).\\n  @location(1) halfSizePx: f32,\\n  @location(2) strokeWidthPx: f32,\\n  @location(3) fillRgba: vec4<f32>,\\n  @location(4) strokeRgba: vec4<f32>,\\n};\\n\\n@vertex\\nfn vsMain(in: VSIn, @builtin(vertex_index) vertexIndex: u32) -> VSOut {\\n  // Fixed local corners for 2 triangles (triangle-list).\\n  let localCorners = array<vec2<f32>, 6>(\\n    vec2<f32>(-1.0, -1.0),\\n    vec2<f32>( 1.0, -1.0),\\n    vec2<f32>(-1.0,  1.0),\\n    vec2<f32>(-1.0,  1.0),\\n    vec2<f32>( 1.0, -1.0),\\n    vec2<f32>( 1.0,  1.0)\\n  );\\n\\n  let corner = localCorners[vertexIndex];\\n\\n  let dpr = select(1.0, vsUniforms.dpr, vsUniforms.dpr > 0.0);\\n  let centerPx = in.centerCssPx * dpr;\\n  let halfSizePx = 0.5 * max(0.0, in.sizeCssPx) * dpr;\\n  let strokeWidthPx = max(0.0, in.strokeWidthCssPx) * dpr;\\n\\n  let posPx = centerPx + corner * halfSizePx;\\n\\n  // Convert device pixels to clip-space with origin at top-left:\\n  // x: [0..w] -> [-1..1], y: [0..h] -> [1..-1]\\n  let clipX = (posPx.x / vsUniforms.viewportPx.x) * 2.0 - 1.0;\\n  let clipY = 1.0 - (posPx.y / vsUniforms.viewportPx.y) * 2.0;\\n\\n  var out: VSOut;\\n  out.clipPosition = vec4<f32>(clipX, clipY, 0.0, 1.0);\\n  out.local = corner;\\n  out.halfSizePx = halfSizePx;\\n  out.strokeWidthPx = strokeWidthPx;\\n  out.fillRgba = in.fillRgba;\\n  out.strokeRgba = in.strokeRgba;\\n  return out;\\n}\\n\\n@fragment\\nfn fsMain(in: VSOut) -> @location(0) vec4<f32> {\\n  if (in.halfSizePx <= 0.0) {\\n    discard;\\n  }\\n\\n  // Circle SDF in normalized space: dist == 1 at the circle boundary.\\n  let dist = length(in.local);\\n  let aa = max(1e-6, fwidth(dist));\\n\\n  // Coverage inside the circle.\\n  let outerCoverage = 1.0 - smoothstep(1.0 - aa, 1.0 + aa, dist);\\n  if (outerCoverage <= 0.0) {\\n    discard;\\n  }\\n\\n  // Optional stroke: compute inner radius in normalized units.\\n  let strokeNorm = clamp(in.strokeWidthPx / max(1e-6, in.halfSizePx), 0.0, 1.0);\\n  let inner = max(0.0, 1.0 - strokeNorm);\\n  let innerCoverage = 1.0 - smoothstep(inner - aa, inner + aa, dist);\\n\\n  let fillCoverage = clamp(innerCoverage, 0.0, 1.0);\\n  let strokeCoverage = clamp(outerCoverage - innerCoverage, 0.0, 1.0);\\n\\n  let fillA = clamp(in.fillRgba.a, 0.0, 1.0) * fillCoverage;\\n  let strokeA = clamp(in.strokeRgba.a, 0.0, 1.0) * strokeCoverage;\\n  let outA = fillA + strokeA;\\n  if (outA <= 0.0) {\\n    discard;\\n  }\\n\\n  // Straight-alpha output: compute a weighted average RGB for correct blending.\\n  let rgb = (in.fillRgba.rgb * fillA + in.strokeRgba.rgb * strokeA) / outA;\\n  return vec4<f32>(rgb, outA);\\n}\\n\\n\"","import annotationMarkerWgsl from '../shaders/annotationMarker.wgsl?raw';\nimport { createRenderPipeline, createUniformBuffer, writeUniformBuffer } from './rendererUtils';\nimport type { PipelineCache } from '../core/PipelineCache';\n\nexport type AnnotationMarkerInstance = Readonly<{\n  /**\n   * Center in CANVAS-LOCAL CSS pixels.\n   * (0,0) is the canvas top-left in CSS pixel coordinates.\n   */\n  xCssPx: number;\n  yCssPx: number;\n\n  /** Marker diameter in CSS pixels. */\n  sizeCssPx: number;\n\n  /** Fill color RGBA in 0..1 (straight alpha). */\n  fillRgba: readonly [r: number, g: number, b: number, a: number];\n\n  /** Optional stroke width in CSS pixels (0 disables stroke). */\n  strokeWidthCssPx?: number;\n\n  /** Optional stroke color RGBA in 0..1 (straight alpha). */\n  strokeRgba?: readonly [r: number, g: number, b: number, a: number];\n}>;\n\nexport interface AnnotationMarkerRenderer {\n  /**\n   * Uploads marker instances and prepares uniforms for rendering.\n   *\n   * Coordinate contract:\n   * - `instances[*].xCssPx/yCssPx` are CANVAS-LOCAL CSS pixels.\n   * - `canvasWidth/canvasHeight` are in *device pixels* (same as render target size).\n   * - `devicePixelRatio` is used to convert CSS px to device px inside the shader.\n   *\n   * Scissor contract:\n   * - This renderer intentionally does NOT set or reset scissor state.\n   *   The caller must set scissor for plot clipping before invoking `render()`.\n   */\n  prepare(params: Readonly<{\n    canvasWidth: number;\n    canvasHeight: number;\n    devicePixelRatio: number;\n    instances: readonly AnnotationMarkerInstance[];\n  }>): void;\n\n  /** Draws all prepared instances (if any). */\n  render(passEncoder: GPURenderPassEncoder, firstInstance?: number, instanceCount?: number): void;\n\n  /** Cleans up GPU resources (best-effort). */\n  dispose(): void;\n}\n\nexport interface AnnotationMarkerRendererOptions {\n  /**\n   * Must match the canvas context format used for the render pass color attachment.\n   * Usually this is `gpuContext.preferredFormat`.\n   *\n   * Defaults to `'bgra8unorm'` for backward compatibility.\n   */\n  readonly targetFormat?: GPUTextureFormat;\n\n  /**\n   * Multisample count for the render pipeline.\n   *\n   * Must match the render pass color attachment sampleCount.\n   * Defaults to 1 (no MSAA).\n   */\n  readonly sampleCount?: number;\n  /**\n   * Optional shared cache for shader modules + render pipelines.\n   */\n  readonly pipelineCache?: PipelineCache;\n}\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\n\n// Instance layout (WGSL VSIn):\n// centerCssPx.xy, sizeCssPx, strokeWidthCssPx, fillRgba.rgba, strokeRgba.rgba\nconst INSTANCE_STRIDE_FLOATS = 12;\nconst INSTANCE_STRIDE_BYTES = INSTANCE_STRIDE_FLOATS * 4;\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\n\nconst nextPow2 = (v: number): number => {\n  if (!Number.isFinite(v) || v <= 0) return 1;\n  const n = Math.ceil(v);\n  return 2 ** Math.ceil(Math.log2(n));\n};\n\nexport function createAnnotationMarkerRenderer(device: GPUDevice, options?: AnnotationMarkerRendererOptions): AnnotationMarkerRenderer {\n  let disposed = false;\n  const targetFormat = options?.targetFormat ?? DEFAULT_TARGET_FORMAT;\n  // Be resilient: coerce invalid values to 1 (no MSAA).\n  const sampleCountRaw = options?.sampleCount ?? 1;\n  const sampleCount = Number.isFinite(sampleCountRaw) ? Math.max(1, Math.floor(sampleCountRaw)) : 1;\n  const pipelineCache = options?.pipelineCache;\n\n  const bindGroupLayout = device.createBindGroupLayout({\n    entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } }],\n  });\n\n  // VSUniforms (WGSL):\n  // viewportPx vec2 + dpr f32 + pad f32 = 16 bytes\n  const vsUniformBuffer = createUniformBuffer(device, 16, { label: 'annotationMarkerRenderer/vsUniforms' });\n  const vsUniformScratchF32 = new Float32Array(4);\n\n  const bindGroup = device.createBindGroup({\n    layout: bindGroupLayout,\n    entries: [{ binding: 0, resource: { buffer: vsUniformBuffer } }],\n  });\n\n  const pipeline = createRenderPipeline(\n    device,\n    {\n      label: 'annotationMarkerRenderer/pipeline',\n      bindGroupLayouts: [bindGroupLayout],\n      vertex: {\n        code: annotationMarkerWgsl,\n        label: 'annotationMarker.wgsl',\n        buffers: [\n          {\n            arrayStride: INSTANCE_STRIDE_BYTES,\n            stepMode: 'instance',\n            attributes: [\n              { shaderLocation: 0, format: 'float32x2', offset: 0 }, // centerCssPx\n              { shaderLocation: 1, format: 'float32', offset: 8 }, // sizeCssPx\n              { shaderLocation: 2, format: 'float32', offset: 12 }, // strokeWidthCssPx\n              { shaderLocation: 3, format: 'float32x4', offset: 16 }, // fillRgba\n              { shaderLocation: 4, format: 'float32x4', offset: 32 }, // strokeRgba\n            ],\n          },\n        ],\n      },\n      fragment: {\n        code: annotationMarkerWgsl,\n        label: 'annotationMarker.wgsl',\n        formats: targetFormat,\n        blend: {\n          color: { operation: 'add', srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha' },\n          alpha: { operation: 'add', srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n        },\n      },\n      primitive: { topology: 'triangle-list', cullMode: 'none' },\n      multisample: { count: sampleCount },\n    },\n    pipelineCache\n  );\n\n  let instanceBuffer: GPUBuffer | null = null;\n  let instanceCount = 0;\n  let cpuInstanceStagingBuffer = new ArrayBuffer(0);\n  let cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('AnnotationMarkerRenderer is disposed.');\n  };\n\n  const ensureCpuInstanceCapacityFloats = (requiredFloats: number): void => {\n    if (requiredFloats <= cpuInstanceStagingF32.length) return;\n    const nextFloats = Math.max(32, nextPow2(requiredFloats));\n    cpuInstanceStagingBuffer = new ArrayBuffer(nextFloats * 4);\n    cpuInstanceStagingF32 = new Float32Array(cpuInstanceStagingBuffer);\n  };\n\n  const writeVsUniforms = (canvasWidthDevicePx: number, canvasHeightDevicePx: number, devicePixelRatio: number): void => {\n    const w = Number.isFinite(canvasWidthDevicePx) && canvasWidthDevicePx > 0 ? canvasWidthDevicePx : 1;\n    const h = Number.isFinite(canvasHeightDevicePx) && canvasHeightDevicePx > 0 ? canvasHeightDevicePx : 1;\n    const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;\n\n    vsUniformScratchF32[0] = w;\n    vsUniformScratchF32[1] = h;\n    vsUniformScratchF32[2] = dpr;\n    vsUniformScratchF32[3] = 0;\n    writeUniformBuffer(device, vsUniformBuffer, vsUniformScratchF32);\n  };\n\n  const prepare: AnnotationMarkerRenderer['prepare'] = ({ canvasWidth, canvasHeight, devicePixelRatio, instances }) => {\n    assertNotDisposed();\n\n    if (!Number.isFinite(canvasWidth) || !Number.isFinite(canvasHeight) || canvasWidth <= 0 || canvasHeight <= 0) {\n      throw new Error('AnnotationMarkerRenderer.prepare: canvasWidth/canvasHeight must be positive finite numbers.');\n    }\n    if (!Array.isArray(instances)) {\n      throw new Error('AnnotationMarkerRenderer.prepare: instances must be an array.');\n    }\n\n    writeVsUniforms(canvasWidth, canvasHeight, devicePixelRatio);\n\n    ensureCpuInstanceCapacityFloats(instances.length * INSTANCE_STRIDE_FLOATS);\n    const f32 = cpuInstanceStagingF32;\n    let outFloats = 0;\n\n    for (let i = 0; i < instances.length; i++) {\n      const m = instances[i];\n      if (!Number.isFinite(m.xCssPx) || !Number.isFinite(m.yCssPx)) continue;\n      if (!Number.isFinite(m.sizeCssPx) || m.sizeCssPx <= 0) continue;\n\n      const strokeWidthCss = m.strokeWidthCssPx ?? 0;\n      const strokeRgba = m.strokeRgba ?? ([0, 0, 0, 0] as const);\n\n      // Clamp colors to [0,1] for deterministic output.\n      const fr = clamp01(m.fillRgba[0]);\n      const fg = clamp01(m.fillRgba[1]);\n      const fb = clamp01(m.fillRgba[2]);\n      const fa = clamp01(m.fillRgba[3]);\n\n      const sr = clamp01(strokeRgba[0]);\n      const sg = clamp01(strokeRgba[1]);\n      const sb = clamp01(strokeRgba[2]);\n      const sa = clamp01(strokeRgba[3]);\n\n      f32[outFloats + 0] = m.xCssPx;\n      f32[outFloats + 1] = m.yCssPx;\n      f32[outFloats + 2] = m.sizeCssPx;\n      f32[outFloats + 3] = Number.isFinite(strokeWidthCss) ? Math.max(0, strokeWidthCss) : 0;\n\n      f32[outFloats + 4] = fr;\n      f32[outFloats + 5] = fg;\n      f32[outFloats + 6] = fb;\n      f32[outFloats + 7] = fa;\n\n      f32[outFloats + 8] = sr;\n      f32[outFloats + 9] = sg;\n      f32[outFloats + 10] = sb;\n      f32[outFloats + 11] = sa;\n\n      outFloats += INSTANCE_STRIDE_FLOATS;\n    }\n\n    instanceCount = outFloats / INSTANCE_STRIDE_FLOATS;\n\n    // PERFORMANCE: Early exit if no valid instances (skip buffer allocation/write)\n    if (instanceCount === 0) {\n      return;\n    }\n\n    const requiredBytes = Math.max(4, instanceCount * INSTANCE_STRIDE_BYTES);\n\n    if (!instanceBuffer || instanceBuffer.size < requiredBytes) {\n      const grownBytes = Math.max(Math.max(4, nextPow2(requiredBytes)), instanceBuffer ? instanceBuffer.size : 0);\n      if (instanceBuffer) {\n        try {\n          instanceBuffer.destroy();\n        } catch {\n          // best-effort\n        }\n      }\n      instanceBuffer = device.createBuffer({\n        label: 'annotationMarkerRenderer/instanceBuffer',\n        size: grownBytes,\n        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n      });\n    }\n\n    // PERFORMANCE: Only write buffer when we have instances (instanceCount > 0 already checked above)\n    device.queue.writeBuffer(instanceBuffer, 0, cpuInstanceStagingBuffer, 0, instanceCount * INSTANCE_STRIDE_BYTES);\n  };\n\n  const render: AnnotationMarkerRenderer['render'] = (passEncoder, firstInstance = 0, requestedCount) => {\n    assertNotDisposed();\n    if (!instanceBuffer || instanceCount === 0) return;\n\n    const first = Number.isFinite(firstInstance) ? Math.max(0, Math.floor(firstInstance)) : 0;\n    const available = Math.max(0, instanceCount - first);\n    const count =\n      requestedCount == null\n        ? available\n        : Number.isFinite(requestedCount)\n          ? Math.max(0, Math.min(available, Math.floor(requestedCount)))\n          : available;\n    if (count === 0) return;\n\n    passEncoder.setPipeline(pipeline);\n    passEncoder.setBindGroup(0, bindGroup);\n    passEncoder.setVertexBuffer(0, instanceBuffer);\n    passEncoder.draw(6, count, 0, first);\n  };\n\n  const dispose: AnnotationMarkerRenderer['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    if (instanceBuffer) {\n      try {\n        instanceBuffer.destroy();\n      } catch {\n        // best-effort\n      }\n    }\n    instanceBuffer = null;\n    instanceCount = 0;\n\n    try {\n      vsUniformBuffer.destroy();\n    } catch {\n      // best-effort\n    }\n  };\n\n  return { prepare, render, dispose };\n}\n\n","import type { GridArea } from '../renderers/createGridRenderer';\n\nexport type ChartGPUEventName = 'mousemove' | 'click' | 'mouseleave';\n\nexport type ChartGPUEventPayload = {\n  readonly x: number;\n  readonly y: number;\n  readonly gridX: number;\n  readonly gridY: number;\n  /** Plot (grid) width in CSS pixels. */\n  readonly plotWidthCss: number;\n  /** Plot (grid) height in CSS pixels. */\n  readonly plotHeightCss: number;\n  readonly isInGrid: boolean;\n  readonly originalEvent: PointerEvent;\n};\n\nexport type ChartGPUEventCallback = (payload: ChartGPUEventPayload) => void;\n\nexport interface EventManager {\n  readonly canvas: HTMLCanvasElement;\n  on(event: ChartGPUEventName, callback: ChartGPUEventCallback): void;\n  off(event: ChartGPUEventName, callback: ChartGPUEventCallback): void;\n  updateGridArea(gridArea: GridArea): void;\n  dispose(): void;\n}\n\ntype ListenerRegistry = Readonly<Record<ChartGPUEventName, Set<ChartGPUEventCallback>>>;\n\ntype TapCandidate = {\n  readonly pointerId: number;\n  readonly startClientX: number;\n  readonly startClientY: number;\n  readonly startTimeMs: number;\n};\n\nconst DEFAULT_TAP_MAX_DISTANCE_CSS_PX = 6;\nconst DEFAULT_TAP_MAX_TIME_MS = 500;\n\nexport function createEventManager(canvas: HTMLCanvasElement, initialGridArea: GridArea): EventManager {\n  let disposed = false;\n  let gridArea = initialGridArea;\n\n  const listeners: ListenerRegistry = {\n    mousemove: new Set<ChartGPUEventCallback>(),\n    click: new Set<ChartGPUEventCallback>(),\n    mouseleave: new Set<ChartGPUEventCallback>(),\n  };\n\n  let tapCandidate: TapCandidate | null = null;\n  let suppressNextLostPointerCaptureId: number | null = null;\n\n  const toPayload = (e: PointerEvent): ChartGPUEventPayload | null => {\n    const rect = canvas.getBoundingClientRect();\n    if (rect.width === 0 || rect.height === 0) return null;\n\n    const x = e.clientX - rect.left;\n    const y = e.clientY - rect.top;\n\n    const plotLeftCss = gridArea.left;\n    const plotTopCss = gridArea.top;\n    const plotWidthCss = rect.width - gridArea.left - gridArea.right;\n    const plotHeightCss = rect.height - gridArea.top - gridArea.bottom;\n\n    const gridX = x - plotLeftCss;\n    const gridY = y - plotTopCss;\n\n    const isInGrid =\n      gridX >= 0 &&\n      gridX <= plotWidthCss &&\n      gridY >= 0 &&\n      gridY <= plotHeightCss;\n\n    return { x, y, gridX, gridY, plotWidthCss, plotHeightCss, isInGrid, originalEvent: e };\n  };\n\n  const emit = (eventName: ChartGPUEventName, e: PointerEvent): void => {\n    const payload = toPayload(e);\n    if (!payload) return;\n\n    for (const cb of listeners[eventName]) cb(payload);\n  };\n\n  const clearTapCandidateIfMatches = (e: PointerEvent): void => {\n    if (!tapCandidate) return;\n    if (!e.isPrimary) return;\n    if (e.pointerId !== tapCandidate.pointerId) return;\n    tapCandidate = null;\n  };\n\n  const onPointerMove = (e: PointerEvent): void => {\n    if (disposed) return;\n    emit('mousemove', e);\n  };\n\n  const onPointerLeave = (e: PointerEvent): void => {\n    if (disposed) return;\n    clearTapCandidateIfMatches(e);\n    emit('mouseleave', e);\n  };\n\n  const onPointerCancel = (e: PointerEvent): void => {\n    if (disposed) return;\n    clearTapCandidateIfMatches(e);\n    emit('mouseleave', e);\n  };\n\n  const onLostPointerCapture = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (suppressNextLostPointerCaptureId === e.pointerId) {\n      suppressNextLostPointerCaptureId = null;\n      return;\n    }\n    clearTapCandidateIfMatches(e);\n    emit('mouseleave', e);\n  };\n\n  const onPointerDown = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (!e.isPrimary) return;\n\n    // For mouse, only allow left button.\n    if (e.pointerType === 'mouse' && e.button !== 0) return;\n\n    // If canvas has no size, treat as non-interactive (and avoid tap tracking).\n    const rect = canvas.getBoundingClientRect();\n    if (rect.width === 0 || rect.height === 0) return;\n\n    tapCandidate = {\n      pointerId: e.pointerId,\n      startClientX: e.clientX,\n      startClientY: e.clientY,\n      startTimeMs: e.timeStamp,\n    };\n\n    // Optional pointer capture improves reliability for touch/pen.\n    try {\n      canvas.setPointerCapture(e.pointerId);\n    } catch {\n      // best-effort\n    }\n  };\n\n  const onPointerUp = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (!e.isPrimary) return;\n    if (!tapCandidate || e.pointerId !== tapCandidate.pointerId) return;\n\n    const dt = e.timeStamp - tapCandidate.startTimeMs;\n    const dx = e.clientX - tapCandidate.startClientX;\n    const dy = e.clientY - tapCandidate.startClientY;\n    const distSq = dx * dx + dy * dy;\n\n    tapCandidate = null;\n\n    // Release capture if we have it; suppress the resulting lostpointercapture.\n    try {\n      if (canvas.hasPointerCapture(e.pointerId)) {\n        suppressNextLostPointerCaptureId = e.pointerId;\n        canvas.releasePointerCapture(e.pointerId);\n      }\n    } catch {\n      // best-effort\n    }\n\n    const maxDist = DEFAULT_TAP_MAX_DISTANCE_CSS_PX;\n    const isTap =\n      dt <= DEFAULT_TAP_MAX_TIME_MS && distSq <= maxDist * maxDist;\n\n    if (isTap) emit('click', e);\n  };\n\n  canvas.addEventListener('pointermove', onPointerMove, { passive: true });\n  canvas.addEventListener('pointerleave', onPointerLeave, { passive: true });\n  canvas.addEventListener('pointercancel', onPointerCancel, { passive: true });\n  canvas.addEventListener('lostpointercapture', onLostPointerCapture, { passive: true });\n  canvas.addEventListener('pointerdown', onPointerDown, { passive: true });\n  canvas.addEventListener('pointerup', onPointerUp, { passive: true });\n\n  const on: EventManager['on'] = (event, callback) => {\n    if (disposed) return;\n    listeners[event].add(callback);\n  };\n\n  const off: EventManager['off'] = (event, callback) => {\n    listeners[event].delete(callback);\n  };\n\n  const updateGridArea: EventManager['updateGridArea'] = (nextGridArea) => {\n    gridArea = nextGridArea;\n  };\n\n  const dispose: EventManager['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    tapCandidate = null;\n    suppressNextLostPointerCaptureId = null;\n\n    canvas.removeEventListener('pointermove', onPointerMove);\n    canvas.removeEventListener('pointerleave', onPointerLeave);\n    canvas.removeEventListener('pointercancel', onPointerCancel);\n    canvas.removeEventListener('lostpointercapture', onLostPointerCapture);\n    canvas.removeEventListener('pointerdown', onPointerDown);\n    canvas.removeEventListener('pointerup', onPointerUp);\n\n    listeners.mousemove.clear();\n    listeners.click.clear();\n    listeners.mouseleave.clear();\n  };\n\n  return { canvas, on, off, updateGridArea, dispose };\n}\n","import type { EventManager, ChartGPUEventPayload } from './createEventManager';\nimport type { ZoomState } from './createZoomState';\n\nexport type InsideZoom = Readonly<{\n  enable(): void;\n  disable(): void;\n  dispose(): void;\n}>;\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));\n\nconst normalizeWheelDelta = (e: WheelEvent, basisCssPx: number): number => {\n  const raw = e.deltaY;\n  if (!Number.isFinite(raw) || raw === 0) return 0;\n\n  // Normalize to CSS pixels-ish so sensitivity is stable across deltaMode.\n  switch (e.deltaMode) {\n    case WheelEvent.DOM_DELTA_PIXEL:\n      return raw;\n    case WheelEvent.DOM_DELTA_LINE:\n      return raw * 16;\n    case WheelEvent.DOM_DELTA_PAGE:\n      return raw * (Number.isFinite(basisCssPx) && basisCssPx > 0 ? basisCssPx : 800);\n    default:\n      return raw;\n  }\n};\n\nconst normalizeWheelDeltaX = (e: WheelEvent, basisCssPx: number): number => {\n  const raw = e.deltaX;\n  if (!Number.isFinite(raw) || raw === 0) return 0;\n\n  // Normalize to CSS pixels-ish so sensitivity is stable across deltaMode.\n  switch (e.deltaMode) {\n    case WheelEvent.DOM_DELTA_PIXEL:\n      return raw;\n    case WheelEvent.DOM_DELTA_LINE:\n      return raw * 16;\n    case WheelEvent.DOM_DELTA_PAGE:\n      return raw * (Number.isFinite(basisCssPx) && basisCssPx > 0 ? basisCssPx : 800);\n    default:\n      return raw;\n  }\n};\n\nconst wheelDeltaToZoomFactor = (deltaCssPx: number): number => {\n  // Positive delta = scroll down = zoom out; negative = zoom in.\n  const abs = Math.abs(deltaCssPx);\n  if (!Number.isFinite(abs) || abs === 0) return 1;\n\n  // Cap extreme deltas (some devices can emit huge values).\n  const capped = Math.min(abs, 200);\n  const sensitivity = 0.002;\n  return Math.exp(capped * sensitivity);\n};\n\nconst isMiddleButtonDrag = (e: PointerEvent): boolean =>\n  e.pointerType === 'mouse' && (e.buttons & 4) !== 0;\n\nconst isShiftLeftDrag = (e: PointerEvent): boolean =>\n  e.pointerType === 'mouse' && e.shiftKey && (e.buttons & 1) !== 0;\n\n/**\n * Internal “inside” zoom interaction:\n * - wheel zoom centered at cursor-x (only when inside grid)\n * - shift+left drag OR middle-mouse drag pans left/right (only when inside grid)\n * - single-finger touch drag pans left/right (only when inside grid)\n * - two-finger pinch-to-zoom centered at finger midpoint (only when inside grid)\n */\nexport function createInsideZoom(eventManager: EventManager, zoomState: ZoomState): InsideZoom {\n  let disposed = false;\n  let enabled = false;\n\n  let lastPointer: ChartGPUEventPayload | null = null;\n  let isPanning = false;\n  let lastPanGridX = 0;\n\n  // --- Touch state ---\n  const isTouchDevice = typeof navigator !== 'undefined' && navigator.maxTouchPoints > 0;\n  const activePointers = new Map<number, { x: number; y: number }>();\n  let previousPinchDist = 0;\n  let savedTouchAction = '';\n\n  const resetPinchState = (): void => {\n    previousPinchDist = 0;\n  };\n\n  const clearPan = (): void => {\n    isPanning = false;\n    lastPanGridX = 0;\n  };\n\n  // --- Mouse event handlers (from EventManager) ---\n\n  const onMouseMove = (payload: ChartGPUEventPayload): void => {\n    lastPointer = payload;\n    if (!enabled) return;\n\n    // Pan only for mouse drags, only when inside grid.\n    const e = payload.originalEvent;\n    const shouldPan = payload.isInGrid && (isShiftLeftDrag(e) || isMiddleButtonDrag(e));\n\n    if (!shouldPan) {\n      clearPan();\n      return;\n    }\n\n    const plotWidthCss = payload.plotWidthCss;\n    if (!(plotWidthCss > 0) || !Number.isFinite(plotWidthCss)) {\n      clearPan();\n      return;\n    }\n\n    if (!isPanning) {\n      isPanning = true;\n      lastPanGridX = payload.gridX;\n      return;\n    }\n\n    const dxCss = payload.gridX - lastPanGridX;\n    lastPanGridX = payload.gridX;\n    if (!Number.isFinite(dxCss) || dxCss === 0) return;\n\n    const { start, end } = zoomState.getRange();\n    const span = end - start;\n    if (!Number.isFinite(span) || span === 0) return;\n\n    // Convert grid-local px to percent points *within the current window*.\n    // “Grab to pan” behavior: dragging right should move the window left (show earlier data).\n    const deltaPct = -(dxCss / plotWidthCss) * span;\n    if (!Number.isFinite(deltaPct) || deltaPct === 0) return;\n    zoomState.pan(deltaPct);\n  };\n\n  const onMouseLeave = (_payload: ChartGPUEventPayload): void => {\n    lastPointer = null;\n    clearPan();\n  };\n\n  // --- Wheel handler ---\n\n  const onWheel = (e: WheelEvent): void => {\n    if (!enabled || disposed) return;\n\n    const p = lastPointer;\n    if (!p || !p.isInGrid) return;\n\n    const plotWidthCss = p.plotWidthCss;\n    const plotHeightCss = p.plotHeightCss;\n    if (!(plotWidthCss > 0) || !(plotHeightCss > 0)) return;\n\n    const deltaYCss = normalizeWheelDelta(e, plotHeightCss);\n    const deltaXCss = normalizeWheelDeltaX(e, plotWidthCss);\n\n    // Check if horizontal scroll is dominant (pan operation).\n    if (Math.abs(deltaXCss) > Math.abs(deltaYCss) && deltaXCss !== 0) {\n      const { start, end } = zoomState.getRange();\n      const span = end - start;\n      if (!Number.isFinite(span) || span === 0) return;\n\n      // Convert horizontal scroll delta to percent pan.\n      // Positive deltaX = scroll right = pan right (show earlier data).\n      const deltaPct = (deltaXCss / plotWidthCss) * span;\n      if (!Number.isFinite(deltaPct) || deltaPct === 0) return;\n\n      e.preventDefault();\n      zoomState.pan(deltaPct);\n      return;\n    }\n\n    // Otherwise, proceed with vertical scroll zoom logic.\n    if (deltaYCss === 0) return;\n\n    const factor = wheelDeltaToZoomFactor(deltaYCss);\n    if (!(factor > 1)) return;\n\n    const { start, end } = zoomState.getRange();\n    const span = end - start;\n    if (!Number.isFinite(span) || span === 0) return;\n    const r = clamp(p.gridX / plotWidthCss, 0, 1);\n    const centerPct = clamp(start + r * span, 0, 100);\n\n    // Only prevent default when we are actually consuming the wheel to zoom.\n    e.preventDefault();\n\n    if (deltaYCss < 0) zoomState.zoomIn(centerPct, factor);\n    else zoomState.zoomOut(centerPct, factor);\n  };\n\n  // --- Touch pointer handlers (on canvas) ---\n\n  /** Fallback grid check when lastPointer isn't available yet (e.g. touch-only device). */\n  const isPointInGrid = (e: PointerEvent, canvas: HTMLCanvasElement): boolean => {\n    const rect = canvas.getBoundingClientRect();\n    if (rect.width === 0 || rect.height === 0) return false;\n    const x = e.clientX - rect.left;\n    const y = e.clientY - rect.top;\n    return x >= 0 && x <= rect.width && y >= 0 && y <= rect.height;\n  };\n\n  const onTouchPointerDown = (e: PointerEvent): void => {\n    if (!enabled || disposed) return;\n    if (e.pointerType !== 'touch') return;\n\n    // Prevent default to suppress browser scroll/zoom on touch.\n    e.preventDefault();\n\n    // Only start tracking if the pointer is inside the grid.\n    // Use lastPointer when available (precise grid margins); fall back to canvas bounds\n    // so touch-only devices (where mousemove may never fire) are not locked out.\n    const inGrid = lastPointer\n      ? lastPointer.isInGrid\n      : isPointInGrid(e, eventManager.canvas);\n\n    if (!inGrid) return;\n\n    // Reject 3+ simultaneous pointers to prevent corrupting pinch state.\n    if (activePointers.size >= 2) return;\n\n    activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n\n    // Capture pointer so up/cancel events route here even if the finger slides off canvas.\n    eventManager.canvas.setPointerCapture(e.pointerId);\n\n    // Reset pinch state when pointer count changes (transition between pan/pinch).\n    resetPinchState();\n  };\n\n  const onTouchPointerMove = (e: PointerEvent): void => {\n    if (!enabled || disposed) return;\n    if (e.pointerType !== 'touch') return;\n    if (!activePointers.has(e.pointerId)) return;\n\n    const pointerCount = activePointers.size;\n\n    if (pointerCount === 1) {\n      // --- Single-finger pan ---\n      const prev = activePointers.get(e.pointerId);\n      if (!prev) return;\n\n      const dxCss = e.clientX - prev.x;\n      activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n\n      if (!Number.isFinite(dxCss) || dxCss === 0) return;\n\n      const plotWidthCss = lastPointer?.plotWidthCss ?? 0;\n      if (!(plotWidthCss > 0)) return;\n\n      const { start, end } = zoomState.getRange();\n      const span = end - start;\n      if (!Number.isFinite(span) || span === 0) return;\n\n      const deltaPct = -(dxCss / plotWidthCss) * span;\n      if (!Number.isFinite(deltaPct) || deltaPct === 0) return;\n\n      zoomState.pan(deltaPct);\n    } else if (pointerCount === 2) {\n      // --- Pinch-to-zoom ---\n      // Update the moved pointer position first.\n      activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n\n      const iter = activePointers.values();\n      const p1 = iter.next().value!;\n      const p2 = iter.next().value!;\n\n      const currentDist = Math.hypot(p1.x - p2.x, p1.y - p2.y);\n      const currentMidX = (p1.x + p2.x) / 2;\n\n      if (!Number.isFinite(currentDist) || currentDist === 0) return;\n\n      if (previousPinchDist > 0 && Number.isFinite(previousPinchDist)) {\n        const ratio = previousPinchDist / currentDist;\n\n        // Compute zoom center in percent space from the midpoint.\n        const canvas = eventManager.canvas;\n        const rect = canvas.getBoundingClientRect();\n        const plotWidthCss = lastPointer?.plotWidthCss ?? 0;\n        if (!(plotWidthCss > 0) || rect.width === 0) {\n          previousPinchDist = currentDist;\n          return;\n        }\n\n        // Estimate grid-left offset from lastPointer (gridX is relative to plot area).\n        // plotLeftCss = lastPointer.x - lastPointer.gridX gives the plot area's left edge\n        // in canvas CSS coordinates. The midpoint in canvas CSS is (currentMidX - rect.left).\n        const plotLeftCss = lastPointer\n          ? lastPointer.x - lastPointer.gridX\n          : 0;\n        const midGridX = currentMidX - rect.left - plotLeftCss;\n        const r = clamp(midGridX / plotWidthCss, 0, 1);\n\n        const { start, end } = zoomState.getRange();\n        const span = end - start;\n        if (!Number.isFinite(span) || span === 0) {\n          previousPinchDist = currentDist;\n          return;\n        }\n\n        const centerPct = clamp(start + r * span, 0, 100);\n\n        // Apply zoom based on pinch direction.\n        // ratio > 1 means fingers got closer => zoom out (pass ratio directly as factor).\n        // ratio < 1 means fingers spread apart => zoom in (invert to get factor > 1).\n        if (ratio > 1) {\n          zoomState.zoomOut(centerPct, ratio);\n        } else if (ratio > 0 && ratio < 1) {\n          zoomState.zoomIn(centerPct, 1 / ratio);\n        }\n      }\n\n      previousPinchDist = currentDist;\n    }\n  };\n\n  const onTouchPointerUp = (e: PointerEvent): void => {\n    if (!enabled || disposed) return;\n    if (e.pointerType !== 'touch') return;\n    activePointers.delete(e.pointerId);\n    resetPinchState();\n  };\n\n  const onTouchPointerCancel = (e: PointerEvent): void => {\n    if (!enabled || disposed) return;\n    if (e.pointerType !== 'touch') return;\n    activePointers.delete(e.pointerId);\n    resetPinchState();\n  };\n\n  // --- Enable / disable / dispose ---\n\n  const enable: InsideZoom['enable'] = () => {\n    if (disposed || enabled) return;\n    enabled = true;\n    eventManager.on('mousemove', onMouseMove);\n    eventManager.on('mouseleave', onMouseLeave);\n    eventManager.canvas.addEventListener('wheel', onWheel, { passive: false });\n\n    // Touch gesture listeners on canvas (only on touch-capable devices to avoid\n    // registering passive-false pointermove on desktop where it degrades scroll perf).\n    if (isTouchDevice) {\n      const canvas = eventManager.canvas;\n      savedTouchAction = canvas.style.touchAction;\n      canvas.style.touchAction = 'none';\n      canvas.addEventListener('pointerdown', onTouchPointerDown, { passive: false });\n      canvas.addEventListener('pointermove', onTouchPointerMove, { passive: false });\n      canvas.addEventListener('pointerup', onTouchPointerUp);\n      canvas.addEventListener('pointercancel', onTouchPointerCancel);\n    }\n  };\n\n  const disable: InsideZoom['disable'] = () => {\n    if (disposed || !enabled) return;\n    enabled = false;\n    eventManager.off('mousemove', onMouseMove);\n    eventManager.off('mouseleave', onMouseLeave);\n    eventManager.canvas.removeEventListener('wheel', onWheel);\n\n    // Remove touch gesture listeners.\n    if (isTouchDevice) {\n      const canvas = eventManager.canvas;\n      canvas.style.touchAction = savedTouchAction;\n      canvas.removeEventListener('pointerdown', onTouchPointerDown);\n      canvas.removeEventListener('pointermove', onTouchPointerMove);\n      canvas.removeEventListener('pointerup', onTouchPointerUp);\n      canvas.removeEventListener('pointercancel', onTouchPointerCancel);\n    }\n\n    activePointers.clear();\n    resetPinchState();\n    lastPointer = null;\n    clearPan();\n  };\n\n  const dispose: InsideZoom['dispose'] = () => {\n    if (disposed) return;\n    disable();\n    disposed = true;\n  };\n\n  return { enable, disable, dispose };\n}\n\n","export type ZoomRange = Readonly<{ start: number; end: number }>;\n\nexport type ZoomRangeChangeCallback = (range: ZoomRange) => void;\n\nexport interface ZoomState {\n  /**\n   * Returns the current zoom window in percent space, clamped to [0, 100].\n   */\n  getRange(): ZoomRange;\n  /**\n   * Sets the zoom window in percent space.\n   */\n  setRange(start: number, end: number): void;\n  /**\n   * Zooms in around `center` by shrinking the span by `factor`.\n   *\n   * `factor <= 1` is treated as a no-op.\n   */\n  zoomIn(center: number, factor: number): void;\n  /**\n   * Zooms out around `center` by growing the span by `factor`.\n   *\n   * `factor <= 1` is treated as a no-op.\n   */\n  zoomOut(center: number, factor: number): void;\n  /**\n   * Pans the zoom window by `delta` percent points (preserving span).\n   */\n  pan(delta: number): void;\n  /**\n   * Subscribes to changes. Returns an unsubscribe function.\n   */\n  onChange(callback: ZoomRangeChangeCallback): () => void;\n}\n\nexport type ZoomSpanConstraints = Readonly<{\n  /**\n   * Minimum allowed span (percent points in [0, 100]).\n   */\n  readonly minSpan?: number;\n  /**\n   * Maximum allowed span (percent points in [0, 100]).\n   */\n  readonly maxSpan?: number;\n}>;\n\nexport type ZoomRangeAnchor =\n  | 'start'\n  | 'end'\n  | 'center'\n  | Readonly<{ center: number; ratio: number }>;\n\nexport interface ZoomStateWithConstraints extends ZoomState {\n  /**\n   * Updates span constraints at runtime (used by coordinator on setOption/appendData).\n   *\n   * Passing `undefined` leaves that constraint unchanged.\n   */\n  setSpanConstraints(minSpan?: number, maxSpan?: number): void;\n  /**\n   * Sets a range with an explicit anchor for clamping (used by slider handles).\n   */\n  setRangeAnchored(start: number, end: number, anchor: ZoomRangeAnchor): void;\n}\n\n// Minimum span of 0.5% prevents zooming beyond what can be reasonably visualized\n// and prevents the slider UI from becoming unusably collapsed.\n// At 0.5% span, a 500px track shows a 2.5px window, which with 10px handles\n// is still somewhat distinguishable. Below 0.5% the UI becomes meaningless.\nconst DEFAULT_MIN_SPAN = 0.5;\nconst DEFAULT_MAX_SPAN = 100;\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));\nconst clamp01 = (v: number): number => clamp(v, 0, 1);\n\nconst normalizeZero = (v: number): number => (Object.is(v, -0) ? 0 : v);\n\nconst copyRange = (r: ZoomRange): ZoomRange => ({ start: r.start, end: r.end });\n\nexport function createZoomState(\n  initialStart: number,\n  initialEnd: number,\n  constraints?: ZoomSpanConstraints,\n): ZoomStateWithConstraints {\n  let start = 0;\n  let end = 100;\n  let lastEmitted: ZoomRange | null = null;\n\n  const listeners = new Set<ZoomRangeChangeCallback>();\n\n  let minSpan = (() => {\n    const v = Number.isFinite(constraints?.minSpan) ? (constraints!.minSpan as number) : DEFAULT_MIN_SPAN;\n    return clamp(Number.isFinite(v) ? v : 0, 0, 100);\n  })();\n\n  let maxSpan = (() => {\n    const v = Number.isFinite(constraints?.maxSpan) ? (constraints!.maxSpan as number) : DEFAULT_MAX_SPAN;\n    return clamp(Number.isFinite(v) ? v : 100, 0, 100);\n  })();\n\n  let normalizedMinSpan = Math.min(minSpan, maxSpan);\n  let normalizedMaxSpan = Math.max(minSpan, maxSpan);\n\n  const emit = (): void => {\n    const next: ZoomRange = { start, end };\n    if (\n      lastEmitted !== null &&\n      lastEmitted.start === next.start &&\n      lastEmitted.end === next.end\n    ) {\n      return;\n    }\n\n    lastEmitted = copyRange(next);\n\n    // Emit to a snapshot so additions/removals during emit don't affect this flush.\n    const snapshot = Array.from(listeners);\n    for (const cb of snapshot) cb({ start, end });\n  };\n\n  const toAnchor = (nextStart: number, nextEnd: number, spec?: ZoomRangeAnchor): { readonly center: number; readonly ratio: number } | undefined => {\n    if (!spec) return undefined;\n    if (typeof spec === 'string') {\n      switch (spec) {\n        case 'start':\n          return { center: nextStart, ratio: 0 };\n        case 'end':\n          return { center: nextEnd, ratio: 1 };\n        case 'center':\n          return { center: (nextStart + nextEnd) * 0.5, ratio: 0.5 };\n      }\n    }\n    if (spec && Number.isFinite(spec.center) && Number.isFinite(spec.ratio)) {\n      return { center: spec.center, ratio: spec.ratio };\n    }\n    return undefined;\n  };\n\n  const applyNextRange = (\n    nextStart: number,\n    nextEnd: number,\n    options?: { readonly emit?: boolean; readonly anchor?: { readonly center: number; readonly ratio: number } },\n  ): void => {\n    if (!Number.isFinite(nextStart) || !Number.isFinite(nextEnd)) return;\n\n    let s = nextStart;\n    let e = nextEnd;\n\n    if (s > e) {\n      const t = s;\n      s = e;\n      e = t;\n    }\n\n    // Enforce span constraints by resizing around the proposed midpoint.\n    let span = e - s;\n    if (!Number.isFinite(span) || span < 0) return;\n\n    const targetSpan = clamp(span, normalizedMinSpan, normalizedMaxSpan);\n    if (targetSpan !== span) {\n      const anchorCenter =\n        options?.anchor && Number.isFinite(options.anchor.center)\n          ? clamp(options.anchor.center, 0, 100)\n          : (s + e) * 0.5;\n      const anchorRatio =\n        options?.anchor && Number.isFinite(options.anchor.ratio)\n          ? clamp01(options.anchor.ratio)\n          : 0.5;\n\n      // Resize around the anchor so zoom operations preserve the cursor location.\n      s = anchorCenter - anchorRatio * targetSpan;\n      e = s + targetSpan;\n      span = targetSpan;\n    }\n\n    // If span exceeds bounds (shouldn't happen with normalizedMaxSpan <= 100), clamp to full extent.\n    if (span > 100) {\n      s = 0;\n      e = 100;\n      span = 100;\n    }\n\n    // Shift into bounds without changing span.\n    if (s < 0) {\n      const shift = -s;\n      s += shift;\n      e += shift;\n    }\n    if (e > 100) {\n      const shift = e - 100;\n      s -= shift;\n      e -= shift;\n    }\n\n    // Final clamp for tiny floating point drift.\n    s = clamp(s, 0, 100);\n    e = clamp(e, 0, 100);\n\n    s = normalizeZero(s);\n    e = normalizeZero(e);\n\n    if (s === start && e === end) return;\n    start = s;\n    end = e;\n\n    if (options?.emit === false) return;\n    emit();\n  };\n\n  // Initialize state (no emit by default).\n  applyNextRange(initialStart, initialEnd, { emit: false });\n\n  const getRange: ZoomState['getRange'] = () => ({ start, end });\n\n  const setRange: ZoomState['setRange'] = (nextStart, nextEnd) => {\n    applyNextRange(nextStart, nextEnd);\n  };\n\n  const setRangeAnchored: ZoomStateWithConstraints['setRangeAnchored'] = (nextStart, nextEnd, anchor) => {\n    applyNextRange(nextStart, nextEnd, { anchor: toAnchor(nextStart, nextEnd, anchor) });\n  };\n\n  const setSpanConstraints: ZoomStateWithConstraints['setSpanConstraints'] = (nextMinSpan, nextMaxSpan) => {\n    // Undefined => leave unchanged (lets coordinator reapply dynamically computed values explicitly).\n    const nextMin =\n      typeof nextMinSpan === 'number' && Number.isFinite(nextMinSpan) ? clamp(nextMinSpan, 0, 100) : minSpan;\n    const nextMax =\n      typeof nextMaxSpan === 'number' && Number.isFinite(nextMaxSpan) ? clamp(nextMaxSpan, 0, 100) : maxSpan;\n\n    if (nextMin === minSpan && nextMax === maxSpan) return;\n\n    minSpan = nextMin;\n    maxSpan = nextMax;\n    normalizedMinSpan = Math.min(minSpan, maxSpan);\n    normalizedMaxSpan = Math.max(minSpan, maxSpan);\n\n    // If the current range violates the new constraints, clamp it.\n    // Heuristic anchors keep \"pinned to start/end\" views stable (auto-scroll and common UX).\n    const s = start;\n    const e = end;\n    const eps = 1e-6;\n    const anchor: ZoomRangeAnchor =\n      e >= 100 - eps ? 'end' : s <= 0 + eps ? 'start' : 'center';\n    applyNextRange(s, e, { anchor: toAnchor(s, e, anchor) });\n  };\n\n  const zoomIn: ZoomState['zoomIn'] = (center, factor) => {\n    if (!Number.isFinite(center) || !Number.isFinite(factor)) return;\n    if (factor <= 1) return;\n\n    const c = clamp(center, 0, 100);\n    const span = end - start;\n    const r = span === 0 ? 0.5 : clamp01((c - start) / span);\n    const nextSpan = span / factor;\n    const nextStart = c - r * nextSpan;\n    const nextEnd = nextStart + nextSpan;\n    applyNextRange(nextStart, nextEnd, { anchor: { center: c, ratio: r } });\n  };\n\n  const zoomOut: ZoomState['zoomOut'] = (center, factor) => {\n    if (!Number.isFinite(center) || !Number.isFinite(factor)) return;\n    if (factor <= 1) return;\n\n    const c = clamp(center, 0, 100);\n    const span = end - start;\n    const r = span === 0 ? 0.5 : clamp01((c - start) / span);\n    const nextSpan = span * factor;\n    const nextStart = c - r * nextSpan;\n    const nextEnd = nextStart + nextSpan;\n    applyNextRange(nextStart, nextEnd, { anchor: { center: c, ratio: r } });\n  };\n\n  const pan: ZoomState['pan'] = (delta) => {\n    if (!Number.isFinite(delta)) return;\n    applyNextRange(start + delta, end + delta);\n  };\n\n  const onChange: ZoomState['onChange'] = (callback) => {\n    listeners.add(callback);\n    return () => {\n      listeners.delete(callback);\n    };\n  };\n\n  return { getRange, setRange, setRangeAnchored, setSpanConstraints, zoomIn, zoomOut, pan, onChange };\n}\n\n","import type { DataPoint, CartesianSeriesData } from '../config/types';\nimport type { ResolvedBarSeriesConfig, ResolvedSeriesConfig } from '../config/OptionResolver';\nimport type { LinearScale } from '../utils/scales';\nimport { computeBarLayoutPx } from './findNearestPoint';\nimport { getPointCount, getX, getY, getSize } from '../data/cartesianData';\n\nexport type PointsAtXMatch = Readonly<{\n  seriesIndex: number;\n  dataIndex: number;\n  point: DataPoint;\n}>;\n\nconst hasNaNXCache = new WeakMap<object, boolean>();\n\nconst seriesHasNaNX = (data: CartesianSeriesData): boolean => {\n  // Use data object as cache key (works for arrays, typed arrays, and XYArraysData)\n  const cacheKey = typeof data === 'object' && data !== null ? data : null;\n  if (cacheKey && hasNaNXCache.has(cacheKey)) {\n    return hasNaNXCache.get(cacheKey)!;\n  }\n\n  let hasNaN = false;\n  const n = getPointCount(data);\n\n  for (let i = 0; i < n; i++) {\n    const x = getX(data, i);\n    if (Number.isNaN(x)) {\n      hasNaN = true;\n      break;\n    }\n  }\n\n  if (cacheKey) hasNaNXCache.set(cacheKey, hasNaN);\n  return hasNaN;\n};\n\ntype BarHitTestLayout = Readonly<{\n  /** bar width in xScale range units (grid-local CSS px) */\n  barWidth: number;\n  /** gap between cluster slots, in xScale range units */\n  gap: number;\n  /** total cluster width (all bar slots), in xScale range units */\n  clusterWidth: number;\n  /** maps global series index -> cluster slot index */\n  clusterIndexByGlobalSeriesIndex: ReadonlyMap<number, number>;\n}>;\n\nconst computeBarHitTestLayout = (\n  series: ReadonlyArray<ResolvedSeriesConfig>,\n  xScale: LinearScale\n): BarHitTestLayout | null => {\n  // Mirror the bar renderer's shared layout math via `computeBarLayoutPx(...)`, but in xScale range units.\n  // IMPORTANT: Bar layout depends on all bar series (stacking + grouped slots), not per-series.\n  const barSeries: { readonly globalSeriesIndex: number; readonly s: ResolvedBarSeriesConfig }[] = [];\n  for (let i = 0; i < series.length; i++) {\n    const s = series[i];\n    if (s?.type === 'bar') barSeries.push({ globalSeriesIndex: i, s });\n  }\n  if (barSeries.length === 0) return null;\n\n  const layout = computeBarLayoutPx(\n    barSeries.map((b) => b.s),\n    xScale\n  );\n\n  const barWidthRange = layout.barWidthPx;\n  const gap = layout.gapPx;\n  const clusterWidth = layout.clusterWidthPx;\n  if (!Number.isFinite(barWidthRange) || !(barWidthRange > 0)) return null;\n\n  const clusterIndexByGlobalSeriesIndex = new Map<number, number>();\n  for (let i = 0; i < barSeries.length; i++) {\n    const globalSeriesIndex = barSeries[i].globalSeriesIndex;\n    const clusterIndex = layout.clusterSlots.clusterIndexBySeries[i] ?? 0;\n    clusterIndexByGlobalSeriesIndex.set(globalSeriesIndex, clusterIndex);\n  }\n\n  return {\n    barWidth: barWidthRange,\n    gap,\n    clusterWidth,\n    clusterIndexByGlobalSeriesIndex,\n  };\n};\n\nconst lowerBoundX = (data: CartesianSeriesData, xTarget: number): number => {\n  let lo = 0;\n  let hi = getPointCount(data);\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const x = getX(data, mid);\n    if (x < xTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n};\n\n/**\n * Finds (at most) one nearest point per series at a given x position.\n *\n * Coordinate system contract (mirrors `findNearestPoint`):\n * - `xValue` and optional `tolerance` MUST be in the same units as `xScale` **range**.\n *   (Example: if your `xScale.range()` is in grid-local CSS pixels, pass `payload.gridX` from `createEventManager`.)\n *   Note: ChartGPU's internal renderer scales are currently in clip space (NDC, typically \\[-1, 1\\]); in that case\n *   convert your pointer x into clip space before calling this helper.\n *\n * Behavior:\n * - Assumes each series is sorted by increasing x in domain space.\n * - Uses a lower-bound binary search in domain-x, then expands outward while x-distance alone can still improve.\n * - Skips points with non-finite domain x or non-finite scaled x. If a series contains any NaN x values, this helper\n *   falls back to an O(n) scan for correctness (NaN breaks total ordering for binary search).\n * - Stable tie-breaking: for equal distance, chooses the smaller `dataIndex`.\n *\n * If `tolerance` is provided, it is interpreted in **xScale range units**. Matches beyond tolerance are omitted.\n * If `tolerance` is omitted (or non-finite), the nearest point per series is returned when possible.\n *\n * Bar series special-case:\n * - Bars occupy x-intervals \\([left, right)\\) in **xScale range units** (grid-local CSS px for interaction scales),\n *   using the same shared layout math as the bar renderer (grouping + stacking slots).\n * - If `tolerance` is finite, a bar match is only returned when `xValue` falls inside the bar interval expanded by\n *   `tolerance` on both sides: \\([left - tolerance, right + tolerance)\\).\n * - If `tolerance` is omitted / non-finite, we first attempt an exact interval hit (no expansion) and otherwise fall\n *   back to the existing nearest-x behavior (so axis-trigger tooltips still work away from bars).\n */\nexport function findPointsAtX(\n  series: ReadonlyArray<ResolvedSeriesConfig>,\n  xValue: number,\n  xScale: LinearScale,\n  tolerance?: number,\n): ReadonlyArray<PointsAtXMatch> {\n  if (!Number.isFinite(xValue)) return [];\n\n  const maxDx =\n    tolerance === undefined || !Number.isFinite(tolerance) ? Number.POSITIVE_INFINITY : Math.max(0, tolerance);\n  const maxDxSq = maxDx * maxDx;\n\n  const xTarget = xScale.invert(xValue);\n  if (!Number.isFinite(xTarget)) return [];\n\n  const matches: PointsAtXMatch[] = [];\n  const barLayout = computeBarHitTestLayout(series, xScale);\n\n  for (let s = 0; s < series.length; s++) {\n    const seriesConfig = series[s];\n    // Pie and candlestick are non-cartesian (or not yet implemented); they can't match an x position.\n    if (seriesConfig.type === 'pie' || seriesConfig.type === 'candlestick') continue;\n\n    // Skip invisible series.\n    if (seriesConfig.visible === false) continue;\n\n    const data = seriesConfig.data as CartesianSeriesData;\n    const n = getPointCount(data);\n    if (n === 0) continue;\n\n    // Bar series: return the correct bar dataIndex for xValue when inside the bar interval.\n    // When tolerance is finite: require an (expanded) interval hit.\n    // When tolerance is non-finite: attempt exact hit, otherwise fall back to nearest-x behavior below.\n    if (seriesConfig.type === 'bar' && barLayout) {\n      const clusterIndex = barLayout.clusterIndexByGlobalSeriesIndex.get(s);\n      if (clusterIndex !== undefined) {\n        const { barWidth, gap, clusterWidth } = barLayout;\n        const offsetLeftFromCategoryCenter = -clusterWidth / 2 + clusterIndex * (barWidth + gap);\n\n        const hitTol =\n          tolerance === undefined || !Number.isFinite(tolerance) ? 0 : Math.max(0, tolerance);\n\n        // If we can't safely compute an interval hit, don't guess when tolerance is finite.\n        if (Number.isFinite(barWidth) && barWidth > 0 && Number.isFinite(offsetLeftFromCategoryCenter)) {\n          let hitIndex = -1;\n\n          const isHit = (xCenterRange: number): boolean => {\n            if (!Number.isFinite(xCenterRange)) return false;\n            const left = xCenterRange + offsetLeftFromCategoryCenter;\n            const right = left + barWidth;\n            // Expanded interval: [left - tol, right + tol)\n            return xValue >= left - hitTol && xValue < right + hitTol;\n          };\n\n          if (seriesHasNaNX(data)) {\n            // NaN breaks ordering; linear scan for correctness.\n            for (let i = 0; i < n; i++) {\n              const px = getX(data, i);\n              if (!Number.isFinite(px)) continue;\n              const xCenter = xScale.scale(px);\n              if (isHit(xCenter)) {\n                hitIndex = hitIndex < 0 ? i : Math.min(hitIndex, i);\n              }\n            }\n          } else {\n            // Use a lower-bound search around the adjusted x (accounts for cluster offset).\n            const xTargetAdjusted = xScale.invert(xValue - offsetLeftFromCategoryCenter);\n            if (Number.isFinite(xTargetAdjusted)) {\n              const insertionIndex = lowerBoundX(data, xTargetAdjusted);\n\n              const getXCenterAt = (idx: number): number | null => {\n                if (idx < 0 || idx >= n) return null;\n                const px = getX(data, idx);\n                if (!Number.isFinite(px)) return null;\n                const xCenter = xScale.scale(px);\n                return Number.isFinite(xCenter) ? xCenter : null;\n              };\n\n              // Scan left while intervals could still contain xValue.\n              for (let i = insertionIndex - 1; i >= 0; i--) {\n                const xCenter = getXCenterAt(i);\n                if (xCenter === null) continue;\n                const left = xCenter + offsetLeftFromCategoryCenter;\n                const right = left + barWidth;\n                if (right + hitTol <= xValue) break;\n                if (xValue >= left - hitTol && xValue < right + hitTol) {\n                  hitIndex = hitIndex < 0 ? i : Math.min(hitIndex, i);\n                }\n              }\n\n              // Scan right until intervals start strictly after xValue.\n              for (let i = insertionIndex; i < n; i++) {\n                const xCenter = getXCenterAt(i);\n                if (xCenter === null) continue;\n                const left = xCenter + offsetLeftFromCategoryCenter;\n                if (left - hitTol > xValue) break;\n                const right = left + barWidth;\n                if (xValue < right + hitTol) {\n                  hitIndex = hitIndex < 0 ? i : Math.min(hitIndex, i);\n                }\n              }\n            }\n          }\n\n          if (hitIndex >= 0) {\n            const x = getX(data, hitIndex);\n            const y = getY(data, hitIndex);\n            const size = getSize(data, hitIndex);\n            const point: DataPoint = size !== undefined ? [x, y, size] : [x, y];\n            matches.push({ seriesIndex: s, dataIndex: hitIndex, point });\n            continue;\n          }\n\n          // If tolerance is finite, require a hit (no nearest-x fallback).\n          if (tolerance !== undefined && Number.isFinite(tolerance)) {\n            continue;\n          }\n          // Else: fall through to nearest-x behavior (existing logic) for axis-trigger tooltips.\n        } else if (tolerance !== undefined && Number.isFinite(tolerance)) {\n          continue;\n        }\n      }\n    }\n\n    let bestDataIndex = -1;\n    let bestPoint: DataPoint | null = null;\n    let bestDxSq = maxDxSq;\n\n    const tryUpdate = (idx: number, dxSq: number) => {\n      if (!Number.isFinite(dxSq)) return;\n      const isBetter =\n        dxSq < bestDxSq || (dxSq === bestDxSq && (bestDataIndex < 0 || idx < bestDataIndex));\n      if (!isBetter) return;\n      bestDxSq = dxSq;\n      bestDataIndex = idx;\n      // Construct DataPoint for return\n      const x = getX(data, idx);\n      const y = getY(data, idx);\n      const size = getSize(data, idx);\n      bestPoint = size !== undefined ? [x, y, size] : [x, y];\n    };\n\n    // If the series contains NaN x values, binary search cannot be trusted (NaN breaks ordering).\n    // Fall back to a linear scan for correctness. Cached per data array for performance.\n    if (seriesHasNaNX(data)) {\n      for (let i = 0; i < n; i++) {\n        const px = getX(data, i);\n        if (!Number.isFinite(px)) continue;\n        const sx = xScale.scale(px);\n        if (!Number.isFinite(sx)) continue;\n        const dx = sx - xValue;\n        tryUpdate(i, dx * dx);\n      }\n    } else {\n      const insertionIndex = lowerBoundX(data, xTarget);\n\n      let left = insertionIndex - 1;\n      let right = insertionIndex;\n\n      const dxSqAt = (idx: number): number | null => {\n        const px = getX(data, idx);\n        if (!Number.isFinite(px)) return null;\n        const sx = xScale.scale(px);\n        if (!Number.isFinite(sx)) return null;\n        const dx = sx - xValue;\n        return dx * dx;\n      };\n\n      while (left >= 0 || right < n) {\n        while (left >= 0 && dxSqAt(left) === null) left--;\n        while (right < n && dxSqAt(right) === null) right++;\n        if (left < 0 && right >= n) break;\n\n        const dxSqLeft = left >= 0 ? (dxSqAt(left) ?? Number.POSITIVE_INFINITY) : Number.POSITIVE_INFINITY;\n        const dxSqRight = right < n ? (dxSqAt(right) ?? Number.POSITIVE_INFINITY) : Number.POSITIVE_INFINITY;\n\n        if (dxSqLeft > bestDxSq && dxSqRight > bestDxSq) break;\n\n        // If both sides are equally close in x, evaluate left first (smaller index) for stable ties.\n        if (dxSqLeft <= dxSqRight) {\n          if (left >= 0 && dxSqLeft <= bestDxSq) tryUpdate(left, dxSqLeft);\n          left--;\n          if (right < n && dxSqRight <= bestDxSq && dxSqRight === dxSqLeft) {\n            tryUpdate(right, dxSqRight);\n            right++;\n          }\n        } else {\n          if (right < n && dxSqRight <= bestDxSq) tryUpdate(right, dxSqRight);\n          right++;\n        }\n      }\n    }\n\n    if (bestPoint !== null) matches.push({ seriesIndex: s, dataIndex: bestDataIndex, point: bestPoint });\n  }\n\n  return matches;\n}\n\n","import type { ResolvedCandlestickSeriesConfig } from '../config/OptionResolver';\nimport type { OHLCDataPoint, OHLCDataPointTuple } from '../config/types';\nimport type { LinearScale } from '../utils/scales';\n\nexport interface CandlestickMatch {\n  seriesIndex: number;\n  dataIndex: number;\n  point: OHLCDataPoint;\n}\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\n\nconst parsePercent = (value: string): number | null => {\n  const m = value.trim().match(/^(\\d+(?:\\.\\d+)?)%$/);\n  if (!m) return null;\n  const p = Number(m[1]) / 100;\n  return Number.isFinite(p) ? p : null;\n};\n\nconst isTupleDataPoint = (p: OHLCDataPoint): p is OHLCDataPointTuple => Array.isArray(p);\n\nconst getTimestamp = (p: OHLCDataPoint): number => (isTupleDataPoint(p) ? p[0] : p.timestamp);\nconst getOpen = (p: OHLCDataPoint): number => (isTupleDataPoint(p) ? p[1] : p.open);\nconst getClose = (p: OHLCDataPoint): number => (isTupleDataPoint(p) ? p[2] : p.close);\n\nconst categoryStepCache = new WeakMap<ReadonlyArray<OHLCDataPoint>, number>();\n\nconst computeCategoryStep = (data: ReadonlyArray<OHLCDataPoint>): number => {\n  const cached = categoryStepCache.get(data);\n  if (cached !== undefined) return cached;\n\n  const timestamps: number[] = [];\n  for (let i = 0; i < data.length; i++) {\n    const t = getTimestamp(data[i]);\n    if (Number.isFinite(t)) timestamps.push(t);\n  }\n\n  if (timestamps.length < 2) return 1;\n  timestamps.sort((a, b) => a - b);\n\n  let minStep = Number.POSITIVE_INFINITY;\n  for (let i = 1; i < timestamps.length; i++) {\n    const d = timestamps[i] - timestamps[i - 1];\n    if (d > 0 && d < minStep) minStep = d;\n  }\n  const step = Number.isFinite(minStep) && minStep > 0 ? minStep : 1;\n  categoryStepCache.set(data, step);\n  return step;\n};\n\n/**\n * Computes the candlestick body width in xScale **range-space** units.\n *\n * Notes:\n * - This mirrors `createCandlestickRenderer.ts` bar width semantics, but stays in range units\n *   (CSS pixels in ChartGPU interaction usage).\n * - No DPR conversions are applied here.\n */\nexport function computeCandlestickBodyWidthRange(\n  series: ResolvedCandlestickSeriesConfig,\n  data: ReadonlyArray<OHLCDataPoint>,\n  xScale: LinearScale,\n  plotWidthFallback?: number,\n): number {\n  if (data.length === 0) return 0;\n\n  const categoryStep = computeCategoryStep(data);\n\n  // Prefer deriving category width from a domain step via xScale.scale(t0 + step) - xScale.scale(t0).\n  let categoryWidthRange = 0;\n  if (Number.isFinite(categoryStep) && categoryStep > 0) {\n    let t0: number | null = null;\n    for (let i = 0; i < data.length; i++) {\n      const t = getTimestamp(data[i]);\n      if (Number.isFinite(t)) {\n        t0 = t;\n        break;\n      }\n    }\n\n    if (t0 != null) {\n      const p0 = xScale.scale(t0);\n      const p1 = xScale.scale(t0 + categoryStep);\n      const w = Math.abs(p1 - p0);\n      if (Number.isFinite(w) && w > 0) categoryWidthRange = w;\n    }\n  }\n\n  // Fallback: approximate based on plot width and data length.\n  if (!(categoryWidthRange > 0) || !Number.isFinite(categoryWidthRange)) {\n    const plotW = Number.isFinite(plotWidthFallback ?? Number.NaN) ? (plotWidthFallback as number) : 0;\n    categoryWidthRange = plotW / Math.max(1, data.length);\n  }\n\n  // barWidth semantics:\n  // - number: width in range units\n  // - percent string: percent of category width in range units\n  let width = 0;\n  const rawBarWidth = series.barWidth;\n  if (typeof rawBarWidth === 'number') {\n    width = Number.isFinite(rawBarWidth) ? Math.max(0, rawBarWidth) : 0;\n  } else if (typeof rawBarWidth === 'string') {\n    const p = parsePercent(rawBarWidth);\n    width = p == null ? 0 : categoryWidthRange * clamp01(p);\n  }\n\n  // Clamp by min/max width (in CSS px; our range-space is CSS px in interaction usage).\n  const minW = Number.isFinite(series.barMinWidth) ? Math.max(0, series.barMinWidth) : 0;\n  const maxWCandidate = Number.isFinite(series.barMaxWidth) ? Math.max(0, series.barMaxWidth) : Number.POSITIVE_INFINITY;\n  const maxW = Math.max(minW, maxWCandidate);\n  width = Math.min(Math.max(width, minW), maxW);\n\n  return Number.isFinite(width) ? width : 0;\n}\n\nconst monotonicTimestampCache = new WeakMap<ReadonlyArray<OHLCDataPoint>, boolean>();\n\nconst isMonotonicNonDecreasingFiniteTimestamps = (data: ReadonlyArray<OHLCDataPoint>): boolean => {\n  const cached = monotonicTimestampCache.get(data);\n  if (cached !== undefined) return cached;\n\n  let prev = Number.NEGATIVE_INFINITY;\n  for (let i = 0; i < data.length; i++) {\n    const t = getTimestamp(data[i]);\n    if (!Number.isFinite(t)) {\n      monotonicTimestampCache.set(data, false);\n      return false;\n    }\n    if (t < prev) {\n      monotonicTimestampCache.set(data, false);\n      return false;\n    }\n    prev = t;\n  }\n  monotonicTimestampCache.set(data, true);\n  return true;\n};\n\nconst lowerBoundByTimestamp = (data: ReadonlyArray<OHLCDataPoint>, xTarget: number): number => {\n  let lo = 0;\n  let hi = data.length;\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1;\n    const t = getTimestamp(data[mid]);\n    if (t < xTarget) lo = mid + 1;\n    else hi = mid;\n  }\n  return lo;\n};\n\n/**\n * Finds the candlestick body under the given cursor position.\n *\n * Coordinate system contract:\n * - `x`/`y` MUST be in the same units as `xScale`/`yScale` **range-space**\n *   (ChartGPU interaction uses grid-local CSS pixels).\n *\n * Hit-test semantics:\n * - Body-only hit-testing (wicks ignored).\n * - A candle hits if:\n *   - `abs(x - xCenter) <= barWidth / 2`, AND\n *   - `y` is between the scaled `open` and `close` (inclusive).\n *\n * Performance:\n * - Per-series lower-bound binary search on timestamp, then scans left/right while x-distance alone can still hit.\n * - If timestamps are not monotonic non-decreasing finite numbers, falls back to an O(n) scan for correctness.\n *\n * Edge cases:\n * - Skips non-finite timestamps/open/close.\n * - If `barWidthClip` is non-finite or <= 0, returns null.\n * - Returns the closest in x (min abs dx) among hits; ties broken by smaller `dataIndex` (then smaller `seriesIndex`).\n */\nexport function findCandlestick(\n  series: ReadonlyArray<ResolvedCandlestickSeriesConfig>,\n  x: number,\n  y: number,\n  xScale: LinearScale,\n  yScale: LinearScale,\n  barWidthClip: number,\n): CandlestickMatch | null {\n  if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n  if (!Number.isFinite(barWidthClip) || !(barWidthClip > 0)) return null;\n\n  const xTarget = xScale.invert(x);\n  if (!Number.isFinite(xTarget)) return null;\n\n  const halfW = barWidthClip / 2;\n\n  let best: CandlestickMatch | null = null;\n  let bestDx = Number.POSITIVE_INFINITY;\n\n  const tryUpdate = (\n    seriesIndex: number,\n    dataIndex: number,\n    point: OHLCDataPoint,\n    dx: number,\n  ): void => {\n    if (!Number.isFinite(dx)) return;\n    if (dx < bestDx) {\n      bestDx = dx;\n      best = { seriesIndex, dataIndex, point };\n      return;\n    }\n    if (dx === bestDx && best) {\n      if (dataIndex < best.dataIndex) {\n        best = { seriesIndex, dataIndex, point };\n      } else if (dataIndex === best.dataIndex && seriesIndex < best.seriesIndex) {\n        best = { seriesIndex, dataIndex, point };\n      }\n    }\n  };\n\n  const isBodyHitAt = (p: OHLCDataPoint): boolean => {\n    const open = getOpen(p);\n    const close = getClose(p);\n    if (!Number.isFinite(open) || !Number.isFinite(close)) return false;\n\n    const yOpen = yScale.scale(open);\n    const yClose = yScale.scale(close);\n    if (!Number.isFinite(yOpen) || !Number.isFinite(yClose)) return false;\n\n    const yMin = Math.min(yOpen, yClose);\n    const yMax = Math.max(yOpen, yClose);\n    return y >= yMin && y <= yMax;\n  };\n\n  for (let s = 0; s < series.length; s++) {\n    const cfg = series[s];\n    const data = cfg.data;\n    const n = data.length;\n    if (n === 0) continue;\n\n    const monotonic = isMonotonicNonDecreasingFiniteTimestamps(data);\n\n    if (!monotonic) {\n      // Fallback O(n) scan for correctness.\n      for (let i = 0; i < n; i++) {\n        const p = data[i];\n        const t = getTimestamp(p);\n        if (!Number.isFinite(t)) continue;\n        const xCenter = xScale.scale(t);\n        if (!Number.isFinite(xCenter)) continue;\n\n        const dx = Math.abs(x - xCenter);\n        if (dx > halfW) continue;\n        if (!isBodyHitAt(p)) continue;\n\n        tryUpdate(s, i, p, dx);\n      }\n      continue;\n    }\n\n    const insertionIndex = lowerBoundByTimestamp(data, xTarget);\n\n    // Scan left while xCenter can still be within [x - halfW, x + halfW].\n    for (let i = insertionIndex - 1; i >= 0; i--) {\n      const p = data[i];\n      const t = getTimestamp(p);\n      const xCenter = xScale.scale(t);\n      if (!Number.isFinite(xCenter)) continue;\n      if (xCenter < x - halfW) break;\n\n      const dx = Math.abs(x - xCenter);\n      if (dx > halfW) continue;\n      if (!isBodyHitAt(p)) continue;\n\n      tryUpdate(s, i, p, dx);\n    }\n\n    // Scan right while xCenter can still be within [x - halfW, x + halfW].\n    for (let i = insertionIndex; i < n; i++) {\n      const p = data[i];\n      const t = getTimestamp(p);\n      const xCenter = xScale.scale(t);\n      if (!Number.isFinite(xCenter)) continue;\n      if (xCenter > x + halfW) break;\n\n      const dx = Math.abs(x - xCenter);\n      if (dx > halfW) continue;\n      if (!isBodyHitAt(p)) continue;\n\n      tryUpdate(s, i, p, dx);\n    }\n  }\n\n  return best;\n}\n\n","import type { ResolvedPieSeriesConfig } from '../config/OptionResolver';\n\nconst TAU = Math.PI * 2;\n\nconst wrapToTau = (thetaRad: number): number => {\n  if (!Number.isFinite(thetaRad)) return 0;\n  const t = thetaRad % TAU;\n  return t < 0 ? t + TAU : t;\n};\n\nexport type PieSliceMatch = Readonly<{\n  seriesIndex: number;\n  dataIndex: number;\n  slice: ResolvedPieSeriesConfig['data'][number];\n}>;\n\nexport type PieHitTestConfig = Readonly<{\n  seriesIndex: number;\n  series: ResolvedPieSeriesConfig;\n}>;\n\nexport type PieCenterCssPx = Readonly<{ x: number; y: number }>;\nexport type PieRadiusCssPx = Readonly<{ inner: number; outer: number }>;\n\n/**\n * Finds the pie slice under a given pointer position.\n *\n * Coordinate contract:\n * - `x`/`y` are plot/grid-local CSS pixels (origin at plot top-left, +y down).\n * - `center` is plot-local CSS pixels.\n * - `radius` is CSS pixels (inner/outer). Points within the donut hole are not hoverable.\n *\n * Angle conventions:\n * - Uses +y up for polar angle (to match `pie.wgsl` atan2(p.y, p.x)).\n * - Wraps angles to [0, 2π).\n * - Matches `createPieRenderer.ts` start angle default (90°).\n *\n * Value conventions:\n * - Ignores non-finite and non-positive slice values (mirrors renderer).\n */\nexport function findPieSlice(\n  x: number,\n  y: number,\n  pieConfig: PieHitTestConfig,\n  center: PieCenterCssPx,\n  radius: PieRadiusCssPx\n): PieSliceMatch | null {\n  if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n  if (!Number.isFinite(center.x) || !Number.isFinite(center.y)) return null;\n\n  const inner = Number.isFinite(radius.inner) ? Math.max(0, radius.inner) : 0;\n  const outer = Number.isFinite(radius.outer) ? Math.max(0, radius.outer) : 0;\n  if (!(outer > 0)) return null;\n\n  // Polar coordinates:\n  // - Pointer `y` is down in CSS px, but shader uses +y up (atan2(p.y, p.x)).\n  const dx = x - center.x;\n  const dyUp = center.y - y;\n  const r = Math.hypot(dx, dyUp);\n  if (!Number.isFinite(r)) return null;\n\n  // Donut hole is non-hoverable; outer bound must be inside.\n  if (r <= inner) return null;\n  if (r > outer) return null;\n\n  const angle = wrapToTau(Math.atan2(dyUp, dx));\n\n  const series = pieConfig.series;\n  const data = series.data;\n\n  // Total positive value for angle allocation (mirrors renderer, exclude hidden slices).\n  let total = 0;\n  let validCount = 0;\n  for (let i = 0; i < data.length; i++) {\n    const item = data[i];\n    const v = item?.value;\n    if (typeof v === 'number' && Number.isFinite(v) && v > 0 && item.visible !== false) {\n      total += v;\n      validCount++;\n    }\n  }\n  if (!(total > 0) || validCount === 0) return null;\n\n  const startDeg =\n    typeof series.startAngle === 'number' && Number.isFinite(series.startAngle) ? series.startAngle : 90;\n  let current = wrapToTau((startDeg * Math.PI) / 180);\n\n  // Mirror renderer float-drift mitigation: force last slice to close the circle.\n  let accumulated = 0;\n  let emitted = 0;\n\n  for (let i = 0; i < data.length; i++) {\n    const slice = data[i];\n    const v = slice?.value;\n    if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) continue;\n    // Skip hidden slices\n    if (slice?.visible === false) continue;\n\n    emitted++;\n    const isLast = emitted === validCount;\n\n    const frac = v / total;\n    let span = frac * TAU;\n    if (isLast) {\n      span = Math.max(0, TAU - accumulated);\n    } else {\n      span = Math.max(0, Math.min(TAU, span));\n    }\n    accumulated += span;\n    if (!(span > 0)) continue;\n\n    const start = current;\n    // When there's only one visible slice, it should span the full circle (0 to TAU).\n    // Don't wrap the end angle in this case, as wrapping (start + TAU) gives start again.\n    const end = validCount === 1 ? current + TAU : wrapToTau(current + span);\n    current = wrapToTau(current + span);\n\n    // Match `pie.wgsl` wedge test (span and rel in [0, TAU) with wrap).\n    let wedgeSpan = end - start;\n    if (wedgeSpan < 0) wedgeSpan += TAU;\n\n    let rel = angle - start;\n    if (rel < 0) rel += TAU;\n\n    if (rel <= wedgeSpan) {\n      return { seriesIndex: pieConfig.seriesIndex, dataIndex: i, slice };\n    }\n  }\n\n  return null;\n}\n\n","export interface LinearScale {\n  /**\n   * Sets the scale domain (data range). Returns self for chaining.\n   */\n  domain(min: number, max: number): LinearScale;\n\n  /**\n   * Sets the scale range (pixel range). Returns self for chaining.\n   */\n  range(min: number, max: number): LinearScale;\n\n  /**\n   * Maps a domain value to a range value.\n   *\n   * Notes:\n   * - No clamping (will extrapolate outside the domain).\n   * - If the domain span is 0 (min === max), returns the midpoint of the range.\n   */\n  scale(value: number): number;\n\n  /**\n   * Maps a range value (pixel) back to a domain value.\n   *\n   * Notes:\n   * - No clamping (will extrapolate outside the range).\n   * - If the domain span is 0 (min === max), returns domain min for any input.\n   */\n  invert(pixel: number): number;\n}\n\nexport interface CategoryScale {\n  /**\n   * Sets the category domain (ordered list of unique category names).\n   * Returns self for chaining.\n   *\n   * Throws if duplicates exist (ambiguous mapping).\n   */\n  domain(categories: string[]): CategoryScale;\n\n  /**\n   * Sets the scale range (pixel range). Returns self for chaining.\n   */\n  range(min: number, max: number): CategoryScale;\n\n  /**\n   * Returns the center x-position for a category.\n   *\n   * Edge cases:\n   * - Unknown category: returns NaN\n   * - Empty domain: returns midpoint of range\n   */\n  scale(category: string): number;\n\n  /**\n   * Width allocated per category (always non-negative).\n   *\n   * Edge cases:\n   * - Empty domain: returns 0\n   * - Reversed ranges allowed\n   */\n  bandwidth(): number;\n\n  /**\n   * Returns the index of a category in the current domain.\n   *\n   * Edge cases:\n   * - Unknown category: returns -1\n   */\n  categoryIndex(category: string): number;\n}\n\nconst assertFinite = (label: string, value: number): void => {\n  if (!Number.isFinite(value)) {\n    throw new Error(`${label} must be a finite number. Received: ${String(value)}`);\n  }\n};\n\n/**\n * Creates a linear scale for mapping a numeric domain to a numeric range.\n *\n * Defaults to an identity mapping:\n * domain [0, 1] -> range [0, 1]\n */\nexport function createLinearScale(): LinearScale {\n  let domainMin = 0;\n  let domainMax = 1;\n  let rangeMin = 0;\n  let rangeMax = 1;\n\n  const self: LinearScale = {\n    domain(min: number, max: number) {\n      assertFinite('domain min', min);\n      assertFinite('domain max', max);\n      domainMin = min;\n      domainMax = max;\n      return self;\n    },\n\n    range(min: number, max: number) {\n      assertFinite('range min', min);\n      assertFinite('range max', max);\n      rangeMin = min;\n      rangeMax = max;\n      return self;\n    },\n\n    scale(value: number) {\n      if (!Number.isFinite(value)) return Number.NaN;\n\n      if (domainMin === domainMax) {\n        return (rangeMin + rangeMax) / 2;\n      }\n\n      const t = (value - domainMin) / (domainMax - domainMin);\n      return rangeMin + t * (rangeMax - rangeMin);\n    },\n\n    invert(pixel: number) {\n      if (!Number.isFinite(pixel)) return Number.NaN;\n\n      if (domainMin === domainMax) {\n        return domainMin;\n      }\n\n      if (rangeMin === rangeMax) {\n        return (domainMin + domainMax) / 2;\n      }\n\n      const t = (pixel - rangeMin) / (rangeMax - rangeMin);\n      return domainMin + t * (domainMax - domainMin);\n    },\n  };\n\n  return self;\n}\n\n/**\n * Creates a category scale for mapping string categories to evenly spaced\n * x-positions across a numeric range.\n *\n * Defaults:\n * - domain: []\n * - range: [0, 1]\n */\nexport function createCategoryScale(): CategoryScale {\n  let categories: readonly string[] = [];\n  let indexByCategory = new Map<string, number>();\n  let rangeMin = 0;\n  let rangeMax = 1;\n\n  const rebuildIndex = (nextCategories: readonly string[]) => {\n    const nextIndex = new Map<string, number>();\n    for (let i = 0; i < nextCategories.length; i++) {\n      const c = nextCategories[i];\n      // Enforce uniqueness to avoid ambiguous mapping\n      if (nextIndex.has(c)) {\n        throw new Error(`Category domain must not contain duplicates. Duplicate: ${JSON.stringify(c)}`);\n      }\n      nextIndex.set(c, i);\n    }\n    indexByCategory = nextIndex;\n  };\n\n  const self: CategoryScale = {\n    domain(nextCategories: string[]) {\n      categories = [...nextCategories];\n      rebuildIndex(categories);\n      return self;\n    },\n\n    range(min: number, max: number) {\n      assertFinite('range min', min);\n      assertFinite('range max', max);\n      rangeMin = min;\n      rangeMax = max;\n      return self;\n    },\n\n    categoryIndex(category: string) {\n      const idx = indexByCategory.get(category);\n      return idx === undefined ? -1 : idx;\n    },\n\n    bandwidth() {\n      const n = categories.length;\n      if (n === 0) return 0;\n      return Math.abs((rangeMax - rangeMin) / n);\n    },\n\n    scale(category: string) {\n      const n = categories.length;\n      if (n === 0) {\n        return (rangeMin + rangeMax) / 2;\n      }\n\n      const i = self.categoryIndex(category);\n      if (i < 0) return Number.NaN;\n\n      const step = (rangeMax - rangeMin) / n; // can be negative (reversed range)\n      return rangeMin + (i + 0.5) * step;\n    },\n  };\n\n  return self;\n}\n","export type TextOverlayAnchor = 'start' | 'middle' | 'end';\n\nexport interface TextOverlayLabelOptions {\n  readonly fontSize?: number;\n  readonly color?: string;\n  readonly anchor?: TextOverlayAnchor;\n  /**\n   * Rotation in degrees (CSS `rotate(<deg>deg)`).\n   */\n  readonly rotation?: number;\n}\n\nexport interface TextOverlayOptions {\n  /**\n   * When true, clip labels to the overlay bounds (default: false).\n   * Prevents labels from overflowing outside the container.\n   */\n  readonly clip?: boolean;\n}\n\nexport interface TextOverlay {\n  clear(): void;\n  addLabel(\n    text: string,\n    x: number,\n    y: number,\n    options?: TextOverlayLabelOptions\n  ): HTMLSpanElement;\n  dispose(): void;\n}\n\nconst getAnchorTransform = (\n  anchor: TextOverlayAnchor\n): Readonly<{ translateX: string; originX: string }> => {\n  switch (anchor) {\n    case 'start':\n      return { translateX: '0%', originX: '0%' };\n    case 'middle':\n      return { translateX: '-50%', originX: '50%' };\n    case 'end':\n      return { translateX: '-100%', originX: '100%' };\n  }\n};\n\nexport function createTextOverlay(container: HTMLElement, options?: TextOverlayOptions): TextOverlay {\n  const computedStyle = getComputedStyle(container);\n  const computedPosition = computedStyle.position;\n  const computedOverflow = computedStyle.overflow;\n\n  const clip = options?.clip ?? false;\n\n  const didSetRelative = computedPosition === 'static';\n  const didSetOverflowVisible = !clip && (computedOverflow === 'hidden' || computedOverflow === 'scroll' || computedOverflow === 'auto');\n\n  const previousInlinePosition = didSetRelative ? container.style.position : null;\n  const previousInlineOverflow = didSetOverflowVisible ? container.style.overflow : null;\n\n  if (didSetRelative) {\n    container.style.position = 'relative';\n  }\n\n  if (didSetOverflowVisible) {\n    container.style.overflow = 'visible';\n  }\n\n  const overlay = document.createElement('div');\n  overlay.style.position = 'absolute';\n  overlay.style.inset = '0';\n  overlay.style.pointerEvents = 'none';\n  overlay.style.overflow = clip ? 'hidden' : 'visible';\n  overlay.style.zIndex = '10'; // Above zoom slider (z-index: 4) and other overlays\n  container.appendChild(overlay);\n\n  let disposed = false;\n\n  const clear = (): void => {\n    if (disposed) return;\n    overlay.replaceChildren();\n  };\n\n  const addLabel: TextOverlay['addLabel'] = (text, x, y, options) => {\n    if (disposed) {\n      // Keep it non-throwing so callsites don't need try/catch in teardown paths.\n      return document.createElement('span');\n    }\n\n    const span = document.createElement('span');\n    span.textContent = text;\n    span.style.position = 'absolute';\n    span.style.left = `${x}px`;\n    span.style.top = `${y}px`;\n    span.style.pointerEvents = 'none';\n    span.style.userSelect = 'none';\n    span.style.whiteSpace = 'nowrap';\n    span.style.lineHeight = '1';\n\n    if (options?.fontSize != null) span.style.fontSize = `${options.fontSize}px`;\n    if (options?.color != null) span.style.color = options.color;\n\n    const rotation = options?.rotation ?? 0;\n    const anchor = options?.anchor ?? 'start';\n    const { translateX, originX } = getAnchorTransform(anchor);\n\n    span.style.transformOrigin = `${originX} 50%`;\n    span.style.transform = `translateX(${translateX}) translateY(-50%) rotate(${rotation}deg)`;\n\n    overlay.appendChild(span);\n    return span;\n  };\n\n  const dispose = (): void => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      overlay.remove();\n    } finally {\n      if (previousInlinePosition !== null) {\n        container.style.position = previousInlinePosition;\n      }\n      if (previousInlineOverflow !== null) {\n        container.style.overflow = previousInlineOverflow;\n      }\n    }\n  };\n\n  return { clear, addLabel, dispose };\n}\n\n  ","import type { SeriesConfig } from '../config/types';\nimport type { ThemeConfig } from '../themes/types';\n\nexport type LegendPosition = 'top' | 'bottom' | 'left' | 'right';\n\nexport interface Legend {\n  update(series: ReadonlyArray<SeriesConfig>, theme: ThemeConfig): void;\n  dispose(): void;\n}\n\nconst getSeriesName = (series: SeriesConfig, index: number): string => {\n  const candidate = series.name?.trim();\n  return candidate ? candidate : `Series ${index + 1}`;\n};\n\nconst getSeriesColor = (\n  series: SeriesConfig,\n  index: number,\n  theme: ThemeConfig\n): string => {\n  const explicit = series.color?.trim();\n  if (explicit) return explicit;\n\n  const palette = theme.colorPalette;\n  if (palette.length > 0) return palette[index % palette.length] ?? '#000000';\n  return '#000000';\n};\n\nconst getPieSliceLabel = (sliceName: string | undefined, sliceIndex: number): string => {\n  const candidate = sliceName?.trim();\n  return candidate ? candidate : `Slice ${sliceIndex + 1}`;\n};\n\nconst getPieSliceColor = (\n  sliceColor: string | undefined,\n  seriesIndex: number,\n  sliceIndex: number,\n  theme: ThemeConfig\n): string => {\n  const explicit = sliceColor?.trim();\n  if (explicit) return explicit;\n\n  const palette = theme.colorPalette;\n  const len = palette.length;\n  if (len > 0) return palette[(seriesIndex + sliceIndex) % len] ?? '#000000';\n  return '#000000';\n};\n\nexport function createLegend(\n  container: HTMLElement,\n  position: LegendPosition = 'right',\n  onSeriesToggle?: (seriesIndex: number, sliceIndex?: number) => void\n): Legend {\n  const computedPosition = getComputedStyle(container).position;\n  const didSetRelative = computedPosition === 'static';\n  const previousInlinePosition = didSetRelative ? container.style.position : null;\n\n  if (didSetRelative) {\n    container.style.position = 'relative';\n  }\n\n  const root = document.createElement('div');\n  root.style.position = 'absolute';\n  root.style.pointerEvents = 'auto';\n  root.style.userSelect = 'none';\n  root.style.boxSizing = 'border-box';\n\n  // Theme-driven styling (set/update in update()).\n  root.style.padding = '8px';\n  root.style.borderRadius = '8px';\n  root.style.borderStyle = 'solid';\n  root.style.borderWidth = '1px';\n  root.style.maxHeight = 'calc(100% - 16px)';\n  root.style.overflow = 'auto';\n\n  const list = document.createElement('div');\n  list.style.display = 'flex';\n  list.style.gap = '8px';\n  root.appendChild(list);\n\n  // Event delegation for series toggle (fixes memory leak and improves performance)\n  if (onSeriesToggle) {\n    list.addEventListener('click', (e) => {\n      const target = e.target as HTMLElement;\n      const item = target.closest('[data-series-index]') as HTMLElement;\n      if (item) {\n        const seriesIndex = parseInt(item.dataset.seriesIndex!, 10);\n        if (!isNaN(seriesIndex)) {\n          // Check if this is a pie slice item\n          const sliceIndexStr = item.dataset.sliceIndex;\n          if (sliceIndexStr !== undefined) {\n            const sliceIndex = parseInt(sliceIndexStr, 10);\n            if (!isNaN(sliceIndex)) {\n              onSeriesToggle(seriesIndex, sliceIndex);\n              return;\n            }\n          }\n          // Regular series toggle\n          onSeriesToggle(seriesIndex);\n        }\n      }\n    });\n\n    // Keyboard navigation support for accessibility\n    list.addEventListener('keydown', (e) => {\n      if (e.key === 'Enter' || e.key === ' ') {\n        const target = e.target as HTMLElement;\n        const item = target.closest('[data-series-index]') as HTMLElement;\n        if (item) {\n          e.preventDefault();\n          const seriesIndex = parseInt(item.dataset.seriesIndex!, 10);\n          if (!isNaN(seriesIndex)) {\n            // Check if this is a pie slice item\n            const sliceIndexStr = item.dataset.sliceIndex;\n            if (sliceIndexStr !== undefined) {\n              const sliceIndex = parseInt(sliceIndexStr, 10);\n              if (!isNaN(sliceIndex)) {\n                onSeriesToggle(seriesIndex, sliceIndex);\n                return;\n              }\n            }\n            // Regular series toggle\n            onSeriesToggle(seriesIndex);\n          }\n        }\n      }\n    });\n  }\n\n  const applyPositionStyles = (p: LegendPosition): void => {\n    // Clear positional styles first so changing position is safe/idempotent.\n    root.style.top = '';\n    root.style.right = '';\n    root.style.bottom = '';\n    root.style.left = '';\n    root.style.maxWidth = '';\n\n    list.style.flexDirection = '';\n    list.style.flexWrap = '';\n    list.style.alignItems = '';\n\n    switch (p) {\n      case 'right': {\n        root.style.top = '8px';\n        root.style.right = '8px';\n        root.style.maxWidth = '40%';\n\n        list.style.flexDirection = 'column';\n        list.style.flexWrap = 'nowrap';\n        list.style.alignItems = 'flex-start';\n        return;\n      }\n      case 'left': {\n        root.style.top = '8px';\n        root.style.left = '8px';\n        root.style.maxWidth = '40%';\n\n        list.style.flexDirection = 'column';\n        list.style.flexWrap = 'nowrap';\n        list.style.alignItems = 'flex-start';\n        return;\n      }\n      case 'top': {\n        root.style.top = '8px';\n        root.style.left = '8px';\n        root.style.right = '8px';\n\n        list.style.flexDirection = 'row';\n        list.style.flexWrap = 'wrap';\n        list.style.alignItems = 'center';\n        return;\n      }\n      case 'bottom': {\n        root.style.bottom = '8px';\n        root.style.left = '8px';\n        root.style.right = '8px';\n\n        list.style.flexDirection = 'row';\n        list.style.flexWrap = 'wrap';\n        list.style.alignItems = 'center';\n        return;\n      }\n    }\n  };\n\n  applyPositionStyles(position);\n  container.appendChild(root);\n\n  let disposed = false;\n\n  const update: Legend['update'] = (series, theme) => {\n    if (disposed) return;\n\n    root.style.color = theme.textColor;\n    root.style.background = theme.backgroundColor;\n    root.style.borderColor = theme.axisLineColor;\n    root.style.fontFamily = theme.fontFamily;\n    root.style.fontSize = `${theme.fontSize}px`;\n\n    const items: HTMLElement[] = [];\n    for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {\n      const s = series[seriesIndex];\n\n      if (s.type === 'pie') {\n        for (let sliceIndex = 0; sliceIndex < s.data.length; sliceIndex++) {\n          const slice = s.data[sliceIndex];\n          const isVisible = slice?.visible !== false;\n\n          const item = document.createElement('div');\n          item.style.display = 'flex';\n          item.style.alignItems = 'center';\n          item.style.gap = '6px';\n          item.style.lineHeight = '1.1';\n          item.style.whiteSpace = 'nowrap';\n          item.style.cursor = onSeriesToggle ? 'pointer' : 'default';\n          item.style.opacity = isVisible ? '1' : '0.5';\n          item.style.transition = 'opacity 0.2s';\n\n          // Add accessibility attributes and data attributes for event delegation\n          if (onSeriesToggle) {\n            item.setAttribute('role', 'button');\n            item.setAttribute('aria-pressed', String(isVisible));\n            item.setAttribute('aria-label', `Toggle ${getPieSliceLabel(slice?.name, sliceIndex)} visibility`);\n            item.tabIndex = 0;\n            item.dataset.seriesIndex = String(seriesIndex);\n            item.dataset.sliceIndex = String(sliceIndex);\n          }\n\n          const swatch = document.createElement('div');\n          swatch.style.width = '10px';\n          swatch.style.height = '10px';\n          swatch.style.borderRadius = '2px';\n          swatch.style.flex = '0 0 auto';\n          swatch.style.background = getPieSliceColor(slice?.color, seriesIndex, sliceIndex, theme);\n          swatch.style.border = `1px solid ${theme.axisLineColor}`;\n\n          const label = document.createElement('span');\n          label.textContent = getPieSliceLabel(slice?.name, sliceIndex);\n          label.style.textDecoration = isVisible ? 'none' : 'line-through';\n\n          item.appendChild(swatch);\n          item.appendChild(label);\n          items.push(item);\n        }\n      } else {\n        const isVisible = s.visible !== false;\n\n        const item = document.createElement('div');\n        item.style.display = 'flex';\n        item.style.alignItems = 'center';\n        item.style.gap = '6px';\n        item.style.lineHeight = '1.1';\n        item.style.whiteSpace = 'nowrap';\n        item.style.cursor = onSeriesToggle ? 'pointer' : 'default';\n        item.style.opacity = isVisible ? '1' : '0.5';\n        item.style.transition = 'opacity 0.2s';\n\n        // Add accessibility attributes and data attribute for event delegation\n        if (onSeriesToggle) {\n          item.setAttribute('role', 'button');\n          item.setAttribute('aria-pressed', String(isVisible));\n          item.setAttribute('aria-label', `Toggle ${getSeriesName(s, seriesIndex)} visibility`);\n          item.tabIndex = 0;\n          item.dataset.seriesIndex = String(seriesIndex);\n        }\n\n        const swatch = document.createElement('div');\n        swatch.style.width = '10px';\n        swatch.style.height = '10px';\n        swatch.style.borderRadius = '2px';\n        swatch.style.flex = '0 0 auto';\n        swatch.style.background = getSeriesColor(s, seriesIndex, theme);\n        swatch.style.border = `1px solid ${theme.axisLineColor}`;\n\n        const label = document.createElement('span');\n        label.textContent = getSeriesName(s, seriesIndex);\n        label.style.textDecoration = isVisible ? 'none' : 'line-through';\n\n        item.appendChild(swatch);\n        item.appendChild(label);\n        items.push(item);\n      }\n    }\n\n    list.replaceChildren(...items);\n  };\n\n  const dispose: Legend['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      root.remove();\n    } finally {\n      if (previousInlinePosition !== null) {\n        container.style.position = previousInlinePosition;\n      }\n    }\n  };\n\n  return { update, dispose };\n}\n\n","export interface Tooltip {\n  /**\n   * Show tooltip at container-local CSS pixel coordinates.\n   *\n   * `content` is treated as HTML (assigned via `innerHTML`).\n   */\n  show(x: number, y: number, content: string): void;\n  hide(): void;\n  dispose(): void;\n}\n\nconst clamp = (value: number, min: number, max: number): number => {\n  if (max < min) return min;\n  if (value < min) return min;\n  if (value > max) return max;\n  return value;\n};\n\nexport function createTooltip(container: HTMLElement): Tooltip {\n  const computedPosition = getComputedStyle(container).position;\n  const didSetRelative = computedPosition === 'static';\n  const previousInlinePosition = didSetRelative ? container.style.position : null;\n\n  if (didSetRelative) {\n    container.style.position = 'relative';\n  }\n\n  const root = document.createElement('div');\n  root.style.position = 'absolute';\n  root.style.left = '0';\n  root.style.top = '0';\n  root.style.pointerEvents = 'none';\n  root.style.userSelect = 'none';\n  root.style.boxSizing = 'border-box';\n\n  // Theme-friendly default visuals with CSS variable override points.\n  root.style.zIndex = 'var(--chartgpu-tooltip-z, 10)';\n  root.style.padding = 'var(--chartgpu-tooltip-padding, 6px 8px)';\n  root.style.borderRadius = 'var(--chartgpu-tooltip-radius, 8px)';\n  root.style.borderStyle = 'solid';\n  root.style.borderWidth = 'var(--chartgpu-tooltip-border-width, 1px)';\n  root.style.borderColor =\n    'var(--chartgpu-tooltip-border, rgba(224,224,224,0.35))';\n  root.style.boxShadow =\n    'var(--chartgpu-tooltip-shadow, 0 6px 18px rgba(0,0,0,0.35))';\n  root.style.maxWidth = 'var(--chartgpu-tooltip-max-width, min(320px, 100%))';\n  root.style.overflow = 'hidden';\n  root.style.fontFamily =\n    'var(--chartgpu-tooltip-font-family, system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\")';\n  root.style.fontSize = 'var(--chartgpu-tooltip-font-size, 12px)';\n  root.style.lineHeight = 'var(--chartgpu-tooltip-line-height, 1.2)';\n  root.style.color = 'var(--chartgpu-tooltip-color, #e0e0e0)';\n  root.style.background = 'var(--chartgpu-tooltip-bg, rgba(26,26,46,0.95))';\n  root.style.whiteSpace = 'normal';\n\n  // Transition-ready baseline; keep fade-out visible until completion.\n  root.style.opacity = '0';\n  root.style.transitionProperty = 'opacity';\n  const fadeMs = 140;\n  root.style.transitionDuration = `${fadeMs}ms`;\n  root.style.transitionTimingFunction = 'ease';\n  root.style.willChange = 'opacity';\n\n  // Keep it out of layout/paint when hidden.\n  root.style.display = 'none';\n  root.style.visibility = 'hidden';\n\n  root.setAttribute('role', 'tooltip');\n  container.appendChild(root);\n\n  let disposed = false;\n  let transitionToken = 0;\n  let hideTimeoutId: number | null = null;\n  let rafId: number | null = null;\n\n  const clearPendingTransitions = (): void => {\n    if (hideTimeoutId != null) {\n      window.clearTimeout(hideTimeoutId);\n      hideTimeoutId = null;\n    }\n    if (rafId != null) {\n      window.cancelAnimationFrame(rafId);\n      rafId = null;\n    }\n  };\n\n  const isCurrentlyHidden = (): boolean =>\n    root.style.display === 'none' || root.style.visibility === 'hidden';\n\n  const measureSize = (): Readonly<{ width: number; height: number }> => {\n    // Measure without touching opacity to avoid restarting fades.\n    // If the tooltip is currently visible, this temporarily hides it from paint\n    // within the same call (no flicker between frames).\n    const prevVisibility = root.style.visibility;\n    root.style.visibility = 'hidden';\n\n    // offsetWidth/offsetHeight are rounded but stable for layout decisions here.\n    const width = root.offsetWidth;\n    const height = root.offsetHeight;\n\n    root.style.visibility = prevVisibility;\n    return { width, height };\n  };\n\n  const show: Tooltip['show'] = (x, y, content) => {\n    if (disposed) return;\n\n    transitionToken += 1;\n    clearPendingTransitions();\n\n    const wasHidden = isCurrentlyHidden();\n\n    root.innerHTML = content;\n\n    const dx = 12;\n    const dy = 12;\n    const pad = 8;\n\n    // Ensure it participates in layout for measurement & positioning.\n    // Keep it hidden from paint until we finish placing it.\n    root.style.display = 'block';\n    root.style.visibility = 'hidden';\n\n    const { width: w, height: h } = measureSize();\n\n    const containerW = container.clientWidth;\n    const containerH = container.clientHeight;\n\n    let left = x + dx;\n    let top = y + dy;\n\n    if (left + w > containerW - pad) left = x - dx - w;\n    if (top + h > containerH - pad) top = y - dy - h;\n\n    left = clamp(left, pad, containerW - pad - w);\n    top = clamp(top, pad, containerH - pad - h);\n\n    root.style.left = `${left}px`;\n    root.style.top = `${top}px`;\n\n    root.style.visibility = 'visible';\n\n    if (wasHidden) {\n      // Only fade in on hidden -> visible transition.\n      root.style.opacity = '0';\n      const myToken = transitionToken;\n      rafId = window.requestAnimationFrame(() => {\n        rafId = null;\n        if (disposed) return;\n        if (myToken !== transitionToken) return;\n        root.style.opacity = '1';\n      });\n    } else {\n      // Frequent updates while visible should not restart the fade.\n      // Also cancels an in-progress hide fade-out by restoring opacity.\n      root.style.opacity = '1';\n    }\n  };\n\n  const hide: Tooltip['hide'] = () => {\n    if (disposed) return;\n\n    transitionToken += 1;\n    clearPendingTransitions();\n\n    // If it's already hidden, keep idempotent and avoid extra work.\n    if (root.style.display === 'none' || root.style.visibility === 'hidden') {\n      root.style.opacity = '0';\n      root.style.visibility = 'hidden';\n      root.style.display = 'none';\n      return;\n    }\n\n    root.style.opacity = '0';\n\n    const myToken = transitionToken;\n    hideTimeoutId = window.setTimeout(() => {\n      hideTimeoutId = null;\n      if (disposed) return;\n      if (myToken !== transitionToken) return;\n      root.style.visibility = 'hidden';\n      root.style.display = 'none';\n    }, fadeMs + 50);\n  };\n\n  const dispose: Tooltip['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      clearPendingTransitions();\n      root.remove();\n    } finally {\n      if (previousInlinePosition !== null) {\n        container.style.position = previousInlinePosition;\n      }\n    }\n  };\n\n  return { show, hide, dispose };\n}\n\n","import type { TooltipParams } from '../config/types';\n\nconst EM_DASH = '\\u2014';\n\nfunction escapeHtml(text: string): string {\n  // Escapes text for safe insertion into HTML text/attribute contexts.\n  // (We only use it for text nodes here, but keeping it generic is fine.)\n  return text\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#39;');\n}\n\nfunction formatNumber(value: number): string {\n  if (!Number.isFinite(value)) return EM_DASH;\n\n  // Normalize -0 to 0 for display stability.\n  const normalized = Object.is(value, -0) ? 0 : value;\n\n  // Maximum 2 decimal places, trim trailing zeros.\n  const fixed = normalized.toFixed(2);\n  const trimmed = fixed.replace(/\\.?0+$/, '');\n  return trimmed === '-0' ? '0' : trimmed;\n}\n\nfunction resolveSeriesName(params: TooltipParams): string {\n  const trimmed = params.seriesName.trim();\n  return trimmed.length > 0 ? trimmed : `Series ${params.seriesIndex + 1}`;\n}\n\nfunction sanitizeCssColor(value: string): string {\n  // Tooltip content is assigned via innerHTML, so treat color as untrusted.\n  // Allow only common safe color syntaxes; otherwise fall back.\n  const s = value.trim();\n  if (s.length === 0) return '#888';\n\n  // Hex: #RGB, #RRGGBB, #RRGGBBAA\n  if (/^#[0-9a-fA-F]{3}$/.test(s)) return s;\n  if (/^#[0-9a-fA-F]{6}$/.test(s)) return s;\n  if (/^#[0-9a-fA-F]{8}$/.test(s)) return s;\n\n  // rgb()/rgba() numeric forms (commas or space-separated with optional slash alpha)\n  if (\n    /^rgba?\\(\\s*\\d{1,3}\\s*(?:,\\s*|\\s+)\\d{1,3}\\s*(?:,\\s*|\\s+)\\d{1,3}(?:\\s*(?:,\\s*|\\/\\s*)(?:0|1|0?\\.\\d+))?\\s*\\)$/.test(\n      s,\n    )\n  ) {\n    return s;\n  }\n\n  // Named colors: basic CSS ident (letters only) to avoid weird tokens.\n  if (/^[a-zA-Z]+$/.test(s)) return s;\n\n  return '#888';\n}\n\nfunction isCandlestickValue(\n  value: readonly [number, number] | readonly [number, number, number, number, number],\n): value is readonly [number, number, number, number, number] {\n  return value.length === 5;\n}\n\nfunction formatPercentChange(open: number, close: number): string {\n  if (!Number.isFinite(open) || !Number.isFinite(close)) return EM_DASH;\n  if (open === 0) return EM_DASH; // Avoid division by zero\n\n  const change = ((close - open) / open) * 100;\n  if (!Number.isFinite(change)) return EM_DASH;\n\n  const sign = change > 0 ? '+' : '';\n  return `${sign}${change.toFixed(2)}%`;\n}\n\nfunction formatRowHtml(params: TooltipParams, valueText: string): string {\n  const safeName = escapeHtml(resolveSeriesName(params));\n  const safeValue = escapeHtml(valueText);\n  const safeColor = escapeHtml(sanitizeCssColor(params.color));\n\n  return [\n    '<div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;\">',\n    '<span style=\"display:flex;align-items:center;gap:8px;min-width:0;\">',\n    `<span style=\"width:8px;height:8px;border-radius:999px;flex:0 0 auto;background-color:${safeColor};\"></span>`,\n    `<span style=\"overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\">${safeName}</span>`,\n    '</span>',\n    `<span style=\"font-variant-numeric:tabular-nums;white-space:nowrap;\">${safeValue}</span>`,\n    '</div>',\n  ].join('');\n}\n\nfunction formatCandlestickRowHtml(params: TooltipParams): string {\n  const [, open, close, low, high] = params.value as readonly [number, number, number, number, number];\n  \n  const safeName = escapeHtml(resolveSeriesName(params));\n  const safeColor = escapeHtml(sanitizeCssColor(params.color));\n\n  // Format OHLC values\n  const openStr = formatNumber(open);\n  const highStr = formatNumber(high);\n  const lowStr = formatNumber(low);\n  const closeStr = formatNumber(close);\n  \n  // Determine direction and arrow\n  const isUp = close > open;\n  const arrow = isUp ? '\\u25B2' : '\\u25BC'; // ▲ or ▼\n  const arrowColor = isUp ? '#22c55e' : '#ef4444';\n  const percentChange = formatPercentChange(open, close);\n\n  const ohlcText = `O: ${openStr} H: ${highStr} L: ${lowStr} C: ${closeStr}`;\n  const safeOHLC = escapeHtml(ohlcText);\n  const safeArrow = escapeHtml(arrow);\n  const safePercent = escapeHtml(percentChange);\n  const safeArrowColor = escapeHtml(arrowColor);\n\n  return [\n    '<div style=\"display:flex;flex-direction:column;gap:4px;\">',\n    // Series name row\n    '<div style=\"display:flex;align-items:center;gap:8px;\">',\n    `<span style=\"width:8px;height:8px;border-radius:999px;flex:0 0 auto;background-color:${safeColor};\"></span>`,\n    `<span style=\"overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;\">${safeName}</span>`,\n    '</div>',\n    // OHLC values row\n    `<div style=\"font-variant-numeric:tabular-nums;white-space:nowrap;font-size:0.9em;\">${safeOHLC}</div>`,\n    // Change row with arrow\n    '<div style=\"display:flex;align-items:center;gap:6px;font-variant-numeric:tabular-nums;\">',\n    `<span style=\"color:${safeArrowColor};font-weight:700;\">${safeArrow}</span>`,\n    `<span style=\"color:${safeArrowColor};font-weight:600;\">${safePercent}</span>`,\n    '</div>',\n    '</div>',\n  ].join('');\n}\n\n/**\n * Default tooltip formatter for candlestick series in item mode.\n * Renders O/H/L/C values with colored arrow and percentage change.\n */\nexport function formatCandlestickTooltip(params: TooltipParams): string {\n  return formatCandlestickRowHtml(params);\n}\n\n/**\n * Default tooltip formatter for item mode.\n * Returns a compact single-row HTML snippet: dot + series name + y value.\n * For candlestick series, returns O/H/L/C with arrow and percentage change.\n */\nexport function formatTooltipItem(params: TooltipParams): string {\n  if (isCandlestickValue(params.value)) {\n    return formatCandlestickTooltip(params);\n  }\n  return formatRowHtml(params, formatNumber(params.value[1]));\n}\n\n/**\n * Default tooltip formatter for axis mode.\n * Renders an x header line then one row per series with the y value.\n * Candlestick series show O/H/L/C values with arrow and percentage change.\n */\nexport function formatTooltipAxis(params: TooltipParams[]): string {\n  if (params.length === 0) return '';\n\n  const xText = `x: ${formatNumber(params[0].value[0])}`;\n  const header = `<div style=\"margin:0 0 6px 0;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap;\">${escapeHtml(\n    xText,\n  )}</div>`;\n\n  const rows = params\n    .map((p) => {\n      if (isCandlestickValue(p.value)) {\n        return formatCandlestickRowHtml(p);\n      }\n      return formatRowHtml(p, formatNumber(p.value[1]));\n    })\n    .join('<div style=\"height:4px;\"></div>');\n\n  return `${header}${rows}`;\n}\n\n","import type { EasingFunction } from '../utils/easing';\n\nexport type AnimationId = symbol;\n\nexport interface AnimationController {\n  animate(\n    from: number,\n    to: number,\n    duration: number,\n    easing: EasingFunction,\n    onUpdate: (value: number) => void,\n    onComplete?: () => void,\n  ): AnimationId;\n  animate(\n    from: ReadonlyArray<number>,\n    to: ReadonlyArray<number>,\n    duration: number,\n    easing: EasingFunction,\n    onUpdate: (value: ReadonlyArray<number>) => void,\n    onComplete?: () => void,\n  ): AnimationId;\n\n  cancel(animationId: AnimationId): void;\n  cancelAll(): void;\n\n  /**\n   * Progresses all active animations to `timestamp` (ms).\n   * Intended to be called once per frame by the caller (e.g. a render loop).\n   */\n  update(timestamp: number): void;\n}\n\ntype ScalarAnimation = Readonly<{\n  kind: 'scalar';\n  from: number;\n  to: number;\n  duration: number;\n  easing: EasingFunction;\n  onUpdate: (value: number) => void;\n  onComplete?: () => void;\n  startTime: number | null;\n}>;\n\ntype ArrayAnimation = Readonly<{\n  kind: 'array';\n  from: ReadonlyArray<number>;\n  to: ReadonlyArray<number>;\n  duration: number;\n  easing: EasingFunction;\n  onUpdate: (value: ReadonlyArray<number>) => void;\n  onComplete?: () => void;\n  startTime: number | null;\n  out: number[];\n}>;\n\ntype AnimationInternal = ScalarAnimation | ArrayAnimation;\n\nconst normalizeDurationMs = (duration: number): number =>\n  Number.isFinite(duration) ? duration : 0;\n\nconst normalizeTimestampMs = (timestamp: number): number | null =>\n  Number.isFinite(timestamp) ? timestamp : null;\n\nexport function createAnimationController(): AnimationController {\n  const animations = new Map<AnimationId, AnimationInternal>();\n\n  function animate(\n    from: number | ReadonlyArray<number>,\n    to: number | ReadonlyArray<number>,\n    duration: number,\n    easing: EasingFunction,\n    onUpdate: ((value: number) => void) | ((value: ReadonlyArray<number>) => void),\n    onComplete?: () => void,\n  ): AnimationId {\n    const id: AnimationId = Symbol('Animation');\n\n    if (Array.isArray(from) || Array.isArray(to)) {\n      if (!Array.isArray(from) || !Array.isArray(to)) {\n        throw new Error('Array animation requires both \"from\" and \"to\" to be arrays');\n      }\n      if (from.length !== to.length) {\n        throw new Error(\n          `Array animation length mismatch: from.length=${from.length}, to.length=${to.length}`,\n        );\n      }\n\n      const out = new Array<number>(from.length);\n      animations.set(id, {\n        kind: 'array',\n        from,\n        to,\n        duration,\n        easing,\n        onUpdate: onUpdate as (value: ReadonlyArray<number>) => void,\n        onComplete,\n        startTime: null,\n        out,\n      });\n      return id;\n    }\n\n    animations.set(id, {\n      kind: 'scalar',\n      from: from as number,\n      to: to as number,\n      duration,\n      easing,\n      onUpdate: onUpdate as (value: number) => void,\n      onComplete,\n      startTime: null,\n    });\n    return id;\n  }\n\n  function cancel(animationId: AnimationId): void {\n    animations.delete(animationId);\n  }\n\n  function cancelAll(): void {\n    animations.clear();\n  }\n\n  function update(timestamp: number): void {\n    const ts = normalizeTimestampMs(timestamp);\n    if (ts === null) return;\n\n    // Snapshot IDs to tolerate cancellation during callbacks and to ensure\n    // animations started during callbacks don't run until next tick.\n    const ids = Array.from(animations.keys());\n    for (const id of ids) {\n      const anim = animations.get(id);\n      if (!anim) continue; // cancelled\n\n      const startTime = anim.startTime ?? ts;\n      if (anim.startTime === null) {\n        // Mutate by replacement to keep internal entries immutable-by-type.\n        animations.set(id, { ...anim, startTime });\n      }\n\n      const durationMs = normalizeDurationMs(anim.duration);\n      const elapsed = Math.max(0, ts - startTime);\n\n      const shouldComplete = durationMs <= 0 || elapsed >= durationMs;\n      const rawT = durationMs <= 0 ? 1 : elapsed / durationMs;\n      const t = shouldComplete ? 1 : anim.easing(rawT);\n\n      if (anim.kind === 'scalar') {\n        const value = anim.from + (anim.to - anim.from) * t;\n        anim.onUpdate(value);\n\n        // Cancellation during callback should prevent onComplete.\n        if (!animations.has(id)) continue;\n      } else {\n        const n = anim.out.length;\n        for (let i = 0; i < n; i++) {\n          const a = anim.from[i] ?? 0;\n          const b = anim.to[i] ?? 0;\n          anim.out[i] = a + (b - a) * t;\n        }\n        anim.onUpdate(anim.out);\n\n        // Cancellation during callback should prevent onComplete.\n        if (!animations.has(id)) continue;\n      }\n\n      if (shouldComplete) {\n        anim.onComplete?.();\n        // If it was cancelled inside onComplete, deletion is harmless.\n        animations.delete(id);\n      }\n    }\n  }\n\n  return {\n    animate: animate as AnimationController['animate'],\n    cancel,\n    cancelAll,\n    update,\n  };\n}\n","export type EasingFunction = (t: number) => number;\n\nimport type { AnimationConfig } from '../config/types';\n\nexport type EasingName = NonNullable<AnimationConfig['easing']>;\n\nconst clamp01 = (t: number): number => {\n  if (Number.isNaN(t)) return 0;\n  if (t <= 0) return 0;\n  if (t >= 1) return 1;\n  return t;\n};\n\nexport function easeLinear(t: number): number {\n  return clamp01(t);\n}\n\nexport function easeCubicOut(t: number): number {\n  const x = clamp01(t);\n  const inv = 1 - x;\n  return 1 - inv * inv * inv;\n}\n\nexport function easeCubicInOut(t: number): number {\n  const x = clamp01(t);\n  // Standard easeInOutCubic:\n  // - accelerating cubic for the first half\n  // - decelerating cubic for the second half\n  if (x < 0.5) return 4 * x * x * x;\n  const y = -2 * x + 2;\n  return 1 - (y * y * y) / 2;\n}\n\nexport function easeBounceOut(t: number): number {\n  const x = clamp01(t);\n  // Standard easeOutBounce (Robert Penner) piecewise approximation.\n  const n1 = 7.5625;\n  const d1 = 2.75;\n\n  if (x < 1 / d1) {\n    return n1 * x * x;\n  }\n  if (x < 2 / d1) {\n    const a = x - 1.5 / d1;\n    return n1 * a * a + 0.75;\n  }\n  if (x < 2.5 / d1) {\n    const a = x - 2.25 / d1;\n    return n1 * a * a + 0.9375;\n  }\n\n  const a = x - 2.625 / d1;\n  return n1 * a * a + 0.984375;\n}\n\nexport function getEasing(\n  name: AnimationConfig['easing'] | null | undefined,\n): EasingFunction {\n  switch (name) {\n    case 'linear':\n      return easeLinear;\n    case 'cubicOut':\n      return easeCubicOut;\n    case 'cubicInOut':\n      return easeCubicInOut;\n    case 'bounceOut':\n      return easeBounceOut;\n    default:\n      return easeLinear;\n  }\n}\n","import type {\n  ResolvedBarSeriesConfig,\n  ResolvedCandlestickSeriesConfig,\n  ResolvedChartGPUOptions,\n  ResolvedPieSeriesConfig,\n} from '../config/OptionResolver';\nimport type {\n  AnimationConfig,\n  AnnotationConfig,\n  DataPoint,\n  DataPointTuple,\n  OHLCDataPoint,\n  PieCenter,\n  PieRadius,\n} from '../config/types';\nimport { GPUContext, isHTMLCanvasElement as isHTMLCanvasElementGPU } from './GPUContext';\nimport { createDataStore } from '../data/createDataStore';\nimport { sampleSeriesDataPoints } from '../data/sampleSeries';\nimport { ohlcSample } from '../data/ohlcSample';\nimport {\n  sliceVisibleRangeByX,\n  sliceVisibleRangeByOHLC,\n  isTupleOHLCDataPoint as isTupleOHLCDataPointImported,\n} from './renderCoordinator/data/computeVisibleSlice';\nimport { \n  getPointCount, \n  getX, \n  getY, \n  getSize,\n  computeRawBoundsFromCartesianData \n} from '../data/cartesianData';\nimport type { CartesianSeriesData } from '../config/types';\nimport { renderAxisLabels } from './renderCoordinator/render/renderAxisLabels';\nimport { renderAnnotationLabels } from './renderCoordinator/render/renderAnnotationLabels';\nimport { prepareOverlays } from './renderCoordinator/render/renderOverlays';\nimport { processAnnotations } from './renderCoordinator/annotations/processAnnotations';\nimport {\n  prepareSeries,\n  encodeScatterDensityCompute,\n  renderSeries as renderSeriesPass,\n  renderAboveSeriesAnnotations,\n} from './renderCoordinator/render/renderSeries';\nimport { createAxisRenderer } from '../renderers/createAxisRenderer';\nimport { createGridRenderer } from '../renderers/createGridRenderer';\nimport type { GridArea } from '../renderers/createGridRenderer';\nimport { createRendererPool } from './renderCoordinator/renderers/rendererPool';\nimport { createTextureManager, ANNOTATION_OVERLAY_MSAA_SAMPLE_COUNT, MAIN_SCENE_MSAA_SAMPLE_COUNT } from './renderCoordinator/gpu/textureManager';\nimport { createCrosshairRenderer } from '../renderers/createCrosshairRenderer';\nimport { createHighlightRenderer } from '../renderers/createHighlightRenderer';\nimport { createReferenceLineRenderer } from '../renderers/createReferenceLineRenderer';\nimport type { ReferenceLineInstance } from '../renderers/createReferenceLineRenderer';\nimport { createAnnotationMarkerRenderer } from '../renderers/createAnnotationMarkerRenderer';\nimport type { AnnotationMarkerInstance } from '../renderers/createAnnotationMarkerRenderer';\nimport { createEventManager } from '../interaction/createEventManager';\nimport type { PipelineCache } from './PipelineCache';\nimport type { ChartGPUEventPayload } from '../interaction/createEventManager';\nimport { createInsideZoom } from '../interaction/createInsideZoom';\nimport { createZoomState } from '../interaction/createZoomState';\nimport type { ZoomRange, ZoomState } from '../interaction/createZoomState';\nimport { findNearestPoint } from '../interaction/findNearestPoint';\nimport { findPointsAtX } from '../interaction/findPointsAtX';\nimport { computeCandlestickBodyWidthRange, findCandlestick } from '../interaction/findCandlestick';\nimport { findPieSlice } from '../interaction/findPieSlice';\nimport { createLinearScale } from '../utils/scales';\nimport type { LinearScale } from '../utils/scales';\nimport { parseCssColorToGPUColor, parseCssColorToRgba01 } from '../utils/colors';\nimport { createTextOverlay } from '../components/createTextOverlay';\nimport type { TextOverlay, TextOverlayAnchor } from '../components/createTextOverlay';\nimport { createLegend } from '../components/createLegend';\nimport type { Legend } from '../components/createLegend';\nimport { createTooltip } from '../components/createTooltip';\nimport type { Tooltip } from '../components/createTooltip';\nimport type { TooltipParams } from '../config/types';\nimport { formatTooltipAxis, formatTooltipItem } from '../components/formatTooltip';\nimport { createAnimationController } from './createAnimationController';\nimport type { AnimationId } from './createAnimationController';\nimport { getEasing } from '../utils/easing';\nimport type { EasingFunction } from '../utils/easing';\nimport type { ZoomChangeSourceKind } from '../ChartGPU';\n\nexport interface GPUContextLike {\n  readonly device: GPUDevice | null;\n  readonly canvas: HTMLCanvasElement | null;\n  readonly canvasContext: GPUCanvasContext | null;\n  readonly preferredFormat: GPUTextureFormat | null;\n  readonly initialized: boolean;\n  readonly devicePixelRatio?: number;\n}\n\n/** Type guard to check if canvas is HTMLCanvasElement (has DOM-specific properties). */\nconst isHTMLCanvasElement = isHTMLCanvasElementGPU;\n\n/** Gets canvas CSS width - clientWidth for HTMLCanvasElement */\nfunction getCanvasCssWidth(canvas: HTMLCanvasElement | null): number {\n  if (!canvas) {\n    return 0;\n  }\n\n  return canvas.clientWidth;\n}\n\n/**\n * Gets canvas CSS size derived strictly from device-pixel dimensions and DPR.\n *\n * This is intentionally different from `getCanvasCssWidth/Height(...)`:\n * - HTMLCanvasElement: `clientWidth/clientHeight` reflect DOM layout and can diverge (rounding, zoom, async resize)\n *   from the WebGPU render target size (`canvas.width/height` in device pixels).\n * - For GPU overlays that round-trip CSS↔device pixels in-shader, we must derive CSS size from\n *   `canvas.width/height` + DPR to keep transforms consistent with the render target.\n *\n * NOTE: Use this for GPU overlay coordinate conversion only (reference lines, markers).\n * Keep DOM overlays (labels/tooltips) using `clientWidth/clientHeight` for layout correctness.\n */\nfunction getCanvasCssSizeFromDevicePixels(\n  canvas: HTMLCanvasElement | null,\n): Readonly<{ width: number; height: number }> {\n  if (!canvas) return { width: 0, height: 0 };\n  const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;\n  // HTMLCanvasElement exposes `.width/.height` in device pixels.\n  return { width: canvas.width / dpr, height: canvas.height / dpr };\n}\n\nexport interface RenderCoordinator {\n  setOptions(resolvedOptions: ResolvedChartGPUOptions): void;\n  /**\n   * Appends new points to a cartesian series’ runtime data without requiring a full `setOptions(...)`\n   * resolver pass.\n   *\n   * Appends are coalesced and flushed once per render frame.\n   */\n  appendData(seriesIndex: number, newPoints: CartesianSeriesData | ReadonlyArray<OHLCDataPoint>): void;\n  /**\n   * Gets the current “interaction x” in domain units (or `null` when inactive).\n   *\n   * This is derived from pointer movement inside the plot grid and can also be driven\n   * externally via `setInteractionX(...)` (e.g. chart sync).\n   */\n  getInteractionX(): number | null;\n  /**\n   * Drives the chart’s crosshair + tooltip from a domain-space x value.\n   *\n   * Passing `null` clears the interaction (hides crosshair/tooltip).\n   */\n  setInteractionX(x: number | null, source?: unknown): void;\n  /**\n   * Subscribes to interaction x changes (domain units).\n   *\n   * Returns an unsubscribe function.\n   */\n  onInteractionXChange(callback: (x: number | null, source?: unknown) => void): () => void;\n  /**\n   * Returns the current percent-space zoom window (or `null` when zoom is disabled).\n   */\n  getZoomRange(): Readonly<{ start: number; end: number }> | null;\n  /**\n   * Sets the percent-space zoom window.\n   *\n   * No-op when zoom is disabled.\n   */\n  setZoomRange(start: number, end: number): void;\n  /**\n   * Subscribes to zoom window changes (percent space).\n   *\n   * Returns an unsubscribe function.\n   */\n  onZoomRangeChange(cb: (range: Readonly<{ start: number; end: number }>, sourceKind?: ZoomChangeSourceKind) => void): () => void;\n  render(): void;\n  dispose(): void;\n}\n\nexport type RenderCoordinatorCallbacks = Readonly<{\n  /**\n   * Optional hook for render-on-demand systems (like `ChartGPU`) to re-render when\n   * interaction state changes (e.g. crosshair on pointer move).\n   */\n  readonly onRequestRender?: () => void;\n  /**\n   * Optional shared cache for shader modules + render pipelines (CGPU-PIPELINE-CACHE).\n   * Opt-in only: if omitted, coordinator/renderers behave identically.\n   */\n  readonly pipelineCache?: PipelineCache;\n}>;\n\ntype Bounds = Readonly<{ xMin: number; xMax: number; yMin: number; yMax: number }>;\n\nconst DEFAULT_TARGET_FORMAT: GPUTextureFormat = 'bgra8unorm';\nconst DEFAULT_TICK_COUNT: number = 5;\n\n// Story 6: time-axis label tiers + adaptive tick count (x-axis only).\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n// Approximate month/year thresholds (requirements are ms-range based, not calendar-aware).\nconst MS_PER_MONTH_APPROX = 30 * MS_PER_DAY;\nconst MS_PER_YEAR_APPROX = 365 * MS_PER_DAY;\n\nconst MAX_TIME_X_TICK_COUNT = 9;\nconst MIN_TIME_X_TICK_COUNT = 1;\nconst MIN_X_LABEL_GAP_CSS_PX = 6;\n\nconst finiteOrNull = (v: number | null | undefined): number | null =>\n  typeof v === 'number' && Number.isFinite(v) ? v : null;\n\nconst finiteOrUndefined = (v: number | undefined): number | undefined =>\n  typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n\n// Story 5.17: CPU-side update interpolation can be expensive for very large series.\n// We still animate domains for large series, but skip per-point y interpolation past this cap.\nconst MAX_ANIMATED_POINTS_PER_SERIES = 20_000;\n\nconst assertUnreachable = (value: never): never => {\n  // Intentionally minimal message: this is used for compile-time exhaustiveness.\n  throw new Error(`RenderCoordinator: unreachable value: ${String(value)}`);\n};\n\nconst isTupleDataPoint = (p: DataPoint): p is DataPointTuple => Array.isArray(p);\n\nconst getPointXY = (p: DataPoint): { readonly x: number; readonly y: number } => {\n  if (isTupleDataPoint(p)) return { x: p[0], y: p[1] };\n  return { x: p.x, y: p.y };\n};\n\n/**\n * Mutable columnar cartesian data store (runtime).\n * - x, y: number[] - coordinate columns\n * - size?: (number|undefined)[] - optional size column (aligned with x/y when present)\n */\ntype MutableXYColumns = {\n  x: number[];\n  y: number[];\n  size?: (number | undefined)[];\n};\n\n/**\n * Helper: Convert CartesianSeriesData to mutable columnar format for runtime storage.\n * Used for streaming appends without per-point allocations.\n */\nconst cartesianDataToMutableColumns = (data: CartesianSeriesData): MutableXYColumns => {\n  const n = getPointCount(data);\n  if (n === 0) return { x: [], y: [] };\n\n  const x: number[] = new Array(n);\n  const y: number[] = new Array(n);\n  let hasSizeValues = false;\n  let size: (number | undefined)[] | undefined;\n\n  // Check if any point has a size value\n  for (let i = 0; i < n; i++) {\n    x[i] = getX(data, i);\n    y[i] = getY(data, i);\n    const s = getSize(data, i);\n    if (s !== undefined) {\n      hasSizeValues = true;\n      if (!size) {\n        // Backfill with undefined for prior points\n        size = new Array(i);\n      }\n      size[i] = s;\n    } else if (size) {\n      size[i] = undefined;\n    }\n  }\n\n  if (hasSizeValues && size) {\n    return { x, y, size };\n  }\n\n  return { x, y };\n};\n\n/**\n * Extends existing bounds with new CartesianSeriesData.\n * Avoids per-point allocations for typed arrays by using direct accessors.\n */\nconst extendBoundsWithCartesianData = (bounds: Bounds | null, data: CartesianSeriesData): Bounds | null => {\n  const newBounds = computeRawBoundsFromCartesianData(data);\n  if (!newBounds) return bounds;\n  if (!bounds) return newBounds;\n\n  // Merge the two bounds\n  let xMin = Math.min(bounds.xMin, newBounds.xMin);\n  let xMax = Math.max(bounds.xMax, newBounds.xMax);\n  let yMin = Math.min(bounds.yMin, newBounds.yMin);\n  let yMax = Math.max(bounds.yMax, newBounds.yMax);\n\n  // Keep bounds usable for downstream scale derivation.\n  if (xMin === xMax) xMax = xMin + 1;\n  if (yMin === yMax) yMax = yMin + 1;\n\n  return { xMin, xMax, yMin, yMax };\n};\n\nconst extendBoundsWithOHLCDataPoints = (bounds: Bounds | null, points: ReadonlyArray<OHLCDataPoint>): Bounds | null => {\n  if (points.length === 0) return bounds;\n\n  let xMin = bounds?.xMin ?? Number.POSITIVE_INFINITY;\n  let xMax = bounds?.xMax ?? Number.NEGATIVE_INFINITY;\n  let yMin = bounds?.yMin ?? Number.POSITIVE_INFINITY;\n  let yMax = bounds?.yMax ?? Number.NEGATIVE_INFINITY;\n\n  for (let i = 0; i < points.length; i++) {\n    const p = points[i]!;\n    const timestamp = isTupleOHLCDataPoint(p) ? p[0] : p.timestamp;\n    const low = isTupleOHLCDataPoint(p) ? p[3] : p.low;\n    const high = isTupleOHLCDataPoint(p) ? p[4] : p.high;\n\n    if (!Number.isFinite(timestamp) || !Number.isFinite(low) || !Number.isFinite(high)) continue;\n    if (timestamp < xMin) xMin = timestamp;\n    if (timestamp > xMax) xMax = timestamp;\n    if (low < yMin) yMin = low;\n    if (high > yMax) yMax = high;\n  }\n\n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || !Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    return bounds;\n  }\n\n  // Keep bounds usable for downstream scale derivation.\n  if (xMin === xMax) xMax = xMin + 1;\n  if (yMin === yMax) yMax = yMin + 1;\n\n  return { xMin, xMax, yMin, yMax };\n};\n\nconst computeGlobalBounds = (\n  series: ResolvedChartGPUOptions['series'],\n  runtimeRawBoundsByIndex?: ReadonlyArray<Bounds | null> | null\n): Bounds => {\n  let xMin = Number.POSITIVE_INFINITY;\n  let xMax = Number.NEGATIVE_INFINITY;\n  let yMin = Number.POSITIVE_INFINITY;\n  let yMax = Number.NEGATIVE_INFINITY;\n\n  for (let s = 0; s < series.length; s++) {\n    const seriesConfig = series[s];\n    // Pie series are non-cartesian; they don't participate in x/y bounds.\n    if (seriesConfig.type === 'pie') continue;\n\n    const runtimeBoundsCandidate = runtimeRawBoundsByIndex?.[s] ?? null;\n    if (runtimeBoundsCandidate) {\n      const b = runtimeBoundsCandidate;\n      if (\n        Number.isFinite(b.xMin) &&\n        Number.isFinite(b.xMax) &&\n        Number.isFinite(b.yMin) &&\n        Number.isFinite(b.yMax)\n      ) {\n        if (b.xMin < xMin) xMin = b.xMin;\n        if (b.xMax > xMax) xMax = b.xMax;\n        if (b.yMin < yMin) yMin = b.yMin;\n        if (b.yMax > yMax) yMax = b.yMax;\n        continue;\n      }\n    }\n\n    // Prefer precomputed bounds from the original (unsampled) data when available.\n    // This ensures sampling cannot affect axis auto-bounds and avoids per-render O(n) scans.\n    const rawBoundsCandidate = seriesConfig.rawBounds;\n    if (rawBoundsCandidate) {\n      const b = rawBoundsCandidate;\n      if (\n        Number.isFinite(b.xMin) &&\n        Number.isFinite(b.xMax) &&\n        Number.isFinite(b.yMin) &&\n        Number.isFinite(b.yMax)\n      ) {\n        if (b.xMin < xMin) xMin = b.xMin;\n        if (b.xMax > xMax) xMax = b.xMax;\n        if (b.yMin < yMin) yMin = b.yMin;\n        if (b.yMax > yMax) yMax = b.yMax;\n        continue;\n      }\n    }\n\n    // Candlestick series: bounds should be precomputed in OptionResolver from timestamp/low/high.\n    // If we reach here, `rawBounds` was undefined; fall back to a raw OHLC scan so axes don't break.\n    if (seriesConfig.type === 'candlestick') {\n      const rawOHLC = (seriesConfig.rawData ?? seriesConfig.data) as ReadonlyArray<OHLCDataPoint>;\n      for (let i = 0; i < rawOHLC.length; i++) {\n        const p = rawOHLC[i]!;\n        if (isTupleOHLCDataPoint(p)) {\n          const timestamp = p[0];\n          const low = p[3];\n          const high = p[4];\n          if (!Number.isFinite(timestamp) || !Number.isFinite(low) || !Number.isFinite(high)) continue;\n\n          const yLow = Math.min(low, high);\n          const yHigh = Math.max(low, high);\n\n          if (timestamp < xMin) xMin = timestamp;\n          if (timestamp > xMax) xMax = timestamp;\n          if (yLow < yMin) yMin = yLow;\n          if (yHigh > yMax) yMax = yHigh;\n        } else {\n          const timestamp = p.timestamp;\n          const low = p.low;\n          const high = p.high;\n          if (!Number.isFinite(timestamp) || !Number.isFinite(low) || !Number.isFinite(high)) continue;\n\n          const yLow = Math.min(low, high);\n          const yHigh = Math.max(low, high);\n\n          if (timestamp < xMin) xMin = timestamp;\n          if (timestamp > xMax) xMax = timestamp;\n          if (yLow < yMin) yMin = yLow;\n          if (yHigh > yMax) yMax = yHigh;\n        }\n      }\n      continue;\n    }\n\n    // Cartesian series (line, area, bar, scatter): use CartesianSeriesData accessors\n    const data = seriesConfig.data as CartesianSeriesData;\n    const n = getPointCount(data);\n    for (let i = 0; i < n; i++) {\n      const x = getX(data, i);\n      const y = getY(data, i);\n      if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  }\n\n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || !Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    return { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };\n  }\n\n  if (xMin === xMax) xMax = xMin + 1;\n  if (yMin === yMax) yMax = yMin + 1;\n\n  return { xMin, xMax, yMin, yMax };\n}; \n\nconst normalizeDomain = (\n  minCandidate: number,\n  maxCandidate: number\n): { readonly min: number; readonly max: number } => {\n  let min = minCandidate;\n  let max = maxCandidate;\n\n  if (!Number.isFinite(min) || !Number.isFinite(max)) {\n    min = 0;\n    max = 1;\n  }\n\n  if (min === max) {\n    max = min + 1;\n  } else if (min > max) {\n    const t = min;\n    min = max;\n    max = t;\n  }\n\n  return { min, max };\n};\n\nconst computeGridArea = (gpuContext: GPUContextLike, options: ResolvedChartGPUOptions): GridArea => {\n  const canvas = gpuContext.canvas;\n  if (!canvas) throw new Error('RenderCoordinator: gpuContext.canvas is required.');\n\n  // GridArea uses:\n  // - Margins (left, right, top, bottom) in CSS pixels\n  // - Canvas dimensions (canvasWidth, canvasHeight) in DEVICE pixels\n  // - devicePixelRatio for CSS-to-device conversion\n  // This allows renderers to multiply margins by DPR and subtract from canvas dimensions\n\n  const dpr = gpuContext.devicePixelRatio ?? 1;\n  const devicePixelRatio = (Number.isFinite(dpr) && dpr > 0) ? dpr : 1;\n\n  // Validate and sanitize canvas dimensions (device pixels)\n  // Canvas dimensions should be set by GPUContext initialization/resize, but guard against edge cases:\n  // - Race conditions during initialization\n  // - Canvas not yet sized (0 dimensions)\n  const rawCanvasWidth = canvas.width;\n  const rawCanvasHeight = canvas.height;\n  \n  if (!Number.isFinite(rawCanvasWidth) || !Number.isFinite(rawCanvasHeight)) {\n    throw new Error(\n      `RenderCoordinator: Invalid canvas dimensions: width=${rawCanvasWidth}, height=${rawCanvasHeight}. ` +\n      `Canvas must be initialized with finite dimensions before rendering.`\n    );\n  }\n  \n  // Be resilient: charts may be mounted into 0-sized containers (e.g. display:none during init).\n  // Renderers guard internally; clamping avoids hard crashes and allows future resize to recover.\n  const canvasWidth = Math.max(1, Math.floor(rawCanvasWidth));\n  const canvasHeight = Math.max(1, Math.floor(rawCanvasHeight));\n\n  // Validate and sanitize grid margins (CSS pixels)\n  // Grid margins come from resolved options and should be finite, but guard against edge cases\n  const left = Number.isFinite(options.grid.left) ? options.grid.left : 0;\n  const right = Number.isFinite(options.grid.right) ? options.grid.right : 0;\n  const top = Number.isFinite(options.grid.top) ? options.grid.top : 0;\n  const bottom = Number.isFinite(options.grid.bottom) ? options.grid.bottom : 0;\n\n  // Ensure margins are non-negative (negative margins could cause rendering issues)\n  const sanitizedLeft = Math.max(0, left);\n  const sanitizedRight = Math.max(0, right);\n  const sanitizedTop = Math.max(0, top);\n  const sanitizedBottom = Math.max(0, bottom);\n\n  return {\n    left: sanitizedLeft,\n    right: sanitizedRight,\n    top: sanitizedTop,\n    bottom: sanitizedBottom,\n    canvasWidth,                      // Device pixels (clamped above)\n    canvasHeight,                     // Device pixels (clamped above)\n    devicePixelRatio,                 // Explicit DPR (validated above)\n  };\n};\n\nconst rgba01ToCssRgba = (rgba: readonly [number, number, number, number]): string => {\n  const r = Math.max(0, Math.min(255, Math.round(rgba[0] * 255)));\n  const g = Math.max(0, Math.min(255, Math.round(rgba[1] * 255)));\n  const b = Math.max(0, Math.min(255, Math.round(rgba[2] * 255)));\n  const a = Math.max(0, Math.min(1, rgba[3]));\n  return `rgba(${r},${g},${b},${a})`;\n};\n\nconst withAlpha = (cssColor: string, alphaMultiplier: number): string => {\n  const parsed = parseCssColorToRgba01(cssColor);\n  if (!parsed) return cssColor;\n  const a = Math.max(0, Math.min(1, parsed[3] * alphaMultiplier));\n  return rgba01ToCssRgba([parsed[0], parsed[1], parsed[2], a]);\n};\n\n\nconst computePlotClipRect = (\n  gridArea: GridArea\n): { readonly left: number; readonly right: number; readonly top: number; readonly bottom: number } => {\n  const { left, right, top, bottom, canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeft = left * devicePixelRatio;\n  const plotRight = canvasWidth - right * devicePixelRatio;\n  const plotTop = top * devicePixelRatio;\n  const plotBottom = canvasHeight - bottom * devicePixelRatio;\n\n  const plotLeftClip = (plotLeft / canvasWidth) * 2.0 - 1.0;\n  const plotRightClip = (plotRight / canvasWidth) * 2.0 - 1.0;\n  const plotTopClip = 1.0 - (plotTop / canvasHeight) * 2.0; // flip Y\n  const plotBottomClip = 1.0 - (plotBottom / canvasHeight) * 2.0; // flip Y\n\n  return {\n    left: plotLeftClip,\n    right: plotRightClip,\n    top: plotTopClip,\n    bottom: plotBottomClip,\n  };\n};\n\nconst clamp01 = (v: number): number => Math.min(1, Math.max(0, v));\nconst clampInt = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v | 0));\n\nconst lerp = (a: number, b: number, t01: number): number => a + (b - a) * clamp01(t01);\n\nconst lerpDomain = (\n  from: { readonly min: number; readonly max: number },\n  to: { readonly min: number; readonly max: number },\n  t01: number\n): { readonly min: number; readonly max: number } => {\n  return normalizeDomain(lerp(from.min, to.min, t01), lerp(from.max, to.max, t01));\n};\n\nconst computePlotScissorDevicePx = (\n  gridArea: GridArea\n): { readonly x: number; readonly y: number; readonly w: number; readonly h: number } => {\n  const { canvasWidth, canvasHeight, devicePixelRatio } = gridArea;\n\n  const plotLeftDevice = gridArea.left * devicePixelRatio;\n  const plotRightDevice = canvasWidth - gridArea.right * devicePixelRatio;\n  const plotTopDevice = gridArea.top * devicePixelRatio;\n  const plotBottomDevice = canvasHeight - gridArea.bottom * devicePixelRatio;\n\n  const scissorX = clampInt(Math.floor(plotLeftDevice), 0, Math.max(0, canvasWidth));\n  const scissorY = clampInt(Math.floor(plotTopDevice), 0, Math.max(0, canvasHeight));\n  const scissorR = clampInt(Math.ceil(plotRightDevice), 0, Math.max(0, canvasWidth));\n  const scissorB = clampInt(Math.ceil(plotBottomDevice), 0, Math.max(0, canvasHeight));\n  const scissorW = Math.max(0, scissorR - scissorX);\n  const scissorH = Math.max(0, scissorB - scissorY);\n\n  return { x: scissorX, y: scissorY, w: scissorW, h: scissorH };\n};\n\nconst clipXToCanvasCssPx = (xClip: number, canvasCssWidth: number): number => ((xClip + 1) / 2) * canvasCssWidth;\nconst clipYToCanvasCssPx = (yClip: number, canvasCssHeight: number): number => ((1 - yClip) / 2) * canvasCssHeight;\n\n// Alias for imported function to maintain compatibility with existing code\nconst isTupleOHLCDataPoint = isTupleOHLCDataPointImported;\n\nconst parseNumberOrPercent = (value: number | string, basis: number): number | null => {\n  if (typeof value === 'number') return Number.isFinite(value) ? value : null;\n  if (typeof value !== 'string') return null;\n\n  const s = value.trim();\n  if (s.length === 0) return null;\n\n  if (s.endsWith('%')) {\n    const pct = Number.parseFloat(s.slice(0, -1));\n    if (!Number.isFinite(pct)) return null;\n    return (pct / 100) * basis;\n  }\n\n  // Be permissive: allow numeric strings like \"120\".\n  const n = Number.parseFloat(s);\n  return Number.isFinite(n) ? n : null;\n};\n\nconst resolvePieCenterPlotCss = (\n  center: PieCenter | undefined,\n  plotWidthCss: number,\n  plotHeightCss: number\n): { readonly x: number; readonly y: number } => {\n  const xRaw = center?.[0] ?? '50%';\n  const yRaw = center?.[1] ?? '50%';\n\n  const x = parseNumberOrPercent(xRaw, plotWidthCss);\n  const y = parseNumberOrPercent(yRaw, plotHeightCss);\n\n  return {\n    x: Number.isFinite(x) ? x! : plotWidthCss * 0.5,\n    y: Number.isFinite(y) ? y! : plotHeightCss * 0.5,\n  };\n};\n\nconst isPieRadiusTuple = (\n  radius: PieRadius\n): radius is readonly [inner: number | string, outer: number | string] => Array.isArray(radius);\n\nconst resolvePieRadiiCss = (\n  radius: PieRadius | undefined,\n  maxRadiusCss: number\n): { readonly inner: number; readonly outer: number } => {\n  // Default similar to common chart libs (mirrors `createPieRenderer.ts`).\n  if (radius == null) return { inner: 0, outer: maxRadiusCss * 0.7 };\n\n  if (isPieRadiusTuple(radius)) {\n    const inner = parseNumberOrPercent(radius[0], maxRadiusCss);\n    const outer = parseNumberOrPercent(radius[1], maxRadiusCss);\n    const innerCss = Math.max(0, Number.isFinite(inner) ? inner! : 0);\n    const outerCss = Math.max(innerCss, Number.isFinite(outer) ? outer! : maxRadiusCss * 0.7);\n    return { inner: innerCss, outer: Math.min(maxRadiusCss, outerCss) };\n  }\n\n  const outer = parseNumberOrPercent(radius, maxRadiusCss);\n  const outerCss = Math.max(0, Number.isFinite(outer) ? outer! : maxRadiusCss * 0.7);\n  return { inner: 0, outer: Math.min(maxRadiusCss, outerCss) };\n};\n\nconst pad2 = (n: number): string => String(Math.trunc(n)).padStart(2, '0');\n\nconst MONTH_SHORT_EN: readonly string[] = [\n  'Jan',\n  'Feb',\n  'Mar',\n  'Apr',\n  'May',\n  'Jun',\n  'Jul',\n  'Aug',\n  'Sep',\n  'Oct',\n  'Nov',\n  'Dec',\n];\n\nconst formatTimeTickValue = (timestampMs: number, visibleRangeMs: number): string | null => {\n  if (!Number.isFinite(timestampMs)) return null;\n  if (!Number.isFinite(visibleRangeMs) || visibleRangeMs < 0) visibleRangeMs = 0;\n\n  const d = new Date(timestampMs);\n  // Guard against out-of-range timestamps that produce an invalid Date.\n  if (!Number.isFinite(d.getTime())) return null;\n  const yyyy = d.getFullYear();\n  const mm = d.getMonth() + 1; // 1-12\n  const dd = d.getDate();\n  const hh = d.getHours();\n  const min = d.getMinutes();\n\n  // Requirements (range in ms):\n  // - < 1 day: HH:mm\n  // - 1-7 days: MM/DD HH:mm\n  // - 1-12 weeks (and up to ~3 months): MM/DD\n  // - 3-12 months: MMM DD\n  // - > 1 year: YYYY/MM\n  if (visibleRangeMs < MS_PER_DAY) {\n    return `${pad2(hh)}:${pad2(min)}`;\n  }\n  // Treat the 7-day boundary as inclusive for the “1–7 days” tier.\n  if (visibleRangeMs <= 7 * MS_PER_DAY) {\n    return `${pad2(mm)}/${pad2(dd)} ${pad2(hh)}:${pad2(min)}`;\n  }\n  // Keep short calendar dates until the visible range reaches ~3 months.\n  // (This covers the 1–12 week requirement, plus the small 12w→3m gap.)\n  if (visibleRangeMs < 3 * MS_PER_MONTH_APPROX) {\n    return `${pad2(mm)}/${pad2(dd)}`;\n  }\n  if (visibleRangeMs <= MS_PER_YEAR_APPROX) {\n    const mmm = MONTH_SHORT_EN[d.getMonth()] ?? pad2(mm);\n    return `${mmm} ${pad2(dd)}`;\n  }\n  return `${yyyy}/${pad2(mm)}`;\n};\n\nconst generateLinearTicks = (domainMin: number, domainMax: number, tickCount: number): number[] => {\n  const count = Math.max(1, Math.floor(tickCount));\n  const ticks: number[] = new Array(count);\n  for (let i = 0; i < count; i++) {\n    const t = count === 1 ? 0.5 : i / (count - 1);\n    ticks[i] = domainMin + t * (domainMax - domainMin);\n  }\n  return ticks;\n};\n\nconst computeAdaptiveTimeXAxisTicks = (params: {\n  readonly axisMin: number | null;\n  readonly axisMax: number | null;\n  readonly xScale: LinearScale;\n  readonly plotClipLeft: number;\n  readonly plotClipRight: number;\n  readonly canvasCssWidth: number;\n  readonly visibleRangeMs: number;\n  readonly measureCtx: CanvasRenderingContext2D | null;\n  readonly measureCache?: Map<string, number>;\n  readonly fontSize: number;\n  readonly fontFamily: string;\n  readonly tickFormatter?: (value: number) => string | null;\n}): { readonly tickCount: number; readonly tickValues: readonly number[] } => {\n  const {\n    axisMin,\n    axisMax,\n    xScale,\n    plotClipLeft,\n    plotClipRight,\n    canvasCssWidth,\n    visibleRangeMs,\n    measureCtx,\n    measureCache,\n    fontSize,\n    fontFamily,\n    tickFormatter,\n  } = params;\n\n  // Domain fallback matches `createAxisRenderer` (use explicit min/max when provided).\n  const domainMin = finiteOrNull(axisMin) ?? xScale.invert(plotClipLeft);\n  const domainMax = finiteOrNull(axisMax) ?? xScale.invert(plotClipRight);\n\n  if (!measureCtx || canvasCssWidth <= 0) {\n    return { tickCount: DEFAULT_TICK_COUNT, tickValues: generateLinearTicks(domainMin, domainMax, DEFAULT_TICK_COUNT) };\n  }\n\n  // Ensure the measurement font matches the overlay labels.\n  measureCtx.font = `${fontSize}px ${fontFamily}`;\n  if (measureCache && measureCache.size > 2000) measureCache.clear();\n\n  // Pre-construct the font part of the cache key to avoid repeated concatenation.\n  const cacheKeyPrefix = measureCache ? `${fontSize}px ${fontFamily}@@` : null;\n\n  for (let tickCount = MAX_TIME_X_TICK_COUNT; tickCount >= MIN_TIME_X_TICK_COUNT; tickCount--) {\n    const tickValues = generateLinearTicks(domainMin, domainMax, tickCount);\n\n    // Compute label extents in *canvas-local CSS px* and ensure adjacent labels don't overlap.\n    let prevRight = Number.NEGATIVE_INFINITY;\n    let ok = true;\n\n    for (let i = 0; i < tickValues.length; i++) {\n      const v = tickValues[i]!;\n      const label = tickFormatter ? tickFormatter(v) : formatTimeTickValue(v, visibleRangeMs);\n      if (label == null) continue;\n\n      const w = (() => {\n        if (!cacheKeyPrefix) return measureCtx.measureText(label).width;\n        const key = cacheKeyPrefix + label;\n        const cached = measureCache!.get(key);\n        if (cached != null) return cached;\n        const measured = measureCtx.measureText(label).width;\n        measureCache!.set(key, measured);\n        return measured;\n      })();\n      const xClip = xScale.scale(v);\n      const xCss = clipXToCanvasCssPx(xClip, canvasCssWidth);\n\n      const anchor: TextOverlayAnchor =\n        tickCount === 1 ? 'middle' : i === 0 ? 'start' : i === tickValues.length - 1 ? 'end' : 'middle';\n\n      const left = anchor === 'start' ? xCss : anchor === 'end' ? xCss - w : xCss - w * 0.5;\n      const right = anchor === 'start' ? xCss + w : anchor === 'end' ? xCss : xCss + w * 0.5;\n\n      if (left < prevRight + MIN_X_LABEL_GAP_CSS_PX) {\n        ok = false;\n        break;\n      }\n      prevRight = right;\n    }\n\n    if (ok) {\n      return { tickCount, tickValues };\n    }\n  }\n\n  return { tickCount: MIN_TIME_X_TICK_COUNT, tickValues: generateLinearTicks(domainMin, domainMax, MIN_TIME_X_TICK_COUNT) };\n};\n\nconst computeBaseXDomain = (\n  options: ResolvedChartGPUOptions,\n  runtimeRawBoundsByIndex?: ReadonlyArray<Bounds | null> | null\n): { readonly min: number; readonly max: number } => {\n  const bounds = computeGlobalBounds(options.series, runtimeRawBoundsByIndex);\n  const baseMin = finiteOrUndefined(options.xAxis.min) ?? bounds.xMin;\n  const baseMax = finiteOrUndefined(options.xAxis.max) ?? bounds.xMax;\n  return normalizeDomain(baseMin, baseMax);\n};\n\n/**\n * Computes Y-axis domain bounds from the visible/rendered series data.\n * This avoids scanning the full raw dataset when yAxis.autoBounds === 'visible'.\n * \n * Performance: O(n) where n = total points across all visible series data.\n * This is called only when renderSeries changes (zoom/pan/data updates), not per-frame.\n */\nconst computeVisibleYBounds = (series: ResolvedChartGPUOptions['series']): Bounds => {\n  let yMin = Number.POSITIVE_INFINITY;\n  let yMax = Number.NEGATIVE_INFINITY;\n\n  for (let s = 0; s < series.length; s++) {\n    const seriesConfig = series[s];\n    // Pie series are non-cartesian; they don't participate in y bounds.\n    if (seriesConfig.type === 'pie') continue;\n\n    // Candlestick series: scan low/high from visible data\n    if (seriesConfig.type === 'candlestick') {\n      const visibleOHLC = seriesConfig.data as ReadonlyArray<OHLCDataPoint>;\n      for (let i = 0; i < visibleOHLC.length; i++) {\n        const p = visibleOHLC[i]!;\n        const low = isTupleOHLCDataPoint(p) ? p[3] : p.low;\n        const high = isTupleOHLCDataPoint(p) ? p[4] : p.high;\n        if (!Number.isFinite(low) || !Number.isFinite(high)) continue;\n\n        // Use Math.min/max to handle inverted low/high gracefully\n        const yLow = Math.min(low, high);\n        const yHigh = Math.max(low, high);\n\n        if (yLow < yMin) yMin = yLow;\n        if (yHigh > yMax) yMax = yHigh;\n      }\n      continue;\n    }\n\n    // Cartesian series (line, area, bar, scatter): scan y from visible data\n    const data = seriesConfig.data as CartesianSeriesData;\n    const n = getPointCount(data);\n    for (let i = 0; i < n; i++) {\n      const y = getY(data, i);\n      if (!Number.isFinite(y)) continue;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  }\n\n  // Fallback for empty/invalid data: return safe default bounds\n  if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    return { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };\n  }\n\n  // Degenerate domain: add unit span to avoid zero-width range\n  if (yMin === yMax) yMax = yMin + 1;\n\n  return { xMin: 0, xMax: 1, yMin, yMax };\n};\n\nconst computeBaseYDomain = (\n  options: ResolvedChartGPUOptions,\n  runtimeRawBoundsByIndex?: ReadonlyArray<Bounds | null> | null,\n  visibleBoundsOverride?: Bounds | null\n): { readonly min: number; readonly max: number } => {\n  // Explicit min/max ALWAYS take precedence over auto-bounds\n  const explicitMin = finiteOrUndefined(options.yAxis.min);\n  const explicitMax = finiteOrUndefined(options.yAxis.max);\n\n  // If both min and max are explicit, use them directly (no auto-bounds needed)\n  if (explicitMin !== undefined && explicitMax !== undefined) {\n    return normalizeDomain(explicitMin, explicitMax);\n  }\n\n  // Determine which bounds to use based on autoBounds mode\n  const autoBoundsMode = options.yAxis.autoBounds ?? 'visible';\n  let bounds: Bounds;\n\n  if (autoBoundsMode === 'visible' && visibleBoundsOverride) {\n    // Use visible bounds from renderSeries (zoom-aware, computed from visible data only)\n    bounds = visibleBoundsOverride;\n  } else {\n    // Use global bounds from full dataset (pre-zoom behavior, computed from all raw data)\n    bounds = computeGlobalBounds(options.series, runtimeRawBoundsByIndex);\n  }\n\n  // Merge explicit bounds with computed bounds (partial override support)\n  const yMin = explicitMin ?? bounds.yMin;\n  const yMax = explicitMax ?? bounds.yMax;\n  return normalizeDomain(yMin, yMax);\n};\n\nconst computeVisibleXDomain = (\n  baseXDomain: { readonly min: number; readonly max: number },\n  zoomRange?: ZoomRange | null\n): { readonly min: number; readonly max: number; readonly spanFraction: number } => {\n  if (!zoomRange) return { ...baseXDomain, spanFraction: 1 };\n  const span = baseXDomain.max - baseXDomain.min;\n  if (!Number.isFinite(span) || span === 0) return { ...baseXDomain, spanFraction: 1 };\n\n  const start = zoomRange.start;\n  const end = zoomRange.end;\n  const xMin = baseXDomain.min + (start / 100) * span;\n  const xMax = baseXDomain.min + (end / 100) * span;\n  const normalized = normalizeDomain(xMin, xMax);\n\n  const fractionRaw = (end - start) / 100;\n  const spanFraction = Number.isFinite(fractionRaw) ? Math.max(0, Math.min(1, fractionRaw)) : 1;\n  return { min: normalized.min, max: normalized.max, spanFraction };\n};\n\ntype IntroPhase = 'pending' | 'running' | 'done';\n\nconst resolveAnimationConfig = (\n  animation: ResolvedChartGPUOptions['animation']\n):\n  | {\n      readonly durationMs: number;\n      readonly delayMs: number;\n      readonly easing: EasingFunction;\n    }\n  | null => {\n  if (animation === false || animation == null) return null;\n\n  const cfg: AnimationConfig | null = animation === true ? {} : animation;\n  if (!cfg) return null;\n\n  const durationMsRaw = cfg.duration ?? 300;\n  const delayMsRaw = cfg.delay ?? 0;\n\n  const durationMs = Number.isFinite(durationMsRaw) ? Math.max(0, durationMsRaw) : 300;\n  const delayMs = Number.isFinite(delayMsRaw) ? Math.max(0, delayMsRaw) : 0;\n\n  return {\n    durationMs,\n    delayMs,\n    easing: getEasing(cfg.easing),\n  };\n};\n\nconst resolveIntroAnimationConfig = (animation: ResolvedChartGPUOptions['animation']) => resolveAnimationConfig(animation);\nconst resolveUpdateAnimationConfig = (animation: ResolvedChartGPUOptions['animation']) => resolveAnimationConfig(animation);\n\n/**\n * Computes container-local CSS pixel anchor coordinates for a candlestick tooltip.\n *\n * The anchor is positioned near the candle body center for stable tooltip positioning\n * even when the cursor is at the edge of the candlestick.\n *\n * Coordinate transformations:\n * 1. Domain values (timestamp, open, close) from CandlestickMatch\n * 2. → xScale/yScale transform to grid-local CSS pixels\n * 3. → Add gridArea offset to get canvas-local CSS pixels\n * 4. → Add canvas offset to get container-local CSS pixels\n *\n * Returns null if any coordinate computation fails (non-finite values).\n */\nconst computeCandlestickTooltipAnchor = (\n  match: { readonly point: OHLCDataPoint },\n  xScale: LinearScale,\n  yScale: LinearScale,\n  gridArea: GridArea,\n  canvas: HTMLCanvasElement\n): Readonly<{ x: number; y: number }> | null => {\n  const point = match.point;\n  \n  const timestamp = isTupleOHLCDataPoint(point) ? point[0] : point.timestamp;\n  const open = isTupleOHLCDataPoint(point) ? point[1] : point.open;\n  const close = isTupleOHLCDataPoint(point) ? point[2] : point.close;\n\n  if (!Number.isFinite(timestamp) || !Number.isFinite(open) || !Number.isFinite(close)) {\n    return null;\n  }\n\n  // Body center in domain space\n  const bodyMidY = (open + close) / 2;\n\n  // Transform to grid-local CSS pixels\n  const xGridCss = xScale.scale(timestamp);\n  const yGridCss = yScale.scale(bodyMidY);\n\n  if (!Number.isFinite(xGridCss) || !Number.isFinite(yGridCss)) {\n    return null;\n  }\n\n  // Convert to canvas-local CSS pixels\n  const xCanvasCss = gridArea.left + xGridCss;\n  const yCanvasCss = gridArea.top + yGridCss;\n\n  // Convert to container-local CSS pixels\n  const xContainerCss = isHTMLCanvasElement(canvas) ? canvas.offsetLeft + xCanvasCss : xCanvasCss;\n  const yContainerCss = isHTMLCanvasElement(canvas) ? canvas.offsetTop + yCanvasCss : yCanvasCss;\n\n  if (!Number.isFinite(xContainerCss) || !Number.isFinite(yContainerCss)) {\n    return null;\n  }\n\n  return { x: xContainerCss, y: yContainerCss };\n};\n\nconst computeBaselineForBarsFromData = (seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>): number => {\n  let yMin = Number.POSITIVE_INFINITY;\n  let yMax = Number.NEGATIVE_INFINITY;\n\n  for (let s = 0; s < seriesConfigs.length; s++) {\n    const data = seriesConfigs[s]!.data as CartesianSeriesData;\n    const n = getPointCount(data);\n    for (let i = 0; i < n; i++) {\n      const y = getY(data, i);\n      if (!Number.isFinite(y)) continue;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  }\n\n  if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) return 0;\n  if (yMin <= 0 && 0 <= yMax) return 0;\n  return Math.abs(yMin) < Math.abs(yMax) ? yMin : yMax;\n};\n\nconst computeBaselineForBarsFromAxis = (\n  seriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n  yScale: LinearScale,\n  plotClipRect: Readonly<{ top: number; bottom: number }>\n): number => {\n  const yDomainA = yScale.invert(plotClipRect.bottom);\n  const yDomainB = yScale.invert(plotClipRect.top);\n  const yMin = Math.min(yDomainA, yDomainB);\n  const yMax = Math.max(yDomainA, yDomainB);\n\n  if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    return computeBaselineForBarsFromData(seriesConfigs);\n  }\n\n  if (yMin <= 0 && 0 <= yMax) return 0;\n  if (yMin > 0) return yMin;\n  if (yMax < 0) return yMax;\n  return computeBaselineForBarsFromData(seriesConfigs);\n};\n\nconst createAnimatedBarYScale = (\n  baseYScale: LinearScale,\n  plotClipRect: Readonly<{ top: number; bottom: number }>,\n  barSeriesConfigs: ReadonlyArray<ResolvedBarSeriesConfig>,\n  progress01: number\n): LinearScale => {\n  const p = clamp01(progress01);\n  if (p >= 1) return baseYScale;\n\n  const baselineDomain = computeBaselineForBarsFromAxis(barSeriesConfigs, baseYScale, plotClipRect);\n  const baselineClip = baseYScale.scale(baselineDomain);\n\n  const wrapper: LinearScale = {\n    domain(min: number, max: number) {\n      baseYScale.domain(min, max);\n      return wrapper;\n    },\n    range(min: number, max: number) {\n      baseYScale.range(min, max);\n      return wrapper;\n    },\n    scale(value: number) {\n      const v = baseYScale.scale(value);\n      if (!Number.isFinite(v) || !Number.isFinite(baselineClip)) return v;\n      return baselineClip + (v - baselineClip) * p;\n    },\n    invert(pixel: number) {\n      return baseYScale.invert(pixel);\n    },\n  };\n\n  return wrapper;\n};\n\nexport function createRenderCoordinator(\n  gpuContext: GPUContext,\n  options: ResolvedChartGPUOptions,\n  callbacks?: RenderCoordinatorCallbacks\n): RenderCoordinator {\n  if (!gpuContext.initialized) {\n    throw new Error('RenderCoordinator: gpuContext must be initialized.');\n  }\n  const device = gpuContext.device;\n  if (!device) {\n    throw new Error('RenderCoordinator: gpuContext.device is required.');\n  }\n  if (!gpuContext.canvas) {\n    throw new Error('RenderCoordinator: gpuContext.canvas is required.');\n  }\n  if (!gpuContext.canvasContext) {\n    throw new Error('RenderCoordinator: gpuContext.canvasContext is required.');\n  }\n\n  const targetFormat = gpuContext.preferredFormat ?? DEFAULT_TARGET_FORMAT;\n  const pipelineCache = callbacks?.pipelineCache;\n  \n  // DOM-dependent features (overlays, legends) require HTMLCanvasElement.\n  const overlayContainer = isHTMLCanvasElement(gpuContext.canvas) ? gpuContext.canvas.parentElement : null;\n  const axisLabelOverlay: TextOverlay | null = overlayContainer ? createTextOverlay(overlayContainer) : null;\n  // Dedicated overlay for annotations (do not reuse axis label overlay).\n  const annotationOverlay: TextOverlay | null = overlayContainer ? createTextOverlay(overlayContainer, { clip: true }) : null;\n\n  const handleSeriesToggle = (seriesIndex: number, sliceIndex?: number): void => {\n    if (disposed) return;\n\n    const series = currentOptions.series;\n    if (seriesIndex < 0 || seriesIndex >= series.length) return;\n\n    const s = series[seriesIndex];\n    if (!s) return;\n\n    // Handle pie slice toggle\n    if (sliceIndex !== undefined && s.type === 'pie') {\n      const pieData = (s as ResolvedPieSeriesConfig).data;\n      if (sliceIndex < 0 || sliceIndex >= pieData.length) return;\n\n      const updatedData = pieData.map((slice, i) =>\n        i === sliceIndex\n          ? { ...slice, visible: slice.visible === false ? true : false }\n          : slice\n      );\n\n      const updatedSeries = series.map((seriesItem, i) =>\n        i === seriesIndex\n          ? ({ ...seriesItem, data: updatedData } as typeof seriesItem)\n          : seriesItem\n      );\n\n      setOptions({ ...currentOptions, series: updatedSeries });\n      return;\n    }\n\n    // Toggle regular series visibility\n    const updatedSeries = series.map((seriesItem, i) =>\n      i === seriesIndex\n        ? ({ ...seriesItem, visible: seriesItem.visible === false ? true : false } as typeof seriesItem)\n        : seriesItem\n    );\n\n    // Update options with new series array\n    setOptions({ ...currentOptions, series: updatedSeries });\n  };\n\n  const legend: Legend | null = overlayContainer && options.legend?.show!==false ? createLegend(overlayContainer, options.legend?.position, handleSeriesToggle) : null;\n  // Text measurement for axis labels. Requires DOM context.\n  const tickMeasureCtx: CanvasRenderingContext2D | null = (() => {\n    if (typeof document === 'undefined') {\n      // No DOM available (e.g., SSR or non-browser environment).\n      return null;\n    }\n    try {\n      const c = document.createElement('canvas');\n      return c.getContext('2d');\n    } catch {\n      return null;\n    }\n  })();\n  const tickMeasureCache: Map<string, number> | null = tickMeasureCtx ? new Map() : null;\n\n  let disposed = false;\n  let currentOptions: ResolvedChartGPUOptions = options;\n  let lastSeriesCount = options.series.length;\n\n  // Story 5.16: initial-load intro animation (series marks only).\n  let introPhase: IntroPhase = 'pending';\n  let introProgress01 = 0;\n  const introAnimController = createAnimationController();\n  let introAnimId: AnimationId | null = null;\n\n  // Story 5.17 (step 1): data update transition state (snapshots only; interpolation occurs later).\n  type UpdateTransitionSnapshot = Readonly<{\n    readonly xBaseDomain: { readonly min: number; readonly max: number };\n    readonly xVisibleDomain: { readonly min: number; readonly max: number };\n    readonly yBaseDomain: { readonly min: number; readonly max: number };\n    readonly series: ResolvedChartGPUOptions['series'];\n  }>;\n\n  type UpdateTransition = Readonly<{\n    readonly from: UpdateTransitionSnapshot;\n    readonly to: UpdateTransitionSnapshot;\n  }>;\n\n  let hasRenderedOnce = false;\n  const updateAnimController = createAnimationController();\n  let updateAnimId: AnimationId | null = null;\n  let updateProgress01 = 1;\n  let updateTransition: UpdateTransition | null = null;\n\n  type UpdateInterpolationCaches = Readonly<{\n    readonly cartesianDataBySeriesIndex: Array<DataPoint[] | null>;\n    readonly pieDataBySeriesIndex: Array<ResolvedPieSeriesConfig['data'] | null>;\n  }>;\n\n  const updateInterpolationCaches: UpdateInterpolationCaches = {\n    cartesianDataBySeriesIndex: [],\n    pieDataBySeriesIndex: [],\n  };\n\n  const resetUpdateInterpolationCaches = (): void => {\n    updateInterpolationCaches.cartesianDataBySeriesIndex.length = 0;\n    updateInterpolationCaches.pieDataBySeriesIndex.length = 0;\n  };\n\n  const interpolateCartesianSeriesDataByIndex = (\n    fromData: CartesianSeriesData,\n    toData: CartesianSeriesData,\n    n: number,\n    t01: number,\n    cache: DataPoint[] | null\n  ): DataPoint[] | null => {\n    if (n === 0) return cache ?? [];\n\n    const out =\n      cache && cache.length === n\n        ? cache\n        : (() => {\n            const created: DataPoint[] = new Array(n);\n            for (let i = 0; i < n; i++) {\n              const x = getX(toData, i);\n              created[i] = [x, 0] as const;\n            }\n            return created;\n          })();\n\n    const t = clamp01(t01);\n    for (let i = 0; i < n; i++) {\n      const xFrom = getX(fromData, i);\n      const xTo = getX(toData, i);\n      const yFrom = getY(fromData, i);\n      const yTo = getY(toData, i);\n      // Interpolate both x and y so scatter (and any series with shifting x) animates smoothly.\n      // For series where x doesn't change (line, area, bar with fixed indices), lerp(x, x, t) = x.\n      const x = Number.isFinite(xFrom) && Number.isFinite(xTo) ? lerp(xFrom, xTo, t) : xTo;\n      const y = Number.isFinite(yFrom) && Number.isFinite(yTo) ? lerp(yFrom, yTo, t) : yTo;\n      const p = out[i]!;\n      if (isTupleDataPoint(p)) {\n        (p as unknown as number[])[0] = x;\n        (p as unknown as number[])[1] = y;\n      } else {\n        (p as any).x = x;\n        (p as any).y = y;\n      }\n    }\n\n    return out;\n  };\n\n  const interpolatePieSeriesByIndex = (\n    fromSeries: ResolvedPieSeriesConfig,\n    toSeries: ResolvedPieSeriesConfig,\n    t01: number,\n    cache: ResolvedPieSeriesConfig['data'] | null\n  ): ResolvedPieSeriesConfig => {\n    const fromData = fromSeries.data;\n    const toData = toSeries.data;\n    if (fromData.length !== toData.length) return toSeries;\n\n    const n = toData.length;\n    const out =\n      cache && cache.length === n\n        ? cache\n        : (() => {\n            const created: any[] = new Array(n);\n            for (let i = 0; i < n; i++) {\n              // Preserve name/color from \"to\"; patch value per frame.\n              created[i] = { ...toData[i]!, value: 0 };\n            }\n            return created as ResolvedPieSeriesConfig['data'];\n          })();\n\n    const t = clamp01(t01);\n    for (let i = 0; i < n; i++) {\n      const vFrom = (fromData[i] as any)?.value;\n      const vTo = (toData[i] as any)?.value;\n      const next =\n        typeof vFrom === 'number' && typeof vTo === 'number' && Number.isFinite(vFrom) && Number.isFinite(vTo)\n          ? Math.max(0, lerp(vFrom, vTo, t))\n          : typeof vTo === 'number' && Number.isFinite(vTo)\n            ? vTo\n            : 0;\n      (out[i] as any).value = next;\n    }\n\n    return { ...toSeries, data: out };\n  };\n\n  const interpolateSeriesForUpdate = (\n    fromSeries: ResolvedChartGPUOptions['series'],\n    toSeries: ResolvedChartGPUOptions['series'],\n    t01: number,\n    caches: UpdateInterpolationCaches | null\n  ): ResolvedChartGPUOptions['series'] => {\n    if (fromSeries.length !== toSeries.length) return toSeries;\n\n    const out: ResolvedChartGPUOptions['series'][number][] = new Array(toSeries.length);\n\n    for (let i = 0; i < toSeries.length; i++) {\n      const a = fromSeries[i]!;\n      const b = toSeries[i]!;\n\n      if (a.type !== b.type) {\n        out[i] = b;\n        continue;\n      }\n\n      if (b.type === 'pie') {\n        const cache = caches?.pieDataBySeriesIndex[i] ?? null;\n        const animated = interpolatePieSeriesByIndex(a as ResolvedPieSeriesConfig, b as ResolvedPieSeriesConfig, t01, cache);\n        if (caches) caches.pieDataBySeriesIndex[i] = animated.data as any;\n        out[i] = animated;\n        continue;\n      }\n\n      // Cartesian series: interpolate y-values by index. Keep x from \"to\".\n      // Data may be ReadonlyArray<DataPoint> OR MutableXYColumns (XYArraysData-compatible) at runtime,\n      // so use getPointCount/getX/getY instead of .length / direct indexing.\n      const aData = (a as unknown as { readonly data: CartesianSeriesData }).data;\n      const bData = (b as unknown as { readonly data: CartesianSeriesData }).data;\n\n      const aLen = getPointCount(aData);\n      const bLen = getPointCount(bData);\n\n      if (aLen !== bLen) {\n        out[i] = b;\n        continue;\n      }\n      if (bLen > MAX_ANIMATED_POINTS_PER_SERIES) {\n        out[i] = b;\n        continue;\n      }\n\n      const cache = caches?.cartesianDataBySeriesIndex[i] ?? null;\n      const animatedData = interpolateCartesianSeriesDataByIndex(aData, bData, aLen, t01, cache);\n      if (!animatedData) {\n        out[i] = b;\n        continue;\n      }\n      if (caches) caches.cartesianDataBySeriesIndex[i] = animatedData;\n\n      out[i] = { ...(b as any), data: animatedData };\n    }\n\n    return out;\n  };\n\n  const computeUpdateSnapshotAtProgress = (\n    transition: UpdateTransition,\n    t01: number,\n    zoomRange: ZoomRange | null\n  ): UpdateTransitionSnapshot => {\n    const xBase = lerpDomain(transition.from.xBaseDomain, transition.to.xBaseDomain, t01);\n    const xVisible = computeVisibleXDomain(xBase, zoomRange);\n    const yBase = lerpDomain(transition.from.yBaseDomain, transition.to.yBaseDomain, t01);\n    const series = interpolateSeriesForUpdate(transition.from.series, transition.to.series, t01, null);\n    return {\n      xBaseDomain: xBase,\n      xVisibleDomain: { min: xVisible.min, max: xVisible.max },\n      yBaseDomain: yBase,\n      series,\n    };\n  };\n\n  // Prevent spamming console.warn for repeated misuse.\n  const warnedPieAppendSeries = new Set<number>();\n  const warnedSamplingDefeatsFastPath = new Set<number>();\n\n  // Coordinator-owned runtime series store.\n  // - `runtimeRawDataByIndex[i]` owns mutable columnar data (MutableXYColumns) for cartesian series,\n  //   or mutable OHLCDataPoint[] for candlestick series. Supports efficient streaming appends\n  //   without per-point object allocations.\n  // - `runtimeRawBoundsByIndex[i]` is incrementally updated to keep scale/bounds derivation cheap.\n  let runtimeRawDataByIndex: Array<MutableXYColumns | OHLCDataPoint[] | null> = new Array(options.series.length).fill(null);\n  let runtimeRawBoundsByIndex: Array<Bounds | null> = new Array(options.series.length).fill(null);\n\n  // Baseline sampled series list derived from runtime raw data (used as the “full span” baseline).\n  // Zoom-visible resampling is derived from this baseline + runtime raw as needed.\n  let runtimeBaseSeries: ResolvedChartGPUOptions['series'] = currentOptions.series;\n\n  // Zoom-aware sampled series list used for rendering + cartesian hit-testing.\n  // Derived from `currentOptions.series` (which still includes baseline sampled `data`).\n  let renderSeries: ResolvedChartGPUOptions['series'] = currentOptions.series;\n\n  // Cache for visible y-bounds computed from renderSeries (for yAxis.autoBounds === 'visible').\n  // Recomputed whenever renderSeries changes (zoom/pan/data updates).\n  let cachedVisibleYBounds: Bounds | null = null;\n\n  const shouldComputeVisibleYBounds = (opts: ResolvedChartGPUOptions): boolean => {\n    const autoBoundsMode = opts.yAxis.autoBounds ?? 'visible';\n    if (autoBoundsMode !== 'visible') return false;\n    // If both bounds are explicit, auto-bounds (including visible) are never consulted.\n    const explicitMin = finiteOrUndefined(opts.yAxis.min);\n    const explicitMax = finiteOrUndefined(opts.yAxis.max);\n    return !(explicitMin !== undefined && explicitMax !== undefined);\n  };\n\n  const recomputeCachedVisibleYBoundsIfNeeded = (): void => {\n    if (shouldComputeVisibleYBounds(currentOptions)) {\n      cachedVisibleYBounds = computeVisibleYBounds(renderSeries);\n    } else {\n      cachedVisibleYBounds = null;\n    }\n  };\n\n  // Cache for sampled data with buffer zones - enables fast slicing during pan without resampling.\n  interface SampledDataCache {\n    data: CartesianSeriesData | ReadonlyArray<OHLCDataPoint>;\n    cachedRange: { min: number; max: number };\n    timestamp: number;\n  }\n  let lastSampledData: Array<SampledDataCache | null> = [];\n\n  // Unified flush scheduler (appends + zoom-aware resampling + optional GPU streaming updates).\n  let flushScheduled = false;\n  let flushRafId: number | null = null;\n  let flushTimeoutId: number | null = null;\n\n  // Zoom changes are debounced to avoid churn while wheel/drag is active.\n  // When the debounce fires, we mark resampling \"due\" and schedule a unified flush.\n  let zoomResampleDebounceTimer: number | null = null;\n  let zoomResampleDue = false;\n\n  // Zoom changes can fire multiple times per frame; slicing and visible-bounds recompute can be O(n).\n  // Coalesce those updates to at most once per rendered frame.\n  let sliceRenderSeriesDue = false;\n\n  // Coalesced streaming appends (flushed at the start of `render()`).\n  // Each entry is an array of batches (preserving original format to avoid per-point allocations).\n  const pendingAppendByIndex = new Map<number, Array<CartesianSeriesData | ReadonlyArray<OHLCDataPoint>>>();\n\n  // Tracks what the DataStore currently represents for each series index.\n  // Used to decide whether `appendSeries(...)` is a correct fast-path.\n  type GpuSeriesKind = 'unknown' | 'fullRawLine' | 'other';\n  let gpuSeriesKindByIndex: GpuSeriesKind[] = new Array(currentOptions.series.length).fill('unknown');\n  const appendedGpuThisFrame = new Set<number>();\n\n  // Tooltip is a DOM overlay element; enable by default unless explicitly disabled.\n  let tooltip: Tooltip | null =\n    overlayContainer && currentOptions.tooltip?.show !== false ? createTooltip(overlayContainer) : null;\n\n  // Cache tooltip state to avoid unnecessary DOM updates\n  let lastTooltipContent: string | null = null;\n  let lastTooltipX: number | null = null;\n  let lastTooltipY: number | null = null;\n\n  // Helper functions for tooltip/legend management\n  const showTooltipInternal = (x: number, y: number, content: string, _params: TooltipParams | TooltipParams[]) => {\n    tooltip?.show(x, y, content);\n  };\n\n  const hideTooltipInternal = () => {\n    tooltip?.hide();\n  };\n\n  const hideTooltip = () => {\n    lastTooltipContent = null;\n    lastTooltipX = null;\n    lastTooltipY = null;\n    hideTooltipInternal();\n  };\n\n  const updateLegend = (series: ResolvedChartGPUOptions['series'], theme: ResolvedChartGPUOptions['theme']) => {\n    legend?.update(series, theme);\n  };\n\n  updateLegend(currentOptions.series, currentOptions.theme);\n\n  let dataStore = createDataStore(device);\n\n  const gridRenderer = createGridRenderer(device, { targetFormat, sampleCount: MAIN_SCENE_MSAA_SAMPLE_COUNT, pipelineCache });\n  // Axis and crosshair renderers draw into the top overlay pass (swapchain, single-sample) — keep sampleCount: 1.\n  const xAxisRenderer = createAxisRenderer(device, { targetFormat, pipelineCache });\n  const yAxisRenderer = createAxisRenderer(device, { targetFormat, pipelineCache });\n  const crosshairRenderer = createCrosshairRenderer(device, { targetFormat, pipelineCache });\n  crosshairRenderer.setVisible(false);\n  // Highlight renders into the top overlay pass (swapchain, single-sample) — keep sampleCount: 1.\n  const highlightRenderer = createHighlightRenderer(device, { targetFormat, pipelineCache });\n  highlightRenderer.setVisible(false);\n\n  // MSAA for the *annotation overlay* (above-series) pass to reduce shimmer during zoom.\n  // NOTE: In WebGPU, pipeline sampleCount must match the render pass attachment sampleCount.\n  // The main scene renders into a 4x MSAA texture (resolved to a single-sample target), then:\n  // - MSAA overlay pass: blit resolved main scene into an MSAA target + draw above-series annotations, resolve to swapchain\n  // - Top overlay pass: draw crosshair/axes/highlight (single-sample) on top of the resolved swapchain\n  // Below-series reference lines and annotation markers draw into the main MSAA pass.\n  const referenceLineRenderer = createReferenceLineRenderer(device, { targetFormat, sampleCount: MAIN_SCENE_MSAA_SAMPLE_COUNT, pipelineCache });\n  const annotationMarkerRenderer = createAnnotationMarkerRenderer(device, { targetFormat, sampleCount: MAIN_SCENE_MSAA_SAMPLE_COUNT, pipelineCache });\n  const referenceLineRendererMsaa = createReferenceLineRenderer(device, {\n    targetFormat,\n    sampleCount: ANNOTATION_OVERLAY_MSAA_SAMPLE_COUNT,\n    pipelineCache,\n  });\n  const annotationMarkerRendererMsaa = createAnnotationMarkerRenderer(device, {\n    targetFormat,\n    sampleCount: ANNOTATION_OVERLAY_MSAA_SAMPLE_COUNT,\n    pipelineCache,\n  });\n\n  const textureManager = createTextureManager({ device, targetFormat, pipelineCache });\n\n  const initialGridArea = computeGridArea(gpuContext, currentOptions);\n  \n  // Event manager requires HTMLCanvasElement (DOM events).\n  const eventManager = isHTMLCanvasElement(gpuContext.canvas) \n    ? createEventManager(gpuContext.canvas, initialGridArea)\n    : null;\n\n  type PointerSource = 'mouse' | 'sync';\n\n  type PointerState = Readonly<{\n    source: PointerSource;\n    x: number;\n    y: number;\n    gridX: number;\n    gridY: number;\n    isInGrid: boolean;\n    hasPointer: boolean;\n  }>;\n\n  let pointerState: PointerState = {\n    source: 'mouse',\n    x: 0,\n    y: 0,\n    gridX: 0,\n    gridY: 0,\n    isInGrid: false,\n    hasPointer: false,\n  };\n\n  // Interaction-x state (domain units). This drives chart sync.\n  let interactionX: number | null = null;\n  let interactionXSource: unknown = undefined;\n  const interactionXListeners = new Set<(x: number | null, source?: unknown) => void>();\n\n  // Cached interaction scales from the last render (used for pointer -> domain-x mapping).\n  let lastInteractionScales:\n    | {\n        readonly xScale: LinearScale;\n        readonly yScale: LinearScale;\n        readonly plotWidthCss: number;\n        readonly plotHeightCss: number;\n      }\n    | null = null;\n\n  const emitInteractionX = (nextX: number | null, source?: unknown): void => {\n    const snapshot = Array.from(interactionXListeners);\n    for (const cb of snapshot) cb(nextX, source);\n  };\n\n  const setInteractionXInternal = (nextX: number | null, source?: unknown): void => {\n    const normalized = nextX !== null && Number.isFinite(nextX) ? nextX : null;\n    if (interactionX === normalized && interactionXSource === source) return;\n    interactionX = normalized;\n    interactionXSource = source;\n    emitInteractionX(interactionX, interactionXSource);\n  };\n\n  const requestRender = (): void => {\n    callbacks?.onRequestRender?.();\n  };\n\n  const isFullSpanZoomRange = (range: ZoomRange | null): boolean => {\n    if (!range) return true;\n    return (\n      Number.isFinite(range.start) &&\n      Number.isFinite(range.end) &&\n      range.start <= 0 &&\n      range.end >= 100\n    );\n  };\n\n  const cancelScheduledFlush = (): void => {\n    if (flushRafId !== null) {\n      cancelAnimationFrame(flushRafId);\n      flushRafId = null;\n    }\n    if (flushTimeoutId !== null) {\n      clearTimeout(flushTimeoutId);\n      flushTimeoutId = null;\n    }\n    flushScheduled = false;\n  };\n\n  const cancelZoomResampleDebounce = (): void => {\n    if (zoomResampleDebounceTimer !== null) {\n      clearTimeout(zoomResampleDebounceTimer);\n      zoomResampleDebounceTimer = null;\n    }\n  };\n\n  const flushPendingAppends = (): boolean => {\n    if (pendingAppendByIndex.size === 0) return false;\n\n    appendedGpuThisFrame.clear();\n\n    const zoomRangeBefore = zoomState?.getRange() ?? null;\n    const isFullSpanZoomBefore = isFullSpanZoomRange(zoomRangeBefore);\n    const canAutoScroll =\n      currentOptions.autoScroll === true &&\n      zoomState != null &&\n      currentOptions.xAxis.min == null &&\n      currentOptions.xAxis.max == null;\n\n    // Capture the pre-append visible domain so we can preserve it for “panned away” behavior.\n    const prevBaseXDomain = computeBaseXDomain(currentOptions, runtimeRawBoundsByIndex);\n    const prevVisibleXDomain = zoomRangeBefore ? computeVisibleXDomain(prevBaseXDomain, zoomRangeBefore) : null;\n\n    let didAppendAny = false;\n\n    for (const [seriesIndex, batches] of pendingAppendByIndex) {\n      if (batches.length === 0) continue;\n      const s = currentOptions.series[seriesIndex];\n      if (!s || s.type === 'pie') continue;\n      didAppendAny = true;\n\n      if (s.type === 'candlestick') {\n        // Handle candlestick OHLC data.\n        let raw = runtimeRawDataByIndex[seriesIndex] as OHLCDataPoint[] | null;\n        if (!raw) {\n          const seed = (s.rawData ?? s.data) as ReadonlyArray<OHLCDataPoint>;\n          raw = seed.length === 0 ? [] : seed.slice();\n          runtimeRawDataByIndex[seriesIndex] = raw;\n          runtimeRawBoundsByIndex[seriesIndex] = s.rawBounds ?? null;\n        }\n\n        // Process each batch of OHLC data\n        for (const batch of batches) {\n          const ohlcPoints = batch as ReadonlyArray<OHLCDataPoint>;\n          raw.push(...ohlcPoints);\n          runtimeRawBoundsByIndex[seriesIndex] = extendBoundsWithOHLCDataPoints(\n            runtimeRawBoundsByIndex[seriesIndex],\n            ohlcPoints\n          );\n        }\n      } else {\n        // Handle other cartesian series (line, area, bar, scatter).\n        let raw = runtimeRawDataByIndex[seriesIndex] as MutableXYColumns | null;\n        if (!raw) {\n          const seed = (s.rawData ?? s.data) as CartesianSeriesData;\n          raw = cartesianDataToMutableColumns(seed);\n          runtimeRawDataByIndex[seriesIndex] = raw;\n          runtimeRawBoundsByIndex[seriesIndex] = s.rawBounds ?? computeRawBoundsFromCartesianData(seed);\n        }\n\n        // Optional fast-path: if the GPU buffer currently represents the full, unsampled line series,\n        // we can append just the new points to the existing GPU buffer (no full re-upload).\n        const canUseFastPath =\n          s.type === 'line' && s.sampling === 'none' && isFullSpanZoomBefore && gpuSeriesKindByIndex[seriesIndex] === 'fullRawLine';\n\n        // Process each batch of cartesian data\n        for (const batch of batches) {\n          const cartesianData = batch as CartesianSeriesData;\n\n          if (canUseFastPath) {\n            try {\n              // Pass CartesianSeriesData directly to DataStore (avoids per-point allocations for typed arrays)\n              dataStore.appendSeries(seriesIndex, cartesianData);\n              appendedGpuThisFrame.add(seriesIndex);\n            } catch {\n              // If the DataStore has not been initialized for this index (or any other error occurs),\n              // fall back to the normal full upload path later in render().\n            }\n          } else if (s.type === 'line' && s.sampling !== 'none' && !warnedSamplingDefeatsFastPath.has(seriesIndex)) {\n            // Warn users that sampling defeats the incremental append optimization\n            warnedSamplingDefeatsFastPath.add(seriesIndex);\n            console.warn(\n              `[ChartGPU] appendData() on series ${seriesIndex} with sampling='${s.sampling}' causes full buffer re-upload every frame. ` +\n                `For optimal streaming performance, use sampling='none'. ` +\n                `See docs/internal/INCREMENTAL_APPEND_OPTIMIZATION.md for details.`\n            );\n          }\n\n          // Update runtime columnar storage (needed for resampling/slicing in non-fast-path cases).\n          // Append each batch into MutableXYColumns using getPointCount/getX/getY/getSize accessors.\n          const n = getPointCount(cartesianData);\n          const rawLenBefore = raw.x.length;\n          for (let i = 0; i < n; i++) {\n            raw.x.push(getX(cartesianData, i));\n            raw.y.push(getY(cartesianData, i));\n            \n            const sizeValue = getSize(cartesianData, i);\n            // Maintain size alignment: if owned.size exists or new batch has any size, keep aligned\n            if (sizeValue !== undefined) {\n              if (!raw.size) {\n                // Backfill undefined for prior points that didn't have size values\n                raw.size = new Array(rawLenBefore + i);\n              }\n              raw.size.push(sizeValue);\n            } else if (raw.size) {\n              raw.size.push(undefined);\n            }\n          }\n\n          // Update bounds using efficient CartesianSeriesData accessor\n          runtimeRawBoundsByIndex[seriesIndex] = extendBoundsWithCartesianData(\n            runtimeRawBoundsByIndex[seriesIndex],\n            cartesianData\n          );\n        }\n      }\n\n      // Invalidate cache for this series since data has changed\n      lastSampledData[seriesIndex] = null;\n    }\n\n    pendingAppendByIndex.clear();\n    if (!didAppendAny) return false;\n\n    // Dataset-aware zoom span constraints depend on raw point density.\n    // When streaming appends add points, recompute and apply constraints so wheel+slider remain consistent.\n    // Arm auto-scroll source kind before setSpanConstraints (clamping may emit onChange).\n    if (canAutoScroll) pendingZoomSourceKind = 'auto-scroll';\n    if (zoomState) {\n      const constraints = computeEffectiveZoomSpanConstraints();\n      const withConstraints = zoomState as unknown as {\n        setSpanConstraints?: (minSpan: number, maxSpan: number) => void;\n      };\n      withConstraints.setSpanConstraints?.(constraints.minSpan, constraints.maxSpan);\n    }\n\n    // Auto-scroll is applied only on append (not on `setOptions`).\n    // Re-arm in case setSpanConstraints already triggered onChange and cleared.\n    if (canAutoScroll && zoomRangeBefore && prevVisibleXDomain) {\n      pendingZoomSourceKind = 'auto-scroll';\n      const r = zoomRangeBefore;\n      if (r.end >= 99.5) {\n        const span = r.end - r.start;\n        const anchored = zoomState! as unknown as {\n          setRangeAnchored?: (start: number, end: number, anchor: 'start' | 'end' | 'center') => void;\n        };\n        // Keep end pinned when constraints clamp the span.\n        if (anchored.setRangeAnchored) {\n          anchored.setRangeAnchored(100 - span, 100, 'end');\n        } else {\n          zoomState!.setRange(100 - span, 100);\n        }\n      } else {\n        const nextBaseXDomain = computeBaseXDomain(currentOptions, runtimeRawBoundsByIndex);\n        const span = nextBaseXDomain.max - nextBaseXDomain.min;\n        if (Number.isFinite(span) && span > 0) {\n          const nextStartRaw = ((prevVisibleXDomain.min - nextBaseXDomain.min) / span) * 100;\n          const nextEndRaw = ((prevVisibleXDomain.max - nextBaseXDomain.min) / span) * 100;\n          // Clamp defensively; ZoomState also clamps/orders internally.\n          const nextStart = Math.max(0, Math.min(100, nextStartRaw));\n          const nextEnd = Math.max(0, Math.min(100, nextEndRaw));\n          zoomState!.setRange(nextStart, nextEnd);\n        }\n      }\n    }\n    // Fallback clear if no onChange fired (e.g. range unchanged).\n    if (canAutoScroll) pendingZoomSourceKind = undefined;\n\n    recomputeRuntimeBaseSeries();\n\n    // If zoom is disabled or full-span, `renderSeries` is just the baseline.\n    // (Zoom-visible resampling is handled by the unified flush when needed.)\n    const zoomRangeAfter = zoomState?.getRange() ?? null;\n    if (zoomRangeAfter == null || isFullSpanZoomRange(zoomRangeAfter)) {\n      renderSeries = runtimeBaseSeries;\n      // Recompute visible y-bounds from the baseline series\n      recomputeCachedVisibleYBoundsIfNeeded();\n    }\n\n    return true;\n  };\n\n  const executeFlush = (options?: { readonly requestRenderAfter?: boolean }): void => {\n    if (disposed) return;\n\n    const requestRenderAfter = options?.requestRenderAfter ?? true;\n\n    const didAppend = flushPendingAppends();\n\n    const zoomRange = zoomState?.getRange() ?? null;\n    const zoomIsFullSpan = isFullSpanZoomRange(zoomRange);\n    const zoomActiveNotFullSpan = zoomRange != null && !zoomIsFullSpan;\n\n    let didResample = false;\n\n    // Zoom changes (debounced): apply on flush.\n    if (zoomResampleDue) {\n      zoomResampleDue = false;\n      cancelZoomResampleDebounce();\n\n      if (!zoomRange || zoomIsFullSpan) {\n        renderSeries = runtimeBaseSeries;\n        // Recompute visible y-bounds from the baseline series\n        recomputeCachedVisibleYBoundsIfNeeded();\n      } else {\n        recomputeRenderSeries();\n      }\n      didResample = true;\n    } else if (didAppend && zoomActiveNotFullSpan) {\n      // Appends during an active zoom window require resampling the visible range.\n      // (Avoid doing this work when zoom is full-span or disabled.)\n      zoomResampleDue = false;\n      cancelZoomResampleDebounce();\n      recomputeRenderSeries();\n      didResample = true;\n    }\n\n    if ((didAppend || didResample) && requestRenderAfter) {\n      requestRender();\n    }\n  };\n\n  const scheduleFlush = (options?: { readonly immediate?: boolean }): void => {\n    if (disposed) return;\n    if (flushScheduled && !options?.immediate) return;\n\n    // Cancel any previous schedule so we coalesce to exactly one pending flush.\n    if (flushRafId !== null) {\n      cancelAnimationFrame(flushRafId);\n      flushRafId = null;\n    }\n    if (flushTimeoutId !== null) {\n      clearTimeout(flushTimeoutId);\n      flushTimeoutId = null;\n    }\n\n    flushScheduled = true;\n\n    flushRafId = requestAnimationFrame(() => {\n      flushRafId = null;\n      if (disposed) {\n        cancelScheduledFlush();\n        return;\n      }\n      // rAF fired first: cancel the fallback timeout.\n      if (flushTimeoutId !== null) {\n        clearTimeout(flushTimeoutId);\n        flushTimeoutId = null;\n      }\n      flushScheduled = false;\n      executeFlush();\n    });\n\n    // Fallback: ensure we flush even if rAF is delayed (high-frequency streams > 60Hz).\n    flushTimeoutId = (typeof self !== 'undefined' ? self : window).setTimeout(() => {\n      if (disposed) {\n        cancelScheduledFlush();\n        return;\n      }\n      if (!flushScheduled) return;\n\n      if (flushRafId !== null) {\n        cancelAnimationFrame(flushRafId);\n        flushRafId = null;\n      }\n      flushScheduled = false;\n      flushTimeoutId = null;\n      executeFlush();\n    }, 16);\n  };\n\n  const scheduleZoomResample = (): void => {\n    if (disposed) return;\n\n    cancelZoomResampleDebounce();\n    zoomResampleDue = false;\n\n    zoomResampleDebounceTimer = (typeof self !== 'undefined' ? self : window).setTimeout(() => {\n      zoomResampleDebounceTimer = null;\n      if (disposed) return;\n      zoomResampleDue = true;\n      scheduleFlush();\n    }, 100);\n  };\n\n  const getPlotSizeCssPx = (\n    canvas: HTMLCanvasElement,\n    gridArea: GridArea\n  ): { readonly plotWidthCss: number; readonly plotHeightCss: number } | null => {\n    let canvasWidthCss: number;\n    let canvasHeightCss: number;\n\n\n    // HTMLCanvasElement: use getBoundingClientRect() for actual CSS dimensions\n    const rect = canvas.getBoundingClientRect();\n    if (!(rect.width > 0) || !(rect.height > 0)) return null;\n    canvasWidthCss = rect.width;\n    canvasHeightCss = rect.height;\n\n    const plotWidthCss = canvasWidthCss - gridArea.left - gridArea.right;\n    const plotHeightCss = canvasHeightCss - gridArea.top - gridArea.bottom;\n    if (!(plotWidthCss > 0) || !(plotHeightCss > 0)) return null;\n\n    return { plotWidthCss, plotHeightCss };\n  };\n\n  const computeInteractionScalesGridCssPx = (\n    gridArea: GridArea,\n    domains: { readonly xDomain: { readonly min: number; readonly max: number }; readonly yDomain: { readonly min: number; readonly max: number } }\n  ):\n    | {\n        readonly xScale: LinearScale;\n        readonly yScale: LinearScale;\n        readonly plotWidthCss: number;\n        readonly plotHeightCss: number;\n      }\n    | null => {\n    const canvas = gpuContext.canvas;\n    if (!canvas) return null;\n\n    const plotSize = getPlotSizeCssPx(canvas, gridArea);\n    if (!plotSize) return null;\n\n    // IMPORTANT: grid-local CSS px ranges (0..plotWidth/Height), for interaction hit-testing.\n    const xScale = createLinearScale().domain(domains.xDomain.min, domains.xDomain.max).range(0, plotSize.plotWidthCss);\n    const yScale = createLinearScale().domain(domains.yDomain.min, domains.yDomain.max).range(plotSize.plotHeightCss, 0);\n\n    const result = { xScale, yScale, plotWidthCss: plotSize.plotWidthCss, plotHeightCss: plotSize.plotHeightCss };\n\n    return result;\n  };\n\n  const buildTooltipParams = (seriesIndex: number, dataIndex: number, point: DataPoint): TooltipParams => {\n    const s = currentOptions.series[seriesIndex];\n    const { x, y } = getPointXY(point);\n    return {\n      seriesName: s?.name ?? '',\n      seriesIndex,\n      dataIndex,\n      value: [x, y],\n      color: s?.color ?? '#888',\n    };\n  };\n\n  const buildCandlestickTooltipParams = (\n    seriesIndex: number,\n    dataIndex: number,\n    point: OHLCDataPoint\n  ): TooltipParams => {\n    const s = currentOptions.series[seriesIndex];\n    if (isTupleOHLCDataPoint(point)) {\n      return {\n        seriesName: s?.name ?? '',\n        seriesIndex,\n        dataIndex,\n        value: [point[0], point[1], point[2], point[3], point[4]] as const,\n        color: s?.color ?? '#888',\n      };\n    } else {\n      return {\n        seriesName: s?.name ?? '',\n        seriesIndex,\n        dataIndex,\n        value: [point.timestamp, point.open, point.close, point.low, point.high] as const,\n        color: s?.color ?? '#888',\n      };\n    }\n  };\n\n  // Helper: Find pie slice at pointer position (extracted to avoid duplication)\n  const findPieSliceAtPointer = (\n    series: ResolvedChartGPUOptions['series'],\n    gridX: number,\n    gridY: number,\n    plotWidthCss: number,\n    plotHeightCss: number\n  ): ReturnType<typeof findPieSlice> | null => {\n    const maxRadiusCss = 0.5 * Math.min(plotWidthCss, plotHeightCss);\n    if (!(maxRadiusCss > 0)) return null;\n\n    // Iterate from last to first for correct z-ordering (last series drawn on top)\n    for (let i = series.length - 1; i >= 0; i--) {\n      const s = series[i];\n      if (s.type !== 'pie') continue;\n      // Skip invisible series (pie hit-testing should respect visibility)\n      if (s.visible === false) continue;\n      const pieSeries = s as ResolvedPieSeriesConfig;\n      const center = resolvePieCenterPlotCss(pieSeries.center, plotWidthCss, plotHeightCss);\n      const radii = resolvePieRadiiCss(pieSeries.radius, maxRadiusCss);\n      const m = findPieSlice(gridX, gridY, { seriesIndex: i, series: pieSeries }, center, radii);\n      if (m) return m;\n    }\n    return null;\n  };\n\n  // Helper: Find candlestick match at pointer position (hoisted to avoid closure allocation)\n  const findCandlestickAtPointer = (\n    series: ResolvedChartGPUOptions['series'],\n    gridX: number,\n    gridY: number,\n    interactionScales: NonNullable<ReturnType<typeof computeInteractionScalesGridCssPx>>\n  ): { params: TooltipParams; match: { point: OHLCDataPoint }; seriesIndex: number } | null => {\n    // Iterate from last to first for correct z-ordering (last series drawn on top)\n    for (let i = series.length - 1; i >= 0; i--) {\n      const s = series[i];\n      if (s.type !== 'candlestick') continue;\n      // Skip invisible series (candlestick hit-testing should respect visibility)\n      if (s.visible === false) continue;\n\n      const cs = s as ResolvedCandlestickSeriesConfig;\n      const barWidthClip = computeCandlestickBodyWidthRange(\n        cs,\n        cs.data,\n        interactionScales.xScale,\n        interactionScales.plotWidthCss\n      );\n\n      const m = findCandlestick(\n        [cs],\n        gridX,\n        gridY,\n        interactionScales.xScale,\n        interactionScales.yScale,\n        barWidthClip\n      );\n      if (!m) continue;\n\n      const params = buildCandlestickTooltipParams(i, m.dataIndex, m.point);\n      return { params, match: { point: m.point }, seriesIndex: i };\n    }\n    return null;\n  };\n\n  const onMouseMove = (payload: ChartGPUEventPayload): void => {\n    pointerState = {\n      source: 'mouse',\n      x: payload.x,\n      y: payload.y,\n      gridX: payload.gridX,\n      gridY: payload.gridY,\n      isInGrid: payload.isInGrid,\n      hasPointer: true,\n    };\n\n    // If we're over the plot and we have recent interaction scales, update interaction-x in domain units.\n    // (Best-effort; render() refreshes scales and overlays.)\n    if (payload.isInGrid && lastInteractionScales) {\n      const xDomain = lastInteractionScales.xScale.invert(payload.gridX);\n      setInteractionXInternal(Number.isFinite(xDomain) ? xDomain : null, 'mouse');\n    } else if (!payload.isInGrid) {\n      // Clear interaction-x when leaving the plot area (keeps synced charts from “sticking”).\n      setInteractionXInternal(null, 'mouse');\n    }\n\n    crosshairRenderer.setVisible(payload.isInGrid);\n    requestRender();\n  };\n\n  const onMouseLeave = (_payload: ChartGPUEventPayload): void => {\n    // Only clear interaction overlays for real pointer interaction.\n    // If we're being driven by a sync-x, leaving the canvas shouldn't hide the overlays.\n    if (pointerState.source !== 'mouse') return;\n\n    pointerState = { ...pointerState, isInGrid: false, hasPointer: false };\n    crosshairRenderer.setVisible(false);\n    hideTooltip();\n    setInteractionXInternal(null, 'mouse');\n    requestRender();\n  };\n\n  // Register event listeners only if event manager is available (HTMLCanvasElement).\n  if (eventManager) {\n    eventManager.on('mousemove', onMouseMove);\n    eventManager.on('mouseleave', onMouseLeave);\n  }\n\n  // Optional internal “inside zoom” (wheel zoom + drag pan).\n  let zoomState: ZoomState | null = null;\n  let insideZoom: ReturnType<typeof createInsideZoom> | null = null;\n  let unsubscribeZoom: (() => void) | null = null;\n  let lastOptionsZoomRange: Readonly<{ start: number; end: number }> | null = null;\n  let pendingZoomSourceKind: ZoomChangeSourceKind | undefined = undefined;\n  const zoomRangeListeners = new Set<(range: Readonly<{ start: number; end: number }>, sourceKind?: ZoomChangeSourceKind) => void>();\n\n  const emitZoomRange = (range: Readonly<{ start: number; end: number }>, sourceKind?: ZoomChangeSourceKind): void => {\n    const snapshot = Array.from(zoomRangeListeners);\n    for (const cb of snapshot) cb(range, sourceKind);\n  };\n\n  const getZoomOptionsConfig = (\n    opts: ResolvedChartGPUOptions\n  ): { readonly start: number; readonly end: number; readonly hasInside: boolean } | null => {\n    // Zoom is enabled when *either* inside or slider exists. A single shared percent-space\n    // window is used for both.\n    const insideCfg = opts.dataZoom?.find((z) => z?.type === 'inside');\n    const sliderCfg = opts.dataZoom?.find((z) => z?.type === 'slider');\n    const cfg = insideCfg ?? sliderCfg;\n    if (!cfg) return null;\n    const start = Number.isFinite(cfg.start) ? cfg.start! : 0;\n    const end = Number.isFinite(cfg.end) ? cfg.end! : 100;\n    return { start, end, hasInside: !!insideCfg };\n  };\n\n  const clampPercent = (v: number): number => Math.min(100, Math.max(0, v));\n\n  const getZoomSpanConstraintsFromOptions = (\n    opts: ResolvedChartGPUOptions\n  ): { readonly minSpan?: number; readonly maxSpan?: number } => {\n    let minSpan: number | null = null;\n    let maxSpan: number | null = null;\n\n    const list = opts.dataZoom ?? [];\n    for (const z of list) {\n      if (!z) continue;\n      if (z.type !== 'inside' && z.type !== 'slider') continue;\n\n      if (Number.isFinite(z.minSpan as number)) {\n        const v = clampPercent(z.minSpan as number);\n        minSpan = minSpan == null ? v : Math.max(minSpan, v);\n      }\n      if (Number.isFinite(z.maxSpan as number)) {\n        const v = clampPercent(z.maxSpan as number);\n        maxSpan = maxSpan == null ? v : Math.min(maxSpan, v);\n      }\n    }\n\n    return { minSpan: minSpan ?? undefined, maxSpan: maxSpan ?? undefined };\n  };\n\n  const computeDatasetAwareDefaultMinSpan = (): number | null => {\n    // Dataset-aware defaults only apply to numeric/time x domains (category is discrete UI-driven).\n    if (currentOptions.xAxis.type === 'category') return null;\n\n    let maxPoints = 0;\n    for (let i = 0; i < currentOptions.series.length; i++) {\n      const s = currentOptions.series[i]!;\n      if (s.type === 'pie') continue;\n      if (s.type === 'candlestick') {\n        const raw =\n          (runtimeRawDataByIndex[i] as ReadonlyArray<OHLCDataPoint> | null) ??\n          ((s.rawData ?? s.data) as ReadonlyArray<OHLCDataPoint>);\n        maxPoints = Math.max(maxPoints, raw.length);\n        continue;\n      }\n\n      // Cartesian series: runtime store is MutableXYColumns\n      const rawCartesian =\n        (runtimeRawDataByIndex[i] as MutableXYColumns | null) ??\n        null;\n      const pointCount = rawCartesian ? rawCartesian.x.length : getPointCount((s.rawData ?? s.data) as CartesianSeriesData);\n      maxPoints = Math.max(maxPoints, pointCount);\n    }\n\n    if (maxPoints < 2) return null;\n    const v = 100 / (maxPoints - 1);\n    return Number.isFinite(v) ? clampPercent(v) : null;\n  };\n\n  const computeEffectiveZoomSpanConstraints = (): { readonly minSpan: number; readonly maxSpan: number } => {\n    const fromOptions = getZoomSpanConstraintsFromOptions(currentOptions);\n    const datasetMin = computeDatasetAwareDefaultMinSpan();\n\n    // Preserve legacy behavior when no constraints (and no dataset signal) are available.\n    // The coordinator will typically override this with datasetMin when the data supports it.\n    const minSpan = Number.isFinite(fromOptions.minSpan as number)\n      ? clampPercent(fromOptions.minSpan as number)\n      : datasetMin ?? 0.5;\n    const maxSpan = Number.isFinite(fromOptions.maxSpan as number)\n      ? clampPercent(fromOptions.maxSpan as number)\n      : 100;\n\n    return { minSpan, maxSpan };\n  };\n\n  const updateZoom = (): void => {\n    const cfg = getZoomOptionsConfig(currentOptions);\n\n    if (!cfg) {\n      insideZoom?.dispose();\n      insideZoom = null;\n      unsubscribeZoom?.();\n      unsubscribeZoom = null;\n      zoomState = null;\n      lastOptionsZoomRange = null;\n      return;\n    }\n\n    if (!zoomState) {\n      const constraints = computeEffectiveZoomSpanConstraints();\n      zoomState = createZoomState(cfg.start, cfg.end, constraints);\n      lastOptionsZoomRange = { start: cfg.start, end: cfg.end };\n      unsubscribeZoom = zoomState.onChange((range) => {\n        // Coalesce slicing (and visible-bounds recompute) to at most once per rendered frame.\n        sliceRenderSeriesDue = true;\n        // Immediate render for UI feedback (axes/crosshair/slider).\n        requestRender();\n        // Debounce resampling; the unified flush will do the work.\n        scheduleZoomResample();\n        // Capture source kind for this change; clear after emit so listeners see it.\n        const sourceKind = pendingZoomSourceKind;\n        emitZoomRange({ start: range.start, end: range.end }, sourceKind);\n        pendingZoomSourceKind = undefined;\n      });\n    } else {\n      const constraints = computeEffectiveZoomSpanConstraints();\n      const withConstraints = zoomState as unknown as {\n        setSpanConstraints?: (minSpan: number, maxSpan: number) => void;\n      };\n      // If setSpanConstraints clamps the range (constraint violation), this is an internal adjustment\n      // (not 'api' since this is driven by setOptions, not setZoomRange; not 'auto-scroll' since no append).\n      // Leave sourceKind undefined (uncategorized).\n      withConstraints.setSpanConstraints?.(constraints.minSpan, constraints.maxSpan);\n\n      if (\n        lastOptionsZoomRange == null ||\n        lastOptionsZoomRange.start !== cfg.start ||\n        lastOptionsZoomRange.end !== cfg.end\n      ) {\n        // Only apply option-provided start/end when:\n        // - zoom is first created, or\n        // - start/end actually changed in options\n        zoomState.setRange(cfg.start, cfg.end);\n        lastOptionsZoomRange = { start: cfg.start, end: cfg.end };\n      }\n    }\n\n    // Only enable inside zoom handler when `{ type: 'inside' }` exists.\n    // Requires event manager (HTMLCanvasElement only).\n    if (cfg.hasInside && eventManager) {\n      if (!insideZoom) {\n        insideZoom = createInsideZoom(eventManager, zoomState);\n        insideZoom.enable();\n      }\n    } else {\n      insideZoom?.dispose();\n      insideZoom = null;\n    }\n  };\n\n  const initRuntimeSeriesFromOptions = (): void => {\n    const count = currentOptions.series.length;\n    runtimeRawDataByIndex = new Array(count).fill(null);\n    runtimeRawBoundsByIndex = new Array(count).fill(null);\n    pendingAppendByIndex.clear();\n\n    for (let i = 0; i < count; i++) {\n      const s = currentOptions.series[i]!;\n      if (s.type === 'pie') continue;\n\n      if (s.type === 'candlestick') {\n        // Store candlestick raw OHLC data (not for streaming append, but for zoom-aware resampling).\n        const rawOHLC = (s.rawData ?? s.data) as ReadonlyArray<OHLCDataPoint>;\n        const owned = rawOHLC.length === 0 ? [] : rawOHLC.slice();\n        runtimeRawDataByIndex[i] = owned;\n        runtimeRawBoundsByIndex[i] = s.rawBounds ?? null;\n        continue;\n      }\n\n      const raw = (s.rawData ?? s.data) as CartesianSeriesData;\n      // Coordinator-owned: convert to mutable columnar format (streaming appends mutate this).\n      const owned = cartesianDataToMutableColumns(raw);\n      runtimeRawDataByIndex[i] = owned;\n      runtimeRawBoundsByIndex[i] = s.rawBounds ?? computeRawBoundsFromCartesianData(raw);\n    }\n  };\n\n  const recomputeRuntimeBaseSeries = (): void => {\n    const next: ResolvedChartGPUOptions['series'][number][] = new Array(currentOptions.series.length);\n    for (let i = 0; i < currentOptions.series.length; i++) {\n      const s = currentOptions.series[i]!;\n      if (s.type === 'pie') {\n        next[i] = s;\n        continue;\n      }\n\n      if (s.type === 'candlestick') {\n        const rawOHLC =\n          (runtimeRawDataByIndex[i] as ReadonlyArray<OHLCDataPoint> | null) ??\n          ((s.rawData ?? s.data) as ReadonlyArray<OHLCDataPoint>);\n        const bounds = runtimeRawBoundsByIndex[i] ?? s.rawBounds ?? undefined;\n        const baselineSampled = s.sampling === 'ohlc' && rawOHLC.length > s.samplingThreshold\n          ? ohlcSample(rawOHLC, s.samplingThreshold)\n          : rawOHLC;\n        next[i] = { ...s, rawData: rawOHLC, rawBounds: bounds, data: baselineSampled };\n        continue;\n      }\n\n      // Cartesian series: runtime store is MutableXYColumns (compatible with CartesianSeriesData at runtime)\n      const rawCartesian: CartesianSeriesData =\n        ((runtimeRawDataByIndex[i] as MutableXYColumns | null) as CartesianSeriesData) ?? \n        ((s.rawData ?? s.data) as CartesianSeriesData);\n      const bounds = runtimeRawBoundsByIndex[i] ?? s.rawBounds ?? undefined;\n      const baselineSampled = sampleSeriesDataPoints(rawCartesian, s.sampling, s.samplingThreshold);\n      next[i] = { ...s, rawData: rawCartesian, rawBounds: bounds, data: baselineSampled };\n    }\n    runtimeBaseSeries = next;\n  };\n\n  function sliceRenderSeriesToVisibleRange(): void {\n    const zoomRange = zoomState?.getRange() ?? null;\n    const baseXDomain = computeBaseXDomain(currentOptions, runtimeRawBoundsByIndex);\n    const visibleX = computeVisibleXDomain(baseXDomain, zoomRange);\n\n    // Fast path: no zoom or full span - use baseline directly\n    const isFullSpan =\n      zoomRange == null ||\n      (Number.isFinite(zoomRange.start) &&\n        Number.isFinite(zoomRange.end) &&\n        zoomRange.start <= 0 &&\n        zoomRange.end >= 100);\n    \n    if (isFullSpan) {\n      renderSeries = runtimeBaseSeries;\n      // Recompute visible y-bounds from the full baseline series\n      recomputeCachedVisibleYBoundsIfNeeded();\n      return;\n    }\n\n    const next: ResolvedChartGPUOptions['series'][number][] = new Array(runtimeBaseSeries.length);\n\n    for (let i = 0; i < runtimeBaseSeries.length; i++) {\n      const baseline = runtimeBaseSeries[i]!;\n      \n      // Pie charts don't need slicing\n      if (baseline.type === 'pie') {\n        next[i] = baseline;\n        continue;\n      }\n\n      const cache = lastSampledData[i];\n      \n      // Strategy 1: Use cache if it covers visible range\n      if (cache && \n          visibleX.min >= cache.cachedRange.min && \n          visibleX.max <= cache.cachedRange.max) {\n        \n        if (baseline.type === 'candlestick') {\n          next[i] = {\n            ...baseline,\n            data: sliceVisibleRangeByOHLC(cache.data as ReadonlyArray<OHLCDataPoint>, visibleX.min, visibleX.max)\n          };\n        } else {\n          next[i] = {\n            ...baseline,\n            data: sliceVisibleRangeByX(cache.data as CartesianSeriesData, visibleX.min, visibleX.max)\n          };\n        }\n        continue;\n      }\n      \n      // Strategy 2: Fallback to baseline sampled data\n      if (baseline.type === 'candlestick') {\n        next[i] = {\n          ...baseline,\n          data: sliceVisibleRangeByOHLC(baseline.data as ReadonlyArray<OHLCDataPoint>, visibleX.min, visibleX.max)\n        };\n      } else {\n        next[i] = {\n          ...baseline,\n          data: sliceVisibleRangeByX(baseline.data as CartesianSeriesData, visibleX.min, visibleX.max)\n        };\n      }\n    }\n\n    renderSeries = next;\n    // Recompute visible y-bounds from the sliced renderSeries\n    recomputeCachedVisibleYBoundsIfNeeded();\n  }\n\n  function recomputeRenderSeries(): void {\n    const zoomRange = zoomState?.getRange() ?? null;\n    const baseXDomain = computeBaseXDomain(currentOptions, runtimeRawBoundsByIndex);\n    const visibleX = computeVisibleXDomain(baseXDomain, zoomRange);\n\n    // Add buffer zone (±10% beyond visible range) for caching\n    const bufferFactor = 0.1;\n    const visibleSpan = visibleX.max - visibleX.min;\n    const bufferSize = visibleSpan * bufferFactor;\n    const bufferedMin = visibleX.min - bufferSize;\n    const bufferedMax = visibleX.max + bufferSize;\n\n    // Sampling scale behavior:\n    // - Use `samplingThreshold` as baseline at full span.\n    // - As zoom span shrinks, raise the threshold so fewer points are dropped (more detail).\n    // - Clamp to avoid huge allocations / pathological thresholds.\n    const MIN_TARGET_POINTS = 2;\n    const MAX_TARGET_POINTS_ABS = 200_000;\n    const MAX_TARGET_MULTIPLIER = 32;\n    const spanFracSafe = Math.max(1e-3, Math.min(1, visibleX.spanFraction));\n\n    const next: ResolvedChartGPUOptions['series'][number][] = new Array(runtimeBaseSeries.length);\n\n    for (let i = 0; i < runtimeBaseSeries.length; i++) {\n      const s = runtimeBaseSeries[i]!;\n\n      if (s.type === 'pie') {\n        next[i] = s;\n        continue;\n      }\n\n      // Fast path: no zoom window / full span. Use baseline resolved `data` (already sampled by resolver).\n      const isFullSpan =\n        zoomRange == null ||\n        (Number.isFinite(zoomRange.start) &&\n          Number.isFinite(zoomRange.end) &&\n          zoomRange.start <= 0 &&\n          zoomRange.end >= 100);\n      if (isFullSpan) {\n        next[i] = s;\n        continue;\n      }\n\n      // Candlestick series: OHLC-specific slicing + sampling.\n      if (s.type === 'candlestick') {\n        const rawOHLC =\n          (runtimeRawDataByIndex[i] as ReadonlyArray<OHLCDataPoint> | null) ??\n          ((s.rawData ?? s.data) as ReadonlyArray<OHLCDataPoint>);\n        // Slice to buffered range for sampling\n        const bufferedOHLC = sliceVisibleRangeByOHLC(rawOHLC, bufferedMin, bufferedMax);\n\n        const sampling = s.sampling;\n        const baseThreshold = s.samplingThreshold;\n\n        const baseT = Number.isFinite(baseThreshold) ? Math.max(1, baseThreshold | 0) : 1;\n        const maxTarget = Math.min(MAX_TARGET_POINTS_ABS, Math.max(MIN_TARGET_POINTS, baseT * MAX_TARGET_MULTIPLIER));\n        const target = clampInt(Math.round(baseT / spanFracSafe), MIN_TARGET_POINTS, maxTarget);\n\n        const sampled = sampling === 'ohlc' && bufferedOHLC.length > target\n          ? ohlcSample(bufferedOHLC, target)\n          : bufferedOHLC;\n\n        // Store sampled data in cache with buffered range\n        lastSampledData[i] = {\n          data: sampled,\n          cachedRange: { min: bufferedMin, max: bufferedMax },\n          timestamp: Date.now()\n        };\n\n        // Slice to actual visible range for renderSeries\n        const visibleSampled = sliceVisibleRangeByOHLC(sampled, visibleX.min, visibleX.max);\n        next[i] = { ...s, data: visibleSampled };\n        continue;\n      }\n\n      // Cartesian series (line, area, bar, scatter).\n      // Runtime store is MutableXYColumns (compatible with CartesianSeriesData at runtime)\n      const rawCartesian: CartesianSeriesData =\n        ((runtimeRawDataByIndex[i] as MutableXYColumns | null) as CartesianSeriesData) ?? \n        ((s.rawData ?? s.data) as CartesianSeriesData);\n      // Slice to buffered range for sampling\n      const bufferedRaw = sliceVisibleRangeByX(rawCartesian, bufferedMin, bufferedMax);\n\n      const sampling = s.sampling;\n      const baseThreshold = s.samplingThreshold;\n\n      const baseT = Number.isFinite(baseThreshold) ? Math.max(1, baseThreshold | 0) : 1;\n      const maxTarget = Math.min(MAX_TARGET_POINTS_ABS, Math.max(MIN_TARGET_POINTS, baseT * MAX_TARGET_MULTIPLIER));\n      const target = clampInt(Math.round(baseT / spanFracSafe), MIN_TARGET_POINTS, maxTarget);\n\n      const sampled = sampleSeriesDataPoints(bufferedRaw, sampling, target);\n\n      // Store sampled data in cache with buffered range\n      lastSampledData[i] = {\n        data: sampled,\n        cachedRange: { min: bufferedMin, max: bufferedMax },\n        timestamp: Date.now()\n      };\n\n      // Slice to actual visible range for renderSeries\n      const visibleSampled = sliceVisibleRangeByX(sampled, visibleX.min, visibleX.max);\n      next[i] = { ...s, data: visibleSampled };\n    }\n\n    renderSeries = next;\n    // Recompute visible y-bounds from the updated renderSeries\n    recomputeCachedVisibleYBoundsIfNeeded();\n  }\n\n  initRuntimeSeriesFromOptions();\n  recomputeRuntimeBaseSeries();\n  updateZoom();\n  recomputeRenderSeries();\n  lastSampledData = new Array(currentOptions.series.length).fill(null);\n\n  const rendererPool = createRendererPool({ device, targetFormat, pipelineCache, sampleCount: MAIN_SCENE_MSAA_SAMPLE_COUNT });\n\n  rendererPool.ensureAreaRendererCount(currentOptions.series.length);\n  rendererPool.ensureLineRendererCount(currentOptions.series.length);\n  rendererPool.ensureScatterRendererCount(currentOptions.series.length);\n  rendererPool.ensureScatterDensityRendererCount(currentOptions.series.length);\n  rendererPool.ensurePieRendererCount(currentOptions.series.length);\n  rendererPool.ensureCandlestickRendererCount(currentOptions.series.length);\n\n  const assertNotDisposed = (): void => {\n    if (disposed) throw new Error('RenderCoordinator is disposed.');\n  };\n\n  const cancelUpdateTransition = (): void => {\n    if (updateAnimId) {\n      try {\n        updateAnimController.cancel(updateAnimId);\n      } catch {\n        // best-effort\n      }\n    }\n    updateAnimId = null;\n    updateProgress01 = 1;\n    updateTransition = null;\n    resetUpdateInterpolationCaches();\n  };\n\n  const isDomainEqual = (a: { readonly min: number; readonly max: number }, b: { readonly min: number; readonly max: number }): boolean =>\n    a.min === b.min && a.max === b.max;\n\n  const didSeriesDataLikelyChange = (\n    prev: ResolvedChartGPUOptions['series'],\n    next: ResolvedChartGPUOptions['series']\n  ): boolean => {\n    if (prev.length !== next.length) return true;\n    for (let i = 0; i < prev.length; i++) {\n      const a = prev[i]!;\n      const b = next[i]!;\n      if (a.type !== b.type) return true;\n\n      // Prefer cheap reference checks (good enough for eligibility gating).\n      if (a.type === 'pie') {\n        const aPie = a as ResolvedPieSeriesConfig;\n        const bPie = b as ResolvedPieSeriesConfig;\n        if (aPie.data !== bPie.data) return true;\n        if (aPie.data.length !== bPie.data.length) return true;\n      } else {\n        const aAny = a as unknown as { readonly rawData?: ReadonlyArray<DataPoint>; readonly data: ReadonlyArray<DataPoint> };\n        const bAny = b as unknown as { readonly rawData?: ReadonlyArray<DataPoint>; readonly data: ReadonlyArray<DataPoint> };\n        const aRaw = (aAny.rawData ?? aAny.data) as ReadonlyArray<DataPoint>;\n        const bRaw = (bAny.rawData ?? bAny.data) as ReadonlyArray<DataPoint>;\n        if (aRaw !== bRaw) return true;\n        if (aRaw.length !== bRaw.length) return true;\n      }\n    }\n    return false;\n  };\n\n  const setOptions: RenderCoordinator['setOptions'] = (resolvedOptions) => {\n    assertNotDisposed();\n\n    // Capture \"from\" snapshot BEFORE overwriting coordinator state.\n    const fromZoomRange = zoomState?.getRange() ?? null;\n    const fromSnapshot: UpdateTransitionSnapshot = (() => {\n      // Requirement (mid-flight updates): if a transition is running, rebase from the current blended state.\n      if (updateTransition && updateAnimId) {\n        try {\n          updateAnimController.update(performance.now());\n        } catch {\n          // best-effort\n        }\n        return computeUpdateSnapshotAtProgress(updateTransition, updateProgress01, fromZoomRange);\n      }\n\n      const fromXBase = computeBaseXDomain(currentOptions, runtimeRawBoundsByIndex);\n      const fromXVisible = computeVisibleXDomain(fromXBase, fromZoomRange);\n      const fromYBase = computeBaseYDomain(currentOptions, runtimeRawBoundsByIndex, cachedVisibleYBounds);\n      return {\n        xBaseDomain: fromXBase,\n        xVisibleDomain: { min: fromXVisible.min, max: fromXVisible.max },\n        yBaseDomain: fromYBase,\n        series: renderSeries,\n      };\n    })();\n\n    // Cancel any prior update transition AFTER capturing the rebased \"from\" snapshot.\n    cancelUpdateTransition();\n    const likelyDataChanged = didSeriesDataLikelyChange(currentOptions.series, resolvedOptions.series);\n\n    currentOptions = resolvedOptions;\n\n    if (likelyDataChanged) {\n      // Series data or structure changed — full reset of runtime data state.\n      runtimeBaseSeries = resolvedOptions.series;\n      renderSeries = resolvedOptions.series;\n      gpuSeriesKindByIndex = new Array(resolvedOptions.series.length).fill('unknown');\n      lastSampledData = new Array(resolvedOptions.series.length).fill(null);\n      cancelZoomResampleDebounce();\n      zoomResampleDue = false;\n      cancelScheduledFlush();\n      initRuntimeSeriesFromOptions();\n    }\n\n    // Always refresh: annotations, themes, tooltip config, etc. may have changed.\n    cachedVisibleYBounds = null;\n    legend?.update(resolvedOptions.series, resolvedOptions.theme);\n    recomputeRuntimeBaseSeries();\n    updateZoom();\n    recomputeRenderSeries();\n\n    // Tooltip enablement may change at runtime.\n    if (overlayContainer) {\n      const shouldHaveTooltip = currentOptions.tooltip?.show !== false;\n      if (shouldHaveTooltip && !tooltip) {\n        tooltip = createTooltip(overlayContainer);\n        lastTooltipContent = null;\n        lastTooltipX = null;\n        lastTooltipY = null;\n      }\n      if (!shouldHaveTooltip && tooltip) {\n        hideTooltip();\n      }\n        } else {\n          hideTooltip();\n        }\n\n    const nextCount = resolvedOptions.series.length;\n    rendererPool.ensureAreaRendererCount(nextCount);\n    rendererPool.ensureLineRendererCount(nextCount);\n    rendererPool.ensureScatterRendererCount(nextCount);\n    rendererPool.ensureScatterDensityRendererCount(nextCount);\n    rendererPool.ensurePieRendererCount(nextCount);\n    rendererPool.ensureCandlestickRendererCount(nextCount);\n\n    // When the series count shrinks, explicitly destroy per-index GPU buffers for removed series.\n    // This avoids recreating the entire DataStore and keeps existing buffers for retained indices.\n    if (nextCount < lastSeriesCount) {\n      for (let i = nextCount; i < lastSeriesCount; i++) {\n        dataStore.removeSeries(i);\n      }\n    }\n    lastSeriesCount = nextCount;\n\n    // If animation is explicitly disabled mid-flight, stop the intro without scheduling more frames.\n    if (currentOptions.animation === false && introPhase === 'running') {\n      introAnimController.cancelAll();\n      introAnimId = null;\n      introPhase = 'done';\n      introProgress01 = 1;\n    }\n\n    // If animation is explicitly disabled, ensure any running update transition is stopped.\n    if (currentOptions.animation === false) {\n      cancelUpdateTransition();\n      // Request a render to reflect the option changes immediately\n      requestRender();\n      return;\n    }\n\n    // Capture \"to\" snapshot after recompute.\n    const toZoomRange = zoomState?.getRange() ?? null;\n    const toXBase = computeBaseXDomain(currentOptions, runtimeRawBoundsByIndex);\n    const toXVisible = computeVisibleXDomain(toXBase, toZoomRange);\n    const toYBase = computeBaseYDomain(currentOptions, runtimeRawBoundsByIndex, cachedVisibleYBounds);\n    const toSeriesForTransition = renderSeries;\n\n    const domainChanged = !isDomainEqual(fromSnapshot.xBaseDomain, toXBase) || !isDomainEqual(fromSnapshot.yBaseDomain, toYBase);\n\n    const shouldAnimateUpdate = hasRenderedOnce && (domainChanged || likelyDataChanged);\n    if (!shouldAnimateUpdate) {\n      // Request a render even when not animating (e.g., theme changes, option updates)\n      requestRender();\n      return;\n    }\n\n    const updateCfg = resolveUpdateAnimationConfig(currentOptions.animation);\n    if (!updateCfg) return;\n\n    updateTransition = {\n      from: {\n        xBaseDomain: fromSnapshot.xBaseDomain,\n        xVisibleDomain: fromSnapshot.xVisibleDomain,\n        yBaseDomain: fromSnapshot.yBaseDomain,\n        series: fromSnapshot.series,\n      },\n      to: {\n        xBaseDomain: toXBase,\n        xVisibleDomain: { min: toXVisible.min, max: toXVisible.max },\n        yBaseDomain: toYBase,\n        series: toSeriesForTransition,\n      },\n    };\n    resetUpdateInterpolationCaches();\n\n    const totalMs = updateCfg.delayMs + updateCfg.durationMs;\n    const easingWithDelay: EasingFunction = (t01) => {\n      const t = clamp01(t01);\n      if (!(totalMs > 0)) return 1;\n\n      const elapsedMs = t * totalMs;\n      if (elapsedMs <= updateCfg.delayMs) return 0;\n\n      if (!(updateCfg.durationMs > 0)) return 1;\n      const innerT = (elapsedMs - updateCfg.delayMs) / updateCfg.durationMs;\n      return updateCfg.easing(innerT);\n    };\n\n    updateProgress01 = 0;\n    const id = updateAnimController.animate(\n      0,\n      1,\n      totalMs,\n      easingWithDelay,\n      (value) => {\n        if (disposed || updateAnimId !== id) return;\n        updateProgress01 = clamp01(value);\n        // Render-on-demand: request frames only while the update transition is active.\n        if (updateProgress01 < 1) requestRender();\n      },\n      () => {\n        if (disposed || updateAnimId !== id) return;\n        updateProgress01 = 1;\n        updateTransition = null;\n        updateAnimId = null;\n        resetUpdateInterpolationCaches();\n      }\n    );\n    updateAnimId = id;\n\n    // Request initial render to kick off the animation.\n    // Without this, the animation won't start until something else triggers a render\n    // (e.g., pointer movement, which may not happen if the user is interacting with\n    // UI overlays like the legend).\n    requestRender();\n  };\n\n  const appendData: RenderCoordinator['appendData'] = (seriesIndex, newPoints) => {\n    assertNotDisposed();\n    if (!Number.isFinite(seriesIndex)) return;\n    if (seriesIndex < 0 || seriesIndex >= currentOptions.series.length) return;\n    if (!newPoints) return;\n\n    const s = currentOptions.series[seriesIndex]!;\n    if (s.type === 'pie') {\n      // Pie series are non-cartesian and currently not supported by streaming append.\n      if (!warnedPieAppendSeries.has(seriesIndex)) {\n        warnedPieAppendSeries.add(seriesIndex);\n        console.warn(\n          `RenderCoordinator.appendData(${seriesIndex}, ...): pie series are not supported by streaming append.`\n        );\n      }\n      return;\n    }\n\n    // Check point count based on format (avoid assuming .length exists for all types)\n    const pointCount = s.type === 'candlestick'\n      ? (newPoints as ReadonlyArray<OHLCDataPoint>).length\n      : getPointCount(newPoints as CartesianSeriesData);\n    if (pointCount === 0) return;\n\n    // Store batches in their original format to avoid per-point allocations for typed arrays.\n    const existing = pendingAppendByIndex.get(seriesIndex);\n    if (existing) {\n      existing.push(newPoints);\n    } else {\n      pendingAppendByIndex.set(seriesIndex, [newPoints]);\n    }\n\n    // Coalesce appends + any required resampling + GPU streaming updates into a single flush.\n    scheduleFlush();\n  };\n\n  const render: RenderCoordinator['render'] = () => {\n    assertNotDisposed();\n    if (!gpuContext.canvasContext || !gpuContext.canvas) return;\n\n    // Safety: if a render is triggered for other reasons (e.g. pointer movement) while appends\n    // are queued, flush them now so this frame draws up-to-date data. This avoids doing any work\n    // when there are no appends.\n    if (pendingAppendByIndex.size > 0 || zoomResampleDue) {\n      cancelScheduledFlush();\n      executeFlush({ requestRenderAfter: false });\n    }\n\n    if (sliceRenderSeriesDue) {\n      sliceRenderSeriesDue = false;\n      sliceRenderSeriesToVisibleRange();\n    }\n\n    const hasCartesianSeries = currentOptions.series.some((s) => s.type !== 'pie');\n    const seriesForIntro = renderSeries;\n\n    // Story 5.16: start/update intro animation once we have drawable series marks.\n    if (introPhase !== 'done') {\n      const introCfg = resolveIntroAnimationConfig(currentOptions.animation);\n\n      const hasDrawableSeriesMarks = (() => {\n        for (let i = 0; i < seriesForIntro.length; i++) {\n          const s = seriesForIntro[i]!;\n          switch (s.type) {\n            case 'pie': {\n              // Pie renderer only emits slices with value > 0.\n              if (s.data.some((it) => typeof it?.value === 'number' && Number.isFinite(it.value) && it.value > 0)) {\n                return true;\n              }\n              break;\n            }\n            case 'line':\n            case 'area':\n            case 'bar':\n            case 'scatter': {\n              // Cartesian series: use getPointCount for all CartesianSeriesData formats\n              const dataLength = getPointCount(s.data as CartesianSeriesData);\n              if (dataLength > 0) return true;\n              break;\n            }\n            case 'candlestick': {\n              const dataLength = (s.data as ReadonlyArray<OHLCDataPoint>).length;\n              if (dataLength > 0) return true;\n              break;\n            }\n            default:\n              assertUnreachable(s);\n          }\n        }\n        return false;\n      })();\n\n      if (introPhase === 'pending' && introCfg && hasDrawableSeriesMarks) {\n        const totalMs = introCfg.delayMs + introCfg.durationMs;\n        const easingWithDelay: EasingFunction = (t01) => {\n          const t = clamp01(t01);\n          if (!(totalMs > 0)) return 1;\n\n          const elapsedMs = t * totalMs;\n          if (elapsedMs <= introCfg.delayMs) return 0;\n\n          if (!(introCfg.durationMs > 0)) return 1;\n          const innerT = (elapsedMs - introCfg.delayMs) / introCfg.durationMs;\n          return introCfg.easing(innerT);\n        };\n\n        introProgress01 = 0;\n        introPhase = 'running';\n        introAnimId = introAnimController.animate(\n          0,\n          1,\n          totalMs,\n          easingWithDelay,\n          (value) => {\n            if (disposed || introPhase !== 'running') return;\n            introProgress01 = clamp01(value);\n            // Render-on-demand: request frames only while the intro is active.\n            if (introProgress01 < 1) requestRender();\n          },\n          () => {\n            if (disposed) return;\n            introPhase = 'done';\n            introProgress01 = 1;\n            introAnimId = null;\n          }\n        );\n      }\n\n      // Progress animations based on wall-clock time. This is cheap when no animations are active.\n      introAnimController.update(performance.now());\n    }\n\n    // Story 5.17: progress update animation based on wall-clock time.\n    // (Interpolation is applied below; this tick just advances progress.)\n    if (updateTransition !== null && updateAnimId) {\n      updateAnimController.update(performance.now());\n    }\n\n    const gridArea = computeGridArea(gpuContext, currentOptions);\n    eventManager?.updateGridArea(gridArea);\n    const zoomRange = zoomState?.getRange() ?? null;\n\n    const updateP = updateTransition ? clamp01(updateProgress01) : 1;\n    const baseXDomain = updateTransition\n      ? lerpDomain(updateTransition.from.xBaseDomain, updateTransition.to.xBaseDomain, updateP)\n      : computeBaseXDomain(currentOptions, runtimeRawBoundsByIndex);\n    const yBaseDomain = updateTransition\n      ? lerpDomain(updateTransition.from.yBaseDomain, updateTransition.to.yBaseDomain, updateP)\n      : computeBaseYDomain(currentOptions, runtimeRawBoundsByIndex, cachedVisibleYBounds);\n    const visibleXDomain = computeVisibleXDomain(baseXDomain, zoomRange);\n\n    const plotClipRect = computePlotClipRect(gridArea);\n    const plotScissor = computePlotScissorDevicePx(gridArea);\n\n    const xScale = createLinearScale()\n      .domain(visibleXDomain.min, visibleXDomain.max)\n      .range(plotClipRect.left, plotClipRect.right);\n    const yScale = createLinearScale().domain(yBaseDomain.min, yBaseDomain.max).range(plotClipRect.bottom, plotClipRect.top);\n\n    // PERFORMANCE: Cache canvas CSS dimensions (used for both GPU overlays and label processing)\n    // Annotations (GPU overlays) are specified in data-space and converted to CANVAS-LOCAL CSS pixels.\n    const canvas = gpuContext.canvas;\n    // IMPORTANT: For GPU overlay annotations only, derive CSS size from device pixels to avoid\n    // DOM `clientWidth/clientHeight` mismatch with the WebGPU render target size.\n    const canvasCssForAnnotations = getCanvasCssSizeFromDevicePixels(canvas);\n    const canvasCssWidthForAnnotations = canvasCssForAnnotations.width;\n    const canvasCssHeightForAnnotations = canvasCssForAnnotations.height;\n\n    const plotLeftCss = canvasCssWidthForAnnotations > 0 ? clipXToCanvasCssPx(plotClipRect.left, canvasCssWidthForAnnotations) : 0;\n    const plotRightCss = canvasCssWidthForAnnotations > 0 ? clipXToCanvasCssPx(plotClipRect.right, canvasCssWidthForAnnotations) : 0;\n    const plotTopCss = canvasCssHeightForAnnotations > 0 ? clipYToCanvasCssPx(plotClipRect.top, canvasCssHeightForAnnotations) : 0;\n    const plotBottomCss = canvasCssHeightForAnnotations > 0 ? clipYToCanvasCssPx(plotClipRect.bottom, canvasCssHeightForAnnotations) : 0;\n    const plotWidthCss = Math.max(0, plotRightCss - plotLeftCss);\n    const plotHeightCss = Math.max(0, plotBottomCss - plotTopCss);\n\n    // Process annotations (convert to GPU instances for rendering)\n    const annotations: ReadonlyArray<AnnotationConfig> = hasCartesianSeries ? (currentOptions.annotations ?? []) : [];\n    const annotationResult = processAnnotations({\n      annotations,\n      xScale,\n      yScale,\n      plotBounds: {\n        leftCss: plotLeftCss,\n        rightCss: plotRightCss,\n        topCss: plotTopCss,\n        bottomCss: plotBottomCss,\n        widthCss: plotWidthCss,\n        heightCss: plotHeightCss,\n      },\n      canvasCssWidth: canvasCssWidthForAnnotations,\n      canvasCssHeight: canvasCssHeightForAnnotations,\n      theme: currentOptions.theme,\n    });\n\n    // Extract annotation instances for GPU rendering\n    const combinedReferenceLines: ReadonlyArray<ReferenceLineInstance> =\n      annotationResult.linesBelow.length + annotationResult.linesAbove.length > 0\n        ? [...annotationResult.linesBelow, ...annotationResult.linesAbove]\n        : [];\n    const combinedMarkers: ReadonlyArray<AnnotationMarkerInstance> =\n      annotationResult.markersBelow.length + annotationResult.markersAbove.length > 0\n        ? [...annotationResult.markersBelow, ...annotationResult.markersAbove]\n        : [];\n    const referenceLineBelowCount = annotationResult.linesBelow.length;\n    const referenceLineAboveCount = annotationResult.linesAbove.length;\n    const markerBelowCount = annotationResult.markersBelow.length;\n    const markerAboveCount = annotationResult.markersAbove.length;\n\n    // Story 6: compute an x tick count that prevents label overlap (time axis only).\n    // IMPORTANT: compute in CSS px, since labels are DOM elements in CSS px.\n    // Note: This requires HTMLCanvasElement for accurate CSS pixel measurement.\n    const canvasCssWidth = getCanvasCssWidth(gpuContext.canvas);\n    const visibleXRangeMs = Math.abs(visibleXDomain.max - visibleXDomain.min);\n\n    let xTickCount = DEFAULT_TICK_COUNT;\n    let xTickValues: readonly number[] = [];\n    if (currentOptions.xAxis.type === 'time') {\n      const computed = computeAdaptiveTimeXAxisTicks({\n        axisMin: finiteOrNull(currentOptions.xAxis.min),\n        axisMax: finiteOrNull(currentOptions.xAxis.max),\n        xScale,\n        plotClipLeft: plotClipRect.left,\n        plotClipRight: plotClipRect.right,\n        canvasCssWidth,\n        visibleRangeMs: visibleXRangeMs,\n        measureCtx: tickMeasureCtx,\n        measureCache: tickMeasureCache ?? undefined,\n        fontSize: currentOptions.theme.fontSize,\n        fontFamily: currentOptions.theme.fontFamily || 'sans-serif',\n        tickFormatter: currentOptions.xAxis.tickFormatter,\n      });\n      xTickCount = computed.tickCount;\n      xTickValues = computed.tickValues;\n    } else {\n      // Keep existing behavior for non-time x axes.\n      const domainMin = finiteOrUndefined(currentOptions.xAxis.min) ?? xScale.invert(plotClipRect.left);\n      const domainMax = finiteOrUndefined(currentOptions.xAxis.max) ?? xScale.invert(plotClipRect.right);\n      xTickValues = generateLinearTicks(domainMin, domainMax, xTickCount);\n    }\n\n    const interactionScales = computeInteractionScalesGridCssPx(gridArea, {\n      xDomain: { min: visibleXDomain.min, max: visibleXDomain.max },\n      yDomain: yBaseDomain,\n    });\n    lastInteractionScales = interactionScales;\n\n    // Story 5.17: during update transitions, render animated series snapshots.\n    const seriesForRender =\n      updateTransition && updateP < 1\n        ? interpolateSeriesForUpdate(updateTransition.from.series, updateTransition.to.series, updateP, updateInterpolationCaches)\n        : renderSeries;\n\n    // Keep `interactionX` in sync with real pointer movement (domain units).\n    if (\n      pointerState.source === 'mouse' &&\n      pointerState.hasPointer &&\n      pointerState.isInGrid &&\n      interactionScales\n    ) {\n      const xDomain = interactionScales.xScale.invert(pointerState.gridX);\n      setInteractionXInternal(Number.isFinite(xDomain) ? xDomain : null, 'mouse');\n    }\n\n    // Compute the effective interaction state:\n    // - mouse: use the latest pointer event payload\n    // - sync: derive a synthetic pointer position from `interactionX` (x only; y is arbitrary)\n    let effectivePointer: PointerState = pointerState;\n    if (pointerState.source === 'sync') {\n      if (interactionX === null || !interactionScales) {\n        effectivePointer = { ...pointerState, hasPointer: false, isInGrid: false };\n      } else {\n        const gridX = interactionScales.xScale.scale(interactionX);\n        const gridY = interactionScales.plotHeightCss * 0.5;\n        const isInGrid =\n          Number.isFinite(gridX) &&\n          Number.isFinite(gridY) &&\n          gridX >= 0 &&\n          gridX <= interactionScales.plotWidthCss &&\n          gridY >= 0 &&\n          gridY <= interactionScales.plotHeightCss;\n\n        effectivePointer = {\n          source: 'sync',\n          gridX: Number.isFinite(gridX) ? gridX : 0,\n          gridY: Number.isFinite(gridY) ? gridY : 0,\n          // Crosshair/tooltip expect CANVAS-LOCAL CSS px.\n          x: gridArea.left + (Number.isFinite(gridX) ? gridX : 0),\n          y: gridArea.top + (Number.isFinite(gridY) ? gridY : 0),\n          isInGrid,\n          hasPointer: isInGrid,\n        };\n      }\n    }\n\n    // Prepare overlay renderers (grid, axes, crosshair, highlight)\n    prepareOverlays(\n      { gridRenderer, xAxisRenderer, yAxisRenderer, crosshairRenderer, highlightRenderer },\n      {\n        currentOptions,\n        xScale,\n        yScale,\n        gridArea,\n        xTickCount,\n        hasCartesianSeries,\n        effectivePointer,\n        interactionScales,\n        seriesForRender,\n        withAlpha,\n      }\n    );\n\n    // Tooltip: on hover, find matches and render tooltip near cursor.\n    // Note: Tooltips require HTMLCanvasElement (DOM-specific positioning).\n    if (effectivePointer.hasPointer && effectivePointer.isInGrid && currentOptions.tooltip?.show !== false) {\n      const canvas = gpuContext.canvas;\n\n      if (interactionScales && canvas && isHTMLCanvasElement(canvas)) {\n        const formatter = currentOptions.tooltip?.formatter;\n        const trigger = currentOptions.tooltip?.trigger ?? 'item';\n\n        const containerX = canvas.offsetLeft + effectivePointer.x;\n        const containerY = canvas.offsetTop + effectivePointer.y;\n\n        if (effectivePointer.source === 'sync') {\n          // Sync semantics:\n          // - Tooltip should be driven by x only (no y).\n          // - In 'axis' mode, show one entry per series nearest in x.\n          // - In 'item' mode, pick a deterministic single entry (first matching series).\n          // findPointsAtX handles visibility filtering internally and returns correct series indices\n          const matches = findPointsAtX(seriesForRender, effectivePointer.gridX, interactionScales.xScale);\n          if (matches.length === 0) {\n            hideTooltip();\n          } else if (trigger === 'axis') {\n            const paramsArray = matches.map((m) => buildTooltipParams(m.seriesIndex, m.dataIndex, m.point));\n            const content = formatter\n              ? (formatter as (p: ReadonlyArray<TooltipParams>) => string)(paramsArray)\n              : formatTooltipAxis(paramsArray);\n            if (content && (content !== lastTooltipContent || containerX !== lastTooltipX || containerY !== lastTooltipY)) {\n              lastTooltipContent = content;\n              lastTooltipX = containerX;\n              lastTooltipY = containerY;\n              showTooltipInternal(containerX, containerY, content, paramsArray);\n            } else if (!content) {\n              hideTooltip();\n            }\n          } else {\n            const m0 = matches[0];\n            const params = buildTooltipParams(m0.seriesIndex, m0.dataIndex, m0.point);\n            const content = formatter ? (formatter as (p: TooltipParams) => string)(params) : formatTooltipItem(params);\n            if (content && (content !== lastTooltipContent || containerX !== lastTooltipX || containerY !== lastTooltipY)) {\n              lastTooltipContent = content;\n              lastTooltipX = containerX;\n              lastTooltipY = containerY;\n              showTooltipInternal(containerX, containerY, content, params);\n            } else if (!content) {\n              hideTooltip();\n            }\n          }\n        } else if (trigger === 'axis') {\n          // Story 4.14: pie slice tooltip hit-testing (mouse only).\n          // If the cursor is over a pie slice, prefer showing that slice tooltip.\n          // findPieSliceAtPointer handles visibility filtering internally and returns correct series indices\n          const pieMatch = findPieSliceAtPointer(\n            seriesForRender,\n            effectivePointer.gridX,\n            effectivePointer.gridY,\n            interactionScales.plotWidthCss,\n            interactionScales.plotHeightCss\n          );\n\n          if (pieMatch) {\n            const params: TooltipParams = {\n              seriesName: pieMatch.slice.name,\n              seriesIndex: pieMatch.seriesIndex,\n              dataIndex: pieMatch.dataIndex,\n              value: [0, pieMatch.slice.value],\n              color: pieMatch.slice.color,\n            };\n\n            const content = formatter\n              ? (formatter as (p: ReadonlyArray<TooltipParams>) => string)([params])\n              : formatTooltipItem(params);\n            if (content && (content !== lastTooltipContent || containerX !== lastTooltipX || containerY !== lastTooltipY)) {\n              lastTooltipContent = content;\n              lastTooltipX = containerX;\n              lastTooltipY = containerY;\n              showTooltipInternal(containerX, containerY, content, [params]);\n            } else if (!content) {\n              hideTooltip();\n            }\n          } else {\n            // Candlestick body hit-testing (mouse, axis trigger): include only when inside candle body.\n            // Hit-testing functions handle visibility filtering internally and return correct series indices\n            const candlestickResult = findCandlestickAtPointer(\n              seriesForRender,\n              effectivePointer.gridX,\n              effectivePointer.gridY,\n              interactionScales\n            );\n\n            const matches = findPointsAtX(seriesForRender, effectivePointer.gridX, interactionScales.xScale);\n            if (matches.length === 0) {\n              if (candlestickResult) {\n                const paramsArray = [candlestickResult.params];\n                const content = formatter\n                  ? (formatter as (p: ReadonlyArray<TooltipParams>) => string)(paramsArray)\n                  : formatTooltipAxis(paramsArray);\n                if (content) {\n                  // Use candlestick anchor for tooltip positioning\n                  const anchor = computeCandlestickTooltipAnchor(\n                    candlestickResult.match,\n                    interactionScales.xScale,\n                    interactionScales.yScale,\n                    gridArea,\n                    canvas\n                  );\n                  const tooltipX = anchor?.x ?? containerX;\n                  const tooltipY = anchor?.y ?? containerY;\n                  if (content !== lastTooltipContent || tooltipX !== lastTooltipX || tooltipY !== lastTooltipY) {\n                    lastTooltipContent = content;\n                    lastTooltipX = tooltipX;\n                    lastTooltipY = tooltipY;\n                    showTooltipInternal(tooltipX, tooltipY, content, paramsArray);\n                  }\n                } else {\n                  hideTooltip();\n                }\n              } else {\n                hideTooltip();\n              }\n            } else {\n              const paramsArray = matches.map((m) => buildTooltipParams(m.seriesIndex, m.dataIndex, m.point));\n              if (candlestickResult) paramsArray.push(candlestickResult.params);\n              const content = formatter\n                ? (formatter as (p: ReadonlyArray<TooltipParams>) => string)(paramsArray)\n                : formatTooltipAxis(paramsArray);\n              if (content) {\n                // Use candlestick anchor if candlestick is present in tooltip\n                let tooltipX = containerX;\n                let tooltipY = containerY;\n                if (candlestickResult) {\n                  const anchor = computeCandlestickTooltipAnchor(\n                    candlestickResult.match,\n                    interactionScales.xScale,\n                    interactionScales.yScale,\n                    gridArea,\n                    canvas\n                  );\n                  if (anchor) {\n                    tooltipX = anchor.x;\n                    tooltipY = anchor.y;\n                  }\n                }\n                if (content !== lastTooltipContent || tooltipX !== lastTooltipX || tooltipY !== lastTooltipY) {\n                  lastTooltipContent = content;\n                  lastTooltipX = tooltipX;\n                  lastTooltipY = tooltipY;\n                  showTooltipInternal(tooltipX, tooltipY, content, paramsArray);\n                }\n              } else {\n                hideTooltip();\n              }\n            }\n          }\n        } else {\n          // Story 4.14: pie slice tooltip hit-testing (mouse only).\n          // If the cursor is over a pie slice, prefer showing that slice tooltip.\n          // findPieSliceAtPointer handles visibility filtering internally and returns correct series indices\n          const pieMatch = findPieSliceAtPointer(\n            seriesForRender,\n            effectivePointer.gridX,\n            effectivePointer.gridY,\n            interactionScales.plotWidthCss,\n            interactionScales.plotHeightCss\n          );\n\n          if (pieMatch) {\n            const params: TooltipParams = {\n              seriesName: pieMatch.slice.name,\n              seriesIndex: pieMatch.seriesIndex,\n              dataIndex: pieMatch.dataIndex,\n              value: [0, pieMatch.slice.value],\n              color: pieMatch.slice.color,\n            };\n            const content = formatter\n              ? (formatter as (p: TooltipParams) => string)(params)\n              : formatTooltipItem(params);\n            if (content && (content !== lastTooltipContent || containerX !== lastTooltipX || containerY !== lastTooltipY)) {\n              lastTooltipContent = content;\n              lastTooltipX = containerX;\n              lastTooltipY = containerY;\n              showTooltipInternal(containerX, containerY, content, params);\n            } else if (!content) {\n              hideTooltip();\n            }\n          } else {\n            // Candlestick body hit-testing (mouse, item trigger): prefer candle body over nearest-point logic.\n            // Hit-testing functions handle visibility filtering internally and return correct series indices\n            const candlestickResult = findCandlestickAtPointer(\n              seriesForRender,\n              effectivePointer.gridX,\n              effectivePointer.gridY,\n              interactionScales\n            );\n            if (candlestickResult) {\n              const content = formatter\n                ? (formatter as (p: TooltipParams) => string)(candlestickResult.params)\n                : formatTooltipItem(candlestickResult.params);\n              if (content) {\n                // Use candlestick anchor for tooltip positioning\n                const anchor = computeCandlestickTooltipAnchor(\n                  candlestickResult.match,\n                  interactionScales.xScale,\n                  interactionScales.yScale,\n                  gridArea,\n                  canvas\n                );\n                const tooltipX = anchor?.x ?? containerX;\n                const tooltipY = anchor?.y ?? containerY;\n                if (content !== lastTooltipContent || tooltipX !== lastTooltipX || tooltipY !== lastTooltipY) {\n                  lastTooltipContent = content;\n                  lastTooltipX = tooltipX;\n                  lastTooltipY = tooltipY;\n                  showTooltipInternal(tooltipX, tooltipY, content, candlestickResult.params);\n                }\n              } else {\n                hideTooltip();\n              }\n              return;\n            }\n\n            const match = findNearestPoint(\n              seriesForRender,\n              effectivePointer.gridX,\n              effectivePointer.gridY,\n              interactionScales.xScale,\n              interactionScales.yScale\n            );\n            if (!match) {\n              hideTooltip();\n            } else {\n              const params = buildTooltipParams(match.seriesIndex, match.dataIndex, match.point);\n              const content = formatter\n                ? (formatter as (p: TooltipParams) => string)(params)\n                : formatTooltipItem(params);\n              if (content && (content !== lastTooltipContent || containerX !== lastTooltipX || containerY !== lastTooltipY)) {\n                lastTooltipContent = content;\n                lastTooltipX = containerX;\n                lastTooltipY = containerY;\n                showTooltipInternal(containerX, containerY, content, params);\n              } else if (!content) {\n                hideTooltip();\n              }\n            }\n          }\n        }\n            } else {\n              hideTooltip();\n            }\n        } else {\n          hideTooltip();\n        }\n\n    // Compute maxRadiusCss for pie intro animation\n    const plotSize = interactionScales ?? (canvas && isHTMLCanvasElement(canvas) ? getPlotSizeCssPx(canvas, gridArea) : null);\n    const maxRadiusCss =\n      plotSize && typeof plotSize.plotWidthCss === 'number' && typeof plotSize.plotHeightCss === 'number'\n        ? 0.5 * Math.min(plotSize.plotWidthCss, plotSize.plotHeightCss)\n        : 0;\n\n    // Cache renderer pool state once per frame to avoid repeated object allocations.\n    const poolState = rendererPool.getState();\n\n    // Prepare all series renderers (area, line, bar, scatter, pie, candlestick)\n    const seriesPreparation = prepareSeries(\n      poolState,\n      {\n        currentOptions,\n        seriesForRender,\n        xScale,\n        yScale,\n        gridArea,\n        dataStore,\n        appendedGpuThisFrame,\n        gpuSeriesKindByIndex,\n        zoomState,\n        visibleXDomain,\n        introPhase,\n        introProgress01,\n        withAlpha,\n        maxRadiusCss,\n      }\n    );\n\n    const { visibleBarSeriesConfigs } = seriesPreparation;\n\n    // Prepare bar renderer with animated scale if intro is running\n    const introP = introPhase === 'running' ? clamp01(introProgress01) : 1;\n    const yScaleForBars = introP < 1 ? createAnimatedBarYScale(yScale, plotClipRect, visibleBarSeriesConfigs, introP) : yScale;\n    poolState.barRenderer.prepare(visibleBarSeriesConfigs, dataStore, xScale, yScaleForBars, gridArea);\n\n    // Prepare annotation GPU overlays (reference lines + point markers).\n    // Note: these renderers expect CANVAS-LOCAL CSS pixel coordinates; the coordinator owns\n    // data-space → canvas-space conversion and plot scissor state.\n    if (hasCartesianSeries) {\n      referenceLineRenderer.prepare(gridArea, combinedReferenceLines);\n      referenceLineRendererMsaa.prepare(gridArea, combinedReferenceLines);\n      annotationMarkerRenderer.prepare({\n        canvasWidth: gridArea.canvasWidth,\n        canvasHeight: gridArea.canvasHeight,\n        devicePixelRatio: gridArea.devicePixelRatio,\n        instances: combinedMarkers,\n      });\n      annotationMarkerRendererMsaa.prepare({\n        canvasWidth: gridArea.canvasWidth,\n        canvasHeight: gridArea.canvasHeight,\n        devicePixelRatio: gridArea.devicePixelRatio,\n        instances: combinedMarkers,\n      });\n    } else {\n      // Ensure prior frame instances don't persist visually if series mode changes.\n      referenceLineRenderer.prepare(gridArea, []);\n      referenceLineRendererMsaa.prepare(gridArea, []);\n      annotationMarkerRenderer.prepare({\n        canvasWidth: gridArea.canvasWidth,\n        canvasHeight: gridArea.canvasHeight,\n        devicePixelRatio: gridArea.devicePixelRatio,\n        instances: [],\n      });\n      annotationMarkerRendererMsaa.prepare({\n        canvasWidth: gridArea.canvasWidth,\n        canvasHeight: gridArea.canvasHeight,\n        devicePixelRatio: gridArea.devicePixelRatio,\n        instances: [],\n      });\n    }\n\n    textureManager.ensureTextures(gridArea.canvasWidth, gridArea.canvasHeight);\n    const texState = textureManager.getState();\n\n    // Swapchain view for the resolved MSAA overlay pass and for the final (load) overlay pass.\n    const swapchainView = gpuContext.canvasContext.getCurrentTexture().createView();\n    const encoder = device.createCommandEncoder({ label: 'renderCoordinator/commandEncoder' });\n    const clearValue = parseCssColorToGPUColor(currentOptions.theme.backgroundColor, { r: 0, g: 0, b: 0, a: 1 });\n\n    // Encode compute passes (scatter density) before the render pass.\n    encodeScatterDensityCompute(\n      poolState,\n      seriesForRender,\n      encoder\n    );\n\n    const mainPass = encoder.beginRenderPass({\n      label: 'renderCoordinator/mainPass',\n      colorAttachments: [\n        {\n          view: texState.mainColorView!,          // MSAA texture (4x)\n          resolveTarget: texState.mainResolveView!, // single-sample resolve target\n          clearValue,\n          loadOp: 'clear',\n          storeOp: 'discard',  // MSAA content discarded after resolve\n        },\n      ],\n    });\n\n    // Render order:\n    // - grid first (background)\n    // - pies early (non-cartesian, visible behind cartesian series)\n    // - area fills next (so they don't cover strokes/axes)\n    // - bars next (fills)\n    // - scatter next (points on top of fills, below strokes/overlays)\n    // - line strokes next\n    // - highlight next (on top of strokes)\n    // - axes last (on top)\n    if (gridRenderer) {\n      gridRenderer.render(mainPass);\n    }\n\n    // Render all series to the main pass with proper layering\n    renderSeriesPass(\n      poolState,\n      { referenceLineRenderer, referenceLineRendererMsaa, annotationMarkerRenderer, annotationMarkerRendererMsaa },\n      {\n        hasCartesianSeries,\n        gridArea,\n        mainPass,\n        plotScissor,\n        introPhase,\n        introProgress01,\n        referenceLineBelowCount,\n        markerBelowCount,\n      },\n      seriesPreparation\n    );\n\n    mainPass.end();\n\n    // MSAA annotation overlay pass: blit main color → MSAA target, then draw above-series annotations.\n    const overlayPass = encoder.beginRenderPass({\n      label: 'renderCoordinator/annotationOverlayMsaaPass',\n      colorAttachments: [\n        {\n          view: texState.overlayMsaaView!,\n          resolveTarget: swapchainView,\n          clearValue,\n          loadOp: 'clear',\n          storeOp: 'discard',\n        },\n      ],\n    });\n\n    overlayPass.setPipeline(texState.overlayBlitPipeline);\n    overlayPass.setBindGroup(0, texState.overlayBlitBindGroup!);\n    overlayPass.draw(3);\n\n    // Render above-series annotations to the overlay pass\n    renderAboveSeriesAnnotations(\n      { referenceLineRenderer, referenceLineRendererMsaa, annotationMarkerRenderer, annotationMarkerRendererMsaa },\n      {\n        hasCartesianSeries,\n        gridArea,\n        overlayPass,\n        plotScissor,\n        referenceLineBelowCount,\n        referenceLineAboveCount,\n        markerBelowCount,\n        markerAboveCount,\n      }\n    );\n\n    overlayPass.end();\n\n    // Top overlays (single-sample): axes, highlights, crosshair.\n    const topOverlayPass = encoder.beginRenderPass({\n      label: 'renderCoordinator/topOverlayPass',\n      colorAttachments: [\n        {\n          view: swapchainView,\n          loadOp: 'load',\n          storeOp: 'store',\n        },\n      ],\n    });\n\n    highlightRenderer.render(topOverlayPass);\n    if (hasCartesianSeries) {\n      xAxisRenderer.render(topOverlayPass);\n      yAxisRenderer.render(topOverlayPass);\n    }\n    crosshairRenderer.render(topOverlayPass);\n\n    topOverlayPass.end();\n    device.queue.submit([encoder.finish()]);\n\n    hasRenderedOnce = true;\n\n    // Generate axis labels for DOM overlay\n    renderAxisLabels(axisLabelOverlay, overlayContainer, {\n      gpuContext,\n      currentOptions,\n      xScale,\n      yScale,\n      xTickValues,\n      plotClipRect,\n      visibleXRangeMs,\n    });\n\n    // Generate annotation labels (DOM overlay)\n    renderAnnotationLabels(annotationOverlay, overlayContainer, {\n      currentOptions,\n      xScale,\n      yScale,\n      canvasCssWidthForAnnotations,\n      canvasCssHeightForAnnotations,\n      plotLeftCss,\n      plotTopCss,\n      plotWidthCss,\n      plotHeightCss,\n      canvas,\n    });\n  };\n\n\n  const dispose: RenderCoordinator['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    // Story 5.16: stop intro animation and avoid further render requests.\n    try {\n      if (introAnimId) introAnimController.cancel(introAnimId);\n      introAnimController.cancelAll();\n    } catch {\n      // best-effort\n    }\n    introAnimId = null;\n    introPhase = 'done';\n    introProgress01 = 1;\n\n    // Story 5.17: stop update animation and avoid further render requests.\n    try {\n      if (updateAnimId) updateAnimController.cancel(updateAnimId);\n      updateAnimController.cancelAll();\n    } catch {\n      // best-effort\n    }\n    updateAnimId = null;\n    updateProgress01 = 1;\n    updateTransition = null;\n\n    cancelScheduledFlush();\n    cancelZoomResampleDebounce();\n    zoomResampleDue = false;\n\n    pendingAppendByIndex.clear();\n\n    insideZoom?.dispose();\n    insideZoom = null;\n    unsubscribeZoom?.();\n    unsubscribeZoom = null;\n    zoomState = null;\n    lastOptionsZoomRange = null;\n    zoomRangeListeners.clear();\n\n    eventManager?.dispose();\n    crosshairRenderer.dispose();\n    highlightRenderer.dispose();\n\n    rendererPool.dispose();\n\n    gridRenderer.dispose();\n    xAxisRenderer.dispose();\n    yAxisRenderer.dispose();\n    referenceLineRenderer.dispose();\n    annotationMarkerRenderer.dispose();\n    referenceLineRendererMsaa.dispose();\n    annotationMarkerRendererMsaa.dispose();\n\n    textureManager.dispose();\n\n    dataStore.dispose();\n\n    // Dispose tooltip/legend before the text overlay (all touch container positioning).\n    tooltip?.dispose();\n    tooltip = null;\n    legend?.dispose();\n    axisLabelOverlay?.dispose();\n    annotationOverlay?.dispose();\n  };\n\n  const getInteractionX: RenderCoordinator['getInteractionX'] = () => interactionX;\n\n  const setInteractionX: RenderCoordinator['setInteractionX'] = (x, source) => {\n    assertNotDisposed();\n    const normalized = x !== null && Number.isFinite(x) ? x : null;\n\n    // External interaction should not depend on y, so we treat it as “sync” mode.\n    pointerState = { ...pointerState, source: normalized === null ? 'mouse' : 'sync' };\n\n    setInteractionXInternal(normalized, source);\n\n    if (normalized === null && pointerState.hasPointer === false) {\n      crosshairRenderer.setVisible(false);\n      highlightRenderer.setVisible(false);\n      hideTooltipInternal();\n    }\n    requestRender();\n  };\n\n  const onInteractionXChange: RenderCoordinator['onInteractionXChange'] = (callback) => {\n    assertNotDisposed();\n    interactionXListeners.add(callback);\n    return () => {\n      interactionXListeners.delete(callback);\n    };\n  };\n\n  const getZoomRange: RenderCoordinator['getZoomRange'] = () => {\n    return zoomState?.getRange() ?? null;\n  };\n\n  const setZoomRange: RenderCoordinator['setZoomRange'] = (start, end) => {\n    assertNotDisposed();\n    if (!zoomState) return;\n    zoomState.setRange(start, end);\n    // onChange will requestRender + emit.\n  };\n\n  const onZoomRangeChange: RenderCoordinator['onZoomRangeChange'] = (cb) => {\n    assertNotDisposed();\n    zoomRangeListeners.add(cb);\n    return () => {\n      zoomRangeListeners.delete(cb);\n    };\n  };\n\n  return {\n    setOptions,\n    appendData,\n    getInteractionX,\n    setInteractionX,\n    onInteractionXChange,\n    getZoomRange,\n    setZoomRange,\n    onZoomRangeChange,\n    render,\n    dispose,\n  };\n}\n\n","import type {\n  AreaStyleConfig,\n  CandlestickItemStyleConfig,\n  CandlestickStyle,\n  ChartGPUOptions,\n  GridConfig,\n  GridLinesConfig,\n  LineStyleConfig,\n} from './types';\n\nexport const defaultGrid = {\n  left: 60,\n  right: 20,\n  top: 40,\n  bottom: 40,\n} as const satisfies Required<GridConfig>;\n\nexport const defaultPalette = [\n  '#5470C6',\n  '#91CC75',\n  '#FAC858',\n  '#EE6666',\n  '#73C0DE',\n  '#3BA272',\n  '#FC8452',\n  '#9A60B4',\n  '#EA7CCC',\n] as const;\n\nexport const defaultLineStyle = {\n  width: 2,\n  opacity: 1,\n} as const satisfies Required<Omit<LineStyleConfig, 'color'>>;\n\nexport const defaultAreaStyle = {\n  opacity: 0.25,\n} as const satisfies Required<Omit<AreaStyleConfig, 'color'>>;\n\nexport const candlestickDefaults = {\n  style: 'classic' as CandlestickStyle,\n  itemStyle: {\n    upColor: '#22c55e',\n    downColor: '#ef4444',\n    upBorderColor: '#22c55e',\n    downBorderColor: '#ef4444',\n    borderWidth: 1,\n  } as const satisfies Required<CandlestickItemStyleConfig>,\n  barWidth: '80%' as const,\n  barMinWidth: 1,\n  barMaxWidth: 50,\n  sampling: 'ohlc' as const,\n  samplingThreshold: 5000,\n} as const;\n\nexport const scatterDefaults = {\n  mode: 'points' as const,\n  // Bin size in CSS pixels for density mode. Must be > 0.\n  binSize: 2,\n  densityColormap: 'viridis' as const,\n  densityNormalization: 'log' as const,\n} as const;\n\n/**\n * Default grid lines configuration.\n * Matches createGridRenderer defaults: horizontal=5, vertical=6.\n */\nexport const defaultGridLines = {\n  show: true,\n  horizontal: {\n    show: true,\n    count: 5,\n  },\n  vertical: {\n    show: true,\n    count: 6,\n  },\n} as const satisfies Required<Omit<GridLinesConfig, 'color' | 'opacity'>> & {\n  readonly horizontal: Required<Omit<import('./types').GridLinesDirectionConfig, 'color'>>;\n  readonly vertical: Required<Omit<import('./types').GridLinesDirectionConfig, 'color'>>;\n};\n\nexport const defaultOptions = {\n  grid: defaultGrid,\n  xAxis: { type: 'value' },\n  yAxis: { type: 'value', autoBounds: 'visible' },\n  autoScroll: false,\n  theme: 'dark',\n  palette: defaultPalette,\n  series: [],\n} as const satisfies Readonly<\n  Required<Pick<ChartGPUOptions, 'grid' | 'xAxis' | 'yAxis' | 'autoScroll' | 'theme' | 'palette'>> & {\n    readonly series: readonly [];\n  }\n>;\n\n","import type { ThemeConfig } from './types';\n\nconst palette = [\n  '#00E5FF',\n  '#FF2D95',\n  '#B026FF',\n  '#00F5A0',\n  '#FFD300',\n  '#FF6B00',\n  '#4D5BFF',\n  '#FF3D3D',\n] as const;\n\nexport const darkTheme = {\n  backgroundColor: '#1a1a2e',\n  textColor: '#e0e0e0',\n  axisLineColor: 'rgba(224,224,224,0.35)',\n  axisTickColor: 'rgba(224,224,224,0.55)',\n  gridLineColor: 'rgba(255,255,255,0.1)',\n  colorPalette: [...palette],\n  fontFamily:\n    'system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n  fontSize: 12,\n} satisfies ThemeConfig;\n","import type { ThemeConfig } from './types';\n\nconst palette = [\n  '#1F77B4',\n  '#FF7F0E',\n  '#2CA02C',\n  '#D62728',\n  '#9467BD',\n  '#8C564B',\n  '#E377C2',\n  '#17BECF',\n] as const;\n\nexport const lightTheme = {\n  backgroundColor: '#ffffff',\n  textColor: '#333333',\n  axisLineColor: 'rgba(0,0,0,0.35)',\n  axisTickColor: 'rgba(0,0,0,0.55)',\n  gridLineColor: 'rgba(0,0,0,0.1)',\n  colorPalette: [...palette],\n  fontFamily:\n    'system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n  fontSize: 12,\n} satisfies ThemeConfig;\n","import type { ThemeConfig } from './types';\nimport { darkTheme } from './darkTheme';\nimport { lightTheme } from './lightTheme';\n\nexport { darkTheme, lightTheme };\nexport type { ThemeConfig };\n\nexport type ThemeName = 'dark' | 'light';\n\nexport function getTheme(name: ThemeName): ThemeConfig {\n  return name === 'dark' ? darkTheme : lightTheme;\n}\n","import type {\n  AreaStyleConfig,\n  AnnotationConfig,\n  AnnotationLabel,\n  AnnotationLabelAnchor,\n  AnnotationLabelBackground,\n  AnnotationPointMarker,\n  AxisConfig,\n  CandlestickItemStyleConfig,\n  CandlestickSeriesConfig,\n  CandlestickStyle,\n  ChartGPUOptions,\n  DataZoomConfig,\n  GridConfig,\n  GridLinesConfig,\n  GridLinesDirectionConfig,\n  LineStyleConfig,\n  OHLCDataPoint,\n  OHLCDataPointTuple,\n  AreaSeriesConfig,\n  BarSeriesConfig,\n  LineSeriesConfig,\n  PieDataItem,\n  PieSeriesConfig,\n  ScatterSeriesConfig,\n  ScatterSymbol,\n  SeriesSampling,\n} from './types';\nimport {\n  candlestickDefaults,\n  defaultAreaStyle,\n  defaultGridLines,\n  defaultLineStyle,\n  defaultOptions,\n  defaultPalette,\n  scatterDefaults,\n} from './defaults';\nimport { getTheme } from '../themes';\nimport type { ThemeConfig } from '../themes/types';\nimport { sampleSeriesDataPoints } from '../data/sampleSeries';\nimport { ohlcSample } from '../data/ohlcSample';\nimport { computeRawBoundsFromCartesianData, hasNullGaps } from '../data/cartesianData';\nimport { parseCssColorToRgba01 } from '../utils/colors';\n\nexport type ResolvedGridConfig = Readonly<Required<GridConfig>>;\nexport type ResolvedLineStyleConfig = Readonly<Required<Omit<LineStyleConfig, 'color'>> & { readonly color: string }>;\nexport type ResolvedAreaStyleConfig = Readonly<Required<Omit<AreaStyleConfig, 'color'>> & { readonly color: string }>;\n\n/**\n * Resolved grid lines direction configuration with all defaults applied.\n */\nexport type ResolvedGridLinesDirectionConfig = Readonly<{\n  readonly show: boolean;\n  readonly count: number;\n  readonly color: string;\n}>;\n\n/**\n * Resolved grid lines configuration with all defaults and color resolution applied.\n */\nexport type ResolvedGridLinesConfig = Readonly<{\n  readonly show: boolean;\n  readonly color: string;\n  readonly opacity: number;\n  readonly horizontal: ResolvedGridLinesDirectionConfig;\n  readonly vertical: ResolvedGridLinesDirectionConfig;\n}>;\n\nexport type RawBounds = Readonly<{ xMin: number; xMax: number; yMin: number; yMax: number }>;\n\nexport type ResolvedLineSeriesConfig = Readonly<\n  Omit<LineSeriesConfig, 'color' | 'lineStyle' | 'areaStyle' | 'sampling' | 'samplingThreshold' | 'data' | 'connectNulls'> & {\n    readonly connectNulls: boolean;\n    readonly color: string;\n    readonly lineStyle: ResolvedLineStyleConfig;\n    readonly areaStyle?: ResolvedAreaStyleConfig;\n    readonly sampling: SeriesSampling;\n    readonly samplingThreshold: number;\n    /**\n     * Original (unsampled) series data.\n     *\n     * Used at runtime for zoom-aware re-sampling so we can increase detail when zoomed-in without\n     * losing outliers or permanently discarding points.\n     */\n    readonly rawData: Readonly<LineSeriesConfig['data']>;\n    readonly data: Readonly<LineSeriesConfig['data']>;\n    /**\n     * Bounds computed from the original (unsampled) data. Used for axis auto-bounds so sampling\n     * cannot clip outliers.\n     */\n    readonly rawBounds?: RawBounds;\n  }\n>;\n\nexport type ResolvedAreaSeriesConfig = Readonly<\n  Omit<AreaSeriesConfig, 'color' | 'areaStyle' | 'sampling' | 'samplingThreshold' | 'data' | 'connectNulls'> & {\n    readonly connectNulls: boolean;\n    readonly color: string;\n    readonly areaStyle: ResolvedAreaStyleConfig;\n    readonly sampling: SeriesSampling;\n    readonly samplingThreshold: number;\n    /** Original (unsampled) series data (see `ResolvedLineSeriesConfig.rawData`). */\n    readonly rawData: Readonly<AreaSeriesConfig['data']>;\n    readonly data: Readonly<AreaSeriesConfig['data']>;\n    /**\n     * Bounds computed from the original (unsampled) data. Used for axis auto-bounds so sampling\n     * cannot clip outliers.\n     */\n    readonly rawBounds?: RawBounds;\n  }\n>;\n\nexport type ResolvedBarSeriesConfig = Readonly<\n  Omit<BarSeriesConfig, 'color' | 'sampling' | 'samplingThreshold' | 'data'> & {\n    readonly color: string;\n    readonly sampling: SeriesSampling;\n    readonly samplingThreshold: number;\n    /** Original (unsampled) series data (see `ResolvedLineSeriesConfig.rawData`). */\n    readonly rawData: Readonly<BarSeriesConfig['data']>;\n    readonly data: Readonly<BarSeriesConfig['data']>;\n    /**\n     * Bounds computed from the original (unsampled) data. Used for axis auto-bounds so sampling\n     * cannot clip outliers.\n     */\n    readonly rawBounds?: RawBounds;\n  }\n>;\n\nexport type ResolvedScatterSeriesConfig = Readonly<\n  Omit<\n    ScatterSeriesConfig,\n    'color' | 'sampling' | 'samplingThreshold' | 'data' | 'mode' | 'binSize' | 'densityColormap' | 'densityNormalization'\n  > & {\n    readonly color: string;\n    readonly sampling: SeriesSampling;\n    readonly samplingThreshold: number;\n    readonly mode: NonNullable<ScatterSeriesConfig['mode']>;\n    readonly binSize: number;\n    readonly densityColormap: NonNullable<ScatterSeriesConfig['densityColormap']>;\n    readonly densityNormalization: NonNullable<ScatterSeriesConfig['densityNormalization']>;\n    /** Original (unsampled) series data (see `ResolvedLineSeriesConfig.rawData`). */\n    readonly rawData: Readonly<ScatterSeriesConfig['data']>;\n    readonly data: Readonly<ScatterSeriesConfig['data']>;\n    /**\n     * Bounds computed from the original (unsampled) data. Used for axis auto-bounds so sampling\n     * cannot clip outliers.\n     */\n    readonly rawBounds?: RawBounds;\n  }\n>;\n\nexport type ResolvedPieDataItem = Readonly<\n  Omit<PieDataItem, 'color' | 'visible'> & {\n    readonly color: string;\n    readonly visible: boolean;\n  }\n>;\n\nexport type ResolvedPieSeriesConfig = Readonly<\n  Omit<PieSeriesConfig, 'color' | 'data'> & {\n    readonly color: string;\n    readonly data: ReadonlyArray<ResolvedPieDataItem>;\n  }\n>;\n\nexport type ResolvedCandlestickItemStyleConfig = Readonly<Required<CandlestickItemStyleConfig>>;\n\nexport type ResolvedCandlestickSeriesConfig = Readonly<\n  Omit<CandlestickSeriesConfig, 'color' | 'style' | 'itemStyle' | 'barWidth' | 'barMinWidth' | 'barMaxWidth' | 'sampling' | 'samplingThreshold' | 'data'> & {\n    readonly color: string;\n    readonly style: CandlestickStyle;\n    readonly itemStyle: ResolvedCandlestickItemStyleConfig;\n    readonly barWidth: number | string;\n    readonly barMinWidth: number;\n    readonly barMaxWidth: number;\n    readonly sampling: 'none' | 'ohlc';\n    readonly samplingThreshold: number;\n    /** Original (unsampled) series data. */\n    readonly rawData: Readonly<CandlestickSeriesConfig['data']>;\n    readonly data: Readonly<CandlestickSeriesConfig['data']>;\n    /**\n     * Bounds computed from the original (unsampled) data. Used for axis auto-bounds so sampling\n     * cannot clip outliers.\n     */\n    readonly rawBounds?: RawBounds;\n  }\n>;\n\nexport type ResolvedSeriesConfig =\n  | ResolvedLineSeriesConfig\n  | ResolvedAreaSeriesConfig\n  | ResolvedBarSeriesConfig\n  | ResolvedScatterSeriesConfig\n  | ResolvedPieSeriesConfig\n  | ResolvedCandlestickSeriesConfig;\n\nexport interface ResolvedChartGPUOptions\n  extends Omit<ChartGPUOptions, 'grid' | 'gridLines' | 'xAxis' | 'yAxis' | 'theme' | 'palette' | 'series' | 'legend'> {\n  readonly grid: ResolvedGridConfig;\n  readonly gridLines: ResolvedGridLinesConfig;\n  readonly xAxis: AxisConfig;\n  readonly yAxis: AxisConfig;\n  readonly autoScroll: boolean;\n  readonly theme: ThemeConfig;\n  readonly palette: ReadonlyArray<string>;\n  readonly series: ReadonlyArray<ResolvedSeriesConfig>;\n  readonly annotations?: ReadonlyArray<AnnotationConfig>;\n  readonly legend?: import('./types').LegendConfig;\n}\n\nconst sanitizeDataZoom = (input: unknown): ReadonlyArray<DataZoomConfig> | undefined => {\n  if (!Array.isArray(input)) return undefined;\n\n  const out: DataZoomConfig[] = [];\n\n  for (const item of input) {\n    if (item === null || typeof item !== 'object' || Array.isArray(item)) continue;\n    const record = item as Record<string, unknown>;\n\n    const type = record.type;\n    if (type !== 'inside' && type !== 'slider') continue;\n\n    const xAxisIndexRaw = record.xAxisIndex;\n    const startRaw = record.start;\n    const endRaw = record.end;\n    const minSpanRaw = record.minSpan;\n    const maxSpanRaw = record.maxSpan;\n\n    const xAxisIndex =\n      typeof xAxisIndexRaw === 'number' && Number.isFinite(xAxisIndexRaw) ? xAxisIndexRaw : undefined;\n    const start = typeof startRaw === 'number' && Number.isFinite(startRaw) ? startRaw : undefined;\n    const end = typeof endRaw === 'number' && Number.isFinite(endRaw) ? endRaw : undefined;\n    const minSpan =\n      typeof minSpanRaw === 'number' && Number.isFinite(minSpanRaw) ? minSpanRaw : undefined;\n    const maxSpan =\n      typeof maxSpanRaw === 'number' && Number.isFinite(maxSpanRaw) ? maxSpanRaw : undefined;\n\n    out.push({ type, xAxisIndex, start, end, minSpan, maxSpan });\n  }\n\n  return out;\n};\n\nconst sanitizeAnnotations = (input: unknown): ReadonlyArray<AnnotationConfig> | undefined => {\n  if (!Array.isArray(input)) return undefined;\n\n  const out: AnnotationConfig[] = [];\n\n  const isLabelAnchor = (v: unknown): v is AnnotationLabelAnchor =>\n    v === 'start' || v === 'center' || v === 'end';\n\n  const isScatterSymbol = (v: unknown): v is ScatterSymbol =>\n    v === 'circle' || v === 'rect' || v === 'triangle';\n\n  const sanitizeString = (v: unknown): string | undefined => {\n    if (typeof v !== 'string') return undefined;\n    const t = v.trim();\n    return t.length > 0 ? t : undefined;\n  };\n\n  const sanitizeFiniteNumber = (v: unknown): number | undefined =>\n    typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n\n  const sanitizeOpacity01 = (v: unknown): number | undefined => {\n    const n = sanitizeFiniteNumber(v);\n    if (n == null) return undefined;\n    return Math.min(1, Math.max(0, n));\n  };\n\n  const sanitizeLineDash = (v: unknown): readonly number[] | undefined => {\n    if (!Array.isArray(v)) return undefined;\n    const cleaned = v\n      .filter((x): x is number => typeof x === 'number' && Number.isFinite(x))\n      .map((x) => x);\n    if (cleaned.length === 0) return undefined;\n    Object.freeze(cleaned);\n    return cleaned;\n  };\n\n  const sanitizePadding = (v: unknown): number | readonly [number, number, number, number] | undefined => {\n    if (typeof v === 'number' && Number.isFinite(v)) return v;\n    if (!Array.isArray(v) || v.length !== 4) return undefined;\n    const t = sanitizeFiniteNumber(v[0]);\n    const r = sanitizeFiniteNumber(v[1]);\n    const b = sanitizeFiniteNumber(v[2]);\n    const l = sanitizeFiniteNumber(v[3]);\n    if (t == null || r == null || b == null || l == null) return undefined;\n    return [t, r, b, l] as const;\n  };\n\n  for (const item of input) {\n    if (item === null || typeof item !== 'object' || Array.isArray(item)) continue;\n    const record = item as Record<string, unknown>;\n\n    const type = record.type;\n    if (type !== 'lineX' && type !== 'lineY' && type !== 'point' && type !== 'text') continue;\n\n    const id = sanitizeString(record.id);\n    const layerRaw = record.layer;\n    const layer = layerRaw === 'belowSeries' || layerRaw === 'aboveSeries' ? layerRaw : undefined;\n\n    const styleRaw = record.style;\n    const style =\n      styleRaw && typeof styleRaw === 'object' && !Array.isArray(styleRaw)\n        ? (() => {\n            const s = styleRaw as Record<string, unknown>;\n            const color = sanitizeString(s.color);\n            const lineWidth = sanitizeFiniteNumber(s.lineWidth);\n            const lineDash = sanitizeLineDash(s.lineDash);\n            const opacity = sanitizeOpacity01(s.opacity);\n            const next: Record<string, unknown> = {\n              ...(color ? { color } : {}),\n              ...(lineWidth != null ? { lineWidth } : {}),\n              ...(lineDash ? { lineDash } : {}),\n              ...(opacity != null ? { opacity } : {}),\n            };\n            return Object.keys(next).length > 0 ? (next as AnnotationConfig['style']) : undefined;\n          })()\n        : undefined;\n\n    const labelRaw = record.label;\n    const label =\n      labelRaw && typeof labelRaw === 'object' && !Array.isArray(labelRaw)\n        ? (() => {\n            const l = labelRaw as Record<string, unknown>;\n            const text = sanitizeString(l.text);\n            const template = sanitizeString(l.template);\n            const decimalsRaw = l.decimals;\n            const decimals =\n              typeof decimalsRaw === 'number' && Number.isFinite(decimalsRaw) && decimalsRaw >= 0\n                ? Math.min(20, Math.floor(decimalsRaw))\n                : undefined;\n            const offsetRaw = l.offset;\n            const offset =\n              Array.isArray(offsetRaw) &&\n              offsetRaw.length === 2 &&\n              typeof offsetRaw[0] === 'number' &&\n              Number.isFinite(offsetRaw[0]) &&\n              typeof offsetRaw[1] === 'number' &&\n              Number.isFinite(offsetRaw[1])\n                ? ([offsetRaw[0], offsetRaw[1]] as const)\n                : undefined;\n            const anchorRaw = l.anchor;\n            const anchor = isLabelAnchor(anchorRaw) ? anchorRaw : undefined;\n            const bgRaw = l.background;\n            const background =\n              bgRaw && typeof bgRaw === 'object' && !Array.isArray(bgRaw)\n                ? (() => {\n                    const bg = bgRaw as Record<string, unknown>;\n                    const color = sanitizeString(bg.color);\n                    const opacity = sanitizeOpacity01(bg.opacity);\n                    const padding = sanitizePadding(bg.padding);\n                    const borderRadius = sanitizeFiniteNumber(bg.borderRadius);\n                    const next: AnnotationLabelBackground = {\n                      ...(color ? { color } : {}),\n                      ...(opacity != null ? { opacity } : {}),\n                      ...(padding != null ? { padding } : {}),\n                      ...(borderRadius != null ? { borderRadius } : {}),\n                    };\n                    return Object.keys(next).length > 0 ? next : undefined;\n                  })()\n                : undefined;\n\n            const next: AnnotationLabel = {\n              ...(text ? { text } : {}),\n              ...(template ? { template } : {}),\n              ...(decimals != null ? { decimals } : {}),\n              ...(offset ? { offset } : {}),\n              ...(anchor ? { anchor } : {}),\n              ...(background ? { background } : {}),\n            };\n\n            return Object.keys(next).length > 0 ? next : undefined;\n          })()\n        : undefined;\n\n    if (type === 'lineX') {\n      const x = sanitizeFiniteNumber(record.x);\n      if (x == null) continue;\n      const base: AnnotationConfig = { type: 'lineX', x, ...(id ? { id } : {}), ...(layer ? { layer } : {}), ...(style ? { style } : {}), ...(label ? { label } : {}) };\n      out.push(base);\n      continue;\n    }\n\n    if (type === 'lineY') {\n      const y = sanitizeFiniteNumber(record.y);\n      if (y == null) continue;\n      const base: AnnotationConfig = { type: 'lineY', y, ...(id ? { id } : {}), ...(layer ? { layer } : {}), ...(style ? { style } : {}), ...(label ? { label } : {}) };\n      out.push(base);\n      continue;\n    }\n\n    if (type === 'point') {\n      const x = sanitizeFiniteNumber(record.x);\n      const y = sanitizeFiniteNumber(record.y);\n      if (x == null || y == null) continue;\n      const markerRaw = record.marker;\n      const marker =\n        markerRaw && typeof markerRaw === 'object' && !Array.isArray(markerRaw)\n          ? (() => {\n              const m = markerRaw as Record<string, unknown>;\n              const symbolRaw = m.symbol;\n              const symbol = isScatterSymbol(symbolRaw) ? symbolRaw : undefined;\n              const size = sanitizeFiniteNumber(m.size);\n              const mStyleRaw = m.style;\n              const mStyle =\n                mStyleRaw && typeof mStyleRaw === 'object' && !Array.isArray(mStyleRaw)\n                  ? (() => {\n                      const s = mStyleRaw as Record<string, unknown>;\n                      const color = sanitizeString(s.color);\n                      const opacity = sanitizeOpacity01(s.opacity);\n                      const lineWidth = sanitizeFiniteNumber(s.lineWidth);\n                      const lineDash = sanitizeLineDash(s.lineDash);\n                      const next: Record<string, unknown> = {\n                        ...(color ? { color } : {}),\n                        ...(opacity != null ? { opacity } : {}),\n                        ...(lineWidth != null ? { lineWidth } : {}),\n                        ...(lineDash ? { lineDash } : {}),\n                      };\n                      return Object.keys(next).length > 0 ? (next as AnnotationConfig['style']) : undefined;\n                    })()\n                  : undefined;\n              const next: AnnotationPointMarker = {\n                ...(symbol ? { symbol } : {}),\n                ...(size != null ? { size } : {}),\n                ...(mStyle ? { style: mStyle } : {}),\n              };\n              return Object.keys(next).length > 0 ? next : undefined;\n            })()\n          : undefined;\n\n      const base: AnnotationConfig = {\n        type: 'point',\n        x,\n        y,\n        ...(marker ? { marker } : {}),\n        ...(id ? { id } : {}),\n        ...(layer ? { layer } : {}),\n        ...(style ? { style } : {}),\n        ...(label ? { label } : {}),\n      };\n      out.push(base);\n      continue;\n    }\n\n    // type === 'text'\n    {\n      const positionRaw = record.position;\n      const text = sanitizeString(record.text);\n      if (!text) continue;\n      if (!positionRaw || typeof positionRaw !== 'object' || Array.isArray(positionRaw)) continue;\n      const p = positionRaw as Record<string, unknown>;\n      const space = p.space;\n      if (space !== 'data' && space !== 'plot') continue;\n      const x = sanitizeFiniteNumber(p.x);\n      const y = sanitizeFiniteNumber(p.y);\n      if (x == null || y == null) continue;\n      const position = { space, x, y } as const;\n\n      const base: AnnotationConfig = {\n        type: 'text',\n        position,\n        text,\n        ...(id ? { id } : {}),\n        ...(layer ? { layer } : {}),\n        ...(style ? { style } : {}),\n        ...(label ? { label } : {}),\n      };\n      out.push(base);\n      continue;\n    }\n  }\n\n  if (out.length === 0) return undefined;\n  Object.freeze(out);\n  return out;\n};\n\nconst sanitizePalette = (palette: unknown): string[] => {\n  if (!Array.isArray(palette)) return [];\n  return palette\n    .filter((c): c is string => typeof c === 'string')\n    .map((c) => c.trim())\n    .filter((c) => c.length > 0);\n};\n\nconst resolveTheme = (themeInput: unknown): ThemeConfig => {\n  const base = getTheme('dark');\n\n  if (typeof themeInput === 'string') {\n    const name = themeInput.trim().toLowerCase();\n    return name === 'light' ? getTheme('light') : getTheme('dark');\n  }\n\n  if (themeInput === null || typeof themeInput !== 'object' || Array.isArray(themeInput)) {\n    return base;\n  }\n\n  const input = themeInput as Partial<Record<keyof ThemeConfig, unknown>>;\n  const takeString = (key: keyof ThemeConfig): string | undefined => {\n    const v = input[key];\n    if (typeof v !== 'string') return undefined;\n    const trimmed = v.trim();\n    return trimmed.length > 0 ? trimmed : undefined;\n  };\n\n  const fontSizeRaw = input.fontSize;\n  const fontSize =\n    typeof fontSizeRaw === 'number' && Number.isFinite(fontSizeRaw) ? fontSizeRaw : undefined;\n\n  const colorPaletteCandidate = sanitizePalette(input.colorPalette);\n\n  return {\n    backgroundColor: takeString('backgroundColor') ?? base.backgroundColor,\n    textColor: takeString('textColor') ?? base.textColor,\n    axisLineColor: takeString('axisLineColor') ?? base.axisLineColor,\n    axisTickColor: takeString('axisTickColor') ?? base.axisTickColor,\n    gridLineColor: takeString('gridLineColor') ?? base.gridLineColor,\n    colorPalette: colorPaletteCandidate.length > 0 ? colorPaletteCandidate : Array.from(base.colorPalette),\n    fontFamily: takeString('fontFamily') ?? base.fontFamily,\n    fontSize: fontSize ?? base.fontSize,\n  };\n};\n\nconst normalizeOptionalColor = (color: unknown): string | undefined => {\n  if (typeof color !== 'string') return undefined;\n  const trimmed = color.trim();\n  return trimmed.length > 0 ? trimmed : undefined;\n};\n\nconst normalizeSampling = (value: unknown): SeriesSampling | undefined => {\n  if (typeof value !== 'string') return undefined;\n  const v = value.trim().toLowerCase();\n  return v === 'none' || v === 'lttb' || v === 'average' || v === 'max' || v === 'min' || v === 'ohlc'\n    ? (v as SeriesSampling)\n    : undefined;\n};\n\nconst normalizeScatterMode = (value: unknown): NonNullable<ScatterSeriesConfig['mode']> | undefined => {\n  if (typeof value !== 'string') return undefined;\n  const v = value.trim().toLowerCase();\n  return v === 'points' || v === 'density' ? (v as NonNullable<ScatterSeriesConfig['mode']>) : undefined;\n};\n\nconst normalizeDensityNormalization = (\n  value: unknown\n): NonNullable<ScatterSeriesConfig['densityNormalization']> | undefined => {\n  if (typeof value !== 'string') return undefined;\n  const v = value.trim().toLowerCase();\n  return v === 'linear' || v === 'sqrt' || v === 'log'\n    ? (v as NonNullable<ScatterSeriesConfig['densityNormalization']>)\n    : undefined;\n};\n\nconst normalizeDensityBinSize = (value: unknown): number | undefined => {\n  if (typeof value !== 'number' || !Number.isFinite(value)) return undefined;\n  const v = Math.floor(value);\n  return v > 0 ? Math.max(1, v) : undefined;\n};\n\nconst normalizeDensityColormap = (\n  value: unknown\n): NonNullable<ScatterSeriesConfig['densityColormap']> | undefined => {\n  if (typeof value === 'string') {\n    const v = value.trim().toLowerCase();\n    return v === 'viridis' || v === 'plasma' || v === 'inferno'\n      ? (v as NonNullable<ScatterSeriesConfig['densityColormap']>)\n      : undefined;\n  }\n\n  if (!Array.isArray(value)) return undefined;\n\n  const isAlreadyCleanStringArray =\n    value.length > 0 && value.every((c) => typeof c === 'string' && c.length > 0 && c === c.trim());\n\n  if (isAlreadyCleanStringArray) {\n    const arr = value as string[];\n    if (!Object.isFrozen(arr)) Object.freeze(arr);\n    return arr as readonly string[];\n  }\n\n  const sanitized = value\n    .filter((c): c is string => typeof c === 'string')\n    .map((c) => c.trim())\n    .filter((c) => c.length > 0);\n\n  if (sanitized.length === 0) return undefined;\n  Object.freeze(sanitized);\n  return sanitized as readonly string[];\n};\n\nconst normalizeCandlestickSampling = (value: unknown): 'none' | 'ohlc' | undefined => {\n  if (typeof value !== 'string') return undefined;\n  const v = value.trim().toLowerCase();\n  return v === 'none' || v === 'ohlc' ? (v as 'none' | 'ohlc') : undefined;\n};\n\nconst normalizeSamplingThreshold = (value: unknown): number | undefined => {\n  if (typeof value !== 'number' || !Number.isFinite(value)) return undefined;\n  const t = Math.floor(value);\n  return t > 0 ? t : undefined;\n};\n\nconst normalizeAxisAutoBounds = (value: unknown): AxisConfig['autoBounds'] | undefined => {\n  if (typeof value !== 'string') return undefined;\n  const v = value.trim().toLowerCase();\n  return v === 'global' || v === 'visible' ? (v as AxisConfig['autoBounds']) : undefined;\n};\n\nconst isTupleOHLCDataPoint = (p: OHLCDataPoint): p is OHLCDataPointTuple => Array.isArray(p);\n\nconst computeRawBoundsFromOHLC = (data: ReadonlyArray<OHLCDataPoint>): RawBounds | undefined => {\n  if (data.length === 0) return undefined;\n\n  let xMin = Number.POSITIVE_INFINITY;\n  let xMax = Number.NEGATIVE_INFINITY;\n  let yMin = Number.POSITIVE_INFINITY;\n  let yMax = Number.NEGATIVE_INFINITY;\n\n  // Hoist tuple-vs-object detection once (assume homogeneous arrays).\n  const isTuple = isTupleOHLCDataPoint(data[0]!);\n\n  if (isTuple) {\n    // Tuple format path: [timestamp, open, close, low, high]\n    const dataAsTuples = data as ReadonlyArray<OHLCDataPointTuple>;\n\n    for (let i = 0; i < dataAsTuples.length; i++) {\n      const p = dataAsTuples[i]!;\n      const x = p[0];\n      const low = p[3];\n      const high = p[4];\n      if (!Number.isFinite(x) || !Number.isFinite(low) || !Number.isFinite(high)) continue;\n\n      const yLow = Math.min(low, high);\n      const yHigh = Math.max(low, high);\n\n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (yLow < yMin) yMin = yLow;\n      if (yHigh > yMax) yMax = yHigh;\n    }\n  } else {\n    // Object format path: { timestamp, open, close, low, high }\n    const dataAsObjects = data as ReadonlyArray<Exclude<OHLCDataPoint, OHLCDataPointTuple>>;\n\n    for (let i = 0; i < dataAsObjects.length; i++) {\n      const p = dataAsObjects[i]!;\n      const x = p.timestamp;\n      const low = p.low;\n      const high = p.high;\n      if (!Number.isFinite(x) || !Number.isFinite(low) || !Number.isFinite(high)) continue;\n\n      const yLow = Math.min(low, high);\n      const yHigh = Math.max(low, high);\n\n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (yLow < yMin) yMin = yLow;\n      if (yHigh > yMax) yMax = yHigh;\n    }\n  }\n\n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || !Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    return undefined;\n  }\n\n  // Keep bounds usable for downstream scale derivation.\n  if (xMin === xMax) xMax = xMin + 1;\n  if (yMin === yMax) yMax = yMin + 1;\n\n  return { xMin, xMax, yMin, yMax };\n};\n\nconst assertUnreachable = (value: never): never => {\n  // Should never happen if SeriesConfig union is exhaustively handled.\n  // This is defensive runtime safety for JS callers / invalid inputs.\n  throw new Error(\n    `Unhandled series type: ${\n      (value as unknown as { readonly type?: unknown } | null)?.type ?? 'unknown'\n    }`\n  );\n};\n\nlet candlestickWarned = false;\nconst warnCandlestickNotImplemented = (): void => {\n  if (!candlestickWarned) {\n    console.warn(\n      'ChartGPU: Candlestick series rendering is not yet implemented. Series will be skipped.'\n    );\n    candlestickWarned = true;\n  }\n};\n\nexport function resolveOptions(userOptions: ChartGPUOptions = {}): ResolvedChartGPUOptions {\n  const baseTheme = resolveTheme(userOptions.theme);\n\n  // runtime safety for JS callers\n  const autoScrollRaw = (userOptions as unknown as { readonly autoScroll?: unknown }).autoScroll;\n  const autoScroll = typeof autoScrollRaw === 'boolean' ? autoScrollRaw : defaultOptions.autoScroll;\n\n  // runtime safety for JS callers\n  const animationRaw = (userOptions as unknown as { readonly animation?: unknown }).animation;\n  const animationCandidate: ChartGPUOptions['animation'] =\n    typeof animationRaw === 'boolean' ||\n    (animationRaw !== null && typeof animationRaw === 'object' && !Array.isArray(animationRaw))\n      ? (animationRaw as ChartGPUOptions['animation'])\n      : undefined;\n  // Default: animation enabled (with defaults) unless explicitly disabled.\n  const animation: ChartGPUOptions['animation'] = animationCandidate ?? true;\n\n  // Backward compatibility:\n  // - If `userOptions.palette` is provided (non-empty), treat it as an override for the theme palette.\n  const paletteOverride = sanitizePalette(userOptions.palette);\n\n  const themeCandidate: ThemeConfig =\n    paletteOverride.length > 0 ? { ...baseTheme, colorPalette: paletteOverride } : baseTheme;\n\n  // Ensure palette used for modulo indexing is never empty.\n  const paletteFromTheme = sanitizePalette(themeCandidate.colorPalette);\n  const safePalette =\n    paletteFromTheme.length > 0\n      ? paletteFromTheme\n      : sanitizePalette(defaultOptions.palette ?? defaultPalette).length > 0\n        ? sanitizePalette(defaultOptions.palette ?? defaultPalette)\n        : Array.from(defaultPalette);\n\n  const paletteForIndexing = safePalette.length > 0 ? safePalette : ['#000000'];\n  const theme: ThemeConfig = { ...themeCandidate, colorPalette: paletteForIndexing.slice() };\n\n  const grid: ResolvedGridConfig = {\n    left: userOptions.grid?.left ?? defaultOptions.grid.left,\n    right: userOptions.grid?.right ?? defaultOptions.grid.right,\n    top: userOptions.grid?.top ?? defaultOptions.grid.top,\n    bottom: userOptions.grid?.bottom ?? defaultOptions.grid.bottom,\n  };\n\n  // Resolve grid lines configuration with color hierarchy:\n  // 1. per-direction color (horizontal.color / vertical.color)\n  // 2. gridLines.color\n  // 3. theme.gridLineColor\n  const resolveGridLines = (\n    input: GridLinesConfig | undefined,\n    theme: ThemeConfig\n  ): ResolvedGridLinesConfig => {\n    const globalShow = input?.show !== false; // default true\n    const globalBaseColor = normalizeOptionalColor(input?.color) ?? theme.gridLineColor;\n    const globalOpacity =\n      typeof input?.opacity === 'number' && Number.isFinite(input.opacity)\n        ? Math.min(1, Math.max(0, input.opacity))\n        : 1;\n\n    // Apply opacity multiplier to a CSS color string (best-effort).\n    const applyOpacity = (color: string, opacity: number): string => {\n      if (opacity === 1) return color;\n      // Simple approach: parse and modify alpha channel\n      const rgba = parseCssColorToRgba01(color);\n      if (!rgba) return color;\n      return `rgba(${Math.round(rgba[0] * 255)}, ${Math.round(rgba[1] * 255)}, ${Math.round(rgba[2] * 255)}, ${rgba[3] * opacity})`;\n    };\n\n    const resolvedGlobalColor = applyOpacity(globalBaseColor, globalOpacity);\n\n    const resolveDirection = (\n      direction: boolean | GridLinesDirectionConfig | undefined,\n      defaultCount: number\n    ): ResolvedGridLinesDirectionConfig => {\n      // Boolean shorthand: false = hide, true/undefined = show with defaults\n      if (direction === false) {\n        return { show: false, count: 0, color: resolvedGlobalColor };\n      }\n      if (direction === true || direction === undefined) {\n        return { show: globalShow, count: defaultCount, color: resolvedGlobalColor };\n      }\n      // Object config\n      const directionShow = direction.show !== false && globalShow; // respect global show\n      const directionCount =\n        typeof direction.count === 'number' && Number.isFinite(direction.count) && direction.count >= 0\n          ? Math.floor(direction.count)\n          : defaultCount;\n      // Direction colors still receive the global opacity multiplier.\n      const directionColorRaw = normalizeOptionalColor(direction.color);\n      const directionColor = directionColorRaw != null ? applyOpacity(directionColorRaw, globalOpacity) : resolvedGlobalColor;\n      return { show: directionShow, count: directionCount, color: directionColor };\n    };\n\n    return {\n      show: globalShow,\n      color: resolvedGlobalColor,\n      opacity: globalOpacity,\n      horizontal: resolveDirection(input?.horizontal, defaultGridLines.horizontal.count),\n      vertical: resolveDirection(input?.vertical, defaultGridLines.vertical.count),\n    };\n  };\n\n  const gridLines = resolveGridLines(userOptions.gridLines, theme);\n\n  const xAxis: AxisConfig = userOptions.xAxis\n    ? {\n        ...defaultOptions.xAxis,\n        ...userOptions.xAxis,\n        // runtime safety for JS callers\n        type: (userOptions.xAxis as unknown as Partial<AxisConfig>).type ?? defaultOptions.xAxis.type,\n        autoBounds:\n          normalizeAxisAutoBounds((userOptions.xAxis as unknown as { readonly autoBounds?: unknown }).autoBounds) ??\n          (defaultOptions.xAxis as AxisConfig).autoBounds,\n      }\n    : { ...defaultOptions.xAxis };\n\n  const yAxis: AxisConfig = userOptions.yAxis\n    ? {\n        ...defaultOptions.yAxis,\n        ...userOptions.yAxis,\n        // runtime safety for JS callers\n        type: (userOptions.yAxis as unknown as Partial<AxisConfig>).type ?? defaultOptions.yAxis.type,\n        autoBounds:\n          normalizeAxisAutoBounds((userOptions.yAxis as unknown as { readonly autoBounds?: unknown }).autoBounds) ??\n          defaultOptions.yAxis.autoBounds,\n      }\n    : { ...defaultOptions.yAxis };\n\n  const series: ReadonlyArray<ResolvedSeriesConfig> = (userOptions.series ?? []).map((s, i) => {\n    const explicitColor = normalizeOptionalColor(s.color);\n    const inheritedColor = theme.colorPalette[i % theme.colorPalette.length];\n    const color = explicitColor ?? inheritedColor;\n\n    // Ensure visible defaults to true (converts undefined to true, preserves explicit false)\n    const visible = s.visible !== false;\n\n    const sampling: SeriesSampling = normalizeSampling((s as unknown as { sampling?: unknown }).sampling) ?? 'lttb';\n    const samplingThreshold: number =\n      normalizeSamplingThreshold((s as unknown as { samplingThreshold?: unknown }).samplingThreshold) ?? 5000;\n\n    switch (s.type) {\n      case 'area': {\n        // Resolve effective fill color with precedence: areaStyle.color → series.color → palette\n        const areaStyleColor = normalizeOptionalColor(s.areaStyle?.color);\n        const effectiveColor = areaStyleColor ?? explicitColor ?? inheritedColor;\n\n        const areaStyle: ResolvedAreaStyleConfig = {\n          opacity: s.areaStyle?.opacity ?? defaultAreaStyle.opacity,\n          color: effectiveColor,\n        };\n\n        const rawBounds = computeRawBoundsFromCartesianData(s.data) ?? undefined;\n        // Bypass sampling when data contains null gap markers to preserve gap structure\n        const sampledAreaData = hasNullGaps(s.data)\n          ? s.data\n          : sampleSeriesDataPoints(s.data, sampling, samplingThreshold);\n        return {\n          ...s,\n          visible,\n          rawData: s.data,\n          data: sampledAreaData,\n          color: effectiveColor,\n          areaStyle,\n          sampling,\n          samplingThreshold,\n          rawBounds,\n          connectNulls: s.connectNulls ?? false,\n        };\n      }\n      case 'line': {\n        // Resolve effective stroke color with precedence: lineStyle.color → series.color → palette\n        const lineStyleColor = normalizeOptionalColor(s.lineStyle?.color);\n        const effectiveStrokeColor = lineStyleColor ?? explicitColor ?? inheritedColor;\n\n        const lineStyle: ResolvedLineStyleConfig = {\n          width: s.lineStyle?.width ?? defaultLineStyle.width,\n          opacity: s.lineStyle?.opacity ?? defaultLineStyle.opacity,\n          color: effectiveStrokeColor,\n        };\n\n        // Avoid leaking the unresolved (user) areaStyle shape via object spread.\n        const { areaStyle: _userAreaStyle, ...rest } = s;\n        const rawBounds = computeRawBoundsFromCartesianData(s.data) ?? undefined;\n        // Bypass sampling when data contains null gap markers to preserve gap structure\n        const sampledData = hasNullGaps(s.data)\n          ? s.data\n          : sampleSeriesDataPoints(s.data, sampling, samplingThreshold);\n\n        return {\n          ...rest,\n          visible,\n          rawData: s.data,\n          data: sampledData,\n          color: effectiveStrokeColor,\n          lineStyle,\n          ...(s.areaStyle\n            ? {\n                areaStyle: {\n                  opacity: s.areaStyle.opacity ?? defaultAreaStyle.opacity,\n                  // Fill color precedence: areaStyle.color → resolved stroke color\n                  color: normalizeOptionalColor(s.areaStyle.color) ?? effectiveStrokeColor,\n                },\n              }\n            : {}),\n          sampling,\n          samplingThreshold,\n          rawBounds,\n          connectNulls: s.connectNulls ?? false,\n        };\n      }\n      case 'bar': {\n        const rawBounds = computeRawBoundsFromCartesianData(s.data) ?? undefined;\n        return {\n          ...s,\n          visible,\n          rawData: s.data,\n          data: sampleSeriesDataPoints(s.data, sampling, samplingThreshold),\n          color,\n          sampling,\n          samplingThreshold,\n          rawBounds,\n        };\n      }\n      case 'scatter': {\n        const rawBounds = computeRawBoundsFromCartesianData(s.data) ?? undefined;\n        const mode =\n          normalizeScatterMode((s as unknown as { readonly mode?: unknown }).mode) ?? scatterDefaults.mode;\n        const binSize =\n          normalizeDensityBinSize((s as unknown as { readonly binSize?: unknown }).binSize) ?? scatterDefaults.binSize;\n        const densityColormap =\n          normalizeDensityColormap((s as unknown as { readonly densityColormap?: unknown }).densityColormap) ??\n          scatterDefaults.densityColormap;\n        const densityNormalization =\n          normalizeDensityNormalization(\n            (s as unknown as { readonly densityNormalization?: unknown }).densityNormalization\n          ) ?? scatterDefaults.densityNormalization;\n        return {\n          ...s,\n          visible,\n          rawData: s.data,\n          data: sampleSeriesDataPoints(s.data, sampling, samplingThreshold),\n          color,\n          mode,\n          binSize,\n          densityColormap,\n          densityNormalization,\n          sampling,\n          samplingThreshold,\n          rawBounds,\n        };\n      }\n      case 'pie': {\n        // Pie series intentionally do NOT support sampling at runtime.\n        // For JS callers, strip any extra sampling keys so they don't leak through the resolver.\n        const { sampling: _sampling, samplingThreshold: _samplingThreshold, ...rest } = s as PieSeriesConfig & {\n          readonly sampling?: unknown;\n          readonly samplingThreshold?: unknown;\n        };\n\n        const resolvedData: ReadonlyArray<ResolvedPieDataItem> = (s.data ?? []).map((item, itemIndex) => {\n          const itemColor = normalizeOptionalColor(item?.color);\n          const fallback = theme.colorPalette[(i + itemIndex) % theme.colorPalette.length];\n          // Ensure visible defaults to true (converts undefined to true, preserves explicit false)\n          const itemVisible = item?.visible !== false;\n          return {\n            ...item,\n            color: itemColor ?? fallback,\n            visible: itemVisible,\n          };\n        });\n\n        return { ...rest, visible, color, data: resolvedData };\n      }\n      case 'candlestick': {\n        warnCandlestickNotImplemented();\n\n        const resolvedSampling: 'none' | 'ohlc' =\n          normalizeCandlestickSampling((s as unknown as { sampling?: unknown }).sampling) ??\n          candlestickDefaults.sampling;\n\n        const resolvedSamplingThreshold: number =\n          normalizeSamplingThreshold((s as unknown as { samplingThreshold?: unknown }).samplingThreshold) ??\n          candlestickDefaults.samplingThreshold;\n\n        const resolvedItemStyle: ResolvedCandlestickItemStyleConfig = {\n          upColor: normalizeOptionalColor(s.itemStyle?.upColor) ?? candlestickDefaults.itemStyle.upColor,\n          downColor: normalizeOptionalColor(s.itemStyle?.downColor) ?? candlestickDefaults.itemStyle.downColor,\n          upBorderColor: normalizeOptionalColor(s.itemStyle?.upBorderColor) ?? candlestickDefaults.itemStyle.upBorderColor,\n          downBorderColor: normalizeOptionalColor(s.itemStyle?.downBorderColor) ?? candlestickDefaults.itemStyle.downBorderColor,\n          borderWidth: typeof s.itemStyle?.borderWidth === 'number' && Number.isFinite(s.itemStyle.borderWidth)\n            ? s.itemStyle.borderWidth\n            : candlestickDefaults.itemStyle.borderWidth,\n        };\n\n        const rawBounds = computeRawBoundsFromOHLC(s.data);\n\n        const sampledData =\n          resolvedSampling === 'ohlc' && s.data.length > resolvedSamplingThreshold\n            ? ohlcSample(s.data, resolvedSamplingThreshold)\n            : s.data;\n\n        return {\n          ...s,\n          visible,\n          rawData: s.data,\n          data: sampledData,\n          color,\n          style: s.style ?? candlestickDefaults.style,\n          itemStyle: resolvedItemStyle,\n          barWidth: s.barWidth ?? candlestickDefaults.barWidth,\n          barMinWidth: s.barMinWidth ?? candlestickDefaults.barMinWidth,\n          barMaxWidth: s.barMaxWidth ?? candlestickDefaults.barMaxWidth,\n          sampling: resolvedSampling,\n          samplingThreshold: resolvedSamplingThreshold,\n          rawBounds,\n        };\n      }\n      default: {\n        return assertUnreachable(s);\n      }\n    }\n  });\n\n  return {\n    grid,\n    gridLines,\n    xAxis,\n    yAxis,\n    autoScroll,\n    dataZoom: sanitizeDataZoom((userOptions as ChartGPUOptions).dataZoom),\n    annotations: sanitizeAnnotations((userOptions as ChartGPUOptions).annotations),\n    animation,\n    theme,\n    palette: theme.colorPalette,\n    series,\n    legend: userOptions.legend,\n  };\n}\n\n/**\n * Data zoom slider dimensions (CSS pixels).\n *\n * Note: these are internal implementation details used to reserve chart space for the\n * slider overlay. We intentionally do not re-export them from the public entrypoint.\n */\nconst DATA_ZOOM_SLIDER_HEIGHT_CSS_PX = 32;\nconst DATA_ZOOM_SLIDER_MARGIN_TOP_CSS_PX = 8;\nconst DATA_ZOOM_SLIDER_RESERVE_CSS_PX =\n  DATA_ZOOM_SLIDER_HEIGHT_CSS_PX + DATA_ZOOM_SLIDER_MARGIN_TOP_CSS_PX;\n\n/**\n * Checks if options include a slider-type dataZoom configuration.\n * \n * @param options - Chart options to check\n * @returns True if slider dataZoom exists\n */\nconst hasSliderDataZoom = (options: ChartGPUOptions): boolean =>\n  options.dataZoom?.some((z) => z?.type === 'slider') ?? false;\n\n/**\n * Resolves chart options with slider bottom-space reservation.\n * \n * This function wraps `resolveOptions()` and applies additional grid bottom spacing\n * when a slider-type dataZoom is configured. The reservation ensures x-axis labels\n * and ticks are visible above the slider overlay.\n * \n * **Usage**: Use this function instead of `resolveOptions()` when creating charts\n * to ensure consistent slider layout.\n * \n * @param userOptions - User-provided chart options\n * @returns Resolved options with slider bottom-space applied if needed\n */\nexport function resolveOptionsForChart(userOptions: ChartGPUOptions = {}): ResolvedChartGPUOptions {\n  const base: ResolvedChartGPUOptions = { ...resolveOptions(userOptions), tooltip: userOptions.tooltip };\n  if (!hasSliderDataZoom(userOptions)) return base;\n  return {\n    ...base,\n    grid: {\n      ...base.grid,\n      bottom: base.grid.bottom + DATA_ZOOM_SLIDER_RESERVE_CSS_PX,\n    },\n  };\n}\n\nexport const OptionResolver = { resolve: resolveOptions } as const;\n\n","import type { ZoomRange, ZoomState } from '../interaction/createZoomState';\nimport type { ThemeConfig } from '../themes/types';\n\nexport interface DataZoomSlider {\n  update(theme: ThemeConfig): void;\n  dispose(): void;\n}\n\nexport interface DataZoomSliderOptions {\n  readonly height?: number;\n  readonly marginTop?: number;\n  readonly zIndex?: number;\n  readonly showPreview?: boolean;\n}\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));\n\nconst normalizeRange = (range: ZoomRange): ZoomRange => {\n  let { start, end } = range;\n  if (start > end) {\n    const t = start;\n    start = end;\n    end = t;\n  }\n  return { start: clamp(start, 0, 100), end: clamp(end, 0, 100) };\n};\n\ntype DragMode = 'left-handle' | 'right-handle' | 'pan-window';\n\nexport function createDataZoomSlider(\n  container: HTMLElement,\n  zoomState: ZoomState,\n  options?: DataZoomSliderOptions\n): DataZoomSlider {\n  const height = options?.height ?? 32;\n  const marginTop = options?.marginTop ?? 8;\n  const zIndex = options?.zIndex ?? 4;\n  const showPreview = options?.showPreview ?? false;\n\n  const root = document.createElement('div');\n  root.style.display = 'block';\n  root.style.width = '100%';\n  root.style.height = `${height}px`;\n  root.style.marginTop = `${marginTop}px`;\n  root.style.boxSizing = 'border-box';\n  root.style.position = 'relative';\n  root.style.zIndex = `${zIndex}`;\n  root.style.userSelect = 'none';\n  root.style.touchAction = 'none';\n\n  // track: full-width bar that hosts preview + window selection.\n  const track = document.createElement('div');\n  track.style.position = 'relative';\n  track.style.height = '100%';\n  track.style.width = '100%';\n  track.style.boxSizing = 'border-box';\n  track.style.borderRadius = '8px';\n  track.style.borderStyle = 'solid';\n  track.style.borderWidth = '1px';\n  track.style.overflow = 'hidden';\n  root.appendChild(track);\n\n  // preview: miniature context under the selection (optional; can be a solid bar for now).\n  const preview = document.createElement('div');\n  preview.style.position = 'absolute';\n  preview.style.inset = '0';\n  preview.style.pointerEvents = 'none';\n  preview.style.opacity = '0.4';\n  preview.style.display = showPreview ? 'block' : 'none';\n  track.appendChild(preview);\n\n  // window: the selected range.\n  const windowEl = document.createElement('div');\n  windowEl.style.position = 'absolute';\n  windowEl.style.top = '0';\n  windowEl.style.bottom = '0';\n  windowEl.style.left = '0%';\n  windowEl.style.width = '100%';\n  windowEl.style.boxSizing = 'border-box';\n  windowEl.style.cursor = 'grab';\n  track.appendChild(windowEl);\n\n  // left/right handles.\n  const leftHandle = document.createElement('div');\n  leftHandle.style.position = 'absolute';\n  leftHandle.style.left = '0';\n  leftHandle.style.top = '0';\n  leftHandle.style.bottom = '0';\n  leftHandle.style.width = '10px';\n  leftHandle.style.cursor = 'ew-resize';\n  windowEl.appendChild(leftHandle);\n\n  const rightHandle = document.createElement('div');\n  rightHandle.style.position = 'absolute';\n  rightHandle.style.right = '0';\n  rightHandle.style.top = '0';\n  rightHandle.style.bottom = '0';\n  rightHandle.style.width = '10px';\n  rightHandle.style.cursor = 'ew-resize';\n  windowEl.appendChild(rightHandle);\n\n  // center grip (hit target for panning).\n  const centerGrip = document.createElement('div');\n  centerGrip.style.position = 'absolute';\n  centerGrip.style.left = '10px';\n  centerGrip.style.right = '10px';\n  centerGrip.style.top = '0';\n  centerGrip.style.bottom = '0';\n  centerGrip.style.cursor = 'grab';\n  windowEl.appendChild(centerGrip);\n\n  container.appendChild(root);\n\n  let disposed = false;\n  let activeDragCleanup: (() => void) | null = null;\n\n  const applyRangeToDom = (range: ZoomRange): void => {\n    const r = normalizeRange(range);\n    const span = clamp(r.end - r.start, 0, 100);\n    windowEl.style.left = `${r.start}%`;\n    windowEl.style.width = `${span}%`;\n  };\n\n  const getTrackWidthPx = (): number | null => {\n    // getBoundingClientRect() is robust even if the container is scaled.\n    const w = track.getBoundingClientRect().width;\n    return Number.isFinite(w) && w > 0 ? w : null;\n  };\n\n  const pxToPercent = (dxPx: number): number | null => {\n    const w = getTrackWidthPx();\n    if (w === null) return null;\n    const p = (dxPx / w) * 100;\n    return Number.isFinite(p) ? p : null;\n  };\n\n  const setPointerCaptureBestEffort = (el: Element, pointerId: number): void => {\n    try {\n      (el as HTMLElement).setPointerCapture(pointerId);\n    } catch {\n      // Ignore (best-effort).\n    }\n  };\n\n  const releasePointerCaptureBestEffort = (el: Element, pointerId: number): void => {\n    try {\n      (el as HTMLElement).releasePointerCapture(pointerId);\n    } catch {\n      // Ignore (best-effort).\n    }\n  };\n\n  const startDrag = (e: PointerEvent, mode: DragMode): void => {\n    if (disposed) return;\n    if (e.button !== 0) return;\n\n    e.preventDefault();\n\n    // If we somehow start a new drag while another is in-flight, clean up first.\n    activeDragCleanup?.();\n    activeDragCleanup = null;\n\n    const dragStartX = e.clientX;\n    const startRange = zoomState.getRange();\n\n    const target = e.currentTarget instanceof Element ? e.currentTarget : windowEl;\n    setPointerCaptureBestEffort(target, e.pointerId);\n\n    if (mode === 'pan-window') {\n      windowEl.style.cursor = 'grabbing';\n      centerGrip.style.cursor = 'grabbing';\n    }\n\n    const onMove = (ev: PointerEvent): void => {\n      if (disposed) return;\n      if (ev.pointerId !== e.pointerId) return;\n\n      ev.preventDefault();\n\n      const dxPercent = pxToPercent(ev.clientX - dragStartX);\n      if (dxPercent === null) return;\n\n      switch (mode) {\n        case 'left-handle': {\n          // UX: don't allow handle crossing; clamp left <= current end.\n          const nextStart = Math.min(startRange.end, startRange.start + dxPercent);\n          const anchored = zoomState as unknown as Partial<{\n            setRangeAnchored: (start: number, end: number, anchor: 'start' | 'end' | 'center') => void;\n          }>;\n          if (anchored.setRangeAnchored) {\n            // When clamped by minSpan/maxSpan, keep the right edge anchored (prevents jumpiness).\n            anchored.setRangeAnchored(nextStart, startRange.end, 'end');\n          } else {\n            zoomState.setRange(nextStart, startRange.end);\n          }\n          return;\n        }\n        case 'right-handle': {\n          // UX: don't allow handle crossing; clamp right >= current start.\n          const nextEnd = Math.max(startRange.start, startRange.end + dxPercent);\n          const anchored = zoomState as unknown as Partial<{\n            setRangeAnchored: (start: number, end: number, anchor: 'start' | 'end' | 'center') => void;\n          }>;\n          if (anchored.setRangeAnchored) {\n            // When clamped by minSpan/maxSpan, keep the left edge anchored (prevents jumpiness).\n            anchored.setRangeAnchored(startRange.start, nextEnd, 'start');\n          } else {\n            zoomState.setRange(startRange.start, nextEnd);\n          }\n          return;\n        }\n        case 'pan-window': {\n          zoomState.setRange(startRange.start + dxPercent, startRange.end + dxPercent);\n          return;\n        }\n      }\n    };\n\n    let cleanedUp = false;\n\n    const cleanup = (): void => {\n      if (cleanedUp) return;\n      cleanedUp = true;\n\n      window.removeEventListener('pointermove', onMove);\n      window.removeEventListener('pointerup', finish);\n      window.removeEventListener('pointercancel', finish);\n\n      if (mode === 'pan-window') {\n        windowEl.style.cursor = 'grab';\n        centerGrip.style.cursor = 'grab';\n      }\n\n      releasePointerCaptureBestEffort(target, e.pointerId);\n\n      // Only clear if we're still the active drag.\n      if (activeDragCleanup === cleanup) activeDragCleanup = null;\n    };\n\n    const finish = (ev: PointerEvent): void => {\n      if (ev.pointerId !== e.pointerId) return;\n      cleanup();\n    };\n\n    activeDragCleanup = cleanup;\n\n    window.addEventListener('pointermove', onMove, { passive: false });\n    window.addEventListener('pointerup', finish, { passive: true });\n    window.addEventListener('pointercancel', finish, { passive: true });\n  };\n\n  const onLeftDown = (e: PointerEvent): void => startDrag(e, 'left-handle');\n  const onRightDown = (e: PointerEvent): void => startDrag(e, 'right-handle');\n  const onPanDown = (e: PointerEvent): void => startDrag(e, 'pan-window');\n\n  leftHandle.addEventListener('pointerdown', onLeftDown, { passive: false });\n  rightHandle.addEventListener('pointerdown', onRightDown, { passive: false });\n  centerGrip.addEventListener('pointerdown', onPanDown, { passive: false });\n\n  // Keep DOM in sync with state.\n  const unsubscribe = zoomState.onChange((range) => {\n    if (disposed) return;\n    applyRangeToDom(range);\n  });\n\n  // Initialize UI.\n  applyRangeToDom(zoomState.getRange());\n\n  const update: DataZoomSlider['update'] = (theme) => {\n    if (disposed) return;\n\n    // Baseline track styling.\n    track.style.background = theme.backgroundColor;\n    track.style.borderColor = theme.axisLineColor;\n\n    // Preview styling (placeholder).\n    preview.style.background = theme.gridLineColor;\n\n    // Window styling.\n    windowEl.style.background = theme.gridLineColor;\n    windowEl.style.border = `1px solid ${theme.axisTickColor}`;\n    windowEl.style.borderRadius = '8px';\n    windowEl.style.boxSizing = 'border-box';\n\n    // Handles styling.\n    const handleBorder = `1px solid ${theme.axisLineColor}`;\n    leftHandle.style.background = theme.axisTickColor;\n    leftHandle.style.borderRight = handleBorder;\n    rightHandle.style.background = theme.axisTickColor;\n    rightHandle.style.borderLeft = handleBorder;\n\n    // Center grip styling: subtle stripes.\n    centerGrip.style.background = 'transparent';\n    centerGrip.style.backgroundImage =\n      'linear-gradient(90deg, rgba(255,255,255,0.0) 0, rgba(255,255,255,0.0) 42%, rgba(255,255,255,0.18) 42%, rgba(255,255,255,0.18) 46%, rgba(255,255,255,0.0) 46%, rgba(255,255,255,0.0) 54%, rgba(255,255,255,0.18) 54%, rgba(255,255,255,0.18) 58%, rgba(255,255,255,0.0) 58%, rgba(255,255,255,0.0) 100%)';\n    centerGrip.style.mixBlendMode = 'normal';\n  };\n\n  const dispose: DataZoomSlider['dispose'] = () => {\n    if (disposed) return;\n    disposed = true;\n\n    // If dispose happens during an active drag, ensure we remove all window listeners.\n    activeDragCleanup?.();\n    activeDragCleanup = null;\n\n    try {\n      unsubscribe();\n    } catch {\n      // Best-effort.\n    }\n\n    leftHandle.removeEventListener('pointerdown', onLeftDown);\n    rightHandle.removeEventListener('pointerdown', onRightDown);\n    centerGrip.removeEventListener('pointerdown', onPanDown);\n\n    root.remove();\n  };\n\n  return { update, dispose };\n}\n\n","import type { ZoomState } from '../interaction/createZoomState';\nimport type { ThemeConfig } from '../themes/types';\n\nexport interface ZoomResetButton {\n  update(theme: ThemeConfig): void;\n  dispose(): void;\n}\n\n/** Tolerance for considering zoom at full range (avoids float imprecision flicker). */\nconst FULL_RANGE_EPSILON = 0.01;\nconst isFullRange = (start: number, end: number): boolean =>\n  start <= FULL_RANGE_EPSILON && end >= 100 - FULL_RANGE_EPSILON;\n\nconst isTouchDevice = (): boolean =>\n  typeof navigator !== 'undefined' && navigator.maxTouchPoints > 0;\n\nexport function createZoomResetButton(\n  container: HTMLElement,\n  zoomState: ZoomState,\n  theme: ThemeConfig,\n): ZoomResetButton {\n  let disposed = false;\n  const touchCapable = isTouchDevice();\n\n  const el = document.createElement('button');\n  el.setAttribute('data-chartgpu-zoom-reset', '');\n  el.setAttribute('aria-label', 'Reset zoom');\n  el.type = 'button';\n\n  // Styling\n  el.style.position = 'absolute';\n  el.style.top = '8px';\n  el.style.right = '8px';\n  el.style.zIndex = '10';\n  el.style.width = '32px';\n  el.style.height = '32px';\n  el.style.border = 'none';\n  el.style.borderRadius = '6px';\n  el.style.cursor = 'pointer';\n  el.style.display = 'none';\n  el.style.alignItems = 'center';\n  el.style.justifyContent = 'center';\n  el.style.fontSize = '16px';\n  el.style.lineHeight = '1';\n  el.style.padding = '0';\n  el.style.touchAction = 'manipulation';\n  el.textContent = '\\u21BA'; // reset arrow\n\n  const applyTheme = (t: ThemeConfig): void => {\n    // Use the theme background with reduced opacity. Setting opacity on the element\n    // keeps the approach format-agnostic (works with hex, rgb, hsl, etc.).\n    el.style.backgroundColor = t.backgroundColor;\n    el.style.opacity = '0.8';\n    el.style.color = t.textColor;\n  };\n  applyTheme(theme);\n\n  const updateVisibility = (): void => {\n    if (!touchCapable) {\n      el.style.display = 'none';\n      return;\n    }\n    const { start, end } = zoomState.getRange();\n    el.style.display = isFullRange(start, end) ? 'none' : 'flex';\n  };\n  updateVisibility();\n\n  const onClick = (): void => {\n    if (disposed) return;\n    zoomState.setRange(0, 100);\n  };\n\n  el.addEventListener('click', onClick);\n\n  const unsubscribe = zoomState.onChange(() => {\n    if (disposed) return;\n    updateVisibility();\n  });\n\n  container.appendChild(el);\n\n  return {\n    update(t: ThemeConfig): void {\n      if (disposed) return;\n      applyTheme(t);\n    },\n    dispose(): void {\n      if (disposed) return;\n      disposed = true;\n      el.removeEventListener('click', onClick);\n      try { unsubscribe(); } catch { /* best-effort */ }\n      el.remove();\n    },\n  };\n}\n","/**\n * WebGPU support detection and validation\n * \n * Provides utilities to check if WebGPU is available and usable in the current environment.\n * Results are memoized to avoid redundant checks.\n */\n\n/**\n * Result of WebGPU support check\n */\nexport interface WebGPUSupportResult {\n  /** Whether WebGPU is supported and available */\n  readonly supported: boolean;\n  /** Optional reason explaining why WebGPU is not supported */\n  readonly reason?: string;\n}\n\n// Memoized support check result\nlet cachedSupportCheck: Promise<WebGPUSupportResult> | null = null;\n\n/**\n * Checks if WebGPU is supported and available in the current environment.\n * \n * This function performs comprehensive checks:\n * - SSR-safe: validates that window and navigator are available\n * - Checks for navigator.gpu API presence\n * - Attempts to request a WebGPU adapter to verify actual support\n * - First tries high-performance adapter to match GPUContext behavior\n * - Falls back to default adapter if high-performance fails\n * \n * The result is memoized for performance, so multiple calls return the same promise.\n * \n * @returns Promise resolving to support check result with optional reason\n * \n * @example\n * ```typescript\n * const { supported, reason } = await checkWebGPUSupport();\n * if (!supported) {\n *   console.error('WebGPU not available:', reason);\n * }\n * ```\n */\nexport async function checkWebGPUSupport(): Promise<WebGPUSupportResult> {\n  // Return cached result if available\n  if (cachedSupportCheck) {\n    return cachedSupportCheck;\n  }\n\n  // Create and cache the promise\n  cachedSupportCheck = (async (): Promise<WebGPUSupportResult> => {\n    // SSR-safe checks: ensure we're in a browser environment\n    if (typeof window === 'undefined') {\n      return {\n        supported: false,\n        reason: 'Not running in a browser environment (window is undefined).',\n      };\n    }\n\n    if (typeof navigator === 'undefined') {\n      return {\n        supported: false,\n        reason: 'Navigator is not available in this environment.',\n      };\n    }\n\n    // Check for navigator.gpu API\n    if (!navigator.gpu) {\n      return {\n        supported: false,\n        reason: 'WebGPU API (navigator.gpu) is not available. Your browser does not support WebGPU.',\n      };\n    }\n\n    // Attempt to request an adapter to verify actual support\n    try {\n      // First attempt: high-performance adapter (aligns with GPUContext behavior)\n      let adapter = await navigator.gpu.requestAdapter({\n        powerPreference: 'high-performance',\n      });\n\n      // Second attempt: default adapter if high-performance is unavailable\n      if (!adapter) {\n        adapter = await navigator.gpu.requestAdapter();\n      }\n\n      // If both attempts fail, WebGPU is not usable\n      if (!adapter) {\n        return {\n          supported: false,\n          reason: 'No compatible WebGPU adapter found. This may occur if: (1) no GPU is available, (2) GPU drivers are outdated or incompatible, (3) running in a VM or headless environment, or (4) WebGPU is disabled in browser settings.',\n        };\n      }\n\n      // Success: WebGPU is supported and an adapter is available\n      return { supported: true };\n    } catch (error) {\n      // Adapter request threw an error\n      let reason = 'Failed to request WebGPU adapter.';\n\n      // Try to extract useful error information\n      if (error instanceof DOMException) {\n        reason = `Failed to request WebGPU adapter: ${error.name}`;\n        if (error.message) {\n          reason += ` - ${error.message}`;\n        }\n      } else if (error instanceof Error) {\n        reason = `Failed to request WebGPU adapter: ${error.message}`;\n      } else {\n        reason = `Failed to request WebGPU adapter: ${String(error)}`;\n      }\n\n      return { supported: false, reason };\n    }\n  })();\n\n  return cachedSupportCheck;\n}\n","import { GPUContext } from './core/GPUContext';\nimport { createRenderCoordinator } from './core/createRenderCoordinator';\nimport type { RenderCoordinator, RenderCoordinatorCallbacks } from './core/createRenderCoordinator';\nimport { resolveOptionsForChart } from './config/OptionResolver';\nimport type { ResolvedCandlestickSeriesConfig, ResolvedChartGPUOptions, ResolvedPieSeriesConfig } from './config/OptionResolver';\nimport type {\n  CartesianSeriesData,\n  ChartGPUOptions,\n  DataPoint,\n  DataPointTuple,\n  OHLCDataPoint,\n  OHLCDataPointTuple,\n  PieCenter,\n  PieRadius,\n  RenderMode,\n} from './config/types';\nimport { createDataZoomSlider } from './components/createDataZoomSlider';\nimport type { DataZoomSlider } from './components/createDataZoomSlider';\nimport { createZoomResetButton } from './components/createZoomResetButton';\nimport type { ZoomResetButton } from './components/createZoomResetButton';\nimport type { ZoomRange, ZoomState } from './interaction/createZoomState';\nimport { computeCandlestickBodyWidthRange, findCandlestick } from './interaction/findCandlestick';\nimport { findNearestPoint } from './interaction/findNearestPoint';\nimport type { NearestPointMatch } from './interaction/findNearestPoint';\nimport { findPieSlice } from './interaction/findPieSlice';\nimport { createLinearScale } from './utils/scales';\nimport type { LinearScale } from './utils/scales';\nimport { checkWebGPUSupport } from './utils/checkWebGPU';\nimport type { PipelineCache } from './core/PipelineCache';\nexport type { PipelineCache, PipelineCacheStats } from './core/PipelineCache';\nimport type {\n  PerformanceMetrics,\n  PerformanceCapabilities,\n  ExactFPS,\n  Milliseconds,\n  Bytes,\n  FrameTimeStats,\n  GPUTimingStats,\n  MemoryStats,\n  FrameDropStats,\n} from './config/types';\nimport {\n  computeRawBoundsFromCartesianData,\n  getPointCount as getCartesianPointCount,\n  getSize as getCartesianSize,\n  getX as getCartesianX,\n  getY as getCartesianY,\n} from './data/cartesianData';\n\n/**\n * Circular buffer size for frame timestamps (120 frames = 2 seconds at 60fps).\n */\nconst FRAME_BUFFER_SIZE = 120;\n\n/**\n * Expected frame time at 60fps (16.67ms).\n */\nconst EXPECTED_FRAME_TIME_MS = 1000 / 60;\n\n/**\n * Frame drop threshold multiplier (1.5x expected frame time).\n */\nconst FRAME_DROP_THRESHOLD_MULTIPLIER = 1.5;\n\n/**\n * Source kind for zoom range changes.\n *\n * Used to distinguish zoom change sources:\n * - `'user'`: Direct user interaction (pan, pinch, wheel, slider)\n * - `'auto-scroll'`: Automatic zoom adjustment from streaming data with auto-scroll enabled\n * - `'api'`: Programmatic zoom via `setZoomRange(..., source)` calls\n */\nexport type ZoomChangeSourceKind = 'user' | 'auto-scroll' | 'api';\n\n/**\n * Hit-test match for a chart element.\n */\nexport type ChartGPUHitTestMatch = Readonly<{\n  readonly kind: 'cartesian' | 'candlestick' | 'pie';\n  readonly seriesIndex: number;\n  readonly dataIndex: number;\n  readonly value: readonly [number, number];\n}>;\n\n/**\n * Result of a hit-test operation on a chart.\n */\nexport type ChartGPUHitTestResult = Readonly<{\n  readonly isInGrid: boolean;\n  readonly canvasX: number;\n  readonly canvasY: number;\n  readonly gridX: number;\n  readonly gridY: number;\n  readonly match: ChartGPUHitTestMatch | null;\n}>;\n\nexport interface ChartGPUInstance {\n  readonly options: Readonly<ChartGPUOptions>;\n  readonly disposed: boolean;\n  setOption(options: ChartGPUOptions): void;\n  /**\n   * Appends new points to a cartesian series at runtime (streaming).\n   *\n   * Accepts multiple formats for efficient data append without per-point object allocations:\n   * - `DataPoint[]`: Traditional array of point objects/tuples (existing behavior)\n   * - `XYArraysData`: Separate x/y/size arrays (`{x: ArrayLike<number>, y: ArrayLike<number>, size?: ArrayLike<number>}`)\n   * - `InterleavedXYData`: Typed array with [x0,y0,x1,y1,...] layout (e.g. `Float32Array`)\n   * - `OHLCDataPoint[]`: For candlestick series only\n   *\n   * Point count is derived via `getPointCount()` from `cartesianData.ts`:\n   * - `XYArraysData`: min(x.length, y.length)\n   * - `InterleavedXYData`: floor(length / 2), ignoring trailing odd element\n   * - `DataView` is unsupported and throws an error\n   *\n   * Pie series are non-cartesian and are not supported by streaming append.\n   */\n  appendData(seriesIndex: number, newPoints: CartesianSeriesData | OHLCDataPoint[]): void;\n  resize(): void;\n  dispose(): void;\n  on(eventName: 'crosshairMove', callback: ChartGPUCrosshairMoveCallback): void;\n  on(eventName: 'zoomRangeChange', callback: ChartGPUZoomRangeChangeCallback): void;\n  on(eventName: 'deviceLost', callback: ChartGPUDeviceLostCallback): void;\n  on(eventName: 'dataAppend', callback: ChartGPUDataAppendCallback): void;\n  on(eventName: ChartGPUEventName, callback: ChartGPUEventCallback): void;\n  off(eventName: 'crosshairMove', callback: ChartGPUCrosshairMoveCallback): void;\n  off(eventName: 'zoomRangeChange', callback: ChartGPUZoomRangeChangeCallback): void;\n  off(eventName: 'deviceLost', callback: ChartGPUDeviceLostCallback): void;\n  off(eventName: 'dataAppend', callback: ChartGPUDataAppendCallback): void;\n  off(eventName: ChartGPUEventName, callback: ChartGPUEventCallback): void;\n  /**\n   * Gets the current “interaction x” in domain units (or `null` when inactive).\n   *\n   * This is derived from pointer movement inside the plot grid and can also be driven\n   * externally via `setInteractionX(...)` (e.g. chart sync).\n   */\n  getInteractionX(): number | null;\n  /**\n   * Drives the chart’s crosshair + tooltip from a domain-space x value.\n   *\n   * Passing `null` clears the interaction (hides crosshair/tooltip).\n   */\n  setInteractionX(x: number | null, source?: unknown): void;\n  /**\n   * Alias for `setInteractionX(...)` for chart sync semantics.\n   */\n  setCrosshairX(x: number | null, source?: unknown): void;\n  /**\n   * Subscribes to interaction x changes (domain units).\n   *\n   * Returns an unsubscribe function.\n   */\n  onInteractionXChange(callback: (x: number | null, source?: unknown) => void): () => void;\n  /**\n   * Returns the current percent-space zoom window (or `null` when zoom is disabled).\n   */\n  getZoomRange(): Readonly<{ start: number; end: number }> | null;\n  /**\n   * Sets the percent-space zoom window.\n   *\n   * No-op when zoom is disabled.\n   */\n  setZoomRange(start: number, end: number, source?: unknown): void;\n  /**\n   * Gets the latest performance metrics.\n   * Returns exact FPS and detailed frame statistics.\n   * \n   * @returns Current performance metrics, or null if not available\n   */\n  getPerformanceMetrics(): Readonly<PerformanceMetrics> | null;\n  /**\n   * Gets the performance capabilities of the current environment.\n   * Indicates which performance features are supported.\n   * \n   * @returns Performance capabilities, or null if not initialized\n   */\n  getPerformanceCapabilities(): Readonly<PerformanceCapabilities> | null;\n  /**\n   * Registers a callback to be notified of performance metric updates.\n   * Callback is invoked every frame with the latest metrics.\n   * \n   * @param callback - Function to call with updated metrics\n   * @returns Unsubscribe function to remove the callback\n   */\n  onPerformanceUpdate(callback: (metrics: Readonly<PerformanceMetrics>) => void): () => void;\n  /**\n   * Performs hit-testing on a pointer or mouse event.\n   *\n   * Returns coordinates and matched chart element (if any).\n   * Accepts both `PointerEvent` (for hover/click) and `MouseEvent` (for contextmenu/right-click).\n   *\n   * @param e - Pointer or mouse event to test\n   * @returns Hit-test result with coordinates and optional match\n   */\n  hitTest(e: PointerEvent | MouseEvent): ChartGPUHitTestResult;\n  /**\n   * Gets the current render mode ('auto' | 'external').\n   */\n  getRenderMode(): RenderMode;\n  /**\n   * Sets the render mode. In 'auto' mode, ChartGPU schedules renders automatically.\n   * In 'external' mode, the application must call renderFrame() on each frame.\n   */\n  setRenderMode(mode: RenderMode): void;\n  /**\n   * Renders a single frame (external mode only).\n   * \n   * In 'auto' mode, this is a no-op and logs a warning in development.\n   * In 'external' mode, executes a render if the chart is dirty.\n   * \n   * @returns true if a frame was rendered, false if the chart was already clean\n   */\n  renderFrame(): boolean;\n  /**\n   * Checks if the chart needs rendering (has pending changes).\n   * \n   * @returns true if the chart is dirty and needs a render\n   */\n  needsRender(): boolean;\n}\n\n// Type-only alias so callsites can write `ChartGPU[]` for chart instances (while `ChartGPU` the value\n// remains the creation API exported from `src/index.ts`).\nexport type ChartGPU = ChartGPUInstance;\n\nexport type ChartGPUEventName = 'click' | 'mouseover' | 'mouseout' | 'crosshairMove' | 'zoomRangeChange' | 'deviceLost' | 'dataAppend';\n\nexport type ChartGPUEventPayload = Readonly<{\n  readonly seriesIndex: number | null;\n  readonly dataIndex: number | null;\n  readonly value: readonly [number, number] | null;\n  readonly seriesName: string | null;\n  readonly event: PointerEvent;\n}>;\n\nexport type ChartGPUDeviceLostPayload = Readonly<{\n  readonly reason: GPUDeviceLostReason;\n  readonly message: string;\n}>;\n\nexport type ChartGPUCrosshairMovePayload = Readonly<{\n  readonly x: number | null;\n  readonly source?: unknown;\n}>;\n\nexport type ChartGPUZoomRangeChangePayload = Readonly<{\n  readonly start: number;\n  readonly end: number;\n  readonly source?: unknown;\n  readonly sourceKind?: ZoomChangeSourceKind;\n}>;\n\nexport type ChartGPUDataAppendPayload = Readonly<{\n  readonly seriesIndex: number;\n  readonly count: number;\n  readonly xExtent: { readonly min: number; readonly max: number };\n}>;\n\nexport type ChartGPUEventCallback = (payload: ChartGPUEventPayload) => void;\n\nexport type ChartGPUCrosshairMoveCallback = (payload: ChartGPUCrosshairMovePayload) => void;\n\nexport type ChartGPUZoomRangeChangeCallback = (payload: ChartGPUZoomRangeChangePayload) => void;\n\nexport type ChartGPUDeviceLostCallback = (payload: ChartGPUDeviceLostPayload) => void;\n\nexport type ChartGPUDataAppendCallback = (payload: ChartGPUDataAppendPayload) => void;\n\ntype AnyChartGPUEventCallback = ChartGPUEventCallback | ChartGPUCrosshairMoveCallback | ChartGPUZoomRangeChangeCallback | ChartGPUDeviceLostCallback | ChartGPUDataAppendCallback;\n\ntype ListenerRegistry = Readonly<Record<ChartGPUEventName, Set<AnyChartGPUEventCallback>>>;\n\n// Pipeline cache types are defined in `src/core/PipelineCache.ts` and re-exported near the top\n// of this file so public API uses a single canonical definition.\n\n/**\n * Context for creating a ChartGPU instance with shared WebGPU device and adapter.\n * Use this to share a single GPU device across multiple chart instances for improved resource efficiency.\n * \n * Optionally provide a `pipelineCache` to share compiled pipelines across charts, reducing\n * shader compilation overhead during initialization.\n */\nexport type ChartGPUCreateContext = Readonly<{\n  readonly device: GPUDevice;\n  readonly adapter: GPUAdapter;\n  /**\n   * Optional pipeline cache for sharing compiled pipelines across charts.\n   * Must be created for the same GPUDevice as the context.\n   * \n   * @example\n   * ```ts\n   * const cache = createPipelineCache(device);\n   * const chart1 = await ChartGPU.create(container1, options, { adapter, device, pipelineCache: cache });\n   * const chart2 = await ChartGPU.create(container2, options, { adapter, device, pipelineCache: cache });\n   * ```\n   */\n  readonly pipelineCache?: PipelineCache;\n}>;\n\ntype TapCandidate = {\n  readonly pointerId: number;\n  readonly startClientX: number;\n  readonly startClientY: number;\n  readonly startTimeMs: number;\n};\n\nconst DEFAULT_TAP_MAX_DISTANCE_CSS_PX = 6;\nconst DEFAULT_TAP_MAX_TIME_MS = 500;\n\ntype Bounds = Readonly<{ xMin: number; xMax: number; yMin: number; yMax: number }>;\n\n/**\n * Mutable columnar store for cartesian series data in the hit-test runtime.\n * Supports efficient append without per-point DataPoint object allocations.\n * \n * Note: size array is aligned with x/y arrays (same length), with undefined for points without size values.\n */\ntype MutableXYColumns = {\n  x: number[];\n  y: number[];\n  size?: (number | undefined)[];\n};\n\nconst isTupleDataPoint = (p: DataPoint): p is DataPointTuple => Array.isArray(p);\nconst isTupleOHLCDataPoint = (p: OHLCDataPoint): p is OHLCDataPointTuple => Array.isArray(p);\n\nconst getPointXY = (p: DataPoint): { readonly x: number; readonly y: number } => {\n  if (isTupleDataPoint(p)) return { x: p[0], y: p[1] };\n  return { x: p.x, y: p.y };\n};\n\n/**\n * Converts CartesianSeriesData to MutableXYColumns for the runtime hit-test store.\n * Extracts x, y, and optional size values into separate mutable arrays.\n * Size array is aligned with x/y arrays (same length), with undefined for points without size values.\n */\nconst cartesianDataToMutableColumns = (data: CartesianSeriesData): MutableXYColumns => {\n  const n = getCartesianPointCount(data);\n  if (n === 0) return { x: [], y: [] };\n\n  const x: number[] = new Array(n);\n  const y: number[] = new Array(n);\n  const sizeValues: (number | undefined)[] = [];\n  let hasSizeValues = false;\n\n  for (let i = 0; i < n; i++) {\n    x[i] = getCartesianX(data, i);\n    y[i] = getCartesianY(data, i);\n    const size = getCartesianSize(data, i);\n    sizeValues[i] = size;\n    if (size !== undefined) {\n      hasSizeValues = true;\n    }\n  }\n\n  return hasSizeValues ? { x, y, size: sizeValues } : { x, y };\n};\n\nconst getOHLCTimestamp = (p: OHLCDataPoint): number => (isTupleOHLCDataPoint(p) ? p[0] : p.timestamp);\nconst getOHLCClose = (p: OHLCDataPoint): number => (isTupleOHLCDataPoint(p) ? p[2] : p.close);\n\nconst hasSliderDataZoom = (options: ChartGPUOptions): boolean => options.dataZoom?.some((z) => z?.type === 'slider') ?? false;\nconst hasInsideDataZoom = (options: ChartGPUOptions): boolean => options.dataZoom?.some((z) => z?.type === 'inside') ?? false;\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));\n\ntype InteractionScalesCache = {\n  rectWidthCss: number;\n  rectHeightCss: number;\n  plotWidthCss: number;\n  plotHeightCss: number;\n  xDomainMin: number;\n  xDomainMax: number;\n  yDomainMin: number;\n  yDomainMax: number;\n  xScale: LinearScale;\n  yScale: LinearScale;\n};\n\n/**\n * Extends bounds with new CartesianSeriesData (any format).\n * Optimized to avoid per-point type checks for typed arrays.\n */\nconst extendBoundsWithCartesianData = (bounds: Bounds | null, data: CartesianSeriesData): Bounds | null => {\n  const n = getCartesianPointCount(data);\n  if (n === 0) return bounds;\n\n  let b = bounds;\n  if (!b) {\n    // No existing bounds - compute from scratch (already optimized)\n    return computeRawBoundsFromCartesianData(data);\n  }\n\n  let xMin = b.xMin;\n  let xMax = b.xMax;\n  let yMin = b.yMin;\n  let yMax = b.yMax;\n\n  // Hoist type detection outside loop to avoid per-point type checks\n  // NOTE: Format detection logic is duplicated in 2 places and must stay in sync:\n  // 1. extendBoundsWithCartesianData (here) - for bounds updates\n  // 2. appendData method - for columnar store appends (also computes xExtent inline)\n  const isXYArrays = \n    typeof data === 'object' &&\n    data !== null &&\n    !Array.isArray(data) &&\n    'x' in data &&\n    'y' in data;\n  \n  const isInterleaved = \n    typeof data === 'object' &&\n    data !== null &&\n    !Array.isArray(data) &&\n    ArrayBuffer.isView(data);\n\n  if (isXYArrays) {\n    // Fast path for XYArraysData\n    const xyData = data as { x: ArrayLike<number>; y: ArrayLike<number> };\n    for (let i = 0; i < n; i++) {\n      const x = xyData.x[i]!;\n      const y = xyData.y[i]!;\n      if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  } else if (isInterleaved) {\n    // Fast path for InterleavedXYData\n    const arr = data as Float32Array | Float64Array;\n    for (let i = 0; i < n; i++) {\n      const x = arr[i * 2]!;\n      const y = arr[i * 2 + 1]!;\n      if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  } else {\n    // Array<DataPoint> path: use helper functions\n    for (let i = 0; i < n; i++) {\n      const x = getCartesianX(data, i);\n      const y = getCartesianY(data, i);\n      if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n      if (x < xMin) xMin = x;\n      if (x > xMax) xMax = x;\n      if (y < yMin) yMin = y;\n      if (y > yMax) yMax = y;\n    }\n  }\n\n  // Keep bounds usable for downstream scale derivation.\n  if (xMin === xMax) xMax = xMin + 1;\n  if (yMin === yMax) yMax = yMin + 1;\n\n  return { xMin, xMax, yMin, yMax };\n};\n\nconst extendBoundsWithOHLCDataPoints = (bounds: Bounds | null, points: ReadonlyArray<OHLCDataPoint>): Bounds | null => {\n  if (points.length === 0) return bounds;\n\n  let xMin = bounds?.xMin ?? Number.POSITIVE_INFINITY;\n  let xMax = bounds?.xMax ?? Number.NEGATIVE_INFINITY;\n  let yMin = bounds?.yMin ?? Number.POSITIVE_INFINITY;\n  let yMax = bounds?.yMax ?? Number.NEGATIVE_INFINITY;\n\n  for (let i = 0; i < points.length; i++) {\n    const p = points[i]!;\n    const timestamp = getOHLCTimestamp(p);\n    const low = isTupleOHLCDataPoint(p) ? p[3] : p.low;\n    const high = isTupleOHLCDataPoint(p) ? p[4] : p.high;\n\n    if (!Number.isFinite(timestamp) || !Number.isFinite(low) || !Number.isFinite(high)) continue;\n    if (timestamp < xMin) xMin = timestamp;\n    if (timestamp > xMax) xMax = timestamp;\n    if (low < yMin) yMin = low;\n    if (high > yMax) yMax = high;\n  }\n\n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || !Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    return bounds;\n  }\n\n  // Keep bounds usable for downstream scale derivation.\n  if (xMin === xMax) xMax = xMin + 1;\n  if (yMin === yMax) yMax = yMin + 1;\n\n  return { xMin, xMax, yMin, yMax };\n};\n\nconst computeGlobalBounds = (\n  series: ResolvedChartGPUOptions['series'],\n  runtimeRawBoundsByIndex?: ReadonlyArray<Bounds | null> | null\n): Bounds => {\n  let xMin = Number.POSITIVE_INFINITY;\n  let xMax = Number.NEGATIVE_INFINITY;\n  let yMin = Number.POSITIVE_INFINITY;\n  let yMax = Number.NEGATIVE_INFINITY;\n\n  for (let s = 0; s < series.length; s++) {\n    const seriesConfig = series[s]!;\n    // Pie series are non-cartesian; they don't participate in x/y bounds.\n    if (seriesConfig.type === 'pie') continue;\n\n    // Prefer the chart-owned runtime bounds (kept up to date by appendData()).\n    const runtimeBoundsCandidate = runtimeRawBoundsByIndex?.[s] ?? null;\n    if (runtimeBoundsCandidate) {\n      const b = runtimeBoundsCandidate;\n      if (\n        Number.isFinite(b.xMin) &&\n        Number.isFinite(b.xMax) &&\n        Number.isFinite(b.yMin) &&\n        Number.isFinite(b.yMax)\n      ) {\n        if (b.xMin < xMin) xMin = b.xMin;\n        if (b.xMax > xMax) xMax = b.xMax;\n        if (b.yMin < yMin) yMin = b.yMin;\n        if (b.yMax > yMax) yMax = b.yMax;\n        continue;\n      }\n    }\n\n    // Prefer resolver-provided bounds when available (avoids O(n) scans on initial setOption()).\n    // (Resolved series types include `rawBounds` for cartesian series; keep this defensive.)\n    const rawBoundsCandidate = (seriesConfig as unknown as { rawBounds?: Bounds | null }).rawBounds ?? null;\n    if (rawBoundsCandidate) {\n      const b = rawBoundsCandidate;\n      if (\n        Number.isFinite(b.xMin) &&\n        Number.isFinite(b.xMax) &&\n        Number.isFinite(b.yMin) &&\n        Number.isFinite(b.yMax)\n      ) {\n        if (b.xMin < xMin) xMin = b.xMin;\n        if (b.xMax > xMax) xMax = b.xMax;\n        if (b.yMin < yMin) yMin = b.yMin;\n        if (b.yMax > yMax) yMax = b.yMax;\n        continue;\n      }\n    }\n\n    if (seriesConfig.type === 'candlestick') {\n      // Fallback scan when resolver-provided bounds aren't present.\n      const data = seriesConfig.data as ReadonlyArray<OHLCDataPoint>;\n      for (let i = 0; i < data.length; i++) {\n        const p = data[i]!;\n        const timestamp = getOHLCTimestamp(p);\n        const low = isTupleOHLCDataPoint(p) ? p[3] : p.low;\n        const high = isTupleOHLCDataPoint(p) ? p[4] : p.high;\n\n        if (!Number.isFinite(timestamp) || !Number.isFinite(low) || !Number.isFinite(high)) continue;\n        if (timestamp < xMin) xMin = timestamp;\n        if (timestamp > xMax) xMax = timestamp;\n        if (low < yMin) yMin = low;\n        if (high > yMax) yMax = high;\n      }\n      continue;\n    }\n\n    const b = computeRawBoundsFromCartesianData(seriesConfig.data as CartesianSeriesData);\n    if (!b) continue;\n    if (b.xMin < xMin) xMin = b.xMin;\n    if (b.xMax > xMax) xMax = b.xMax;\n    if (b.yMin < yMin) yMin = b.yMin;\n    if (b.yMax > yMax) yMax = b.yMax;\n  }\n\n  if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || !Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n    return { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };\n  }\n\n  if (xMin === xMax) xMax = xMin + 1;\n  if (yMin === yMax) yMax = yMin + 1;\n\n  return { xMin, xMax, yMin, yMax };\n};\n\nconst normalizeDomain = (\n  minCandidate: number,\n  maxCandidate: number\n): { readonly min: number; readonly max: number } => {\n  let min = minCandidate;\n  let max = maxCandidate;\n\n  if (!Number.isFinite(min) || !Number.isFinite(max)) {\n    min = 0;\n    max = 1;\n  }\n\n  if (min === max) {\n    max = min + 1;\n  } else if (min > max) {\n    const t = min;\n    min = max;\n    max = t;\n  }\n\n  return { min, max };\n};\n\ntype CartesianHitTestMatch = Readonly<{\n  kind: 'cartesian';\n  match: NearestPointMatch;\n}>;\n\ntype PieHitTestMatch = Readonly<{\n  kind: 'pie';\n  seriesIndex: number;\n  dataIndex: number;\n  sliceValue: number;\n}>;\n\ntype CandlestickHitTestMatch = Readonly<{\n  kind: 'candlestick';\n  seriesIndex: number;\n  dataIndex: number;\n  point: OHLCDataPoint;\n}>;\n\ntype HitTestMatch = CartesianHitTestMatch | PieHitTestMatch | CandlestickHitTestMatch;\n\nconst parseNumberOrPercent = (value: number | string, basis: number): number | null => {\n  if (typeof value === 'number') return Number.isFinite(value) ? value : null;\n  if (typeof value !== 'string') return null;\n\n  const s = value.trim();\n  if (s.length === 0) return null;\n\n  if (s.endsWith('%')) {\n    const pct = Number.parseFloat(s.slice(0, -1));\n    if (!Number.isFinite(pct)) return null;\n    return (pct / 100) * basis;\n  }\n\n  // Be permissive: allow numeric strings like \"120\" even though the public type primarily documents percent strings.\n  const n = Number.parseFloat(s);\n  return Number.isFinite(n) ? n : null;\n};\n\nconst resolvePieCenterPlotCss = (\n  center: PieCenter | undefined,\n  plotWidthCss: number,\n  plotHeightCss: number\n): { readonly x: number; readonly y: number } => {\n  const xRaw = center?.[0] ?? '50%';\n  const yRaw = center?.[1] ?? '50%';\n\n  const x = parseNumberOrPercent(xRaw, plotWidthCss);\n  const y = parseNumberOrPercent(yRaw, plotHeightCss);\n\n  return {\n    x: Number.isFinite(x) ? x! : plotWidthCss * 0.5,\n    y: Number.isFinite(y) ? y! : plotHeightCss * 0.5,\n  };\n};\n\nconst isPieRadiusTuple = (radius: PieRadius): radius is readonly [inner: number | string, outer: number | string] =>\n  Array.isArray(radius);\n\nconst resolvePieRadiiCss = (\n  radius: PieRadius | undefined,\n  maxRadiusCss: number\n): { readonly inner: number; readonly outer: number } => {\n  // Default similar to common chart libs (mirrors `createPieRenderer.ts` and coordinator helpers).\n  if (radius == null) return { inner: 0, outer: maxRadiusCss * 0.7 };\n\n  if (isPieRadiusTuple(radius)) {\n    const inner = parseNumberOrPercent(radius[0], maxRadiusCss);\n    const outer = parseNumberOrPercent(radius[1], maxRadiusCss);\n    const innerCss = Math.max(0, Number.isFinite(inner) ? inner! : 0);\n    const outerCss = Math.max(innerCss, Number.isFinite(outer) ? outer! : maxRadiusCss * 0.7);\n    return { inner: innerCss, outer: Math.min(maxRadiusCss, outerCss) };\n  }\n\n  const outer = parseNumberOrPercent(radius, maxRadiusCss);\n  const outerCss = Math.max(0, Number.isFinite(outer) ? outer! : maxRadiusCss * 0.7);\n  return { inner: 0, outer: Math.min(maxRadiusCss, outerCss) };\n};\n\n/**\n * Creates a ChartGPU instance with default WebGPU initialization.\n */\nexport async function createChartGPU(\n  container: HTMLElement,\n  options: ChartGPUOptions\n): Promise<ChartGPUInstance>;\n\n/**\n * Creates a ChartGPU instance with a shared WebGPU device and adapter.\n * Use this overload to share a single GPU device across multiple chart instances.\n * \n * @param container - HTML container element for the chart\n * @param options - Chart configuration options\n * @param context - Shared GPU context with device and adapter\n */\nexport async function createChartGPU(\n  container: HTMLElement,\n  options: ChartGPUOptions,\n  context: ChartGPUCreateContext\n): Promise<ChartGPUInstance>;\n\nexport async function createChartGPU(\n  container: HTMLElement,\n  options: ChartGPUOptions,\n  context?: ChartGPUCreateContext\n): Promise<ChartGPUInstance> {\n  // Check WebGPU support before creating canvas or any resources.\n  // When the caller injects an adapter+device, avoid requesting another adapter during the\n  // support check (shared device mode).\n  if (!context) {\n    const supportCheck = await checkWebGPUSupport();\n    if (!supportCheck.supported) {\n      const reason = supportCheck.reason || 'Unknown reason';\n      throw new Error(\n        `ChartGPU: WebGPU is not available.\\n` +\n          `Reason: ${reason}\\n` +\n          `Browser support: Chrome/Edge 113+, Safari 18+, Firefox not yet supported.\\n` +\n          `Resources:\\n` +\n          `  - MDN WebGPU API: https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API\\n` +\n          `  - Browser compatibility: https://caniuse.com/webgpu\\n` +\n          `  - WebGPU specification: https://www.w3.org/TR/webgpu/\\n` +\n          `  - Check your system: https://webgpureport.org/`\n      );\n    }\n  } else {\n    // Minimal sanity checks: the injected device path still requires WebGPU globals for canvas format negotiation.\n    if (typeof navigator === 'undefined' || !navigator.gpu) {\n      throw new Error('ChartGPU: Shared device mode requires WebGPU globals (navigator.gpu) to be available.');\n    }\n  }\n\n  // Fail fast: pipeline cache (if provided) must be created for the same GPUDevice this chart will use.\n  if (context?.pipelineCache && context.pipelineCache.device !== context.device) {\n    throw new Error(\n      'ChartGPU: pipelineCache.device must match the GPUDevice in the creation context. ' +\n        'Create the pipeline cache with the same device: createPipelineCache(device).'\n    );\n  }\n\n  const canvas = document.createElement('canvas');\n\n  // Ensure the canvas participates in layout and can size via the container.\n  canvas.style.display = 'block';\n  canvas.style.width = '100%';\n  canvas.style.height = '100%';\n\n  // Append before awaiting so it appears immediately and has measurable size.\n  container.appendChild(canvas);\n\n  const isSharedDevice = !!context;\n  \n  let disposed = false;\n  let renderMode: RenderMode = options.renderMode ?? 'auto';\n  let isRendering = false;\n  let deviceIsLost = false;\n  let gpuContext: GPUContext | null = null;\n  let coordinator: RenderCoordinator | null = null;\n  let coordinatorTargetFormat: GPUTextureFormat | null = null;\n  let unsubscribeCoordinatorInteractionXChange: (() => void) | null = null;\n  let unsubscribeCoordinatorZoomRangeChange: (() => void) | null = null;\n\n  // For chart-sync loop prevention: when `setZoomRange(..., source)` is called, we \"arm\" the next\n  // coordinator zoom-change notification with the provided source token so downstream listeners\n  // can skip rebroadcast.\n  let pendingZoomSource: unknown = undefined;\n  let pendingZoomSourceArmed = false;\n\n  let dataZoomSliderHost: HTMLDivElement | null = null;\n  let dataZoomSlider: DataZoomSlider | null = null;\n  let zoomResetButton: ZoomResetButton | null = null;\n\n  let currentOptions: ChartGPUOptions = options;\n  let resolvedOptions: ResolvedChartGPUOptions = resolveOptionsForChart(currentOptions);\n\n  // Chart-owned runtime series store for hit-testing only (cartesian only).\n  // - `runtimeRawDataByIndex[i]` is a mutable columnar store (MutableXYColumns) for non-candlestick series,\n  //   or a mutable OHLCDataPoint[] for candlestick series. This supports efficient streaming appends\n  //   without per-point object allocations.\n  // - `runtimeRawBoundsByIndex[i]` is incrementally updated to keep scale/bounds derivation cheap.\n  let runtimeRawDataByIndex: Array<MutableXYColumns | OHLCDataPoint[]> = new Array(resolvedOptions.series.length).fill(null).map(() => ({ x: [], y: [] }));\n  let runtimeRawBoundsByIndex: Array<Bounds | null> = new Array(resolvedOptions.series.length).fill(null);\n  let runtimeHitTestSeriesCache: ResolvedChartGPUOptions['series'] | null = null;\n  let runtimeHitTestSeriesVersion = 0;\n\n  const initRuntimeHitTestStoreFromResolvedOptions = (): void => {\n    runtimeRawDataByIndex = new Array(resolvedOptions.series.length).fill(null).map(() => ({ x: [], y: [] }));\n    runtimeRawBoundsByIndex = new Array(resolvedOptions.series.length).fill(null);\n    runtimeHitTestSeriesCache = null;\n    runtimeHitTestSeriesVersion++;\n\n    for (let i = 0; i < resolvedOptions.series.length; i++) {\n      const s = resolvedOptions.series[i]!;\n      if (s.type === 'pie') {\n        // Pie series don't use the runtime store (non-cartesian)\n        runtimeRawDataByIndex[i] = { x: [], y: [] };\n        continue;\n      }\n\n      if (s.type === 'candlestick') {\n        const raw = ((s as unknown as { rawData?: ReadonlyArray<OHLCDataPoint> }).rawData ?? s.data) as ReadonlyArray<OHLCDataPoint>;\n        runtimeRawDataByIndex[i] = raw.length === 0 ? [] : raw.slice();\n        runtimeRawBoundsByIndex[i] = ((s as unknown as { rawBounds?: Bounds | null }).rawBounds ?? null);\n      } else {\n        const raw = ((s as unknown as { rawData?: CartesianSeriesData }).rawData ?? s.data) as CartesianSeriesData;\n        runtimeRawDataByIndex[i] = cartesianDataToMutableColumns(raw);\n        runtimeRawBoundsByIndex[i] =\n          ((s as unknown as { rawBounds?: Bounds | null }).rawBounds ?? null) ?? (computeRawBoundsFromCartesianData(raw) as Bounds | null);\n      }\n    }\n  };\n\n  const getRuntimeHitTestSeries = (): ResolvedChartGPUOptions['series'] => {\n    if (runtimeHitTestSeriesCache) return runtimeHitTestSeriesCache;\n    // Replace cartesian series `data` with chart-owned runtime data (pie series are unchanged).\n    runtimeHitTestSeriesCache = resolvedOptions.series.map((s, i) => {\n      if (s.type === 'pie') return s;\n      if (s.type === 'candlestick') {\n        return { ...s, data: runtimeRawDataByIndex[i] ?? (s.data as ReadonlyArray<OHLCDataPoint>) };\n      }\n      // For non-candlestick cartesian series, runtime store is MutableXYColumns (compatible with XYArraysData)\n      const runtimeData = runtimeRawDataByIndex[i] as MutableXYColumns;\n      return { ...s, data: runtimeData as CartesianSeriesData };\n    }) as ResolvedChartGPUOptions['series'];\n    return runtimeHitTestSeriesCache;\n  };\n\n  initRuntimeHitTestStoreFromResolvedOptions();\n\n  // Cache global bounds and interaction scales; avoids O(N) data scans per pointer move.\n  let cachedGlobalBounds: Bounds = computeGlobalBounds(resolvedOptions.series, runtimeRawBoundsByIndex);\n  let interactionScalesCache: InteractionScalesCache | null = null;\n\n  const listeners: ListenerRegistry = {\n    click: new Set<ChartGPUEventCallback>(),\n    mouseover: new Set<ChartGPUEventCallback>(),\n    mouseout: new Set<ChartGPUEventCallback>(),\n    crosshairMove: new Set<ChartGPUCrosshairMoveCallback>(),\n    zoomRangeChange: new Set<ChartGPUZoomRangeChangeCallback>(),\n    deviceLost: new Set<ChartGPUDeviceLostCallback>(),\n    dataAppend: new Set<ChartGPUDataAppendCallback>(),\n  };\n\n  // AC-6: Boolean flag for zero-overhead check (faster than Set.size property access in hot path)\n  let hasDataAppendListeners = false;\n\n  let tapCandidate: TapCandidate | null = null;\n  let suppressNextLostPointerCaptureId: number | null = null;\n\n  let hovered: HitTestMatch | null = null;\n\n  // Prevent spamming console.warn for repeated misuse.\n  const warnedPieAppendSeries = new Set<number>();\n\n  let scheduledRaf: number | null = null;\n  let lastConfigured: { width: number; height: number; format: GPUTextureFormat } | null = null;\n  let isDirty = true;\n\n  // Performance tracking state\n  const frameTimestamps = new Float64Array(FRAME_BUFFER_SIZE);\n  let frameTimestampIndex = 0;\n  let frameTimestampCount = 0;\n  let totalFrames = 0;\n  let totalDroppedFrames = 0;\n  let consecutiveDroppedFrames = 0;\n  let lastDropTimestamp = 0;\n  const startTime = performance.now();\n  let lastFrameTime = 0;\n  let lastCPUTime = 0;\n  const performanceUpdateCallbacks = new Set<(metrics: Readonly<PerformanceMetrics>) => void>();\n\n  const hasHoverListeners = (): boolean => listeners.mouseover.size > 0 || listeners.mouseout.size > 0;\n  const hasClickListeners = (): boolean => listeners.click.size > 0;\n\n  const cancelPendingFrame = (): void => {\n    if (scheduledRaf === null) return;\n    cancelAnimationFrame(scheduledRaf);\n    scheduledRaf = null;\n  };\n\n  const resetPerfMetricsInternal = (): void => {\n    lastFrameTime = 0;\n    totalDroppedFrames = 0;\n    consecutiveDroppedFrames = 0;\n    lastDropTimestamp = 0;\n    frameTimestampIndex = 0;\n    frameTimestampCount = 0;\n  };\n\n  const doFrame = (trackFrameDrops: boolean): void => {\n    if (disposed) return;\n    if (deviceIsLost) return;\n    if (isRendering) return;\n    isRendering = true;\n    const frameStartTime = performance.now();\n\n    try {\n      frameTimestamps[frameTimestampIndex] = frameStartTime;\n      frameTimestampIndex = (frameTimestampIndex + 1) % FRAME_BUFFER_SIZE;\n      if (frameTimestampCount < FRAME_BUFFER_SIZE) frameTimestampCount++;\n      totalFrames++;\n\n      if (trackFrameDrops) {\n        if (lastFrameTime > 0) {\n          const deltaTime = frameStartTime - lastFrameTime;\n          if (deltaTime > EXPECTED_FRAME_TIME_MS * FRAME_DROP_THRESHOLD_MULTIPLIER) {\n            totalDroppedFrames++;\n            consecutiveDroppedFrames++;\n            lastDropTimestamp = frameStartTime;\n          } else {\n            consecutiveDroppedFrames = 0;\n          }\n        }\n        lastFrameTime = frameStartTime;\n      }\n\n      resizeInternal(false);\n      if (!coordinator || !gpuContext?.device) return;\n\n      if (isDirty) {\n        isDirty = false;\n        try {\n          coordinator.render();\n        } catch {\n          isDirty = true;\n        }\n      }\n\n      lastCPUTime = performance.now() - frameStartTime;\n      const metrics = calculatePerformanceMetrics();\n      for (const callback of performanceUpdateCallbacks) {\n        try {\n          callback(metrics);\n        } catch (error) {\n          console.error('Error in performance update callback:', error);\n        }\n      }\n    } finally {\n      isRendering = false;\n    }\n  };\n\n  const requestRender = (): void => {\n    if (disposed) return;\n    isDirty = true;\n    if (renderMode === 'external') return;\n    if (scheduledRaf !== null) return;\n    scheduledRaf = requestAnimationFrame(() => {\n      scheduledRaf = null;\n      if (disposed) return;\n      doFrame(true);\n    });\n  };\n\n  const unbindCoordinatorInteractionXChange = (): void => {\n    if (!unsubscribeCoordinatorInteractionXChange) return;\n    try {\n      unsubscribeCoordinatorInteractionXChange();\n    } finally {\n      unsubscribeCoordinatorInteractionXChange = null;\n    }\n  };\n\n  const unbindCoordinatorZoomRangeChange = (): void => {\n    if (!unsubscribeCoordinatorZoomRangeChange) return;\n    try {\n      unsubscribeCoordinatorZoomRangeChange();\n    } finally {\n      unsubscribeCoordinatorZoomRangeChange = null;\n    }\n  };\n\n  const disposeDataZoomSlider = (): void => {\n    dataZoomSlider?.dispose();\n    dataZoomSlider = null;\n  };\n\n  const disposeDataZoomSliderHost = (): void => {\n    dataZoomSliderHost?.remove();\n    dataZoomSliderHost = null;\n  };\n\n  const disposeDataZoomUi = (): void => {\n    disposeDataZoomSlider();\n    disposeDataZoomSliderHost();\n  };\n\n  const DATA_ZOOM_SLIDER_HEIGHT_CSS_PX = 32;\n  const DATA_ZOOM_SLIDER_MARGIN_TOP_CSS_PX = 8;\n  const DATA_ZOOM_SLIDER_RESERVE_CSS_PX = DATA_ZOOM_SLIDER_HEIGHT_CSS_PX + DATA_ZOOM_SLIDER_MARGIN_TOP_CSS_PX;\n\n  const ensureDataZoomSliderHost = (): HTMLDivElement => {\n    if (dataZoomSliderHost) return dataZoomSliderHost;\n\n    // Ensure the host's absolute positioning is anchored to the chart container.\n    // If the container is already positioned, avoid overwriting user styles.\n    try {\n      const pos = window.getComputedStyle(container).position;\n      if (pos === 'static') container.style.position = 'relative';\n    } catch {\n      // best-effort\n    }\n\n    const host = document.createElement('div');\n    host.style.position = 'absolute';\n    host.style.left = '0';\n    host.style.right = '0';\n    host.style.bottom = '0';\n    host.style.height = `${DATA_ZOOM_SLIDER_RESERVE_CSS_PX}px`;\n    host.style.paddingTop = `${DATA_ZOOM_SLIDER_MARGIN_TOP_CSS_PX}px`;\n    host.style.boxSizing = 'border-box';\n    host.style.pointerEvents = 'auto';\n    host.style.zIndex = '5';\n    container.appendChild(host);\n    dataZoomSliderHost = host;\n    return host;\n  };\n\n  const computeZoomInOutAnchorRatio = (range: ZoomRange, center: number): number => {\n    const span = range.end - range.start;\n    if (!Number.isFinite(span) || span === 0) return 0.5;\n    return clamp((center - range.start) / span, 0, 1);\n  };\n\n  const createCoordinatorZoomStateLike = (): ZoomState => {\n    const getRange: ZoomState['getRange'] = () => coordinator?.getZoomRange() ?? { start: 0, end: 100 };\n    const setRange: ZoomState['setRange'] = (start, end) => {\n      coordinator?.setZoomRange(start, end);\n    };\n    const zoomIn: ZoomState['zoomIn'] = (center, factor) => {\n      if (!Number.isFinite(center) || !Number.isFinite(factor) || factor <= 1) return;\n      const r = coordinator?.getZoomRange();\n      if (!r) return;\n      const c = clamp(center, 0, 100);\n      const ratio = computeZoomInOutAnchorRatio(r, c);\n      const span = r.end - r.start;\n      const nextSpan = span / factor;\n      const nextStart = c - ratio * nextSpan;\n      coordinator?.setZoomRange(nextStart, nextStart + nextSpan);\n    };\n    const zoomOut: ZoomState['zoomOut'] = (center, factor) => {\n      if (!Number.isFinite(center) || !Number.isFinite(factor) || factor <= 1) return;\n      const r = coordinator?.getZoomRange();\n      if (!r) return;\n      const c = clamp(center, 0, 100);\n      const ratio = computeZoomInOutAnchorRatio(r, c);\n      const span = r.end - r.start;\n      const nextSpan = span * factor;\n      const nextStart = c - ratio * nextSpan;\n      coordinator?.setZoomRange(nextStart, nextStart + nextSpan);\n    };\n    const pan: ZoomState['pan'] = (delta) => {\n      if (!Number.isFinite(delta)) return;\n      const r = coordinator?.getZoomRange();\n      if (!r) return;\n      coordinator?.setZoomRange(r.start + delta, r.end + delta);\n    };\n    const onChange: ZoomState['onChange'] = (callback) => coordinator?.onZoomRangeChange(callback) ?? (() => {});\n\n    return { getRange, setRange, zoomIn, zoomOut, pan, onChange };\n  };\n\n  const syncDataZoomUi = (): void => {\n    const shouldHaveSlider = hasSliderDataZoom(currentOptions);\n    if (!shouldHaveSlider) {\n      disposeDataZoomUi();\n      return;\n    }\n\n    // Slider requires a coordinator-backed zoom state.\n    if (!coordinator) return;\n    if (!coordinator.getZoomRange()) return;\n\n    const host = ensureDataZoomSliderHost();\n    if (!dataZoomSlider) {\n      dataZoomSlider = createDataZoomSlider(host, createCoordinatorZoomStateLike(), {\n        height: DATA_ZOOM_SLIDER_HEIGHT_CSS_PX,\n        marginTop: 0, // host provides vertical spacing\n      });\n    }\n    dataZoomSlider.update(resolvedOptions.theme);\n  };\n\n  const disposeZoomResetButton = (): void => {\n    zoomResetButton?.dispose();\n    zoomResetButton = null;\n  };\n\n  const syncZoomResetButton = (): void => {\n    const shouldHaveButton = hasInsideDataZoom(currentOptions);\n    if (!shouldHaveButton) {\n      disposeZoomResetButton();\n      return;\n    }\n\n    // Button requires a coordinator-backed zoom state.\n    if (!coordinator) return;\n    if (!coordinator.getZoomRange()) return;\n\n    if (!zoomResetButton) {\n      zoomResetButton = createZoomResetButton(container, createCoordinatorZoomStateLike(), resolvedOptions.theme);\n    } else {\n      zoomResetButton.update(resolvedOptions.theme);\n    }\n  };\n\n  // Reusable event payloads to avoid allocations in hot paths (pointer/zoom/dataAppend interactions).\n  // Internal mutable versions; cast to readonly when emitting (safe since payload is passed by reference\n  // and consumers receive readonly types, preventing external mutation).\n  const crosshairMovePayload = { x: null as number | null, source: undefined as unknown };\n  const zoomRangeChangePayload = { start: 0, end: 100, source: undefined as unknown, sourceKind: undefined as ZoomChangeSourceKind | undefined };\n  const dataAppendPayload = { seriesIndex: 0, count: 0, xExtent: { min: 0, max: 0 } };\n\n  const bindCoordinatorInteractionXChange = (): void => {\n    unbindCoordinatorInteractionXChange();\n    if (disposed) return;\n    if (!coordinator) return;\n\n    unsubscribeCoordinatorInteractionXChange = coordinator.onInteractionXChange((x, source) => {\n      crosshairMovePayload.x = x;\n      crosshairMovePayload.source = source;\n      emit('crosshairMove', crosshairMovePayload as ChartGPUCrosshairMovePayload);\n    });\n  };\n\n  const bindCoordinatorZoomRangeChange = (): void => {\n    unbindCoordinatorZoomRangeChange();\n    if (disposed) return;\n    if (!coordinator) return;\n\n    unsubscribeCoordinatorZoomRangeChange = coordinator.onZoomRangeChange((range, sourceKind) => {\n      // If setZoomRange armed the next change, classify it as 'api'. If a source token was provided,\n      // forward it so chart sync can avoid feedback loops.\n      const wasApiArmed = pendingZoomSourceArmed;\n      const pendingSource = pendingZoomSource;\n      pendingZoomSourceArmed = false;\n      pendingZoomSource = undefined;\n\n      const source = pendingSource !== undefined ? pendingSource : undefined;\n\n      // Classify zoom change source:\n      // - Use coordinator's sourceKind if provided (e.g., 'user', 'auto-scroll')\n      // - If no sourceKind but this was armed by setZoomRange, classify as 'api'\n      // - Otherwise, leave sourceKind undefined (not classified)\n      const classifiedSourceKind = sourceKind ?? (wasApiArmed ? 'api' : undefined);\n\n      zoomRangeChangePayload.start = range.start;\n      zoomRangeChangePayload.end = range.end;\n      zoomRangeChangePayload.source = source;\n      zoomRangeChangePayload.sourceKind = classifiedSourceKind;\n      emit('zoomRangeChange', zoomRangeChangePayload as ChartGPUZoomRangeChangePayload);\n    });\n  };\n\n  const recreateCoordinator = (): void => {\n    if (disposed) return;\n    if (!gpuContext || !gpuContext.initialized) return;\n\n    const prevZoomRange = coordinator?.getZoomRange() ?? null;\n\n    unbindCoordinatorInteractionXChange();\n    unbindCoordinatorZoomRangeChange();\n    // Coordinator recreation invalidates zoom subscriptions; recreate the slider and reset button if present.\n    disposeDataZoomSlider();\n    disposeZoomResetButton();\n    coordinator?.dispose();\n    \n    // Clear any pending zoom source tokens to avoid stale tokens after recreation.\n    pendingZoomSourceArmed = false;\n    pendingZoomSource = undefined;\n    \n    const coordinatorCallbacks: RenderCoordinatorCallbacks = {\n      onRequestRender: requestRender,\n      pipelineCache: context?.pipelineCache,\n    };\n    coordinator = createRenderCoordinator(gpuContext, resolvedOptions, coordinatorCallbacks);\n    coordinatorTargetFormat = gpuContext.preferredFormat;\n    bindCoordinatorInteractionXChange();\n    bindCoordinatorZoomRangeChange();\n\n    if (prevZoomRange) coordinator.setZoomRange(prevZoomRange.start, prevZoomRange.end);\n    syncDataZoomUi();\n    syncZoomResetButton();\n  };\n\n  const resizeInternal = (shouldRequestRenderAfterChanges: boolean): void => {\n    if (disposed) return;\n\n    const rect = canvas.getBoundingClientRect();\n    const dpr = window.devicePixelRatio || 1;\n\n    const maxDimension = gpuContext?.device?.limits.maxTextureDimension2D ?? 8192;\n    const width = Math.min(maxDimension, Math.max(1, Math.round(rect.width * dpr)));\n    const height = Math.min(maxDimension, Math.max(1, Math.round(rect.height * dpr)));\n\n    const sizeChanged = canvas.width !== width || canvas.height !== height;\n    if (sizeChanged) {\n      canvas.width = width;\n      canvas.height = height;\n    }\n\n    const device = gpuContext?.device;\n    const canvasContext = gpuContext?.canvasContext;\n    const preferredFormat = gpuContext?.preferredFormat;\n\n    let didConfigure = false;\n    if (device && canvasContext && preferredFormat) {\n      const shouldConfigure =\n        sizeChanged ||\n        !lastConfigured ||\n        lastConfigured.width !== canvas.width ||\n        lastConfigured.height !== canvas.height ||\n        lastConfigured.format !== preferredFormat;\n\n      if (shouldConfigure) {\n        canvasContext.configure({\n          device,\n          format: preferredFormat,\n          alphaMode: 'opaque',\n        });\n        lastConfigured = { width: canvas.width, height: canvas.height, format: preferredFormat };\n        didConfigure = true;\n\n        // Requirement: if the target format changes, recreate coordinator/pipelines.\n        if (coordinator && coordinatorTargetFormat !== preferredFormat) {\n          recreateCoordinator();\n        }\n      }\n    }\n\n    if (shouldRequestRenderAfterChanges && (sizeChanged || didConfigure)) {\n      // Requirement: resize() requests a render after size/config changes.\n      requestRender();\n    }\n  };\n\n  const resize = (): void => resizeInternal(true);\n\n  const getNearestPointFromPointerEvent = (\n    e: PointerEvent\n  ): { readonly match: HitTestMatch | null; readonly isInGrid: boolean } => {\n    const rect = canvas.getBoundingClientRect();\n    if (!(rect.width > 0) || !(rect.height > 0)) return { match: null, isInGrid: false };\n\n    const x = e.clientX - rect.left;\n    const y = e.clientY - rect.top;\n\n    const plotLeftCss = resolvedOptions.grid.left;\n    const plotTopCss = resolvedOptions.grid.top;\n    const plotWidthCss = rect.width - resolvedOptions.grid.left - resolvedOptions.grid.right;\n    const plotHeightCss = rect.height - resolvedOptions.grid.top - resolvedOptions.grid.bottom;\n    if (!(plotWidthCss > 0) || !(plotHeightCss > 0)) return { match: null, isInGrid: false };\n\n    const gridX = x - plotLeftCss;\n    const gridY = y - plotTopCss;\n\n    const isInGrid =\n      gridX >= 0 &&\n      gridX <= plotWidthCss &&\n      gridY >= 0 &&\n      gridY <= plotHeightCss;\n\n    if (!isInGrid) return { match: null, isInGrid: false };\n\n    const xMin = resolvedOptions.xAxis.min ?? cachedGlobalBounds.xMin;\n    const xMax = resolvedOptions.xAxis.max ?? cachedGlobalBounds.xMax;\n    const yMin = resolvedOptions.yAxis.min ?? cachedGlobalBounds.yMin;\n    const yMax = resolvedOptions.yAxis.max ?? cachedGlobalBounds.yMax;\n\n    // Make hit-testing zoom-aware (mirror coordinator percent->domain mapping).\n    const baseXDomain = normalizeDomain(xMin, xMax);\n    const zoomRange = coordinator?.getZoomRange() ?? null;\n    const xDomain = (() => {\n      if (!zoomRange) return baseXDomain;\n      const span = baseXDomain.max - baseXDomain.min;\n      if (!Number.isFinite(span) || span === 0) return baseXDomain;\n      const start = zoomRange.start;\n      const end = zoomRange.end;\n      const zMin = baseXDomain.min + (start / 100) * span;\n      const zMax = baseXDomain.min + (end / 100) * span;\n      return normalizeDomain(zMin, zMax);\n    })();\n    const yDomain = normalizeDomain(yMin, yMax);\n\n    // Cache hit-testing scales for identical (rect, grid, axis domain) inputs.\n    const canReuseScales =\n      interactionScalesCache !== null &&\n      interactionScalesCache.rectWidthCss === rect.width &&\n      interactionScalesCache.rectHeightCss === rect.height &&\n      interactionScalesCache.plotWidthCss === plotWidthCss &&\n      interactionScalesCache.plotHeightCss === plotHeightCss &&\n      interactionScalesCache.xDomainMin === xDomain.min &&\n      interactionScalesCache.xDomainMax === xDomain.max &&\n      interactionScalesCache.yDomainMin === yDomain.min &&\n      interactionScalesCache.yDomainMax === yDomain.max;\n\n    if (!canReuseScales) {\n      // IMPORTANT: grid-local CSS px ranges (0..plotWidth/Height), for interaction hit-testing.\n      const xScale = createLinearScale().domain(xDomain.min, xDomain.max).range(0, plotWidthCss);\n      const yScale = createLinearScale().domain(yDomain.min, yDomain.max).range(plotHeightCss, 0);\n      interactionScalesCache = {\n        rectWidthCss: rect.width,\n        rectHeightCss: rect.height,\n        plotWidthCss,\n        plotHeightCss,\n        xDomainMin: xDomain.min,\n        xDomainMax: xDomain.max,\n        yDomainMin: yDomain.min,\n        yDomainMax: yDomain.max,\n        xScale,\n        yScale,\n      };\n    }\n\n    // At this point, the cache must exist (either reused or created above).\n    const scales = interactionScalesCache!;\n\n    // Story 4.14: pie slice hit-testing (grid-local CSS px).\n    const pieMatch = (() => {\n      const maxRadiusCss = 0.5 * Math.min(plotWidthCss, plotHeightCss);\n      if (!(maxRadiusCss > 0)) return null;\n\n      // Prefer later series indices (deterministic and mirrors the coordinator tooltip logic).\n      for (let i = resolvedOptions.series.length - 1; i >= 0; i--) {\n        const s = resolvedOptions.series[i];\n        if (s.type !== 'pie') continue;\n\n        // Skip invisible series.\n        if (s.visible === false) continue;\n\n        const pieSeries = s as ResolvedPieSeriesConfig;\n        const center = resolvePieCenterPlotCss(pieSeries.center, plotWidthCss, plotHeightCss);\n        const radii = resolvePieRadiiCss(pieSeries.radius, maxRadiusCss);\n        const m = findPieSlice(gridX, gridY, { seriesIndex: i, series: pieSeries }, center, radii);\n        if (!m) continue;\n\n        const v = m.slice.value;\n        return {\n          kind: 'pie' as const,\n          seriesIndex: m.seriesIndex,\n          dataIndex: m.dataIndex,\n          sliceValue: typeof v === 'number' && Number.isFinite(v) ? v : 0,\n        };\n      }\n      return null;\n    })();\n\n    if (pieMatch) return { match: pieMatch, isInGrid: true };\n\n    // Candlestick body hit-testing (grid-local CSS px), prefer later series indices.\n    for (let i = resolvedOptions.series.length - 1; i >= 0; i--) {\n      const s = resolvedOptions.series[i];\n      if (s?.type !== 'candlestick') continue;\n\n      // Skip invisible series.\n      if (s.visible === false) continue;\n\n      const seriesCfg = s as ResolvedCandlestickSeriesConfig;\n      const barWidthRange = computeCandlestickBodyWidthRange(seriesCfg, seriesCfg.data, scales.xScale, plotWidthCss);\n      const m = findCandlestick([seriesCfg], gridX, gridY, scales.xScale, scales.yScale, barWidthRange);\n      if (!m) continue;\n\n      return {\n        match: { kind: 'candlestick', seriesIndex: i, dataIndex: m.dataIndex, point: m.point },\n        isInGrid: true,\n      };\n    }\n\n    const cartesianMatch = findNearestPoint(\n      getRuntimeHitTestSeries(),\n      gridX,\n      gridY,\n      scales.xScale,\n      scales.yScale\n    );\n\n    return {\n      match: cartesianMatch ? ({ kind: 'cartesian', match: cartesianMatch } as const) : null,\n      isInGrid: true,\n    };\n  };\n\n  const calculateExactFPS = (): ExactFPS => {\n    if (frameTimestampCount < 2) {\n      return 0 as ExactFPS;\n    }\n\n    const startIndex = (frameTimestampIndex - frameTimestampCount + FRAME_BUFFER_SIZE) % FRAME_BUFFER_SIZE;\n    \n    let totalDelta = 0;\n    for (let i = 1; i < frameTimestampCount; i++) {\n      const prevIndex = (startIndex + i - 1) % FRAME_BUFFER_SIZE;\n      const currIndex = (startIndex + i) % FRAME_BUFFER_SIZE;\n      const delta = frameTimestamps[currIndex] - frameTimestamps[prevIndex];\n      totalDelta += delta;\n    }\n\n    const avgFrameTime = totalDelta / (frameTimestampCount - 1);\n    const fps = avgFrameTime > 0 ? 1000 / avgFrameTime : 0;\n    \n    return fps as ExactFPS;\n  };\n\n  const calculateFrameTimeStats = (): FrameTimeStats => {\n    if (frameTimestampCount < 2) {\n      return {\n        min: 0 as Milliseconds,\n        max: 0 as Milliseconds,\n        avg: 0 as Milliseconds,\n        p50: 0 as Milliseconds,\n        p95: 0 as Milliseconds,\n        p99: 0 as Milliseconds,\n      };\n    }\n\n    const startIndex = (frameTimestampIndex - frameTimestampCount + FRAME_BUFFER_SIZE) % FRAME_BUFFER_SIZE;\n    \n    const deltas = new Array<number>(frameTimestampCount - 1);\n    let min = Number.POSITIVE_INFINITY;\n    let max = Number.NEGATIVE_INFINITY;\n    let sum = 0;\n    \n    for (let i = 1; i < frameTimestampCount; i++) {\n      const prevIndex = (startIndex + i - 1) % FRAME_BUFFER_SIZE;\n      const currIndex = (startIndex + i) % FRAME_BUFFER_SIZE;\n      const delta = frameTimestamps[currIndex] - frameTimestamps[prevIndex];\n      deltas[i - 1] = delta;\n      \n      if (delta < min) min = delta;\n      if (delta > max) max = delta;\n      sum += delta;\n    }\n\n    const avg = sum / deltas.length;\n\n    // Sort for percentile calculations\n    deltas.sort((a, b) => a - b);\n\n    const p50Index = Math.floor(deltas.length * 0.50);\n    const p95Index = Math.floor(deltas.length * 0.95);\n    const p99Index = Math.floor(deltas.length * 0.99);\n\n    return {\n      min: min as Milliseconds,\n      max: max as Milliseconds,\n      avg: avg as Milliseconds,\n      p50: deltas[p50Index] as Milliseconds,\n      p95: deltas[p95Index] as Milliseconds,\n      p99: deltas[p99Index] as Milliseconds,\n    };\n  };\n\n  const calculatePerformanceMetrics = (): PerformanceMetrics => {\n    const fps = calculateExactFPS();\n    const frameTimeStats = calculateFrameTimeStats();\n    \n    const gpuTiming: GPUTimingStats = {\n      enabled: false, // GPU timing not yet implemented for main thread\n      cpuTime: lastCPUTime as Milliseconds,\n      gpuTime: 0 as Milliseconds,\n    };\n    \n    const memory: MemoryStats = {\n      used: 0 as Bytes,\n      peak: 0 as Bytes,\n      allocated: 0 as Bytes,\n    };\n    \n    const frameDrops: FrameDropStats = renderMode === 'external'\n      ? { totalDrops: 0, consecutiveDrops: 0, lastDropTimestamp: 0 as Milliseconds }\n      : {\n          totalDrops: totalDroppedFrames,\n          consecutiveDrops: consecutiveDroppedFrames,\n          lastDropTimestamp: lastDropTimestamp as Milliseconds,\n        };\n    \n    const elapsedTime = performance.now() - startTime;\n    \n    return {\n      fps,\n      frameTimeStats,\n      gpuTiming,\n      memory,\n      frameDrops,\n      totalFrames,\n      elapsedTime: elapsedTime as Milliseconds,\n    };\n  };\n\n  const buildPayload = (match: HitTestMatch | null, event: PointerEvent): ChartGPUEventPayload => {\n    if (!match) {\n      return { seriesIndex: null, dataIndex: null, value: null, seriesName: null, event };\n    }\n\n    const seriesIndex = match.kind === 'cartesian' ? match.match.seriesIndex : match.seriesIndex;\n    const dataIndex = match.kind === 'cartesian' ? match.match.dataIndex : match.dataIndex;\n\n    const series = resolvedOptions.series[seriesIndex];\n    const seriesNameRaw = series?.name ?? null;\n    const seriesName = seriesNameRaw && seriesNameRaw.trim().length > 0 ? seriesNameRaw : null;\n\n    if (match.kind === 'pie') {\n      // Pie series are non-cartesian; expose slice value in y so consumers can read a numeric.\n      return {\n        seriesIndex,\n        dataIndex,\n        value: [0, match.sliceValue],\n        seriesName,\n        event,\n      };\n    }\n\n    if (match.kind === 'candlestick') {\n      const timestamp = getOHLCTimestamp(match.point);\n      const close = getOHLCClose(match.point);\n      return {\n        seriesIndex,\n        dataIndex,\n        value: [timestamp, close],\n        seriesName,\n        event,\n      };\n    }\n\n    const { x, y } = getPointXY(match.match.point);\n    return {\n      seriesIndex,\n      dataIndex,\n      value: [x, y],\n      seriesName,\n      event,\n    };\n  };\n\n  const emit = (\n    eventName: ChartGPUEventName,\n    payload: ChartGPUEventPayload | ChartGPUCrosshairMovePayload | ChartGPUZoomRangeChangePayload | ChartGPUDeviceLostPayload | ChartGPUDataAppendPayload\n  ): void => {\n    if (disposed) return;\n    for (const cb of listeners[eventName]) (cb as (p: typeof payload) => void)(payload);\n  };\n\n  const setHovered = (next: HitTestMatch | null, event: PointerEvent): void => {\n    const prev = hovered;\n    hovered = next;\n\n    if (prev === null && next === null) return;\n\n    if (prev === null && next !== null) {\n      emit('mouseover', buildPayload(next, event));\n      return;\n    }\n\n    if (prev !== null && next === null) {\n      emit('mouseout', buildPayload(prev, event));\n      return;\n    }\n\n    if (prev === null || next === null) return;\n\n    const prevSeriesIndex = prev.kind === 'cartesian' ? prev.match.seriesIndex : prev.seriesIndex;\n    const prevDataIndex = prev.kind === 'cartesian' ? prev.match.dataIndex : prev.dataIndex;\n    const nextSeriesIndex = next.kind === 'cartesian' ? next.match.seriesIndex : next.seriesIndex;\n    const nextDataIndex = next.kind === 'cartesian' ? next.match.dataIndex : next.dataIndex;\n\n    const samePoint = prevSeriesIndex === nextSeriesIndex && prevDataIndex === nextDataIndex;\n    if (samePoint) return;\n\n    emit('mouseout', buildPayload(prev, event));\n    emit('mouseover', buildPayload(next, event));\n  };\n\n  const clearTapCandidateIfMatches = (e: PointerEvent): void => {\n    if (!tapCandidate) return;\n    if (!e.isPrimary) return;\n    if (e.pointerId !== tapCandidate.pointerId) return;\n    tapCandidate = null;\n  };\n\n  const onPointerMove = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (!hasHoverListeners()) return;\n    const { match, isInGrid } = getNearestPointFromPointerEvent(e);\n    if (!isInGrid) {\n      setHovered(null, e);\n      return;\n    }\n    setHovered(match, e);\n  };\n\n  const onPointerLeave = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (!hasHoverListeners() && !tapCandidate) return;\n    clearTapCandidateIfMatches(e);\n    setHovered(null, e);\n  };\n\n  const onPointerCancel = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (!hasHoverListeners() && !tapCandidate) return;\n    clearTapCandidateIfMatches(e);\n    setHovered(null, e);\n  };\n\n  const onLostPointerCapture = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (!hasHoverListeners() && !tapCandidate && suppressNextLostPointerCaptureId !== e.pointerId) return;\n    if (suppressNextLostPointerCaptureId === e.pointerId) {\n      suppressNextLostPointerCaptureId = null;\n      return;\n    }\n    clearTapCandidateIfMatches(e);\n    setHovered(null, e);\n  };\n\n  const onPointerDown = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (!hasClickListeners()) return;\n    if (!e.isPrimary) return;\n\n    // For mouse, only allow left button.\n    if (e.pointerType === 'mouse' && e.button !== 0) return;\n\n    tapCandidate = {\n      pointerId: e.pointerId,\n      startClientX: e.clientX,\n      startClientY: e.clientY,\n      startTimeMs: e.timeStamp,\n    };\n\n    // Optional pointer capture improves reliability for touch/pen.\n    try {\n      canvas.setPointerCapture(e.pointerId);\n    } catch {\n      // best-effort\n    }\n  };\n\n  const onPointerUp = (e: PointerEvent): void => {\n    if (disposed) return;\n    if (!hasClickListeners()) return;\n    if (!e.isPrimary) return;\n    if (!tapCandidate || e.pointerId !== tapCandidate.pointerId) return;\n\n    const dt = e.timeStamp - tapCandidate.startTimeMs;\n    const dx = e.clientX - tapCandidate.startClientX;\n    const dy = e.clientY - tapCandidate.startClientY;\n    const distSq = dx * dx + dy * dy;\n\n    tapCandidate = null;\n\n    // Release capture if we have it; suppress the resulting lostpointercapture.\n    try {\n      if (canvas.hasPointerCapture(e.pointerId)) {\n        suppressNextLostPointerCaptureId = e.pointerId;\n        canvas.releasePointerCapture(e.pointerId);\n      }\n    } catch {\n      // best-effort\n    }\n\n    const maxDist = DEFAULT_TAP_MAX_DISTANCE_CSS_PX;\n    const isTap = dt <= DEFAULT_TAP_MAX_TIME_MS && distSq <= maxDist * maxDist;\n    if (!isTap) return;\n\n    const { match } = getNearestPointFromPointerEvent(e);\n    emit('click', buildPayload(match, e));\n  };\n\n  canvas.addEventListener('pointermove', onPointerMove, { passive: true });\n  canvas.addEventListener('pointerleave', onPointerLeave, { passive: true });\n  canvas.addEventListener('pointercancel', onPointerCancel, { passive: true });\n  canvas.addEventListener('lostpointercapture', onLostPointerCapture, { passive: true });\n  canvas.addEventListener('pointerdown', onPointerDown, { passive: true });\n  canvas.addEventListener('pointerup', onPointerUp, { passive: true });\n\n  const dispose = (): void => {\n    if (disposed) return;\n    disposed = true;\n\n    try {\n      // Requirement: dispose order: cancel RAF, coordinator.dispose(), gpuContext.destroy(), remove canvas.\n      cancelPendingFrame();\n      disposeDataZoomUi();\n      disposeZoomResetButton();\n      unbindCoordinatorInteractionXChange();\n      unbindCoordinatorZoomRangeChange();\n      coordinator?.dispose();\n      coordinator = null;\n      coordinatorTargetFormat = null;\n      gpuContext?.destroy();\n    } finally {\n      tapCandidate = null;\n      suppressNextLostPointerCaptureId = null;\n      hovered = null;\n      interactionScalesCache = null;\n      pendingZoomSourceArmed = false;\n      pendingZoomSource = undefined;\n\n      canvas.removeEventListener('pointermove', onPointerMove);\n      canvas.removeEventListener('pointerleave', onPointerLeave);\n      canvas.removeEventListener('pointercancel', onPointerCancel);\n      canvas.removeEventListener('lostpointercapture', onLostPointerCapture);\n      canvas.removeEventListener('pointerdown', onPointerDown);\n      canvas.removeEventListener('pointerup', onPointerUp);\n\n      listeners.click.clear();\n      listeners.mouseover.clear();\n      listeners.mouseout.clear();\n      listeners.crosshairMove.clear();\n      listeners.zoomRangeChange.clear();\n      listeners.deviceLost.clear();\n      listeners.dataAppend.clear();\n      hasDataAppendListeners = false;\n\n      gpuContext = null;\n      canvas.remove();\n    }\n  };\n\n  const instance: ChartGPUInstance = {\n    get options() {\n      return currentOptions;\n    },\n    get disposed() {\n      return disposed;\n    },\n    setOption(nextOptions) {\n      if (disposed) return;\n      currentOptions = nextOptions;\n      resolvedOptions = resolveOptionsForChart(nextOptions);\n      coordinator?.setOptions(resolvedOptions);\n      initRuntimeHitTestStoreFromResolvedOptions();\n      cachedGlobalBounds = computeGlobalBounds(resolvedOptions.series, runtimeRawBoundsByIndex);\n      interactionScalesCache = null;\n      syncDataZoomUi();\n      syncZoomResetButton();\n\n      // Requirement: setOption triggers a render (and thus series parsing/extent/scales update inside render).\n      requestRender();\n    },\n    appendData(seriesIndex, newPoints) {\n      if (disposed) return;\n      if (!Number.isFinite(seriesIndex)) return;\n      if (seriesIndex < 0 || seriesIndex >= resolvedOptions.series.length) return;\n\n      const s = resolvedOptions.series[seriesIndex]!;\n      if (s.type === 'pie') {\n        // Pie series are non-cartesian and currently not supported by streaming append.\n        if (!warnedPieAppendSeries.has(seriesIndex)) {\n          warnedPieAppendSeries.add(seriesIndex);\n          console.warn(\n            `ChartGPU.appendData(${seriesIndex}, ...): pie series are not supported by streaming append. Use setOption(...) to replace pie data.`\n          );\n        }\n        return;\n      }\n\n      // Early validation: compute append count in a format-aware way.\n      // Disambiguate by series type (avoid heuristics on the data payload).\n      let pointCount = 0;\n      if (s.type === 'candlestick') {\n        if (!Array.isArray(newPoints)) return;\n        pointCount = newPoints.length;\n      } else {\n        pointCount = getCartesianPointCount(newPoints as CartesianSeriesData);\n      }\n      if (pointCount === 0) return;\n\n      // Forward to coordinator (GPU buffers + render-state updates), then keep ChartGPU's\n      // hit-testing runtime store in sync.\n      coordinator?.appendData(seriesIndex, newPoints);\n\n      // Track xExtent during append (avoids separate iteration when listeners present)\n      let appendXMin = Number.POSITIVE_INFINITY;\n      let appendXMax = Number.NEGATIVE_INFINITY;\n\n      if (s.type === 'candlestick') {\n        // Handle candlestick series with OHLC data points.\n        const existing = runtimeRawDataByIndex[seriesIndex];\n        const owned = (Array.isArray(existing) ? existing : []) as OHLCDataPoint[];\n        const ohlcPoints = newPoints as OHLCDataPoint[];\n        \n        // Track xExtent during push if listeners present\n        if (hasDataAppendListeners) {\n          for (let i = 0; i < pointCount; i++) {\n            const x = getOHLCTimestamp(ohlcPoints[i]!);\n            if (Number.isFinite(x)) {\n              if (x < appendXMin) appendXMin = x;\n              if (x > appendXMax) appendXMax = x;\n            }\n          }\n        }\n        \n        owned.push(...ohlcPoints);\n        runtimeRawDataByIndex[seriesIndex] = owned;\n\n        runtimeRawBoundsByIndex[seriesIndex] = extendBoundsWithOHLCDataPoints(\n          runtimeRawBoundsByIndex[seriesIndex],\n          ohlcPoints\n        );\n      } else {\n        // Handle other cartesian series (line, area, bar, scatter) with columnar append.\n        const owned = runtimeRawDataByIndex[seriesIndex] as MutableXYColumns;\n        const appendData = newPoints as CartesianSeriesData;\n\n        // Hoist type detection outside loops to avoid per-point type checks\n        // Check format once, then use specialized fast paths\n        // NOTE: Format detection logic is duplicated in 2 places and must stay in sync:\n        // 1. extendBoundsWithCartesianData - for bounds updates\n        // 2. appendData method (here) - for columnar store appends (also computes xExtent inline)\n        const isXYArrays = \n          typeof appendData === 'object' &&\n          appendData !== null &&\n          !Array.isArray(appendData) &&\n          'x' in appendData &&\n          'y' in appendData;\n        \n        const isInterleaved = \n          typeof appendData === 'object' &&\n          appendData !== null &&\n          !Array.isArray(appendData) &&\n          ArrayBuffer.isView(appendData);\n\n        // Track if any appended point has a size value\n        let hasAnySizeValue = false;\n        const sizesToAppend: (number | undefined)[] = new Array(pointCount);\n\n        if (isXYArrays) {\n          // Fast path for XYArraysData: direct array access without type checks\n          const xyData = appendData as { x: ArrayLike<number>; y: ArrayLike<number>; size?: ArrayLike<number> };\n          \n          // Append x, y values (and track xExtent if listeners present)\n          for (let i = 0; i < pointCount; i++) {\n            const x = xyData.x[i]!;\n            owned.x.push(x);\n            owned.y.push(xyData.y[i]!);\n            \n            // Track xExtent during iteration (avoids second O(n) pass)\n            if (hasDataAppendListeners && Number.isFinite(x)) {\n              if (x < appendXMin) appendXMin = x;\n              if (x > appendXMax) appendXMax = x;\n            }\n          }\n          \n          // Handle size array if present\n          if (xyData.size) {\n            hasAnySizeValue = true;\n            for (let i = 0; i < pointCount; i++) {\n              sizesToAppend[i] = xyData.size[i];\n            }\n          }\n        } else if (isInterleaved) {\n          // Fast path for InterleavedXYData: direct typed array access\n          const arr = appendData as Float32Array | Float64Array;\n          \n          // Append x, y values from interleaved layout (and track xExtent if listeners present)\n          for (let i = 0; i < pointCount; i++) {\n            const x = arr[i * 2]!;\n            owned.x.push(x);\n            owned.y.push(arr[i * 2 + 1]!);\n            \n            // Track xExtent during iteration\n            if (hasDataAppendListeners && Number.isFinite(x)) {\n              if (x < appendXMin) appendXMin = x;\n              if (x > appendXMax) appendXMax = x;\n            }\n          }\n          // InterleavedXYData doesn't support size\n        } else {\n          // Array<DataPoint> path: use helper functions\n          for (let i = 0; i < pointCount; i++) {\n            const x = getCartesianX(appendData, i);\n            owned.x.push(x);\n            owned.y.push(getCartesianY(appendData, i));\n            const size = getCartesianSize(appendData, i);\n            sizesToAppend[i] = size;\n            if (size !== undefined) hasAnySizeValue = true;\n            \n            // Track xExtent during iteration\n            if (hasDataAppendListeners && Number.isFinite(x)) {\n              if (x < appendXMin) appendXMin = x;\n              if (x > appendXMax) appendXMax = x;\n            }\n          }\n        }\n\n        // Handle size array alignment: ensure size array indices match x/y array indices\n        // If we've ever had a size array, keep it aligned by appending `undefined` when missing.\n        if (owned.size || hasAnySizeValue) {\n          if (!owned.size) {\n            // Backfill undefined for existing points that didn't have size values\n            owned.size = new Array(owned.x.length - pointCount);\n          }\n          // Append size values (including undefined for points without size)\n          owned.size.push(...sizesToAppend);\n        }\n\n        runtimeRawBoundsByIndex[seriesIndex] = extendBoundsWithCartesianData(\n          runtimeRawBoundsByIndex[seriesIndex],\n          appendData\n        );\n      }\n\n      cachedGlobalBounds = computeGlobalBounds(resolvedOptions.series, runtimeRawBoundsByIndex);\n\n      runtimeHitTestSeriesCache = null;\n      runtimeHitTestSeriesVersion++;\n      interactionScalesCache = null;\n\n      // Ensure a render is scheduled (coalesced) like setOption does.\n      requestRender();\n\n      // AC-6: Only emit if listeners are registered (zero-overhead when unused).\n      // xExtent was already computed during the append iteration above (no separate O(n) pass).\n      if (hasDataAppendListeners) {\n        // Normalize xExtent: return zero extent if no finite values found\n        if (!Number.isFinite(appendXMin) || !Number.isFinite(appendXMax)) {\n          appendXMin = 0;\n          appendXMax = 0;\n        }\n        \n        // Update reusable payload and emit\n        dataAppendPayload.seriesIndex = seriesIndex;\n        dataAppendPayload.count = pointCount;\n        dataAppendPayload.xExtent.min = appendXMin;\n        dataAppendPayload.xExtent.max = appendXMax;\n        emit('dataAppend', dataAppendPayload as ChartGPUDataAppendPayload);\n      }\n    },\n    renderFrame() {\n      if (disposed) return false;\n      if (deviceIsLost) return false;\n      \n      // Warn if called in auto mode\n      if (renderMode === 'auto') {\n        console.warn('renderFrame() called in auto mode - this is a no-op. Set renderMode to \"external\" to use manual rendering.');\n        return false;\n      }\n      \n      // Already rendering - prevent reentrancy\n      if (isRendering) return false;\n      \n      if (!coordinator || !gpuContext?.device) return false;\n      if (!isDirty) return false;\n      \n      try {\n        doFrame(false);\n        return true;\n      } catch {\n        return false;\n      }\n    },\n    needsRender: () => (disposed ? false : isDirty),\n    getRenderMode: () => renderMode,\n    setRenderMode(mode: RenderMode) {\n      if (disposed) return;\n      if (mode !== 'auto' && mode !== 'external') {\n        console.warn(`setRenderMode(): invalid mode '${String(mode)}', ignoring.`);\n        return;\n      }\n      if (renderMode === mode) return;\n\n      resetPerfMetricsInternal();\n      renderMode = mode;\n      if (mode === 'external') cancelPendingFrame();\n      else if (isDirty) requestRender();\n    },\n    resize,\n    dispose,\n    on(eventName, callback) {\n      if (disposed) return;\n      listeners[eventName].add(callback as AnyChartGPUEventCallback);\n      // Update hot-path flag for dataAppend event\n      if (eventName === 'dataAppend') hasDataAppendListeners = true;\n    },\n    off(eventName, callback) {\n      listeners[eventName].delete(callback as AnyChartGPUEventCallback);\n      // Update hot-path flag for dataAppend event\n      if (eventName === 'dataAppend') hasDataAppendListeners = listeners.dataAppend.size > 0;\n    },\n    getInteractionX() {\n      if (disposed) return null;\n      return coordinator?.getInteractionX() ?? null;\n    },\n    setInteractionX(x, source) {\n      if (disposed) return;\n      coordinator?.setInteractionX(x, source);\n    },\n    setCrosshairX(x, source) {\n      if (disposed) return;\n      coordinator?.setInteractionX(x, source);\n    },\n    onInteractionXChange(callback) {\n      if (disposed) return () => {};\n      return coordinator?.onInteractionXChange(callback) ?? (() => {});\n    },\n    getZoomRange() {\n      if (disposed) return null;\n      return coordinator?.getZoomRange() ?? null;\n    },\n    setZoomRange(start, end, source) {\n      if (disposed) return;\n      if (!coordinator) return;\n\n      // If data zoom is disabled, coordinator returns null and setZoomRange is a no-op.\n      const before = coordinator.getZoomRange();\n      if (!before) return;\n\n      // Mark the next coordinator zoom-range notification as originating from this API call.\n      // If a `source` token is provided, it will be forwarded to zoom listeners (loop prevention).\n      pendingZoomSourceArmed = true;\n      pendingZoomSource = source;\n\n      coordinator.setZoomRange(start, end);\n\n      // If range did not change, clear the pending token to avoid incorrectly tagging the next user zoom.\n      const after = coordinator.getZoomRange();\n      if (!after || (after.start === before.start && after.end === before.end)) {\n        pendingZoomSourceArmed = false;\n        pendingZoomSource = undefined;\n      }\n    },\n    getPerformanceMetrics() {\n      if (disposed) return null;\n      return calculatePerformanceMetrics();\n    },\n    getPerformanceCapabilities() {\n      if (disposed) return null;\n      return {\n        gpuTimingSupported: false, // Not yet implemented for main thread\n        highResTimerSupported: typeof performance !== 'undefined' && typeof performance.now === 'function',\n        performanceMetricsSupported: true,\n      };\n    },\n    onPerformanceUpdate(callback) {\n      if (disposed) return () => {};\n      performanceUpdateCallbacks.add(callback);\n      return () => {\n        performanceUpdateCallbacks.delete(callback);\n      };\n    },\n    hitTest(e) {\n      const rect = canvas.getBoundingClientRect();\n      const canvasX = e.clientX - rect.left;\n      const canvasY = e.clientY - rect.top;\n\n      // Default result for cases where rect is invalid or disposed\n      if (disposed || !(rect.width > 0) || !(rect.height > 0)) {\n        return {\n          isInGrid: false,\n          canvasX,\n          canvasY,\n          gridX: 0,\n          gridY: 0,\n          match: null,\n        };\n      }\n\n      const plotLeftCss = resolvedOptions.grid.left;\n      const plotTopCss = resolvedOptions.grid.top;\n      const plotWidthCss = rect.width - resolvedOptions.grid.left - resolvedOptions.grid.right;\n      const plotHeightCss = rect.height - resolvedOptions.grid.top - resolvedOptions.grid.bottom;\n\n      const gridX = canvasX - plotLeftCss;\n      const gridY = canvasY - plotTopCss;\n\n      // If plot dimensions are invalid, return coords but no match\n      if (!(plotWidthCss > 0) || !(plotHeightCss > 0)) {\n        return {\n          isInGrid: false,\n          canvasX,\n          canvasY,\n          gridX,\n          gridY,\n          match: null,\n        };\n      }\n\n      const isInGrid =\n        gridX >= 0 &&\n        gridX <= plotWidthCss &&\n        gridY >= 0 &&\n        gridY <= plotHeightCss;\n\n      // If outside grid, return early\n      if (!isInGrid) {\n        return {\n          isInGrid: false,\n          canvasX,\n          canvasY,\n          gridX,\n          gridY,\n          match: null,\n        };\n      }\n\n      // Compute domain and scales for hit-testing\n      const xMin = resolvedOptions.xAxis.min ?? cachedGlobalBounds.xMin;\n      const xMax = resolvedOptions.xAxis.max ?? cachedGlobalBounds.xMax;\n      const yMin = resolvedOptions.yAxis.min ?? cachedGlobalBounds.yMin;\n      const yMax = resolvedOptions.yAxis.max ?? cachedGlobalBounds.yMax;\n\n      const baseXDomain = normalizeDomain(xMin, xMax);\n      const zoomRange = coordinator?.getZoomRange() ?? null;\n      const xDomain = (() => {\n        if (!zoomRange) return baseXDomain;\n        const span = baseXDomain.max - baseXDomain.min;\n        if (!Number.isFinite(span) || span === 0) return baseXDomain;\n        const start = zoomRange.start;\n        const end = zoomRange.end;\n        const zMin = baseXDomain.min + (start / 100) * span;\n        const zMax = baseXDomain.min + (end / 100) * span;\n        return normalizeDomain(zMin, zMax);\n      })();\n      const yDomain = normalizeDomain(yMin, yMax);\n\n      // Reuse or rebuild interaction scales cache\n      const canReuseScales =\n        interactionScalesCache !== null &&\n        interactionScalesCache.rectWidthCss === rect.width &&\n        interactionScalesCache.rectHeightCss === rect.height &&\n        interactionScalesCache.plotWidthCss === plotWidthCss &&\n        interactionScalesCache.plotHeightCss === plotHeightCss &&\n        interactionScalesCache.xDomainMin === xDomain.min &&\n        interactionScalesCache.xDomainMax === xDomain.max &&\n        interactionScalesCache.yDomainMin === yDomain.min &&\n        interactionScalesCache.yDomainMax === yDomain.max;\n\n      if (!canReuseScales) {\n        const xScale = createLinearScale().domain(xDomain.min, xDomain.max).range(0, plotWidthCss);\n        const yScale = createLinearScale().domain(yDomain.min, yDomain.max).range(plotHeightCss, 0);\n        interactionScalesCache = {\n          rectWidthCss: rect.width,\n          rectHeightCss: rect.height,\n          plotWidthCss,\n          plotHeightCss,\n          xDomainMin: xDomain.min,\n          xDomainMax: xDomain.max,\n          yDomainMin: yDomain.min,\n          yDomainMax: yDomain.max,\n          xScale,\n          yScale,\n        };\n      }\n\n      const scales = interactionScalesCache!;\n\n      // Pie slice hit-testing\n      const pieMatch = (() => {\n        const maxRadiusCss = 0.5 * Math.min(plotWidthCss, plotHeightCss);\n        if (!(maxRadiusCss > 0)) return null;\n\n        for (let i = resolvedOptions.series.length - 1; i >= 0; i--) {\n          const s = resolvedOptions.series[i];\n          if (s.type !== 'pie') continue;\n          \n          // Skip invisible series\n          if (s.visible === false) continue;\n          \n          const pieSeries = s as ResolvedPieSeriesConfig;\n          const center = resolvePieCenterPlotCss(pieSeries.center, plotWidthCss, plotHeightCss);\n          const radii = resolvePieRadiiCss(pieSeries.radius, maxRadiusCss);\n          const m = findPieSlice(gridX, gridY, { seriesIndex: i, series: pieSeries }, center, radii);\n          if (!m) continue;\n\n          const v = m.slice.value;\n          return {\n            kind: 'pie' as const,\n            seriesIndex: m.seriesIndex,\n            dataIndex: m.dataIndex,\n            sliceValue: typeof v === 'number' && Number.isFinite(v) ? v : 0,\n          };\n        }\n        return null;\n      })();\n\n      if (pieMatch) {\n        return {\n          isInGrid: true,\n          canvasX,\n          canvasY,\n          gridX,\n          gridY,\n          match: {\n            kind: 'pie',\n            seriesIndex: pieMatch.seriesIndex,\n            dataIndex: pieMatch.dataIndex,\n            value: [0, pieMatch.sliceValue],\n          },\n        };\n      }\n\n      // Candlestick body hit-testing\n      for (let i = resolvedOptions.series.length - 1; i >= 0; i--) {\n        const s = resolvedOptions.series[i];\n        if (s?.type !== 'candlestick') continue;\n        \n        // Skip invisible series\n        if (s.visible === false) continue;\n\n        const seriesCfg = s as ResolvedCandlestickSeriesConfig;\n        const barWidthRange = computeCandlestickBodyWidthRange(seriesCfg, seriesCfg.data, scales.xScale, plotWidthCss);\n        const m = findCandlestick([seriesCfg], gridX, gridY, scales.xScale, scales.yScale, barWidthRange);\n        if (!m) continue;\n\n        const timestamp = getOHLCTimestamp(m.point);\n        const close = getOHLCClose(m.point);\n\n        return {\n          isInGrid: true,\n          canvasX,\n          canvasY,\n          gridX,\n          gridY,\n          match: {\n            kind: 'candlestick',\n            seriesIndex: i,\n            dataIndex: m.dataIndex,\n            value: [timestamp, close],\n          },\n        };\n      }\n\n      // Cartesian nearest-point hit-testing\n      const cartesianMatch = findNearestPoint(\n        getRuntimeHitTestSeries(),\n        gridX,\n        gridY,\n        scales.xScale,\n        scales.yScale\n      );\n\n      if (cartesianMatch) {\n        const { x, y } = getPointXY(cartesianMatch.point);\n        return {\n          isInGrid: true,\n          canvasX,\n          canvasY,\n          gridX,\n          gridY,\n          match: {\n            kind: 'cartesian',\n            seriesIndex: cartesianMatch.seriesIndex,\n            dataIndex: cartesianMatch.dataIndex,\n            value: [x, y],\n          },\n        };\n      }\n\n      // Inside grid but no match\n      return {\n        isInGrid: true,\n        canvasX,\n        canvasY,\n        gridX,\n        gridY,\n        match: null,\n      };\n    },\n  };\n\n  try {\n    // Establish initial canvas backing size before WebGPU initialization.\n    resizeInternal(false);\n\n    // Try to create GPU context; wrap errors with detailed WebGPU unavailability message\n    try {\n      // Use shared device/adapter if provided in context parameter\n      const gpuContextOptions = context\n        ? { device: context.device, adapter: context.adapter }\n        : undefined;\n      gpuContext = await GPUContext.create(canvas, gpuContextOptions);\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : String(error);\n      throw new Error(\n        `ChartGPU: WebGPU is not available.\\n` +\n        `Reason: ${errorMessage}\\n` +\n        `Browser support: Chrome/Edge 113+, Safari 18+, Firefox not yet supported.\\n` +\n        `Resources:\\n` +\n        `  - MDN WebGPU API: https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API\\n` +\n        `  - Browser compatibility: https://caniuse.com/webgpu\\n` +\n        `  - WebGPU specification: https://www.w3.org/TR/webgpu/\\n` +\n        `  - Check your system: https://webgpureport.org/`\n      );\n    }\n\n    gpuContext.device?.lost.then((info) => {\n      deviceIsLost = true;\n      if (disposed) return;\n      if (info.reason !== 'destroyed') {\n        console.warn('WebGPU device lost:', info);\n      }\n\n      // AC-6 (CGPU-SHARED-DEVICE): Emit deviceLost event only for shared (injected) devices.\n      // - Shared devices: Application manages device lifecycle; emit event for recovery coordination.\n      // - Owned devices: ChartGPU manages device internally; no event needed (dispose handles cleanup).\n      // Note: 'destroyed' reason indicates intentional destruction, not a loss event.\n      if (isSharedDevice && info.reason !== 'destroyed') {\n        emit('deviceLost', { reason: info.reason, message: info.message });\n      }\n      // Requirement: device loss routes through the same dispose path regardless of ownership.\n      dispose();\n    });\n\n    // Ensure canvas configuration matches the final measured size/format.\n    resizeInternal(false);\n\n    // Requirement: after GPUContext is initialized, create RenderCoordinator with resolved options.\n    recreateCoordinator();\n\n    // Mount data-zoom UI (if configured).\n    syncDataZoomUi();\n    syncZoomResetButton();\n\n    // Kick an initial render.\n    if (renderMode === 'auto') requestRender();\n    return instance;\n  } catch (error) {\n    instance.dispose();\n    throw error;\n  }\n}\n\nexport const ChartGPU = {\n  create: createChartGPU,\n};\n\n","import type { ChartGPU, ChartGPUCrosshairMovePayload, ChartGPUZoomRangeChangePayload } from '../ChartGPU';\n\nexport type DisconnectCharts = () => void;\n\nexport type ChartSyncOptions = Readonly<{\n  /**\n   * Sync crosshair + tooltip (interaction-x) across charts.\n   *x\n   * @default true\n   */\n  readonly syncCrosshair?: boolean;\n  /**\n   * Sync zoom/pan across charts (percent-space zoom range).\n   *\n   * @default false\n   */\n  readonly syncZoom?: boolean;\n}>;\n\n/**\n * Connects multiple charts so pointer movement in one chart drives crosshair/tooltip x\n * in the other charts (domain x sync). Returns a `disconnect()` function.\n *\n * Notes:\n * - By default, syncs interaction only (crosshair + tooltip x), not zoom/options.\n * - Enable zoom sync via `{ syncZoom: true }`.\n * - Uses a per-connection loop guard to prevent feedback.\n */\nexport function connectCharts(charts: ChartGPU[], options?: ChartSyncOptions): DisconnectCharts {\n  const syncCrosshair = options?.syncCrosshair ?? true;\n  const syncZoom = options?.syncZoom ?? false;\n\n  const connectionToken = Symbol('ChartGPU.connectCharts');\n\n  let disconnected = false;\n  const unsubscribeFns: Array<() => void> = [];\n\n  // Cache last broadcast values to avoid redundant broadcasts when values unchanged.\n  // Use explicit \"uninitialized\" sentinels so the first broadcast always propagates\n  // (important if peers are already in a non-default state and the first sync event is a reset).\n  let hasLastCrosshair = false;\n  let lastCrosshairX: number | null = null;\n  let hasLastZoom = false;\n  let lastZoomStart = 0;\n  let lastZoomEnd = 100;\n\n  const broadcastCrosshair = (sourceChart: ChartGPU, x: number | null): void => {\n    // Early-exit: avoid iteration if value unchanged (reduces work on repeated pointer moves at same x).\n    if (hasLastCrosshair && x === lastCrosshairX) return;\n    hasLastCrosshair = true;\n    lastCrosshairX = x;\n\n    for (const chart of charts) {\n      if (chart === sourceChart) continue;\n      if (chart.disposed) continue;\n      chart.setCrosshairX(x, connectionToken);\n    }\n  };\n\n  const broadcastZoom = (sourceChart: ChartGPU, start: number, end: number): void => {\n    // Early-exit: avoid iteration if range unchanged (reduces work on redundant zoom events).\n    if (hasLastZoom && start === lastZoomStart && end === lastZoomEnd) return;\n    hasLastZoom = true;\n    lastZoomStart = start;\n    lastZoomEnd = end;\n\n    for (const chart of charts) {\n      if (chart === sourceChart) continue;\n      if (chart.disposed) continue;\n      chart.setZoomRange(start, end, connectionToken);\n    }\n  };\n\n  for (const chart of charts) {\n    if (chart.disposed) continue;\n\n    if (syncCrosshair) {\n      const onCrosshairMove = (payload: ChartGPUCrosshairMovePayload): void => {\n        if (disconnected) return;\n        if (payload.source === connectionToken) return;\n        if (chart.disposed) return;\n        broadcastCrosshair(chart, payload.x);\n      };\n\n      chart.on('crosshairMove', onCrosshairMove);\n      const unsub = (): void => chart.off('crosshairMove', onCrosshairMove);\n      unsubscribeFns.push(unsub);\n    }\n\n    if (syncZoom) {\n      const onZoomRangeChange = (payload: ChartGPUZoomRangeChangePayload): void => {\n        if (disconnected) return;\n        if (payload.source === connectionToken) return;\n        // Ignore auto-scroll zoom changes to prevent syncing streaming-induced zoom adjustments\n        if (payload.sourceKind === 'auto-scroll') return;\n        if (chart.disposed) return;\n        broadcastZoom(chart, payload.start, payload.end);\n      };\n\n      chart.on('zoomRangeChange', onZoomRangeChange);\n      const unsub = (): void => chart.off('zoomRangeChange', onZoomRangeChange);\n      unsubscribeFns.push(unsub);\n    }\n  }\n\n  return () => {\n    if (disconnected) return;\n    disconnected = true;\n\n    for (const unsub of unsubscribeFns) unsub();\n    unsubscribeFns.length = 0;\n\n    // Clear any “stuck” remote interactions.\n    for (const chart of charts) {\n      if (chart.disposed) continue;\n      if (syncCrosshair) chart.setCrosshairX(null, connectionToken);\n    }\n  };\n}\n\n","/**\n * Hit testing for chart annotations\n *\n * Detects which annotation (if any) the user clicked or hovered over.\n * Uses canvas-space coordinates and configurable hit tolerances.\n */\n\nimport type { AnnotationConfig, DataPoint } from '../config/types.js';\nimport type { ChartGPUInstance } from '../ChartGPU.js';\n\nexport interface AnnotationHitTestResult {\n  readonly annotationIndex: number;\n  readonly annotation: AnnotationConfig;\n  readonly hitType: 'line' | 'text' | 'point' | 'label';\n  readonly distanceCssPx: number;\n}\n\nexport interface AnnotationHitTesterOptions {\n  readonly lineTolerance?: number;      // Default: 20px\n  readonly textTolerance?: number;      // Default: 8px\n  readonly pointTolerance?: number;     // Default: 16px\n  readonly labelTolerance?: number;     // Default: 2px\n  readonly spatialGridThreshold?: number; // Default: 20 annotations\n}\n\nexport interface AnnotationHitTester {\n  hitTest(canvasX: number, canvasY: number): AnnotationHitTestResult | null;\n  updateTextBounds(textBounds: Map<number, DOMRect>): void;\n  invalidateCache(): void;\n  dispose(): void;\n}\n\ninterface CachedAnnotationBounds {\n  canvasX?: number;\n  canvasY?: number;\n  width?: number;\n  height?: number;\n}\n\n/**\n * Creates an annotation hit tester for detecting pointer interactions\n */\nexport function createAnnotationHitTester(\n  chart: ChartGPUInstance,\n  canvas: HTMLCanvasElement,\n  options: AnnotationHitTesterOptions = {}\n): AnnotationHitTester {\n  const lineTolerance = options.lineTolerance ?? 20;\n  const textTolerance = options.textTolerance ?? 8;\n  const pointTolerance = options.pointTolerance ?? 16;\n  // labelTolerance reserved for future use\n  // spatialGridThreshold reserved for future optimization when >20 annotations\n\n  // Cache for annotation bounds in canvas-space\n  let boundsCache = new Map<number, CachedAnnotationBounds>();\n  let textBoundsCache = new Map<number, DOMRect>();\n  let cacheValid = false;\n\n  // Type guards for data points\n  const isTupleDataPoint = (p: any): p is [number, number] => Array.isArray(p);\n  const isTupleOHLCDataPoint = (p: any): p is [number, number, number, number, number] => Array.isArray(p);\n  const getPointX = (p: any): number => (isTupleDataPoint(p) ? p[0] : p.x);\n  const getPointY = (p: any): number => (isTupleDataPoint(p) ? p[1] : p.y);\n  const getOHLCTimestamp = (p: any): number => (isTupleOHLCDataPoint(p) ? p[0] : p.timestamp);\n  const getOHLCHigh = (p: any): number => (isTupleOHLCDataPoint(p) ? p[2] : p.high);\n  const getOHLCLow = (p: any): number => (isTupleOHLCDataPoint(p) ? p[3] : p.low);\n\n  /**\n   * Compute the actual X domain from series data (with zoom applied)\n   */\n  function computeXDomain(): { min: number; max: number } {\n    const opts = chart.options;\n    let xMin = opts.xAxis?.min;\n    let xMax = opts.xAxis?.max;\n\n    // If not explicitly set, derive from series data\n    if (xMin === undefined || xMax === undefined) {\n      const series = opts.series ?? [];\n      let dataXMin = Number.POSITIVE_INFINITY;\n      let dataXMax = Number.NEGATIVE_INFINITY;\n\n      for (const s of series) {\n        if (s.type === 'pie') continue;\n\n        if (s.type === 'candlestick') {\n          const data = s.data;\n          for (const p of data) {\n            const timestamp = getOHLCTimestamp(p);\n            if (timestamp < dataXMin) dataXMin = timestamp;\n            if (timestamp > dataXMax) dataXMax = timestamp;\n          }\n        } else {\n          // TODO(step 2): normalize CartesianSeriesData to ReadonlyArray<DataPoint>\n          const data = s.data as ReadonlyArray<DataPoint>;\n          for (const p of data) {\n            const x = getPointX(p);\n            if (x < dataXMin) dataXMin = x;\n            if (x > dataXMax) dataXMax = x;\n          }\n        }\n      }\n\n      if (xMin === undefined) xMin = Number.isFinite(dataXMin) ? dataXMin : 0;\n      if (xMax === undefined) xMax = Number.isFinite(dataXMax) ? dataXMax : 100;\n    }\n\n    // Apply zoom if present\n    const zoomRange = chart.getZoomRange();\n    if (zoomRange) {\n      const span = xMax - xMin;\n      const zoomMin = xMin + (zoomRange.start / 100) * span;\n      const zoomMax = xMin + (zoomRange.end / 100) * span;\n      return { min: zoomMin, max: zoomMax };\n    }\n\n    return { min: xMin, max: xMax };\n  }\n\n  /**\n   * Compute the actual Y domain from series data\n   */\n  function computeYDomain(): { min: number; max: number } {\n    const opts = chart.options;\n    let yMin = opts.yAxis?.min;\n    let yMax = opts.yAxis?.max;\n\n    // If not explicitly set, derive from series data\n    if (yMin === undefined || yMax === undefined) {\n      const series = opts.series ?? [];\n      let dataYMin = Number.POSITIVE_INFINITY;\n      let dataYMax = Number.NEGATIVE_INFINITY;\n\n      for (const s of series) {\n        if (s.type === 'pie') continue;\n\n        if (s.type === 'candlestick') {\n          const data = s.data;\n          for (const p of data) {\n            const high = getOHLCHigh(p);\n            const low = getOHLCLow(p);\n            if (high > dataYMax) dataYMax = high;\n            if (low < dataYMin) dataYMin = low;\n          }\n        } else {\n          // TODO(step 2): normalize CartesianSeriesData to ReadonlyArray<DataPoint>\n          const data = s.data as ReadonlyArray<DataPoint>;\n          for (const p of data) {\n            const y = getPointY(p);\n            if (y < dataYMin) dataYMin = y;\n            if (y > dataYMax) dataYMax = y;\n          }\n        }\n      }\n\n      if (yMin === undefined) yMin = Number.isFinite(dataYMin) ? dataYMin : 0;\n      if (yMax === undefined) yMax = Number.isFinite(dataYMax) ? dataYMax : 100;\n    }\n\n    return { min: yMin, max: yMax };\n  }\n\n  /**\n   * Convert data-space coordinates to canvas-space CSS pixels\n   */\n  function dataToCanvas(x: number | undefined, y: number | undefined): { x: number; y: number } {\n    const chartOptions = chart.options;\n    const rect = canvas.getBoundingClientRect();\n\n    const grid = chartOptions.grid ?? { left: 60, right: 20, top: 40, bottom: 40 };\n    const canvasWidth = rect.width;\n    const canvasHeight = rect.height;\n\n    const plotLeft = grid.left ?? 60;\n    const plotRight = canvasWidth - (grid.right ?? 20);\n    const plotTop = grid.top ?? 40;\n    const plotBottom = canvasHeight - (grid.bottom ?? 40);\n    const plotWidth = plotRight - plotLeft;\n    const plotHeight = plotBottom - plotTop;\n\n    // Get scales from chart\n    const xAxis = chartOptions.xAxis;\n    const yAxis = chartOptions.yAxis;\n\n    let canvasX = 0;\n    let canvasY = 0;\n\n    // Convert X coordinate\n    if (x !== undefined && xAxis) {\n      if (xAxis.type === 'category' && Array.isArray((xAxis as any).data)) {\n        // Category scale: find index and map to plot space\n        const data = (xAxis as any).data as string[];\n        const index = data.indexOf(String(x));\n        if (index >= 0) {\n          const fraction = index / (data.length - 1 || 1);\n          canvasX = plotLeft + fraction * plotWidth;\n        }\n      } else {\n        // Linear scale - compute actual domain from data\n        const domain = computeXDomain();\n        const min = domain.min;\n        const max = domain.max;\n        const fraction = (x - min) / (max - min || 1);\n        canvasX = plotLeft + fraction * plotWidth;\n      }\n    }\n\n    // Convert Y coordinate (inverted: canvas top = max Y value)\n    if (y !== undefined && yAxis) {\n      // Compute actual domain from data\n      const domain = computeYDomain();\n      const min = domain.min;\n      const max = domain.max;\n      const fraction = (y - min) / (max - min || 1);\n      canvasY = plotBottom - fraction * plotHeight; // Inverted Y\n    }\n\n    return { x: canvasX, y: canvasY };\n  }\n\n  /**\n   * Convert plot-space coordinates (0-1 fractions) to canvas-space CSS pixels\n   */\n  function plotToCanvas(x: number, y: number): { x: number; y: number } {\n    const chartOptions = chart.options;\n    const rect = canvas.getBoundingClientRect();\n\n    const grid = chartOptions.grid ?? { left: 60, right: 20, top: 40, bottom: 40 };\n    const canvasWidth = rect.width;\n    const canvasHeight = rect.height;\n\n    const plotLeft = grid.left ?? 60;\n    const plotRight = canvasWidth - (grid.right ?? 20);\n    const plotTop = grid.top ?? 40;\n    const plotBottom = canvasHeight - (grid.bottom ?? 40);\n    const plotWidth = plotRight - plotLeft;\n    const plotHeight = plotBottom - plotTop;\n\n    return {\n      x: plotLeft + x * plotWidth,\n      y: plotTop + y * plotHeight,\n    };\n  }\n\n  /**\n   * Update cached bounds for all annotations\n   */\n  function updateCache(annotations: readonly AnnotationConfig[]): void {\n    boundsCache.clear();\n\n    annotations.forEach((annotation, index) => {\n      const bounds: CachedAnnotationBounds = {};\n\n      if (annotation.type === 'lineX' && annotation.x !== undefined) {\n        const { x } = dataToCanvas(annotation.x, undefined);\n        bounds.canvasX = x;\n      } else if (annotation.type === 'lineY' && annotation.y !== undefined) {\n        const { y } = dataToCanvas(undefined, annotation.y);\n        bounds.canvasY = y;\n      } else if (annotation.type === 'point' && annotation.x !== undefined && annotation.y !== undefined) {\n        const { x, y } = dataToCanvas(annotation.x, annotation.y);\n        bounds.canvasX = x;\n        bounds.canvasY = y;\n      } else if (annotation.type === 'text') {\n        const pos = annotation.position;\n        if (pos.space === 'plot') {\n          const { x, y } = plotToCanvas(pos.x, pos.y);\n          bounds.canvasX = x;\n          bounds.canvasY = y;\n        } else if (pos.space === 'data') {\n          const { x, y } = dataToCanvas(pos.x, pos.y);\n          bounds.canvasX = x;\n          bounds.canvasY = y;\n        }\n      }\n\n      boundsCache.set(index, bounds);\n    });\n\n    cacheValid = true;\n  }\n\n  /**\n   * Calculate distance from pointer to a line (vertical or horizontal)\n   */\n  function distanceToLine(\n    pointerX: number,\n    pointerY: number,\n    lineX?: number,\n    lineY?: number\n  ): number {\n    if (lineX !== undefined) {\n      // Vertical line: distance is horizontal difference\n      return Math.abs(pointerX - lineX);\n    } else if (lineY !== undefined) {\n      // Horizontal line: distance is vertical difference\n      return Math.abs(pointerY - lineY);\n    }\n    return Infinity;\n  }\n\n  /**\n   * Calculate distance from pointer to a point\n   */\n  function distanceToPoint(\n    pointerX: number,\n    pointerY: number,\n    pointX: number,\n    pointY: number\n  ): number {\n    const dx = pointerX - pointX;\n    const dy = pointerY - pointY;\n    return Math.sqrt(dx * dx + dy * dy);\n  }\n\n  /**\n   * Check if pointer is inside a rectangle (with tolerance padding)\n   */\n  function isInsideRect(\n    pointerX: number,\n    pointerY: number,\n    rect: DOMRect,\n    tolerance: number\n  ): boolean {\n    return (\n      pointerX >= rect.left - tolerance &&\n      pointerX <= rect.right + tolerance &&\n      pointerY >= rect.top - tolerance &&\n      pointerY <= rect.bottom + tolerance\n    );\n  }\n\n  /**\n   * Perform hit test at the given canvas position\n   */\n  function hitTest(canvasX: number, canvasY: number): AnnotationHitTestResult | null {\n    const annotations = chart.options.annotations ?? [];\n\n    if (annotations.length === 0) {\n      return null;\n    }\n\n    // Update cache if invalid\n    if (!cacheValid) {\n      updateCache(annotations);\n    }\n\n    let closestHit: AnnotationHitTestResult | null = null;\n    let closestDistance = Infinity;\n\n    // Priority order: Labels > Points > Text > Lines\n    // Test in reverse priority order (lines first) so higher priority overwrites\n\n    // 1. Test lines (lowest priority)\n    for (let i = 0; i < annotations.length; i++) {\n      const annotation = annotations[i];\n      const bounds = boundsCache.get(i);\n\n      if (!bounds) {\n        continue;\n      }\n\n      if (annotation.type === 'lineX' && bounds.canvasX !== undefined) {\n        const distance = distanceToLine(canvasX, canvasY, bounds.canvasX, undefined);\n        if (distance <= lineTolerance && distance < closestDistance) {\n          closestDistance = distance;\n          closestHit = {\n            annotationIndex: i,\n            annotation,\n            hitType: 'line',\n            distanceCssPx: distance,\n          };\n        }\n      } else if (annotation.type === 'lineY' && bounds.canvasY !== undefined) {\n        const distance = distanceToLine(canvasX, canvasY, undefined, bounds.canvasY);\n        if (distance <= lineTolerance && distance < closestDistance) {\n          closestDistance = distance;\n          closestHit = {\n            annotationIndex: i,\n            annotation,\n            hitType: 'line',\n            distanceCssPx: distance,\n          };\n        }\n      }\n    }\n\n    // 2. Test text (medium priority)\n    for (let i = 0; i < annotations.length; i++) {\n      const annotation = annotations[i];\n      const textRect = textBoundsCache.get(i);\n\n      if (annotation.type === 'text' && textRect) {\n        if (isInsideRect(canvasX, canvasY, textRect, textTolerance)) {\n          const centerX = textRect.left + textRect.width / 2;\n          const centerY = textRect.top + textRect.height / 2;\n          const distance = distanceToPoint(canvasX, canvasY, centerX, centerY);\n\n          if (distance < closestDistance) {\n            closestDistance = distance;\n            closestHit = {\n              annotationIndex: i,\n              annotation,\n              hitType: 'text',\n              distanceCssPx: distance,\n            };\n          }\n        }\n      }\n    }\n\n    // 3. Test points (high priority)\n    for (let i = 0; i < annotations.length; i++) {\n      const annotation = annotations[i];\n      const bounds = boundsCache.get(i);\n\n      if (bounds && annotation.type === 'point' && bounds.canvasX !== undefined && bounds.canvasY !== undefined) {\n        const distance = distanceToPoint(canvasX, canvasY, bounds.canvasX, bounds.canvasY);\n        if (distance <= pointTolerance && distance < closestDistance) {\n          closestDistance = distance;\n          closestHit = {\n            annotationIndex: i,\n            annotation,\n            hitType: 'point',\n            distanceCssPx: distance,\n          };\n        }\n      }\n    }\n\n    // 4. Test labels (highest priority) - TODO: implement once label bounds are available\n\n    return closestHit;\n  }\n\n  /**\n   * Update text bounds from DOM measurements\n   */\n  function updateTextBounds(textBounds: Map<number, DOMRect>): void {\n    textBoundsCache = new Map(textBounds);\n  }\n\n  /**\n   * Invalidate cache (call on zoom, pan, or resize)\n   */\n  function invalidateCache(): void {\n    cacheValid = false;\n  }\n\n  /**\n   * Dispose of resources\n   */\n  function dispose(): void {\n    boundsCache.clear();\n    textBoundsCache.clear();\n  }\n\n  return {\n    hitTest,\n    updateTextBounds,\n    invalidateCache,\n    dispose,\n  };\n}\n","/**\n * Drag handler for repositioning annotations\n *\n * Handles dragging annotations to reposition them:\n * - lineX: constrained to horizontal movement\n * - lineY: constrained to vertical movement\n * - text: free 2D movement (data or plot space)\n * - point: free 2D movement (data space)\n *\n * Uses optimistic updates during drag for 60 FPS performance.\n */\n\nimport type { AnnotationConfig, DataPoint } from '../config/types.js';\nimport type { ChartGPUInstance } from '../ChartGPU.js';\n\nexport interface AnnotationDragCallbacks {\n  onDragMove: (index: number, updates: Partial<AnnotationConfig>) => void;\n  onDragEnd: (index: number, updates: Partial<AnnotationConfig>) => void;\n  onDragCancel: () => void;\n}\n\nexport interface AnnotationDragHandler {\n  startDrag(\n    annotationIndex: number,\n    annotation: AnnotationConfig,\n    startPointerX: number,\n    startPointerY: number\n  ): void;\n  isDragging(): boolean;\n  dispose(): void;\n}\n\ninterface DragState {\n  annotationIndex: number;\n  annotation: AnnotationConfig;\n  startPointerX: number;\n  startPointerY: number;\n  pointerId: number | null;\n}\n\n/**\n * Creates a drag handler for repositioning annotations\n */\nexport function createAnnotationDragHandler(\n  chart: ChartGPUInstance,\n  canvas: HTMLCanvasElement,\n  callbacks: AnnotationDragCallbacks\n): AnnotationDragHandler {\n  let dragState: DragState | null = null;\n\n  // Type guards for data points\n  const isTupleDataPoint = (p: any): p is [number, number] => Array.isArray(p);\n  const isTupleOHLCDataPoint = (p: any): p is [number, number, number, number, number] => Array.isArray(p);\n  const getPointX = (p: any): number => (isTupleDataPoint(p) ? p[0] : p.x);\n  const getPointY = (p: any): number => (isTupleDataPoint(p) ? p[1] : p.y);\n  const getOHLCTimestamp = (p: any): number => (isTupleOHLCDataPoint(p) ? p[0] : p.timestamp);\n  const getOHLCHigh = (p: any): number => (isTupleOHLCDataPoint(p) ? p[2] : p.high);\n  const getOHLCLow = (p: any): number => (isTupleOHLCDataPoint(p) ? p[3] : p.low);\n\n  /**\n   * Compute the actual X domain from series data (with zoom applied)\n   */\n  function computeXDomain(): { min: number; max: number } {\n    const opts = chart.options;\n    let xMin = opts.xAxis?.min;\n    let xMax = opts.xAxis?.max;\n\n    if (xMin === undefined || xMax === undefined) {\n      const series = opts.series ?? [];\n      let dataXMin = Number.POSITIVE_INFINITY;\n      let dataXMax = Number.NEGATIVE_INFINITY;\n\n      for (const s of series) {\n        if (s.type === 'pie') continue;\n\n        if (s.type === 'candlestick') {\n          const data = s.data;\n          for (const p of data) {\n            const timestamp = getOHLCTimestamp(p);\n            if (timestamp < dataXMin) dataXMin = timestamp;\n            if (timestamp > dataXMax) dataXMax = timestamp;\n          }\n        } else {\n          // TODO(step 2): normalize CartesianSeriesData to ReadonlyArray<DataPoint>\n          const data = s.data as ReadonlyArray<DataPoint>;\n          for (const p of data) {\n            const x = getPointX(p);\n            if (x < dataXMin) dataXMin = x;\n            if (x > dataXMax) dataXMax = x;\n          }\n        }\n      }\n\n      if (xMin === undefined) xMin = Number.isFinite(dataXMin) ? dataXMin : 0;\n      if (xMax === undefined) xMax = Number.isFinite(dataXMax) ? dataXMax : 100;\n    }\n\n    const zoomRange = chart.getZoomRange();\n    if (zoomRange) {\n      const span = xMax - xMin;\n      const zoomMin = xMin + (zoomRange.start / 100) * span;\n      const zoomMax = xMin + (zoomRange.end / 100) * span;\n      return { min: zoomMin, max: zoomMax };\n    }\n\n    return { min: xMin, max: xMax };\n  }\n\n  /**\n   * Compute the actual Y domain from series data\n   */\n  function computeYDomain(): { min: number; max: number } {\n    const opts = chart.options;\n    let yMin = opts.yAxis?.min;\n    let yMax = opts.yAxis?.max;\n\n    if (yMin === undefined || yMax === undefined) {\n      const series = opts.series ?? [];\n      let dataYMin = Number.POSITIVE_INFINITY;\n      let dataYMax = Number.NEGATIVE_INFINITY;\n\n      for (const s of series) {\n        if (s.type === 'pie') continue;\n\n        if (s.type === 'candlestick') {\n          const data = s.data;\n          for (const p of data) {\n            const high = getOHLCHigh(p);\n            const low = getOHLCLow(p);\n            if (high > dataYMax) dataYMax = high;\n            if (low < dataYMin) dataYMin = low;\n          }\n        } else {\n          // TODO(step 2): normalize CartesianSeriesData to ReadonlyArray<DataPoint>\n          const data = s.data as ReadonlyArray<DataPoint>;\n          for (const p of data) {\n            const y = getPointY(p);\n            if (y < dataYMin) dataYMin = y;\n            if (y > dataYMax) dataYMax = y;\n          }\n        }\n      }\n\n      if (yMin === undefined) yMin = Number.isFinite(dataYMin) ? dataYMin : 0;\n      if (yMax === undefined) yMax = Number.isFinite(dataYMax) ? dataYMax : 100;\n    }\n\n    return { min: yMin, max: yMax };\n  }\n\n  /**\n   * Convert canvas-space CSS pixels to data-space coordinates\n   */\n  function canvasToData(canvasX: number, canvasY: number): { x: number; y: number } {\n    const chartOptions = chart.options;\n    const rect = canvas.getBoundingClientRect();\n\n    const grid = chartOptions.grid ?? { left: 60, right: 20, top: 40, bottom: 40 };\n    const canvasWidth = rect.width;\n    const canvasHeight = rect.height;\n\n    const plotLeft = grid.left ?? 60;\n    const plotRight = canvasWidth - (grid.right ?? 20);\n    const plotTop = grid.top ?? 40;\n    const plotBottom = canvasHeight - (grid.bottom ?? 40);\n    const plotWidth = plotRight - plotLeft;\n    const plotHeight = plotBottom - plotTop;\n\n    const xAxis = chartOptions.xAxis;\n    const yAxis = chartOptions.yAxis;\n\n    let dataX = 0;\n    let dataY = 0;\n\n    // Convert X coordinate\n    if (xAxis) {\n      const xFraction = (canvasX - plotLeft) / plotWidth;\n      if (xAxis.type === 'category' && Array.isArray((xAxis as any).data)) {\n        // Category scale: map fraction to category index\n        const data = (xAxis as any).data as any[];\n        const index = Math.round(xFraction * (data.length - 1 || 1));\n        dataX = data[Math.max(0, Math.min(index, data.length - 1))] as number;\n      } else {\n        // Linear scale - compute actual domain from data\n        const domain = computeXDomain();\n        dataX = domain.min + xFraction * (domain.max - domain.min);\n      }\n    }\n\n    // Convert Y coordinate (inverted: canvas top = max Y value)\n    if (yAxis) {\n      const yFraction = (plotBottom - canvasY) / plotHeight;\n      // Compute actual domain from data\n      const domain = computeYDomain();\n      dataY = domain.min + yFraction * (domain.max - domain.min);\n    }\n\n    return { x: dataX, y: dataY };\n  }\n\n  /**\n   * Convert canvas-space CSS pixels to plot-space coordinates (0-1 fractions)\n   */\n  function canvasToPlot(canvasX: number, canvasY: number): { x: number; y: number } {\n    const chartOptions = chart.options;\n    const rect = canvas.getBoundingClientRect();\n\n    const grid = chartOptions.grid ?? { left: 60, right: 20, top: 40, bottom: 40 };\n    const canvasWidth = rect.width;\n    const canvasHeight = rect.height;\n\n    const plotLeft = grid.left ?? 60;\n    const plotRight = canvasWidth - (grid.right ?? 20);\n    const plotTop = grid.top ?? 40;\n    const plotBottom = canvasHeight - (grid.bottom ?? 40);\n    const plotWidth = plotRight - plotLeft;\n    const plotHeight = plotBottom - plotTop;\n\n    const x = (canvasX - plotLeft) / plotWidth;\n    const y = (canvasY - plotTop) / plotHeight;\n\n    // Clamp to [0, 1]\n    return {\n      x: Math.max(0, Math.min(1, x)),\n      y: Math.max(0, Math.min(1, y)),\n    };\n  }\n\n  /**\n   * Handle pointer move during drag\n   */\n  function onPointerMove(e: PointerEvent): void {\n    if (!dragState) {\n      return;\n    }\n\n    e.preventDefault();\n\n    const rect = canvas.getBoundingClientRect();\n    const canvasX = e.clientX - rect.left;\n    const canvasY = e.clientY - rect.top;\n\n    const annotation = dragState.annotation;\n    const updates: any = {}; // Use 'any' to bypass TypeScript union narrowing issues\n\n    if (annotation.type === 'lineX') {\n      // Constrain to horizontal movement only\n      const { x } = canvasToData(canvasX, 0);\n      updates.x = x;\n    } else if (annotation.type === 'lineY') {\n      // Constrain to vertical movement only\n      const { y } = canvasToData(0, canvasY);\n      updates.y = y;\n    } else if (annotation.type === 'text') {\n      // Free 2D movement (respect original space)\n      const space = annotation.position.space;\n      if (space === 'plot') {\n        const { x, y } = canvasToPlot(canvasX, canvasY);\n        updates.position = { space, x, y };\n      } else {\n        // data space\n        const { x, y } = canvasToData(canvasX, canvasY);\n        updates.position = { space, x, y };\n      }\n    } else if (annotation.type === 'point') {\n      // Free 2D movement in data space\n      const { x, y } = canvasToData(canvasX, canvasY);\n      updates.x = x;\n      updates.y = y;\n    }\n\n    // Optimistic update (no history push)\n    callbacks.onDragMove(dragState.annotationIndex, updates);\n  }\n\n  /**\n   * Handle pointer up (drag end)\n   */\n  function onPointerUp(e: PointerEvent): void {\n    if (!dragState) return;\n\n    const rect = canvas.getBoundingClientRect();\n    const canvasX = e.clientX - rect.left;\n    const canvasY = e.clientY - rect.top;\n\n    const annotation = dragState.annotation;\n    const updates: any = {}; // Use 'any' to bypass TypeScript union narrowing issues\n\n    if (annotation.type === 'lineX') {\n      const { x } = canvasToData(canvasX, 0);\n      updates.x = x;\n    } else if (annotation.type === 'lineY') {\n      const { y } = canvasToData(0, canvasY);\n      updates.y = y;\n    } else if (annotation.type === 'text') {\n      const space = annotation.position.space;\n      if (space === 'plot') {\n        const { x, y } = canvasToPlot(canvasX, canvasY);\n        updates.position = { space, x, y };\n      } else {\n        const { x, y } = canvasToData(canvasX, canvasY);\n        updates.position = { space, x, y };\n      }\n    } else if (annotation.type === 'point') {\n      const { x, y } = canvasToData(canvasX, canvasY);\n      updates.x = x;\n      updates.y = y;\n    }\n\n    // Push single history entry with final position\n    callbacks.onDragEnd(dragState.annotationIndex, updates);\n\n    cleanup();\n  }\n\n  /**\n   * Handle pointer cancel (drag cancelled)\n   */\n  function onPointerCancel(): void {\n    if (!dragState) return;\n\n    // Revert to original position\n    callbacks.onDragCancel();\n\n    cleanup();\n  }\n\n  /**\n   * Handle keyboard events during drag\n   */\n  function onKeyDown(e: KeyboardEvent): void {\n    if (!dragState) return;\n\n    if (e.key === 'Escape') {\n      e.preventDefault();\n      onPointerCancel();\n    }\n  }\n\n  /**\n   * Cleanup drag state and event listeners\n   */\n  function cleanup(): void {\n    if (!dragState) return;\n\n    window.removeEventListener('pointermove', onPointerMove);\n    window.removeEventListener('pointerup', onPointerUp);\n    window.removeEventListener('pointercancel', onPointerCancel);\n    document.removeEventListener('keydown', onKeyDown);\n\n    if (dragState.pointerId !== null) {\n      try {\n        canvas.releasePointerCapture(dragState.pointerId);\n      } catch (err) {\n        // Ignore errors (pointer may already be released)\n      }\n    }\n\n    document.body.style.cursor = '';\n    dragState = null;\n  }\n\n  /**\n   * Start dragging an annotation\n   */\n  function startDrag(\n    annotationIndex: number,\n    annotation: AnnotationConfig,\n    startPointerX: number,\n    startPointerY: number\n  ): void {\n    // Cancel any existing drag\n    if (dragState) {\n      cleanup();\n    }\n\n    dragState = {\n      annotationIndex,\n      annotation,\n      startPointerX,\n      startPointerY,\n      pointerId: null,\n    };\n\n    // Set cursor based on annotation type\n    if (annotation.type === 'lineX') {\n      document.body.style.cursor = 'ew-resize';\n    } else if (annotation.type === 'lineY') {\n      document.body.style.cursor = 'ns-resize';\n    } else {\n      document.body.style.cursor = 'grabbing';\n    }\n\n    // Attach window-level listeners for smooth dragging outside canvas\n    window.addEventListener('pointermove', onPointerMove, { passive: false });\n    window.addEventListener('pointerup', onPointerUp, { passive: true });\n    window.addEventListener('pointercancel', onPointerCancel, { passive: true });\n    document.addEventListener('keydown', onKeyDown, { passive: false });\n\n    // Visual feedback: reduce opacity\n    callbacks.onDragMove(annotationIndex, {\n      style: { ...annotation.style, opacity: 0.7 },\n    });\n  }\n\n  /**\n   * Check if currently dragging\n   */\n  function isDragging(): boolean {\n    return dragState !== null;\n  }\n\n  /**\n   * Dispose of resources\n   */\n  function dispose(): void {\n    cleanup();\n  }\n\n  return {\n    startDrag,\n    isDragging,\n    dispose,\n  };\n}\n","/**\n * Configuration dialog for creating and editing annotations\n *\n * Provides a modal dialog for setting annotation properties:\n * - Label, color, line style, line width (for lines)\n * - Text content, color, font size (for text annotations)\n * - Label, color, marker size (for point annotations)\n */\n\nimport type { AnnotationConfig } from '../config/types.js';\n\nexport interface AnnotationConfigDialogOptions {\n  readonly palette?: readonly string[];\n  readonly zIndex?: number;\n}\n\nexport interface AnnotationConfigDialog {\n  showCreate(\n    annotationType: 'lineX' | 'lineY' | 'text' | 'point',\n    defaults: Partial<AnnotationConfig>,\n    onSave: (config: AnnotationConfig) => void,\n    onCancel: () => void\n  ): void;\n\n  showEdit(\n    annotation: AnnotationConfig,\n    onSave: (updates: Partial<AnnotationConfig>) => void,\n    onCancel: () => void\n  ): void;\n\n  hide(): void;\n  dispose(): void;\n}\n\nconst HIGH_CONTRAST_PALETTE = [\n  '#ef4444', // Red (critical)\n  '#f97316', // Orange (warning)\n  '#eab308', // Yellow (caution)\n  '#22c55e', // Green (success)\n  '#06b6d4', // Cyan (info)\n  '#3b82f6', // Blue (primary)\n  '#8b5cf6', // Purple (accent)\n  '#ec4899', // Pink (highlight)\n  '#ffffff', // White (high contrast)\n  '#94a3b8', // Gray (neutral)\n  '#64748b', // Dark gray (subtle)\n  '#1e293b', // Near-black (background)\n] as const;\n\n/**\n * Creates a configuration dialog for annotations\n */\nexport function createAnnotationConfigDialog(\n  container: HTMLElement,\n  options: AnnotationConfigDialogOptions = {}\n): AnnotationConfigDialog {\n  const palette = options.palette ?? HIGH_CONTRAST_PALETTE;\n  const zIndex = options.zIndex ?? 1000;\n\n  let overlay: HTMLDivElement | null = null;\n  let dialog: HTMLDivElement | null = null;\n  let currentOnSave: ((config: any) => void) | null = null;\n  let currentOnCancel: (() => void) | null = null;\n  let mode: 'create' | 'edit' = 'create';\n\n  /**\n   * Create the modal overlay\n   */\n  function createOverlay(): HTMLDivElement {\n    const div = document.createElement('div');\n    div.style.cssText = `\n      position: fixed;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      background: rgba(0, 0, 0, 0.4);\n      z-index: ${zIndex + 1};\n      display: flex;\n      align-items: center;\n      justify-content: center;\n    `;\n    div.addEventListener('click', (e) => {\n      if (e.target === div) {\n        handleCancel();\n      }\n    });\n    return div;\n  }\n\n  /**\n   * Create the dialog box\n   */\n  function createDialog(): HTMLDivElement {\n    const div = document.createElement('div');\n    div.style.cssText = `\n      width: 320px;\n      background: #1a1a2e;\n      border: 1px solid #333;\n      border-radius: 8px;\n      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.7);\n      padding: 20px;\n      font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n      color: #e0e0e0;\n    `;\n    return div;\n  }\n\n  /**\n   * Create a form field container\n   */\n  function createField(label: string, input: HTMLElement): HTMLDivElement {\n    const field = document.createElement('div');\n    field.style.cssText = `\n      margin-bottom: 16px;\n    `;\n\n    const labelEl = document.createElement('label');\n    labelEl.textContent = label;\n    labelEl.style.cssText = `\n      display: block;\n      margin-bottom: 8px;\n      font-size: 13px;\n      font-weight: 500;\n      color: #b0b0b0;\n    `;\n\n    field.appendChild(labelEl);\n    field.appendChild(input);\n    return field;\n  }\n\n  /**\n   * Create a text input\n   */\n  function createTextInput(placeholder: string, maxLength: number, defaultValue = ''): HTMLInputElement {\n    const input = document.createElement('input');\n    input.type = 'text';\n    input.placeholder = placeholder;\n    input.maxLength = maxLength;\n    input.value = defaultValue;\n    input.style.cssText = `\n      width: 100%;\n      padding: 8px 12px;\n      background: #2a2a3e;\n      border: 1px solid #444;\n      border-radius: 4px;\n      color: #e0e0e0;\n      font-size: 14px;\n      box-sizing: border-box;\n    `;\n    input.addEventListener('focus', () => {\n      input.style.borderColor = '#3b82f6';\n    });\n    input.addEventListener('blur', () => {\n      input.style.borderColor = '#444';\n    });\n    return input;\n  }\n\n  /**\n   * Create a textarea\n   */\n  function createTextArea(placeholder: string, maxLength: number, defaultValue = ''): HTMLTextAreaElement {\n    const textarea = document.createElement('textarea');\n    textarea.placeholder = placeholder;\n    textarea.maxLength = maxLength;\n    textarea.value = defaultValue;\n    textarea.rows = 3;\n    textarea.style.cssText = `\n      width: 100%;\n      padding: 8px 12px;\n      background: #2a2a3e;\n      border: 1px solid #444;\n      border-radius: 4px;\n      color: #e0e0e0;\n      font-size: 14px;\n      box-sizing: border-box;\n      resize: vertical;\n      font-family: inherit;\n    `;\n    textarea.addEventListener('focus', () => {\n      textarea.style.borderColor = '#3b82f6';\n    });\n    textarea.addEventListener('blur', () => {\n      textarea.style.borderColor = '#444';\n    });\n    return textarea;\n  }\n\n  /**\n   * Create a color picker grid\n   */\n  function createColorPicker(selectedColor: string): { container: HTMLDivElement; getValue: () => string } {\n    const container = document.createElement('div');\n    container.style.cssText = `\n      display: grid;\n      grid-template-columns: repeat(4, 1fr);\n      gap: 8px;\n    `;\n\n    let currentColor = selectedColor;\n\n    palette.forEach((color) => {\n      // Button wrapper for swatch\n      const swatch = document.createElement('button');\n      swatch.type = 'button';\n      swatch.dataset.color = color; // Store color for reliable comparison\n      swatch.style.cssText = `\n        position: relative;\n        width: 100%;\n        aspect-ratio: 1;\n        background: ${color};\n        border: none;\n        border-radius: 4px;\n        cursor: pointer;\n        transition: transform 0.15s ease-out, box-shadow 0.15s ease-out;\n        box-shadow: ${color === currentColor ? 'inset 0 0 0 2px #ffffff, inset 0 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.3)' : 'inset 0 0 0 1px rgba(255, 255, 255, 0.1)'};\n        transform: ${color === currentColor ? 'scale(1.05)' : 'scale(1)'};\n      `;\n\n      // Create checkmark SVG for selected state\n      const checkmark = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n      checkmark.setAttribute('viewBox', '0 0 24 24');\n      checkmark.setAttribute('fill', 'none');\n      checkmark.style.cssText = `\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n        width: 60%;\n        height: 60%;\n        pointer-events: none;\n        opacity: ${color === currentColor ? '1' : '0'};\n        transition: opacity 0.15s ease-out;\n      `;\n\n      // Checkmark path with dual stroke for maximum contrast\n      const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n      path.setAttribute('d', 'M20 6L9 17l-5-5');\n      path.setAttribute('stroke', '#ffffff');\n      path.setAttribute('stroke-width', '3');\n      path.setAttribute('stroke-linecap', 'round');\n      path.setAttribute('stroke-linejoin', 'round');\n      path.style.cssText = `\n        filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.8)) drop-shadow(0 0 1px rgba(0, 0, 0, 0.9));\n      `;\n\n      checkmark.appendChild(path);\n      swatch.appendChild(checkmark);\n\n      // Click handler\n      swatch.addEventListener('click', () => {\n        currentColor = color;\n        // Update all swatches\n        Array.from(container.children).forEach((child) => {\n          const btn = child as HTMLButtonElement;\n          const btnColor = btn.dataset.color; // Use data attribute for reliable comparison\n          const isSelected = btnColor === color;\n\n          // Update box-shadow for border effect\n          btn.style.boxShadow = isSelected\n            ? 'inset 0 0 0 2px #ffffff, inset 0 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.3)'\n            : 'inset 0 0 0 1px rgba(255, 255, 255, 0.1)';\n\n          // Update scale\n          btn.style.transform = isSelected ? 'scale(1.05)' : 'scale(1)';\n\n          // Update checkmark visibility\n          const svg = btn.querySelector('svg');\n          if (svg) {\n            svg.style.opacity = isSelected ? '1' : '0';\n          }\n        });\n      });\n\n      // Hover effects\n      swatch.addEventListener('mouseenter', () => {\n        if (color !== currentColor) {\n          swatch.style.transform = 'scale(1.1)';\n          swatch.style.boxShadow = 'inset 0 0 0 2px rgba(255, 255, 255, 0.2), 0 4px 12px rgba(0, 0, 0, 0.4)';\n        }\n      });\n\n      swatch.addEventListener('mouseleave', () => {\n        if (color !== currentColor) {\n          swatch.style.transform = 'scale(1)';\n          swatch.style.boxShadow = 'inset 0 0 0 1px rgba(255, 255, 255, 0.1)';\n        }\n      });\n\n      container.appendChild(swatch);\n    });\n\n    return {\n      container,\n      getValue: () => currentColor,\n    };\n  }\n\n  /**\n   * Create a dropdown select\n   */\n  function createDropdown(\n    options: Array<{ label: string; value: string }>,\n    defaultValue: string\n  ): { container: HTMLSelectElement; getValue: () => string } {\n    const select = document.createElement('select');\n    select.style.cssText = `\n      width: 100%;\n      padding: 8px 12px;\n      background: #2a2a3e;\n      border: 1px solid #444;\n      border-radius: 4px;\n      color: #e0e0e0;\n      font-size: 14px;\n      cursor: pointer;\n    `;\n\n    options.forEach((opt) => {\n      const option = document.createElement('option');\n      option.value = opt.value;\n      option.textContent = opt.label;\n      if (opt.value === defaultValue) {\n        option.selected = true;\n      }\n      select.appendChild(option);\n    });\n\n    return {\n      container: select,\n      getValue: () => select.value,\n    };\n  }\n\n  /**\n   * Create a slider with numeric value display\n   */\n  function createSlider(\n    min: number,\n    max: number,\n    step: number,\n    defaultValue: number,\n    unit = ''\n  ): { container: HTMLDivElement; getValue: () => number } {\n    const container = document.createElement('div');\n    container.style.cssText = `\n      display: flex;\n      align-items: center;\n      gap: 12px;\n    `;\n\n    const slider = document.createElement('input');\n    slider.type = 'range';\n    slider.min = String(min);\n    slider.max = String(max);\n    slider.step = String(step);\n    slider.value = String(defaultValue);\n    slider.style.cssText = `\n      flex: 1;\n      cursor: pointer;\n    `;\n\n    const valueDisplay = document.createElement('span');\n    valueDisplay.textContent = `${defaultValue}${unit}`;\n    valueDisplay.style.cssText = `\n      min-width: 40px;\n      text-align: right;\n      font-size: 13px;\n      color: #b0b0b0;\n    `;\n\n    slider.addEventListener('input', () => {\n      valueDisplay.textContent = `${slider.value}${unit}`;\n    });\n\n    container.appendChild(slider);\n    container.appendChild(valueDisplay);\n\n    return {\n      container,\n      getValue: () => parseFloat(slider.value),\n    };\n  }\n\n  /**\n   * Create action buttons\n   */\n  function createButtons(saveLabel: string, onSaveClick: () => void, onCancelClick: () => void): HTMLDivElement {\n    const container = document.createElement('div');\n    container.style.cssText = `\n      display: flex;\n      justify-content: flex-end;\n      gap: 12px;\n      margin-top: 24px;\n    `;\n\n    const cancelBtn = document.createElement('button');\n    cancelBtn.type = 'button';\n    cancelBtn.textContent = 'Cancel';\n    cancelBtn.style.cssText = `\n      padding: 8px 16px;\n      background: transparent;\n      border: 1px solid #444;\n      border-radius: 4px;\n      color: #888;\n      font-size: 14px;\n      cursor: pointer;\n      transition: background 0.15s;\n    `;\n    cancelBtn.addEventListener('mouseenter', () => {\n      cancelBtn.style.background = '#2a2a3e';\n    });\n    cancelBtn.addEventListener('mouseleave', () => {\n      cancelBtn.style.background = 'transparent';\n    });\n    cancelBtn.addEventListener('click', onCancelClick);\n\n    const saveBtn = document.createElement('button');\n    saveBtn.type = 'button';\n    saveBtn.textContent = saveLabel;\n    saveBtn.style.cssText = `\n      padding: 8px 16px;\n      background: #2a2a3e;\n      border: 1px solid #444;\n      border-radius: 4px;\n      color: #e0e0e0;\n      font-size: 14px;\n      font-weight: 500;\n      cursor: pointer;\n      transition: background 0.15s;\n    `;\n    saveBtn.addEventListener('mouseenter', () => {\n      saveBtn.style.background = '#3a3a4e';\n    });\n    saveBtn.addEventListener('mouseleave', () => {\n      saveBtn.style.background = '#2a2a3e';\n    });\n    saveBtn.addEventListener('click', onSaveClick);\n\n    container.appendChild(cancelBtn);\n    container.appendChild(saveBtn);\n\n    return container;\n  }\n\n  /**\n   * Build form for line annotations (lineX/lineY)\n   */\n  function buildLineForm(defaults: Partial<AnnotationConfig>): HTMLDivElement {\n    const form = document.createElement('div');\n\n    // Extract label text from label object (if it exists)\n    const defaultLabelText = (defaults as any).label?.text ?? '';\n    const labelInput = createTextInput('Line label', 100, defaultLabelText);\n    const colorPicker = createColorPicker(defaults.style?.color ?? palette[0]);\n    const lineStyleDropdown = createDropdown(\n      [\n        { label: 'Solid', value: 'solid' },\n        { label: 'Dashed', value: 'dashed' },\n        { label: 'Dotted', value: 'dotted' },\n      ],\n      defaults.style?.lineDash ? (defaults.style.lineDash.length === 4 ? 'dashed' : 'dotted') : 'solid'\n    );\n    const lineWidthSlider = createSlider(1, 8, 1, defaults.style?.lineWidth ?? 2, 'px');\n\n    form.appendChild(createField('Label (optional)', labelInput));\n    form.appendChild(createField('Color', colorPicker.container));\n    form.appendChild(createField('Line Style', lineStyleDropdown.container));\n    form.appendChild(createField('Line Width', lineWidthSlider.container));\n\n    const buttons = createButtons(\n      mode === 'create' ? 'Create' : 'Save',\n      () => {\n        const lineDashMap: Record<string, number[] | undefined> = {\n          solid: undefined,\n          dashed: [4, 4],\n          dotted: [2, 2],\n        };\n\n        const labelText = labelInput.value.trim();\n        const config: Partial<AnnotationConfig> = {\n          ...defaults,\n          label: labelText\n            ? {\n                ...(defaults as any).label,\n                text: labelText,\n              }\n            : undefined,\n          style: {\n            ...defaults.style,\n            color: colorPicker.getValue(),\n            lineWidth: lineWidthSlider.getValue(),\n            lineDash: lineDashMap[lineStyleDropdown.getValue()],\n          },\n        };\n\n        handleSave(config);\n      },\n      () => handleCancel()\n    );\n\n    form.appendChild(buttons);\n\n    return form;\n  }\n\n  /**\n   * Build form for text annotations\n   */\n  function buildTextForm(defaults: Partial<AnnotationConfig>): HTMLDivElement {\n    const form = document.createElement('div');\n\n    // Extract text from text annotation\n    const defaultText = (defaults as any).text ?? '';\n    const textArea = createTextArea('Text content', 500, defaultText);\n    const colorPicker = createColorPicker(defaults.style?.color ?? palette[0]);\n\n    form.appendChild(createField('Text', textArea));\n    form.appendChild(createField('Color', colorPicker.container));\n\n    const buttons = createButtons(\n      mode === 'create' ? 'Create' : 'Save',\n      () => {\n        const text = textArea.value.trim();\n        if (!text) {\n          textArea.style.borderColor = '#ef4444';\n          textArea.focus();\n          return;\n        }\n\n        const config: Partial<AnnotationConfig> = {\n          ...defaults,\n          text,\n          style: {\n            ...defaults.style,\n            color: colorPicker.getValue(),\n          },\n        } as any; // Use 'any' to bypass type narrowing issues\n\n        handleSave(config);\n      },\n      () => handleCancel()\n    );\n\n    form.appendChild(buttons);\n\n    return form;\n  }\n\n  /**\n   * Build form for point annotations\n   */\n  function buildPointForm(defaults: Partial<AnnotationConfig>): HTMLDivElement {\n    const form = document.createElement('div');\n\n    // Extract label text from label object (if it exists)\n    const defaultLabelText = (defaults as any).label?.text ?? '';\n    const labelInput = createTextInput('Point label', 100, defaultLabelText);\n    const colorPicker = createColorPicker(defaults.style?.color ?? palette[0]);\n\n    // For point annotations, marker style might be in marker.style or just style\n    const defaultMarkerSize = (defaults as any).marker?.size ?? (defaults as any).marker?.style?.markerSize ?? 8;\n    const markerSizeSlider = createSlider(4, 16, 1, defaultMarkerSize, 'px');\n\n    form.appendChild(createField('Label (optional)', labelInput));\n    form.appendChild(createField('Color', colorPicker.container));\n    form.appendChild(createField('Marker Size', markerSizeSlider.container));\n\n    const buttons = createButtons(\n      mode === 'create' ? 'Create' : 'Save',\n      () => {\n        const labelText = labelInput.value.trim();\n        const config: Partial<AnnotationConfig> = {\n          ...defaults,\n          label: labelText\n            ? {\n                ...(defaults as any).label,\n                text: labelText,\n              }\n            : undefined,\n          marker: {\n            ...(defaults as any).marker,\n            size: markerSizeSlider.getValue(),\n            style: {\n              ...(defaults as any).marker?.style,\n              color: colorPicker.getValue(),\n            },\n          },\n        };\n\n        handleSave(config);\n      },\n      () => handleCancel()\n    );\n\n    form.appendChild(buttons);\n\n    return form;\n  }\n\n  /**\n   * Handle save action\n   */\n  function handleSave(config: any): void {\n    if (currentOnSave) {\n      currentOnSave(config);\n    }\n    hide();\n  }\n\n  /**\n   * Handle cancel action\n   */\n  function handleCancel(): void {\n    if (currentOnCancel) {\n      currentOnCancel();\n    }\n    hide();\n  }\n\n  /**\n   * Handle keyboard events\n   */\n  function handleKeyDown(e: KeyboardEvent): void {\n    if (e.key === 'Escape') {\n      e.preventDefault();\n      handleCancel();\n    }\n  }\n\n  /**\n   * Show dialog for creating a new annotation\n   */\n  function showCreate(\n    type: 'lineX' | 'lineY' | 'text' | 'point',\n    defaults: Partial<AnnotationConfig>,\n    onSave: (config: AnnotationConfig) => void,\n    onCancel: () => void\n  ): void {\n    mode = 'create';\n    currentOnSave = onSave;\n    currentOnCancel = onCancel;\n\n    overlay = createOverlay();\n    dialog = createDialog();\n\n    const title = document.createElement('h3');\n    title.textContent = `Add ${type === 'lineX' ? 'Vertical Line' : type === 'lineY' ? 'Horizontal Line' : type === 'text' ? 'Text Note' : 'Point Marker'}`;\n    title.style.cssText = `\n      margin: 0 0 20px 0;\n      font-size: 16px;\n      font-weight: 600;\n      color: #ffffff;\n    `;\n    dialog.appendChild(title);\n\n    let form: HTMLDivElement;\n    if (type === 'lineX' || type === 'lineY') {\n      form = buildLineForm(defaults);\n    } else if (type === 'text') {\n      form = buildTextForm(defaults);\n    } else {\n      form = buildPointForm(defaults);\n    }\n\n    dialog.appendChild(form);\n    overlay.appendChild(dialog);\n    container.appendChild(overlay);\n\n    document.addEventListener('keydown', handleKeyDown);\n\n    // Focus first input\n    const firstInput = dialog.querySelector('input, textarea') as HTMLElement;\n    if (firstInput) {\n      setTimeout(() => firstInput.focus(), 50);\n    }\n  }\n\n  /**\n   * Show dialog for editing an existing annotation\n   */\n  function showEdit(\n    annotation: AnnotationConfig,\n    onSave: (updates: Partial<AnnotationConfig>) => void,\n    onCancel: () => void\n  ): void {\n    mode = 'edit';\n    currentOnSave = onSave;\n    currentOnCancel = onCancel;\n\n    overlay = createOverlay();\n    dialog = createDialog();\n\n    const title = document.createElement('h3');\n    title.textContent = 'Edit Annotation';\n    title.style.cssText = `\n      margin: 0 0 20px 0;\n      font-size: 16px;\n      font-weight: 600;\n      color: #ffffff;\n    `;\n    dialog.appendChild(title);\n\n    let form: HTMLDivElement;\n    if (annotation.type === 'lineX' || annotation.type === 'lineY') {\n      form = buildLineForm(annotation);\n    } else if (annotation.type === 'text') {\n      form = buildTextForm(annotation);\n    } else {\n      form = buildPointForm(annotation);\n    }\n\n    dialog.appendChild(form);\n    overlay.appendChild(dialog);\n    container.appendChild(overlay);\n\n    document.addEventListener('keydown', handleKeyDown);\n\n    // Focus first input\n    const firstInput = dialog.querySelector('input, textarea') as HTMLElement;\n    if (firstInput) {\n      setTimeout(() => firstInput.focus(), 50);\n    }\n  }\n\n  /**\n   * Hide the dialog\n   */\n  function hide(): void {\n    if (overlay && overlay.parentNode) {\n      overlay.parentNode.removeChild(overlay);\n    }\n    overlay = null;\n    dialog = null;\n    currentOnSave = null;\n    currentOnCancel = null;\n\n    document.removeEventListener('keydown', handleKeyDown);\n  }\n\n  /**\n   * Dispose of resources\n   */\n  function dispose(): void {\n    hide();\n  }\n\n  return {\n    showCreate,\n    showEdit,\n    hide,\n    dispose,\n  };\n}\n","/**\n * Annotation authoring helper for ChartGPU instances.\n *\n * Provides right-click context menu for adding vertical lines and text annotations,\n * with undo/redo, JSON export, drag-to-reposition, and editing capabilities.\n */\n\nimport type { ChartGPUInstance, ChartGPUHitTestResult } from '../ChartGPU';\nimport type { AnnotationConfig, DataPoint, DataPointTuple, OHLCDataPoint, OHLCDataPointTuple } from '../config/types';\nimport { defaultGrid } from '../config/defaults';\nimport { createAnnotationHitTester } from './createAnnotationHitTester';\nimport { createAnnotationDragHandler } from './createAnnotationDragHandler';\nimport { createAnnotationConfigDialog } from '../components/createAnnotationConfigDialog';\n\n// Type guards and helpers\nconst isTupleDataPoint = (p: DataPoint): p is DataPointTuple => Array.isArray(p);\nconst isTupleOHLCDataPoint = (p: OHLCDataPoint): p is OHLCDataPointTuple => Array.isArray(p);\n\nconst getPointX = (p: DataPoint): number => (isTupleDataPoint(p) ? p[0] : p.x);\nconst getPointY = (p: DataPoint): number => (isTupleDataPoint(p) ? p[1] : p.y);\nconst getOHLCTimestamp = (p: OHLCDataPoint): number => (isTupleOHLCDataPoint(p) ? p[0] : p.timestamp);\n\n/**\n * Configuration options for annotation authoring.\n */\nexport interface AnnotationAuthoringOptions {\n  /**\n   * Z-index for the context menu (default: 1000).\n   */\n  readonly menuZIndex?: number;\n  /**\n   * Enable right-click context menu (default: true).\n   */\n  readonly enableContextMenu?: boolean;\n}\n\n/**\n * Annotation authoring instance returned by `createAnnotationAuthoring`.\n * \n * Provides programmatic control over annotations and manages UI lifecycle.\n */\nexport interface AnnotationAuthoringInstance {\n  /**\n   * Programmatically add a vertical line annotation.\n   * \n   * @param x - X-coordinate in data domain units\n   */\n  addVerticalLine(x: number): void;\n  /**\n   * Programmatically add a text annotation.\n   * \n   * @param x - X-coordinate (domain units for 'data' space, fraction [0-1] for 'plot' space)\n   * @param y - Y-coordinate (domain units for 'data' space, fraction [0-1] for 'plot' space)\n   * @param text - Annotation text content\n   * @param space - Coordinate space: 'data' (default) or 'plot'\n   */\n  addTextNote(x: number, y: number, text: string, space?: 'data' | 'plot'): void;\n  /**\n   * Undo the last annotation change.\n   * \n   * @returns `true` if undo was successful, `false` if nothing to undo\n   */\n  undo(): boolean;\n  /**\n   * Redo a previously undone change.\n   * \n   * @returns `true` if redo was successful, `false` if nothing to redo\n   */\n  redo(): boolean;\n  /**\n   * Export current annotations as JSON string.\n   * \n   * @returns JSON string representation of annotations array\n   */\n  exportJSON(): string;\n  /**\n   * Get the current annotations array.\n   * \n   * @returns Readonly copy of current annotations\n   */\n  getAnnotations(): readonly AnnotationConfig[];\n  /**\n   * Clean up event listeners and DOM elements.\n   * \n   * Safe to call multiple times. After disposal, the instance should not be used.\n   */\n  dispose(): void;\n}\n\ninterface HistoryEntry {\n  readonly annotations: readonly AnnotationConfig[];\n}\n\n/**\n * Creates an annotation authoring helper for a chart instance.\n * \n * Features:\n * - Right-click context menu for adding vertical lines and text annotations\n * - Optional toolbar with undo/redo/export buttons\n * - Undo/redo history (50 entries max)\n * - JSON export with clipboard integration\n * - Automatic coordinate conversion (data-space and plot-space)\n * - Event listener cleanup on dispose\n * \n * Annotations are persisted by calling `chart.setOption({ ...options, annotations })`,\n * so they integrate seamlessly with the chart's option system.\n * \n * @param container - The chart container element (must contain the chart canvas)\n * @param chart - The ChartGPU instance\n * @param options - Optional configuration for menu/toolbar z-index and visibility\n * @returns Annotation authoring instance with programmatic API and dispose method\n * @throws Error if canvas is not found\n * \n * @example\n * ```ts\n * const chart = await ChartGPU.create(container, options);\n * const authoring = createAnnotationAuthoring(container, chart, {\n *   showToolbar: true,\n *   enableContextMenu: true,\n * });\n * \n * // Programmatic API\n * authoring.addVerticalLine(Date.now());\n * authoring.addTextNote(x, y, 'Peak', 'data');\n * authoring.undo();\n * authoring.redo();\n * const json = authoring.exportJSON();\n * \n * // Cleanup\n * authoring.dispose();\n * chart.dispose();\n * ```\n */\nexport function createAnnotationAuthoring(\n  container: HTMLElement,\n  chart: ChartGPUInstance,\n  options: AnnotationAuthoringOptions = {}\n): AnnotationAuthoringInstance {\n  const {\n    menuZIndex = 1000,\n    enableContextMenu = true,\n  } = options;\n\n  // Find the canvas element\n  const canvas = container.querySelector('canvas');\n  if (!canvas) {\n    throw new Error('createAnnotationAuthoring: canvas element not found in container');\n  }\n\n  // History management\n  let history: HistoryEntry[] = [{ annotations: chart.options.annotations ?? [] }];\n  let historyIndex = 0;\n  let disposed = false;\n\n  // Create hit tester, drag handler, and config dialog\n  const hitTester = createAnnotationHitTester(chart, canvas, {\n    lineTolerance: 20,\n    textTolerance: 8,\n    pointTolerance: 16,\n  });\n\n  const configDialog = createAnnotationConfigDialog(container, {\n    zIndex: menuZIndex,\n  });\n\n  const dragHandler = createAnnotationDragHandler(chart, canvas, {\n    onDragMove: (index, updates) => {\n      // Optimistic update without history\n      const current = getCurrentAnnotations();\n      const next = current.map((a, i) => (i === index ? { ...a, ...updates } as AnnotationConfig : a));\n      applyAnnotations(next);\n    },\n    onDragEnd: (index, updates) => {\n      // Final position with history push\n      const current = getCurrentAnnotations();\n      const next = current.map((a, i) => (i === index ? { ...a, ...updates } as AnnotationConfig : a));\n      applyAnnotations(next);\n      pushHistory(next);\n    },\n    onDragCancel: () => {\n      // Revert to last history state (no push)\n      const entry = history[historyIndex];\n      if (entry) {\n        applyAnnotations(entry.annotations);\n      }\n    },\n  });\n\n  // Get current annotations\n  const getCurrentAnnotations = (): readonly AnnotationConfig[] => {\n    return chart.options.annotations ?? [];\n  };\n\n  // Push a new history entry\n  const pushHistory = (annotations: readonly AnnotationConfig[]): void => {\n    // Truncate any redo history\n    history = history.slice(0, historyIndex + 1);\n    history.push({ annotations: [...annotations] });\n    historyIndex = history.length - 1;\n\n    // Limit history size to 50 entries\n    if (history.length > 50) {\n      history.shift();\n      historyIndex--;\n    }\n  };\n\n  // Apply annotations to chart\n  const applyAnnotations = (annotations: readonly AnnotationConfig[]): void => {\n    chart.setOption({\n      ...chart.options,\n      annotations: [...annotations],\n    });\n    // Invalidate hit tester cache so it picks up the new/modified annotations\n    hitTester.invalidateCache();\n  };\n\n  // Context menu DOM\n  let contextMenu: HTMLDivElement | null = null;\n\n  const createContextMenu = (): HTMLDivElement => {\n    const menu = document.createElement('div');\n    menu.style.position = 'fixed';\n    menu.style.display = 'none';\n    menu.style.backgroundColor = '#1a1a2e';\n    menu.style.border = '1px solid #333';\n    menu.style.borderRadius = '8px';\n    menu.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.5)';\n    menu.style.zIndex = String(menuZIndex);\n    menu.style.minWidth = '180px';\n    menu.style.padding = '6px 0';\n    menu.style.fontFamily = '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif';\n    menu.style.fontSize = '14px';\n    menu.style.color = '#e0e0e0';\n\n    document.body.appendChild(menu);\n    return menu;\n  };\n\n  const createMenuItem = (text: string, onClick: () => void): HTMLDivElement => {\n    const item = document.createElement('div');\n    item.textContent = text;\n    item.style.padding = '8px 16px';\n    item.style.cursor = 'pointer';\n    item.style.transition = 'background-color 0.15s';\n    item.style.userSelect = 'none';\n\n    item.addEventListener('mouseenter', () => {\n      item.style.backgroundColor = '#2a2a3e';\n    });\n    item.addEventListener('mouseleave', () => {\n      item.style.backgroundColor = 'transparent';\n    });\n    item.addEventListener('click', () => {\n      onClick();\n      hideContextMenu();\n    });\n\n    return item;\n  };\n\n  const createMenuSeparator = (): HTMLDivElement => {\n    const separator = document.createElement('div');\n    separator.style.height = '1px';\n    separator.style.backgroundColor = '#333';\n    separator.style.margin = '6px 0';\n    return separator;\n  };\n\n  const populateContextMenuForAnnotation = (\n    menu: HTMLDivElement,\n    annotationIndex: number,\n    annotation: AnnotationConfig\n  ): void => {\n    // Clear existing items\n    menu.innerHTML = '';\n\n    // Edit and delete for the annotation\n    menu.appendChild(createMenuItem('Edit annotation...', () => handleEditAnnotation(annotationIndex, annotation)));\n    menu.appendChild(createMenuItem('Delete annotation', () => handleDeleteAnnotation(annotationIndex)));\n    menu.appendChild(createMenuSeparator());\n\n    // Add new annotations\n    menu.appendChild(createMenuItem('Add vertical line here', () => handleAddVerticalLine()));\n    menu.appendChild(createMenuItem('Add horizontal line here', () => handleAddHorizontalLine()));\n    menu.appendChild(createMenuItem('Add text note here', () => handleAddTextNote()));\n  };\n\n  const populateContextMenuForEmptySpace = (menu: HTMLDivElement): void => {\n    // Clear existing items\n    menu.innerHTML = '';\n\n    // Add new annotations\n    menu.appendChild(createMenuItem('Add vertical line here', () => handleAddVerticalLine()));\n    menu.appendChild(createMenuItem('Add horizontal line here', () => handleAddHorizontalLine()));\n    menu.appendChild(createMenuItem('Add text note here', () => handleAddTextNote()));\n  };\n\n  // Toolbar removed - UI decluttering\n\n  // Show context menu\n  let lastHitTestResult: ChartGPUHitTestResult | null = null;\n\n  const showContextMenu = (e: MouseEvent): void => {\n    if (!contextMenu) return;\n\n    lastHitTestResult = chart.hitTest(e);\n\n    // Perform annotation hit test\n    const rect = canvas.getBoundingClientRect();\n    const canvasX = e.clientX - rect.left;\n    const canvasY = e.clientY - rect.top;\n    const annotationHit = hitTester.hitTest(canvasX, canvasY);\n\n    if (annotationHit) {\n      // Right-clicked on an annotation - show edit/delete menu\n      populateContextMenuForAnnotation(contextMenu, annotationHit.annotationIndex, annotationHit.annotation);\n    } else {\n      // Right-clicked on empty space - show add menu\n      populateContextMenuForEmptySpace(contextMenu);\n    }\n\n    contextMenu.style.display = 'block';\n    contextMenu.style.left = `${e.clientX}px`;\n    contextMenu.style.top = `${e.clientY}px`;\n\n    // Adjust position if menu goes off-screen (check both viewport bounds)\n    requestAnimationFrame(() => {\n      if (!contextMenu || contextMenu.style.display !== 'block') return;\n\n      const menuRect = contextMenu.getBoundingClientRect();\n      let adjustedX = e.clientX;\n      let adjustedY = e.clientY;\n\n      // Adjust horizontal position if menu extends beyond right edge\n      if (menuRect.right > window.innerWidth) {\n        adjustedX = Math.max(0, e.clientX - menuRect.width);\n      }\n\n      // Adjust vertical position if menu extends beyond bottom edge\n      if (menuRect.bottom > window.innerHeight) {\n        adjustedY = Math.max(0, e.clientY - menuRect.height);\n      }\n\n      // Apply adjustments if needed\n      if (adjustedX !== e.clientX || adjustedY !== e.clientY) {\n        contextMenu.style.left = `${adjustedX}px`;\n        contextMenu.style.top = `${adjustedY}px`;\n      }\n    });\n  };\n\n  const hideContextMenu = (): void => {\n    if (!contextMenu) return;\n    contextMenu.style.display = 'none';\n    lastHitTestResult = null;\n  };\n\n  // Compute visible x-domain for freeform vertical lines\n  const computeVisibleXDomain = (): { min: number; max: number } => {\n    const opts = chart.options;\n    \n    // Get base domain\n    let xMin = opts.xAxis?.min;\n    let xMax = opts.xAxis?.max;\n\n    // If not explicitly set, derive from series data\n    if (xMin === undefined || xMax === undefined) {\n      const series = opts.series ?? [];\n      let dataXMin = Number.POSITIVE_INFINITY;\n      let dataXMax = Number.NEGATIVE_INFINITY;\n\n      for (const s of series) {\n        if (s.type === 'pie') continue;\n\n        if (s.type === 'candlestick') {\n          // Candlestick uses timestamp (first element)\n          const data = s.data;\n          for (const p of data) {\n            const timestamp = getOHLCTimestamp(p);\n            if (timestamp < dataXMin) dataXMin = timestamp;\n            if (timestamp > dataXMax) dataXMax = timestamp;\n          }\n        } else {\n          // Cartesian series\n          // TODO(step 2): normalize CartesianSeriesData to ReadonlyArray<DataPoint>\n          const data = s.data as ReadonlyArray<DataPoint>;\n          for (const p of data) {\n            const x = getPointX(p);\n            if (x < dataXMin) dataXMin = x;\n            if (x > dataXMax) dataXMax = x;\n          }\n        }\n      }\n\n      if (xMin === undefined) xMin = Number.isFinite(dataXMin) ? dataXMin : 0;\n      if (xMax === undefined) xMax = Number.isFinite(dataXMax) ? dataXMax : 100;\n    }\n\n    // Apply zoom if present\n    const zoomRange = chart.getZoomRange();\n    if (zoomRange) {\n      const span = xMax - xMin;\n      const zoomMin = xMin + (zoomRange.start / 100) * span;\n      const zoomMax = xMin + (zoomRange.end / 100) * span;\n      return { min: zoomMin, max: zoomMax };\n    }\n\n    return { min: xMin, max: xMax };\n  };\n\n  // Compute visible y-domain for horizontal lines\n  const computeVisibleYDomain = (): { min: number; max: number } => {\n    const opts = chart.options;\n\n    // Get base domain\n    let yMin = opts.yAxis?.min;\n    let yMax = opts.yAxis?.max;\n\n    // If not explicitly set, derive from series data\n    if (yMin === undefined || yMax === undefined) {\n      const series = opts.series ?? [];\n      let dataYMin = Number.POSITIVE_INFINITY;\n      let dataYMax = Number.NEGATIVE_INFINITY;\n\n      for (const s of series) {\n        if (s.type === 'pie') continue;\n\n        if (s.type === 'candlestick') {\n          // Candlestick uses low/high\n          const data = s.data;\n          for (const p of data) {\n            const low = isTupleOHLCDataPoint(p) ? p[3] : p.low;\n            const high = isTupleOHLCDataPoint(p) ? p[4] : p.high;\n            if (low < dataYMin) dataYMin = low;\n            if (high > dataYMax) dataYMax = high;\n          }\n        } else {\n          // Cartesian series\n          // TODO(step 2): normalize CartesianSeriesData to ReadonlyArray<DataPoint>\n          const data = s.data as ReadonlyArray<DataPoint>;\n          for (const p of data) {\n            const y = getPointY(p);\n            if (y < dataYMin) dataYMin = y;\n            if (y > dataYMax) dataYMax = y;\n          }\n        }\n      }\n\n      if (yMin === undefined) yMin = Number.isFinite(dataYMin) ? dataYMin : 0;\n      if (yMax === undefined) yMax = Number.isFinite(dataYMax) ? dataYMax : 100;\n    }\n\n    return { min: yMin, max: yMax };\n  };\n\n  // Convert grid-space coordinates to data-space x\n  const gridXToDataX = (gridX: number): number => {\n    const rect = canvas.getBoundingClientRect();\n    const grid = chart.options.grid ?? defaultGrid;\n    const plotWidthCss = rect.width - (grid.left ?? defaultGrid.left) - (grid.right ?? defaultGrid.right);\n    \n    const xDomain = computeVisibleXDomain();\n    const t = plotWidthCss > 0 ? gridX / plotWidthCss : 0;\n    return xDomain.min + t * (xDomain.max - xDomain.min);\n  };\n\n  // Convert grid-space coordinates to plot-space [0-1]\n  const gridToPlotSpace = (gridX: number, gridY: number): { x: number; y: number } => {\n    const rect = canvas.getBoundingClientRect();\n    const grid = chart.options.grid ?? defaultGrid;\n    const plotWidthCss = rect.width - (grid.left ?? defaultGrid.left) - (grid.right ?? defaultGrid.right);\n    const plotHeightCss = rect.height - (grid.top ?? defaultGrid.top) - (grid.bottom ?? defaultGrid.bottom);\n    \n    const px = plotWidthCss > 0 ? gridX / plotWidthCss : 0;\n    const py = plotHeightCss > 0 ? gridY / plotHeightCss : 0;\n    return { x: px, y: py };\n  };\n\n  // Handle \"Add vertical line here\"\n  const handleAddVerticalLine = (): void => {\n    if (!lastHitTestResult) return;\n\n    const { match, isInGrid, gridX } = lastHitTestResult;\n\n    let x: number;\n    if (match) {\n      // Use matched data point x\n      x = match.value[0];\n    } else if (isInGrid) {\n      // Compute x from grid position\n      x = gridXToDataX(gridX);\n    } else {\n      return; // Outside grid, do nothing\n    }\n\n    // Show configuration dialog\n    configDialog.showCreate(\n      'lineX',\n      {\n        type: 'lineX',\n        x,\n        layer: 'aboveSeries',\n        style: {\n          color: '#ffa500',\n          lineWidth: 2,\n        },\n      },\n      (config) => {\n        const current = getCurrentAnnotations();\n        const next = [...current, config as AnnotationConfig];\n        applyAnnotations(next);\n        pushHistory(next);\n      },\n      () => {\n        // Cancelled - do nothing\n      }\n    );\n  };\n\n  // Handle \"Add horizontal line here\"\n  const handleAddHorizontalLine = (): void => {\n    if (!lastHitTestResult) return;\n\n    const { match, isInGrid, gridY } = lastHitTestResult;\n\n    let y: number;\n    if (match) {\n      // Use matched data point y\n      y = match.value[1];\n    } else if (isInGrid) {\n      // Compute y from grid position using actual visible Y domain\n      const rect = canvas.getBoundingClientRect();\n      const grid = chart.options.grid ?? defaultGrid;\n      const plotHeightCss = rect.height - (grid.top ?? defaultGrid.top) - (grid.bottom ?? defaultGrid.bottom);\n\n      // Get actual visible Y domain (not hardcoded defaults!)\n      const yDomain = computeVisibleYDomain();\n\n      // Invert Y (canvas top = max Y value)\n      const t = plotHeightCss > 0 ? 1 - gridY / plotHeightCss : 0.5;\n      y = yDomain.min + t * (yDomain.max - yDomain.min);\n    } else {\n      return; // Outside grid, do nothing\n    }\n\n    // Show configuration dialog\n    configDialog.showCreate(\n      'lineY',\n      {\n        type: 'lineY',\n        y,\n        layer: 'aboveSeries',\n        style: {\n          color: '#ffa500',\n          lineWidth: 2,\n        },\n      },\n      (config) => {\n        const current = getCurrentAnnotations();\n        const next = [...current, config as AnnotationConfig];\n        applyAnnotations(next);\n        pushHistory(next);\n      },\n      () => {\n        // Cancelled - do nothing\n      }\n    );\n  };\n\n  // Handle \"Add text note here\"\n  const handleAddTextNote = (): void => {\n    if (!lastHitTestResult) return;\n\n    const { match, isInGrid, gridX, gridY } = lastHitTestResult;\n\n    let space: 'data' | 'plot';\n    let x: number;\n    let y: number;\n\n    if (match) {\n      // Use data-space position\n      space = 'data';\n      x = match.value[0];\n      y = match.value[1];\n    } else if (isInGrid) {\n      // Use plot-space position\n      const plotPos = gridToPlotSpace(gridX, gridY);\n      space = 'plot';\n      x = plotPos.x;\n      y = plotPos.y;\n    } else {\n      return; // Outside grid, do nothing\n    }\n\n    // Show configuration dialog\n    configDialog.showCreate(\n      'text',\n      {\n        type: 'text',\n        position: { space, x, y },\n        text: 'Note',\n        layer: 'aboveSeries',\n        style: {\n          color: '#00d4ff',\n        },\n      },\n      (config) => {\n        const current = getCurrentAnnotations();\n        const next = [...current, config as AnnotationConfig];\n        applyAnnotations(next);\n        pushHistory(next);\n      },\n      () => {\n        // Cancelled - do nothing\n      }\n    );\n  };\n\n  // Handle \"Edit annotation...\"\n  const handleEditAnnotation = (index: number, annotation: AnnotationConfig): void => {\n    configDialog.showEdit(\n      annotation,\n      (updates) => {\n        const current = getCurrentAnnotations();\n        const next = current.map((a, i) => (i === index ? { ...a, ...updates } as AnnotationConfig : a));\n        applyAnnotations(next);\n        pushHistory(next);\n      },\n      () => {\n        // Cancelled - do nothing\n      }\n    );\n  };\n\n  // Handle \"Delete annotation\"\n  const handleDeleteAnnotation = (index: number): void => {\n    const current = getCurrentAnnotations();\n    const next = current.filter((_, i) => i !== index);\n    applyAnnotations(next);\n    pushHistory(next);\n  };\n\n  // Pointer down event handler for drag\n  const onPointerDown = (e: PointerEvent): void => {\n    if (disposed || e.button === 2) return; // Ignore right-click\n\n    const rect = canvas.getBoundingClientRect();\n    const canvasX = e.clientX - rect.left;\n    const canvasY = e.clientY - rect.top;\n\n    const annotationHit = hitTester.hitTest(canvasX, canvasY);\n\n    if (annotationHit) {\n      e.preventDefault();\n      // Don't set pointer capture here - let drag handler manage window-level events\n\n      dragHandler.startDrag(\n        annotationHit.annotationIndex,\n        annotationHit.annotation,\n        e.clientX,\n        e.clientY\n      );\n    }\n  };\n\n  // Context menu event handler\n  const onContextMenu = (e: MouseEvent): void => {\n    if (disposed || !enableContextMenu) return;\n    e.preventDefault();\n    e.stopPropagation();\n    showContextMenu(e);\n  };\n\n  // Click outside to close context menu\n  const onDocumentClick = (e: MouseEvent): void => {\n    if (disposed) return;\n    if (contextMenu && !contextMenu.contains(e.target as Node)) {\n      hideContextMenu();\n    }\n  };\n\n  // Escape to close context menu\n  const onDocumentKeyDown = (e: KeyboardEvent): void => {\n    if (disposed) return;\n    if (e.key === 'Escape' && contextMenu && contextMenu.style.display === 'block') {\n      hideContextMenu();\n    }\n  };\n\n  // Scroll/resize to close context menu (prevents menu from floating at wrong position)\n  const onWindowScrollOrResize = (): void => {\n    if (disposed) return;\n    if (contextMenu && contextMenu.style.display === 'block') {\n      hideContextMenu();\n    }\n  };\n\n  // Public API\n  const addVerticalLine = (x: number): void => {\n    const current = getCurrentAnnotations();\n    const newAnnotation: AnnotationConfig = {\n      type: 'lineX',\n      x,\n      layer: 'aboveSeries',\n      style: {\n        color: '#ffa500',\n        lineWidth: 2,\n        opacity: 0.9,\n      },\n    };\n    const next = [...current, newAnnotation];\n    applyAnnotations(next);\n    pushHistory(next);\n  };\n\n  const addTextNote = (x: number, y: number, text: string, space: 'data' | 'plot' = 'data'): void => {\n    const current = getCurrentAnnotations();\n    const newAnnotation: AnnotationConfig = {\n      type: 'text',\n      position: { space, x, y },\n      text,\n      layer: 'aboveSeries',\n      style: {\n        color: '#00d4ff',\n        opacity: 1,\n      },\n    };\n    const next = [...current, newAnnotation];\n    applyAnnotations(next);\n    pushHistory(next);\n  };\n\n  const undo = (): boolean => {\n    if (historyIndex <= 0) return false;\n    historyIndex--;\n    const entry = history[historyIndex];\n    if (!entry) return false;\n    applyAnnotations(entry.annotations);\n    return true;\n  };\n\n  const redo = (): boolean => {\n    if (historyIndex >= history.length - 1) return false;\n    historyIndex++;\n    const entry = history[historyIndex];\n    if (!entry) return false;\n    applyAnnotations(entry.annotations);\n    return true;\n  };\n\n  const exportJSON = (): string => {\n    const annotations = getCurrentAnnotations();\n    return JSON.stringify(annotations, null, 2);\n  };\n\n  const getAnnotations = (): readonly AnnotationConfig[] => {\n    return getCurrentAnnotations();\n  };\n\n  const dispose = (): void => {\n    if (disposed) return;\n    disposed = true;\n\n    canvas.removeEventListener('pointerdown', onPointerDown);\n    canvas.removeEventListener('contextmenu', onContextMenu);\n    document.removeEventListener('click', onDocumentClick);\n    document.removeEventListener('keydown', onDocumentKeyDown);\n    window.removeEventListener('scroll', onWindowScrollOrResize, true);\n    window.removeEventListener('resize', onWindowScrollOrResize);\n\n    contextMenu?.remove();\n    contextMenu = null;\n\n    hitTester.dispose();\n    dragHandler.dispose();\n    configDialog.dispose();\n\n    history = [];\n  };\n\n  // Initialize\n  if (enableContextMenu) {\n    contextMenu = createContextMenu();\n    canvas.addEventListener('contextmenu', onContextMenu);\n    document.addEventListener('click', onDocumentClick);\n    document.addEventListener('keydown', onDocumentKeyDown);\n    // Capture phase for scroll to handle all scrollable ancestors\n    window.addEventListener('scroll', onWindowScrollOrResize, true);\n    window.addEventListener('resize', onWindowScrollOrResize);\n  }\n\n  // Attach pointer event for dragging (always enabled)\n  canvas.addEventListener('pointerdown', onPointerDown);\n\n  return {\n    addVerticalLine,\n    addTextNote,\n    undo,\n    redo,\n    exportJSON,\n    getAnnotations,\n    dispose,\n  };\n}\n","/**\n * RenderScheduler - 60fps render loop management\n * \n * Manages a requestAnimationFrame-based render loop that runs at 60fps,\n * providing delta time tracking and frame scheduling control.\n * \n * This module provides both functional and class-based APIs for maximum flexibility.\n * The functional API is preferred for better type safety and immutability.\n */\n\n/**\n * Callback function type for render frames.\n * Receives delta time in milliseconds since the last frame.\n */\nexport type RenderCallback = (deltaTime: number) => void;\n\nimport type { ExactFPS, Milliseconds, FrameTimeStats, FrameDropStats } from '../config/types';\n\n/**\n * Represents the state of a render scheduler.\n * All properties are readonly to ensure immutability.\n */\nexport interface RenderSchedulerState {\n  readonly id: symbol;\n  readonly running: boolean;\n}\n\n/**\n * Circular buffer for frame timestamps (120 frames = 2 seconds at 60fps).\n * Uses Float64Array for high-precision timestamps from performance.now().\n */\nconst FRAME_BUFFER_SIZE = 120;\n\n/**\n * Expected frame time at 60fps (16.67ms).\n * Used for frame drop detection.\n */\nconst EXPECTED_FRAME_TIME_MS = 1000 / 60;\n\n/**\n * Frame drop threshold multiplier (1.5x expected frame time).\n * Frame times exceeding this are counted as drops.\n */\nconst FRAME_DROP_THRESHOLD_MULTIPLIER = 1.5;\n\n/**\n * Internal mutable state for the render scheduler.\n * Stored separately from the public state interface.\n */\ninterface RenderSchedulerInternalState {\n  rafId: number | null;\n  callback: RenderCallback | null;\n  lastFrameTime: number;\n  dirty: boolean;\n  frameHandler: ((time: number) => void) | null;\n  \n  // Performance tracking\n  frameTimestamps: Float64Array;\n  frameTimestampIndex: number;\n  frameTimestampCount: number;\n  totalFrames: number;\n  totalDroppedFrames: number;\n  consecutiveDroppedFrames: number;\n  lastDropTimestamp: number;\n  startTime: number;\n}\n\n/**\n * Map to store internal mutable state for each scheduler state instance.\n * Keyed by the state's unique ID symbol.\n */\nconst internalStateMap = new Map<symbol, RenderSchedulerInternalState>();\n\n/**\n * Creates a new RenderScheduler state with initial values.\n * \n * @returns A new RenderSchedulerState instance\n */\nexport function createRenderScheduler(): RenderSchedulerState {\n  const id = Symbol('RenderScheduler');\n  const state: RenderSchedulerState = {\n    id,\n    running: false,\n  };\n\n  // Initialize internal mutable state\n  internalStateMap.set(id, {\n    rafId: null,\n    callback: null,\n    lastFrameTime: 0,\n    dirty: false,\n    frameHandler: null,\n    \n    // Performance tracking\n    frameTimestamps: new Float64Array(FRAME_BUFFER_SIZE),\n    frameTimestampIndex: 0,\n    frameTimestampCount: 0,\n    totalFrames: 0,\n    totalDroppedFrames: 0,\n    consecutiveDroppedFrames: 0,\n    lastDropTimestamp: 0,\n    startTime: performance.now(),\n  });\n\n  return state;\n}\n\n/**\n * Starts the render loop.\n * \n * Begins a requestAnimationFrame loop that calls the provided callback\n * every frame with the delta time in milliseconds since the last frame.\n * Returns a new state object with running set to true.\n * \n * @param state - The scheduler state to start\n * @param callback - Function to call each frame with delta time\n * @returns A new RenderSchedulerState with running set to true\n * @throws {Error} If callback is not provided\n * @throws {Error} If scheduler is already running\n * @throws {Error} If state is invalid\n */\nexport function startRenderScheduler(\n  state: RenderSchedulerState,\n  callback: RenderCallback\n): RenderSchedulerState {\n  if (!callback) {\n    throw new Error('Render callback is required');\n  }\n\n  const internalState = internalStateMap.get(state.id);\n  if (!internalState) {\n    throw new Error('Invalid scheduler state. Use createRenderScheduler() to create a new state.');\n  }\n\n  if (state.running) {\n    throw new Error('RenderScheduler is already running. Call stopRenderScheduler() before starting again.');\n  }\n\n  // Update internal state\n  internalState.callback = callback;\n  internalState.lastFrameTime = performance.now();\n  internalState.dirty = true;\n\n  const schedulerId = state.id;\n  const frameHandler = (currentTime: number) => {\n    // Look up internal state - may be null if scheduler was destroyed\n    const currentInternalState = internalStateMap.get(schedulerId);\n    if (!currentInternalState || !currentInternalState.callback) {\n      // Scheduler was stopped or destroyed, exit gracefully\n      return;\n    }\n\n    // Clear rafId at the start - we are no longer scheduled (now idle)\n    currentInternalState.rafId = null;\n\n    // Record frame timestamp in circular buffer BEFORE rendering\n    // Use performance.now() exclusively for exact FPS measurement\n    const timestamp = performance.now();\n    currentInternalState.frameTimestamps[currentInternalState.frameTimestampIndex] = timestamp;\n    currentInternalState.frameTimestampIndex = (currentInternalState.frameTimestampIndex + 1) % FRAME_BUFFER_SIZE;\n    if (currentInternalState.frameTimestampCount < FRAME_BUFFER_SIZE) {\n      currentInternalState.frameTimestampCount++;\n    }\n    currentInternalState.totalFrames++;\n\n    // Calculate deltaTime with capping to prevent animation jumps after idle\n    let deltaTime = currentTime - currentInternalState.lastFrameTime;\n    // Cap deltaTime to 100ms (1/10th second) to prevent huge jumps\n    const MAX_DELTA_TIME = 100;\n    if (deltaTime > MAX_DELTA_TIME) {\n      deltaTime = MAX_DELTA_TIME;\n    }\n\n    // Frame drop detection (only after first frame)\n    if (currentInternalState.lastFrameTime > 0 && deltaTime > EXPECTED_FRAME_TIME_MS * FRAME_DROP_THRESHOLD_MULTIPLIER) {\n      currentInternalState.totalDroppedFrames++;\n      currentInternalState.consecutiveDroppedFrames++;\n      currentInternalState.lastDropTimestamp = timestamp;\n    } else if (currentInternalState.lastFrameTime > 0) {\n      // Reset consecutive counter on successful frame\n      currentInternalState.consecutiveDroppedFrames = 0;\n    }\n\n    currentInternalState.lastFrameTime = currentTime;\n\n    // Only render if dirty\n    if (currentInternalState.dirty) {\n      // Reset dirty flag BEFORE calling callback\n      currentInternalState.dirty = false;\n\n      // Call the render callback with delta time\n      currentInternalState.callback(deltaTime);\n\n      // After callback returns, check if dirty was set again (callback-triggered renders for animations)\n      // Re-check internal state in case it was destroyed during callback execution\n      const nextInternalState = internalStateMap.get(schedulerId);\n      if (nextInternalState && nextInternalState.callback && nextInternalState.dirty) {\n        // Schedule another frame since callback requested a render\n        nextInternalState.rafId = requestAnimationFrame(frameHandler);\n      }\n    }\n    // If not dirty, we remain idle (rafId stays null, no frame scheduled)\n  };\n\n  // Store frameHandler in internal state so requestRender() can access it\n  internalState.frameHandler = frameHandler;\n\n  // Start the first frame\n  internalState.rafId = requestAnimationFrame(frameHandler);\n\n  // Return new state with running set to true\n  return {\n    id: state.id,\n    running: true,\n  };\n}\n\n/**\n * Stops the render loop.\n * \n * Cancels any pending requestAnimationFrame calls and stops the loop.\n * Returns a new state object with running set to false.\n * The scheduler can be restarted by calling startRenderScheduler() again.\n * \n * @param state - The scheduler state to stop\n * @returns A new RenderSchedulerState with running set to false\n * @throws {Error} If state is invalid\n */\nexport function stopRenderScheduler(state: RenderSchedulerState): RenderSchedulerState {\n  const internalState = internalStateMap.get(state.id);\n  if (!internalState) {\n    throw new Error('Invalid scheduler state. Use createRenderScheduler() to create a new state.');\n  }\n\n  internalState.callback = null;\n  internalState.frameHandler = null;\n\n  if (internalState.rafId !== null) {\n    cancelAnimationFrame(internalState.rafId);\n    internalState.rafId = null;\n  }\n\n  // Return new state with running set to false\n  return {\n    id: state.id,\n    running: false,\n  };\n}\n\n/**\n * Marks the current frame as dirty and schedules a render if idle.\n * \n * This function implements render-on-demand: it schedules a frame when the\n * scheduler is idle. Multiple calls coalesce into a single frame.\n * \n * @param state - The scheduler state\n * @throws {Error} If state is invalid\n */\nexport function requestRender(state: RenderSchedulerState): void {\n  const internalState = internalStateMap.get(state.id);\n  if (!internalState) {\n    throw new Error('Invalid scheduler state. Use createRenderScheduler() to create a new state.');\n  }\n\n  // Mark as dirty\n  internalState.dirty = true;\n\n  // If not running, return early\n  if (internalState.callback === null) {\n    return;\n  }\n\n  // If already scheduled, return early (coalescing)\n  if (internalState.rafId !== null) {\n    return;\n  }\n\n  // Idle - schedule a frame\n  // Reset lastFrameTime to current time to ensure reasonable deltaTime after idle\n  internalState.lastFrameTime = performance.now();\n  \n  // Schedule RAF using the stored frameHandler\n  if (internalState.frameHandler) {\n    internalState.rafId = requestAnimationFrame(internalState.frameHandler);\n  }\n}\n\n/**\n * Calculates exact FPS from frame timestamp deltas.\n * \n * Uses the circular buffer of performance.now() timestamps to calculate\n * frame-perfect FPS. Algorithm:\n * 1. Sum all frame time deltas in the buffer\n * 2. Divide by (count - 1) to get average frame time\n * 3. Convert to FPS: 1000ms / avg_frame_time\n * \n * Returns 0 if insufficient data (< 2 frames).\n * \n * @param state - The scheduler state\n * @returns Exact FPS measurement\n */\nexport function getCurrentFPS(state: RenderSchedulerState): ExactFPS {\n  const internalState = internalStateMap.get(state.id);\n  if (!internalState) {\n    return 0 as ExactFPS;\n  }\n\n  const count = internalState.frameTimestampCount;\n  if (count < 2) {\n    return 0 as ExactFPS; // Need at least 2 frames to calculate FPS\n  }\n\n  // Calculate sum of deltas between consecutive timestamps\n  const timestamps = internalState.frameTimestamps;\n  const bufferSize = FRAME_BUFFER_SIZE;\n  const startIndex = (internalState.frameTimestampIndex - count + bufferSize) % bufferSize;\n  \n  let totalDelta = 0;\n  for (let i = 1; i < count; i++) {\n    const prevIndex = (startIndex + i - 1) % bufferSize;\n    const currIndex = (startIndex + i) % bufferSize;\n    const delta = timestamps[currIndex] - timestamps[prevIndex];\n    totalDelta += delta;\n  }\n\n  const avgFrameTime = totalDelta / (count - 1);\n  const fps = avgFrameTime > 0 ? 1000 / avgFrameTime : 0;\n  \n  return fps as ExactFPS;\n}\n\n/**\n * Calculates frame time statistics from the circular buffer.\n * \n * Computes min, max, avg, and percentiles (p50, p95, p99) for frame times.\n * Returns zero stats if insufficient data.\n * \n * @param state - The scheduler state\n * @returns Frame time statistics\n */\nexport function getFrameStats(state: RenderSchedulerState): FrameTimeStats {\n  const internalState = internalStateMap.get(state.id);\n  if (!internalState) {\n    return {\n      min: 0 as Milliseconds,\n      max: 0 as Milliseconds,\n      avg: 0 as Milliseconds,\n      p50: 0 as Milliseconds,\n      p95: 0 as Milliseconds,\n      p99: 0 as Milliseconds,\n    };\n  }\n\n  const count = internalState.frameTimestampCount;\n  if (count < 2) {\n    return {\n      min: 0 as Milliseconds,\n      max: 0 as Milliseconds,\n      avg: 0 as Milliseconds,\n      p50: 0 as Milliseconds,\n      p95: 0 as Milliseconds,\n      p99: 0 as Milliseconds,\n    };\n  }\n\n  // Extract deltas from circular buffer\n  const timestamps = internalState.frameTimestamps;\n  const bufferSize = FRAME_BUFFER_SIZE;\n  const startIndex = (internalState.frameTimestampIndex - count + bufferSize) % bufferSize;\n  \n  const deltas = new Array<number>(count - 1);\n  let min = Number.POSITIVE_INFINITY;\n  let max = Number.NEGATIVE_INFINITY;\n  let sum = 0;\n  \n  for (let i = 1; i < count; i++) {\n    const prevIndex = (startIndex + i - 1) % bufferSize;\n    const currIndex = (startIndex + i) % bufferSize;\n    const delta = timestamps[currIndex] - timestamps[prevIndex];\n    deltas[i - 1] = delta;\n    \n    if (delta < min) min = delta;\n    if (delta > max) max = delta;\n    sum += delta;\n  }\n\n  const avg = sum / deltas.length;\n\n  // Sort for percentile calculations\n  deltas.sort((a, b) => a - b);\n\n  const p50Index = Math.floor(deltas.length * 0.50);\n  const p95Index = Math.floor(deltas.length * 0.95);\n  const p99Index = Math.floor(deltas.length * 0.99);\n\n  return {\n    min: min as Milliseconds,\n    max: max as Milliseconds,\n    avg: avg as Milliseconds,\n    p50: deltas[p50Index] as Milliseconds,\n    p95: deltas[p95Index] as Milliseconds,\n    p99: deltas[p99Index] as Milliseconds,\n  };\n}\n\n/**\n * Gets frame drop statistics for the scheduler.\n * \n * @param state - The scheduler state\n * @returns Frame drop statistics\n */\nexport function getFrameDropStats(state: RenderSchedulerState): FrameDropStats {\n  const internalState = internalStateMap.get(state.id);\n  if (!internalState) {\n    return {\n      totalDrops: 0,\n      consecutiveDrops: 0,\n      lastDropTimestamp: 0 as Milliseconds,\n    };\n  }\n\n  return {\n    totalDrops: internalState.totalDroppedFrames,\n    consecutiveDrops: internalState.consecutiveDroppedFrames,\n    lastDropTimestamp: internalState.lastDropTimestamp as Milliseconds,\n  };\n}\n\n/**\n * Gets total frames rendered and elapsed time.\n * \n * @param state - The scheduler state\n * @returns Object with totalFrames and elapsedTime\n */\nexport function getTotalFrames(state: RenderSchedulerState): { totalFrames: number; elapsedTime: Milliseconds } {\n  const internalState = internalStateMap.get(state.id);\n  if (!internalState) {\n    return { totalFrames: 0, elapsedTime: 0 as Milliseconds };\n  }\n\n  const elapsedTime = performance.now() - internalState.startTime;\n  return {\n    totalFrames: internalState.totalFrames,\n    elapsedTime: elapsedTime as Milliseconds,\n  };\n}\n\n/**\n * Destroys the render scheduler and cleans up resources.\n * Stops the loop if running and removes internal state from the map.\n * Returns a new state object with reset values.\n * After calling this, the scheduler must be recreated before use.\n * \n * **Important:** Always call this function when done with a scheduler to prevent memory leaks.\n * The internal state map will retain entries until explicitly destroyed.\n * \n * @param state - The scheduler state to destroy\n * @returns A new RenderSchedulerState with reset values\n */\nexport function destroyRenderScheduler(state: RenderSchedulerState): RenderSchedulerState {\n  const internalState = internalStateMap.get(state.id);\n  \n  if (internalState) {\n    // Stop the loop if running\n    if (internalState.rafId !== null) {\n      cancelAnimationFrame(internalState.rafId);\n      internalState.rafId = null;\n    }\n    \n    // Clear callback and frameHandler to prevent further execution\n    internalState.callback = null;\n    internalState.frameHandler = null;\n    \n    // Clean up internal state from map to prevent memory leak\n    internalStateMap.delete(state.id);\n  }\n\n  // Return new state with reset values\n  return createRenderScheduler();\n}\n\n/**\n * Convenience function that creates a scheduler and starts it in one step.\n * \n * @param callback - Function to call each frame with delta time\n * @returns A RenderSchedulerState with the loop running\n * @throws {Error} If callback is not provided\n * \n * @example\n * ```typescript\n * const scheduler = createRenderSchedulerAsync((deltaTime) => {\n *   renderFrame(deltaTime);\n * });\n * ```\n */\nexport function createRenderSchedulerAsync(callback: RenderCallback): RenderSchedulerState {\n  const state = createRenderScheduler();\n  return startRenderScheduler(state, callback);\n}\n\n/**\n * RenderScheduler class wrapper for backward compatibility.\n * \n * This class provides a class-based API that internally uses the functional implementation.\n * Use the functional API directly for better type safety and immutability.\n */\nexport class RenderScheduler {\n  private _state: RenderSchedulerState;\n\n  /**\n   * Checks if the scheduler is currently running.\n   */\n  get running(): boolean {\n    return this._state.running;\n  }\n\n  /**\n   * Creates a new RenderScheduler instance.\n   */\n  constructor() {\n    this._state = createRenderScheduler();\n  }\n\n  /**\n   * Starts the render loop.\n   * \n   * @param callback - Function to call each frame with delta time\n   * @throws {Error} If callback is not provided or scheduler already running\n   */\n  start(callback: RenderCallback): void {\n    this._state = startRenderScheduler(this._state, callback);\n  }\n\n  /**\n   * Stops the render loop.\n   */\n  stop(): void {\n    this._state = stopRenderScheduler(this._state);\n  }\n\n  /**\n   * Marks the current frame as dirty, indicating it needs to be rendered.\n   */\n  requestRender(): void {\n    requestRender(this._state);\n  }\n\n  /**\n   * Destroys the render scheduler and cleans up resources.\n   * After calling destroy(), the scheduler must be recreated before use.\n   */\n  destroy(): void {\n    this._state = destroyRenderScheduler(this._state);\n  }\n}\n","/**\n * CGPU-PIPELINE-CACHE\n *\n * Dedupes immutable WebGPU objects across charts:\n * - GPUShaderModule (keyed by WGSL source)\n * - GPURenderPipeline (keyed by identity-defining pipeline state)\n *\n * Notes:\n * - Cache is bound to a single GPUDevice.\n * - Cache auto-clears on device loss (`device.lost`) and resets stats.\n * - This cache does NOT store per-chart buffers/uniforms/bind groups.\n */\n\nexport type PipelineCacheStats = Readonly<{\n  readonly shaderModules: Readonly<{\n    readonly total: number;\n    readonly hits: number;\n    readonly misses: number;\n    readonly entries: number;\n  }>;\n  readonly renderPipelines: Readonly<{\n    readonly total: number;\n    readonly hits: number;\n    readonly misses: number;\n    readonly entries: number;\n  }>;\n  readonly computePipelines: Readonly<{\n    readonly total: number;\n    readonly hits: number;\n    readonly misses: number;\n    readonly entries: number;\n  }>;\n}>;\n\nexport interface PipelineCache {\n  readonly device: GPUDevice;\n\n  /**\n   * Returns an immutable snapshot of totals/hits/misses.\n   */\n  getStats(): PipelineCacheStats;\n\n  /**\n   * Clears all cached entries and resets stats.\n   * Automatically invoked on `device.lost`.\n   */\n  clear(): void;\n\n  /**\n   * Shader module dedupe keyed by WGSL source string.\n   */\n  getOrCreateShaderModule(code: string, label?: string): GPUShaderModule;\n\n  /**\n   * Render pipeline dedupe keyed by identity-defining fields.\n   */\n  getOrCreateRenderPipeline(descriptor: GPURenderPipelineDescriptor): GPURenderPipeline;\n\n  /**\n   * Compute pipeline dedupe keyed by identity-defining fields.\n   */\n  getOrCreateComputePipeline(descriptor: GPUComputePipelineDescriptor): GPUComputePipeline;\n}\n\nconst FNV1A_64_OFFSET = 0xcbf29ce484222325n;\nconst FNV1A_64_PRIME = 0x100000001b3n;\nconst U64_MASK: bigint = 0xffffffffffffffffn;\n\nconst fnv1a64Hex = (s: string): string => {\n  // Deterministic hash over UTF-16 code units.\n  let h = FNV1A_64_OFFSET;\n  for (let i = 0; i < s.length; i++) {\n    h ^= BigInt(s.charCodeAt(i));\n    h = BigInt(h * FNV1A_64_PRIME) & U64_MASK;\n  }\n  return h.toString(16).padStart(16, '0');\n};\n\n// Fast, deterministic render pipeline key generation.\n//\n// Performance goals:\n// - Avoid building large normalized objects + generic stable stringify\n// - Minimize allocations in hot paths (pipeline creation during renderer init)\n//\n// Correctness goals:\n// - Deterministic keys for equivalent descriptors\n// - Normalize default values so `{}` and `undefined` fields key identically\n// - Preserve existing semantics for nullable/undefined array entries (filtering)\nconst DEFAULT_WRITE_MASK_ALL = 0xF;\n\nconst pushTag = (parts: string[], tag: string): void => {\n  parts.push(tag, '|');\n};\n\nconst pushStr = (parts: string[], s: string): void => {\n  // Length-prefix prevents delimiter collisions without JSON escaping overhead.\n  parts.push('s', String(s.length), ':', s, '|');\n};\n\nconst pushNum = (parts: string[], n: number): void => {\n  parts.push('n', String(n), '|');\n};\n\nconst pushBool = (parts: string[], b: boolean): void => {\n  parts.push(b ? 't|' : 'f|');\n};\n\nconst pushNull = (parts: string[]): void => {\n  parts.push('0|');\n};\n\nconst pushConstants = (parts: string[], constants: Record<string, GPUPipelineConstantValue> | undefined): void => {\n  if (!constants) {\n    pushTag(parts, 'C0');\n    return;\n  }\n  const keys = Object.keys(constants);\n  if (keys.length === 0) {\n    pushTag(parts, 'C0');\n    return;\n  }\n  keys.sort();\n  pushTag(parts, 'C1');\n  pushNum(parts, keys.length);\n  for (let i = 0; i < keys.length; i++) {\n    const k = keys[i]!;\n    pushStr(parts, k);\n    // GPUPipelineConstantValue is numeric; encode deterministically.\n    const v = constants[k] as unknown as number;\n    pushNum(parts, v);\n  }\n};\n\nconst cmpString = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);\n\nconst pushVertexBuffers = (parts: string[], buffers: readonly (GPUVertexBufferLayout | null | undefined)[] | undefined): void => {\n  if (!buffers || buffers.length === 0) {\n    pushTag(parts, 'B0');\n    return;\n  }\n\n  // Preserve existing key semantics: filter falsey entries (even though this can shift slots).\n  let count = 0;\n  for (let i = 0; i < buffers.length; i++) {\n    if (buffers[i]) count++;\n  }\n  if (count === 0) {\n    pushTag(parts, 'B0');\n    return;\n  }\n\n  pushTag(parts, 'B1');\n  pushNum(parts, count);\n\n  for (let i = 0; i < buffers.length; i++) {\n    const b = buffers[i];\n    if (!b) continue;\n\n    pushNum(parts, b.arrayStride);\n    pushStr(parts, (b.stepMode ?? 'vertex') as string);\n\n    const attrs = Array.from(b.attributes ?? []);\n    if (attrs.length === 0) {\n      pushTag(parts, 'A0');\n      continue;\n    }\n    attrs.sort((a: GPUVertexAttribute, c: GPUVertexAttribute) => {\n      if (a.shaderLocation !== c.shaderLocation) return a.shaderLocation - c.shaderLocation;\n      if (a.offset !== c.offset) return a.offset - c.offset;\n      return cmpString(a.format as unknown as string, c.format as unknown as string);\n    });\n\n    pushTag(parts, 'A1');\n    pushNum(parts, attrs.length);\n    for (let j = 0; j < attrs.length; j++) {\n      const a = attrs[j]!;\n      pushNum(parts, a.shaderLocation);\n      pushNum(parts, a.offset);\n      pushStr(parts, a.format as unknown as string);\n    }\n  }\n};\n\nconst pushBlend = (parts: string[], blend: GPUBlendState | undefined): void => {\n  if (!blend) {\n    pushTag(parts, 'BL0');\n    return;\n  }\n  pushTag(parts, 'BL1');\n  pushStr(parts, blend.color.operation as unknown as string);\n  pushStr(parts, blend.color.srcFactor as unknown as string);\n  pushStr(parts, blend.color.dstFactor as unknown as string);\n  pushStr(parts, blend.alpha.operation as unknown as string);\n  pushStr(parts, blend.alpha.srcFactor as unknown as string);\n  pushStr(parts, blend.alpha.dstFactor as unknown as string);\n};\n\nconst pushTargets = (parts: string[], targets: readonly (GPUColorTargetState | null | undefined)[] | undefined): void => {\n  if (!targets || targets.length === 0) {\n    pushTag(parts, 'T0');\n    return;\n  }\n  pushTag(parts, 'T1');\n  pushNum(parts, targets.length);\n  for (let i = 0; i < targets.length; i++) {\n    const t = targets[i];\n    if (!t) {\n      pushNull(parts);\n      continue;\n    }\n    pushStr(parts, t.format as unknown as string);\n    pushBlend(parts, t.blend);\n    const writeMask = (t.writeMask as unknown as number | undefined) ?? DEFAULT_WRITE_MASK_ALL;\n    pushNum(parts, writeMask);\n  }\n};\n\nconst pushPrimitive = (parts: string[], p: GPUPrimitiveState | undefined): void => {\n  const topology = (p?.topology ?? 'triangle-list') as string;\n  const stripIndexFormat = (p?.stripIndexFormat ?? null) as unknown as string | null;\n  const frontFace = (p?.frontFace ?? 'ccw') as string;\n  const cullMode = (p?.cullMode ?? 'none') as string;\n  const unclippedDepth = p?.unclippedDepth ?? false;\n\n  pushStr(parts, topology);\n  if (stripIndexFormat == null) pushNull(parts);\n  else pushStr(parts, stripIndexFormat);\n  pushStr(parts, frontFace);\n  pushStr(parts, cullMode);\n  pushBool(parts, unclippedDepth);\n};\n\nconst pushMultisample = (parts: string[], m: GPUMultisampleState | undefined): void => {\n  const count = m?.count ?? 1;\n  const mask = (m?.mask as unknown as number | undefined) ?? 0xffffffff;\n  const alphaToCoverageEnabled = m?.alphaToCoverageEnabled ?? false;\n  pushNum(parts, count);\n  pushNum(parts, mask);\n  pushBool(parts, alphaToCoverageEnabled);\n};\n\nconst pushStencilFace = (parts: string[], face: GPUStencilFaceState | undefined): void => {\n  if (!face) {\n    pushTag(parts, 'SF0');\n    return;\n  }\n  pushTag(parts, 'SF1');\n  pushStr(parts, face.compare as unknown as string);\n  pushStr(parts, face.failOp as unknown as string);\n  pushStr(parts, face.depthFailOp as unknown as string);\n  pushStr(parts, face.passOp as unknown as string);\n};\n\nconst pushDepthStencil = (parts: string[], d: GPUDepthStencilState | undefined): void => {\n  if (!d) {\n    pushTag(parts, 'DS0');\n    return;\n  }\n  pushTag(parts, 'DS1');\n  pushStr(parts, d.format as unknown as string);\n  pushBool(parts, d.depthWriteEnabled ?? false);\n  pushStr(parts, (d.depthCompare ?? 'always') as unknown as string);\n  pushStencilFace(parts, d.stencilFront);\n  pushStencilFace(parts, d.stencilBack);\n  pushNum(parts, (d.stencilReadMask as unknown as number | undefined) ?? 0xffffffff);\n  pushNum(parts, (d.stencilWriteMask as unknown as number | undefined) ?? 0xffffffff);\n  pushNum(parts, d.depthBias ?? 0);\n  pushNum(parts, d.depthBiasSlopeScale ?? 0);\n  pushNum(parts, d.depthBiasClamp ?? 0);\n};\n\nexport function createPipelineCache(device: GPUDevice): PipelineCache {\n  const shaderModuleByWgsl = new Map<string, GPUShaderModule>();\n  const renderPipelineByKey = new Map<string, GPURenderPipeline>();\n  const computePipelineByKey = new Map<string, GPUComputePipeline>();\n\n  let shaderTotal = 0;\n  let shaderHits = 0;\n  let shaderMisses = 0;\n\n  let pipeTotal = 0;\n  let pipeHits = 0;\n  let pipeMisses = 0;\n\n  let computeTotal = 0;\n  let computeHits = 0;\n  let computeMisses = 0;\n\n  // Weak identity maps for keying.\n  let moduleIdByModule = new WeakMap<GPUShaderModule, string>();\n  let layoutIdByLayout = new WeakMap<GPUPipelineLayout, string>();\n  let nextExternalModuleId = 0;\n  let nextLayoutId = 0;\n\n  const getOrAssignModuleId = (module: GPUShaderModule): string => {\n    const existing = moduleIdByModule.get(module);\n    if (existing) return existing;\n    const id = `ext:${++nextExternalModuleId}`;\n    moduleIdByModule.set(module, id);\n    return id;\n  };\n\n  const getOrAssignLayoutId = (layout: GPUPipelineLayout): string => {\n    const existing = layoutIdByLayout.get(layout);\n    if (existing) return existing;\n    const id = `layout:${++nextLayoutId}`;\n    layoutIdByLayout.set(layout, id);\n    return id;\n  };\n\n  const clear = (): void => {\n    shaderModuleByWgsl.clear();\n    renderPipelineByKey.clear();\n    computePipelineByKey.clear();\n\n    shaderTotal = 0;\n    shaderHits = 0;\n    shaderMisses = 0;\n\n    pipeTotal = 0;\n    pipeHits = 0;\n    pipeMisses = 0;\n\n    computeTotal = 0;\n    computeHits = 0;\n    computeMisses = 0;\n\n    moduleIdByModule = new WeakMap();\n    layoutIdByLayout = new WeakMap();\n    nextExternalModuleId = 0;\n    nextLayoutId = 0;\n  };\n\n  // Auto-clear on device loss (best-effort).\n  // Some test mocks may not provide a proper device.lost promise; handle gracefully.\n  try {\n    void device.lost\n      .then(() => {\n        clear();\n      })\n      .catch((err) => {\n        // Device loss promise rejection is unusual; log for debugging.\n        // Suppress in test environments where mocks may behave differently.\n        if (typeof err === 'object' && err !== null && 'message' in err) {\n          console.warn('PipelineCache: device.lost promise rejected:', err);\n        }\n        clear();\n      });\n  } catch (err) {\n    // Some mocks may not expose device.lost at all; this is acceptable.\n    // Real GPUDevice instances always have device.lost, so this catch is primarily for tests.\n  }\n\n  const getOrCreateShaderModule = (code: string, label?: string): GPUShaderModule => {\n    shaderTotal++;\n    const cached = shaderModuleByWgsl.get(code);\n    if (cached) {\n      shaderHits++;\n      return cached;\n    }\n    shaderMisses++;\n    const module = device.createShaderModule({ code, label });\n    shaderModuleByWgsl.set(code, module);\n    // Prefer stable id derived from WGSL (shorter than embedding source into pipeline keys).\n    const stableId = `wgsl:${fnv1a64Hex(code)}:${code.length}`;\n    moduleIdByModule.set(module, stableId);\n    return module;\n  };\n\n  const getOrCreateRenderPipeline = (descriptor: GPURenderPipelineDescriptor): GPURenderPipeline => {\n    pipeTotal++;\n\n    const layout = (descriptor.layout ?? 'auto') as GPUPipelineLayout | 'auto';\n    const layoutKey = layout === 'auto' ? 'auto' : getOrAssignLayoutId(layout);\n\n    const vertex = descriptor.vertex;\n    const fragment = descriptor.fragment;\n\n    const parts: string[] = [];\n    // Version prefix allows evolving the key format without ambiguity.\n    pushTag(parts, 'rp1');\n\n    pushTag(parts, 'L');\n    pushStr(parts, layoutKey);\n\n    pushTag(parts, 'V');\n    pushStr(parts, getOrAssignModuleId(vertex.module));\n    pushStr(parts, vertex.entryPoint ?? '');\n    pushConstants(parts, vertex.constants);\n    pushVertexBuffers(parts, vertex.buffers as unknown as readonly (GPUVertexBufferLayout | null | undefined)[]);\n\n    pushTag(parts, 'F');\n    if (!fragment) {\n      pushTag(parts, 'F0');\n    } else {\n      pushTag(parts, 'F1');\n      pushStr(parts, getOrAssignModuleId(fragment.module));\n      pushStr(parts, fragment.entryPoint ?? '');\n      pushConstants(parts, fragment.constants);\n      pushTargets(parts, fragment.targets as unknown as readonly (GPUColorTargetState | null | undefined)[]);\n    }\n\n    pushTag(parts, 'P');\n    pushPrimitive(parts, descriptor.primitive);\n\n    pushDepthStencil(parts, descriptor.depthStencil);\n\n    pushTag(parts, 'M');\n    pushMultisample(parts, descriptor.multisample);\n\n    const cacheKey = parts.join('');\n    const cached = renderPipelineByKey.get(cacheKey);\n    if (cached) {\n      pipeHits++;\n      return cached;\n    }\n\n    pipeMisses++;\n    const pipeline = device.createRenderPipeline(descriptor);\n    renderPipelineByKey.set(cacheKey, pipeline);\n    return pipeline;\n  };\n\n  const getOrCreateComputePipeline = (descriptor: GPUComputePipelineDescriptor): GPUComputePipeline => {\n    computeTotal++;\n\n    const layout = (descriptor.layout ?? 'auto') as GPUPipelineLayout | 'auto';\n    const layoutKey = layout === 'auto' ? 'auto' : getOrAssignLayoutId(layout);\n\n    const compute = descriptor.compute;\n\n    const parts: string[] = [];\n    // Version prefix allows evolving the key format without ambiguity.\n    pushTag(parts, 'cp1');\n\n    pushTag(parts, 'L');\n    pushStr(parts, layoutKey);\n\n    pushTag(parts, 'CS');\n    pushStr(parts, getOrAssignModuleId(compute.module));\n    pushStr(parts, compute.entryPoint ?? '');\n    pushConstants(parts, compute.constants);\n\n    const cacheKey = parts.join('');\n    const cached = computePipelineByKey.get(cacheKey);\n    if (cached) {\n      computeHits++;\n      return cached;\n    }\n\n    computeMisses++;\n    const pipeline = device.createComputePipeline(descriptor);\n    computePipelineByKey.set(cacheKey, pipeline);\n    return pipeline;\n  };\n\n  const getStats = (): PipelineCacheStats => ({\n    shaderModules: {\n      total: shaderTotal,\n      hits: shaderHits,\n      misses: shaderMisses,\n      entries: shaderModuleByWgsl.size,\n    },\n    renderPipelines: {\n      total: pipeTotal,\n      hits: pipeHits,\n      misses: pipeMisses,\n      entries: renderPipelineByKey.size,\n    },\n    computePipelines: {\n      total: computeTotal,\n      hits: computeHits,\n      misses: computeMisses,\n      entries: computePipelineByKey.size,\n    },\n  });\n\n  return {\n    device,\n    getStats,\n    clear,\n    getOrCreateShaderModule,\n    getOrCreateRenderPipeline,\n    getOrCreateComputePipeline,\n  };\n}\n","/**\n * Public entrypoint for CGPU-PIPELINE-CACHE.\n *\n * This module re-exports the core PipelineCache types and factory function,\n * plus convenience helpers for stats and disposal.\n */\n\nexport type { PipelineCache, PipelineCacheStats } from './PipelineCache';\nexport { createPipelineCache } from './PipelineCache';\n\nimport type { PipelineCache, PipelineCacheStats } from './PipelineCache';\n\n/**\n * Gets statistics for a pipeline cache.\n * \n * @param cache - The pipeline cache to query\n * @returns Cache statistics including hit/miss counts and pipeline count\n */\nexport function getPipelineCacheStats(cache: PipelineCache): PipelineCacheStats {\n  return cache.getStats();\n}\n\n/**\n * Destroys a pipeline cache and releases its resources.\n * \n * @param cache - The pipeline cache to destroy\n */\nexport function destroyPipelineCache(cache: PipelineCache): void {\n  cache.clear();\n}\n","/**\n * ChartGPU - A GPU-accelerated charting library built with WebGPU\n */\n\nexport const version = '1.0.0';\n\n// Chart API (Phase 1)\nimport { ChartGPU as ChartGPUNamespace } from './ChartGPU';\n\n// Export ChartGPU namespace\nexport const ChartGPU = ChartGPUNamespace;\n\nexport { createChartGPU as createChart } from './ChartGPU';\nexport type { ChartGPUInstance } from './ChartGPU';\nexport type {\n  ChartGPUEventName,\n  ChartGPUEventPayload,\n  ChartGPUCrosshairMovePayload,\n  ChartGPUEventCallback,\n  ChartGPUCrosshairMoveCallback,\n  ChartGPUZoomRangeChangePayload,\n  ChartGPUZoomRangeChangeCallback,\n  ChartGPUDeviceLostPayload,\n  ChartGPUDeviceLostCallback,\n  ChartGPUCreateContext,\n  ChartGPUHitTestMatch,\n  ChartGPUHitTestResult,\n  ZoomChangeSourceKind,\n} from './ChartGPU';\nexport type {\n  AnnotationConfig,\n  AnnotationConfigBase,\n  AnnotationLabel,\n  AnnotationLabelAnchor,\n  AnnotationLabelBackground,\n  AnnotationLabelPadding,\n  AnnotationLayer,\n  AnnotationLineX,\n  AnnotationLineY,\n  AnnotationPoint,\n  AnnotationPointMarker,\n  AnnotationPosition,\n  AnnotationStyle,\n  AnnotationText,\n  AreaStyleConfig,\n  AnimationConfig,\n  AxisConfig,\n  AxisType,\n  BarItemStyleConfig,\n  CandlestickItemStyleConfig,\n  CandlestickSeriesConfig,\n  CandlestickStyle,\n  ChartGPUOptions,\n  DataZoomConfig,\n  DataPoint,\n  GridConfig,\n  GridLinesConfig,\n  GridLinesDirectionConfig,\n  LegendConfig,\n  LegendPosition,\n  LineStyleConfig,\n  AreaSeriesConfig,\n  LineSeriesConfig,\n  BarSeriesConfig,\n  PerformanceMetrics,\n  OHLCDataPoint,\n  PieCenter,\n  PieDataItem,\n  PieItemStyleConfig,\n  PieRadius,\n  PieSeriesConfig,\n  RenderMode,\n  ScatterSeriesConfig,\n  ScatterSymbol,\n  ScatterPointTuple,\n  SeriesConfig,\n  SeriesSampling,\n  SeriesType,\n  TooltipConfig,\n  TooltipParams,\n} from './config/types';\n\n// Options defaults + resolution\nexport { candlestickDefaults, defaultOptions } from './config/defaults';\nexport { OptionResolver, resolveOptions } from './config/OptionResolver';\nexport type {\n  ResolvedCandlestickSeriesConfig,\n  ResolvedChartGPUOptions,\n  ResolvedAreaSeriesConfig,\n  ResolvedAreaStyleConfig,\n  ResolvedGridConfig,\n  ResolvedGridLinesConfig,\n  ResolvedGridLinesDirectionConfig,\n  ResolvedLineSeriesConfig,\n  ResolvedLineStyleConfig,\n  ResolvedPieDataItem,\n  ResolvedPieSeriesConfig,\n  ResolvedSeriesConfig,\n} from './config/OptionResolver';\n\n// Themes\nexport type { ThemeConfig } from './themes/types';\nexport { darkTheme, lightTheme, getTheme } from './themes';\nexport type { ThemeName } from './themes';\n\n// Scales - Pure utilities\nexport { createLinearScale, createCategoryScale } from './utils/scales';\nexport type { LinearScale, CategoryScale } from './utils/scales';\n\n// Chart sync (interaction)\nexport { connectCharts } from './interaction/createChartSync';\nexport type { ChartSyncOptions } from './interaction/createChartSync';\n\n// Annotation authoring (interaction)\nexport { createAnnotationAuthoring } from './interaction/createAnnotationAuthoring';\nexport type { AnnotationAuthoringInstance, AnnotationAuthoringOptions } from './interaction/createAnnotationAuthoring';\n\n// Core exports - Functional API (preferred)\nexport type {\n  GPUContextState,\n  GPUContextOptions,\n  SupportedCanvas,\n} from './core/GPUContext';\nexport {\n  createGPUContext,\n  createGPUContextAsync,\n  initializeGPUContext,\n  getCanvasTexture,\n  clearScreen,\n  destroyGPUContext,\n} from './core/GPUContext';\n\n// Class-based API (for backward compatibility)\nexport { GPUContext } from './core/GPUContext';\n\n// Render scheduler - Functional API (preferred)\nexport type { RenderSchedulerState, RenderCallback } from './core/RenderScheduler';\nexport {\n  createRenderScheduler,\n  createRenderSchedulerAsync,\n  startRenderScheduler,\n  stopRenderScheduler,\n  requestRender,\n  destroyRenderScheduler,\n} from './core/RenderScheduler';\n\n// Class-based API (for backward compatibility)\nexport { RenderScheduler } from './core/RenderScheduler';\n\n// Pipeline cache - Functional API\nexport type { PipelineCache, PipelineCacheStats } from './core/PipelineCache';\nexport { createPipelineCache, getPipelineCacheStats, destroyPipelineCache } from './core/createPipelineCache';\n"]}