@opendata-ai/openchart-vanilla 8.2.3 → 8.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/barlist-mount.ts","../src/barlist-renderer.ts","../src/export-sequence.ts","../src/graph/simulation-worker-url.ts","../src/graph-mount.ts","../src/graph/camera.ts","../src/graph/zoom.ts","../src/graph/canvas-renderer.ts","../src/graph/entrance.ts","../src/graph/focus-transition.ts","../src/graph/interaction.ts","../src/graph/keyboard.ts","../src/graph/legend.ts","../src/graph/search.ts","../src/graph/seed.ts","../src/graph/simulation.ts","../src/graph/spatial-index.ts","../src/graph/update-diff-config.ts","../src/graph/update-diff.ts","../src/renderers/table-cells.ts","../src/sankey-mount.ts","../src/sankey-renderer.ts","../src/table-keyboard.ts","../src/table-mount.ts","../src/table-renderer.ts","../src/tilemap-mount.ts"],"sourcesContent":["/**\n * BarList mount API: the main entry point for vanilla JS barlist usage.\n *\n * createBarList() takes a container, BarListSpec, and options, compiles the\n * barlist, renders it as SVG, sets up responsive resizing, tooltip interaction,\n * and returns a BarListInstance with update/resize/export/destroy.\n */\n\nimport type {\n BarListLayout,\n BarListSpec,\n CompileOptions,\n DarkMode,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { compileBarList } from '@opendata-ai/openchart-engine';\nimport { cancelAnimations, setupAnimationCleanup } from './animation';\nimport { renderBarListSVG } from './barlist-renderer';\nimport {\n exportJPG,\n exportPNG,\n exportSVG,\n exportSVGWithFonts,\n type JPGExportOptions,\n type SVGExportOptions,\n} from './export';\nimport { createMeasureText, resolveFontFamily, scheduleFontReload } from './measure-text';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { createTooltipManager, type TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface BarListMountOptions {\n theme?: ThemeConfig;\n darkMode?: DarkMode;\n responsive?: boolean;\n watermark?: boolean;\n tooltip?: boolean;\n onRowClick?: (row: { label: string; value: number; data: Record<string, unknown> }) => void;\n onRowHover?: (\n row: { label: string; value: number; data: Record<string, unknown> } | null,\n ) => void;\n}\n\nexport interface BarListInstance {\n update(spec: BarListSpec): void;\n resize(): void;\n export(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options?: JPGExportOptions | SVGExportOptions,\n ): string | Promise<Blob> | Promise<string>;\n destroy(): void;\n readonly layout: BarListLayout;\n}\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\nexport function createBarList(\n container: HTMLElement,\n spec: BarListSpec,\n options?: BarListMountOptions,\n): BarListInstance {\n let currentSpec = spec;\n let currentLayout: BarListLayout;\n let destroyed = false;\n\n let svgElement: SVGSVGElement | null = null;\n let tooltipManager: TooltipManager | null = null;\n let cleanupTooltipEvents: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n let animationCleanup: (() => void) | null = null;\n let pendingResize = false;\n\n // Set when webfonts have loaded and a recompile is owed. The next render()\n // that recompiles flips data-oc-fonts-state to 'ready' and clears this, so\n // the attribute stays honest when resize() defers to pendingResize during\n // the entrance animation.\n let fontsReloadPending = false;\n\n // Apply the root class up front so getComputedStyle sees --oc-font-family\n // before the text measurer is built.\n container.classList.add('oc-barlist-root');\n\n // Resolve the effective font the way compile() will: compile merges\n // { ...spec.theme, ...options.theme }, so options.theme wins over the\n // spec-level theme; fall back to the container's computed font. Measuring a\n // different font than gets rendered desyncs layout metrics and the reload watcher.\n function resolveEffectiveFont(): string {\n return (\n options?.theme?.fonts?.family ??\n currentSpec.theme?.fonts?.family ??\n resolveFontFamily(container)\n );\n }\n let fontFamily = resolveEffectiveFont();\n let measureText = createMeasureText(fontFamily);\n let renderGen = 0;\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n function compile(): BarListLayout {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n measureText,\n };\n\n const layout = compileBarList(currentSpec, compileOpts);\n\n // Auto-size height to content so few-row lists don't leave empty space.\n if (layout.rows.length > 0) {\n const lastRow = layout.rows[layout.rows.length - 1];\n const contentBottom =\n lastRow.y + lastRow.height + layout.chrome.bottomHeight + layout.theme.spacing.padding;\n if (contentBottom < layout.height) {\n layout.height = contentBottom;\n layout.area.height =\n contentBottom - layout.area.y - layout.chrome.bottomHeight - layout.theme.spacing.padding;\n }\n }\n\n return layout;\n }\n\n function wireTooltipAndInteraction(svg: SVGSVGElement, layout: BarListLayout): () => void {\n const cleanups: Array<() => void> = [];\n\n const rowElements = svg.querySelectorAll('.oc-barlist-row');\n for (const el of rowElements) {\n const indexStr = el.getAttribute('data-row-index');\n if (indexStr === null) continue;\n\n const content = layout.tooltipDescriptors.get(indexStr);\n const row = layout.rows[Number(indexStr)];\n\n const handleMouseEnter = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n if (content && tooltipManager && options?.tooltip !== false) {\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n if (row) {\n options?.onRowHover?.({\n label: row.label.text,\n value: row.value,\n data: row.data,\n });\n }\n };\n\n const handleMouseMove = (e: Event) => {\n if (content && tooltipManager && options?.tooltip !== false) {\n const mouseEvent = e as MouseEvent;\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n };\n\n const handleMouseLeave = () => {\n tooltipManager?.hide();\n options?.onRowHover?.(null);\n };\n\n const handleClick = () => {\n if (row) {\n options?.onRowClick?.({\n label: row.label.text,\n value: row.value,\n data: row.data,\n });\n }\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n\n cleanups.push(() => {\n el.removeEventListener('mouseenter', handleMouseEnter);\n el.removeEventListener('mousemove', handleMouseMove);\n el.removeEventListener('mouseleave', handleMouseLeave);\n el.removeEventListener('click', handleClick);\n });\n }\n\n return () => {\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n function render(animate = false): void {\n if (animationCleanup) {\n animationCleanup();\n animationCleanup = null;\n }\n\n if (svgElement) {\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n svgElement.remove();\n }\n\n const newSvg = renderBarListSVG(currentLayout, { animate });\n container.appendChild(newSvg);\n svgElement = newSvg;\n\n cleanupTooltipEvents = wireTooltipAndInteraction(newSvg, currentLayout);\n\n if (options?.tooltip !== false) {\n if (!tooltipManager) {\n tooltipManager = createTooltipManager(container);\n }\n }\n\n if (currentLayout.animation?.enter) {\n animationCleanup = setupAnimationCleanup(newSvg, () => {\n // Null the handle BEFORE replaying, or the deferred resize hits the\n // entrance-in-flight gate and re-defers forever (mount.ts pattern).\n animationCleanup = null;\n if (pendingResize && !destroyed) {\n pendingResize = false;\n resize();\n }\n });\n }\n\n renderGen += 1;\n container.dataset.ocRenderGen = String(renderGen);\n\n // This render recompiled with the loaded webfonts; publish 'ready' now\n // rather than right after the fonts-ready resize() (which may have deferred\n // to pendingResize during the entrance animation).\n if (fontsReloadPending) {\n fontsReloadPending = false;\n container.dataset.ocFontsState = 'ready';\n }\n }\n\n function update(newSpec: BarListSpec): void {\n currentSpec = newSpec;\n // A new spec can change theme.fonts.family; rebuild the measurer so layout\n // measures the font compile will actually render with.\n const nextFont = resolveEffectiveFont();\n if (nextFont !== fontFamily) {\n fontFamily = nextFont;\n measureText = createMeasureText(fontFamily);\n }\n currentLayout = compile();\n render();\n }\n\n function resize(): void {\n if (destroyed) return;\n if (animationCleanup) {\n pendingResize = true;\n return;\n }\n currentLayout = compile();\n render();\n }\n\n function exportChart(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options_?: JPGExportOptions | SVGExportOptions,\n ): string | Promise<Blob> | Promise<string> {\n if (!svgElement) return '';\n switch (format) {\n case 'svg':\n return exportSVG(svgElement);\n case 'svg-with-fonts':\n return exportSVGWithFonts(svgElement);\n case 'png':\n return exportPNG(svgElement, options_ as JPGExportOptions);\n case 'jpg':\n return exportJPG(svgElement, options_ as JPGExportOptions);\n default:\n return '';\n }\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n if (animationCleanup) {\n cancelAnimations(svgElement);\n animationCleanup();\n animationCleanup = null;\n }\n\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n\n if (svgElement) {\n svgElement.remove();\n svgElement = null;\n }\n\n if (tooltipManager) {\n tooltipManager.destroy();\n tooltipManager = null;\n }\n\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n container.classList.remove('oc-barlist-root', 'oc-dark');\n }\n\n // Initialize\n if (resolveDarkMode(options?.darkMode)) {\n container.classList.add('oc-dark');\n }\n\n currentLayout = compile();\n render(true);\n\n // Recompile once after webfonts load so row labels aren't stuck measured\n // against fallback metrics on real devices.\n const fontsPending = scheduleFontReload(\n fontFamily,\n () => !destroyed,\n () => {\n // Mark the reload owed, then recompile. render() flips the attribute to\n // 'ready' once it actually recompiles, including the pendingResize replay\n // after the entrance animation finishes.\n fontsReloadPending = true;\n resize();\n },\n );\n container.dataset.ocFontsState = fontsPending ? 'pending' : 'ready';\n\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => resize());\n }\n\n return {\n update,\n resize,\n export: exportChart,\n destroy,\n get layout(): BarListLayout {\n return currentLayout;\n },\n };\n}\n","/**\n * BarList SVG renderer: converts a BarListLayout into SVG DOM elements.\n *\n * Creates an <svg> with rows of label + track + bar + value. Animation is\n * pure CSS, driven by data attributes and CSS custom properties.\n */\n\nimport type { BarListLayout, BarListRowMark, ResolvedAnimation } from '@opendata-ai/openchart-core';\nimport { textAscent } from '@opendata-ai/openchart-core';\nimport { stampAnimationVars } from './animation-vars';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst XLINK_NS = 'http://www.w3.org/1999/xlink';\nconst BRAND_URL = 'https://tryopendata.ai';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createSVGElement(tag: string): SVGElement {\n return document.createElementNS(SVG_NS, tag);\n}\n\nfunction setAttrs(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, String(value));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Chrome rendering\n// ---------------------------------------------------------------------------\n\nfunction renderChrome(parent: SVGElement, layout: BarListLayout): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-chrome');\n\n const { chrome } = layout;\n const bottomOffset = layout.area.y + layout.area.height;\n\n for (const key of ['title', 'subtitle', 'source', 'byline', 'footer'] as const) {\n const el = chrome[key];\n if (!el) continue;\n\n const isBottom = key === 'source' || key === 'byline' || key === 'footer';\n const text = createSVGElement('text');\n // el.y is the TOP of the text box; convert to the alphabetic baseline\n // (WebKit mishandles dominant-baseline:hanging, see renderers/chrome.ts).\n setAttrs(text, {\n x: el.x,\n y: (isBottom ? bottomOffset + el.y : el.y) + textAscent(el.style.fontSize),\n });\n text.setAttribute('class', `oc-${key}`);\n text.setAttribute('font-family', el.style.fontFamily);\n text.setAttribute('font-size', String(el.style.fontSize));\n text.setAttribute('font-weight', String(el.style.fontWeight));\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', el.style.fill);\n text.textContent = el.text;\n g.appendChild(text);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Watermark rendering\n// ---------------------------------------------------------------------------\n\nfunction renderWatermark(parent: SVGElement, layout: BarListLayout): void {\n if (layout.width < 480) return;\n\n const { width, height, theme } = layout;\n const padding = theme.spacing.padding;\n const rightEdge = width - padding;\n const bottomEdge = height - padding;\n const fill = theme.colors.axis;\n\n const a = createSVGElement('a');\n a.setAttribute('href', BRAND_URL);\n a.setAttributeNS(XLINK_NS, 'xlink:href', BRAND_URL);\n a.setAttribute('target', '_blank');\n a.setAttribute('rel', 'noopener');\n a.setAttribute('class', 'oc-chrome-ref');\n\n const text = createSVGElement('text');\n setAttrs(text, {\n x: rightEdge,\n y: bottomEdge,\n 'dominant-baseline': 'alphabetic',\n 'text-anchor': 'end',\n 'font-family': theme.fonts.family,\n 'font-size': 12,\n 'fill-opacity': 0.55,\n });\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', fill);\n\n const trySpan = createSVGElement('tspan');\n setAttrs(trySpan, { 'font-weight': 500 });\n trySpan.textContent = 'try';\n\n const openDataSpan = createSVGElement('tspan');\n setAttrs(openDataSpan, { 'font-weight': 600, 'font-size': 16 });\n openDataSpan.textContent = 'OpenData';\n\n const aiSpan = createSVGElement('tspan');\n setAttrs(aiSpan, { 'font-weight': 500 });\n aiSpan.textContent = '.ai';\n\n text.appendChild(trySpan);\n text.appendChild(openDataSpan);\n text.appendChild(aiSpan);\n a.appendChild(text);\n parent.appendChild(a);\n}\n\n// ---------------------------------------------------------------------------\n// Row rendering\n// ---------------------------------------------------------------------------\n\nfunction renderRows(\n parent: SVGElement,\n rows: BarListRowMark[],\n animation?: ResolvedAnimation,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-barlist-rows');\n g.setAttribute('role', 'list');\n\n for (const row of rows) {\n const rowGroup = createSVGElement('g');\n rowGroup.setAttribute('class', 'oc-barlist-row');\n rowGroup.setAttribute('data-row-index', String(row.index));\n rowGroup.setAttribute('role', 'listitem');\n if (row.aria?.label) {\n rowGroup.setAttribute('aria-label', row.aria.label);\n }\n\n if (animation?.enter) {\n rowGroup.setAttribute('data-animation-index', String(row.animationIndex));\n const style = (rowGroup as SVGElement & ElementCSSInlineStyle).style;\n style.setProperty('--oc-mark-index', String(row.animationIndex));\n style.setProperty('--oc-row-delay', `${row.animationIndex * 40}ms`);\n }\n\n // Label text\n const labelEl = createSVGElement('text');\n setAttrs(labelEl, {\n x: row.label.x,\n y: row.label.y,\n 'dominant-baseline': 'central',\n 'text-anchor': 'start',\n 'font-family': row.label.style.fontFamily,\n 'font-size': row.label.style.fontSize,\n 'font-weight': row.label.style.fontWeight,\n });\n (labelEl as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', row.label.style.fill);\n labelEl.textContent = row.label.text;\n rowGroup.appendChild(labelEl);\n\n // Subtitle text (if present)\n if (row.subtitle?.visible) {\n const subEl = createSVGElement('text');\n setAttrs(subEl, {\n x: row.subtitle.x,\n y: row.subtitle.y,\n 'dominant-baseline': 'central',\n 'text-anchor': 'start',\n 'font-family': row.subtitle.style.fontFamily,\n 'font-size': row.subtitle.style.fontSize,\n 'font-weight': row.subtitle.style.fontWeight,\n });\n (subEl as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n row.subtitle.style.fill,\n );\n subEl.textContent = row.subtitle.text;\n rowGroup.appendChild(subEl);\n }\n\n // Track (muted background bar)\n const trackRect = createSVGElement('rect');\n setAttrs(trackRect, {\n x: row.track.x,\n y: row.track.y,\n width: row.track.width,\n height: row.track.height,\n rx: row.track.cornerRadius,\n fill: 'currentColor',\n 'fill-opacity': 0.06,\n });\n trackRect.setAttribute('class', 'oc-barlist-track');\n rowGroup.appendChild(trackRect);\n\n // Fill bar\n const barRect = createSVGElement('rect');\n setAttrs(barRect, {\n x: row.bar.x,\n y: row.bar.y,\n width: row.bar.width,\n height: row.bar.height,\n rx: row.bar.cornerRadius,\n fill: row.bar.fill,\n });\n barRect.setAttribute('class', 'oc-barlist-bar');\n rowGroup.appendChild(barRect);\n\n // Value label (right-aligned)\n const valueEl = createSVGElement('text');\n setAttrs(valueEl, {\n x: row.valueLabel.x,\n y: row.valueLabel.y,\n 'dominant-baseline': 'central',\n 'text-anchor': 'end',\n 'font-family': row.valueLabel.style.fontFamily,\n 'font-size': row.valueLabel.style.fontSize,\n 'font-weight': row.valueLabel.style.fontWeight,\n });\n (valueEl as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n row.valueLabel.style.fill,\n );\n valueEl.textContent = row.valueLabel.text;\n rowGroup.appendChild(valueEl);\n\n g.appendChild(rowGroup);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport function renderBarListSVG(\n layout: BarListLayout,\n opts?: { animate?: boolean },\n): SVGSVGElement {\n const { width, height, rows, a11y, watermark, animation } = layout;\n const animate = opts?.animate && !!animation?.enter;\n\n const svg = createSVGElement('svg') as SVGSVGElement;\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`);\n svg.setAttribute('role', 'list');\n // No explicit width attribute — CSS width:100% on .oc-barlist-root handles it.\n // Explicit pixel height via inline style avoids iOS Safari's height:100% quirk.\n svg.style.height = `${height}px`;\n if (a11y.altText) {\n svg.setAttribute('aria-label', a11y.altText);\n }\n\n const classes = animate ? 'oc-barlist oc-animate' : 'oc-barlist';\n svg.setAttribute('class', classes);\n\n if (animate && animation?.enter) {\n stampAnimationVars(svg, { duration: animation.enter.duration, stagger: 40 });\n }\n\n renderChrome(svg, layout);\n renderRows(svg, rows, animate ? animation : undefined);\n\n if (watermark) {\n renderWatermark(svg, layout);\n }\n\n return svg;\n}\n","/**\n * Multi-spec (\"keyframe\") GIF export.\n *\n * The single-chart GIF export (`exportGIF`) reproduces ONE chart's entrance\n * animation. Some charts animate a different way: a component swaps in a whole\n * new spec on a timer (e.g. a year-by-year stepper), and the on-screen chart\n * tweens between them via `chart.update()` (bars grow/shrink smoothly). The DATA\n * changes across frames, and the motion is the update transition — neither of\n * which `exportGIF` can express.\n *\n * `exportSpecSequence` reproduces that. It mounts one offscreen chart, holds the\n * first spec, then walks the rest by driving `chart.beginManualUpdate(spec)` and\n * stepping the returned transition frame-by-frame (no rAF clock), rasterizing\n * each step. Between transitions it holds the settled frame for `dwellMs`. The\n * result is the same bar tween the reader sees on screen, captured to GIF.\n *\n * `gifenc` is an optional peer dependency, loaded dynamically.\n */\n\nimport type {\n AnimationSpec,\n ChartSpec,\n LayerSpec,\n ResolvedTheme,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport {\n embedFonts,\n ensureSVGDimensions,\n exportSVG,\n getSVGBackgroundColor,\n getSVGDimensions,\n rasterizeSVGToCanvas,\n} from './export';\nimport { isModuleNotFound, resolveRepeat } from './export-gif';\nimport { paletteFromCanvas, readCanvasSRGB } from './gif-encode';\nimport { createChart } from './mount';\nimport { injectThemeStyleBlock } from './theme-style-block';\n\nexport interface SpecSequenceOptions {\n /** DPI scaling factor, matching PNG export. Defaults to 2. */\n dpi?: number;\n /** Frames per second for the tween between specs. Defaults to 25. */\n fps?: number;\n /** How long to hold each spec's settled frame, in ms. Defaults to 1200 (the common stepper cadence). */\n dwellMs?: number;\n /**\n * Loop behavior, same semantics as `exportGIF`:\n * - `false` / omitted: play once, then hold the final frame\n * - `true`: loop forever\n * - a number: loop that many times, then hold\n */\n loop?: boolean | number;\n /** Render width in CSS px. Required — pass the on-screen chart width so the export matches. */\n width: number;\n /** Render height in CSS px. Required — pass the on-screen chart height so the export matches. */\n height: number;\n /** Dark mode for the rendered charts. Defaults to 'off' (light). */\n darkMode?: 'auto' | 'force' | 'off';\n /**\n * Rendering backend for point marks; see `MountOptions.renderer`. Pass the\n * SAME value your on-screen chart mounts with so the export matches.\n */\n renderer?: 'auto' | 'svg' | 'canvas';\n /**\n * Theme overrides applied when rendering, exactly as a consumer's live wrapper\n * would apply them. Pass the SAME theme your on-screen chart uses (e.g. the\n * blog's `blogChartTheme(isDark)`), or the export won't match — its background,\n * gridlines, and chrome colors fall back to openchart's raw defaults.\n */\n theme?: ThemeConfig;\n /** Embed fonts as base64 so text matches on-screen. Defaults to true. */\n embedFonts?: boolean;\n /** Opaque background fill. Defaults to the chart's own background (white if transparent). */\n backgroundColor?: string;\n}\n\n/**\n * Force `enter:false` (each spec must render settled) while KEEPING the author's\n * own update config when they wrote one as an object. Replacing it wholesale\n * would drop fields like `update.maxMarks`, so a spec that deliberately raised\n * the tween cap above 500 marks would silently swap instead of tween on export.\n */\nexport function settleAnimation(spec: ChartSpec | LayerSpec): ChartSpec | LayerSpec {\n const authored = (spec as { animation?: AnimationSpec }).animation;\n const update =\n typeof authored === 'object' && typeof authored.update === 'object'\n ? { ...authored.update }\n : true;\n return {\n ...(spec as unknown as Record<string, unknown>),\n animation: { enter: false, update },\n } as unknown as ChartSpec | LayerSpec;\n}\n\n/**\n * Mount one live chart offscreen (used as the persistent host we step through\n * every spec). Entrance is disabled so the first spec renders settled; update\n * transitions are enabled so `beginManualUpdate` can tween between specs.\n */\nfunction mountOffscreen(\n spec: ChartSpec | LayerSpec,\n width: number,\n height: number,\n darkMode: 'auto' | 'force' | 'off',\n themeConfig: ThemeConfig | undefined,\n renderer: 'auto' | 'svg' | 'canvas' | undefined,\n): {\n instance: ReturnType<typeof createChart>;\n svg: SVGElement;\n container: HTMLElement;\n} {\n const container = document.createElement('div');\n // Offscreen but laid out, so text measurement (needs a live DOM) works.\n container.style.cssText = `position:absolute;left:-99999px;top:0;width:${width}px;height:${height}px;visibility:hidden;`;\n document.body.appendChild(container);\n\n // enter:false → first spec settled immediately; update:true → tween on\n // beginManualUpdate. responsive:false → no ResizeObserver on a detached node.\n const settledSpec = settleAnimation(spec);\n let instance: ReturnType<typeof createChart>;\n try {\n instance = createChart(container, settledSpec, {\n darkMode,\n theme: themeConfig,\n renderer,\n responsive: false,\n });\n } catch (err) {\n // A spec that fails to compile must not leak the offscreen container.\n container.remove();\n throw err;\n }\n\n const svg = container.querySelector('svg');\n if (!svg) {\n instance.destroy();\n container.remove();\n throw new Error('exportSpecSequence: chart produced no SVG');\n }\n return { instance, svg, container };\n}\n\n/**\n * Render an ordered list of specs to an animated GIF Blob. Holds the first spec,\n * then tweens to each subsequent spec (openchart's update transition, driven\n * frame-by-frame) and holds it — reproducing the on-screen bar grow/shrink.\n *\n * @param specs - Ordered specs, one per keyframe (e.g. one per year).\n * @param options - dpi, fps, dwell, loop, size (required), dark mode, theme, fonts.\n * @returns A Promise resolving to the GIF Blob.\n */\nexport async function exportSpecSequence(\n specs: Array<ChartSpec | LayerSpec>,\n options: SpecSequenceOptions,\n): Promise<Blob> {\n if (specs.length === 0) {\n throw new Error('exportSpecSequence requires at least one spec');\n }\n\n let gifenc: typeof import('gifenc');\n try {\n gifenc = await import('gifenc');\n } catch (err) {\n if (isModuleNotFound(err)) {\n throw new Error(\n \"GIF export requires the optional 'gifenc' peer dependency. Install it: npm install gifenc\",\n );\n }\n throw new Error('GIF export failed to load the gifenc encoder', {\n cause: err,\n });\n }\n const { GIFEncoder, quantize, applyPalette } = gifenc;\n\n const dpi = options.dpi ?? 2;\n const fps = options.fps ?? 25;\n const dwellMs = options.dwellMs ?? 1200;\n const frameDelay = Math.round(1000 / fps);\n const { width, height } = options;\n const darkMode = options.darkMode ?? 'off';\n const themeConfig = options.theme;\n const shouldEmbed = options.embedFonts ?? true;\n const repeat = resolveRepeat(options.loop);\n\n const { instance, svg, container } = mountOffscreen(\n specs[0],\n width,\n height,\n darkMode,\n themeConfig,\n options.renderer,\n );\n\n try {\n const w = getSVGDimensions(svg).width || width;\n const h = getSVGDimensions(svg).height || height;\n const theme = (): ResolvedTheme | undefined => {\n const layout = instance.layout;\n return layout && 'theme' in layout ? (layout.theme as ResolvedTheme) : undefined;\n };\n // The theme background is the fallback: canvas mark mode suppresses the SVG\n // background rect, so there's nothing in the markup to read it from.\n const bgColor =\n options.backgroundColor ?? getSVGBackgroundColor(svg, theme()?.colors.background);\n const fillBackground = (ctx: CanvasRenderingContext2D, cv: HTMLCanvasElement): void => {\n ctx.fillStyle = bgColor;\n ctx.fillRect(0, 0, cv.width, cv.height);\n };\n\n // `beginManualUpdate`/`render` REPLACES the live <svg> node each spec, so the\n // injected theme + @font-face blocks don't carry over. Re-inject them into\n // whatever <svg> is currently live. Fonts embed once per spec (not per frame\n // — embedding fetches font files, which across a tween would be far too slow).\n const prepareLiveSvg = async (): Promise<SVGElement> => {\n const live = container.querySelector('svg');\n if (!live) throw new Error('exportSpecSequence: live SVG vanished mid-sequence');\n injectThemeStyleBlock(live, theme());\n if (shouldEmbed) await embedFonts(live);\n return live;\n };\n\n // Rasterize the given live svg at its current (stepped) state.\n const grab = async (live: SVGElement): Promise<HTMLCanvasElement> => {\n const svgString = ensureSVGDimensions(exportSVG(live), w, h);\n return rasterizeSVGToCanvas(svgString, w, h, dpi, fillBackground);\n };\n\n // frames: each canvas with its hold duration. Hold the first spec, then for\n // each subsequent spec step its tween (frameDelay each) and hold the result.\n const frames: Array<{ canvas: HTMLCanvasElement; delayMs: number }> = [];\n let live = await prepareLiveSvg();\n frames.push({ canvas: await grab(live), delayMs: dwellMs });\n\n for (let i = 1; i < specs.length; i++) {\n // Re-apply the same settle/animation wrapper the offscreen mount used, so\n // update transitions stay enabled for each step.\n const nextSpec = settleAnimation(specs[i]);\n const handle = instance.beginManualUpdate(nextSpec);\n // render() replaced the <svg>; re-inject theme/fonts into the new one.\n live = await prepareLiveSvg();\n\n if (handle) {\n const totalMs = handle.totalMs;\n // Step across the tween at fps; capture each frame. Start one frame in\n // (t=0 is the previous held frame) and go through the final step.\n for (let t = frameDelay; t < totalMs; t += frameDelay) {\n handle.step(t);\n frames.push({ canvas: await grab(live), delayMs: frameDelay });\n }\n // Final settled frame of this spec, held for the dwell.\n handle.step(totalMs);\n handle.cancel();\n }\n frames.push({ canvas: await grab(live), delayMs: dwellMs });\n }\n\n // One global palette from the last frame — a full-color settled frame, and\n // stepper frames share a palette by construction.\n const globalPalette = paletteFromCanvas(frames[frames.length - 1].canvas, quantize);\n\n const gif = GIFEncoder();\n frames.forEach(({ canvas, delayMs }, i) => {\n const index = applyPalette(readCanvasSRGB(canvas), globalPalette);\n gif.writeFrame(index, canvas.width, canvas.height, {\n palette: globalPalette,\n delay: delayMs,\n // `repeat` writes the loop extension; only meaningful on the first frame.\n ...(i === 0 ? { repeat } : {}),\n });\n });\n\n gif.finish();\n return new Blob([gif.bytes() as BlobPart], { type: 'image/gif' });\n } finally {\n instance.destroy();\n container.remove();\n }\n}\n","/**\n * Creates a Web Worker running the force simulation.\n *\n * References the built .js file via `new URL` + `import.meta.url`.\n * The consuming app's bundler resolves the worker path at build time.\n *\n * Note: SimulationManager handles .js/.ts fallback internally. This\n * helper is exported for consumers who want to manage the worker directly.\n */\nexport function createSimulationWorker(): Worker {\n return new Worker(new URL('./simulation-worker.js', import.meta.url), { type: 'module' });\n}\n","/**\n * Graph mount API: the main entry point for vanilla JS graph usage.\n *\n * createGraph() takes a container, GraphSpec, and options, compiles the graph,\n * creates a force simulation, canvas renderer, spatial index, interaction\n * manager, and search manager, then runs an animation loop driven by\n * simulation ticks. Returns a GraphInstance with update/search/zoom/destroy.\n */\n\nimport type {\n CompileOptions,\n DarkMode,\n GraphSpec,\n ThemeConfig,\n TooltipContent,\n} from '@opendata-ai/openchart-core';\nimport type {\n CompiledGraphEdge,\n CompiledGraphNode,\n GraphCompilation,\n} from '@opendata-ai/openchart-engine';\nimport { buildEdgeTooltip, compileGraph } from '@opendata-ai/openchart-engine';\nimport {\n type CameraFlightOptions,\n clampK,\n createCameraFlight,\n createCameraFollow,\n} from './graph/camera';\nimport { GraphCanvasRenderer } from './graph/canvas-renderer';\nimport { ENTRANCE_STAGGER_MAX_NODES, entranceOffsets, entranceOrder } from './graph/entrance';\nimport {\n composeStandingFocus,\n type FocusSnapshot,\n FocusTransition,\n layerHoverFocus,\n} from './graph/focus-transition';\nimport { GraphInteractionManager } from './graph/interaction';\nimport { attachGraphKeyboardNav } from './graph/keyboard';\nimport { createGraphLegend, type GraphLegendController } from './graph/legend';\nimport { createTween, prefersReducedMotion, resolveEase } from './graph/motion';\nimport { AnimationScheduler, type GraphAnimation } from './graph/scheduler';\nimport { GraphSearchManager } from './graph/search';\nimport { seedNodePositions } from './graph/seed';\nimport { SimulationManager } from './graph/simulation';\nimport { SpatialIndex } from './graph/spatial-index';\nimport type { GraphRenderState, PositionedEdge, PositionedNode } from './graph/types';\nimport { diffGraphUpdate } from './graph/update-diff';\nimport type { SimEdge, SimNode } from './graph/worker-protocol';\nimport { ZoomTransform } from './graph/zoom';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { createTooltipManager, type TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A programmatic highlight target. Resolved once at call time into a node id set\n * and rendered through the focus model (eased crossfade). See {@link GraphInstance.highlight}.\n */\nexport type GraphHighlightTarget =\n | { nodeIds: string[] }\n | { category: { field: string; value: string | string[] } }\n | { neighborsOf: string; includeSelf?: boolean };\n\n/** A hovered node or edge, passed to a tooltip formatter. */\nexport interface GraphTooltipItem {\n kind: 'node' | 'edge';\n /** The raw datum (node record, or edge record with source/target). */\n data: Record<string, unknown>;\n}\n\n/**\n * Custom tooltip content builder. Receives the hovered item and the library's\n * default {@link TooltipContent}, returns replacement content.\n *\n * Safety contract:\n * - A returned `TooltipContent` or `string` is escaped by the library (strings\n * are inserted via `textContent`, never `innerHTML`).\n * - A returned `HTMLElement` is trusted verbatim — the host owns sanitization.\n * - `null` suppresses the tooltip for that item.\n */\nexport type GraphTooltipFormatter = (\n item: GraphTooltipItem,\n defaults: TooltipContent,\n) => TooltipContent | string | HTMLElement | null;\n\n/** Built-in legend data (headless mirror of the rendered legend). */\nexport interface GraphLegendData {\n field: string | null;\n nodes: Array<{ label: string; color: string; count?: number; active: boolean }>;\n edges: Array<{ label: string; color: string; count?: number }>;\n}\n\nexport interface GraphMountOptions {\n theme?: ThemeConfig;\n darkMode?: DarkMode;\n /**\n * Sink for advisory spec warnings from the compiler (unknown `seedNode` id,\n * deprecations, and so on). Defaults to `console.warn`. Each distinct message\n * is emitted at most once per instance, so a warning that survives every\n * recompile doesn't spam the console on `update()`.\n */\n onWarn?: (message: string) => void;\n responsive?: boolean;\n /** Show the tryOpenData.ai watermark. Defaults to true. */\n watermark?: boolean;\n /** Show the built-in tooltip; pass an object for a custom formatter. Defaults to true. */\n tooltip?: boolean | { formatter?: GraphTooltipFormatter };\n /**\n * Built-in legend. `true` (default) renders an interactive legend with counts.\n * `false` renders none (set this if you render your own legend). An object\n * toggles interactivity/counts.\n */\n legend?: boolean | { interactive?: boolean; counts?: boolean };\n onNodeClick?: (node: Record<string, unknown>) => void;\n onNodeDoubleClick?: (node: Record<string, unknown>) => void;\n onNodeHover?: (node: Record<string, unknown> | null) => void;\n onEdgeHover?: (edge: Record<string, unknown> | null) => void;\n onSelectionChange?: (nodeIds: string[]) => void;\n /** Fired when the user hovers a legend entry (null on leave). */\n onLegendHover?: (entry: { field: string; value: string } | null) => void;\n /** Fired when legend toggle state changes; `activeValues` is the active category set (empty = all). */\n onLegendToggle?: (activeValues: string[]) => void;\n /** Fired whenever the highlight set changes (programmatic or legend), null when cleared. */\n onHighlightChange?: (nodeIds: string[] | null) => void;\n /**\n * Fit the graph to the viewport on the first tick. Default true. Set false to\n * restore a saved camera (e.g. getCamera() + flyTo) without the initial fit.\n */\n fitOnLoad?: boolean;\n /** Camera change callback, rAF-coalesced (fires at most once per rendered frame). */\n onCameraChange?: (camera: { x: number; y: number; k: number }) => void;\n /** Skip the entrance reveal/flight on mount (spec unchanged; used by wrappers when recreating for a theme/darkMode-only change so the entrance doesn't replay). Warmup still runs. */\n suppressEntrance?: boolean;\n}\n\nexport interface GraphInstance {\n update(spec: GraphSpec): void;\n /** Re-compile encoding/legend/chrome without restarting the simulation. Preserves node positions. */\n updateVisuals(spec: GraphSpec): void;\n search(query: string): void;\n clearSearch(): void;\n /** Fit all nodes into the viewport. Animated by default; `{ duration: 0 }` snaps. */\n zoomToFit(opts?: CameraFlightOptions & { padding?: number }): void;\n /** Fly to a node and zoom in (default scale 2). Tracks the node while it settles. */\n zoomToNode(nodeId: string, opts?: CameraFlightOptions & { scale?: number }): void;\n /** Fly the camera to a graph-space target. */\n flyTo(target: { x: number; y: number; k?: number }, opts?: CameraFlightOptions): void;\n /** Center the camera on a graph-space point (keeps current zoom). */\n centerAt(x: number, y: number, opts?: CameraFlightOptions): void;\n /** Current camera (graph-space center-ish transform components). */\n getCamera(): { x: number; y: number; k: number };\n /** Select a node; `{ fly: true }` also flies to it (default follows interaction.select.flyTo). */\n selectNode(nodeId: string, opts?: { fly?: boolean } & CameraFlightOptions): void;\n getSelectedNodes(): string[];\n /** Node ids currently matching the active search query. */\n getSearchMatches(): string[];\n /**\n * Emphasize a set of nodes; eased via the focus model. Layers over the\n * standing category filter (`setActiveCategories`) rather than replacing it:\n * the effective set is the intersection, or the highlight alone when the two\n * are disjoint. Legend toggle state is untouched.\n */\n highlight(target: GraphHighlightTarget, opts?: { dimOpacity?: number }): void;\n /**\n * Clear the transient highlight, returning to the standing category filter.\n * Does NOT clear the filter — use `setActiveCategories([])` for that.\n */\n clearHighlight(): void;\n /** The effective highlighted node ids, or null when nothing is highlighted. */\n getHighlight(): string[] | null;\n /** Headless snapshot of the legend (node categories + edge categories). */\n getLegend(): GraphLegendData;\n /** Set the active category filter declaratively (replaces legend toggle state). */\n setActiveCategories(values: string[]): void;\n /** Current active category values (empty = all active, no filter). */\n getActiveCategories(): string[];\n resize(): void;\n destroy(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Physics-feel gates (Phase 8)\n// ---------------------------------------------------------------------------\n\n/** Springy drag disables above this node count (warm-sim cost at scale). */\nconst SPRINGY_DRAG_MAX_NODES = 5000;\n/** Cursor-repulsion disables above this node count (mirrors the glow gate). */\nconst CURSOR_FORCE_MAX_NODES = 2000;\n/** Cursor pointer-feed throttle (~30Hz) so we don't post on every mousemove. */\nconst CURSOR_POINTER_THROTTLE_MS = 33;\n\n/** Post-flight camera follow stops once the sim alpha settles below this. */\nconst FOLLOW_SETTLE_ALPHA = 0.05;\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a graph instance from a spec and mount it into a container.\n *\n * @param container - The DOM element to render into.\n * @param spec - The graph spec.\n * @param options - Mount options.\n * @returns A GraphInstance with update/search/zoom/destroy methods.\n */\nexport function createGraph(\n container: HTMLElement,\n spec: GraphSpec,\n options?: GraphMountOptions,\n): GraphInstance {\n let currentSpec = spec;\n let compilation: GraphCompilation;\n let destroyed = false;\n\n // DOM elements\n let wrapper: HTMLElement | null = null;\n let canvas: HTMLCanvasElement | null = null;\n let chromeEl: HTMLElement | null = null;\n let legendEl: HTMLElement | null = null;\n let legendController: GraphLegendController | null = null;\n\n // Subsystems\n let renderer: GraphCanvasRenderer | null = null;\n let simulation: SimulationManager | null = null;\n const spatialIndex = new SpatialIndex();\n let interactionManager: GraphInteractionManager | null = null;\n const searchManager = new GraphSearchManager();\n let tooltipManager: TooltipManager | null = null;\n let cleanupKeyboard: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n\n // State\n let positionedNodes: PositionedNode[] = [];\n let positionedEdges: PositionedEdge[] = [];\n let adjacencyMap = new Map<string, Set<string>>();\n let nodeDataMap = new Map<string, Record<string, unknown>>();\n let edgeDataMap = new Map<string, Record<string, unknown>>();\n let hoveredNodeId: string | null = null;\n let hoveredEdgeId: string | null = null;\n let selectedNodeIds = new Set<string>();\n let animFrameId: number | null = null;\n let needsRender = false;\n let isGesturing = false;\n // Continuous-animation scheduler. Arms the first frame via scheduleRender on\n // the idle→active transition; the render loop ticks it each frame and re-arms\n // only while it stays active, so the base loop stays strictly dirty-flag.\n const scheduler = new AnimationScheduler(() => scheduleRender());\n let gestureTimeout: ReturnType<typeof setTimeout> | null = null;\n let lastEdgeHitTime = 0;\n // Cursor-repulsion pointer-feed throttle timestamp (Phase 8).\n let lastPointerFeedTime = 0;\n // Camera flight state.\n let activeFlight: GraphAnimation | null = null;\n // Post-flight follow for provider-form flights (tracks a still-settling node).\n let activeFollow: GraphAnimation | null = null;\n // Latest simulation alpha, fed by onTick; the follow stops below the threshold.\n let lastAlpha = 1;\n let cameraChangePending = false;\n\n // Focus / highlight state (Phase 5). Two independent layers compose into one\n // effective set:\n // - `activeCategories` is the STICKY category filter (empty = no filter),\n // written only by setActiveCategories / toggleLegendCategory /\n // applyInitialHighlight. Legend `active` flags track this and only this.\n // - `transientHighlight` is the TRANSIENT emphasis owned solely by\n // highlight() / clearHighlight(), along with `highlightDimOpacity`.\n // `transientTarget` keeps the unresolved target so a category-form\n // highlight can be re-resolved against new data on update().\n // `highlightSet` is the cached composition of the two (null = nothing\n // highlighted), assigned only by recomputeHighlight(). It is the cache\n // standingSnapshot() reads, so armFocus stays allocation-free per frame.\n let highlightSet: Set<string> | null = null;\n let highlightDimOpacity: number | null = null;\n // Ids exempt from highlight/filter dimming (the spec's `seedNode`). Kept OUT\n // of `highlightSet` on purpose: composeStandingFocus expands the core set to\n // `core ∪ neighbors(core)`, and a seed is by construction a hub, so unioning\n // it would light most of the graph and defeat the category filter. Keeping it\n // out also means getHighlight()/onHighlightChange never leak the seed id.\n // Re-derived wherever `compilation` is reassigned.\n let seedIds = new Set<string>();\n let activeCategories = new Set<string>();\n let transientHighlight: Set<string> | null = null;\n let transientTarget: GraphHighlightTarget | null = null;\n // Compiler warnings already emitted by this instance (see warnOnce).\n const seenWarnings = new Set<string>();\n // Node id → its legend-field category value, for category-based highlight.\n let nodeCategory = new Map<string, string>();\n // The live focus crossfade, driving eased dimming. Rebuilt on first render.\n let focusTransition: FocusTransition | null = null;\n // Scheduler animation that keeps frames dirty while the focus crossfade runs.\n let focusAnim: GraphAnimation | null = null;\n // Hovered-node radius tween (1 → 1.15), null when settled at 1.\n let hoverRadiusTween: { nodeId: string; scale: number } | null = null;\n\n // Entrance choreography state (Phase 6). `entranceProgress` is a mount-level\n // 0→1 value read by buildRenderState; < 1 means the reveal is mid-flight.\n // `entranceActive` gates the render-state `entrance` field. `entranceFitInFlight`\n // tracks the entrance camera flight so a resize can cancel just it (keeping the\n // reveal). `entranceReveal` is the scheduler tween driving `entranceProgress`.\n let entranceProgress = 1;\n let entranceActive = false;\n let entranceStagger = false;\n // Pop choreography inputs (staggered entrances only): hash-scattered stagger\n // rank and per-node convergence drift vectors, built once at entrance start.\n let entranceOrderMap: Map<string, number> | null = null;\n let entranceOffsetMap: Map<string, { x: number; y: number }> | null = null;\n let entranceFitInFlight = false;\n let entranceReveal: GraphAnimation | null = null;\n // Mount-level opt-out (Phase 9): when set, the FIRST entrance takes the instant\n // -fit branch (no 0.92 pullback, no reveal tween, no camera flight) even under\n // normal motion. Wrappers set this when recreating for a theme/darkMode-only\n // change so the entrance doesn't replay. Warmup still runs. One-shot.\n let suppressEntranceOnce = options?.suppressEntrance ?? false;\n\n // Data-update transition state (Phase 7). `enterAlphaMap` fades newly-added\n // nodes in over update.duration (null when no enter-fade is live). `exiting`\n // holds ghost marks (removed nodes/edges) fading out over exit.duration; both\n // are read by buildRenderState and drawn by the canvas renderer.\n let enterAlphaMap: Map<string, number> | null = null;\n let exitingGhosts: { nodes: PositionedNode[]; edges: PositionedEdge[]; alpha: number } | null =\n null;\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function markGesture(): void {\n isGesturing = true;\n if (gestureTimeout !== null) clearTimeout(gestureTimeout);\n gestureTimeout = setTimeout(() => {\n isGesturing = false;\n gestureTimeout = null;\n needsRender = true;\n scheduleRender();\n }, 150);\n }\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n /**\n * Compiler warning sink. `update()` recompiles, so a warning about a standing\n * spec condition (an unknown `seedNode` id, say) would otherwise repeat on\n * every data change. Emit each distinct message once per instance.\n */\n function warnOnce(message: string): void {\n if (seenWarnings.has(message)) return;\n seenWarnings.add(message);\n if (options?.onWarn) options.onWarn(message);\n else console.warn(message);\n }\n\n function compile(): GraphCompilation {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n onWarn: warnOnce,\n };\n\n return compileGraph(currentSpec, compileOpts);\n }\n\n function buildDataMaps(): void {\n nodeDataMap = new Map(compilation.nodes.map((n) => [n.id, n.data ?? {}]));\n edgeDataMap = new Map(compilation.edges.map((e) => [`${e.source}->${e.target}`, e.data ?? {}]));\n\n // Node → legend-field category value, for category hover/highlight. Empty\n // when the graph has no categorical color field.\n nodeCategory = new Map();\n const field = compilation.legendField;\n if (field) {\n for (const n of compilation.nodes) {\n const v = n.data?.[field];\n if (v != null) nodeCategory.set(n.id, String(v));\n }\n }\n }\n\n function buildAdjacencyMap(edges: CompiledGraphEdge[]): Map<string, Set<string>> {\n const map = new Map<string, Set<string>>();\n for (const edge of edges) {\n if (!map.has(edge.source)) map.set(edge.source, new Set());\n if (!map.has(edge.target)) map.set(edge.target, new Set());\n map.get(edge.source)!.add(edge.target);\n map.get(edge.target)!.add(edge.source);\n }\n return map;\n }\n\n function toSimNodes(nodes: CompiledGraphNode[]): SimNode[] {\n return nodes.map((n) => ({\n id: n.id,\n radius: n.radius,\n community: n.community,\n }));\n }\n\n function toSimEdges(edges: CompiledGraphEdge[]): SimEdge[] {\n return edges.map((e) => ({\n source: e.source,\n target: e.target,\n }));\n }\n\n /**\n * Look up a node's data from the compilation by id.\n * Falls back to an empty object if not found.\n */\n function nodeDataById(nodeId: string): Record<string, unknown> {\n return nodeDataMap.get(nodeId) ?? {};\n }\n\n /**\n * Point-to-line-segment distance for edge hit testing.\n * Returns the shortest distance from point (px, py) to the segment (ax, ay)-(bx, by).\n */\n function pointToSegmentDist(\n px: number,\n py: number,\n ax: number,\n ay: number,\n bx: number,\n by: number,\n ): number {\n const dx = bx - ax;\n const dy = by - ay;\n const lenSq = dx * dx + dy * dy;\n if (lenSq === 0) return Math.hypot(px - ax, py - ay);\n const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));\n return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));\n }\n\n /**\n * Find the edge closest to a graph-space point, within a threshold.\n * Returns an edge key \"source->target\" or null.\n */\n function hitTestEdge(graphX: number, graphY: number, threshold: number): string | null {\n let bestDist = threshold;\n let bestEdgeId: string | null = null;\n\n for (const edge of positionedEdges) {\n const dist = pointToSegmentDist(\n graphX,\n graphY,\n edge.sourceX,\n edge.sourceY,\n edge.targetX,\n edge.targetY,\n );\n if (dist < bestDist) {\n bestDist = dist;\n bestEdgeId = `${edge.source}->${edge.target}`;\n }\n }\n\n return bestEdgeId;\n }\n\n /**\n * Look up edge data by edge id (\"source->target\").\n */\n function edgeDataById(edgeId: string): Record<string, unknown> | null {\n return edgeDataMap.get(edgeId) ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // DOM creation\n // ---------------------------------------------------------------------------\n\n function createDOM(): void {\n const { width, height } = getContainerDimensions();\n const isDark = resolveDarkMode(options?.darkMode);\n\n // Wrapper\n wrapper = document.createElement('div');\n wrapper.className = isDark ? 'oc-graph-wrapper oc-dark' : 'oc-graph-wrapper';\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Apply theme colors as CSS custom properties so chrome HTML picks them up.\n // Without this, consumer-supplied theme.colors.text only affects canvas-drawn\n // labels but not the HTML title/subtitle which read from --oc-text.\n const resolvedTheme = compilation.theme;\n if (resolvedTheme) {\n const s = wrapper.style;\n s.setProperty('--oc-bg', resolvedTheme.colors.background);\n s.setProperty('--oc-text', resolvedTheme.colors.text);\n s.setProperty('--oc-text-secondary', resolvedTheme.colors.axis ?? resolvedTheme.colors.text);\n s.setProperty('--oc-font-family', resolvedTheme.fonts.family);\n s.fontFamily = resolvedTheme.fonts.family;\n }\n\n // Chrome (title, subtitle)\n chromeEl = document.createElement('div');\n chromeEl.className = 'oc-graph-chrome';\n renderChrome();\n wrapper.appendChild(chromeEl);\n\n // Canvas\n canvas = document.createElement('canvas');\n canvas.className = 'oc-graph-canvas';\n canvas.setAttribute('role', 'img');\n if (compilation.a11y?.altText) {\n canvas.setAttribute('aria-label', compilation.a11y.altText);\n }\n wrapper.appendChild(canvas);\n\n // Legend\n if (legendSetting() !== false) {\n legendEl = document.createElement('div');\n legendEl.className = 'oc-graph-legend';\n renderLegend();\n wrapper.appendChild(legendEl);\n }\n\n container.appendChild(wrapper);\n syncChromeInset();\n\n // Canvas uses the full container height; chrome overlays on top\n const canvasHeight = Math.max(height, 200);\n renderer = new GraphCanvasRenderer(canvas);\n renderer.resize(width, canvasHeight);\n }\n\n function renderChrome(): void {\n if (!chromeEl) return;\n let html = '';\n\n if (compilation.chrome.title) {\n html += `<h2 class=\"oc-title\">${escapeHtml(compilation.chrome.title.text)}</h2>`;\n }\n if (compilation.chrome.subtitle) {\n html += `<p class=\"oc-subtitle\">${escapeHtml(compilation.chrome.subtitle.text)}</p>`;\n }\n\n chromeEl.innerHTML = html;\n\n // Hide chrome if empty\n if (!html) {\n chromeEl.style.display = 'none';\n } else {\n chromeEl.style.display = '';\n }\n }\n\n /**\n * The effective legend setting. `GraphSpec.legend` and the mount option say\n * the same thing at two levels; the mount option wins when both are set, so a\n * host can override a spec it doesn't own.\n */\n function legendSetting(): GraphMountOptions['legend'] {\n return options?.legend ?? currentSpec.legend;\n }\n\n /** Resolve legend interactive/counts flags from the legend setting. */\n function legendConfig(): { interactive: boolean; counts: boolean } {\n const l = legendSetting();\n if (l && typeof l === 'object') {\n return { interactive: l.interactive ?? true, counts: l.counts ?? true };\n }\n return { interactive: true, counts: true };\n }\n\n /** Build the legend view data from the compilation + current toggle state. */\n function legendViewData(): { nodes: GraphLegendData['nodes']; edges: GraphLegendData['edges'] } {\n return { nodes: getLegend().nodes, edges: getLegend().edges };\n }\n\n /** Create or refresh the interactive legend from current state. */\n function renderLegend(): void {\n if (!legendEl) return;\n const cfg = legendConfig();\n if (!legendController) {\n legendController = createGraphLegend(legendEl, legendViewData(), {\n interactive: cfg.interactive,\n counts: cfg.counts,\n onToggle: (value) => toggleLegendCategory(value),\n onHover: (value) => {\n const field = compilation.legendField;\n options?.onLegendHover?.(value !== null && field ? { field, value } : null);\n },\n });\n } else {\n legendController.update(legendViewData());\n }\n }\n\n /** Re-render the legend to reflect the current active-category state. */\n function syncLegendActiveState(): void {\n if (legendController) legendController.update(legendViewData());\n syncChromeInset();\n }\n\n /**\n * Keep the chrome block out of the legend's column: the title/subtitle wrap\n * before they reach the legend box instead of running underneath it. No-op\n * when there's no legend (or it has no measurable width, e.g. in happy-dom).\n */\n function syncChromeInset(): void {\n if (!chromeEl) return;\n const legendW = legendEl?.offsetWidth ?? 0;\n chromeEl.style.right = legendW > 0 ? `${legendW + 24}px` : '';\n }\n\n /**\n * Height of the chrome overlay band (title + subtitle) the camera fit should\n * reserve, so nodes never settle underneath the text. 0 when chrome is empty\n * or unmeasurable (happy-dom).\n */\n function chromeInsetTop(): number {\n if (!chromeEl || chromeEl.style.display === 'none') return 0;\n return chromeEl.offsetHeight;\n }\n\n // ---------------------------------------------------------------------------\n // Physics-feel gates (Phase 8)\n // ---------------------------------------------------------------------------\n\n /**\n * Springy drag on: config opts in AND the graph is small enough that holding\n * the sim warm during a drag is cheap. Off/above threshold → legacy pin/unpin.\n */\n function springyDragEnabled(): boolean {\n return (\n compilation.interaction.springyDrag && compilation.nodes.length <= SPRINGY_DRAG_MAX_NODES\n );\n }\n\n /**\n * Cursor repulsion on: config opts in, the graph is small enough (mirrors the\n * glow gate), and reduced motion is off (ambient pointer-driven motion is\n * exactly what reduced-motion suppresses).\n */\n function cursorForceEnabled(): boolean {\n return (\n compilation.interaction.cursorRepulsion !== null &&\n compilation.nodes.length <= CURSOR_FORCE_MAX_NODES &&\n !prefersReducedMotion()\n );\n }\n\n // ---------------------------------------------------------------------------\n // Simulation and animation\n // ---------------------------------------------------------------------------\n\n /**\n * Create the simulation for the current compilation.\n *\n * On the initial mount (no `opts`), every node is seeded deterministically and\n * a fresh warmup runs. On a data update, `opts` supplies pre-known positions\n * (survivors keep their prior x/y; enterers get spawn positions), suppresses\n * the center force (it snaps every node by the full centroid error on tick 1,\n * the global-jump artifact this phase eliminates), and overrides the initial\n * alpha to the local-reheat impulse. Update sims skip warmup — survivors are\n * already settled, so a headless warmup would just churn them.\n */\n function initSimulation(opts?: {\n positions?: Map<string, { x: number; y: number }>;\n suppressCenter?: boolean;\n initialAlpha?: number;\n skipWarmup?: boolean;\n skipEntrance?: boolean;\n }): void {\n const simNodes = toSimNodes(compilation.nodes);\n const simEdges = toSimEdges(compilation.edges);\n const config = compilation.simulationConfig;\n\n if (opts?.positions) {\n // Update path: place each node at its known (survivor or spawn) position.\n // Any node without a supplied position (shouldn't happen) falls back to the\n // seeded disc so it isn't stuck at the origin.\n seedNodePositions(simNodes, config.seed ?? 0);\n for (const n of simNodes) {\n const p = opts.positions.get(n.id);\n if (p) {\n n.x = p.x;\n n.y = p.y;\n }\n }\n } else {\n // Initial mount: seed deterministic positions BEFORE the simulation starts,\n // so the settled layout is reproducible for a given (spec, seed).\n seedNodePositions(simNodes, config.seed ?? 0);\n }\n\n simulation = SimulationManager.create(simNodes, simEdges, {\n chargeStrength: config.chargeStrength,\n linkDistance: config.linkDistance,\n clustering: config.clustering,\n alphaDecay: config.alphaDecay,\n velocityDecay: config.velocityDecay,\n collisionRadius: config.collisionRadius,\n collisionPadding: config.collisionPadding,\n linkStrength: config.linkStrength,\n // Update sims suppress the (non-alpha-scaled) center force to avoid a\n // global jump on tick 1; the alpha-scaled forceX/forceY gravity still\n // holds the layout centered.\n centerForce: opts?.suppressCenter ? false : config.centerForce,\n warmupTicks: opts?.skipWarmup ? 0 : config.warmupTicks,\n warmupBudgetMs: config.warmupBudgetMs,\n initialAlpha: opts?.initialAlpha ?? config.initialAlpha,\n // Cursor force radius/strength (null when disabled or gated off by node\n // count). The mount only feeds pointer positions when the same gate holds.\n cursorRepulsion: cursorForceEnabled() ? compilation.interaction.cursorRepulsion : null,\n });\n\n // A fresh sim starts hot; don't let a settled previous sim's alpha linger\n // (it would end a post-flight camera follow before the first tick lands).\n lastAlpha = opts?.initialAlpha ?? 1;\n\n let initialSettleDone = false;\n // Update sims keep the current camera and don't run the entrance reveal, so\n // pre-mark the fit as done — the first update tick just streams positions.\n let initialFitDone = opts?.skipEntrance ?? false;\n\n simulation.onTick((positions, alpha) => {\n if (destroyed) return;\n lastAlpha = alpha;\n\n // Build position lookup\n const posMap = new Map<string, { x: number; y: number }>();\n for (const p of positions) {\n posMap.set(p.id, { x: p.x, y: p.y });\n }\n\n // Build positioned nodes\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = posMap.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n\n // Build positioned edges\n positionedEdges = compilation.edges.map((edge) => {\n const src = posMap.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = posMap.get(edge.target) ?? { x: 0, y: 0 };\n return {\n ...edge,\n sourceX: src.x,\n sourceY: src.y,\n targetX: tgt.x,\n targetY: tgt.y,\n };\n });\n\n // Rebuild spatial index\n spatialIndex.rebuild(positionedNodes);\n\n // Fit + entrance choreography on the FIRST post-warmup tick (the sim only\n // starts streaming ticks once warmup, if any, has completed). After that,\n // let the user interact freely while the simulation keeps settling.\n if (\n !initialFitDone &&\n positionedNodes.length > 0 &&\n interactionManager &&\n options?.fitOnLoad !== false\n ) {\n initialFitDone = true;\n startEntrance();\n } else if (!initialFitDone && options?.fitOnLoad === false) {\n // Skip the fit but mark it done so a saved camera (getCamera/flyTo) sticks.\n initialFitDone = true;\n }\n\n needsRender = true;\n scheduleRender();\n });\n\n simulation.onSettled(() => {\n if (initialSettleDone) return;\n initialSettleDone = true;\n });\n }\n\n /**\n * Compute the initial fit transform. Bypasses fitBounds' spread inflation when\n * warmup ran (warmed bounds are near-final; inflating them fits too small).\n */\n function computeInitialFit(): ZoomTransform {\n const { width: cw, height: ch } = getCanvasDimensions();\n const warmed = (compilation.simulationConfig.warmupTicks ?? 0) > 0;\n const { transform } = ZoomTransform.fitBounds(positionedNodes, cw, ch, undefined, {\n spread: !warmed,\n insetTop: chromeInsetTop(),\n });\n return transform;\n }\n\n /**\n * Fit + run the entrance reveal on the first post-warmup tick. Under an enabled\n * `enter` phase and normal motion, the camera starts pulled back to 0.92× the\n * fit and (optionally) flies in while a mount-level `entranceProgress` tween\n * ramps node/edge/label reveal. Under reduced motion or `animation: false`,\n * it's an instant fit (warmup still ran — it reduces motion, it isn't motion).\n */\n function startEntrance(): void {\n if (!interactionManager) return;\n const fit = computeInitialFit();\n const enter = compilation.animation?.enter ?? null;\n // One-shot mount-level suppression (theme/darkMode remount): take the same\n // instant-fit branch as reduced motion so the entrance doesn't replay.\n const suppressed = suppressEntranceOnce;\n suppressEntranceOnce = false;\n if (suppressed || !enter || prefersReducedMotion()) {\n interactionManager.setTransform(fit);\n cameraChangePending = true;\n entranceActive = false;\n entranceProgress = 1;\n return;\n }\n\n // Start pulled back so the reveal has somewhere to fly in from. 0.7 gives a\n // clearly readable pull-in on load (0.85 was too subtle to register as motion).\n const { width: cw, height: ch } = getCanvasDimensions();\n const pulledBack = fit.zoomAt(fit.k * 0.7, cw / 2, ch / 2);\n interactionManager.setTransform(pulledBack);\n cameraChangePending = true;\n\n entranceActive = true;\n entranceProgress = 0;\n // Stagger only when the spec asks for it AND the graph is small enough that\n // per-node start times still batch. Above the cap: a single global fade.\n entranceStagger = enter.stagger && positionedNodes.length <= ENTRANCE_STAGGER_MAX_NODES;\n // Pop choreography inputs, computed once against the warmed (near-final)\n // positions: hash-scattered stagger order + per-node convergence drift.\n if (entranceStagger) {\n entranceOrderMap = entranceOrder(positionedNodes);\n entranceOffsetMap = entranceOffsets(positionedNodes);\n } else {\n entranceOrderMap = null;\n entranceOffsetMap = null;\n }\n\n // Optional camera flight from the pulled-back framing to the true fit.\n if (enter.cameraFit) {\n entranceFitInFlight = true;\n flyCamera(fit, { duration: enter.duration + 100 }, () => {\n entranceFitInFlight = false;\n });\n }\n\n // Reveal tween drives entranceProgress 0→1; ends the entrance on completion.\n const ease = resolveEase(enter.ease);\n entranceReveal = createTween({\n duration: enter.duration,\n ease,\n apply: (t) => {\n entranceProgress = t;\n needsRender = true;\n },\n onDone: () => {\n entranceProgress = 1;\n entranceActive = false;\n entranceReveal = null;\n needsRender = true;\n },\n });\n scheduler.add(entranceReveal);\n }\n\n function getCanvasDimensions(): { width: number; height: number } {\n if (!canvas) return { width: 600, height: 400 };\n const rect = canvas.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n function scheduleRender(): void {\n if (animFrameId !== null || destroyed) return;\n animFrameId = requestAnimationFrame(renderFrame);\n }\n\n // -------------------------------------------------------------------------\n // Focus model (Phase 5): highlight ∩ search + hover neighborhood, eased.\n // -------------------------------------------------------------------------\n\n /** Neighborhood of the hovered node under the resolved hover mode. */\n function hoverConnectedSet(nodeId: string | null): Set<string> | null {\n if (nodeId === null) return null;\n if (compilation.interaction.hoverMode === 'category') {\n const cat = nodeCategory.get(nodeId);\n const set = new Set<string>([nodeId]);\n if (cat !== undefined) {\n for (const [id, c] of nodeCategory) if (c === cat) set.add(id);\n }\n return set;\n }\n // 'neighbors' (default): the node plus its adjacency.\n const set = new Set<string>([nodeId]);\n const neighbors = adjacencyMap.get(nodeId);\n if (neighbors) for (const nid of neighbors) set.add(nid);\n return set;\n }\n\n /** The standing (non-hover) focus from highlight + search + selection. */\n function standingSnapshot(): FocusSnapshot {\n return composeStandingFocus(\n highlightSet,\n searchManager.getMatches(),\n selectedNodeIds,\n adjacencyMap,\n );\n }\n\n /** The full target snapshot = standing state with the hover layer on top. */\n function targetSnapshot(): FocusSnapshot {\n return layerHoverFocus(standingSnapshot(), hoveredNodeId, hoverConnectedSet(hoveredNodeId));\n }\n\n /**\n * Point the focus crossfade at the current target and arm a scheduler\n * animation that keeps frames dirty until it settles. Snaps instantly under\n * reduced motion or when hover animation is disabled.\n */\n function armFocus(now: number): void {\n const target = targetSnapshot();\n const hoverCfg = compilation.animation?.hover ?? null;\n const duration = hoverCfg && !prefersReducedMotion() ? hoverCfg.duration : 0;\n const ease = resolveEase(hoverCfg?.ease ?? 'smooth');\n\n if (!focusTransition) {\n focusTransition = new FocusTransition(target, duration, ease, now);\n return;\n }\n focusTransition.retarget(target, now);\n\n // Keep the crossfade running via a scheduler animation until settled.\n if (focusAnim) scheduler.remove(focusAnim);\n focusAnim = {\n tick: (t: number): boolean => {\n const running = focusTransition !== null && !focusTransition.isSettled(t);\n if (!running) focusAnim = null;\n return running;\n },\n finish: (): void => {\n focusAnim = null;\n },\n cancel: (): void => {\n focusAnim = null;\n },\n };\n scheduler.add(focusAnim);\n }\n\n /** Recompute the highlight set and re-arm the focus crossfade. */\n function refreshFocus(): void {\n armFocus(performance.now());\n needsRender = true;\n scheduleRender();\n }\n\n /** Resolve a highlight target into a concrete node id set. */\n function resolveHighlightTarget(target: GraphHighlightTarget): Set<string> {\n if ('nodeIds' in target) return new Set(target.nodeIds);\n if ('neighborsOf' in target) {\n const set = new Set<string>();\n if (target.includeSelf !== false) set.add(target.neighborsOf);\n const neighbors = adjacencyMap.get(target.neighborsOf);\n if (neighbors) for (const nid of neighbors) set.add(nid);\n return set;\n }\n // Category form: match nodes whose `field` value is in `value`.\n const values = new Set(\n Array.isArray(target.category.value) ? target.category.value : [target.category.value],\n );\n const field = target.category.field;\n const set = new Set<string>();\n for (const n of compilation.nodes) {\n const v = n.data?.[field];\n if (v != null && values.has(String(v))) set.add(n.id);\n }\n return set;\n }\n\n /** Node ids for the active legend categories (empty categories = no filter). */\n function categoryHighlightSet(): Set<string> | null {\n if (activeCategories.size === 0) return null;\n const set = new Set<string>();\n for (const [id, cat] of nodeCategory) if (activeCategories.has(cat)) set.add(id);\n return set;\n }\n\n /**\n * Recompose the effective highlight from the sticky category filter and the\n * transient highlight(). THE ONLY assigner of `highlightSet`.\n *\n * Composition rule: both present → their intersection, except when the\n * intersection is empty, in which case the transient wins (a host legend\n * hovering an out-of-filter row previews that category — the built-in legend\n * only fires `onLegendHover`, so this path is host-driven). Exactly one\n * present → that one. Neither → null. Same shape as the highlight ∩ search\n * rule in composeStandingFocus.\n *\n * An EMPTY transient is not a transient: `resolveHighlightTarget` returns an\n * empty set for `{ nodeIds: [] }` or a category matching zero nodes, and\n * treating that as a layer would silently wipe the standing filter's dimming.\n */\n function recomputeHighlight(): void {\n const filter = categoryHighlightSet();\n const transient =\n transientHighlight !== null && transientHighlight.size > 0 ? transientHighlight : null;\n if (filter !== null && transient !== null) {\n const inter = new Set<string>();\n for (const id of transient) if (filter.has(id)) inter.add(id);\n highlightSet = inter.size > 0 ? inter : transient;\n return;\n }\n highlightSet = filter ?? transient;\n }\n\n /**\n * Re-resolve the standing transient target against the current compilation\n * and drop ids that no longer exist. Category-form targets track data changes\n * this way; an unpruned id set would otherwise resurrect deleted nodes.\n */\n function refreshTransientHighlight(): void {\n if (transientTarget === null) {\n transientHighlight = null;\n return;\n }\n const nextIds = new Set(compilation.nodes.map((n) => n.id));\n const resolved = new Set(\n [...resolveHighlightTarget(transientTarget)].filter((id) => nextIds.has(id)),\n );\n transientHighlight = resolved.size > 0 ? resolved : null;\n }\n\n /** The custom `dimOpacity` applies only while a transient highlight is up. */\n function effectiveDimOpacity(): number {\n const custom = transientHighlight !== null ? highlightDimOpacity : null;\n return custom ?? compilation.interaction.dimOpacity;\n }\n\n /** Fire onHighlightChange with the current highlight set. */\n function emitHighlightChange(): void {\n options?.onHighlightChange?.(highlightSet ? [...highlightSet] : null);\n }\n\n /** Apply the resolved initialHighlight from the compilation, if any. */\n function applyInitialHighlight(): void {\n const init = compilation.initialHighlight;\n if (!init) return;\n activeCategories = new Set(init.values);\n recomputeHighlight();\n }\n\n /** Start (or cancel) the hovered node's 1 → 1.15 radius tween. */\n function startHoverRadiusTween(nodeId: string | null): void {\n const hoverCfg = compilation.animation?.hover ?? null;\n // No hover animation, reduced motion, or hover-off → snap (no tween).\n if (nodeId === null || !hoverCfg || prefersReducedMotion()) {\n hoverRadiusTween = nodeId ? { nodeId, scale: 1.15 } : null;\n return;\n }\n hoverRadiusTween = { nodeId, scale: 1 };\n const ease = resolveEase(hoverCfg.ease);\n const tween = createTween({\n duration: hoverCfg.duration,\n ease,\n apply: (t) => {\n // Guard against a newer hover having replaced the target mid-tween.\n if (hoverRadiusTween?.nodeId === nodeId) hoverRadiusTween.scale = 1 + 0.15 * t;\n needsRender = true;\n },\n onDone: () => {\n if (hoverRadiusTween?.nodeId === nodeId) hoverRadiusTween.scale = 1.15;\n },\n });\n scheduler.add(tween);\n }\n\n /** Resolve tooltip content for a node, applying a custom formatter if set. */\n function showNodeTooltip(nodeId: string): void {\n if (!tooltipManager || !interactionManager) return;\n const defaults = compilation.tooltipDescriptors.get(nodeId);\n if (!defaults) return;\n const node = positionedNodes.find((n) => n.id === nodeId);\n if (!node) return;\n const screen = interactionManager.getTransform().graphToScreen(node.x, node.y);\n\n const formatter = tooltipFormatter();\n if (!formatter) {\n tooltipManager.show(defaults, screen.x, screen.y);\n return;\n }\n const result = formatter({ kind: 'node', data: nodeDataById(nodeId) }, defaults);\n applyFormatterResult(result, screen.x, screen.y);\n }\n\n /** Resolve tooltip content for an edge (lazy default), applying a formatter. */\n function showEdgeTooltip(\n edgeId: string,\n data: Record<string, unknown>,\n screenX: number,\n screenY: number,\n ): void {\n if (!tooltipManager) return;\n const edge = compilation.edges.find((e) => `${e.source}->${e.target}` === edgeId);\n const defaults = edge ? buildEdgeTooltip(edge) : { title: edgeId, fields: [] };\n\n const formatter = tooltipFormatter();\n if (!formatter) {\n tooltipManager.show(defaults, screenX, screenY);\n return;\n }\n const result = formatter({ kind: 'edge', data }, defaults);\n applyFormatterResult(result, screenX, screenY);\n }\n\n /** The configured tooltip formatter, or null when tooltips are plain. */\n function tooltipFormatter(): GraphTooltipFormatter | null {\n const t = options?.tooltip;\n return t && typeof t === 'object' ? (t.formatter ?? null) : null;\n }\n\n /** Route a formatter's return value to the tooltip manager (or hide on null). */\n function applyFormatterResult(\n result: TooltipContent | string | HTMLElement | null,\n x: number,\n y: number,\n ): void {\n if (!tooltipManager) return;\n if (result === null) {\n tooltipManager.hide();\n } else if (typeof result === 'string') {\n tooltipManager.show({ text: result }, x, y);\n } else if (result instanceof HTMLElement) {\n tooltipManager.show({ element: result }, x, y);\n } else {\n tooltipManager.show(result, x, y);\n }\n }\n\n /** Build the immutable per-frame render state from current mount state. */\n function buildRenderState(now: number): GraphRenderState {\n const transform = interactionManager!.getTransform();\n\n // Ensure a focus transition exists (first render), pointed at current state.\n if (!focusTransition) armFocus(now);\n const ft = focusTransition as FocusTransition;\n const t = ft.progress(now);\n const focus = t < 1 ? { t, prev: ft.prev, next: ft.next } : undefined;\n // When settled we still pass the resting snapshot as `next` for the fast path.\n const settledNext = ft.next;\n\n const hoverRadiusScale = hoverRadiusTween\n ? new Map([[hoverRadiusTween.nodeId, hoverRadiusTween.scale]])\n : undefined;\n\n return {\n nodes: positionedNodes,\n edges: positionedEdges,\n transform: { x: transform.x, y: transform.y, k: transform.k },\n hoveredNodeId,\n hoveredEdgeId,\n selectedNodeIds,\n adjacencyMap,\n theme: compilation.theme,\n searchMatches: searchManager.getMatches(),\n exemptIds: seedIds,\n isGesturing,\n watermark: compilation.watermark,\n focus: focus ?? { t: 1, prev: settledNext, next: settledNext },\n hoverRadiusScale,\n dimOpacity: effectiveDimOpacity(),\n entrance:\n entranceActive && entranceProgress < 1\n ? {\n t: entranceProgress,\n stagger: entranceStagger,\n order: entranceOrderMap ?? undefined,\n offsets: entranceOffsetMap ?? undefined,\n }\n : undefined,\n enterAlpha: enterAlphaMap ?? undefined,\n exiting: exitingGhosts ?? undefined,\n };\n }\n\n function renderFrame(now: number): void {\n animFrameId = null;\n if (destroyed || !renderer || !interactionManager) return;\n\n // Tick animations first; a running animation dirties the frame. Animations\n // mutate mount state only — they never render or arm rAF themselves.\n if (scheduler.tick(now)) needsRender = true;\n\n if (needsRender) {\n needsRender = false;\n renderer.render(buildRenderState(now));\n }\n\n // Re-arm only while animations are active; otherwise the loop goes idle.\n if (scheduler.active) scheduleRender();\n\n // Emit a coalesced camera-change after the frame renders (at most once/frame).\n if (cameraChangePending) {\n cameraChangePending = false;\n const t = interactionManager.getTransform();\n options?.onCameraChange?.({ x: t.x, y: t.y, k: t.k });\n }\n }\n\n /**\n * Fly the camera to a target transform (or a provider that tracks a moving\n * target). Cancels any prior flight. Snaps instead of flying when animation is\n * disabled, reduced motion is active, or duration is 0.\n */\n function flyCamera(\n to: ZoomTransform | (() => ZoomTransform),\n opts?: CameraFlightOptions,\n onDone?: () => void,\n ): void {\n if (destroyed || !interactionManager) return;\n\n // Cancel any in-flight camera animation (and a lingering post-flight follow).\n cancelFlight();\n\n const cameraCfg = compilation.animation?.camera ?? null;\n const resolveTarget = () => (typeof to === 'function' ? to() : to);\n const snap = cameraCfg === null || prefersReducedMotion() || opts?.duration === 0;\n\n if (snap) {\n interactionManager.setTransform(resolveTarget());\n cameraChangePending = true;\n needsRender = true;\n scheduleRender();\n onDone?.();\n return;\n }\n\n const { width, height } = getCanvasDimensions();\n const duration = opts?.duration ?? cameraCfg?.duration ?? 'auto';\n const ease = opts?.ease ?? cameraCfg?.ease ?? 'smooth';\n // Large graphs skip labels/glow while flying; the final frame is full quality.\n const heavy = positionedNodes.length > 1000;\n\n const flight = createCameraFlight({\n from: interactionManager.getTransform(),\n to,\n viewport: { width, height },\n apply: (t) => {\n interactionManager!.setTransform(t);\n isGesturing = heavy;\n cameraChangePending = true;\n needsRender = true;\n },\n onDone: () => {\n activeFlight = null;\n isGesturing = false;\n needsRender = true;\n // Provider-form flights converge at t=1 while the tracked node may\n // still be settling — keep following it until the sim quiets down.\n if (typeof to === 'function') startFollow(to);\n scheduleRender();\n onDone?.();\n },\n opts: { duration, ease },\n });\n activeFlight = flight;\n scheduler.add(flight);\n }\n\n /** Snap the camera to a provider each frame until the sim settles. */\n function startFollow(target: () => ZoomTransform): void {\n const follow = createCameraFollow({\n target,\n apply: (t) => {\n interactionManager!.setTransform(t);\n cameraChangePending = true;\n needsRender = true;\n },\n isActive: () => !destroyed && lastAlpha >= FOLLOW_SETTLE_ALPHA,\n });\n activeFollow = follow;\n scheduler.add(follow);\n }\n\n /** Cancel any active camera flight/follow (called by user-initiated pan/zoom). */\n function cancelFlight(): void {\n if (activeFlight) {\n scheduler.remove(activeFlight);\n activeFlight = null;\n isGesturing = false;\n }\n if (activeFollow) {\n scheduler.remove(activeFollow);\n activeFollow = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Interaction wiring\n // ---------------------------------------------------------------------------\n\n function initInteraction(): void {\n if (!canvas) return;\n\n if (options?.tooltip !== false) {\n tooltipManager = createTooltipManager(wrapper!);\n }\n\n interactionManager = new GraphInteractionManager(canvas, spatialIndex, {\n onTransformChange(_transform) {\n // User-initiated pan/zoom cancels any camera flight. Programmatic\n // setTransform does NOT call this callback, so there's no feedback loop;\n // node-drag correctly doesn't reach here either.\n cancelFlight();\n markGesture();\n // User pan/zoom is a camera change too — zoom UIs and camera\n // persistence rely on the coalesced onCameraChange, not polling.\n cameraChangePending = true;\n needsRender = true;\n scheduleRender();\n },\n onHoverChange(nodeId) {\n // Skip redundant work (and a duplicate onNodeHover fire) when the hovered\n // node id is unchanged.\n if (nodeId === hoveredNodeId) return;\n hoveredNodeId = nodeId;\n // Re-arm the focus crossfade so the hover neighborhood eases in/out.\n armFocus(performance.now());\n startHoverRadiusTween(nodeId);\n needsRender = true;\n scheduleRender();\n\n // Race-safe ordering: clear any edge hover and fire onEdgeHover(null)\n // BEFORE onNodeHover(node), so an edge session never interleaves inside\n // a node hover session.\n if (nodeId && hoveredEdgeId) {\n hoveredEdgeId = null;\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n\n // Fire onNodeHover callback\n options?.onNodeHover?.(nodeId ? nodeDataById(nodeId) : null);\n\n // Show or hide tooltip\n if (nodeId && tooltipManager) {\n showNodeTooltip(nodeId);\n } else if (!nodeId) {\n // Tooltip hiding handled in onBackgroundHover (edge may show tooltip).\n tooltipManager?.hide();\n }\n },\n onBackgroundHover(graphX, graphY, screenX, screenY) {\n // A live node hover owns the tooltip; don't let edge hit-testing steal it.\n if (hoveredNodeId) return;\n // Throttle edge hit testing to avoid O(n) scan on every mousemove\n const now = performance.now();\n if (now - lastEdgeHitTime < 32) {\n // When throttled, clear edge hover so hover-off transitions stay snappy\n if (hoveredEdgeId) {\n hoveredEdgeId = null;\n needsRender = true;\n scheduleRender();\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n return;\n }\n lastEdgeHitTime = now;\n\n // Edge hit testing: check proximity to edge line segments\n const transform = interactionManager?.getTransform();\n const threshold = 5 / (transform?.k ?? 1); // 5px in screen space\n const edgeId = hitTestEdge(graphX, graphY, threshold);\n\n if (edgeId !== hoveredEdgeId) {\n hoveredEdgeId = edgeId;\n needsRender = true;\n scheduleRender();\n\n if (edgeId) {\n const data = edgeDataById(edgeId);\n options?.onEdgeHover?.(data);\n if (tooltipManager && data) showEdgeTooltip(edgeId, data, screenX, screenY);\n } else {\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n }\n },\n onSelectionChange(nodeIds) {\n selectedNodeIds = new Set(nodeIds);\n armFocus(performance.now());\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.(nodeIds);\n\n // Fire onNodeClick for the most recently added node\n if (nodeIds.length > 0) {\n const lastId = nodeIds[nodeIds.length - 1];\n options?.onNodeClick?.(nodeDataById(lastId));\n }\n },\n onNodeDragStart(nodeId) {\n // Pin at the node's current position to avoid visual snap to origin\n const node = positionedNodes.find((n) => n.id === nodeId);\n const x = node?.x ?? 0;\n const y = node?.y ?? 0;\n // Springy: hold the sim warm (alphaTarget 0.3) so neighbors follow. Off\n // → legacy pin with no alphaTarget field (byte-identical message).\n simulation?.pinNode(nodeId, x, y, springyDragEnabled() ? 0.3 : undefined);\n canvas?.classList.add('oc-graph-canvas--dragging');\n },\n onNodeDrag(nodeId, x, y) {\n simulation?.dragNode(nodeId, x, y);\n },\n onNodeDragEnd(nodeId) {\n // Springy: cool the sim back down (alphaTarget 0). Off → legacy unpin\n // with no alphaTarget field (preserves the legacy reheat behavior).\n simulation?.unpinNode(nodeId, springyDragEnabled() ? 0 : undefined);\n canvas?.classList.remove('oc-graph-canvas--dragging');\n },\n onPointerMove(graphX, graphY) {\n if (!cursorForceEnabled()) return;\n // Throttle the pointer feed to ~30Hz so we don't post on every mousemove.\n const now = performance.now();\n if (now - lastPointerFeedTime < CURSOR_POINTER_THROTTLE_MS) return;\n lastPointerFeedTime = now;\n simulation?.setPointer(graphX, graphY, true);\n },\n onPointerLeave() {\n if (!cursorForceEnabled()) return;\n simulation?.setPointer(0, 0, false);\n },\n onDoubleClick(nodeId) {\n options?.onNodeDoubleClick?.(nodeDataById(nodeId));\n },\n });\n\n // Wire keyboard navigation\n cleanupKeyboard = attachGraphKeyboardNav({\n canvas,\n getNodes: () => positionedNodes,\n getSelectedIds: () => [...selectedNodeIds],\n getAdjacency: () => adjacencyMap,\n onSelect(nodeId) {\n selectedNodeIds = new Set([nodeId]);\n needsRender = true;\n scheduleRender();\n options?.onNodeClick?.(nodeDataById(nodeId));\n options?.onSelectionChange?.([nodeId]);\n },\n onDeselect() {\n selectedNodeIds.clear();\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.([]);\n },\n onZoom(direction) {\n if (!interactionManager || !canvas) return;\n const t = interactionManager.getTransform();\n const { width: cw, height: ch } = getCanvasDimensions();\n const factor = direction === 'in' ? 1.2 : 0.8;\n const newK = t.k * factor;\n const newTransform = t.zoomAt(newK, cw / 2, ch / 2);\n flyCamera(newTransform, { duration: 200 });\n },\n onFitAll() {\n zoomToFit();\n },\n });\n\n // Handle node clicks (from interaction manager selection change wiring above)\n // We catch clicks via the interaction manager's onSelectionChange callback\n }\n\n // ---------------------------------------------------------------------------\n // Public API methods\n // ---------------------------------------------------------------------------\n\n function search(query: string): void {\n if (destroyed) return;\n searchManager.search(query, positionedNodes);\n needsRender = true;\n scheduleRender();\n }\n\n function clearSearch(): void {\n if (destroyed) return;\n searchManager.clearSearch();\n needsRender = true;\n scheduleRender();\n }\n\n function zoomToFit(opts?: CameraFlightOptions & { padding?: number }): void {\n if (destroyed || !interactionManager || positionedNodes.length === 0) return;\n const { width: cw, height: ch } = getCanvasDimensions();\n const { transform: fitTransform } = ZoomTransform.fitBounds(\n positionedNodes,\n cw,\n ch,\n opts?.padding,\n {\n insetTop: chromeInsetTop(),\n },\n );\n flyCamera(fitTransform, opts);\n }\n\n function zoomToNode(nodeId: string, opts?: CameraFlightOptions & { scale?: number }): void {\n if (destroyed || !interactionManager || !canvas) return;\n const node = positionedNodes.find((n) => n.id === nodeId);\n if (!node) return;\n\n const { width: cw, height: ch } = getCanvasDimensions();\n const k = clampK(opts?.scale ?? 2);\n // Provider form: re-read the node's live position each frame so the camera\n // tracks it while the simulation is still settling.\n const provider = (): ZoomTransform => {\n const live = positionedNodes.find((n) => n.id === nodeId) ?? node;\n return new ZoomTransform(cw / 2 - live.x * k, ch / 2 - live.y * k, k);\n };\n flyCamera(provider, opts);\n }\n\n function flyTo(target: { x: number; y: number; k?: number }, opts?: CameraFlightOptions): void {\n if (destroyed || !interactionManager) return;\n const { width: cw, height: ch } = getCanvasDimensions();\n const k = clampK(target.k ?? interactionManager.getTransform().k);\n flyCamera(new ZoomTransform(cw / 2 - target.x * k, ch / 2 - target.y * k, k), opts);\n }\n\n function centerAt(x: number, y: number, opts?: CameraFlightOptions): void {\n flyTo({ x, y }, opts);\n }\n\n function getCamera(): { x: number; y: number; k: number } {\n const t = interactionManager?.getTransform() ?? ZoomTransform.identity();\n return { x: t.x, y: t.y, k: t.k };\n }\n\n function selectNode(nodeId: string, opts?: { fly?: boolean } & CameraFlightOptions): void {\n if (destroyed) return;\n selectedNodeIds = new Set([nodeId]);\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.([nodeId]);\n const shouldFly = opts?.fly ?? compilation.interaction.selectFlyTo;\n if (shouldFly) zoomToNode(nodeId, opts);\n }\n\n function getSelectedNodes(): string[] {\n return [...selectedNodeIds];\n }\n\n function getSearchMatches(): string[] {\n return [...(searchManager.getMatches() ?? [])];\n }\n\n // -------------------------------------------------------------------------\n // Highlight API (transient layer over the sticky category filter)\n // -------------------------------------------------------------------------\n\n /**\n * Emphasize a set of nodes on top of the standing category filter. Does not\n * touch the filter; legend `active` flags are unaffected. A target that\n * resolves to no nodes is a no-op layer: the standing filter keeps dimming.\n * The target is retained and re-resolved on `update()`, so a category-form\n * highlight tracks data changes.\n */\n function highlight(target: GraphHighlightTarget, opts?: { dimOpacity?: number }): void {\n if (destroyed) return;\n transientTarget = target;\n refreshTransientHighlight();\n highlightDimOpacity = opts?.dimOpacity ?? null;\n recomputeHighlight();\n refreshFocus();\n emitHighlightChange();\n }\n\n /**\n * Drop the transient highlight. The category filter stands — clearing that is\n * `setActiveCategories([])`.\n */\n function clearHighlight(): void {\n if (destroyed) return;\n transientTarget = null;\n transientHighlight = null;\n highlightDimOpacity = null;\n recomputeHighlight();\n refreshFocus();\n emitHighlightChange();\n }\n\n function getHighlight(): string[] | null {\n return highlightSet ? [...highlightSet] : null;\n }\n\n function getLegend(): GraphLegendData {\n const nodeEntries = 'entries' in compilation.legend ? compilation.legend.entries : [];\n return {\n field: compilation.legendField,\n nodes: nodeEntries\n .filter((e) => !e.overflow)\n .map((e) => ({\n label: e.label,\n color: e.color,\n count: e.count,\n active: activeCategories.size === 0 || activeCategories.has(e.label),\n })),\n edges: (compilation.edgeLegend ?? []).map((e) => ({\n label: e.label,\n color: e.color,\n count: e.count,\n })),\n };\n }\n\n /**\n * Set the sticky category filter (built-in legend + headless). A transient\n * highlight() layers over this rather than replacing it. Empty active set =\n * all categories shown (no filter, no dimming).\n */\n function setActiveCategories(values: string[]): void {\n if (destroyed) return;\n activeCategories = new Set(values);\n recomputeHighlight();\n syncLegendActiveState();\n refreshFocus();\n emitHighlightChange();\n }\n\n function getActiveCategories(): string[] {\n return [...activeCategories];\n }\n\n function toggleLegendCategory(value: string): void {\n if (activeCategories.has(value)) activeCategories.delete(value);\n else activeCategories.add(value);\n recomputeHighlight();\n syncLegendActiveState();\n refreshFocus();\n options?.onLegendToggle?.([...activeCategories]);\n emitHighlightChange();\n }\n\n function doResize(): void {\n if (destroyed || !canvas || !renderer || !wrapper) return;\n const { width, height } = getContainerDimensions();\n const canvasHeight = Math.max(height, 200);\n renderer.resize(width, canvasHeight);\n // A width change can rewrap the title or move the legend; re-derive the\n // chrome/legend separation before any fit below measures the chrome band.\n syncChromeInset();\n\n // Mid-entrance: the in-flight camera fit targets the OLD viewport, so cancel\n // just that flight and snap to the new-viewport fit. The reveal tween (node\n // alpha/scale ramp) is orthogonal to the camera and keeps running.\n if (entranceFitInFlight && interactionManager) {\n cancelFlight();\n entranceFitInFlight = false;\n interactionManager.setTransform(computeInitialFit());\n cameraChangePending = true;\n }\n\n needsRender = true;\n scheduleRender();\n }\n\n /**\n * Unified data update.\n *\n * 1. Finish in-flight animations, then compile the new spec.\n * 2. Diff prev↔next. A `visualOnly` change (identical node AND edge id sets AND\n * equal simulationConfig) takes the position-preserving refresh — no sim\n * restart. Anything else (added/removed marks, or a physics change) is a\n * structural update.\n * 3. Structural update: tear down the SIM ONLY (interaction manager, camera\n * transform, and surviving selection are kept). Survivors keep their prior\n * x/y; enterers get spawn positions. A new sim is created with the center\n * force suppressed and a low reheat alpha, so survivors barely move and\n * enterers locally settle. Enter fades and exit ghosts animate the delta.\n */\n function update(newSpec: GraphSpec): void {\n if (destroyed) return;\n currentSpec = newSpec;\n\n // Finish any in-flight animations (e.g. an entrance reveal or a prior\n // update's enter/exit fade) so they snap to their final state and fire\n // onDone rather than being hard-cancelled mid-flight.\n scheduler.finishAll();\n entranceActive = false;\n entranceProgress = 1;\n entranceFitInFlight = false;\n entranceReveal = null;\n // Clear any lingering update-transition state (finishAll ran their onDone).\n enterAlphaMap = null;\n exitingGhosts = null;\n\n // Capture prev state BEFORE recompiling.\n const prevNodes = positionedNodes;\n const prevEdges = positionedEdges;\n const prevConfig = compilation.simulationConfig;\n\n // Recompile with the new spec.\n compilation = compile();\n seedIds = new Set(compilation.seedNodeIds);\n\n const diff = diffGraphUpdate(\n prevNodes,\n prevEdges,\n compilation,\n prevConfig,\n prevConfig.seed ?? 0,\n );\n\n if (diff.visualOnly) {\n runVisualOnlyUpdate();\n return;\n }\n\n runStructuralUpdate(diff);\n }\n\n /**\n * Position-preserving visual refresh: recompile changed encoding/chrome/legend\n * and transfer existing node positions. No simulation restart. (Assumes the\n * new `compilation` is already set and node/edge id sets are unchanged.)\n */\n function runVisualOnlyUpdate(): void {\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n\n // Build a position lookup from the current positioned nodes.\n const posMap = new Map<string, { x: number; y: number }>();\n for (const node of positionedNodes) {\n posMap.set(node.id, { x: node.x, y: node.y });\n }\n\n // Transfer positions to the newly compiled nodes.\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = posMap.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n\n positionedEdges = compilation.edges.map((edge) => {\n const src = posMap.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = posMap.get(edge.target) ?? { x: 0, y: 0 };\n return { ...edge, sourceX: src.x, sourceY: src.y, targetX: tgt.x, targetY: tgt.y };\n });\n\n spatialIndex.rebuild(positionedNodes);\n\n // Highlight persists: re-resolve both layers against the new nodes. Node\n // ids are identical on a visual-only update, but a category-form transient\n // can still change membership if the underlying field values changed.\n refreshTransientHighlight();\n recomputeHighlight();\n\n // Search survives: re-run the stored query against the new nodes.\n reRunSearch();\n\n renderChrome();\n renderLegend();\n syncLegendActiveState();\n\n needsRender = true;\n scheduleRender();\n }\n\n /**\n * Structural update: nodes/edges added/removed or physics changed. Tears down\n * only the simulation, recreates it with survivor/spawn positions and a local\n * reheat, and animates the enter/exit delta.\n */\n function runStructuralUpdate(diff: ReturnType<typeof diffGraphUpdate>): void {\n // Tear down the SIM ONLY. Interaction manager, camera transform, and\n // selection all survive (the plan's key divergence from the old full update).\n teardownSimOnly();\n\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n\n // Known positions for the new sim: survivors keep prior x/y, enterers spawn.\n const positions = new Map<string, { x: number; y: number }>();\n for (const [id, p] of diff.survivingPositions) positions.set(id, p);\n for (const [id, p] of diff.spawnPositions) positions.set(id, p);\n\n // changeRatio = max(node churn, edge churn). Node churn is\n // (|entering| + |exiting nodes|) / max(prevNodeCount, nextNodeCount); the\n // edge analog uses the same shape. Low alpha IS the local reheat.\n const prevNodeCount = diff.survivingPositions.size + diff.exitingNodes.length;\n const nextNodeCount = diff.survivingPositions.size + diff.enteringIds.length;\n const nodeRatio = ratio(\n diff.enteringIds.length + diff.exitingNodes.length,\n Math.max(prevNodeCount, nextNodeCount),\n );\n const prevEdgeCount =\n compilation.edges.length - diff.enteringEdgeCount + diff.exitingEdges.length;\n const nextEdgeCount = compilation.edges.length;\n const edgeRatio = ratio(\n diff.enteringEdgeCount + diff.exitingEdges.length,\n Math.max(prevEdgeCount, nextEdgeCount),\n );\n const changeRatio = Math.max(nodeRatio, edgeRatio);\n const initialAlpha = Math.min(1, 0.3 + 0.7 * changeRatio);\n\n // Seed positioned nodes/edges immediately so the first frame (before the sim\n // streams its first tick) draws survivors at their prior spots and enterers\n // at their spawn spots — no flash at the origin.\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = positions.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n positionedEdges = compilation.edges.map((edge) => {\n const src = positions.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = positions.get(edge.target) ?? { x: 0, y: 0 };\n return { ...edge, sourceX: src.x, sourceY: src.y, targetX: tgt.x, targetY: tgt.y };\n });\n spatialIndex.rebuild(positionedNodes);\n\n initSimulation({\n positions,\n suppressCenter: true,\n initialAlpha,\n skipWarmup: true,\n skipEntrance: true,\n });\n\n // Update DOM chrome/legend for the new graph.\n renderChrome();\n renderLegend();\n\n // Reconcile interaction/highlight/search state against the new node set.\n reconcileStateAfterUpdate();\n\n // Wire the enter-fade and exit-ghost transitions.\n startUpdateTransitions(diff);\n\n needsRender = true;\n scheduleRender();\n }\n\n /** Safe ratio (0 when the denominator is 0). */\n function ratio(numerator: number, denominator: number): number {\n return denominator > 0 ? numerator / denominator : 0;\n }\n\n /**\n * Prune stale interaction state after a structural update: drop hovered\n * node/edge ids that no longer exist, intersect the selection with the new\n * node set (pushing the pruned set into the interaction manager so a later\n * shift-click can't resurrect deleted ids), re-resolve highlight, and re-run\n * any active search.\n */\n function reconcileStateAfterUpdate(): void {\n const nextIds = new Set(compilation.nodes.map((n) => n.id));\n\n // Hovered node: clear if gone.\n if (hoveredNodeId && !nextIds.has(hoveredNodeId)) hoveredNodeId = null;\n // Hovered edge: clear if either endpoint is gone.\n if (hoveredEdgeId) {\n const [src, tgt] = hoveredEdgeId.split('->');\n if (!nextIds.has(src) || !nextIds.has(tgt)) hoveredEdgeId = null;\n }\n\n // Selection: intersect with survivors, then push into the interaction manager.\n const survivingSelection = [...selectedNodeIds].filter((id) => nextIds.has(id));\n selectedNodeIds = new Set(survivingSelection);\n interactionManager?.setSelection(survivingSelection);\n\n // Highlight persists. Re-resolve the transient layer against the new nodes\n // first (this both tracks category membership changes and drops deleted\n // ids, which an unpruned set would resurrect), then recompose.\n refreshTransientHighlight();\n recomputeHighlight();\n syncLegendActiveState();\n\n // Search survives: re-run the stored query against the new nodes.\n reRunSearch();\n\n // Re-arm the focus crossfade against the reconciled state (a fresh transition\n // so it doesn't blend from stale prev/next snapshots).\n focusTransition = null;\n armFocus(performance.now());\n }\n\n /** Re-run the active search query (if any) against the current positioned nodes. */\n function reRunSearch(): void {\n const q = searchManager.getQuery();\n if (q !== null) searchManager.search(q, positionedNodes);\n }\n\n /**\n * Start the enter-fade (new nodes 0→1 over update.duration, quantized for\n * batching) and the exit-ghost fade (removed marks 1→0 over exit.duration).\n * Snaps instantly under reduced motion or when the phase is disabled.\n */\n function startUpdateTransitions(diff: ReturnType<typeof diffGraphUpdate>): void {\n const updateCfg = compilation.animation?.update ?? null;\n const exitCfg = compilation.animation?.exit ?? null;\n const reduced = prefersReducedMotion();\n\n // -- Enter fade --\n if (diff.enteringIds.length > 0 && updateCfg && !reduced) {\n const entering = diff.enteringIds;\n enterAlphaMap = new Map(entering.map((id) => [id, 0]));\n const ease = resolveEase(updateCfg.ease);\n const tween = createTween({\n duration: updateCfg.duration,\n ease,\n apply: (t) => {\n // Quantize to 8 buckets so per-node alpha keeps fill-batching bounded.\n const q = Math.round(t * 8) / 8;\n if (enterAlphaMap) for (const id of entering) enterAlphaMap.set(id, q);\n needsRender = true;\n },\n onDone: () => {\n enterAlphaMap = null;\n needsRender = true;\n },\n });\n scheduler.add(tween);\n }\n\n // -- Exit ghosts --\n if ((diff.exitingNodes.length > 0 || diff.exitingEdges.length > 0) && exitCfg && !reduced) {\n exitingGhosts = { nodes: diff.exitingNodes, edges: diff.exitingEdges, alpha: 1 };\n const ease = resolveEase(exitCfg.ease);\n const tween = createTween({\n duration: exitCfg.duration,\n ease,\n apply: (t) => {\n if (exitingGhosts) exitingGhosts.alpha = 1 - t;\n needsRender = true;\n },\n onDone: () => {\n exitingGhosts = null;\n needsRender = true;\n },\n });\n scheduler.add(tween);\n }\n }\n\n /**\n * @deprecated Use {@link GraphInstance.update} instead. `update` now handles\n * both visual-only and structural changes (diffed automatically) and preserves\n * node positions when nothing structural changed. Kept as an alias for backward\n * compatibility.\n */\n function updateVisuals(newSpec: GraphSpec): void {\n update(newSpec);\n }\n\n /** Tear down ONLY the simulation, keeping interaction/transform/selection. */\n function teardownSimOnly(): void {\n simulation?.destroy();\n simulation = null;\n }\n\n function teardownSubsystems(): void {\n // Cancel animations BEFORE tearing down the sim/DOM, so no in-flight tick\n // writes to removed state.\n scheduler.cancelAll();\n activeFlight = null;\n activeFollow = null;\n // Reset entrance state so a remount (React StrictMode) starts clean.\n entranceReveal = null;\n entranceActive = false;\n entranceProgress = 1;\n entranceFitInFlight = false;\n // Reset update-transition state too (cancelAll already stopped the tweens).\n enterAlphaMap = null;\n exitingGhosts = null;\n if (animFrameId !== null) {\n cancelAnimationFrame(animFrameId);\n animFrameId = null;\n }\n if (cleanupKeyboard) {\n cleanupKeyboard();\n cleanupKeyboard = null;\n }\n interactionManager?.destroy();\n interactionManager = null;\n simulation?.destroy();\n simulation = null;\n tooltipManager?.destroy();\n tooltipManager = null;\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n if (gestureTimeout !== null) {\n clearTimeout(gestureTimeout);\n gestureTimeout = null;\n }\n\n teardownSubsystems();\n\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n legendController?.destroy();\n legendController = null;\n\n if (wrapper?.parentNode) {\n wrapper.parentNode.removeChild(wrapper);\n }\n wrapper = null;\n canvas = null;\n chromeEl = null;\n legendEl = null;\n renderer = null;\n\n container.classList.remove('oc-dark');\n }\n\n // ---------------------------------------------------------------------------\n // Initialize\n // ---------------------------------------------------------------------------\n\n try {\n compilation = compile();\n seedIds = new Set(compilation.seedNodeIds);\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n applyInitialHighlight();\n createDOM();\n initSimulation();\n initInteraction();\n } catch (err) {\n // Same failure semantics as createChart and createSankey: a spec that\n // cannot compile or mount is a caller bug, and a throw is the only signal\n // that reaches them. (A silent no-op instance used to be returned here.)\n console.error('[viz] Graph mount failed:', err);\n throw err;\n }\n\n // Responsive resize\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => {\n doResize();\n });\n }\n\n return {\n update,\n updateVisuals,\n search,\n clearSearch,\n zoomToFit,\n zoomToNode,\n flyTo,\n centerAt,\n getCamera,\n selectNode,\n getSelectedNodes,\n getSearchMatches,\n highlight,\n clearHighlight,\n getHighlight,\n getLegend,\n setActiveCategories,\n getActiveCategories,\n resize: doResize,\n destroy,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Util\n// ---------------------------------------------------------------------------\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n","/**\n * Animated camera for the graph.\n *\n * Flights interpolate between two `ZoomTransform`s along d3's geodesic\n * `interpolateZoom` path (smooth zoom-and-pan that pulls back to see both\n * endpoints before diving in). A flight is a {@link GraphAnimation}, driven by\n * the mount's AnimationScheduler.\n *\n * `interpolateZoom` operates on a `[cx, cy, width]` \"view\" — the graph-space\n * center and the graph-space width visible across the viewport. We convert the\n * ZoomTransform ⇄ view around a fixed viewport size.\n */\n\nimport type { AnimationEase } from '@opendata-ai/openchart-core';\nimport { interpolateZoom } from 'd3-interpolate';\nimport { resolveEase } from './motion';\nimport type { GraphAnimation } from './scheduler';\nimport { ZoomTransform } from './zoom';\n\n/** Minimum/maximum zoom scale a flight may resolve to. */\nconst K_MIN = 0.05;\nconst K_MAX = 15;\n\n/** Auto-duration bounds (ms) and the interpolateZoom.duration scale factor. */\nconst AUTO_MIN_MS = 300;\nconst AUTO_MAX_MS = 1200;\nconst AUTO_SCALE = 0.6;\n\n/** A d3 zoom \"view\": graph-space center x/y and the graph-space width in view. */\nexport type ZoomView = [number, number, number];\n\n/** Viewport size a flight is framed against. */\nexport interface Viewport {\n width: number;\n height: number;\n}\n\n/**\n * Convert a ZoomTransform to a d3 zoom view against a viewport.\n *\n * The view width is the graph-space span visible across `viewport.width`\n * (= viewport.width / k); the center is the graph point under the viewport\n * center.\n */\nexport function transformToView(t: ZoomTransform, viewport: Viewport): ZoomView {\n const cx = (viewport.width / 2 - t.x) / t.k;\n const cy = (viewport.height / 2 - t.y) / t.k;\n const width = viewport.width / t.k;\n return [cx, cy, width];\n}\n\n/** Inverse of {@link transformToView}: a zoom view back to a ZoomTransform. */\nexport function viewToTransform(view: ZoomView, viewport: Viewport): ZoomTransform {\n const [cx, cy, width] = view;\n const k = clampK(viewport.width / width);\n const x = viewport.width / 2 - cx * k;\n const y = viewport.height / 2 - cy * k;\n return new ZoomTransform(x, y, k);\n}\n\n/** Clamp a zoom scale into the allowed range. */\nexport function clampK(k: number): number {\n if (!Number.isFinite(k) || k <= 0) return K_MIN;\n return Math.min(K_MAX, Math.max(K_MIN, k));\n}\n\n/** Options controlling a camera flight. */\nexport interface CameraFlightOptions {\n /** Duration in ms, or `'auto'` to derive from the zoom distance. */\n duration?: number | 'auto';\n /** Easing preset. Default `'smooth'`. */\n ease?: AnimationEase;\n}\n\n/** Inputs to {@link createCameraFlight}. */\nexport interface CameraFlightInputs {\n /** Starting transform. */\n from: ZoomTransform;\n /**\n * Target transform, or a provider that returns the current target each frame\n * (used to track a node that is still settling). When a provider is passed,\n * the interpolator is rebuilt each frame from the ORIGINAL `from` to the\n * current target, so a moving target stays smooth.\n */\n to: ZoomTransform | (() => ZoomTransform);\n /** Viewport the flight is framed against. */\n viewport: Viewport;\n /** Applies an interpolated transform to the scene each frame. */\n apply: (t: ZoomTransform) => void;\n /** Fired once on natural completion (not on cancel). */\n onDone?: () => void;\n /** Flight options. */\n opts?: CameraFlightOptions;\n}\n\n/**\n * Create a camera flight as a {@link GraphAnimation}.\n *\n * Degenerate from≈to is guarded: duration is forced to at least 1ms and NaN\n * interpolation results fall back to the target. The start time locks on the\n * first tick for deterministic tests.\n */\nexport function createCameraFlight(inputs: CameraFlightInputs): GraphAnimation {\n const { from, viewport, apply, onDone, opts } = inputs;\n const ease = resolveEase(opts?.ease ?? 'smooth');\n const isProvider = typeof inputs.to === 'function';\n\n const fromView = transformToView(from, viewport);\n\n function targetView(): ZoomView {\n const to = isProvider ? (inputs.to as () => ZoomTransform)() : (inputs.to as ZoomTransform);\n return transformToView(to, viewport);\n }\n\n // Build the interpolator (rebuilt each frame in provider mode).\n let interp = interpolateZoom(fromView, targetView());\n const resolvedDuration = resolveDuration(opts?.duration, interp.duration);\n\n let startTime: number | null = null;\n let finished = false;\n\n function applyAt(t: number): void {\n if (isProvider) interp = interpolateZoom(fromView, targetView());\n const view = interp(t) as ZoomView;\n if (view.some((v) => !Number.isFinite(v))) {\n apply(viewToTransform(targetView(), viewport));\n return;\n }\n apply(viewToTransform(view, viewport));\n }\n\n return {\n tick(now: number): boolean {\n if (finished) return false;\n if (startTime === null) startTime = now;\n const raw = resolvedDuration <= 0 ? 1 : Math.min(1, (now - startTime) / resolvedDuration);\n applyAt(ease(raw));\n if (raw >= 1) {\n finished = true;\n onDone?.();\n return false;\n }\n return true;\n },\n finish(): void {\n if (finished) return;\n finished = true;\n applyAt(1);\n onDone?.();\n },\n cancel(): void {\n finished = true;\n },\n };\n}\n\n/** Inputs to {@link createCameraFollow}. */\nexport interface CameraFollowInputs {\n /** Provider returning the current target transform each frame. */\n target: () => ZoomTransform;\n /** Applies the followed transform to the scene each frame. */\n apply: (t: ZoomTransform) => void;\n /** True while the tracked target may still move (e.g. sim alpha ≥ threshold). */\n isActive: () => boolean;\n}\n\n/**\n * Post-flight follow for provider-form flights: the flight converges at t=1\n * while the tracked node may still be settling, so this cheap animation snaps\n * the camera to the provider each frame until `isActive()` reports the sim has\n * settled. User input or a new flight cancels it via the scheduler.\n */\nexport function createCameraFollow(inputs: CameraFollowInputs): GraphAnimation {\n let finished = false;\n return {\n tick(): boolean {\n if (finished) return false;\n if (!inputs.isActive()) {\n finished = true;\n return false;\n }\n inputs.apply(inputs.target());\n return true;\n },\n finish(): void {\n finished = true;\n },\n cancel(): void {\n finished = true;\n },\n };\n}\n\n/**\n * Resolve a flight duration. `'auto'` (or undefined) derives from d3's\n * interpolateZoom.duration (a perceptual distance), scaled and clamped. An\n * explicit number is honored but floored at 1ms so degenerate flights still tick.\n */\nexport function resolveDuration(\n duration: number | 'auto' | undefined,\n interpDuration: number,\n): number {\n if (typeof duration === 'number') return Math.max(1, duration);\n const scaled = interpDuration * AUTO_SCALE;\n return Math.max(AUTO_MIN_MS, Math.min(AUTO_MAX_MS, Math.max(1, scaled)));\n}\n","/**\n * Immutable zoom transform for graph pan/zoom.\n *\n * Provides coordinate conversion between screen space (canvas pixels)\n * and graph space (simulation coordinates). All mutations return new\n * instances rather than modifying in place.\n */\n\nimport type { PositionedNode } from './types';\n\nexport class ZoomTransform {\n constructor(\n readonly x: number,\n readonly y: number,\n readonly k: number,\n ) {}\n\n /** Convert screen coordinates to graph coordinates. */\n screenToGraph(sx: number, sy: number): { x: number; y: number } {\n return {\n x: (sx - this.x) / this.k,\n y: (sy - this.y) / this.k,\n };\n }\n\n /** Convert graph coordinates to screen coordinates. */\n graphToScreen(gx: number, gy: number): { x: number; y: number } {\n return {\n x: gx * this.k + this.x,\n y: gy * this.k + this.y,\n };\n }\n\n /**\n * Zoom to a target scale, keeping the given screen-space pivot\n * point fixed (content under the cursor stays under the cursor).\n */\n zoomAt(targetK: number, pivotX: number, pivotY: number): ZoomTransform {\n // The graph point under the pivot should remain at the same screen position.\n // Before: pivotX = gx * k + x => gx = (pivotX - x) / k\n // After: pivotX = gx * targetK + newX => newX = pivotX - gx * targetK\n const gx = (pivotX - this.x) / this.k;\n const gy = (pivotY - this.y) / this.k;\n return new ZoomTransform(pivotX - gx * targetK, pivotY - gy * targetK, targetK);\n }\n\n /** Pan by a screen-space delta. */\n pan(dx: number, dy: number): ZoomTransform {\n return new ZoomTransform(this.x + dx, this.y + dy, this.k);\n }\n\n /**\n * Compute a transform that fits all nodes within the given canvas\n * dimensions with the specified padding.\n *\n * Returns the transform and the ideal content height (in screen pixels)\n * so callers can shrink the canvas to eliminate dead space.\n */\n static fitBounds(\n nodes: PositionedNode[],\n canvasW: number,\n canvasH: number,\n padding: number = 40,\n opts?: { spread?: boolean; insetTop?: number },\n ): { transform: ZoomTransform; contentHeight: number } {\n if (nodes.length === 0) {\n return { transform: ZoomTransform.identity(), contentHeight: canvasH };\n }\n\n // Reserved band at the top of the canvas (the HTML chrome overlay). The fit\n // centers within the remaining area so nodes don't sit under the title.\n // Clamped so a degenerate measurement can't consume the whole viewport.\n const insetTop = Math.min(Math.max(0, opts?.insetTop ?? 0), canvasH * 0.4);\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n for (const n of nodes) {\n const r = n.radius;\n if (n.x - r < minX) minX = n.x - r;\n if (n.y - r < minY) minY = n.y - r;\n if (n.x + r > maxX) maxX = n.x + r;\n if (n.y + r > maxY) maxY = n.y + r;\n }\n\n let graphW = maxX - minX;\n let graphH = maxY - minY;\n\n if (graphW === 0 && graphH === 0) {\n // All nodes at the same point; center within the area below the inset\n return {\n transform: new ZoomTransform(\n canvasW / 2 - minX,\n insetTop + (canvasH - insetTop) / 2 - minY,\n 1,\n ),\n contentHeight: padding * 2 + insetTop,\n };\n }\n\n // When called early in the simulation (first tick), the bounding box\n // underestimates the final spread. Apply a spread multiplier based on\n // node count: larger graphs expand more as charge forces push nodes\n // apart over subsequent ticks. The sqrt scaling mirrors how d3-force\n // charge repulsion grows with node count.\n //\n // Warmup (Phase 6) settles the layout headlessly before the first fit, so\n // the bounds are already near-final; inflating them then fits at roughly\n // half the correct zoom at 10k nodes. Callers pass `spread: false` after a\n // warmup ran to skip the inflation. Default true = the original behavior.\n if (opts?.spread !== false && nodes.length > 50) {\n const spread = 1 + Math.sqrt(nodes.length) / 120;\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n graphW *= spread;\n graphH *= spread;\n minX = cx - graphW / 2;\n maxX = cx + graphW / 2;\n minY = cy - graphH / 2;\n maxY = cy + graphH / 2;\n }\n\n const availW = canvasW - padding * 2;\n const availH = canvasH - insetTop - padding * 2;\n // Cap at 1 so the graph never renders larger than its natural size\n const k = Math.min(1, availW / graphW, availH / graphH);\n\n // Center horizontally; center vertically within the area below the inset.\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n const tx = canvasW / 2 - cx * k;\n const ty = insetTop + (canvasH - insetTop) / 2 - cy * k;\n\n // Content height = scaled graph extent + top and bottom padding + inset\n const contentHeight = graphH * k + padding * 2 + insetTop;\n\n return {\n transform: new ZoomTransform(tx, ty, k),\n contentHeight,\n };\n }\n\n /** Identity transform (no pan, no zoom). */\n static identity(): ZoomTransform {\n return new ZoomTransform(0, 0, 1);\n }\n}\n","/**\n * Canvas 2D renderer for force-directed graph visualization.\n *\n * Stateless renderer: receives a GraphRenderState each frame and draws it.\n * Handles DPR scaling, viewport culling, LOD labels, dark mode glow effects,\n * and batched drawing for performance at 10k+ nodes.\n *\n * Performance strategy:\n * - Edges batched by (stroke, strokeWidth, dash) key → one stroke() per group\n * - Nodes batched by fill color → one fill() per color group\n * - Node strokes batched by stroke color\n * - Labels and glow skipped during active pan/zoom gestures\n */\n\nimport { BRAND_FONT_SIZE, BRAND_MIN_WIDTH } from '@opendata-ai/openchart-core';\nimport { driftFactor, nodeEnterProgress, popAlpha, popScale } from './entrance';\nimport type { FocusSnapshot } from './focus-transition';\nimport type { GraphRenderState, PositionedEdge, PositionedNode } from './types';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst LABEL_FONT_MIN = 8;\nconst LABEL_FONT_MAX = 12;\nconst EDGE_ALPHA_DEFAULT = 0.35;\nconst EDGE_ALPHA_CONNECTED = 1.0;\nconst SEARCH_NON_MATCH_ALPHA = 0.15;\n/** Default node dim tier — the ratio the edge dim tier derives against. */\nconst DEFAULT_DIM_OPACITY = 0.15;\n/** Above this visible-edge count a focus crossfade snaps (no per-frame blend). */\nconst CROSSFADE_MAX_EDGES = 20000;\n\n/**\n * The three emphasis tiers an edge/node can fall into for a given focus state.\n * `default` is the resting alpha (nothing emphasized); `connected` is fully lit;\n * `dimmed` is de-emphasized while something else is in focus.\n */\ntype FocusTier = 'default' | 'connected' | 'dimmed';\n\n/** Classify an edge under a focus snapshot into its emphasis tier. */\nfunction edgeTier(edge: PositionedEdge, focus: FocusSnapshot): FocusTier {\n if (!focus.hasActive) return 'default';\n return focus.connected.has(edge.source) && focus.connected.has(edge.target)\n ? 'connected'\n : 'dimmed';\n}\n\n/**\n * Classify a node under a focus snapshot into its emphasis tier.\n *\n * Ids in `exemptIds` (the graph's seed node) never dim: they classify as\n * `connected` under any focus state — highlight, category filter, hover, or\n * selection. (Search dimming is a separate alpha multiplier keyed off\n * `searchMatches` and is deliberately not exempted: a seed that doesn't match\n * the query shouldn't pretend to.) The exemption lives here rather than in the highlight set on\n * purpose -- `composeStandingFocus` expands the core set to\n * `core ∪ neighbors(core)`, and a seed is by construction a hub, so unioning it\n * into the highlight would light most of the graph and defeat the category\n * filter. `edgeTier` is deliberately NOT exempted: the seed stays lit while its\n * edges dim with everything else, which is exactly \"lit without lighting its\n * neighborhood\". Exempt nodes land in the existing connected-alpha bucket, so\n * the node fill/stroke batching keys are unaffected.\n */\nfunction nodeTier(\n node: PositionedNode,\n focus: FocusSnapshot,\n exemptIds: Set<string> | undefined,\n): FocusTier {\n if (!focus.hasActive) return 'default';\n if (exemptIds?.has(node.id)) return 'connected';\n return focus.connected.has(node.id) ? 'connected' : 'dimmed';\n}\n\n/**\n * Resolve a tier to its edge alpha. The dimmed tier derives from the node dim\n * knob (`dimOpacity / 3`), preserving the deliberate 0.15-node / 0.05-edge ratio\n * that keeps dense hairballs quiet during hover.\n */\nfunction edgeTierAlpha(tier: FocusTier, dimOpacity: number): number {\n switch (tier) {\n case 'connected':\n return EDGE_ALPHA_CONNECTED;\n case 'dimmed':\n return dimOpacity / 3;\n default:\n return EDGE_ALPHA_DEFAULT;\n }\n}\n\n/** Resolve a tier to its node alpha. The dimmed tier is the raw dim knob. */\nfunction nodeTierAlpha(tier: FocusTier, dimOpacity: number): number {\n switch (tier) {\n case 'dimmed':\n return dimOpacity;\n default:\n return 1;\n }\n}\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n/**\n * Per-frame entrance reveal helper, built from the mount's `entrance` state.\n *\n * Staggered (≤ ENTRANCE_STAGGER_MAX_NODES): each node POPS — scale runs 0→~1.1→1\n * (back-out overshoot), alpha 0→1 over the first 60% of its window, and the node\n * converges from a small centroid-outward drift offset to its final position.\n * `nodeT` is a per-node staggered+quantized progress ordered by `entrance.order`\n * (hash-scattered rank), so nodes reveal at independent, scattered times.\n *\n * Unstaggered (large graphs): the legacy single global fade — alpha and scale\n * both ramp `0.6 + 0.4·g`, no drift.\n *\n * Edges lag 30% behind the global progress; labels fade with the raw global\n * progress. `total` is fixed at build time so the stagger window is stable.\n */\ninterface EntranceReveal {\n nodeAlpha(node: PositionedNode): number;\n nodeScale(node: PositionedNode): number;\n /** Drift offset for a node id at the current frame (graph-space px). */\n shift(id: string): { x: number; y: number };\n edgeAlpha: number;\n labelAlpha: number;\n}\n\nconst ZERO_SHIFT = { x: 0, y: 0 };\n\nfunction makeEntranceReveal(\n entrance: GraphRenderState['entrance'] & { t: number },\n total: number,\n): EntranceReveal {\n const g = entrance.t;\n const { stagger, order, offsets } = entrance;\n const rankOf = (node: PositionedNode) => order?.get(node.id) ?? node.index;\n const nodeT = (node: PositionedNode) => (stagger ? nodeEnterProgress(g, rankOf(node), total) : g);\n const edgeAlpha = Math.max(0, (g - 0.3) / 0.7); // lag 30%\n const globalRamp = 0.6 + 0.4 * g;\n return {\n nodeAlpha: (node) => (stagger ? popAlpha(nodeT(node)) : globalRamp),\n nodeScale: (node) => (stagger ? popScale(nodeT(node)) : globalRamp),\n shift: (id) => {\n if (!stagger || !offsets || !order) return ZERO_SHIFT;\n const off = offsets.get(id);\n if (!off) return ZERO_SHIFT;\n const f = driftFactor(nodeEnterProgress(g, order.get(id) ?? 0, total));\n return f > 0 ? { x: off.x * f, y: off.y * f } : ZERO_SHIFT;\n },\n edgeAlpha,\n labelAlpha: g,\n };\n}\n\n/**\n * Derive a focus snapshot from hovered/selected nodes when the mount doesn't\n * supply an explicit crossfade. Preserves the legacy \"hover/select dims the\n * rest\" behavior for callers (and tests) that pass raw render state.\n */\nfunction deriveFocus(\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n adjacencyMap: Map<string, Set<string>>,\n): FocusSnapshot {\n const hasActive = hoveredNodeId !== null || selectedNodeIds.size > 0;\n const connected = new Set<string>();\n if (hasActive) {\n const active = new Set<string>();\n if (hoveredNodeId) active.add(hoveredNodeId);\n for (const id of selectedNodeIds) active.add(id);\n for (const id of active) {\n connected.add(id);\n const neighbors = adjacencyMap.get(id);\n if (neighbors) for (const nid of neighbors) connected.add(nid);\n }\n }\n return { hasActive, connected, searchMatches: null, selected: selectedNodeIds };\n}\nconst GLOW_NODE_THRESHOLD = 2000;\nconst GLOW_RADIUS_MULTIPLIER = 1.3;\nconst GLOW_ALPHA = 0.15;\nconst CULL_MARGIN = 50;\nconst TWO_PI = Math.PI * 2;\n\n/** Minimum node radius in screen pixels. Keeps nodes visible when zoomed out. */\nconst MIN_SCREEN_RADIUS = 2.5;\n\n// ---------------------------------------------------------------------------\n// Helpers (exported for testing)\n// ---------------------------------------------------------------------------\n\n/**\n * Compute label visibility threshold from zoom level.\n * At zoom 0.2 (zoomed out): threshold ~1.0 (only top ~5% visible).\n * At zoom 2.0+: threshold ~0.0 (all visible).\n */\nexport function labelThreshold(zoom: number): number {\n const t = Math.max(0, Math.min(1, (zoom - 0.2) / 1.8));\n return 1 - t;\n}\n\n/** Compute visible rect in graph coordinates from canvas size + transform. */\nexport function visibleRect(\n canvasWidth: number,\n canvasHeight: number,\n transform: { x: number; y: number; k: number },\n margin: number = CULL_MARGIN,\n): { minX: number; minY: number; maxX: number; maxY: number } {\n const { x, y, k } = transform;\n return {\n minX: (-x - margin) / k,\n minY: (-y - margin) / k,\n maxX: (canvasWidth - x + margin) / k,\n maxY: (canvasHeight - y + margin) / k,\n };\n}\n\n/** Check if a node falls within the visible rect. */\nfunction nodeInView(\n node: PositionedNode,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n): boolean {\n return (\n node.x + node.radius >= rect.minX &&\n node.x - node.radius <= rect.maxX &&\n node.y + node.radius >= rect.minY &&\n node.y - node.radius <= rect.maxY\n );\n}\n\n/** Check if an edge has at least one endpoint in view. */\nfunction edgeInView(\n edge: PositionedEdge,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n): boolean {\n return (\n (edge.sourceX >= rect.minX &&\n edge.sourceX <= rect.maxX &&\n edge.sourceY >= rect.minY &&\n edge.sourceY <= rect.maxY) ||\n (edge.targetX >= rect.minX &&\n edge.targetX <= rect.maxX &&\n edge.targetY >= rect.minY &&\n edge.targetY <= rect.maxY)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Dash patterns for edge styles\n// ---------------------------------------------------------------------------\n\nconst DASH_PATTERNS: Record<string, number[]> = {\n solid: [],\n dashed: [6, 4],\n dotted: [2, 3],\n};\n\n// ---------------------------------------------------------------------------\n// GraphCanvasRenderer\n// ---------------------------------------------------------------------------\n\nexport class GraphCanvasRenderer {\n private canvas: HTMLCanvasElement;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private ctx: CanvasRenderingContext2D;\n private dpr: number;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private cssWidth = 0;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private cssHeight = 0;\n\n constructor(canvas: HTMLCanvasElement) {\n this.canvas = canvas;\n this.ctx = canvas.getContext('2d')!;\n this.dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n }\n\n /** Update canvas dimensions with DPR scaling. CSS size stays at css values. */\n resize(width: number, height: number): void {\n this.cssWidth = width;\n this.cssHeight = height;\n this.canvas.width = width * this.dpr;\n this.canvas.height = height * this.dpr;\n }\n\n /** Clear canvas and render the full graph state. */\n render(state: GraphRenderState): void {\n const { ctx, dpr, cssWidth, cssHeight } = this;\n const {\n nodes,\n edges,\n transform,\n hoveredNodeId,\n hoveredEdgeId,\n selectedNodeIds,\n adjacencyMap,\n theme,\n searchMatches,\n isGesturing,\n } = state;\n const dimOpacity = state.dimOpacity ?? DEFAULT_DIM_OPACITY;\n\n // Resolve the current (next) focus snapshot. When the mount supplies a\n // crossfade in `state.focus`, use its endpoints; otherwise derive a snapshot\n // from hovered/selected nodes (backward-compatible, no crossfade).\n const nextFocus: FocusSnapshot =\n state.focus?.next ?? deriveFocus(hoveredNodeId, selectedNodeIds, adjacencyMap);\n // A crossfade is live only when focus is present, mid-flight, and (cheaply)\n // the graph isn't gesturing (during gestures we snap for perf).\n const crossfade = state.focus && state.focus.t < 1 && !isGesturing ? state.focus : null;\n\n // Entrance reveal (Phase 6): present only while the mount is mid-entrance.\n const entrance =\n state.entrance && state.entrance.t < 1\n ? makeEntranceReveal(state.entrance, nodes.length)\n : null;\n\n // Data-update enter fade (Phase 7): per-node alpha for newly-added nodes,\n // multiplied into node/edge/label alpha. Absent id → full alpha (1).\n const enterAlpha = state.enterAlpha ?? null;\n const enterAlphaFor = (id: string): number => enterAlpha?.get(id) ?? 1;\n\n // Viewport culling\n const rect = visibleRect(cssWidth, cssHeight, transform);\n const visibleNodes = nodes.filter((n) => nodeInView(n, rect));\n const visibleEdges = edges.filter((e) => edgeInView(e, rect));\n\n const isDark = theme.isDark;\n const showGlow = isDark && !isGesturing && visibleNodes.length < GLOW_NODE_THRESHOLD;\n const threshold = labelThreshold(transform.k);\n // Minimum radius in graph coordinates so nodes stay visible when zoomed out\n const minRadius = MIN_SCREEN_RADIUS / transform.k;\n\n // -- Clear and apply transform --\n ctx.save();\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, cssWidth, cssHeight);\n\n // Fill background (skip if transparent to let page background show through)\n if (theme.colors.background !== 'transparent') {\n ctx.fillStyle = theme.colors.background;\n ctx.fillRect(0, 0, cssWidth, cssHeight);\n }\n\n ctx.translate(transform.x, transform.y);\n ctx.scale(transform.k, transform.k);\n\n // -- Draw exit ghosts FIRST/UNDER the live marks (Phase 7) --\n // Removed nodes/edges fade out beneath the live graph. Not hit-tested (the\n // mount never rebuilds the spatial index with them), just painted.\n if (state.exiting && state.exiting.alpha > 0) {\n this.drawGhosts(ctx, state.exiting, rect);\n }\n\n // -- Draw edges (batched) -- crossfade path only mid-transition, else the\n // fast 3-bucket steady-state path. Degrade to snap for huge edge sets.\n if (crossfade && visibleEdges.length <= CROSSFADE_MAX_EDGES) {\n this.drawEdgesCrossfade(\n ctx,\n visibleEdges,\n crossfade.prev,\n crossfade.next,\n crossfade.t,\n dimOpacity,\n isGesturing ? null : searchMatches,\n hoveredEdgeId,\n entrance,\n enterAlphaFor,\n );\n } else {\n this.drawEdgesBatched(\n ctx,\n visibleEdges,\n nextFocus,\n dimOpacity,\n isGesturing ? null : searchMatches,\n hoveredEdgeId,\n entrance,\n enterAlphaFor,\n );\n }\n\n // -- Draw nodes (batched by fill color) --\n this.drawNodesBatched(\n ctx,\n visibleNodes,\n hoveredNodeId,\n selectedNodeIds,\n isGesturing ? null : searchMatches,\n showGlow,\n theme,\n minRadius,\n nextFocus,\n dimOpacity,\n state.exemptIds,\n crossfade,\n state.hoverRadiusScale,\n entrance,\n enterAlphaFor,\n );\n\n // -- Draw labels (skipped during gestures) --\n if (!isGesturing) {\n this.drawLabels(\n ctx,\n visibleNodes,\n threshold,\n hoveredNodeId,\n selectedNodeIds,\n searchMatches,\n transform.k,\n theme,\n entrance,\n enterAlphaFor,\n );\n }\n\n ctx.restore();\n\n // Brand watermark in screen coordinates (unaffected by pan/zoom)\n if (state.watermark) {\n this.drawBrand(ctx, cssWidth, cssHeight, theme);\n }\n }\n\n // -------------------------------------------------------------------------\n // Brand rendering\n // -------------------------------------------------------------------------\n\n private drawBrand(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n theme: GraphRenderState['theme'],\n ): void {\n if (w < BRAND_MIN_WIDTH) return;\n const { dpr } = this;\n ctx.save();\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n const padding = theme.spacing.padding;\n const x = w - padding;\n const y = h - padding;\n ctx.font = `600 ${BRAND_FONT_SIZE}px ${theme.fonts.family}`;\n ctx.fillStyle = theme.colors.axis;\n ctx.globalAlpha = 0.55;\n ctx.textAlign = 'right';\n ctx.textBaseline = 'alphabetic';\n ctx.fillText('tryOpenData.ai', x, y);\n ctx.restore();\n }\n\n // -------------------------------------------------------------------------\n // Batched edge drawing\n // -------------------------------------------------------------------------\n\n private drawEdgesBatched(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n focus: FocusSnapshot,\n dimOpacity: number,\n searchMatches: Set<string> | null,\n hoveredEdgeId: string | null,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Settled fast path: classify each edge into one of 3 tiers, batch within.\n const buckets: Record<FocusTier, PositionedEdge[]> = {\n dimmed: [],\n default: [],\n connected: [],\n };\n let hoveredEdge: PositionedEdge | null = null;\n\n for (const edge of edges) {\n const edgeId = `${edge.source}->${edge.target}`;\n if (edgeId === hoveredEdgeId) {\n hoveredEdge = edge;\n continue; // Draw hovered edge last, on top\n }\n buckets[edgeTier(edge, focus)].push(edge);\n }\n\n // Entrance: edges lag 30% behind the reveal, so scale every tier's alpha.\n const ea = entrance ? entrance.edgeAlpha : 1;\n\n // Draw dimmed first, then default, then connected (on top)\n this.drawEdgeGroupBatched(\n ctx,\n buckets.dimmed,\n edgeTierAlpha('dimmed', dimOpacity) * ea,\n searchMatches,\n enterAlphaFor,\n );\n this.drawEdgeGroupBatched(\n ctx,\n buckets.default,\n EDGE_ALPHA_DEFAULT * ea,\n searchMatches,\n enterAlphaFor,\n );\n this.drawEdgeGroupBatched(\n ctx,\n buckets.connected,\n EDGE_ALPHA_CONNECTED * ea,\n searchMatches,\n enterAlphaFor,\n );\n\n this.drawHoveredEdge(ctx, hoveredEdge);\n }\n\n /**\n * Crossfade edges between a prev and next focus state. Each edge is classified\n * under BOTH snapshots → at most 9 (prevTier × nextTier) buckets, each drawn\n * batched at `lerp(edgeTierAlpha[prev], edgeTierAlpha[next], t)`. Preserves the\n * per-group style batching within each bucket.\n */\n private drawEdgesCrossfade(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n prev: FocusSnapshot,\n next: FocusSnapshot,\n t: number,\n dimOpacity: number,\n searchMatches: Set<string> | null,\n hoveredEdgeId: string | null,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n const ea = entrance ? entrance.edgeAlpha : 1;\n // Bucket by (prevTier, nextTier). Key encodes both tiers.\n const buckets = new Map<string, PositionedEdge[]>();\n let hoveredEdge: PositionedEdge | null = null;\n\n for (const edge of edges) {\n const edgeId = `${edge.source}->${edge.target}`;\n if (edgeId === hoveredEdgeId) {\n hoveredEdge = edge;\n continue;\n }\n const key = `${edgeTier(edge, prev)}|${edgeTier(edge, next)}`;\n let bucket = buckets.get(key);\n if (!bucket) {\n bucket = [];\n buckets.set(key, bucket);\n }\n bucket.push(edge);\n }\n\n // Draw dim→dim first (lowest alpha) up to connected→connected, so brighter\n // buckets paint over dimmer ones. Sort by blended alpha ascending.\n const ordered = [...buckets.entries()]\n .map(([key, bucket]) => {\n const [prevTier, nextTier] = key.split('|') as [FocusTier, FocusTier];\n const alpha =\n lerp(edgeTierAlpha(prevTier, dimOpacity), edgeTierAlpha(nextTier, dimOpacity), t) * ea;\n return { alpha, bucket };\n })\n .sort((a, b) => a.alpha - b.alpha);\n\n for (const { alpha, bucket } of ordered) {\n this.drawEdgeGroupBatched(ctx, bucket, alpha, searchMatches, enterAlphaFor);\n }\n\n this.drawHoveredEdge(ctx, hoveredEdge);\n }\n\n /** Draw the hovered edge on top with a thickened highlight stroke. */\n private drawHoveredEdge(ctx: CanvasRenderingContext2D, hoveredEdge: PositionedEdge | null): void {\n if (!hoveredEdge) return;\n const dash = DASH_PATTERNS[hoveredEdge.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = hoveredEdge.stroke;\n ctx.lineWidth = hoveredEdge.strokeWidth * 2;\n ctx.globalAlpha = EDGE_ALPHA_CONNECTED;\n ctx.beginPath();\n ctx.moveTo(hoveredEdge.sourceX, hoveredEdge.sourceY);\n ctx.lineTo(hoveredEdge.targetX, hoveredEdge.targetY);\n ctx.stroke();\n ctx.setLineDash([]);\n ctx.globalAlpha = 1;\n }\n\n /**\n * Draw a group of edges at a given alpha, batched by (stroke, strokeWidth, style).\n * When search is inactive, all edges of the same style are drawn in a single path.\n * When search is active, edges split by search-match status for alpha dimming.\n */\n private drawEdgeGroupBatched(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n alpha: number,\n searchMatches: Set<string> | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n if (edges.length === 0) return;\n\n // Group by visual key: stroke + strokeWidth + style + quantized edge-enter\n // alpha. An edge touching a newly-added node fades with the min of its\n // endpoints' enter alpha (8-bucket quantized so batching stays bounded). When\n // no enter fade is active every edge quantizes to 1 → a single batch (no\n // overhead in the common case).\n const groups = new Map<string, { edges: PositionedEdge[]; enter: number }>();\n for (const edge of edges) {\n const rawEnter = Math.min(enterAlphaFor(edge.source), enterAlphaFor(edge.target));\n const enter = Math.round(rawEnter * 8) / 8;\n const key = `${edge.stroke}|${edge.strokeWidth}|${edge.style}|${enter}`;\n let group = groups.get(key);\n if (!group) {\n group = { edges: [], enter };\n groups.set(key, group);\n }\n group.edges.push(edge);\n }\n\n for (const [, { edges: group, enter }] of groups) {\n const sample = group[0];\n const dash = DASH_PATTERNS[sample.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = sample.stroke;\n ctx.lineWidth = sample.strokeWidth;\n const groupAlpha = alpha * enter;\n\n if (!searchMatches) {\n // Fast path: single batched path for all edges in this group\n ctx.globalAlpha = groupAlpha;\n ctx.beginPath();\n for (const edge of group) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n } else {\n // Search active: split into matched and non-matched batches\n ctx.globalAlpha = groupAlpha;\n ctx.beginPath();\n let hasMatched = false;\n\n const nonMatchPath: PositionedEdge[] = [];\n\n for (const edge of group) {\n const srcMatch = searchMatches.has(edge.source);\n const tgtMatch = searchMatches.has(edge.target);\n if (srcMatch || tgtMatch) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n hasMatched = true;\n } else {\n nonMatchPath.push(edge);\n }\n }\n if (hasMatched) ctx.stroke();\n\n // Draw non-matching edges dimmed\n if (nonMatchPath.length > 0) {\n ctx.globalAlpha = SEARCH_NON_MATCH_ALPHA * groupAlpha;\n ctx.beginPath();\n for (const edge of nonMatchPath) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n }\n }\n }\n\n ctx.setLineDash([]);\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Batched node drawing\n // -------------------------------------------------------------------------\n\n private drawNodesBatched(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n searchMatches: Set<string> | null,\n showGlow: boolean,\n theme: GraphRenderState['theme'],\n minRadius: number,\n nextFocus: FocusSnapshot,\n dimOpacity: number,\n exemptIds: Set<string> | undefined,\n crossfade: { t: number; prev: FocusSnapshot; next: FocusSnapshot } | null,\n hoverRadiusScale: Map<string, number> | undefined,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Effective per-node alpha = focus dim × search dim. Focus dim crossfades\n // between prev/next tiers when a transition is live; otherwise it's the\n // settled next-tier alpha. Batching is preserved by grouping nodes that\n // share (fill, quantized-alpha) — at most 2 focus tiers × 2 search tiers.\n const focusAlpha = (node: PositionedNode): number => {\n if (crossfade) {\n return lerp(\n nodeTierAlpha(nodeTier(node, crossfade.prev, exemptIds), dimOpacity),\n nodeTierAlpha(nodeTier(node, crossfade.next, exemptIds), dimOpacity),\n crossfade.t,\n );\n }\n return nodeTierAlpha(nodeTier(node, nextFocus, exemptIds), dimOpacity);\n };\n const searchAlpha = (node: PositionedNode): number =>\n searchMatches !== null && !searchMatches.has(node.id) ? SEARCH_NON_MATCH_ALPHA : 1;\n // Entrance pop: alpha and scale run separate curves (alpha 0→1, scale\n // 0→overshoot→1). Both derive from the same quantized per-node progress, so\n // the fill/stroke batching keys stay bounded during the reveal.\n const entranceAlpha = (node: PositionedNode): number =>\n entrance ? entrance.nodeAlpha(node) : 1;\n const entranceScale = (node: PositionedNode): number =>\n entrance ? entrance.nodeScale(node) : 1;\n // Convergence drift: nodes pop in slightly outside their final spot and\n // slide home. Zero when the entrance is settled or unstaggered.\n const entranceShift = (node: PositionedNode): { x: number; y: number } =>\n entrance ? entrance.shift(node.id) : ZERO_SHIFT;\n // Data-update enter fade: newly-added nodes ramp 0→1 (already bucket-quantized\n // by the mount), preserving fill/stroke batching keys.\n const effectiveAlpha = (node: PositionedNode): number =>\n focusAlpha(node) * searchAlpha(node) * entranceAlpha(node) * enterAlphaFor(node.id);\n\n // Separate special nodes (hovered/selected, or mid radius-tween) from bulk.\n const bulkNodes: PositionedNode[] = [];\n const specialNodes: PositionedNode[] = [];\n\n for (const node of nodes) {\n if (\n node.id === hoveredNodeId ||\n selectedNodeIds.has(node.id) ||\n (hoverRadiusScale?.has(node.id) ?? false)\n ) {\n specialNodes.push(node);\n } else {\n bulkNodes.push(node);\n }\n }\n\n // Helper: effective radius clamped to minimum screen size, then scaled by\n // the entrance pop curve.\n const r = (node: PositionedNode) => Math.max(node.radius, minRadius) * entranceScale(node);\n\n // --- Glow pass (dark mode only, before fills) ---\n // Skipped mid-entrance: the glow draws at final positions and would visibly\n // detach from nodes still on their convergence drift.\n if (showGlow && !entrance) {\n this.drawGlowBatched(ctx, bulkNodes, searchMatches, minRadius);\n }\n\n // --- Bulk fill pass: batch by (fill color, quantized alpha) ---\n const fillGroups = new Map<string, { fill: string; alpha: number; nodes: PositionedNode[] }>();\n for (const node of bulkNodes) {\n const alpha = effectiveAlpha(node);\n const key = `${node.fill}|${alpha.toFixed(3)}`;\n let group = fillGroups.get(key);\n if (!group) {\n group = { fill: node.fill, alpha, nodes: [] };\n fillGroups.set(key, group);\n }\n group.nodes.push(node);\n }\n\n for (const { fill, alpha, nodes: group } of fillGroups.values()) {\n ctx.fillStyle = fill;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n const nr = r(node);\n if (nr <= 0) continue;\n const s = entranceShift(node);\n ctx.moveTo(node.x + s.x + nr, node.y + s.y);\n ctx.arc(node.x + s.x, node.y + s.y, nr, 0, TWO_PI);\n }\n ctx.fill();\n }\n\n // --- Bulk stroke pass: batch by (stroke color+width, quantized alpha) ---\n const strokeGroups = new Map<\n string,\n { stroke: string; width: number; alpha: number; nodes: PositionedNode[] }\n >();\n for (const node of bulkNodes) {\n const alpha = effectiveAlpha(node);\n const key = `${node.stroke}|${node.strokeWidth}|${alpha.toFixed(3)}`;\n let group = strokeGroups.get(key);\n if (!group) {\n group = { stroke: node.stroke, width: node.strokeWidth, alpha, nodes: [] };\n strokeGroups.set(key, group);\n }\n group.nodes.push(node);\n }\n\n for (const { stroke, width, alpha, nodes: group } of strokeGroups.values()) {\n ctx.strokeStyle = stroke;\n ctx.lineWidth = width;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n const nr = r(node);\n if (nr <= 0) continue;\n const s = entranceShift(node);\n ctx.moveTo(node.x + s.x + nr, node.y + s.y);\n ctx.arc(node.x + s.x, node.y + s.y, nr, 0, TWO_PI);\n }\n ctx.stroke();\n }\n\n // --- Special nodes (hovered/selected) drawn individually ---\n for (const node of specialNodes) {\n const isHovered = node.id === hoveredNodeId;\n const isSelected = selectedNodeIds.has(node.id);\n const dimmed = searchMatches !== null && !searchMatches.has(node.id);\n const baseRadius = Math.max(node.radius, minRadius);\n // Hover radius tween: scale routes through here until it returns to 1.\n const hoverScale = hoverRadiusScale?.get(node.id) ?? (isHovered ? 1.15 : 1);\n const radius = baseRadius * hoverScale;\n // brighten() switches at the scale midpoint (>1.075 of the 1→1.15 range).\n const brightened = isHovered && hoverScale >= 1.075;\n const s = entranceShift(node);\n const nx = node.x + s.x;\n const ny = node.y + s.y;\n\n ctx.globalAlpha = dimmed ? SEARCH_NON_MATCH_ALPHA : 1;\n\n // Glow for special nodes\n if (showGlow && !dimmed) {\n ctx.beginPath();\n ctx.arc(nx, ny, radius * GLOW_RADIUS_MULTIPLIER, 0, TWO_PI);\n ctx.fillStyle = node.fill;\n ctx.globalAlpha = GLOW_ALPHA;\n ctx.fill();\n ctx.globalAlpha = dimmed ? SEARCH_NON_MATCH_ALPHA : 1;\n }\n\n // Fill\n ctx.beginPath();\n ctx.arc(nx, ny, radius, 0, TWO_PI);\n ctx.fillStyle = brightened ? brighten(node.fill) : node.fill;\n ctx.fill();\n\n // Stroke\n ctx.strokeStyle = node.stroke;\n ctx.lineWidth = node.strokeWidth;\n ctx.stroke();\n\n // Selection ring\n if (isSelected) {\n ctx.beginPath();\n ctx.arc(nx, ny, radius + 3, 0, TWO_PI);\n ctx.strokeStyle = theme.colors.categorical[0] ?? '#3b82f6';\n ctx.lineWidth = 2;\n ctx.stroke();\n }\n }\n\n ctx.globalAlpha = 1;\n }\n\n /** Batch glow circles by fill color. */\n private drawGlowBatched(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n searchMatches: Set<string> | null,\n minRadius: number,\n ): void {\n const glowGroups = new Map<string, PositionedNode[]>();\n for (const node of nodes) {\n if (searchMatches && !searchMatches.has(node.id)) continue;\n let group = glowGroups.get(node.fill);\n if (!group) {\n group = [];\n glowGroups.set(node.fill, group);\n }\n group.push(node);\n }\n\n ctx.globalAlpha = GLOW_ALPHA;\n for (const [fill, group] of glowGroups) {\n ctx.fillStyle = fill;\n ctx.beginPath();\n for (const node of group) {\n const gr = Math.max(node.radius, minRadius) * GLOW_RADIUS_MULTIPLIER;\n ctx.moveTo(node.x + gr, node.y);\n ctx.arc(node.x, node.y, gr, 0, TWO_PI);\n }\n ctx.fill();\n }\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Labels (drawn individually, skipped during gestures)\n // -------------------------------------------------------------------------\n\n private drawLabels(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n threshold: number,\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n searchMatches: Set<string> | null,\n zoom: number,\n theme: GraphRenderState['theme'],\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Labels fade in with the raw entrance progress (× on top of dim alpha).\n const la = entrance ? entrance.labelAlpha : 1;\n // Font size inversely scaled by zoom, clamped to readable range\n const rawSize = 10 / zoom;\n const fontSize = Math.max(LABEL_FONT_MIN, Math.min(LABEL_FONT_MAX, rawSize));\n\n ctx.font = `${fontSize}px ${theme.fonts.family}`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n\n for (const node of nodes) {\n if (!node.label) continue;\n\n const isHovered = node.id === hoveredNodeId;\n const isSelected = selectedNodeIds.has(node.id);\n const forced = isHovered || isSelected;\n const dimmed = searchMatches !== null && !searchMatches.has(node.id);\n\n // LOD: skip labels below threshold unless forced\n if (!forced && node.labelPriority < threshold) continue;\n\n ctx.globalAlpha = (dimmed ? SEARCH_NON_MATCH_ALPHA : 1) * la * enterAlphaFor(node.id);\n\n const labelY = node.y + node.radius + 3;\n\n // Halo for readability: stroke behind text in the background color\n // so labels stay legible over edges and other nodes.\n if (theme.colors.background !== 'transparent') {\n ctx.strokeStyle = theme.colors.background;\n } else {\n // Transparent bg inherits its mode from the theme's darkMode flag, not\n // from text luminance. Dark mode = dark page = dark halo behind light\n // text; light mode = light page = light halo.\n ctx.strokeStyle = theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.85)';\n }\n ctx.lineWidth = 3;\n ctx.lineJoin = 'round';\n ctx.miterLimit = 2;\n ctx.strokeText(node.label, node.x, labelY);\n\n ctx.fillStyle = theme.colors.text;\n ctx.fillText(node.label, node.x, labelY);\n }\n\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Exit ghosts (Phase 7)\n // -------------------------------------------------------------------------\n\n /**\n * Draw exit ghosts (removed nodes/edges) UNDER the live marks at a global fade\n * alpha. Neutral rendering: no focus dim, no search dim, no selection ring —\n * they're on their way out. Batched by (stroke/style) for edges and (fill) for\n * nodes, culled to the visible rect.\n */\n private drawGhosts(\n ctx: CanvasRenderingContext2D,\n exiting: NonNullable<GraphRenderState['exiting']>,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n ): void {\n const alpha = exiting.alpha;\n\n // Ghost edges: batch by (stroke, strokeWidth, style).\n const edgeGroups = new Map<string, PositionedEdge[]>();\n for (const edge of exiting.edges) {\n if (!edgeInView(edge, rect)) continue;\n const key = `${edge.stroke}|${edge.strokeWidth}|${edge.style}`;\n const group = edgeGroups.get(key);\n if (group) group.push(edge);\n else edgeGroups.set(key, [edge]);\n }\n for (const [, group] of edgeGroups) {\n const sample = group[0];\n const dash = DASH_PATTERNS[sample.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = sample.stroke;\n ctx.lineWidth = sample.strokeWidth;\n ctx.globalAlpha = EDGE_ALPHA_DEFAULT * alpha;\n ctx.beginPath();\n for (const edge of group) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n }\n ctx.setLineDash([]);\n\n // Ghost nodes: batch by fill color.\n const nodeGroups = new Map<string, PositionedNode[]>();\n for (const node of exiting.nodes) {\n if (!nodeInView(node, rect)) continue;\n const group = nodeGroups.get(node.fill);\n if (group) group.push(node);\n else nodeGroups.set(node.fill, [node]);\n }\n for (const [fill, group] of nodeGroups) {\n ctx.fillStyle = fill;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n ctx.moveTo(node.x + node.radius, node.y);\n ctx.arc(node.x, node.y, node.radius, 0, TWO_PI);\n }\n ctx.fill();\n }\n\n ctx.globalAlpha = 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Color helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Brighten a hex/rgb color by ~20% for hover effect.\n * Quick and dirty approach: parse hex, lighten each channel.\n */\nfunction brighten(color: string): string {\n // Handle rgb(r,g,b) or rgb(r, g, b)\n const rgbMatch = color.match(/^rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n if (rgbMatch) {\n const r = Math.min(255, parseInt(rgbMatch[1], 10) + 40);\n const g = Math.min(255, parseInt(rgbMatch[2], 10) + 40);\n const b = Math.min(255, parseInt(rgbMatch[3], 10) + 40);\n return `rgb(${r},${g},${b})`;\n }\n\n // Handle hex colors (#rgb and #rrggbb)\n const hex = color.replace('#', '');\n const full =\n hex.length === 3\n ? hex\n .split('')\n .map((c) => c + c)\n .join('')\n : hex;\n\n if (full.length === 6) {\n const r = Math.min(255, parseInt(full.slice(0, 2), 16) + 40);\n const g = Math.min(255, parseInt(full.slice(2, 4), 16) + 40);\n const b = Math.min(255, parseInt(full.slice(4, 6), 16) + 40);\n return `rgb(${r},${g},${b})`;\n }\n\n return color;\n}\n","/**\n * Entrance choreography math for the graph reveal.\n *\n * The mount drives a single 0→1 `entranceProgress`; this module turns it into a\n * per-node reveal alpha with a staggered start. The per-node alpha is quantized\n * to a small number of levels so the canvas renderer can still batch fills by\n * (color, alpha) — an unquantized stagger would force one fill() per node.\n *\n * Above ENTRANCE_STAGGER_MAX_NODES the mount uses a single global fade (no\n * per-node stagger) because thousands of distinct start times defeat batching\n * and read as noise anyway.\n */\n\n/**\n * Per-node reveal progress at global progress `globalT`.\n *\n * Node `index` starts revealing at `(index/total)·0.4` and ramps over the next\n * `0.6` of the timeline, so the first node is fully in at t=0.6 and the last at\n * t=1.0. The result is quantized to `buckets` (≤8) levels to preserve batching.\n */\nexport function nodeEnterProgress(\n globalT: number,\n index: number,\n total: number,\n buckets = 8,\n): number {\n const g = globalT <= 0 ? 0 : globalT >= 1 ? 1 : globalT;\n const start = total > 0 ? (index / total) * 0.4 : 0;\n const local = (g - start) / 0.6;\n const clamped = local <= 0 ? 0 : local >= 1 ? 1 : local;\n // Quantize to `buckets` levels (0, 1/b, ..., 1) so fills stay batchable.\n const b = Math.max(1, Math.floor(buckets));\n return Math.round(clamped * b) / b;\n}\n\n/**\n * Above this node count the entrance uses a single global fade with no per-node\n * stagger — thousands of start times kill batching and read as noise.\n */\nexport const ENTRANCE_STAGGER_MAX_NODES = 3000;\n\n// ---------------------------------------------------------------------------\n// Organic pop: order, scale curve, and convergence drift\n// ---------------------------------------------------------------------------\n\n/** How far (px, graph space) a node starts from its final spot during the pop. */\nexport const ENTRANCE_DRIFT_PX = 16;\n\ntype XYNode = { id: string; x: number; y: number };\n\nfunction centroid(nodes: XYNode[]): { cx: number; cy: number } {\n let cx = 0;\n let cy = 0;\n for (const n of nodes) {\n cx += n.x;\n cy += n.y;\n }\n cx /= nodes.length;\n cy /= nodes.length;\n return { cx, cy };\n}\n\n/**\n * Deterministic hash of a node id to a well-distributed 32-bit unsigned int\n * (FNV-1a). Used to scatter entrance timing: same id → same slot every render,\n * so the reveal stays stable and testable without a spatial or index bias.\n */\nfunction hashId(id: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < id.length; i++) {\n h ^= id.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n/**\n * Stagger rank for each node: a deterministic hash-shuffle of the node ids, so\n * each node reveals at an independent, scattered time rather than in a uniform\n * center-outward ripple. The scatter reads as many independent elements popping\n * in; a spatial (centroid-radial) or index order reads as one coordinated sweep.\n * Hashing the id keeps the order stable per render (pure function of ids), so it\n * survives re-renders and stays testable.\n */\nexport function entranceOrder(nodes: XYNode[]): Map<string, number> {\n const rank = new Map<string, number>();\n if (nodes.length === 0) return rank;\n const sorted = [...nodes].sort((a, b) => {\n const ha = hashId(a.id);\n const hb = hashId(b.id);\n // Tie-break on id so equal hashes (rare) still give a total, stable order.\n return ha - hb || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\n });\n for (let i = 0; i < sorted.length; i++) rank.set(sorted[i].id, i);\n return rank;\n}\n\n/**\n * Per-node drift offset: a unit vector pointing away from the layout centroid,\n * scaled to ENTRANCE_DRIFT_PX. Nodes pop in slightly outside their final spot\n * and converge inward. Deterministic (pure function of positions), zero-safe\n * at the centroid.\n */\nexport function entranceOffsets(\n nodes: XYNode[],\n dist: number = ENTRANCE_DRIFT_PX,\n): Map<string, { x: number; y: number }> {\n const offsets = new Map<string, { x: number; y: number }>();\n if (nodes.length === 0) return offsets;\n const { cx, cy } = centroid(nodes);\n for (const n of nodes) {\n const dx = n.x - cx;\n const dy = n.y - cy;\n const len = Math.sqrt(dx * dx + dy * dy);\n if (len < 1e-6) {\n offsets.set(n.id, { x: 0, y: -dist });\n } else {\n offsets.set(n.id, { x: (dx / len) * dist, y: (dy / len) * dist });\n }\n }\n return offsets;\n}\n\n/**\n * Pop scale curve: 0 at t=0, overshoots to ~1.1 around t≈0.7, settles at 1.\n * Standard back-out easing applied to scale, so nodes pop rather than fade.\n */\nexport function popScale(t: number): number {\n if (t <= 0) return 0;\n if (t >= 1) return 1;\n const c1 = 1.70158;\n const c3 = c1 + 1;\n const u = t - 1;\n return 1 + c3 * u * u * u + c1 * u * u;\n}\n\n/**\n * Pop alpha curve: reaches full opacity by 60% of the node's window so the\n * overshoot phase of the scale pop is fully visible, not half-faded.\n */\nexport function popAlpha(t: number): number {\n const a = t / 0.6;\n return a >= 1 ? 1 : a <= 0 ? 0 : a;\n}\n\n/**\n * Drift factor: 1 at t=0 (full offset from final position) easing quadratically\n * to 0 (node at rest). Multiplied into the per-node entrance offset.\n */\nexport function driftFactor(t: number): number {\n const u = 1 - (t <= 0 ? 0 : t >= 1 ? 1 : t);\n return u * u;\n}\n","/**\n * Per-frame focus model for the graph: unifies the three emphasis sources\n * (programmatic highlight, search matches, hover-neighborhood) into a single\n * snapshot pair that the renderer crossfades between.\n *\n * Composition, not strict precedence (see plan §5a):\n * - Standing state = highlight ∩ search when both active; if that intersection\n * is empty, search matches win (fresher user intent). This preserves the\n * \"filter by topic, then search within it\" workflow.\n * - Hover-neighborhood is the transient top layer over whatever the standing\n * state is.\n *\n * The transition tweens between two discrete FocusSnapshots. Rapid hover sweeps\n * retarget mid-flight: `retarget(next, now)` snapshots the endpoint CLOSEST to\n * the current display (`prev = p < 0.5 ? old prev : old next`), so a low-p\n * retarget keeps the old prev — no forward snap, worst-case visual jump halved\n * vs. a naive \"always start from current next\" rule. Exact per-edge capture\n * would destroy the renderer's tier batching, so this discrete approximation is\n * deliberate.\n */\n\n/** The set of emphasis relationships in effect for one steady state. */\nexport interface FocusSnapshot {\n /** True when any emphasis source is active (something is dimmed). */\n hasActive: boolean;\n /** Nodes connected to the active/hovered set (includes the active nodes). */\n connected: Set<string>;\n /** Search matches, or null when search is inactive. */\n searchMatches: Set<string> | null;\n /** Selected nodes (selection rings; always emphasized). */\n selected: Set<string>;\n}\n\n/** An empty (nothing emphasized) snapshot. */\nexport function emptyFocusSnapshot(): FocusSnapshot {\n return { hasActive: false, connected: new Set(), searchMatches: null, selected: new Set() };\n}\n\n/** Whether two snapshots describe the same emphasis state (skip re-tween). */\nexport function focusSnapshotsEqual(a: FocusSnapshot, b: FocusSnapshot): boolean {\n return (\n a.hasActive === b.hasActive &&\n setsEqual(a.connected, b.connected) &&\n nullableSetsEqual(a.searchMatches, b.searchMatches) &&\n setsEqual(a.selected, b.selected)\n );\n}\n\nfunction setsEqual(a: Set<string>, b: Set<string>): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction nullableSetsEqual(a: Set<string> | null, b: Set<string> | null): boolean {\n if (a === null || b === null) return a === b;\n return setsEqual(a, b);\n}\n\n/**\n * A crossfade between two focus snapshots, eased over `duration` ms.\n *\n * Time is injected (`now` passed to `retarget`/`progress`) so the mount's\n * scheduler drives it deterministically and tests use a controllable clock.\n */\nexport class FocusTransition {\n prev: FocusSnapshot;\n next: FocusSnapshot;\n private startTime: number;\n private duration: number;\n private ease: (t: number) => number;\n\n constructor(initial: FocusSnapshot, duration: number, ease: (t: number) => number, now: number) {\n this.prev = initial;\n this.next = initial;\n this.startTime = now;\n this.duration = Math.max(0, duration);\n this.ease = ease;\n }\n\n /**\n * Point a fresh transition at `target`, capturing the endpoint closest to the\n * current display as the new `prev` (the `p < 0.5` rule). A no-op when\n * `target` already equals `next`.\n */\n retarget(target: FocusSnapshot, now: number): void {\n if (focusSnapshotsEqual(target, this.next)) return;\n const p = this.rawProgress(now);\n // Below the midpoint the display is still nearer the old prev; keep it to\n // avoid a forward snap. At/after the midpoint the old next is nearer.\n this.prev = p < 0.5 ? this.prev : this.next;\n this.next = target;\n this.startTime = now;\n }\n\n /** Raw (un-eased) 0..1 progress. */\n private rawProgress(now: number): number {\n if (this.duration <= 0) return 1;\n return Math.min(1, Math.max(0, (now - this.startTime) / this.duration));\n }\n\n /** Eased 0..1 progress toward `next`. */\n progress(now: number): number {\n return this.ease(this.rawProgress(now));\n }\n\n /** True once the transition has fully settled onto `next`. */\n isSettled(now: number): boolean {\n return this.rawProgress(now) >= 1;\n }\n}\n\n/**\n * Build the standing (non-hover) focus snapshot from highlight + search sets.\n *\n * Composition rule: emphasized = highlight ∩ search when both non-empty; if the\n * intersection is empty, search wins. Selection is always carried through.\n */\nexport function composeStandingFocus(\n highlight: Set<string> | null,\n searchMatches: Set<string> | null,\n selected: Set<string>,\n adjacency: Map<string, Set<string>>,\n): FocusSnapshot {\n const hasHighlight = highlight !== null && highlight.size > 0;\n const hasSearch = searchMatches !== null && searchMatches.size > 0;\n\n // Determine the emphasized \"core\" set that drives connected-neighborhood dim.\n let core: Set<string> | null = null;\n if (highlight !== null && hasHighlight && searchMatches !== null && hasSearch) {\n const inter = intersect(highlight, searchMatches);\n core = inter.size > 0 ? inter : searchMatches;\n } else if (hasHighlight) {\n core = highlight;\n } else if (hasSearch) {\n // Search dims via searchMatches directly (renderer's search path), not the\n // connected-neighborhood path — leave core null so hover still works.\n core = null;\n }\n\n const connected = new Set<string>();\n if (core) {\n for (const id of core) {\n connected.add(id);\n const neighbors = adjacency.get(id);\n if (neighbors) for (const nid of neighbors) connected.add(nid);\n }\n }\n\n const hasActive = (core !== null && core.size > 0) || hasSearch || selected.size > 0;\n\n return {\n hasActive,\n connected,\n searchMatches: hasSearch ? searchMatches : null,\n selected,\n };\n}\n\n/**\n * Layer a hover neighborhood on top of a standing snapshot. The hovered node\n * and its neighbors become the connected set; the standing search/selection\n * carry through so search dimming and selection rings persist under hover.\n */\nexport function layerHoverFocus(\n standing: FocusSnapshot,\n hoveredId: string | null,\n hoverConnected: Set<string> | null,\n): FocusSnapshot {\n if (hoveredId === null || hoverConnected === null) return standing;\n return {\n hasActive: true,\n connected: hoverConnected,\n searchMatches: standing.searchMatches,\n selected: standing.selected,\n };\n}\n\nfunction intersect(a: Set<string>, b: Set<string>): Set<string> {\n const [small, large] = a.size <= b.size ? [a, b] : [b, a];\n const out = new Set<string>();\n for (const v of small) if (large.has(v)) out.add(v);\n return out;\n}\n","/**\n * Graph interaction manager.\n *\n * Handles mouse/touch events on the canvas and translates them into\n * high-level graph interactions: pan, zoom, hover, select, drag nodes.\n * Uses the spatial index for hit testing and ZoomTransform for coordinate\n * conversion.\n */\n\nimport type { SpatialIndex } from './spatial-index';\nimport { ZoomTransform } from './zoom';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst ZOOM_MIN = 0.05;\nconst ZOOM_MAX = 15;\nconst ZOOM_STEP = -0.001;\nconst HIT_DISTANCE = 5;\n\n// ---------------------------------------------------------------------------\n// Callback interface\n// ---------------------------------------------------------------------------\n\nexport interface InteractionCallbacks {\n onTransformChange(transform: ZoomTransform): void;\n onHoverChange(nodeId: string | null): void;\n /** Called during mouse move when no node is hit, with graph-space coordinates for edge hit testing. */\n onBackgroundHover?(graphX: number, graphY: number, screenX: number, screenY: number): void;\n onSelectionChange(nodeIds: string[]): void;\n onNodeDragStart(nodeId: string): void;\n onNodeDrag(nodeId: string, x: number, y: number): void;\n onNodeDragEnd(nodeId: string): void;\n onDoubleClick(nodeId: string): void;\n /** Pointer moved over the canvas (graph-space coords). Used by cursor repulsion. */\n onPointerMove?(graphX: number, graphY: number): void;\n /** Pointer left the canvas. Used to deactivate cursor repulsion. */\n onPointerLeave?(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\ninterface DragState {\n nodeId: string;\n started: boolean;\n}\n\ninterface PanState {\n startX: number;\n startY: number;\n}\n\n// ---------------------------------------------------------------------------\n// GraphInteractionManager\n// ---------------------------------------------------------------------------\n\nexport class GraphInteractionManager {\n private canvas: HTMLCanvasElement;\n private spatialIndex: SpatialIndex;\n private callbacks: InteractionCallbacks;\n private transform = ZoomTransform.identity();\n\n private dragState: DragState | null = null;\n private panState: PanState | null = null;\n private mousedownNodeId: string | null = null;\n private selectedIds: Set<string> = new Set();\n\n // Touch state\n private lastTouchDist: number | null = null;\n private lastTouchCenter: { x: number; y: number } | null = null;\n\n // Bound handlers for cleanup\n private boundWheel: (e: WheelEvent) => void;\n private boundMouseDown: (e: MouseEvent) => void;\n private boundMouseMove: (e: MouseEvent) => void;\n private boundMouseUp: (e: MouseEvent) => void;\n private boundDblClick: (e: MouseEvent) => void;\n private boundTouchStart: (e: TouchEvent) => void;\n private boundTouchMove: (e: TouchEvent) => void;\n private boundTouchEnd: (e: TouchEvent) => void;\n private boundMouseLeave: (e: MouseEvent) => void;\n\n constructor(\n canvas: HTMLCanvasElement,\n spatialIndex: SpatialIndex,\n callbacks: InteractionCallbacks,\n ) {\n this.canvas = canvas;\n this.spatialIndex = spatialIndex;\n this.callbacks = callbacks;\n\n // Bind handlers\n this.boundWheel = this.onWheel.bind(this);\n this.boundMouseDown = this.onMouseDown.bind(this);\n this.boundMouseMove = this.onMouseMove.bind(this);\n this.boundMouseUp = this.onMouseUp.bind(this);\n this.boundMouseLeave = this.onMouseLeave.bind(this);\n this.boundDblClick = this.onDblClick.bind(this);\n this.boundTouchStart = this.onTouchStart.bind(this);\n this.boundTouchMove = this.onTouchMove.bind(this);\n this.boundTouchEnd = this.onTouchEnd.bind(this);\n\n // Attach event listeners\n canvas.addEventListener('wheel', this.boundWheel, { passive: false });\n canvas.addEventListener('mousedown', this.boundMouseDown);\n canvas.addEventListener('mousemove', this.boundMouseMove);\n canvas.addEventListener('mouseup', this.boundMouseUp);\n canvas.addEventListener('mouseleave', this.boundMouseLeave);\n canvas.addEventListener('dblclick', this.boundDblClick);\n canvas.addEventListener('touchstart', this.boundTouchStart, {\n passive: false,\n });\n canvas.addEventListener('touchmove', this.boundTouchMove, {\n passive: false,\n });\n canvas.addEventListener('touchend', this.boundTouchEnd);\n }\n\n setTransform(transform: ZoomTransform): void {\n this.transform = transform;\n }\n\n getTransform(): ZoomTransform {\n return this.transform;\n }\n\n /**\n * Replace the internal selection set. Used by the mount to prune deleted ids\n * after a data update so a later shift-click can't resurrect them through\n * `onSelectionChange`. Does NOT fire `onSelectionChange` — the caller owns any\n * downstream sync (the mount already holds the pruned set).\n */\n setSelection(ids: string[]): void {\n this.selectedIds = new Set(ids);\n }\n\n destroy(): void {\n this.canvas.removeEventListener('wheel', this.boundWheel);\n this.canvas.removeEventListener('mousedown', this.boundMouseDown);\n this.canvas.removeEventListener('mousemove', this.boundMouseMove);\n this.canvas.removeEventListener('mouseup', this.boundMouseUp);\n this.canvas.removeEventListener('mouseleave', this.boundMouseLeave);\n this.canvas.removeEventListener('dblclick', this.boundDblClick);\n this.canvas.removeEventListener('touchstart', this.boundTouchStart);\n this.canvas.removeEventListener('touchmove', this.boundTouchMove);\n this.canvas.removeEventListener('touchend', this.boundTouchEnd);\n }\n\n // -------------------------------------------------------------------------\n // Mouse handlers\n // -------------------------------------------------------------------------\n\n private canvasXY(e: MouseEvent): { x: number; y: number } {\n const rect = this.canvas.getBoundingClientRect();\n return { x: e.clientX - rect.left, y: e.clientY - rect.top };\n }\n\n private hitTest(screenX: number, screenY: number): string | null {\n const graph = this.transform.screenToGraph(screenX, screenY);\n const node = this.spatialIndex.findNearest(graph.x, graph.y, HIT_DISTANCE / this.transform.k);\n return node?.id ?? null;\n }\n\n private onWheel(e: WheelEvent): void {\n e.preventDefault();\n const { x, y } = this.canvasXY(e);\n const factor = e.deltaY * ZOOM_STEP;\n const newK = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, this.transform.k * (1 + factor)));\n this.transform = this.transform.zoomAt(newK, x, y);\n this.callbacks.onTransformChange(this.transform);\n }\n\n private onMouseDown(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n const hitId = this.hitTest(x, y);\n\n if (hitId) {\n // Start potential node drag\n this.dragState = { nodeId: hitId, started: false };\n this.mousedownNodeId = hitId;\n } else {\n // Start pan\n this.panState = { startX: x, startY: y };\n this.mousedownNodeId = null;\n }\n }\n\n private onMouseMove(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n\n // Feed the pointer position (graph-space) to cursor repulsion, if wired.\n // Fires on every move; the mount owns throttling and the node-count gate.\n if (this.callbacks.onPointerMove) {\n const gp = this.transform.screenToGraph(x, y);\n this.callbacks.onPointerMove(gp.x, gp.y);\n }\n\n if (this.dragState) {\n const graph = this.transform.screenToGraph(x, y);\n if (!this.dragState.started) {\n this.dragState.started = true;\n this.callbacks.onNodeDragStart(this.dragState.nodeId);\n }\n this.callbacks.onNodeDrag(this.dragState.nodeId, graph.x, graph.y);\n return;\n }\n\n if (this.panState) {\n const dx = x - this.panState.startX;\n const dy = y - this.panState.startY;\n this.transform = this.transform.pan(dx, dy);\n this.panState = { startX: x, startY: y };\n this.callbacks.onTransformChange(this.transform);\n return;\n }\n\n // Hover detection\n const hitId = this.hitTest(x, y);\n this.callbacks.onHoverChange(hitId);\n\n // If no node hit, check edges via callback\n if (!hitId) {\n const graph = this.transform.screenToGraph(x, y);\n this.callbacks.onBackgroundHover?.(graph.x, graph.y, x, y);\n }\n\n // Update cursor\n this.canvas.style.cursor = hitId ? 'pointer' : 'default';\n }\n\n private onMouseUp(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n\n if (this.dragState) {\n if (this.dragState.started) {\n this.callbacks.onNodeDragEnd(this.dragState.nodeId);\n } else {\n // Was a click on a node (no drag movement)\n this.handleNodeClick(this.dragState.nodeId, e.shiftKey);\n }\n this.dragState = null;\n return;\n }\n\n if (this.panState) {\n this.panState = null;\n\n // If mouse up is on background (no node), treat as background click\n if (!this.mousedownNodeId) {\n const hitId = this.hitTest(x, y);\n if (!hitId) {\n // Background click: clear selection\n this.selectedIds.clear();\n this.callbacks.onSelectionChange([]);\n }\n }\n return;\n }\n }\n\n private onDblClick(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n const hitId = this.hitTest(x, y);\n if (hitId) {\n this.callbacks.onDoubleClick(hitId);\n }\n }\n\n private onMouseLeave(_e: MouseEvent): void {\n this.callbacks.onHoverChange(null);\n this.canvas.style.cursor = 'default';\n // Deactivate cursor repulsion when the pointer leaves the canvas.\n this.callbacks.onPointerLeave?.();\n\n // Cancel any in-progress pan\n if (this.panState) {\n this.panState = null;\n }\n }\n\n private handleNodeClick(nodeId: string, shiftKey: boolean): void {\n if (shiftKey) {\n // Toggle node in multi-select\n if (this.selectedIds.has(nodeId)) {\n this.selectedIds.delete(nodeId);\n } else {\n this.selectedIds.add(nodeId);\n }\n } else {\n // Single select\n this.selectedIds.clear();\n this.selectedIds.add(nodeId);\n }\n\n this.callbacks.onSelectionChange([...this.selectedIds]);\n }\n\n // -------------------------------------------------------------------------\n // Touch handlers\n // -------------------------------------------------------------------------\n\n private onTouchStart(e: TouchEvent): void {\n e.preventDefault();\n\n if (e.touches.length === 2) {\n // Pinch-zoom start\n const [t0, t1] = [e.touches[0], e.touches[1]];\n this.lastTouchDist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);\n this.lastTouchCenter = {\n x: (t0.clientX + t1.clientX) / 2,\n y: (t0.clientY + t1.clientY) / 2,\n };\n } else if (e.touches.length === 1) {\n const touch = e.touches[0];\n const rect = this.canvas.getBoundingClientRect();\n const x = touch.clientX - rect.left;\n const y = touch.clientY - rect.top;\n\n const hitId = this.hitTest(x, y);\n if (hitId) {\n this.mousedownNodeId = hitId;\n } else {\n this.panState = { startX: x, startY: y };\n this.mousedownNodeId = null;\n }\n }\n }\n\n private onTouchMove(e: TouchEvent): void {\n e.preventDefault();\n\n if (e.touches.length === 2 && this.lastTouchDist !== null) {\n const [t0, t1] = [e.touches[0], e.touches[1]];\n const newDist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);\n const rect = this.canvas.getBoundingClientRect();\n const centerX = (t0.clientX + t1.clientX) / 2 - rect.left;\n const centerY = (t0.clientY + t1.clientY) / 2 - rect.top;\n\n const scale = newDist / this.lastTouchDist;\n const newK = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, this.transform.k * scale));\n this.transform = this.transform.zoomAt(newK, centerX, centerY);\n\n // Pan from center movement\n if (this.lastTouchCenter) {\n const dx = centerX - (this.lastTouchCenter.x - rect.left);\n const dy = centerY - (this.lastTouchCenter.y - rect.top);\n this.transform = this.transform.pan(dx, dy);\n }\n\n this.lastTouchDist = newDist;\n this.lastTouchCenter = {\n x: (t0.clientX + t1.clientX) / 2,\n y: (t0.clientY + t1.clientY) / 2,\n };\n this.callbacks.onTransformChange(this.transform);\n } else if (e.touches.length === 1 && this.panState) {\n const touch = e.touches[0];\n const rect = this.canvas.getBoundingClientRect();\n const x = touch.clientX - rect.left;\n const y = touch.clientY - rect.top;\n\n const dx = x - this.panState.startX;\n const dy = y - this.panState.startY;\n this.transform = this.transform.pan(dx, dy);\n this.panState = { startX: x, startY: y };\n this.callbacks.onTransformChange(this.transform);\n }\n }\n\n private onTouchEnd(e: TouchEvent): void {\n if (e.touches.length === 0) {\n // Tap-select\n if (this.mousedownNodeId && !this.panState) {\n this.handleNodeClick(this.mousedownNodeId, false);\n } else if (!this.mousedownNodeId && this.panState) {\n // Background tap: clear selection\n this.selectedIds.clear();\n this.callbacks.onSelectionChange([]);\n }\n\n this.panState = null;\n this.mousedownNodeId = null;\n this.lastTouchDist = null;\n this.lastTouchCenter = null;\n }\n }\n}\n","/**\n * Keyboard navigation for the graph canvas.\n *\n * Provides accessible keyboard control: Tab to focus, arrow keys to\n * navigate between adjacent nodes (following edges), Enter to select,\n * Escape to clear, +/- to zoom, Home to fit all, / to focus search.\n */\n\nimport type { PositionedNode } from './types';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface KeyboardNavOptions {\n canvas: HTMLCanvasElement;\n getNodes(): PositionedNode[];\n getSelectedIds(): string[];\n getAdjacency(): Map<string, Set<string>>;\n onSelect(nodeId: string): void;\n onDeselect(): void;\n onZoom(direction: 'in' | 'out'): void;\n onFitAll(): void;\n onFocusSearch?(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Attach keyboard navigation to a graph canvas.\n * Returns a cleanup function that removes all listeners.\n */\nexport function attachGraphKeyboardNav(options: KeyboardNavOptions): () => void {\n const {\n canvas,\n getNodes,\n getSelectedIds,\n getAdjacency,\n onSelect,\n onDeselect,\n onZoom,\n onFitAll,\n onFocusSearch,\n } = options;\n\n let focusedNodeId: string | null = null;\n\n // Make canvas focusable\n if (!canvas.hasAttribute('tabindex')) {\n canvas.setAttribute('tabindex', '0');\n }\n\n function findNodeById(id: string): PositionedNode | undefined {\n return getNodes().find((n) => n.id === id);\n }\n\n /**\n * Given a set of neighbor node ids, pick the one that best matches\n * the arrow key direction relative to the current focused node.\n */\n function pickDirectionalNeighbor(\n fromNode: PositionedNode,\n neighborIds: Set<string>,\n direction: 'up' | 'down' | 'left' | 'right',\n ): string | null {\n const nodes = getNodes();\n const candidates = nodes.filter((n) => neighborIds.has(n.id));\n if (candidates.length === 0) return null;\n\n // Score each candidate by how well it matches the desired direction\n let best: PositionedNode | null = null;\n let bestScore = -Infinity;\n\n for (const c of candidates) {\n const dx = c.x - fromNode.x;\n const dy = c.y - fromNode.y;\n let score: number;\n\n switch (direction) {\n case 'right':\n score = dx - Math.abs(dy) * 0.5;\n break;\n case 'left':\n score = -dx - Math.abs(dy) * 0.5;\n break;\n case 'down':\n score = dy - Math.abs(dx) * 0.5;\n break;\n case 'up':\n score = -dy - Math.abs(dx) * 0.5;\n break;\n }\n\n if (score > bestScore) {\n bestScore = score;\n best = c;\n }\n }\n\n return best?.id ?? null;\n }\n\n function onKeyDown(e: KeyboardEvent): void {\n switch (e.key) {\n case 'Tab': {\n // Focus first/selected node\n const selected = getSelectedIds();\n const nodes = getNodes();\n if (nodes.length === 0) return;\n\n if (selected.length > 0) {\n focusedNodeId = selected[0];\n } else if (!focusedNodeId || !findNodeById(focusedNodeId)) {\n focusedNodeId = nodes[0].id;\n }\n\n e.preventDefault();\n break;\n }\n\n case 'ArrowUp':\n case 'ArrowDown':\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (!focusedNodeId) return;\n e.preventDefault();\n\n const focusedNode = findNodeById(focusedNodeId);\n if (!focusedNode) return;\n\n const adjacency = getAdjacency();\n const neighbors = adjacency.get(focusedNodeId);\n if (!neighbors || neighbors.size === 0) return;\n\n const dirMap: Record<string, 'up' | 'down' | 'left' | 'right'> = {\n ArrowUp: 'up',\n ArrowDown: 'down',\n ArrowLeft: 'left',\n ArrowRight: 'right',\n };\n\n const nextId = pickDirectionalNeighbor(focusedNode, neighbors, dirMap[e.key]);\n if (nextId) {\n focusedNodeId = nextId;\n onSelect(nextId);\n }\n break;\n }\n\n case 'Enter': {\n if (focusedNodeId) {\n e.preventDefault();\n const selected = getSelectedIds();\n if (selected.includes(focusedNodeId)) {\n onDeselect();\n } else {\n onSelect(focusedNodeId);\n }\n }\n break;\n }\n\n case 'Escape': {\n e.preventDefault();\n focusedNodeId = null;\n onDeselect();\n break;\n }\n\n case '+':\n case '=': {\n e.preventDefault();\n onZoom('in');\n break;\n }\n\n case '-':\n case '_': {\n e.preventDefault();\n onZoom('out');\n break;\n }\n\n case 'Home': {\n e.preventDefault();\n onFitAll();\n break;\n }\n\n case '/': {\n if (onFocusSearch) {\n e.preventDefault();\n onFocusSearch();\n }\n break;\n }\n }\n }\n\n canvas.addEventListener('keydown', onKeyDown);\n\n // Return cleanup function\n return () => {\n canvas.removeEventListener('keydown', onKeyDown);\n };\n}\n","/**\n * Interactive built-in graph legend.\n *\n * Replaces the old innerHTML legend build in graph-mount with a keyboard- and\n * pointer-accessible control. Node-category rows are buttons (`aria-pressed`)\n * that toggle emphasis; edge-category rows are non-interactive line swatches.\n *\n * The legend is a thin view: it holds no highlight state of its own. Toggling a\n * row calls back into the mount, which owns the single highlight slot and\n * re-renders the legend from the resulting active-category set. Hovering a row\n * previews that category via the same highlight code path.\n */\n\n/** Escape a string for safe interpolation into innerHTML. */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;');\n}\n\n/** A node-category legend row. */\nexport interface GraphLegendNodeEntry {\n label: string;\n color: string;\n count?: number;\n /** Whether this category is currently emphasized (drives the inactive style). */\n active: boolean;\n}\n\n/** An edge-category legend row (non-interactive). */\nexport interface GraphLegendEdgeEntry {\n label: string;\n color: string;\n count?: number;\n}\n\n/** Data the legend renders. */\nexport interface GraphLegendViewData {\n nodes: GraphLegendNodeEntry[];\n edges: GraphLegendEdgeEntry[];\n}\n\n/** Callbacks the legend fires; the mount owns the resulting state. */\nexport interface GraphLegendCallbacks {\n /** Whether node rows toggle emphasis. When false, rows are static labels. */\n interactive: boolean;\n /** Whether to show per-category counts. */\n counts: boolean;\n /** Toggle a node category's emphasis. */\n onToggle(value: string): void;\n /** Hover a node category (null on leave) for a live preview. */\n onHover(value: string | null): void;\n}\n\nexport interface GraphLegendController {\n /** Re-render from fresh data (e.g. after a toggle or update()). */\n update(data: GraphLegendViewData): void;\n /** Remove listeners and clear the host. */\n destroy(): void;\n}\n\n/**\n * Create an interactive legend inside `host`. Returns a controller with\n * `update`/`destroy`. The host is fully owned by the legend (cleared on each\n * update and on destroy).\n */\nexport function createGraphLegend(\n host: HTMLElement,\n data: GraphLegendViewData,\n callbacks: GraphLegendCallbacks,\n): GraphLegendController {\n const listeners: Array<() => void> = [];\n\n function render(view: GraphLegendViewData): void {\n teardownListeners();\n host.replaceChildren();\n\n if (view.nodes.length === 0 && view.edges.length === 0) {\n host.style.display = 'none';\n return;\n }\n host.style.display = '';\n\n for (const entry of view.nodes) {\n host.appendChild(nodeRow(entry));\n }\n for (const entry of view.edges) {\n host.appendChild(edgeRow(entry));\n }\n }\n\n function nodeRow(entry: GraphLegendNodeEntry): HTMLElement {\n const interactive = callbacks.interactive;\n const el = document.createElement(interactive ? 'button' : 'div');\n el.className = 'oc-graph-legend-item';\n if (!entry.active) el.classList.add('oc-graph-legend-item--inactive');\n\n if (interactive) {\n const btn = el as HTMLButtonElement;\n btn.type = 'button';\n btn.setAttribute('aria-pressed', String(entry.active));\n const onClick = () => callbacks.onToggle(entry.label);\n const onEnter = () => callbacks.onHover(entry.label);\n const onLeave = () => callbacks.onHover(null);\n btn.addEventListener('click', onClick);\n btn.addEventListener('mouseenter', onEnter);\n btn.addEventListener('mouseleave', onLeave);\n listeners.push(() => {\n btn.removeEventListener('click', onClick);\n btn.removeEventListener('mouseenter', onEnter);\n btn.removeEventListener('mouseleave', onLeave);\n });\n }\n\n el.innerHTML =\n `<span class=\"oc-graph-legend-swatch\" style=\"background:${escapeHtml(entry.color)}\"></span>` +\n `<span class=\"oc-graph-legend-label\">${escapeHtml(entry.label)}</span>` +\n (callbacks.counts && entry.count != null\n ? `<span class=\"oc-graph-legend-count\">${entry.count.toLocaleString()}</span>`\n : '');\n return el;\n }\n\n function edgeRow(entry: GraphLegendEdgeEntry): HTMLElement {\n const el = document.createElement('div');\n el.className = 'oc-graph-legend-item oc-graph-legend-item--edge';\n el.innerHTML =\n `<span class=\"oc-graph-legend-swatch oc-graph-legend-swatch--line\" style=\"background:${escapeHtml(entry.color)}\"></span>` +\n `<span class=\"oc-graph-legend-label\">${escapeHtml(entry.label)}</span>` +\n (callbacks.counts && entry.count != null\n ? `<span class=\"oc-graph-legend-count\">${entry.count.toLocaleString()}</span>`\n : '');\n return el;\n }\n\n function teardownListeners(): void {\n for (const off of listeners) off();\n listeners.length = 0;\n }\n\n render(data);\n\n return {\n update: render,\n destroy(): void {\n teardownListeners();\n host.replaceChildren();\n },\n };\n}\n","/**\n * Graph search manager.\n *\n * Provides case-insensitive substring matching against node labels/ids.\n * Returns a Set of matching node ids that the renderer uses to dim\n * non-matching nodes and edges.\n */\n\nexport class GraphSearchManager {\n private matchedIds: Set<string> | null = null;\n /** The last active (non-empty) query, so a data update can re-run it. */\n private query: string | null = null;\n\n /**\n * Search for nodes matching the query string.\n * Returns a Set of matching node ids, or an empty set if nothing matches.\n */\n search(query: string, nodes: Array<{ id: string; label?: string }>): Set<string> {\n const q = query.toLowerCase().trim();\n\n if (q === '') {\n this.matchedIds = null;\n this.query = null;\n return new Set();\n }\n\n this.query = query;\n\n const matches = new Set<string>();\n for (const node of nodes) {\n const label = (node.label ?? '').toLowerCase();\n const id = node.id.toLowerCase();\n if (label.includes(q) || id.includes(q)) {\n matches.add(node.id);\n }\n }\n\n this.matchedIds = matches;\n return matches;\n }\n\n /**\n * Clear the current search.\n * Returns null to indicate no active search.\n */\n clearSearch(): Set<string> | null {\n this.matchedIds = null;\n this.query = null;\n return null;\n }\n\n /** Get the current set of matched ids, or null if no search is active. */\n getMatches(): Set<string> | null {\n return this.matchedIds;\n }\n\n /** The last active query string, or null when no search is active. */\n getQuery(): string | null {\n return this.query;\n }\n}\n","/**\n * Deterministic seeded initial layout for the force simulation.\n *\n * Without seeding, d3-force places nodes on a phyllotaxis spiral, so the settled\n * layout depends on node ORDER and isn't reproducible across data reshapes. With\n * a seed, each node's start position is a pure function of `(id, seed, community)`,\n * so the same spec + seed produces the same settled layout — and adding a node\n * leaves the others' start positions untouched (their hash inputs don't change).\n *\n * Determinism is scoped PER EXECUTION PATH (worker OR sync): identical within a\n * path, not guaranteed identical across the two.\n */\n\nimport type { SimNode } from './worker-protocol';\n\n/** FNV-1a 32-bit hash of a string. Fast, dependency-free, well-distributed. */\nexport function hash32(str: string): number {\n let h = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n // 32-bit FNV prime multiply via shifts (stays within Math.imul's 32-bit range).\n h = Math.imul(h, 0x01000193);\n }\n // Coerce to an unsigned 32-bit integer.\n return h >>> 0;\n}\n\n/** Map a 32-bit hash to a float in [0, 1). */\nfunction unit(h: number): number {\n return (h >>> 0) / 0x100000000;\n}\n\n/**\n * Seed each node's initial `x`/`y` as a pure function of `(id, seed, community)`.\n * Mutates the nodes in place (matching how d3 mutates its node objects).\n *\n * Placement is a uniform disc: `r = R0·sqrt(u1)`, `θ = u2·2π`. When a node has a\n * `community`, its disc center is biased by `0.6·R0` toward a per-community angle,\n * so clusters start near their eventual region and settle faster/cleaner.\n *\n * DEVIATION from the plan's `R0 ≈ 10·√n`: R0 is a FIXED constant, not derived\n * from the live node count. An n-dependent R0 would rescale every node's start\n * position whenever a node is added, which directly violates the reproducibility\n * guarantee (\"adding a node leaves the others' seed positions unchanged\"). Since\n * a deterministic seed is pointless if one insertion reshuffles the layout, the\n * per-node determinism wins; d3-force's charge repulsion still spreads the cloud\n * to fill the viewport regardless of the initial disc radius. R0 is sized for a\n * ~900-node graph (10·√900 = 300), a reasonable middle for the initial cloud.\n */\nconst R0 = 300;\n\nexport function seedNodePositions(nodes: SimNode[], seed: number): void {\n const n = nodes.length;\n if (n === 0) return;\n\n for (const node of nodes) {\n // Two independent hash streams per node from distinct salts.\n const u1 = unit(hash32(`${node.id}|${seed}|r`));\n const u2 = unit(hash32(`${node.id}|${seed}|t`));\n const r = R0 * Math.sqrt(u1);\n const theta = u2 * Math.PI * 2;\n\n let cx = 0;\n let cy = 0;\n if (node.community) {\n // Per-community region angle: stable across runs, varies with seed.\n const ca = unit(hash32(`${node.community}|${seed}`)) * Math.PI * 2;\n const bias = 0.6 * R0;\n cx = Math.cos(ca) * bias;\n cy = Math.sin(ca) * bias;\n }\n\n node.x = cx + r * Math.cos(theta);\n node.y = cy + r * Math.sin(theta);\n }\n}\n","/**\n * SimulationManager: spawns a Web Worker for the force simulation,\n * or falls back to synchronous d3-force on the main thread.\n *\n * The worker is always preferred when available. Synchronous fallback\n * is only used when Web Workers are unavailable (SSR, test environments).\n * The sync path batches ticks via requestAnimationFrame to avoid\n * blocking the main thread.\n */\n\nimport {\n forceCenter,\n forceCollide,\n forceLink,\n forceManyBody,\n forceSimulation,\n forceX,\n forceY,\n type Simulation,\n type SimulationNodeDatum,\n} from 'd3-force';\n\nimport type { SimEdge, SimNode, WorkerOutMessage, WorkerSimulationConfig } from './worker-protocol';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYNC_TICKS_PER_BATCH = 15;\n/** Absolute ceiling on the derived sync tick cap (guards tiny alphaDecay). */\nconst SYNC_MAX_TICKS_CEIL = 800;\n/** d3's default alphaMin — the alpha at which forceSimulation stops. */\nconst DEFAULT_ALPHA_MIN = 0.001;\n/** Default warmup budget (ms) mirroring the engine's DEFAULT_WARMUP_BUDGET_MS. */\nconst DEFAULT_WARMUP_BUDGET_MS = 250;\n\n/**\n * Ticks d3 needs to reach `alphaMin` from alpha=1 for a given `alphaDecay`,\n * ceilinged. d3 stops when `alpha < alphaMin`, where `alpha *= (1 - alphaDecay)`\n * each tick, so `alpha(n) = (1 - alphaDecay)^n`. Solving `(1 - d)^n = alphaMin`\n * gives `n = log(alphaMin) / log(1 - d)`. This aligns the sync cap with the\n * worker path (which runs to `alpha < alphaMin`) instead of a fixed 300, so a\n * `settle: 'thorough'` graph (alphaDecay 0.01, ~690 ticks) settles fully on both.\n *\n * Determinism is scoped PER EXECUTION PATH: same spec + seed ⇒ identical settled\n * layout within a given path. Worker-vs-sync parity is not guaranteed as-is.\n */\nexport function ticksToAlphaMin(alphaDecay: number, alphaMin = DEFAULT_ALPHA_MIN): number {\n if (!(alphaDecay > 0) || alphaDecay >= 1) return SYNC_MAX_TICKS_CEIL;\n const n = Math.ceil(Math.log(alphaMin) / Math.log(1 - alphaDecay));\n return Math.min(SYNC_MAX_TICKS_CEIL, Math.max(1, n));\n}\n\n// ---------------------------------------------------------------------------\n// Internal node shape for sync simulation\n// ---------------------------------------------------------------------------\n\ninterface SyncNode extends SimulationNodeDatum {\n id: string;\n radius: number;\n community?: string;\n fx?: number | null;\n fy?: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Cluster force (duplicated in simulation-worker.ts for the Web Worker path.\n// Worker can't import from workspace packages, so both copies must stay in sync.)\n// ---------------------------------------------------------------------------\n\nfunction forceCluster(nodes: SyncNode[], strength: number) {\n return (alpha: number) => {\n const cx = new Map<string, number>();\n const cy = new Map<string, number>();\n const count = new Map<string, number>();\n\n for (const node of nodes) {\n if (!node.community) continue;\n const c = node.community;\n cx.set(c, (cx.get(c) ?? 0) + (node.x ?? 0));\n cy.set(c, (cy.get(c) ?? 0) + (node.y ?? 0));\n count.set(c, (count.get(c) ?? 0) + 1);\n }\n\n for (const [c, n] of count) {\n cx.set(c, cx.get(c)! / n);\n cy.set(c, cy.get(c)! / n);\n }\n\n const k = strength * alpha;\n for (const node of nodes) {\n if (!node.community) continue;\n const targetX = cx.get(node.community)!;\n const targetY = cy.get(node.community)!;\n node.vx = (node.vx ?? 0) + (targetX - (node.x ?? 0)) * k;\n node.vy = (node.vy ?? 0) + (targetY - (node.y ?? 0)) * k;\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// Cursor-repulsion force (duplicated in simulation-worker.ts for the Web Worker\n// path. Worker can't import from workspace packages, so both copies stay in sync.)\n// ---------------------------------------------------------------------------\n\n/** Live pointer position + radius/strength, mutated by setPointer(). */\ninterface PointerState {\n x: number;\n y: number;\n active: boolean;\n radius: number;\n strength: number;\n}\n\nfunction forceCursor(nodes: SyncNode[], pointer: PointerState) {\n return (alpha: number) => {\n if (!pointer.active || pointer.radius <= 0) return;\n const r2 = pointer.radius * pointer.radius;\n const k = pointer.strength * alpha;\n for (const node of nodes) {\n const dx = (node.x ?? 0) - pointer.x;\n const dy = (node.y ?? 0) - pointer.y;\n const dist2 = dx * dx + dy * dy;\n if (dist2 >= r2) continue;\n const dist = Math.sqrt(dist2) || 1e-6;\n // Linear falloff: full push at the pointer, zero at the radius edge.\n const falloff = (pointer.radius - dist) / pointer.radius;\n const push = (k * falloff) / dist;\n node.vx = (node.vx ?? 0) + dx * push;\n node.vy = (node.vy ?? 0) + dy * push;\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// SimulationManager\n// ---------------------------------------------------------------------------\n\ntype TickCallback = (positions: Array<{ id: string; x: number; y: number }>, alpha: number) => void;\n\ntype SettledCallback = () => void;\n\nexport class SimulationManager {\n private worker: Worker | null = null;\n private syncSim: Simulation<SyncNode, undefined> | null = null;\n private syncNodes: SyncNode[] = [];\n private syncNodeMap: Map<string, SyncNode> = new Map();\n private tickCb: TickCallback | null = null;\n private settledCb: SettledCallback | null = null;\n private destroyed = false;\n private syncRafId: number | null = null;\n /** Derived per-graph cap on sync ticks, from alphaDecay via ticksToAlphaMin. */\n private syncMaxTicks = SYNC_MAX_TICKS_CEIL;\n /** True until the sync warmup loop has completed (nothing renders before then). */\n private syncWarmupPending = false;\n /** Remaining warmup ticks and the ms budget, consumed by the pre-reveal loop. */\n private syncWarmupTicks = 0;\n private syncWarmupBudgetMs = DEFAULT_WARMUP_BUDGET_MS;\n /** Injectable clock for the warmup ms budget (deterministic in tests). */\n private now: () => number =\n typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();\n\n // Stored for worker->sync fallback\n private initNodes: SimNode[] = [];\n private initEdges: SimEdge[] = [];\n private initConfig: WorkerSimulationConfig | null = null;\n\n // Cursor-repulsion pointer state (sync path). radius=0 keeps the force inert.\n private pointer: PointerState = { x: 0, y: 0, active: false, radius: 0, strength: 0 };\n // Separately tracked alpha-target intents so drag and cursor don't stomp each\n // other. The higher intent wins; releasing one falls back to the other.\n private dragAlphaTarget = 0;\n private cursorAlphaTarget = 0;\n\n private constructor() {}\n\n /**\n * Create a SimulationManager. Uses Web Worker for large graphs,\n * synchronous fallback for small graphs or when Worker unavailable.\n *\n * `opts.now` injects a clock for the warmup ms budget (tests pass a fake one).\n */\n static create(\n nodes: SimNode[],\n edges: SimEdge[],\n config: WorkerSimulationConfig,\n opts?: { now?: () => number },\n ): SimulationManager {\n const mgr = new SimulationManager();\n if (opts?.now) mgr.now = opts.now;\n\n const useWorker = typeof Worker !== 'undefined';\n\n if (useWorker) {\n mgr.initWorker(nodes, edges, config);\n } else {\n mgr.initSync(nodes, edges, config);\n }\n\n return mgr;\n }\n\n /** Register a callback for position updates. */\n onTick(cb: TickCallback): void {\n this.tickCb = cb;\n }\n\n /** Register a callback for when the simulation has settled. */\n onSettled(cb: SettledCallback): void {\n this.settledCb = cb;\n }\n\n /** Reheat the simulation. */\n reheat(alpha?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'reheat', alpha });\n } else if (this.syncSim) {\n this.syncSim.alpha(alpha ?? 0.3).restart();\n this.runSyncTicks();\n }\n }\n\n /**\n * Pin a node to fixed x/y coordinates.\n *\n * When `alphaTarget` is provided (springy drag), the sim is held warm so the\n * pinned node's neighbors follow springily. Omitting it posts a byte-identical\n * legacy message and leaves alpha untouched.\n */\n pinNode(id: string, x: number, y: number, alphaTarget?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n // Only include alphaTarget when springy, so the legacy message stays\n // byte-identical (and a stale worker sees exactly the old shape).\n this.worker.postMessage(\n alphaTarget != null\n ? { type: 'pin', nodeId: id, x, y, alphaTarget }\n : { type: 'pin', nodeId: id, x, y },\n );\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = x;\n node.fy = y;\n }\n if (alphaTarget != null && this.syncSim) {\n this.dragAlphaTarget = alphaTarget;\n this.syncAlphaTarget();\n this.syncSim.restart();\n this.runSyncTicks();\n }\n }\n }\n\n /**\n * Unpin a node and reheat so forces settle it into equilibrium.\n *\n * When `alphaTarget` is provided (springy release), the sim cools back toward\n * that target instead of the legacy gentle reheat. Omitting it preserves the\n * exact legacy reheat behavior (and posts a byte-identical message).\n */\n unpinNode(id: string, alphaTarget?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage(\n alphaTarget != null\n ? { type: 'unpin', nodeId: id, alphaTarget }\n : { type: 'unpin', nodeId: id },\n );\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = null;\n node.fy = null;\n }\n if (alphaTarget != null) {\n // Springy release: cool toward the requested target (typically 0). Keep\n // the tick loop running so the sim actually eases down to the target;\n // the alpha-target path owns re-settling (no legacy reheat).\n this.dragAlphaTarget = alphaTarget;\n this.syncAlphaTarget();\n this.runSyncTicks();\n } else if (this.syncSim && this.syncSim.alpha() < 0.1) {\n // LEGACY path: gentle reheat, byte-identical to pre-springy behavior.\n this.syncSim.alpha(0.1).restart();\n this.runSyncTicks();\n }\n }\n }\n\n /**\n * Feed the cursor-repulsion force a pointer position. `active: false` clears\n * the force. No-op when the graph has no cursor force configured (radius 0).\n */\n setPointer(x: number, y: number, active: boolean): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'pointer', x, y, active });\n } else {\n // No cursor force configured (radius 0) → a true no-op; don't warm the sim\n // or the toy-force alpha would keep an otherwise-settled graph ticking.\n if (this.pointer.radius <= 0) return;\n this.pointer.x = x;\n this.pointer.y = y;\n this.pointer.active = active;\n this.cursorAlphaTarget = active ? 0.03 : 0;\n if (this.syncSim) {\n this.syncAlphaTarget();\n if (active) {\n this.syncSim.restart();\n this.runSyncTicks();\n }\n }\n }\n }\n\n /** Apply the max of the tracked alpha-target intents to the sync sim. */\n private syncAlphaTarget(): void {\n if (!this.syncSim) return;\n this.syncSim.alphaTarget(Math.max(this.dragAlphaTarget, this.cursorAlphaTarget));\n }\n\n /** Drag a node (pins it and reheats slightly). */\n dragNode(id: string, x: number, y: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'drag', nodeId: id, x, y });\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = x;\n node.fy = y;\n }\n if (this.syncSim && this.syncSim.alpha() < 0.1) {\n this.syncSim.alpha(0.1).restart();\n this.runSyncTicks();\n }\n }\n }\n\n /** Tear down the simulation and release resources. */\n destroy(): void {\n this.destroyed = true;\n\n if (this.syncRafId !== null) {\n cancelAnimationFrame(this.syncRafId);\n this.syncRafId = null;\n }\n\n if (this.worker) {\n this.worker.postMessage({ type: 'stop' });\n this.worker.terminate();\n this.worker = null;\n }\n\n if (this.syncSim) {\n this.syncSim.stop();\n this.syncSim = null;\n }\n\n this.tickCb = null;\n this.settledCb = null;\n }\n\n // -------------------------------------------------------------------------\n // Worker path\n // -------------------------------------------------------------------------\n\n private initWorker(nodes: SimNode[], edges: SimEdge[], config: WorkerSimulationConfig): void {\n // Store for fallback if worker fails to load\n this.initNodes = nodes;\n this.initEdges = edges;\n this.initConfig = config;\n\n // Worker URL resolution:\n // - Built dist/ consumers: import.meta.url points at dist/index.js,\n // so ./simulation-worker.js resolves to dist/simulation-worker.js.\n // - Vite dev with source aliases (Ladle): import.meta.url points at\n // src/graph/simulation.ts, so ./simulation-worker.js doesn't exist.\n // The .js worker fails to load, and the onerror handler retries\n // with .ts which Vite transforms on the fly.\n // - Vite production build: detects `new Worker(new URL(...))` and\n // bundles the worker as a hashed .js asset.\n const initMsg = { type: 'init' as const, nodes, edges, config };\n const wireWorker = (worker: Worker) => {\n this.worker = worker;\n\n worker.onmessage = (event: MessageEvent<WorkerOutMessage>) => {\n if (this.destroyed) return;\n const msg = event.data;\n\n switch (msg.type) {\n case 'positions':\n this.tickCb?.(msg.nodes, msg.alpha);\n break;\n case 'settled':\n this.settledCb?.();\n break;\n case 'error':\n console.error('[SimulationManager] Worker error:', msg.message);\n break;\n }\n };\n\n worker.postMessage(initMsg);\n };\n\n try {\n const w = new Worker(new URL('./simulation-worker.js', import.meta.url), {\n type: 'module',\n });\n\n w.onerror = () => {\n // .js failed (likely Vite dev with source aliases). Try .ts.\n // The URL is constructed dynamically to prevent bundlers (Rollup)\n // from statically analyzing it and trying to resolve the .ts file\n // as an asset entry point in production builds.\n if (this.destroyed) return;\n w.terminate();\n this.worker = null;\n\n try {\n const tsUrl = new URL(import.meta.url.replace(/\\/[^/]+$/, '/simulation-worker.ts'));\n const w2 = new Worker(tsUrl, { type: 'module' });\n\n w2.onerror = () => {\n // Both .js and .ts failed - fall back to sync.\n if (this.destroyed) return;\n console.warn('[SimulationManager] Worker failed to load, falling back to sync');\n w2.terminate();\n this.worker = null;\n this.initSync(this.initNodes, this.initEdges, this.initConfig!);\n };\n\n wireWorker(w2);\n } catch {\n console.warn('[SimulationManager] Worker creation failed, using sync fallback');\n this.initSync(this.initNodes, this.initEdges, this.initConfig!);\n }\n };\n\n wireWorker(w);\n } catch {\n // Worker construction failed (e.g. SSR or restrictive CSP)\n console.warn('[SimulationManager] Worker creation failed, using sync fallback');\n this.initSync(nodes, edges, config);\n }\n }\n\n // -------------------------------------------------------------------------\n // Synchronous fallback\n // -------------------------------------------------------------------------\n\n private initSync(nodes: SimNode[], edges: SimEdge[], config: WorkerSimulationConfig): void {\n this.syncNodes = nodes.map((n) => ({\n id: n.id,\n x: n.x,\n y: n.y,\n radius: n.radius,\n community: n.community,\n }));\n\n this.syncNodeMap = new Map(this.syncNodes.map((n) => [n.id, n]));\n\n const linkForce = forceLink(edges.map((e) => ({ ...e })))\n .id((d) => (d as SyncNode).id)\n .distance(config.linkDistance);\n if (config.linkStrength != null) {\n linkForce.strength(config.linkStrength);\n }\n\n const padding = config.collisionPadding ?? 2;\n\n this.syncSim = forceSimulation<SyncNode>(this.syncNodes)\n .force('link', linkForce)\n .force('charge', forceManyBody().strength(config.chargeStrength))\n .force(\n 'collide',\n forceCollide<SyncNode>().radius((d) => d.radius + padding),\n )\n // Weak gravity keeps disconnected nodes from drifting far from center\n .force('gravityX', forceX<SyncNode>(0).strength(0.05))\n .force('gravityY', forceY<SyncNode>(0).strength(0.05))\n .alphaDecay(config.alphaDecay)\n .velocityDecay(config.velocityDecay)\n .stop(); // Don't auto-run; we tick manually\n\n // Center force (default true)\n if (config.centerForce !== false) {\n this.syncSim.force('center', forceCenter(0, 0));\n }\n\n // Add clustering force if configured\n if (config.clustering) {\n const clusterFn = forceCluster(this.syncNodes, config.clustering.strength);\n // d3 calls force functions with (alpha) on each tick\n this.syncSim.force('cluster', clusterFn as unknown as ReturnType<typeof forceCenter>);\n }\n\n // Cursor-repulsion force: always registered but inert until setPointer()\n // activates it (pointer.active + radius > 0). Reset intent so a re-init\n // (worker->sync fallback) doesn't inherit stale state.\n this.pointer.active = false;\n this.pointer.radius = config.cursorRepulsion?.radius ?? 0;\n this.pointer.strength = config.cursorRepulsion?.strength ?? 0;\n this.dragAlphaTarget = 0;\n this.cursorAlphaTarget = 0;\n const cursorFn = forceCursor(this.syncNodes, this.pointer);\n this.syncSim.force('cursor', cursorFn as unknown as ReturnType<typeof forceCenter>);\n\n // Initial alpha (entrance / reheat impulse) applied before warmup.\n if (config.initialAlpha != null) {\n this.syncSim.alpha(config.initialAlpha);\n }\n\n // Derive the tick cap from alphaDecay so the sync path settles as far as the\n // worker (which runs to alpha < alphaMin) rather than a fixed 300.\n this.syncMaxTicks = ticksToAlphaMin(config.alphaDecay);\n\n // Warmup: settle a bounded number of ticks BEFORE the first reveal so the\n // entrance doesn't start from an explosive layout. The sync path is on the\n // main thread, so warmup is chunked across rAF frames (a single 100-tick\n // batch at 10k nodes is a 1s+ freeze). Nothing renders until warmup finishes.\n this.syncWarmupTicks = config.warmupTicks ?? 0;\n this.syncWarmupBudgetMs = config.warmupBudgetMs ?? DEFAULT_WARMUP_BUDGET_MS;\n this.syncWarmupPending = this.syncWarmupTicks > 0;\n\n // Defer initial delivery: callbacks aren't wired yet at create() time\n this.runSyncTicks(true);\n }\n\n /**\n * Run simulation ticks in batches, yielding to the main thread between\n * batches via requestAnimationFrame. This prevents a multi-second freeze\n * when the sync fallback handles large graphs (1k+ nodes).\n *\n * Each batch runs SYNC_TICKS_PER_BATCH ticks, emits positions for\n * progressive rendering, then schedules the next batch.\n *\n * @param deferred - When true, start via microtask (initial run where\n * callbacks aren't wired yet). Otherwise start immediately.\n */\n private runSyncTicks(deferred = false): void {\n if (!this.syncSim || this.destroyed) return;\n\n // Cancel any in-flight batched run (e.g. from a previous reheat)\n if (this.syncRafId !== null) {\n cancelAnimationFrame(this.syncRafId);\n this.syncRafId = null;\n }\n\n const sim = this.syncSim;\n const maxTicks = this.syncMaxTicks;\n let tickCount = 0;\n\n // Pre-reveal warmup: chunk the headless settle across rAF frames, bounded by\n // BOTH the remaining tick count AND the ms budget. Nothing is delivered to\n // the tick callback until this completes, so the entrance never starts from\n // an explosive layout and the main thread never freezes. Duplicated in the\n // worker (simulation-worker.ts) as a synchronous loop (it's off-thread).\n const runWarmup = () => {\n if (this.destroyed || !this.syncSim) return;\n this.syncRafId = null;\n\n const start = this.now();\n while (this.syncWarmupTicks > 0) {\n for (let i = 0; i < SYNC_TICKS_PER_BATCH && this.syncWarmupTicks > 0; i++) {\n sim.tick();\n this.syncWarmupTicks--;\n if (sim.alpha() < DEFAULT_ALPHA_MIN) {\n this.syncWarmupTicks = 0;\n break;\n }\n }\n // Budget check between chunks: bail (accept a truncated warmup) at scale.\n if (this.syncWarmupTicks > 0 && this.now() - start >= this.syncWarmupBudgetMs) {\n this.syncWarmupTicks = 0;\n break;\n }\n }\n\n this.syncWarmupPending = false;\n // Warmup done → proceed to the normal reveal/settle loop.\n runBatch();\n };\n\n const runBatch = () => {\n if (this.destroyed || !this.syncSim) return;\n this.syncRafId = null;\n\n for (let i = 0; i < SYNC_TICKS_PER_BATCH && tickCount < maxTicks; i++, tickCount++) {\n sim.tick();\n if (sim.alpha() < DEFAULT_ALPHA_MIN) {\n tickCount = maxTicks;\n break;\n }\n }\n\n const positions = this.syncNodes.map((n) => ({\n id: n.id,\n x: n.x ?? 0,\n y: n.y ?? 0,\n }));\n const alpha = sim.alpha();\n const settled = alpha < DEFAULT_ALPHA_MIN || tickCount >= maxTicks;\n\n this.tickCb?.(positions, alpha);\n\n if (settled) {\n this.settledCb?.();\n } else {\n this.syncRafId = requestAnimationFrame(runBatch);\n }\n };\n\n const start = this.syncWarmupPending ? runWarmup : runBatch;\n\n if (deferred) {\n queueMicrotask(start);\n } else {\n start();\n }\n }\n}\n","/**\n * Re-export shim: the spatial index moved to `../spatial-index` and is now\n * generic over any `{ x, y, radius }` entry so the scatter canvas layer can\n * reuse it. Graph call sites get the node-typed instantiation.\n */\nimport { SpatialIndex as GenericSpatialIndex } from '../spatial-index';\nimport type { PositionedNode } from './types';\n\nexport type { SpatialEntry } from '../spatial-index';\n\n/** Quadtree index over positioned graph nodes. */\nexport class SpatialIndex extends GenericSpatialIndex<PositionedNode> {}\n","/**\n * Resolved simulationConfig equality for update-diff's visual-only detection.\n *\n * The old React heuristic only compared `clustering.field`, so a physics change\n * (charge strength, settle/energy, raw force numbers) with the same ids silently\n * took the position-preserving path and never reheated. That's a correctness bug\n * for the flagship API. Here we compare the FULL resolved config via a stable\n * stringify so any physics change forces a structural update.\n */\n\n/** The subset of engine SimulationConfig fields that affect the simulation. */\nexport interface SimulationConfigLike {\n chargeStrength: number;\n linkDistance: number;\n clustering: { field: string; strength: number } | null;\n alphaDecay: number;\n velocityDecay: number;\n collisionRadius: number;\n collisionPadding?: number;\n linkStrength?: number;\n centerForce?: boolean;\n seed?: number;\n warmupTicks?: number;\n warmupBudgetMs?: number;\n initialAlpha?: number;\n}\n\n/**\n * A stable, key-ordered stringify of the config fields that influence the\n * settled layout. `initialAlpha` is INTENTIONALLY excluded: it's the update\n * reheat impulse the mount sets per-update, not a spec-level physics knob, so it\n * must not force a structural update on its own.\n */\nfunction stableKey(c: SimulationConfigLike): string {\n return JSON.stringify([\n c.chargeStrength,\n c.linkDistance,\n c.clustering ? [c.clustering.field, c.clustering.strength] : null,\n c.alphaDecay,\n c.velocityDecay,\n c.collisionRadius,\n c.collisionPadding ?? null,\n c.linkStrength ?? null,\n c.centerForce ?? null,\n c.seed ?? null,\n c.warmupTicks ?? null,\n c.warmupBudgetMs ?? null,\n ]);\n}\n\n/** Deep-equal two resolved simulation configs (excluding `initialAlpha`). */\nexport function simulationConfigEqual(a: SimulationConfigLike, b: SimulationConfigLike): boolean {\n return stableKey(a) === stableKey(b);\n}\n","/**\n * Data-update diff for the graph mount's unified `update()`.\n *\n * Given the previously positioned nodes/edges and a freshly compiled graph,\n * classify the change into:\n * - `visualOnly`: same node AND edge id sets AND equal simulationConfig. The\n * mount can re-run the position-preserving visual refresh with no sim restart.\n * NOTE: unlike the old React JSON-diff heuristic (which only compared\n * `clustering.field`), visual-only here requires the FULL resolved\n * simulationConfig to match — a physics change (charge/settle/energy/etc.)\n * must reheat, not silently skip.\n * - Otherwise a structural update: which nodes enter, where they spawn, which\n * survive (and keep their prior x/y), and which nodes/edges exit (ghosts).\n *\n * Spawn placement: an entering node spawns at its first surviving neighbor's\n * NEW-adjacency position, nudged by a deterministic ±8px hash jitter (so a node\n * doesn't land exactly on its neighbor). A node with no surviving neighbor falls\n * back to a seeded disc position (same hash family as seed.ts).\n */\n\nimport { hash32 } from './seed';\nimport type { PositionedEdge, PositionedNode } from './types';\nimport type { SimulationConfigLike } from './update-diff-config';\nimport { simulationConfigEqual } from './update-diff-config';\n\n/** Minimal position record kept for survivors and computed for spawns. */\nexport interface XY {\n x: number;\n y: number;\n}\n\n/** A freshly compiled graph, reduced to what the diff needs. */\nexport interface NextGraph {\n nodes: Array<{ id: string; community?: string }>;\n edges: Array<{ source: string; target: string }>;\n simulationConfig: SimulationConfigLike;\n}\n\nexport interface GraphUpdateDiff {\n /** True when node+edge id sets are identical AND simulationConfig is equal. */\n visualOnly: boolean;\n /** Ids of nodes present in `next` but not in prev. */\n enteringIds: string[];\n /** Prior x/y for nodes present in BOTH prev and next, keyed by id. */\n survivingPositions: Map<string, XY>;\n /** Computed spawn x/y for entering nodes, keyed by id. */\n spawnPositions: Map<string, XY>;\n /** Prev-positioned nodes removed in `next` (ghosts). */\n exitingNodes: PositionedNode[];\n /** Prev-positioned edges removed in `next` (ghosts). */\n exitingEdges: PositionedEdge[];\n /**\n * Count of edges present in `next` but not in prev — including edges added\n * between two surviving nodes, which \"touches an entering node\" would miss.\n */\n enteringEdgeCount: number;\n}\n\n/** ±8px deterministic jitter from an id+seed hash (two independent streams). */\nconst JITTER = 8;\n\nfunction jitter(id: string, seed: number): XY {\n // Map two hash streams to [-1, 1) then scale, so a spawn doesn't land exactly\n // on its neighbor. Deterministic in (id, seed).\n const jx = (hash32(`${id}|${seed}|jx`) / 0x100000000) * 2 - 1;\n const jy = (hash32(`${id}|${seed}|jy`) / 0x100000000) * 2 - 1;\n return { x: jx * JITTER, y: jy * JITTER };\n}\n\n/**\n * Seeded disc fallback for a node with no surviving neighbor. Mirrors seed.ts's\n * disc placement (r = R0·√u1, θ = u2·2π) so orphan spawns land in the same cloud.\n */\nconst FALLBACK_R0 = 300;\n\nfunction seededDisc(id: string, seed: number): XY {\n const u1 = hash32(`${id}|${seed}|r`) / 0x100000000;\n const u2 = hash32(`${id}|${seed}|t`) / 0x100000000;\n const r = FALLBACK_R0 * Math.sqrt(u1);\n const theta = u2 * Math.PI * 2;\n return { x: r * Math.cos(theta), y: r * Math.sin(theta) };\n}\n\n/**\n * Diff the previous positioned graph against a freshly compiled `next`.\n *\n * @param prevNodes - Previously positioned nodes (carry x/y).\n * @param prevEdges - Previously positioned edges.\n * @param next - The freshly compiled graph (nodes/edges/simulationConfig).\n * @param prevConfig - The prior simulationConfig, for visual-only detection.\n * @param seed - Layout seed for deterministic jitter/fallback placement.\n */\nexport function diffGraphUpdate(\n prevNodes: PositionedNode[],\n prevEdges: PositionedEdge[],\n next: NextGraph,\n prevConfig: SimulationConfigLike,\n seed: number,\n): GraphUpdateDiff {\n const prevIds = new Set(prevNodes.map((n) => n.id));\n const nextIds = new Set(next.nodes.map((n) => n.id));\n\n const survivingPositions = new Map<string, XY>();\n const enteringIds: string[] = [];\n\n for (const n of prevNodes) {\n if (nextIds.has(n.id)) survivingPositions.set(n.id, { x: n.x, y: n.y });\n }\n for (const n of next.nodes) {\n if (!prevIds.has(n.id)) enteringIds.push(n.id);\n }\n\n // Exiting marks: prev nodes/edges gone from next. An edge exits when its\n // (source,target) key is absent from the next edge set — this catches BOTH\n // edges whose endpoint was removed AND edges removed between two survivors.\n const exitingNodes = prevNodes.filter((n) => !nextIds.has(n.id));\n const nextEdgeKeys = new Set(next.edges.map((e) => `${e.source} ${e.target}`));\n const exitingEdges = prevEdges.filter((e) => !nextEdgeKeys.has(`${e.source} ${e.target}`));\n const prevEdgeCounts = new Map<string, number>();\n for (const e of prevEdges) {\n const k = `${e.source} ${e.target}`;\n prevEdgeCounts.set(k, (prevEdgeCounts.get(k) ?? 0) + 1);\n }\n let enteringEdgeCount = 0;\n const remainingPrev = new Map(prevEdgeCounts);\n for (const e of next.edges) {\n const k = `${e.source} ${e.target}`;\n const c = remainingPrev.get(k);\n if (c && c > 0) {\n remainingPrev.set(k, c - 1);\n } else {\n enteringEdgeCount++;\n }\n }\n\n // Visual-only: identical node AND edge id sets AND equal simulationConfig.\n const sameNodes = prevIds.size === nextIds.size && enteringIds.length === 0;\n const sameEdges = edgeSetsEqual(prevEdges, next.edges);\n const visualOnly =\n sameNodes && sameEdges && simulationConfigEqual(prevConfig, next.simulationConfig);\n\n // Spawn placement for enterers: first surviving neighbor in the NEW adjacency,\n // plus ±8px jitter; fallback to a seeded disc when no surviving neighbor.\n const spawnPositions = new Map<string, XY>();\n if (enteringIds.length > 0) {\n const enteringSet = new Set(enteringIds);\n const nextAdjacency = buildAdjacency(next.edges);\n for (const id of enteringIds) {\n const neighborPos = firstSurvivingNeighborPos(\n id,\n nextAdjacency,\n survivingPositions,\n enteringSet,\n );\n const j = jitter(id, seed);\n if (neighborPos) {\n spawnPositions.set(id, { x: neighborPos.x + j.x, y: neighborPos.y + j.y });\n } else {\n const disc = seededDisc(id, seed);\n spawnPositions.set(id, { x: disc.x + j.x, y: disc.y + j.y });\n }\n }\n }\n\n return {\n visualOnly,\n enteringIds,\n survivingPositions,\n spawnPositions,\n exitingNodes,\n exitingEdges,\n enteringEdgeCount,\n };\n}\n\n/** Build an undirected adjacency map from an edge list. */\nfunction buildAdjacency(edges: Array<{ source: string; target: string }>): Map<string, string[]> {\n const map = new Map<string, string[]>();\n const push = (a: string, b: string) => {\n const list = map.get(a);\n if (list) list.push(b);\n else map.set(a, [b]);\n };\n for (const e of edges) {\n push(e.source, e.target);\n push(e.target, e.source);\n }\n return map;\n}\n\n/**\n * First neighbor of `id` (in the new adjacency) that already has a known\n * position — i.e. a survivor, not another enterer. Deterministic: neighbors are\n * scanned in edge-declaration order.\n */\nfunction firstSurvivingNeighborPos(\n id: string,\n adjacency: Map<string, string[]>,\n survivingPositions: Map<string, XY>,\n enteringSet: Set<string>,\n): XY | null {\n const neighbors = adjacency.get(id);\n if (!neighbors) return null;\n for (const nid of neighbors) {\n if (enteringSet.has(nid)) continue;\n const pos = survivingPositions.get(nid);\n if (pos) return pos;\n }\n return null;\n}\n\n/**\n * Order-insensitive multiset equality of two edge lists by (source,target) key.\n * Counts matter: prev [A→B, A→B, C→D] vs next [A→B, C→D, C→D] is a structural\n * change, not visual-only.\n */\nfunction edgeSetsEqual(\n prev: Array<{ source: string; target: string }>,\n next: Array<{ source: string; target: string }>,\n): boolean {\n if (prev.length !== next.length) return false;\n const key = (e: { source: string; target: string }) => `${e.source}->${e.target}`;\n const counts = new Map<string, number>();\n for (const e of prev) {\n const k = key(e);\n counts.set(k, (counts.get(k) ?? 0) + 1);\n }\n for (const e of next) {\n const k = key(e);\n const c = counts.get(k);\n if (!c) return false;\n counts.set(k, c - 1);\n }\n return true;\n}\n","/**\n * Table cell renderers: produce DOM elements for each cell type.\n *\n * Each renderer takes a resolved TableCell and returns an HTMLElement\n * (typically a <td>) with appropriate content, styling, and classes.\n */\n\nimport type {\n BarTableCell,\n CategoryTableCell,\n FlagTableCell,\n HeatmapTableCell,\n ImageTableCell,\n SparklineData,\n SparklineTableCell,\n TableCell,\n TextTableCell,\n} from '@opendata-ai/openchart-core';\n\n// ---------------------------------------------------------------------------\n// Utility\n// ---------------------------------------------------------------------------\n\n/** Apply common cell styles (background, color, font weight, font variant). */\nfunction applyCellStyle(td: HTMLTableCellElement, cell: TableCell): void {\n if (cell.style.backgroundColor) {\n td.style.background = cell.style.backgroundColor;\n }\n if (cell.style.color) {\n td.style.color = cell.style.color;\n }\n if (cell.style.fontWeight) {\n td.style.fontWeight = String(cell.style.fontWeight);\n }\n if (cell.style.fontVariant) {\n td.style.fontVariant = cell.style.fontVariant;\n }\n if (cell.aria) {\n td.setAttribute('aria-label', cell.aria);\n }\n}\n\n/**\n * Convert a 2-char ISO 3166-1 alpha-2 country code to a flag emoji.\n * Each letter maps to a Regional Indicator Symbol (offset 0x1F1A5 from ASCII).\n */\nfunction countryToEmoji(code: string): string {\n return [...code.toUpperCase()]\n .map((c) => String.fromCodePoint(c.charCodeAt(0) + 0x1f1a5))\n .join('');\n}\n\n/**\n * Lookup map for common country names by ISO 3166-1 alpha-2 code.\n * Falls back to the raw code for unrecognized values.\n */\nconst COUNTRY_NAMES: Record<string, string> = {\n US: 'United States',\n CN: 'China',\n IN: 'India',\n ID: 'Indonesia',\n BR: 'Brazil',\n PK: 'Pakistan',\n NG: 'Nigeria',\n BD: 'Bangladesh',\n RU: 'Russia',\n MX: 'Mexico',\n JP: 'Japan',\n DE: 'Germany',\n GB: 'United Kingdom',\n FR: 'France',\n IT: 'Italy',\n CA: 'Canada',\n AU: 'Australia',\n KR: 'South Korea',\n ES: 'Spain',\n AR: 'Argentina',\n CO: 'Colombia',\n ZA: 'South Africa',\n TR: 'Turkey',\n SA: 'Saudi Arabia',\n UA: 'Ukraine',\n PL: 'Poland',\n NL: 'Netherlands',\n SE: 'Sweden',\n NO: 'Norway',\n DK: 'Denmark',\n FI: 'Finland',\n CH: 'Switzerland',\n AT: 'Austria',\n BE: 'Belgium',\n PT: 'Portugal',\n IE: 'Ireland',\n NZ: 'New Zealand',\n SG: 'Singapore',\n IL: 'Israel',\n AE: 'United Arab Emirates',\n EG: 'Egypt',\n TH: 'Thailand',\n VN: 'Vietnam',\n PH: 'Philippines',\n MY: 'Malaysia',\n CL: 'Chile',\n PE: 'Peru',\n CZ: 'Czech Republic',\n GR: 'Greece',\n HU: 'Hungary',\n RO: 'Romania',\n ET: 'Ethiopia',\n};\n\n/** Get a human-readable name for a country code. */\nfunction getCountryName(code: string): string {\n return COUNTRY_NAMES[code.toUpperCase()] || code.toUpperCase();\n}\n\n/**\n * Describe a sparkline trend for screen readers.\n * Compares first and last values to determine direction.\n */\nfunction describeSparklineTrend(data: SparklineData): string {\n if (data.type === 'line' && data.points.length >= 2) {\n const first = data.points[0].y;\n const last = data.points[data.points.length - 1].y;\n const count = data.points.length;\n if (last > first) return `Sparkline with ${count} points, trending upward`;\n if (last < first) return `Sparkline with ${count} points, trending downward`;\n return `Sparkline with ${count} points, roughly flat`;\n }\n if ((data.type === 'bar' || data.type === 'column') && data.bars.length > 0) {\n return `${data.type === 'column' ? 'Column' : 'Bar'} sparkline with ${data.bars.length} values`;\n }\n return 'Sparkline';\n}\n\n// ---------------------------------------------------------------------------\n// Cell renderers\n// ---------------------------------------------------------------------------\n\n/** Render a plain text cell. */\nexport function renderTextCell(cell: TextTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n td.textContent = cell.formattedValue;\n applyCellStyle(td, cell);\n return td;\n}\n\n/** Render a heatmap-colored cell. */\nexport function renderHeatmapCell(cell: HeatmapTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n td.className = 'oc-table-heatmap';\n td.textContent = cell.formattedValue;\n applyCellStyle(td, cell);\n return td;\n}\n\n/** Render a category-colored cell. */\nexport function renderCategoryCell(cell: CategoryTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n td.className = 'oc-table-category';\n td.textContent = cell.formattedValue;\n applyCellStyle(td, cell);\n return td;\n}\n\n/** Render a cell with an inline bar visualization. */\nexport function renderBarCell(cell: BarTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n td.className = 'oc-table-bar';\n applyCellStyle(td, cell);\n\n const fill = document.createElement('div');\n fill.className = 'oc-table-bar-fill';\n fill.style.width = `${Math.round(cell.barWidth * 100)}%`;\n fill.style.left = `${Math.round(cell.barOffset * 100)}%`;\n fill.style.background = cell.barColor;\n td.appendChild(fill);\n\n const valueSpan = document.createElement('span');\n valueSpan.className = 'oc-table-bar-value';\n valueSpan.textContent = cell.formattedValue;\n td.appendChild(valueSpan);\n\n return td;\n}\n\n/**\n * Format a sparkline endpoint value for display.\n * Keeps it compact: no decimals for values >= 100, one decimal otherwise.\n */\nfunction formatSparklineValue(v: number): string {\n if (Math.abs(v) >= 1000) {\n return v.toLocaleString('en-US', { maximumFractionDigits: 0 });\n }\n if (Math.abs(v) >= 100) {\n return v.toFixed(0);\n }\n return v.toFixed(1);\n}\n\n/** Render a cell with an inline sparkline SVG. */\nexport function renderSparklineCell(cell: SparklineTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n applyCellStyle(td, cell);\n\n const sparklineData = cell.sparklineData;\n if (!sparklineData || sparklineData.count === 0) {\n td.textContent = cell.formattedValue || '';\n return td;\n }\n\n // Add aria-label describing the trend for screen readers\n const trendDescription = describeSparklineTrend(sparklineData);\n if (!td.getAttribute('aria-label')) {\n td.setAttribute('aria-label', trendDescription);\n }\n\n const wrapper = document.createElement('span');\n wrapper.className = 'oc-table-sparkline';\n\n const svgNS = 'http://www.w3.org/2000/svg';\n\n if (sparklineData.type === 'line') {\n // Infrographic-style sparkline: SVG polyline fills cell width via percentage x-coords.\n // Dots are HTML elements (not SVG circles) to avoid aspect-ratio distortion.\n // Labels are HTML below the SVG.\n const svgH = 28;\n const padY = 4;\n const lineH = svgH - padY * 2;\n\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('aria-hidden', 'true');\n svg.setAttribute('xmlns', svgNS);\n svg.style.height = `${svgH}px`;\n\n // Compute y positions in pixel space (no viewBox scaling)\n const yPositions = sparklineData.points.map((p) => padY + (1 - p.y) * lineH);\n\n // SVG polyline doesn't support % in points attribute, so use a viewBox\n // with a wide aspect ratio and map x to 0-1000 range. preserveAspectRatio=\"none\"\n // stretches the x-axis to fill the cell width. Y values stay in pixel space\n // since viewBox height matches the SVG height. Only the polyline is in the SVG;\n // dots are HTML elements to avoid circle distortion from the non-uniform scaling.\n const viewW = 1000;\n svg.setAttribute('viewBox', `0 0 ${viewW} ${svgH}`);\n svg.setAttribute('preserveAspectRatio', 'none');\n\n const ptsScaled = sparklineData.points.map((p, i) => ({\n x: p.x * viewW,\n y: yPositions[i],\n }));\n const scaledPointsStr = ptsScaled.map((p) => `${p.x},${p.y}`).join(' ');\n\n const polyline = document.createElementNS(svgNS, 'polyline');\n polyline.setAttribute('points', scaledPointsStr);\n polyline.setAttribute('fill', 'none');\n polyline.setAttribute('stroke', sparklineData.color);\n polyline.setAttribute('stroke-width', '1.5');\n polyline.setAttribute('stroke-linejoin', 'round');\n polyline.setAttribute('vector-effect', 'non-scaling-stroke');\n svg.appendChild(polyline);\n\n wrapper.appendChild(svg);\n\n // HTML dots at start and end (positioned absolutely over the SVG)\n const firstY = yPositions[0];\n const lastY = yPositions[yPositions.length - 1];\n const dotSize = 5;\n\n const startDot = document.createElement('span');\n startDot.className = 'oc-table-sparkline-dot';\n startDot.style.left = '0';\n startDot.style.top = `${firstY - dotSize / 2}px`;\n startDot.style.background = sparklineData.color;\n wrapper.appendChild(startDot);\n\n const endDot = document.createElement('span');\n endDot.className = 'oc-table-sparkline-dot';\n endDot.style.right = '0';\n endDot.style.top = `${lastY - dotSize / 2}px`;\n endDot.style.background = sparklineData.color;\n wrapper.appendChild(endDot);\n\n // HTML labels below the SVG, positioned at left and right edges\n const labelsRow = document.createElement('span');\n labelsRow.className = 'oc-table-sparkline-labels';\n labelsRow.style.color = sparklineData.color;\n\n const startLabel = document.createElement('span');\n startLabel.textContent = formatSparklineValue(sparklineData.startValue);\n labelsRow.appendChild(startLabel);\n\n const endLabel = document.createElement('span');\n endLabel.textContent = formatSparklineValue(sparklineData.endValue);\n labelsRow.appendChild(endLabel);\n\n wrapper.appendChild(labelsRow);\n } else if (sparklineData.type === 'column') {\n // Vertical bars at proportional heights\n const width = 80;\n const height = 20;\n const padding = 2;\n const innerW = width - padding * 2;\n const innerH = height - padding * 2;\n\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('width', String(width));\n svg.setAttribute('height', String(height));\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`);\n svg.setAttribute('aria-hidden', 'true');\n\n const barCount = sparklineData.bars.length;\n if (barCount > 0) {\n const gap = 1;\n const barW = Math.max(1, (innerW - gap * (barCount - 1)) / barCount);\n\n for (let i = 0; i < barCount; i++) {\n const barH = Math.max(1, sparklineData.bars[i] * innerH);\n const x = padding + i * (barW + gap);\n const y = padding + innerH - barH;\n\n const rect = document.createElementNS(svgNS, 'rect');\n rect.setAttribute('x', String(x));\n rect.setAttribute('y', String(y));\n rect.setAttribute('width', String(barW));\n rect.setAttribute('height', String(barH));\n rect.setAttribute('rx', '1.5');\n rect.setAttribute('fill', sparklineData.color);\n svg.appendChild(rect);\n }\n }\n\n wrapper.appendChild(svg);\n } else {\n // 'bar' type: horizontal bars at proportional widths\n const width = 80;\n const height = 20;\n const padding = 2;\n const innerW = width - padding * 2;\n const innerH = height - padding * 2;\n\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('width', String(width));\n svg.setAttribute('height', String(height));\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`);\n svg.setAttribute('aria-hidden', 'true');\n\n const barCount = sparklineData.bars.length;\n if (barCount > 0) {\n const gap = 1;\n const barH = Math.max(1, (innerH - gap * (barCount - 1)) / barCount);\n\n for (let i = 0; i < barCount; i++) {\n const barW = Math.max(1, sparklineData.bars[i] * innerW);\n const x = padding;\n const y = padding + i * (barH + gap);\n\n const rect = document.createElementNS(svgNS, 'rect');\n rect.setAttribute('x', String(x));\n rect.setAttribute('y', String(y));\n rect.setAttribute('width', String(barW));\n rect.setAttribute('height', String(barH));\n rect.setAttribute('rx', '1.5');\n rect.setAttribute('fill', sparklineData.color);\n svg.appendChild(rect);\n }\n }\n\n wrapper.appendChild(svg);\n }\n\n td.appendChild(wrapper);\n\n return td;\n}\n\n/** Render a cell with an image. */\nexport function renderImageCell(cell: ImageTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n applyCellStyle(td, cell);\n\n const wrapper = document.createElement('span');\n wrapper.className = `oc-table-image${cell.rounded ? ' oc-table-image-rounded' : ''}`;\n\n const img = document.createElement('img');\n img.src = cell.src;\n img.alt = cell.formattedValue || '';\n img.width = cell.imageWidth;\n img.height = cell.imageHeight;\n img.loading = 'lazy';\n\n wrapper.appendChild(img);\n td.appendChild(wrapper);\n\n return td;\n}\n\n/** Render a cell with a country flag emoji. */\nexport function renderFlagCell(cell: FlagTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n applyCellStyle(td, cell);\n\n const span = document.createElement('span');\n span.className = 'oc-table-flag';\n span.setAttribute('role', 'img');\n\n if (cell.countryCode && cell.countryCode.length === 2) {\n const countryName = getCountryName(cell.countryCode);\n span.textContent = countryToEmoji(cell.countryCode);\n span.setAttribute('aria-label', `Flag: ${countryName}`);\n } else {\n span.textContent = cell.formattedValue;\n span.setAttribute('aria-label', cell.formattedValue);\n }\n\n td.appendChild(span);\n\n return td;\n}\n\n// ---------------------------------------------------------------------------\n// Dispatcher\n// ---------------------------------------------------------------------------\n\n/** Render any table cell by dispatching on its cellType. */\nexport function renderCell(cell: TableCell): HTMLTableCellElement {\n switch (cell.cellType) {\n case 'text':\n return renderTextCell(cell);\n case 'heatmap':\n return renderHeatmapCell(cell);\n case 'category':\n return renderCategoryCell(cell);\n case 'bar':\n return renderBarCell(cell);\n case 'sparkline':\n return renderSparklineCell(cell);\n case 'image':\n return renderImageCell(cell);\n case 'flag':\n return renderFlagCell(cell);\n default:\n // Exhaustive check fallback\n return renderTextCell(cell as TextTableCell);\n }\n}\n","/**\n * Sankey mount API: the main entry point for vanilla JS sankey usage.\n *\n * createSankey() takes a container, SankeySpec, and options, compiles the\n * sankey, renders it as SVG, sets up responsive resizing, tooltip interaction,\n * hover highlighting, and returns a SankeyInstance with update/resize/export/destroy.\n */\n\nimport type {\n CompileOptions,\n DarkMode,\n SankeyLayout,\n SankeySpec,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { compileSankey } from '@opendata-ai/openchart-engine';\nimport { cancelAnimations, setupAnimationCleanup } from './animation';\nimport {\n exportJPG,\n exportPNG,\n exportSVG,\n exportSVGWithFonts,\n type JPGExportOptions,\n type SVGExportOptions,\n} from './export';\nimport { createMeasureText, resolveFontFamily, scheduleFontReload } from './measure-text';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { renderSankeySVG } from './sankey-renderer';\nimport { createTooltipManager, type TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SankeyMountOptions {\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode setting: \"auto\" (system pref), \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Enable responsive resizing. Defaults to true. */\n responsive?: boolean;\n /** Show the tryOpenData.ai watermark. Defaults to true. */\n watermark?: boolean;\n /** Show tooltips on hover. Defaults to true. */\n tooltip?: boolean;\n /** Callback when a node is clicked. */\n onNodeClick?: (node: Record<string, unknown>) => void;\n /** Callback when a link is clicked. */\n onLinkClick?: (link: Record<string, unknown>) => void;\n /** Callback when a node is hovered (null on mouse leave). */\n onNodeHover?: (node: Record<string, unknown> | null) => void;\n /** Callback when a link is hovered (null on mouse leave). */\n onLinkHover?: (link: Record<string, unknown> | null) => void;\n}\n\nexport interface SankeyInstance {\n /** Re-compile and re-render with a new spec. */\n update(spec: SankeySpec): void;\n /** Re-compile at current container dimensions. */\n resize(): void;\n /** Export the sankey diagram. */\n export(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options?: JPGExportOptions,\n ): string | Promise<Blob> | Promise<string>;\n /** Remove all DOM elements and disconnect observers. */\n destroy(): void;\n /** The current compiled layout. */\n readonly layout: SankeyLayout;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Opacity for links connected to hovered node. */\nconst HIGHLIGHT_OPACITY = 0.7;\n/** Opacity for links NOT connected to hovered node. */\nconst DIM_OPACITY = 0.15;\n/** Opacity for nodes NOT connected to hovered node. */\nconst NODE_DIM_OPACITY = 0.2;\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a sankey instance from a spec and mount it into a container.\n */\nexport function createSankey(\n container: HTMLElement,\n spec: SankeySpec,\n options?: SankeyMountOptions,\n): SankeyInstance {\n let currentSpec = spec;\n let currentLayout: SankeyLayout;\n let destroyed = false;\n\n // DOM\n let svgElement: SVGSVGElement | null = null;\n\n // Subsystems\n let tooltipManager: TooltipManager | null = null;\n let cleanupTooltipEvents: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n\n // Animation state\n let isFirstRender = true;\n let animationCleanup: (() => void) | null = null;\n let pendingResize = false;\n\n // Set when webfonts have loaded and a recompile is owed. The next render()\n // that recompiles flips data-oc-fonts-state to 'ready' and clears this, so\n // the attribute stays honest when resize() defers to pendingResize during\n // the entrance animation.\n let fontsReloadPending = false;\n\n // Apply the root class up front so getComputedStyle sees --oc-font-family\n // before the text measurer is built.\n container.classList.add('oc-sankey-root');\n\n // Resolve the effective font the way compile() will: compile merges\n // { ...spec.theme, ...options.theme }, so options.theme wins over the\n // spec-level theme; fall back to the container's computed font. Measuring a\n // different font than gets rendered desyncs layout metrics and the reload watcher.\n function resolveEffectiveFont(): string {\n return (\n options?.theme?.fonts?.family ??\n currentSpec.theme?.fonts?.family ??\n resolveFontFamily(container)\n );\n }\n let fontFamily = resolveEffectiveFont();\n let measureText = createMeasureText(fontFamily);\n let renderGen = 0;\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n function compile(): SankeyLayout {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n measureText,\n };\n\n return compileSankey(currentSpec, compileOpts);\n }\n\n // ---------------------------------------------------------------------------\n // Tooltip and interaction wiring\n // ---------------------------------------------------------------------------\n\n function wireTooltipAndInteraction(svg: SVGSVGElement, layout: SankeyLayout): () => void {\n const cleanups: Array<() => void> = [];\n\n const nodesById = new Map<string, (typeof layout.nodes)[number]>();\n for (const n of layout.nodes) {\n if (!nodesById.has(n.nodeId)) nodesById.set(n.nodeId, n);\n }\n\n // Wire tooltip on node elements\n const nodeElements = svg.querySelectorAll('.oc-sankey-node');\n for (const el of nodeElements) {\n const markId = el.getAttribute('data-mark-id');\n if (!markId) continue;\n\n const content = layout.tooltipDescriptors.get(markId);\n const nodeId = el.getAttribute('data-node-id');\n const nodeData = nodeId ? (nodesById.get(nodeId)?.data ?? {}) : {};\n\n const handleMouseEnter = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n if (content && tooltipManager) {\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n options?.onNodeHover?.(nodeData);\n if (nodeId) highlightConnectedLinks(svg, nodeId, layout);\n };\n\n const handleMouseMove = (e: Event) => {\n if (content && tooltipManager) {\n const mouseEvent = e as MouseEvent;\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n };\n\n const handleMouseLeave = () => {\n tooltipManager?.hide();\n options?.onNodeHover?.(null);\n resetLinkOpacity(svg, layout);\n };\n\n const handleClick = () => {\n options?.onNodeClick?.(nodeData);\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n\n cleanups.push(() => {\n el.removeEventListener('mouseenter', handleMouseEnter);\n el.removeEventListener('mousemove', handleMouseMove);\n el.removeEventListener('mouseleave', handleMouseLeave);\n el.removeEventListener('click', handleClick);\n });\n }\n\n // Wire tooltip on link elements\n const linkElements = svg.querySelectorAll('.oc-sankey-link');\n for (const el of linkElements) {\n const markId = el.getAttribute('data-mark-id');\n if (!markId) continue;\n\n const content = layout.tooltipDescriptors.get(markId);\n const sourceId = el.getAttribute('data-source');\n const targetId = el.getAttribute('data-target');\n const linkData = findLinkData(layout, sourceId, targetId);\n\n const handleMouseEnter = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n if (content && tooltipManager) {\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n options?.onLinkHover?.(linkData);\n };\n\n const handleMouseMove = (e: Event) => {\n if (content && tooltipManager) {\n const mouseEvent = e as MouseEvent;\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n };\n\n const handleMouseLeave = () => {\n tooltipManager?.hide();\n options?.onLinkHover?.(null);\n };\n\n const handleClick = () => {\n options?.onLinkClick?.(linkData);\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n\n cleanups.push(() => {\n el.removeEventListener('mouseenter', handleMouseEnter);\n el.removeEventListener('mousemove', handleMouseMove);\n el.removeEventListener('mouseleave', handleMouseLeave);\n el.removeEventListener('click', handleClick);\n });\n }\n\n return () => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n };\n }\n\n /**\n * Find the data record for a link by source/target ids.\n */\n function findLinkData(\n layout: SankeyLayout,\n sourceId: string | null,\n targetId: string | null,\n ): Record<string, unknown> {\n if (!sourceId || !targetId) return {};\n const link = layout.links.find((l) => l.sourceId === sourceId && l.targetId === targetId);\n return link?.data ?? {};\n }\n\n /**\n * Highlight links connected to a node and dim unconnected links.\n */\n function highlightConnectedLinks(\n svg: SVGSVGElement,\n nodeId: string,\n _layout: SankeyLayout,\n ): void {\n // Collect connected node IDs (the hovered node + its direct neighbors)\n const connectedNodeIds = new Set<string>([nodeId]);\n const linkElements = svg.querySelectorAll('.oc-sankey-link');\n for (const el of linkElements) {\n const source = el.getAttribute('data-source');\n const target = el.getAttribute('data-target');\n const path = el.querySelector('path');\n if (!path) continue;\n\n const isConnected = source === nodeId || target === nodeId;\n path.setAttribute('fill-opacity', String(isConnected ? HIGHLIGHT_OPACITY : DIM_OPACITY));\n if (isConnected) {\n if (source) connectedNodeIds.add(source);\n if (target) connectedNodeIds.add(target);\n }\n }\n\n // Dim unconnected nodes (rect + label)\n const nodeElements = svg.querySelectorAll('.oc-sankey-node');\n for (const el of nodeElements) {\n const nid = el.getAttribute('data-node-id');\n if (!nid) continue;\n const isConnected = connectedNodeIds.has(nid);\n (el as SVGElement).style.opacity = isConnected ? '1' : String(NODE_DIM_OPACITY);\n }\n }\n\n /**\n * Reset all link opacities and node opacities to their original values.\n */\n function resetLinkOpacity(svg: SVGSVGElement, layout: SankeyLayout): void {\n const linkElements = svg.querySelectorAll('.oc-sankey-link');\n for (const el of linkElements) {\n const path = el.querySelector('path');\n if (!path) continue;\n // Look up the original opacity via data attributes rather than positional index\n const source = el.getAttribute('data-source');\n const target = el.getAttribute('data-target');\n const link = layout.links.find((l) => l.sourceId === source && l.targetId === target);\n path.setAttribute('fill-opacity', String(link?.fillOpacity ?? 0.5));\n }\n\n // Restore all node opacities\n const nodeElements = svg.querySelectorAll('.oc-sankey-node');\n for (const el of nodeElements) {\n (el as SVGElement).style.opacity = '1';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n\n function render(): void {\n if (destroyed) return;\n\n // Cancel in-progress animations before tearing down\n if (animationCleanup) {\n animationCleanup();\n animationCleanup = null;\n }\n if (svgElement) {\n cancelAnimations(svgElement);\n }\n\n // Clean up previous tooltip listeners\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n\n // Remove old SVG\n if (svgElement?.parentNode) {\n svgElement.parentNode.removeChild(svgElement);\n }\n\n // Compile\n currentLayout = compile();\n\n // Determine if we should animate\n const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;\n isFirstRender = false;\n\n // Render\n const animation = shouldAnimate ? currentLayout.animation : undefined;\n svgElement = renderSankeySVG(currentLayout, animation);\n container.appendChild(svgElement);\n\n // Dark mode class on container\n const isDark = resolveDarkMode(options?.darkMode);\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Wire tooltip + interaction events\n if (options?.tooltip !== false && svgElement) {\n if (!tooltipManager) {\n tooltipManager = createTooltipManager(container);\n }\n cleanupTooltipEvents = wireTooltipAndInteraction(svgElement, currentLayout);\n }\n\n // Animation cleanup on first render\n if (shouldAnimate && svgElement) {\n animationCleanup = setupAnimationCleanup(svgElement, () => {\n animationCleanup = null;\n if (pendingResize) {\n pendingResize = false;\n resize();\n }\n });\n }\n\n renderGen += 1;\n container.dataset.ocRenderGen = String(renderGen);\n\n // This render recompiled with the loaded webfonts; publish 'ready' now\n // rather than right after the fonts-ready resize() (which may have deferred\n // to pendingResize during the entrance animation).\n if (fontsReloadPending) {\n fontsReloadPending = false;\n container.dataset.ocFontsState = 'ready';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n function update(newSpec: SankeySpec): void {\n if (destroyed) return;\n currentSpec = newSpec;\n // A new spec can change theme.fonts.family; rebuild the measurer so layout\n // measures the font compile will actually render with.\n const nextFont = resolveEffectiveFont();\n if (nextFont !== fontFamily) {\n fontFamily = nextFont;\n measureText = createMeasureText(fontFamily);\n }\n isFirstRender = true; // Allow animation on update\n render();\n }\n\n function resize(): void {\n if (destroyed) return;\n // Skip resize during entrance animation to avoid tearing down the animated SVG.\n // Queued resizes replay once animation completes.\n if (animationCleanup) {\n pendingResize = true;\n return;\n }\n render();\n }\n\n function doExport(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n exportOptions?: JPGExportOptions,\n ): string | Promise<Blob> | Promise<string> {\n if (!svgElement) {\n throw new Error('Sankey is not rendered yet');\n }\n\n switch (format) {\n case 'svg':\n return exportSVG(svgElement);\n case 'svg-with-fonts':\n return exportSVGWithFonts(svgElement, exportOptions as SVGExportOptions);\n case 'png':\n return exportPNG(svgElement, exportOptions);\n case 'jpg':\n return exportJPG(svgElement, exportOptions);\n default:\n throw new Error(`Unsupported export format: ${format}`);\n }\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n // Cancel entrance animations\n if (animationCleanup) {\n animationCleanup();\n animationCleanup = null;\n pendingResize = false;\n }\n if (svgElement) {\n cancelAnimations(svgElement);\n }\n\n // Disconnect resize observer\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n // Clean up tooltip events\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n\n // Destroy tooltip manager\n if (tooltipManager) {\n tooltipManager.destroy();\n tooltipManager = null;\n }\n\n // Remove SVG\n if (svgElement?.parentNode) {\n svgElement.parentNode.removeChild(svgElement);\n }\n svgElement = null;\n\n container.classList.remove('oc-dark');\n container.classList.remove('oc-sankey-root');\n }\n\n // ---------------------------------------------------------------------------\n // Initialize\n // ---------------------------------------------------------------------------\n\n try {\n currentLayout = compile();\n\n // Determine if we should animate\n const shouldAnimate = !!currentLayout.animation?.enter;\n isFirstRender = false;\n\n // Render\n const animation = shouldAnimate ? currentLayout.animation : undefined;\n svgElement = renderSankeySVG(currentLayout, animation);\n container.appendChild(svgElement);\n\n // Dark mode class on container\n const isDark = resolveDarkMode(options?.darkMode);\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Wire tooltip + interaction events\n if (options?.tooltip !== false && svgElement) {\n tooltipManager = createTooltipManager(container);\n cleanupTooltipEvents = wireTooltipAndInteraction(svgElement, currentLayout);\n }\n\n // Animation cleanup\n if (shouldAnimate && svgElement) {\n animationCleanup = setupAnimationCleanup(svgElement, () => {\n animationCleanup = null;\n if (pendingResize) {\n pendingResize = false;\n resize();\n }\n });\n }\n\n renderGen += 1;\n container.dataset.ocRenderGen = String(renderGen);\n } catch (err) {\n console.error('[viz] Sankey mount failed:', err);\n // Re-throw so callers can handle the error rather than silently returning a broken instance\n throw err;\n }\n\n // Recompile once after webfonts load so late-swapping fonts don't leave\n // node labels measured against fallback metrics.\n const fontsPending = scheduleFontReload(\n fontFamily,\n () => !destroyed,\n () => {\n // Mark the reload owed, then recompile. render() flips the attribute to\n // 'ready' once it actually recompiles, including the pendingResize replay\n // after the entrance animation finishes.\n fontsReloadPending = true;\n resize();\n },\n );\n container.dataset.ocFontsState = fontsPending ? 'pending' : 'ready';\n\n // Responsive resize\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => {\n resize();\n });\n }\n\n return {\n update,\n resize,\n export: doExport,\n destroy,\n get layout() {\n return currentLayout;\n },\n };\n}\n","/**\n * Sankey SVG renderer: converts a SankeyLayout into SVG DOM elements.\n *\n * Creates an <svg> with gradient defs, links (behind), nodes, labels,\n * legend, and chrome. All styling via inline SVG attributes from layout data.\n * Animation is pure CSS, driven by data attributes and CSS custom properties.\n */\n\nimport type {\n CategoricalLegendLayout,\n LegendLayout,\n MeasureTextFn,\n ResolvedAnimation,\n ResolvedChromeElement,\n SankeyLayout,\n SankeyLinkMark,\n SankeyNodeMark,\n TextStyle,\n} from '@opendata-ai/openchart-core';\nimport {\n BRAND_FONT_SIZE,\n BRAND_MIN_WIDTH,\n estimateTextWidth,\n textAscent,\n wrapText,\n} from '@opendata-ai/openchart-core';\nimport { clampStaggerDelay } from '@opendata-ai/openchart-engine';\nimport { stampAnimationVars } from './animation-vars';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst XLINK_NS = 'http://www.w3.org/1999/xlink';\nconst BRAND_URL = 'https://tryopendata.ai';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createSVGElement(tag: string): SVGElement {\n return document.createElementNS(SVG_NS, tag);\n}\n\nfunction setAttrs(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, String(value));\n }\n}\n\nfunction applyTextStyle(el: SVGElement, style: TextStyle): void {\n setAttrs(el, {\n 'font-family': style.fontFamily,\n 'font-size': style.fontSize,\n 'font-weight': style.fontWeight,\n });\n (el as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', style.fill);\n if (style.textAnchor) {\n el.setAttribute('text-anchor', style.textAnchor);\n }\n if (style.dominantBaseline) {\n el.setAttribute('dominant-baseline', style.dominantBaseline);\n }\n if (style.fontVariant) {\n el.setAttribute('font-variant', style.fontVariant);\n }\n}\n\n/**\n * Stamp animation index attributes on a mark element when animation is enabled.\n */\nfunction stampAnimationAttrs(\n el: SVGElement,\n mark: { animationIndex?: number },\n fallbackIndex: number,\n animation?: ResolvedAnimation,\n): void {\n if (!animation?.enter) return;\n const idx = mark.animationIndex ?? fallbackIndex;\n el.setAttribute('data-animation-index', String(idx));\n (el as SVGElement & ElementCSSInlineStyle).style.setProperty('--oc-mark-index', String(idx));\n}\n\n// ---------------------------------------------------------------------------\n// Chrome rendering\n// ---------------------------------------------------------------------------\n\nfunction renderChromeElement(\n parent: SVGElement,\n element: ResolvedChromeElement,\n className: string,\n chromeKey: string,\n measureText?: MeasureTextFn,\n): void {\n const text = createSVGElement('text');\n // element.y is the TOP of the text box; convert to the alphabetic baseline\n // (see renderers/chrome.ts — WebKit mishandles dominant-baseline:hanging).\n setAttrs(text, { x: element.x, y: element.y + textAscent(element.style.fontSize) });\n applyTextStyle(text, element.style);\n text.setAttribute('class', className);\n text.setAttribute('data-chrome-key', chromeKey);\n\n const lines = wrapText(\n element.text,\n element.style.fontSize,\n element.style.fontWeight,\n element.maxWidth,\n measureText,\n );\n\n if (lines.length === 1) {\n text.textContent = element.text;\n } else {\n const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);\n for (let i = 0; i < lines.length; i++) {\n const tspan = createSVGElement('tspan');\n setAttrs(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });\n tspan.textContent = lines[i];\n text.appendChild(tspan);\n }\n }\n\n parent.appendChild(text);\n}\n\nfunction renderChrome(parent: SVGElement, layout: SankeyLayout): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-chrome');\n\n const { chrome, measureText } = layout;\n\n if (chrome.title) {\n renderChromeElement(g, chrome.title, 'oc-title', 'title', measureText);\n }\n if (chrome.subtitle) {\n renderChromeElement(g, chrome.subtitle, 'oc-subtitle', 'subtitle', measureText);\n }\n\n // Bottom chrome: positioned below the sankey drawing area.\n // Sankey has no x-axis, so no extra axis extent to account for.\n const bottomOffset = layout.area.y + layout.area.height;\n if (chrome.source) {\n renderChromeElement(\n g,\n { ...chrome.source, y: bottomOffset + chrome.source.y },\n 'oc-source',\n 'source',\n measureText,\n );\n }\n if (chrome.byline) {\n renderChromeElement(\n g,\n { ...chrome.byline, y: bottomOffset + chrome.byline.y },\n 'oc-byline',\n 'byline',\n measureText,\n );\n }\n if (chrome.footer) {\n renderChromeElement(\n g,\n { ...chrome.footer, y: bottomOffset + chrome.footer.y },\n 'oc-footer',\n 'footer',\n measureText,\n );\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Brand rendering\n// ---------------------------------------------------------------------------\n\nfunction renderBrand(parent: SVGElement, layout: SankeyLayout): void {\n if (layout.dimensions.width < BRAND_MIN_WIDTH) return;\n\n const { width } = layout.dimensions;\n const padding = layout.theme.spacing.padding;\n const rightEdge = width - padding;\n const fill = layout.theme.colors.axis;\n\n const bottomOffset = layout.area.y + layout.area.height;\n const { chrome } = layout;\n const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;\n const chromeY = firstBottom\n ? bottomOffset + firstBottom.y\n : bottomOffset + layout.theme.spacing.chartToFooter;\n\n const a = createSVGElement('a');\n a.setAttribute('href', BRAND_URL);\n a.setAttributeNS(XLINK_NS, 'xlink:href', BRAND_URL);\n a.setAttribute('target', '_blank');\n a.setAttribute('rel', 'noopener');\n a.setAttribute('class', 'oc-chrome-ref');\n\n const BRAND_LARGE = 16;\n const text = createSVGElement('text');\n setAttrs(text, {\n x: rightEdge,\n y: chromeY + BRAND_LARGE,\n 'dominant-baseline': 'alphabetic',\n 'text-anchor': 'end',\n 'font-family': layout.theme.fonts.family,\n 'font-size': BRAND_FONT_SIZE,\n 'fill-opacity': 0.55,\n });\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', fill);\n\n const trySpan = createSVGElement('tspan');\n setAttrs(trySpan, { 'font-weight': 500 });\n trySpan.textContent = 'try';\n\n const openDataSpan = createSVGElement('tspan');\n setAttrs(openDataSpan, { 'font-weight': 600, 'font-size': BRAND_LARGE });\n openDataSpan.textContent = 'OpenData';\n\n const aiSpan = createSVGElement('tspan');\n setAttrs(aiSpan, { 'font-weight': 500 });\n aiSpan.textContent = '.ai';\n\n text.appendChild(trySpan);\n text.appendChild(openDataSpan);\n text.appendChild(aiSpan);\n a.appendChild(text);\n parent.appendChild(a);\n}\n\n// ---------------------------------------------------------------------------\n// Legend rendering\n// ---------------------------------------------------------------------------\n\nfunction renderLegend(parent: SVGElement, legendLayout: LegendLayout): void {\n if (!('entries' in legendLayout) || legendLayout.entries.length === 0) return;\n const legend = legendLayout as CategoricalLegendLayout;\n\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-legend');\n g.setAttribute('role', 'list');\n g.setAttribute('aria-label', 'Chart legend');\n\n const isHorizontal = legend.position === 'top' || legend.position === 'bottom';\n let offsetX = legend.bounds.x;\n let offsetY = legend.bounds.y;\n\n for (let i = 0; i < legend.entries.length; i++) {\n const entry = legend.entries[i];\n\n // Wrap to next line if this entry would overflow bounds\n if (isHorizontal && i > 0) {\n const labelWidth = estimateTextWidth(\n entry.label,\n legend.labelStyle.fontSize,\n legend.labelStyle.fontWeight,\n );\n const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;\n if (offsetX + entryWidth > legend.bounds.x + legend.bounds.width) {\n offsetX = legend.bounds.x;\n offsetY += legend.swatchSize + 6;\n }\n }\n\n const entryG = createSVGElement('g');\n entryG.setAttribute('class', 'oc-legend-entry');\n entryG.setAttribute('role', 'listitem');\n entryG.setAttribute('data-legend-index', String(i));\n entryG.setAttribute('data-legend-label', entry.label);\n\n if (entry.overflow) {\n entryG.setAttribute('data-legend-overflow', 'true');\n entryG.setAttribute('aria-label', entry.label);\n entryG.setAttribute('opacity', '0.5');\n } else {\n entryG.setAttribute(\n 'aria-label',\n `${entry.label}: ${entry.active !== false ? 'visible' : 'hidden'}`,\n );\n entryG.setAttribute('style', 'cursor: pointer');\n if (entry.active === false) {\n entryG.setAttribute('opacity', '0.3');\n }\n }\n\n // Swatch (always rect for sankey)\n const rect = createSVGElement('rect');\n setAttrs(rect, {\n x: offsetX,\n y: offsetY,\n width: legend.swatchSize,\n height: legend.swatchSize,\n fill: entry.color,\n rx: 2,\n });\n entryG.appendChild(rect);\n\n // Label\n const label = createSVGElement('text');\n setAttrs(label, {\n x: offsetX + legend.swatchSize + legend.swatchGap,\n y: offsetY + legend.swatchSize / 2,\n 'dominant-baseline': 'central',\n });\n applyTextStyle(label, legend.labelStyle);\n label.textContent = entry.label;\n entryG.appendChild(label);\n\n g.appendChild(entryG);\n\n // Advance position for next entry\n if (isHorizontal) {\n const labelWidth = estimateTextWidth(\n entry.label,\n legend.labelStyle.fontSize,\n legend.labelStyle.fontWeight,\n );\n const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;\n offsetX += entryWidth;\n } else {\n offsetY += legend.swatchSize + legend.entryGap;\n }\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Gradient defs\n// ---------------------------------------------------------------------------\n\n/**\n * Build a node position lookup for gradient x1/x2 coordinates.\n * Maps nodeId to { x, width } so we can compute gradient endpoints.\n */\nfunction buildNodePositionMap(nodes: SankeyNodeMark[]): Map<string, { x: number; width: number }> {\n const map = new Map<string, { x: number; width: number }>();\n for (const node of nodes) {\n map.set(node.nodeId, { x: node.x, width: node.width });\n }\n return map;\n}\n\n/** Sanitize a string for use as an SVG element ID (no spaces, $, or other invalid chars). */\nfunction sanitizeId(s: string): string {\n return s.replace(/[^a-zA-Z0-9_-]/g, '_');\n}\n\nfunction renderGradientDefs(\n defs: SVGElement,\n links: SankeyLinkMark[],\n nodePositions: Map<string, { x: number; width: number }>,\n): void {\n for (let i = 0; i < links.length; i++) {\n const link = links[i];\n // Only create gradients when source and target colors differ\n if (link.sourceColor === link.targetColor) continue;\n\n // Use sanitized node names for debuggability, with index suffix for uniqueness\n const gradId = `oc-sg-${sanitizeId(link.sourceId)}-${sanitizeId(link.targetId)}-${i}`;\n const gradient = createSVGElement('linearGradient');\n gradient.setAttribute('id', gradId);\n gradient.setAttribute('gradientUnits', 'userSpaceOnUse');\n\n // x1 = right edge of source node, x2 = left edge of target node\n const sourcePos = nodePositions.get(link.sourceId);\n const targetPos = nodePositions.get(link.targetId);\n const x1 = sourcePos ? sourcePos.x + sourcePos.width : 0;\n const x2 = targetPos ? targetPos.x : 0;\n\n gradient.setAttribute('x1', String(x1));\n gradient.setAttribute('x2', String(x2));\n\n const stop0 = createSVGElement('stop');\n setAttrs(stop0, { offset: '0%', 'stop-color': link.sourceColor });\n gradient.appendChild(stop0);\n\n const stop1 = createSVGElement('stop');\n setAttrs(stop1, { offset: '100%', 'stop-color': link.targetColor });\n gradient.appendChild(stop1);\n\n defs.appendChild(gradient);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Links rendering\n// ---------------------------------------------------------------------------\n\nfunction renderLinks(\n parent: SVGElement,\n links: SankeyLinkMark[],\n _nodePositions: Map<string, { x: number; width: number }>,\n animation?: ResolvedAnimation,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-sankey-links');\n\n for (let i = 0; i < links.length; i++) {\n const link = links[i];\n\n const linkG = createSVGElement('g');\n linkG.setAttribute('class', 'oc-sankey-link');\n linkG.setAttribute('data-mark-id', `link-${link.sourceId}-${link.targetId}-${i}`);\n linkG.setAttribute('data-source', link.sourceId);\n linkG.setAttribute('data-target', link.targetId);\n\n if (link.aria?.label) {\n linkG.setAttribute('aria-label', link.aria.label);\n }\n\n stampAnimationAttrs(linkG, link, i, animation);\n\n const path = createSVGElement('path');\n path.setAttribute('d', link.path);\n path.setAttribute('stroke', 'none');\n path.setAttribute('fill-opacity', String(link.fillOpacity));\n\n // Use gradient fill when colors differ, otherwise solid fill\n if (link.sourceColor !== link.targetColor) {\n const gradId = `oc-sg-${sanitizeId(link.sourceId)}-${sanitizeId(link.targetId)}-${i}`;\n path.setAttribute('fill', `url(#${gradId})`);\n } else {\n path.setAttribute('fill', link.sourceColor);\n }\n\n linkG.appendChild(path);\n g.appendChild(linkG);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Nodes rendering\n// ---------------------------------------------------------------------------\n\nfunction renderNodes(\n parent: SVGElement,\n nodes: SankeyNodeMark[],\n animation?: ResolvedAnimation,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-sankey-nodes');\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n\n const nodeG = createSVGElement('g');\n nodeG.setAttribute('class', 'oc-sankey-node');\n nodeG.setAttribute('data-mark-id', `node-${node.nodeId}`);\n nodeG.setAttribute('data-node-id', node.nodeId);\n\n if (node.aria?.label) {\n nodeG.setAttribute('aria-label', node.aria.label);\n }\n\n stampAnimationAttrs(nodeG, node, i, animation);\n\n const rect = createSVGElement('rect');\n setAttrs(rect, {\n x: node.x,\n y: node.y,\n width: node.width,\n height: Math.max(node.height, 1), // Ensure at least 1px visibility\n fill: node.fill,\n rx: node.cornerRadius,\n });\n\n if (node.stroke) {\n rect.setAttribute('stroke', node.stroke);\n }\n if (node.strokeWidth) {\n rect.setAttribute('stroke-width', String(node.strokeWidth));\n }\n\n nodeG.appendChild(rect);\n g.appendChild(nodeG);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Labels rendering\n// ---------------------------------------------------------------------------\n\nfunction renderLabels(\n parent: SVGElement,\n nodes: SankeyNodeMark[],\n measureText?: MeasureTextFn,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-sankey-labels');\n\n for (const node of nodes) {\n const { label } = node;\n if (!label.visible) continue;\n\n const text = createSVGElement('text');\n setAttrs(text, { x: label.x, y: label.y });\n applyTextStyle(text, label.style);\n\n // Wrap label text when maxWidth is set and text would overflow\n if (label.maxWidth !== undefined && label.maxWidth > 0) {\n const fontSize = label.style.fontSize ?? 12;\n const fontWeight = label.style.fontWeight ?? 400;\n const lines = wrapText(label.text, fontSize, fontWeight, label.maxWidth, measureText);\n if (lines.length > 1) {\n const lineHeight = fontSize * (label.style.lineHeight ?? 1.3);\n // Center the multi-line block vertically around the label y position\n const totalHeight = (lines.length - 1) * lineHeight;\n const startY = label.y - totalHeight / 2;\n for (let i = 0; i < lines.length; i++) {\n const tspan = createSVGElement('tspan');\n tspan.setAttribute('x', String(label.x));\n tspan.setAttribute('y', String(startY + i * lineHeight));\n tspan.textContent = lines[i];\n text.appendChild(tspan);\n }\n } else {\n text.textContent = label.text;\n }\n } else {\n text.textContent = label.text;\n }\n\n g.appendChild(text);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Main render function\n// ---------------------------------------------------------------------------\n\n/**\n * Render a SankeyLayout into an SVG element.\n *\n * The SVG structure is: background > defs (gradients) > links > nodes > labels > legend > chrome > brand.\n * Links render before nodes so they appear visually behind.\n */\nexport function renderSankeySVG(\n layout: SankeyLayout,\n animation?: ResolvedAnimation,\n): SVGSVGElement {\n const { width, height } = layout.dimensions;\n\n const svg = createSVGElement('svg') as SVGSVGElement;\n setAttrs(svg, {\n viewBox: `0 0 ${width} ${height}`,\n xmlns: SVG_NS,\n overflow: 'visible',\n });\n svg.style.height = `${height}px`;\n svg.setAttribute('role', layout.a11y.role);\n svg.setAttribute('aria-label', layout.a11y.altText);\n\n // Classes: oc-chart oc-sankey, plus oc-animate if animated\n const enterPhase = animation?.enter;\n const animate = !!enterPhase;\n const classes = animate ? 'oc-chart oc-sankey oc-animate' : 'oc-chart oc-sankey';\n svg.setAttribute('class', classes);\n\n // Set animation CSS custom properties when the enter phase is enabled\n if (enterPhase) {\n const totalMarks = layout.nodes.length + layout.links.length;\n const stagger = clampStaggerDelay(enterPhase.staggerDelay, totalMarks);\n stampAnimationVars(svg, {\n duration: enterPhase.duration,\n stagger,\n annotationDelay: animation!.annotationDelay,\n ease: enterPhase.ease,\n });\n }\n\n // Background\n const bg = createSVGElement('rect');\n bg.setAttribute('class', 'oc-background');\n setAttrs(bg, {\n x: 0,\n y: 0,\n width,\n height,\n fill: layout.theme.colors.background,\n });\n svg.appendChild(bg);\n\n // Defs: gradient definitions for links\n const nodePositions = buildNodePositionMap(layout.nodes);\n const defs = createSVGElement('defs');\n renderGradientDefs(defs, layout.links, nodePositions);\n svg.appendChild(defs);\n\n // Links (behind nodes)\n renderLinks(svg, layout.links, nodePositions, animation);\n\n // Nodes\n renderNodes(svg, layout.nodes, animation);\n\n // Labels\n renderLabels(svg, layout.nodes, layout.measureText);\n\n // Legend\n renderLegend(svg, layout.legend);\n\n // Chrome (title, subtitle, source, byline, footer) renders on top\n renderChrome(svg, layout);\n\n // Brand\n if (layout.watermark) {\n renderBrand(svg, layout);\n }\n\n return svg;\n}\n","/**\n * Table keyboard navigation: arrow-key cell navigation, Enter to sort,\n * Escape to clear search, and aria-activedescendant management.\n *\n * Designed to be wired up by table-mount.ts after render. Returns a\n * cleanup function to remove listeners on re-render or destroy.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface KeyboardNavOptions {\n /** The wrapper element containing the whole table UI. */\n wrapper: HTMLElement;\n /** Callback to trigger sort on a column. */\n onSort: (columnKey: string) => void;\n /** Callback to clear search and return focus to the table body. */\n onClearSearch: () => void;\n /** Callback to announce text to screen readers via the live region. */\n onAnnounce: (message: string) => void;\n}\n\ninterface CellPosition {\n row: number;\n col: number;\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\n/**\n * Attach keyboard navigation to a rendered table.\n *\n * @returns A cleanup function that removes all event listeners.\n */\nexport function attachKeyboardNav(options: KeyboardNavOptions): () => void {\n const { wrapper, onSort, onClearSearch, onAnnounce } = options;\n\n let focusedCell: CellPosition = { row: -1, col: 0 };\n\n const table = wrapper.querySelector('table');\n if (!table) return () => {};\n\n const tbody = table.querySelector('tbody');\n const thead = table.querySelector('thead');\n if (!tbody || !thead) return () => {};\n\n // Make tbody focusable\n tbody.setAttribute('tabindex', '0');\n\n function getRows(): HTMLTableRowElement[] {\n if (!tbody) return [];\n return Array.from(tbody.querySelectorAll('tr'));\n }\n\n function getHeaderCells(): HTMLTableCellElement[] {\n if (!thead) return [];\n const headerRow = thead.querySelector('tr');\n if (!headerRow) return [];\n return Array.from(headerRow.querySelectorAll('th'));\n }\n\n function getCellsInRow(tr: HTMLTableRowElement): HTMLTableCellElement[] {\n return Array.from(tr.querySelectorAll('td'));\n }\n\n function getColCount(): number {\n const rows = getRows();\n if (rows.length === 0) return getHeaderCells().length;\n return getCellsInRow(rows[0]).length;\n }\n\n function clearFocusHighlight(): void {\n const prev = wrapper.querySelector('.oc-table-cell-focus');\n if (prev) {\n prev.classList.remove('oc-table-cell-focus');\n prev.removeAttribute('id');\n }\n }\n\n function setFocusedCell(row: number, col: number): void {\n clearFocusHighlight();\n const rows = getRows();\n const colCount = getColCount();\n\n // Clamp values\n if (rows.length === 0) return;\n row = Math.max(0, Math.min(row, rows.length - 1));\n col = Math.max(0, Math.min(col, colCount - 1));\n\n focusedCell = { row, col };\n\n // Highlight the cell\n const tr = rows[row];\n if (!tr) return;\n const cells = getCellsInRow(tr);\n const cell = cells[col];\n if (!cell) return;\n\n const cellId = `oc-cell-${row}-${col}`;\n cell.id = cellId;\n cell.classList.add('oc-table-cell-focus');\n cell.setAttribute('data-row', String(row));\n cell.setAttribute('data-col', String(col));\n\n // Set aria-activedescendant on tbody\n if (tbody) {\n tbody.setAttribute('aria-activedescendant', cellId);\n }\n\n // Scroll cell into view if needed\n cell.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n\n function handleTbodyFocus(): void {\n // When tbody receives focus, highlight the first cell (or restore last)\n if (focusedCell.row < 0) {\n setFocusedCell(0, 0);\n } else {\n setFocusedCell(focusedCell.row, focusedCell.col);\n }\n }\n\n function handleTbodyKeydown(e: KeyboardEvent): void {\n const rows = getRows();\n if (rows.length === 0) return;\n\n const colCount = getColCount();\n const { row, col } = focusedCell;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n if (row < rows.length - 1) {\n setFocusedCell(row + 1, col);\n }\n break;\n case 'ArrowUp':\n e.preventDefault();\n if (row > 0) {\n setFocusedCell(row - 1, col);\n } else {\n // Move focus to the header\n focusHeaderCell(col);\n }\n break;\n case 'ArrowRight':\n e.preventDefault();\n if (col < colCount - 1) {\n setFocusedCell(row, col + 1);\n }\n break;\n case 'ArrowLeft':\n e.preventDefault();\n if (col > 0) {\n setFocusedCell(row, col - 1);\n }\n break;\n case 'Home':\n e.preventDefault();\n setFocusedCell(row, 0);\n break;\n case 'End':\n e.preventDefault();\n setFocusedCell(row, colCount - 1);\n break;\n }\n }\n\n // Header cell keyboard handling\n function focusHeaderCell(col: number): void {\n const headers = getHeaderCells();\n if (col >= 0 && col < headers.length) {\n clearFocusHighlight();\n headers[col].focus();\n }\n }\n\n function handleHeaderKeydown(e: KeyboardEvent): void {\n const th = e.currentTarget as HTMLTableCellElement;\n const headers = getHeaderCells();\n const colIndex = headers.indexOf(th);\n if (colIndex < 0) return;\n\n switch (e.key) {\n case 'ArrowRight':\n e.preventDefault();\n if (colIndex < headers.length - 1) {\n headers[colIndex + 1].focus();\n }\n break;\n case 'ArrowLeft':\n e.preventDefault();\n if (colIndex > 0) {\n headers[colIndex - 1].focus();\n }\n break;\n case 'ArrowDown':\n e.preventDefault();\n // Move focus to first body row at this column\n if (tbody) {\n tbody.focus();\n setFocusedCell(0, colIndex);\n }\n break;\n case 'Enter':\n case ' ': {\n e.preventDefault();\n const sortColumn = th.getAttribute('data-column');\n const sortBtn = th.querySelector('[data-sort-column]');\n if (sortColumn && sortBtn) {\n onSort(sortColumn);\n }\n break;\n }\n }\n }\n\n // Search escape handling\n const searchInput = wrapper.querySelector('.oc-table-search input') as HTMLInputElement | null;\n\n function handleSearchKeydown(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n e.preventDefault();\n onClearSearch();\n // Return focus to tbody\n if (tbody) {\n tbody.focus();\n onAnnounce('Search cleared');\n }\n }\n }\n\n // Wire up event listeners\n tbody.addEventListener('focus', handleTbodyFocus);\n tbody.addEventListener('keydown', handleTbodyKeydown as EventListener);\n\n // Make header cells focusable and wire keyboard\n const headerCells = getHeaderCells();\n for (const th of headerCells) {\n th.setAttribute('tabindex', '0');\n th.addEventListener('keydown', handleHeaderKeydown as EventListener);\n }\n\n if (searchInput) {\n searchInput.addEventListener('keydown', handleSearchKeydown as EventListener);\n }\n\n // Cleanup\n return () => {\n tbody.removeEventListener('focus', handleTbodyFocus);\n tbody.removeEventListener('keydown', handleTbodyKeydown as EventListener);\n\n for (const th of headerCells) {\n th.removeEventListener('keydown', handleHeaderKeydown as EventListener);\n }\n\n if (searchInput) {\n searchInput.removeEventListener('keydown', handleSearchKeydown as EventListener);\n }\n\n clearFocusHighlight();\n };\n}\n","/**\n * Table mount API: the main entry point for vanilla JS table usage.\n *\n * createTable() takes a container, spec, and options, compiles the table,\n * renders it as HTML, sets up responsive resizing, sort/search/pagination\n * interactivity, and returns a TableInstance with update/resize/export/destroy.\n *\n * Supports both controlled and uncontrolled modes:\n * - Uncontrolled (default): manages sort/search/page internally\n * - Controlled: reads state from externalState, fires onStateChange\n */\n\nimport type {\n CompileTableOptions,\n DarkMode,\n SortState,\n TableLayout,\n TableSpec,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { getBreakpoint } from '@opendata-ai/openchart-core';\nimport { compileTable } from '@opendata-ai/openchart-engine';\nimport { setupTableAnimationCleanup } from './animation';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { attachKeyboardNav } from './table-keyboard';\nimport { renderTable } from './table-renderer';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TableState {\n sort: SortState | null;\n search: string;\n page: number;\n}\n\nexport interface TableMountOptions {\n theme?: ThemeConfig;\n darkMode?: DarkMode;\n responsive?: boolean;\n /** Show the tryOpenData.ai watermark. Defaults to true. */\n watermark?: boolean;\n onRowClick?: (row: Record<string, unknown>) => void;\n onStateChange?: (state: TableState) => void;\n externalState?: { sort?: SortState | null; search?: string; page?: number };\n}\n\nexport interface TableInstance {\n update(spec: TableSpec): void;\n resize(): void;\n export(format: 'csv'): string;\n getState(): TableState;\n setState(partial: Partial<TableState>): void;\n destroy(): void;\n}\n\n// ---------------------------------------------------------------------------\n// CSV export\n// ---------------------------------------------------------------------------\n\nfunction csvEscape(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n') || value.includes('\\r')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\n// ---------------------------------------------------------------------------\n// Sort cycling\n// ---------------------------------------------------------------------------\n\n/**\n * Cycle sort state: clicking same column cycles none -> asc -> desc -> none.\n * Clicking a different column resets to asc.\n */\nfunction cycleSort(current: SortState | null, column: string): SortState | null {\n if (!current || current.column !== column) {\n return { column, direction: 'asc' };\n }\n if (current.direction === 'asc') {\n return { column, direction: 'desc' };\n }\n // desc -> none\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a table instance from a spec and mount it into a container.\n *\n * @param container - The DOM element to render into.\n * @param spec - The table spec.\n * @param options - Mount options.\n * @returns A TableInstance with update/resize/export/destroy methods.\n */\nexport function createTable(\n container: HTMLElement,\n spec: TableSpec,\n options?: TableMountOptions,\n): TableInstance {\n let currentSpec = spec;\n let currentLayout: TableLayout;\n let wrapperElement: HTMLElement | null = null;\n let disconnectResize: (() => void) | null = null;\n let cleanupKeyboard: (() => void) | null = null;\n let cleanupAnimations: (() => void) | null = null;\n let isFirstRender = true;\n let destroyed = false;\n\n // Internal state (used in uncontrolled mode)\n const internalState: TableState = {\n sort: null,\n search: '',\n page: 0,\n };\n\n // Debounce timers\n let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n let resizeDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const isControlled = options?.externalState !== undefined;\n\n function getState(): TableState {\n if (isControlled && options?.externalState) {\n return {\n sort: options.externalState.sort ?? null,\n search: options.externalState.search ?? '',\n page: options.externalState.page ?? 0,\n };\n }\n return { ...internalState };\n }\n\n function updateState(partial: Partial<TableState>): void {\n if (isControlled) {\n // In controlled mode, notify parent\n const current = getState();\n const next: TableState = {\n sort: partial.sort !== undefined ? partial.sort : current.sort,\n search: partial.search !== undefined ? partial.search : current.search,\n page: partial.page !== undefined ? partial.page : current.page,\n };\n options?.onStateChange?.(next);\n } else {\n // In uncontrolled mode, update internal state\n if (partial.sort !== undefined) internalState.sort = partial.sort;\n if (partial.search !== undefined) internalState.search = partial.search;\n if (partial.page !== undefined) internalState.page = partial.page;\n options?.onStateChange?.({ ...internalState });\n }\n }\n\n function compile(): TableLayout {\n const state = getState();\n const darkMode = resolveDarkMode(options?.darkMode);\n const { width } = getContainerDimensions();\n\n const compileOpts: CompileTableOptions = {\n width,\n height: 600,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n sort: state.sort ?? undefined,\n search: state.search || undefined,\n page: state.page,\n };\n\n return compileTable(currentSpec, compileOpts);\n }\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n /**\n * Announce a message to screen readers via the live region.\n */\n function announce(message: string): void {\n if (!wrapperElement) return;\n const liveRegion = wrapperElement.querySelector('.oc-table-live-region');\n if (liveRegion) {\n liveRegion.textContent = message;\n }\n }\n\n /**\n * Apply responsive breakpoint class based on container width.\n * Only auto-adds compact mode at small sizes. Never removes compact\n * if the spec explicitly requests it (layout.compact === true).\n */\n function applyBreakpointClass(): void {\n if (!wrapperElement) return;\n const { width } = getContainerDimensions();\n const bp = getBreakpoint(width);\n\n if (bp === 'compact' || bp === 'medium') {\n wrapperElement.classList.add('oc-table--compact');\n } else if (!currentLayout?.compact) {\n // Only remove compact if the spec didn't explicitly request it\n wrapperElement.classList.remove('oc-table--compact');\n }\n }\n\n function render(): void {\n if (destroyed) return;\n\n try {\n // Cancel any in-flight animations before re-rendering\n if (cleanupAnimations) {\n cleanupAnimations();\n cleanupAnimations = null;\n }\n\n // Clean up previous keyboard nav\n if (cleanupKeyboard) {\n cleanupKeyboard();\n cleanupKeyboard = null;\n }\n\n // Clean up previous render\n if (wrapperElement?.parentNode) {\n wrapperElement.parentNode.removeChild(wrapperElement);\n wrapperElement = null;\n }\n\n currentLayout = compile();\n const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;\n wrapperElement = renderTable(currentLayout, container, { animate: shouldAnimate });\n\n // Set up animation cleanup on first animated render\n if (shouldAnimate && wrapperElement) {\n cleanupAnimations = setupTableAnimationCleanup(wrapperElement);\n }\n if (isFirstRender) {\n isFirstRender = false;\n }\n\n // Apply dark mode class\n const isDark = resolveDarkMode(options?.darkMode);\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Apply responsive breakpoint\n applyBreakpointClass();\n\n // Add clickable class if onRowClick is provided\n if (options?.onRowClick) {\n wrapperElement.classList.add('oc-table--clickable');\n }\n\n // Wire up event handlers\n wireEvents();\n\n // Wire up keyboard navigation\n if (wrapperElement) {\n cleanupKeyboard = attachKeyboardNav({\n wrapper: wrapperElement,\n onSort: (columnKey: string) => {\n const state = getState();\n const newSort = cycleSort(state.sort, columnKey);\n updateState({ sort: newSort, page: 0 });\n\n // Announce sort change\n if (newSort) {\n const dir = newSort.direction === 'asc' ? 'ascending' : 'descending';\n announce(`Sorted by ${columnKey} ${dir}`);\n } else {\n announce('Sort cleared');\n }\n\n if (!isControlled) {\n rerender();\n }\n },\n onClearSearch: () => {\n updateState({ search: '', page: 0 });\n if (!isControlled) {\n rerender();\n }\n },\n onAnnounce: announce,\n });\n }\n } catch (err) {\n console.error('[viz] Table render failed:', err);\n }\n }\n\n function wireEvents(): void {\n if (!wrapperElement) return;\n\n // Sort click handlers on thead buttons\n const sortBtns = wrapperElement.querySelectorAll('[data-sort-column]');\n for (const btn of sortBtns) {\n btn.addEventListener('click', handleSortClick);\n }\n\n // Search input\n const searchInput = wrapperElement.querySelector(\n '.oc-table-search input',\n ) as HTMLInputElement | null;\n if (searchInput) {\n searchInput.addEventListener('input', handleSearchInput);\n }\n\n // Pagination buttons\n const pageButtons = wrapperElement.querySelectorAll('[data-page-action]');\n for (const btn of pageButtons) {\n btn.addEventListener('click', handlePageClick);\n }\n\n // Row click\n if (options?.onRowClick) {\n const rows = wrapperElement.querySelectorAll('tbody tr');\n for (const row of rows) {\n row.addEventListener('click', handleRowClick);\n }\n }\n }\n\n function handleSortClick(e: Event): void {\n const btn = e.currentTarget as HTMLElement;\n const column = btn.getAttribute('data-sort-column');\n if (!column) return;\n\n const state = getState();\n const newSort = cycleSort(state.sort, column);\n\n updateState({ sort: newSort, page: 0 });\n\n // Announce sort change for screen readers\n if (newSort) {\n const dir = newSort.direction === 'asc' ? 'ascending' : 'descending';\n announce(`Sorted by ${column} ${dir}`);\n } else {\n announce('Sort cleared');\n }\n\n if (!isControlled) {\n rerender();\n }\n }\n\n function handleSearchInput(e: Event): void {\n const input = e.target as HTMLInputElement;\n const query = input.value;\n\n if (searchDebounceTimer !== null) {\n clearTimeout(searchDebounceTimer);\n }\n\n searchDebounceTimer = setTimeout(() => {\n searchDebounceTimer = null;\n updateState({ search: query, page: 0 });\n\n if (!isControlled) {\n rerender();\n // Announce search result count\n const rowCount = currentLayout?.rows?.length ?? 0;\n if (query) {\n announce(`${rowCount} result${rowCount !== 1 ? 's' : ''} found`);\n }\n }\n }, 200);\n }\n\n function handlePageClick(e: Event): void {\n const btn = e.currentTarget as HTMLElement;\n const action = btn.getAttribute('data-page-action');\n const state = getState();\n\n if (action === 'prev' && state.page > 0) {\n updateState({ page: state.page - 1 });\n } else if (action === 'next') {\n updateState({ page: state.page + 1 });\n }\n\n if (!isControlled) {\n rerender();\n }\n }\n\n function handleRowClick(e: Event): void {\n const tr = e.currentTarget as HTMLElement;\n const rowId = tr.getAttribute('data-row-id');\n if (!rowId || !currentLayout) return;\n\n const row = currentLayout.rows.find((r) => r.id === rowId);\n if (row) {\n options?.onRowClick?.(row.data);\n }\n }\n\n /**\n * Re-render the table, preserving search input focus across the DOM rebuild.\n */\n function rerender(): void {\n if (destroyed) return;\n\n // Capture current search input state before re-render\n const searchInput = wrapperElement?.querySelector(\n '.oc-table-search input',\n ) as HTMLInputElement | null;\n const hadFocus = searchInput && document.activeElement === searchInput;\n const selectionStart = searchInput?.selectionStart ?? 0;\n const selectionEnd = searchInput?.selectionEnd ?? 0;\n\n render();\n\n // Restore search focus after re-render\n if (hadFocus) {\n const newInput = wrapperElement?.querySelector(\n '.oc-table-search input',\n ) as HTMLInputElement | null;\n if (newInput) {\n newInput.focus();\n newInput.setSelectionRange(selectionStart, selectionEnd);\n }\n }\n }\n\n function update(newSpec: TableSpec): void {\n if (destroyed) return;\n currentSpec = newSpec;\n render();\n }\n\n function resize(): void {\n if (destroyed) return;\n if (cleanupAnimations) return; // Skip resize during entrance animation\n render();\n }\n\n function doExport(format: 'csv'): string {\n if (format !== 'csv') {\n throw new Error(`Unsupported export format: ${format}`);\n }\n\n // Export all filtered/sorted data (not just current page)\n // Re-compile without pagination\n const state = getState();\n const darkMode = resolveDarkMode(options?.darkMode);\n const { width } = getContainerDimensions();\n\n const fullLayout = compileTable(currentSpec, {\n width,\n height: 600,\n theme: options?.theme,\n darkMode,\n sort: state.sort ?? undefined,\n search: state.search || undefined,\n // No page/pageSize: get all rows\n });\n\n const headers = fullLayout.columns.map((c) => c.label);\n const csvRows = [headers.map(csvEscape).join(',')];\n\n for (const row of fullLayout.rows) {\n const values = row.cells.map((cell) => csvEscape(cell.formattedValue));\n csvRows.push(values.join(','));\n }\n\n return csvRows.join('\\n');\n }\n\n function setState(partial: Partial<TableState>): void {\n if (destroyed) return;\n\n // In controlled mode compile() reads from options.externalState, so\n // updating internalState alone would be ignored. Push the new values onto\n // externalState (the source getState() reads) so the render reflects them.\n if (isControlled && options?.externalState) {\n if (partial.sort !== undefined) options.externalState.sort = partial.sort;\n if (partial.search !== undefined) options.externalState.search = partial.search;\n if (partial.page !== undefined) options.externalState.page = partial.page;\n } else {\n if (partial.sort !== undefined) internalState.sort = partial.sort;\n if (partial.search !== undefined) internalState.search = partial.search;\n if (partial.page !== undefined) internalState.page = partial.page;\n }\n\n render();\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n if (cleanupAnimations) {\n cleanupAnimations();\n cleanupAnimations = null;\n }\n if (cleanupKeyboard) {\n cleanupKeyboard();\n cleanupKeyboard = null;\n }\n if (searchDebounceTimer !== null) {\n clearTimeout(searchDebounceTimer);\n searchDebounceTimer = null;\n }\n if (resizeDebounceTimer !== null) {\n clearTimeout(resizeDebounceTimer);\n resizeDebounceTimer = null;\n }\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n if (wrapperElement?.parentNode) {\n wrapperElement.parentNode.removeChild(wrapperElement);\n wrapperElement = null;\n }\n container.classList.remove('oc-dark');\n }\n\n // Initial render\n render();\n\n // Set up responsive resize with breakpoint detection\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => {\n if (resizeDebounceTimer !== null) {\n clearTimeout(resizeDebounceTimer);\n }\n resizeDebounceTimer = setTimeout(() => {\n resizeDebounceTimer = null;\n // Update breakpoint class without full re-render when possible\n applyBreakpointClass();\n resize();\n }, 100);\n });\n }\n\n return {\n update,\n resize,\n export: doExport,\n getState,\n setState,\n destroy,\n };\n}\n","/**\n * Table renderer: produces semantic HTML from a TableLayout.\n *\n * renderTable() creates the full DOM structure: chrome, search bar,\n * scrollable table with sticky column support, pagination, and footer.\n * The returned element replaces or appends to the given container.\n */\n\nimport type { ResolvedColumn, TableLayout, TableRow } from '@opendata-ai/openchart-core';\nimport { BRAND_FONT_SIZE } from '@opendata-ai/openchart-core';\nimport { clampStaggerDelay } from '@opendata-ai/openchart-engine';\nimport { stampAnimationVars } from './animation-vars';\nimport { applySrOnlyStyles } from './dom-helpers';\nimport { renderCell } from './renderers/table-cells';\n\n/** Options for renderTable(). */\nexport interface TableRenderOptions {\n /** Whether to apply entrance animation on this render. */\n animate?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst BRAND_URL = 'https://tryopendata.ai';\n\n// ---------------------------------------------------------------------------\n// Chrome rendering\n// ---------------------------------------------------------------------------\n\n/** Create chrome (title/subtitle or source/footer) block. */\nfunction renderChromeBlock(\n layout: TableLayout,\n position: 'header' | 'footer',\n): HTMLDivElement | null {\n const chrome = layout.chrome;\n\n if (position === 'header') {\n if (!chrome.title && !chrome.subtitle) return null;\n\n const div = document.createElement('div');\n div.className = 'oc-chrome';\n\n if (chrome.title) {\n const h = document.createElement('div');\n h.className = 'oc-table-title';\n h.textContent = chrome.title.text;\n div.appendChild(h);\n }\n if (chrome.subtitle) {\n const sub = document.createElement('div');\n sub.className = 'oc-table-subtitle';\n sub.textContent = chrome.subtitle.text;\n div.appendChild(sub);\n }\n\n return div;\n }\n\n // Footer position\n if (!chrome.source && !chrome.footer) return null;\n\n const div = document.createElement('div');\n div.className = 'oc-chrome oc-chrome-footer';\n\n if (chrome.source) {\n const src = document.createElement('div');\n src.className = 'oc-table-source';\n src.textContent = chrome.source.text;\n div.appendChild(src);\n }\n if (chrome.footer) {\n const foot = document.createElement('div');\n foot.className = 'oc-table-footer-text';\n foot.textContent = chrome.footer.text;\n div.appendChild(foot);\n }\n\n return div;\n}\n\n// ---------------------------------------------------------------------------\n// Table head\n// ---------------------------------------------------------------------------\n\nfunction renderThead(\n columns: ResolvedColumn[],\n sort: TableLayout['sort'],\n): HTMLTableSectionElement {\n const thead = document.createElement('thead');\n const tr = document.createElement('tr');\n tr.setAttribute('role', 'row');\n\n for (const col of columns) {\n const th = document.createElement('th');\n th.setAttribute('scope', 'col');\n th.setAttribute('role', 'columnheader');\n th.style.textAlign = col.align;\n th.style.width = `${col.width}px`;\n\n // Sort state: use 'ascending'/'descending' per WAI-ARIA spec\n let ariaSortValue: string = 'none';\n if (sort && sort.column === col.key) {\n ariaSortValue = sort.direction === 'asc' ? 'ascending' : 'descending';\n }\n th.setAttribute('aria-sort', ariaSortValue);\n th.setAttribute('data-column', col.key);\n\n // Label\n const labelSpan = document.createTextNode(col.label);\n th.appendChild(labelSpan);\n\n // Sort button\n if (col.sortable) {\n const btn = document.createElement('button');\n btn.className = 'oc-table-sort-btn';\n btn.setAttribute('aria-label', `Sort by ${col.label}`);\n btn.setAttribute('data-sort-column', col.key);\n btn.type = 'button';\n th.appendChild(btn);\n }\n\n tr.appendChild(th);\n }\n\n thead.appendChild(tr);\n return thead;\n}\n\n// ---------------------------------------------------------------------------\n// Table body\n// ---------------------------------------------------------------------------\n\nfunction renderTbody(rows: TableRow[], columns: ResolvedColumn[]): HTMLTableSectionElement {\n const tbody = document.createElement('tbody');\n\n for (let r = 0; r < rows.length; r++) {\n const row = rows[r];\n const tr = document.createElement('tr');\n tr.setAttribute('role', 'row');\n tr.setAttribute('data-row-id', row.id);\n tr.style.setProperty('--oc-row-index', String(r));\n\n for (let c = 0; c < columns.length; c++) {\n const cell = row.cells[c];\n if (!cell) continue;\n\n const td = renderCell(cell);\n td.setAttribute('role', 'gridcell');\n td.style.textAlign = columns[c].align;\n tr.appendChild(td);\n }\n\n tbody.appendChild(tr);\n }\n\n return tbody;\n}\n\n// ---------------------------------------------------------------------------\n// Search bar\n// ---------------------------------------------------------------------------\n\nfunction renderSearchBar(layout: TableLayout): HTMLDivElement | null {\n if (!layout.search.enabled) return null;\n\n const div = document.createElement('div');\n div.className = 'oc-table-search';\n\n const input = document.createElement('input');\n input.type = 'search';\n input.placeholder = layout.search.placeholder;\n input.setAttribute('aria-label', 'Search table');\n input.value = layout.search.query;\n\n div.appendChild(input);\n return div;\n}\n\n// ---------------------------------------------------------------------------\n// Pagination\n// ---------------------------------------------------------------------------\n\nfunction renderPagination(layout: TableLayout): HTMLDivElement | null {\n if (!layout.pagination) return null;\n\n const { page, pageSize, totalRows, totalPages } = layout.pagination;\n\n const div = document.createElement('div');\n div.className = 'oc-table-pagination';\n\n const info = document.createElement('span');\n info.className = 'oc-table-pagination-info';\n\n if (totalRows === 0) {\n info.textContent = 'No results';\n } else {\n const start = page * pageSize + 1;\n const end = Math.min((page + 1) * pageSize, totalRows);\n info.textContent = `Showing ${start}-${end} of ${totalRows}`;\n }\n\n div.appendChild(info);\n\n const btnGroup = document.createElement('span');\n btnGroup.className = 'oc-table-pagination-btns';\n\n const prevBtn = document.createElement('button');\n prevBtn.setAttribute('aria-label', 'Previous page');\n prevBtn.setAttribute('data-page-action', 'prev');\n prevBtn.textContent = 'Prev';\n prevBtn.disabled = page <= 0;\n btnGroup.appendChild(prevBtn);\n\n const nextBtn = document.createElement('button');\n nextBtn.setAttribute('aria-label', 'Next page');\n nextBtn.setAttribute('data-page-action', 'next');\n nextBtn.textContent = 'Next';\n nextBtn.disabled = page >= totalPages - 1;\n btnGroup.appendChild(nextBtn);\n\n div.appendChild(btnGroup);\n return div;\n}\n\n// ---------------------------------------------------------------------------\n// Empty state\n// ---------------------------------------------------------------------------\n\nfunction renderEmptyState(message: string): HTMLDivElement {\n const div = document.createElement('div');\n div.className = 'oc-table-empty';\n div.setAttribute('aria-live', 'polite');\n div.textContent = message;\n return div;\n}\n\n// ---------------------------------------------------------------------------\n// Main render\n// ---------------------------------------------------------------------------\n\n/**\n * Render a TableLayout into a full DOM structure.\n *\n * @param layout - The compiled table layout.\n * @param container - The container element to render into.\n * @returns The wrapper element that was created.\n */\nexport function renderTable(\n layout: TableLayout,\n container: HTMLElement,\n opts?: TableRenderOptions,\n): HTMLElement {\n const wrapper = document.createElement('div');\n wrapper.className = 'oc-table-wrapper';\n\n // Apply theme colors as CSS custom properties so table CSS picks them up.\n // Without this, dark-background themes show invisible text since the\n // CSS defaults (--oc-text etc.) are light-mode values.\n const { theme, chrome } = layout;\n if (theme) {\n const s = wrapper.style;\n s.setProperty('--oc-bg', theme.colors.background);\n s.setProperty('--oc-text', theme.colors.text);\n s.setProperty('--oc-text-secondary', theme.colors.axis ?? theme.colors.text);\n s.setProperty('--oc-text-muted', theme.colors.axis ?? theme.colors.text);\n s.setProperty('--oc-gridline', theme.colors.gridline);\n s.setProperty('--oc-border', theme.colors.gridline);\n s.setProperty('--oc-font-family', theme.fonts.family);\n s.fontFamily = theme.fonts.family;\n }\n\n // Set computed chrome CSS custom properties so chrome elements pick up\n // theme-resolved values via CSS fallbacks (e.g. --oc-title-computed-size).\n {\n const s = wrapper.style;\n if (chrome.title) {\n s.setProperty('--oc-title-computed-size', `${chrome.title.style.fontSize}px`);\n s.setProperty('--oc-title-computed-weight', String(chrome.title.style.fontWeight));\n s.setProperty('--oc-title-computed-color', chrome.title.style.fill);\n }\n if (chrome.subtitle) {\n s.setProperty('--oc-subtitle-computed-size', `${chrome.subtitle.style.fontSize}px`);\n s.setProperty('--oc-subtitle-computed-weight', String(chrome.subtitle.style.fontWeight));\n s.setProperty('--oc-subtitle-computed-color', chrome.subtitle.style.fill);\n }\n if (chrome.source) {\n s.setProperty('--oc-source-computed-size', `${chrome.source.style.fontSize}px`);\n s.setProperty('--oc-source-computed-color', chrome.source.style.fill);\n }\n if (chrome.footer) {\n s.setProperty('--oc-footer-computed-size', `${chrome.footer.style.fontSize}px`);\n s.setProperty('--oc-footer-computed-color', chrome.footer.style.fill);\n }\n }\n\n // Apply class modifiers\n if (layout.compact) {\n wrapper.classList.add('oc-table--compact');\n }\n\n // Header chrome\n const headerChrome = renderChromeBlock(layout, 'header');\n if (headerChrome) {\n wrapper.appendChild(headerChrome);\n }\n\n // Search bar\n const searchBar = renderSearchBar(layout);\n if (searchBar) {\n wrapper.appendChild(searchBar);\n }\n\n // Handle empty data\n if (layout.rows.length === 0) {\n const message = layout.search.query ? 'No results found' : 'No data';\n wrapper.appendChild(renderEmptyState(message));\n } else {\n // Scroll container\n const scroll = document.createElement('div');\n scroll.className = 'oc-table-scroll';\n\n // Table\n const table = document.createElement('table');\n table.setAttribute('role', 'grid');\n table.setAttribute('aria-label', layout.a11y.caption);\n\n if (layout.stickyFirstColumn) {\n table.classList.add('oc-table--sticky');\n }\n\n // Caption (screen reader only – inline styles ensure hiding even without\n // the external stylesheet, e.g. CDN / esm.sh usage)\n const caption = document.createElement('caption');\n caption.className = 'oc-sr-only';\n applySrOnlyStyles(caption);\n caption.textContent = layout.a11y.summary;\n table.appendChild(caption);\n\n // Thead\n table.appendChild(renderThead(layout.columns, layout.sort));\n\n // Tbody\n table.appendChild(renderTbody(layout.rows, layout.columns));\n\n scroll.appendChild(table);\n wrapper.appendChild(scroll);\n }\n\n // Pagination\n const pagination = renderPagination(layout);\n if (pagination) {\n wrapper.appendChild(pagination);\n }\n\n // Footer row: source/footer chrome on the left, brand watermark on the\n // right, laid out on a single line via flex. Built up here and appended\n // after the live region so source text and watermark share a baseline.\n const footerRow = document.createElement('div');\n footerRow.className = 'oc-table-footer-row';\n\n const footerChrome = renderChromeBlock(layout, 'footer');\n if (footerChrome) {\n footerRow.appendChild(footerChrome);\n }\n\n // Live region for screen reader announcements (sort changes, search results)\n const liveRegion = document.createElement('div');\n liveRegion.className = 'oc-table-live-region oc-sr-only';\n applySrOnlyStyles(liveRegion);\n liveRegion.setAttribute('aria-live', 'polite');\n liveRegion.setAttribute('aria-atomic', 'true');\n liveRegion.setAttribute('role', 'status');\n wrapper.appendChild(liveRegion);\n\n // Brand watermark — joins the footer row, pushed to the right by the\n // flex container so it sits on the same line as the source text.\n if (layout.watermark) {\n const brandColor = theme ? theme.colors.axis : '#999999';\n const brand = document.createElement('div');\n brand.className = 'oc-table-ref';\n const brandLink = document.createElement('a');\n brandLink.href = BRAND_URL;\n brandLink.target = '_blank';\n brandLink.rel = 'noopener';\n brandLink.style.cssText = `font-size: ${BRAND_FONT_SIZE}px; font-weight: 600; color: ${brandColor}; opacity: 0.55; text-decoration: none; font-family: ${theme ? theme.fonts.family : 'sans-serif'};`;\n brandLink.textContent = 'tryOpenData.ai';\n brand.appendChild(brandLink);\n footerRow.appendChild(brand);\n }\n\n // Append the footer row only when it has content (source/footer/watermark).\n if (footerRow.childElementCount > 0) {\n wrapper.appendChild(footerRow);\n }\n\n // Animation: stamp CSS custom properties and add oc-animate class BEFORE\n // DOM insertion to avoid a flash of final state.\n if (opts?.animate && layout.animation?.enter) {\n const enterPhase = layout.animation.enter;\n const rowCount = layout.rows.length;\n const stagger = clampStaggerDelay(enterPhase.staggerDelay, rowCount);\n stampAnimationVars(wrapper, {\n duration: enterPhase.duration,\n stagger,\n ease: enterPhase.ease,\n });\n wrapper.classList.add('oc-animate');\n }\n\n container.appendChild(wrapper);\n return wrapper;\n}\n","/**\n * TileMap mount API: the main entry point for vanilla JS tilemap usage.\n *\n * createTileMap() takes a container, TileMapSpec, and options, compiles the\n * tilemap, renders it as SVG, sets up responsive resizing, tooltip interaction,\n * and returns a TileMapInstance with update/resize/export/destroy.\n */\n\nimport type {\n CompileOptions,\n DarkMode,\n ThemeConfig,\n TileMapLayout,\n TileMapSpec,\n} from '@opendata-ai/openchart-core';\nimport { BREAKPOINT_COMPACT_MAX, BREAKPOINT_MEDIUM_MAX } from '@opendata-ai/openchart-core';\nimport { compileTileMap } from '@opendata-ai/openchart-engine';\nimport { cancelAnimations, setupAnimationCleanup } from './animation';\nimport {\n exportJPG,\n exportPNG,\n exportSVG,\n exportSVGWithFonts,\n type JPGExportOptions,\n type SVGExportOptions,\n} from './export';\nimport { createMeasureText, resolveFontFamily, scheduleFontReload } from './measure-text';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { renderTileMapSVG } from './tilemap-renderer';\nimport { createTooltipManager, type TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Responsive tile sizing\n// ---------------------------------------------------------------------------\n\n// The state grid is 8 rows tall (see engine/tilemap/layout.ts GRID_ROWS).\n// TILEMAP_TILE_GAP must match the gap passed to computeTilePositions in\n// compile-tilemap.ts (currently 5) so the mount's height budget matches the\n// grid the compiler builds.\nconst TILEMAP_GRID_ROWS = 8;\nconst TILEMAP_TILE_GAP = 5;\n// Generous vertical reserve for chrome (title/subtitle/source) + padding +\n// legend. Intentionally over-reserved: on narrow widths we WANT tile size to be\n// bound by width, so any excess height budget just lets the map grow taller\n// (which is the desired mobile behavior) rather than starving the tiles.\nconst TILEMAP_CHROME_RESERVE = 180;\n\n/**\n * Target tile size (px) for a given container width. Drives the height budget\n * on non-desktop widths so the 8-row grid isn't starved to tiny tiles after\n * chrome is subtracted from a square drawing area. Width still caps the actual\n * tile size, so these are ceilings the layout grows toward, not guarantees.\n */\nfunction pickTargetTileSize(width: number): number {\n if (width < BREAKPOINT_COMPACT_MAX) return 34; // compact / mobile\n return 40; // medium (tablet, sidebars)\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TileMapMountOptions {\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode setting: \"auto\" (system pref), \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Enable responsive resizing. Defaults to true. */\n responsive?: boolean;\n /** Show the tryOpenData.ai watermark. Defaults to true. */\n watermark?: boolean;\n /** Show tooltips on hover. Defaults to true. */\n tooltip?: boolean;\n /** Callback when a tile is clicked. */\n onTileClick?: (tile: {\n stateCode: string;\n stateName: string;\n value: number | null;\n data: Record<string, unknown>;\n }) => void;\n /** Callback when a tile is hovered (null on mouse leave). */\n onTileHover?: (\n tile: {\n stateCode: string;\n stateName: string;\n value: number | null;\n data: Record<string, unknown>;\n } | null,\n ) => void;\n}\n\nexport interface TileMapInstance {\n /** Re-compile and re-render with a new spec. */\n update(spec: TileMapSpec): void;\n /** Re-compile at current container dimensions. */\n resize(): void;\n /** Export the tilemap. */\n export(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options?: JPGExportOptions | SVGExportOptions,\n ): string | Promise<Blob> | Promise<string>;\n /** Remove all DOM elements and disconnect observers. */\n destroy(): void;\n /** The current compiled layout. */\n readonly layout: TileMapLayout;\n}\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a tilemap instance from a spec and mount it into a container.\n */\nexport function createTileMap(\n container: HTMLElement,\n spec: TileMapSpec,\n options?: TileMapMountOptions,\n): TileMapInstance {\n let currentSpec = spec;\n let currentLayout: TileMapLayout;\n let destroyed = false;\n\n // DOM\n let svgElement: SVGSVGElement | null = null;\n\n // Subsystems\n let tooltipManager: TooltipManager | null = null;\n let cleanupTooltipEvents: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n\n // Animation state\n let animationCleanup: (() => void) | null = null;\n let pendingResize = false;\n\n // Latches whether the host constrains our height. Measured once, on the\n // first render that produces a real rect: height 0 with a real width means\n // the host is auto-height and we pick our own budget. Any other height is\n // an explicit box we must fit inside. Re-reading this after the first paint\n // would just measure our own SVG, so the value has to latch.\n let isAutoHeight: boolean | null = null;\n\n // Set when webfonts have loaded and a recompile is owed. The next render()\n // that recompiles flips data-oc-fonts-state to 'ready' and clears this, so\n // the attribute stays honest when resize() defers to pendingResize during\n // the entrance animation.\n let fontsReloadPending = false;\n\n // Apply the root class up front so getComputedStyle sees --oc-font-family\n // before the text measurer is built.\n container.classList.add('oc-tilemap-root');\n\n // Resolve the effective font the way compile() will: compile merges\n // { ...spec.theme, ...options.theme }, so options.theme wins over the\n // spec-level theme; fall back to the container's computed font. Measuring a\n // different font than gets rendered desyncs layout metrics and the reload watcher.\n function resolveEffectiveFont(): string {\n return (\n options?.theme?.fonts?.family ??\n currentSpec.theme?.fonts?.family ??\n resolveFontFamily(container)\n );\n }\n let fontFamily = resolveEffectiveFont();\n let measureText = createMeasureText(fontFamily);\n let renderGen = 0;\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n const width = Math.max(rect.width || 600, 100);\n\n if (isAutoHeight === null && (rect.width > 0 || rect.height > 0)) {\n isAutoHeight = rect.height === 0 && rect.width > 0;\n }\n\n // An explicit host height is a hard budget: the compiler derives a tight\n // content height from whatever we pass, so handing it the container's own\n // height keeps the SVG inside its box instead of overflowing onto whatever\n // sits below it.\n if (isAutoHeight === false && rect.height > 0) {\n return { width, height: Math.max(rect.height, 100) };\n }\n\n // Auto-height host: pick our own budget. The compiler derives its own tight\n // content height and treats the passed height as a cap. On desktop, a square\n // budget (height = width) keeps tiles from ballooning (the 8-row grid becomes\n // the binding constraint at ~width/8). Below desktop, a square budget starves\n // the grid after chrome, so give it a height budget sized to a comfortable\n // target tile instead — the map then grows taller as needed rather than\n // shrinking the tiles.\n if (width >= BREAKPOINT_MEDIUM_MAX) {\n return { width, height: width };\n }\n const targetTile = pickTargetTileSize(width);\n const height =\n targetTile * TILEMAP_GRID_ROWS +\n TILEMAP_TILE_GAP * (TILEMAP_GRID_ROWS - 1) +\n TILEMAP_CHROME_RESERVE;\n return { width, height };\n }\n\n function compile(): TileMapLayout {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n measureText,\n };\n\n return compileTileMap(currentSpec, compileOpts);\n }\n\n // ---------------------------------------------------------------------------\n // Tooltip and interaction wiring\n // ---------------------------------------------------------------------------\n\n function wireTooltipAndInteraction(svg: SVGSVGElement, layout: TileMapLayout): () => void {\n const cleanups: Array<() => void> = [];\n\n const tilesByStateCode = new Map<string, (typeof layout.tiles)[number]>();\n for (const t of layout.tiles) {\n if (!tilesByStateCode.has(t.stateCode)) tilesByStateCode.set(t.stateCode, t);\n }\n\n // Wire tooltip on tile elements\n const tileElements = svg.querySelectorAll('.oc-tilemap-tile');\n for (const el of tileElements) {\n const stateCode = el.getAttribute('data-state');\n if (!stateCode) continue;\n\n const content = layout.tooltipDescriptors.get(stateCode);\n const tile = tilesByStateCode.get(stateCode);\n\n const handleMouseEnter = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n if (content && tooltipManager && options?.tooltip !== false) {\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n if (tile) {\n options?.onTileHover?.({\n stateCode: tile.stateCode,\n stateName: tile.data.stateName as string,\n value: tile.value,\n data: tile.data,\n });\n }\n };\n\n const handleMouseMove = (e: Event) => {\n if (content && tooltipManager && options?.tooltip !== false) {\n const mouseEvent = e as MouseEvent;\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n };\n\n const handleMouseLeave = () => {\n tooltipManager?.hide();\n options?.onTileHover?.(null);\n };\n\n const handleClick = () => {\n if (tile) {\n options?.onTileClick?.({\n stateCode: tile.stateCode,\n stateName: tile.data.stateName as string,\n value: tile.value,\n data: tile.data,\n });\n }\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n\n cleanups.push(() => {\n el.removeEventListener('mouseenter', handleMouseEnter);\n el.removeEventListener('mousemove', handleMouseMove);\n el.removeEventListener('mouseleave', handleMouseLeave);\n el.removeEventListener('click', handleClick);\n });\n }\n\n return () => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n };\n }\n\n // ---------------------------------------------------------------------------\n // Render + update\n // ---------------------------------------------------------------------------\n\n function render(animate = false): void {\n // Cancel running animations\n if (animationCleanup) {\n animationCleanup();\n animationCleanup = null;\n }\n\n // Remove old SVG\n if (svgElement) {\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n svgElement.remove();\n }\n\n // Render new SVG\n const newSvg = renderTileMapSVG(currentLayout, { animate });\n container.appendChild(newSvg);\n svgElement = newSvg;\n\n // Wire interactions\n cleanupTooltipEvents = wireTooltipAndInteraction(newSvg, currentLayout);\n\n // Setup tooltips if enabled\n if (options?.tooltip !== false) {\n if (!tooltipManager) {\n tooltipManager = createTooltipManager(container);\n }\n }\n\n // Setup animation cleanup only when actually animating\n if (animate && currentLayout.animation?.enter) {\n animationCleanup = setupAnimationCleanup(newSvg, () => {\n // On animation complete, check if resize was pending\n if (pendingResize && !destroyed) {\n pendingResize = false;\n resize();\n }\n });\n }\n\n renderGen += 1;\n container.dataset.ocRenderGen = String(renderGen);\n\n // This render recompiled with the loaded webfonts; publish 'ready' now\n // rather than right after the fonts-ready resize() (which may have deferred\n // to pendingResize during the entrance animation).\n if (fontsReloadPending) {\n fontsReloadPending = false;\n container.dataset.ocFontsState = 'ready';\n }\n }\n\n function update(newSpec: TileMapSpec): void {\n currentSpec = newSpec;\n // A new spec can change theme.fonts.family; rebuild the measurer so layout\n // measures the font compile will actually render with.\n const nextFont = resolveEffectiveFont();\n if (nextFont !== fontFamily) {\n fontFamily = nextFont;\n measureText = createMeasureText(fontFamily);\n }\n currentLayout = compile();\n render();\n }\n\n function resize(): void {\n if (destroyed) return;\n\n // If animation is running, queue the resize for after it completes\n if (animationCleanup) {\n pendingResize = true;\n return;\n }\n\n currentLayout = compile();\n render();\n }\n\n // ---------------------------------------------------------------------------\n // Export\n // ---------------------------------------------------------------------------\n\n function exportChart(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options_?: JPGExportOptions | SVGExportOptions,\n ): string | Promise<Blob> | Promise<string> {\n if (!svgElement) return '';\n\n switch (format) {\n case 'svg':\n return exportSVG(svgElement);\n case 'svg-with-fonts':\n return exportSVGWithFonts(svgElement);\n case 'png':\n return exportPNG(svgElement, options_ as JPGExportOptions);\n case 'jpg':\n return exportJPG(svgElement, options_ as JPGExportOptions);\n default:\n return '';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Destroy\n // ---------------------------------------------------------------------------\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n // Cancel running animations\n if (animationCleanup) {\n cancelAnimations(svgElement);\n animationCleanup();\n animationCleanup = null;\n }\n\n // Clean up events\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n\n // Remove SVG\n if (svgElement) {\n svgElement.remove();\n svgElement = null;\n }\n\n // Unmount tooltip manager\n if (tooltipManager) {\n tooltipManager.destroy();\n tooltipManager = null;\n }\n\n // Disconnect resize observer\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n // Remove root classes\n container.classList.remove('oc-tilemap-root', 'oc-dark');\n }\n\n // ---------------------------------------------------------------------------\n // Initialize\n // ---------------------------------------------------------------------------\n\n // Root class was applied before the measurer was built (see above); dark\n // mode class still applies here.\n if (resolveDarkMode(options?.darkMode)) {\n container.classList.add('oc-dark');\n }\n\n // Initial compile and render (animate on first mount)\n currentLayout = compile();\n render(true);\n\n // Recompile once after webfonts load so labels aren't stuck measured\n // against fallback metrics on real devices.\n const fontsPending = scheduleFontReload(\n fontFamily,\n () => !destroyed,\n () => {\n // Mark the reload owed, then recompile. render() flips the attribute to\n // 'ready' once it actually recompiles, including the pendingResize replay\n // after the entrance animation finishes.\n fontsReloadPending = true;\n resize();\n },\n );\n container.dataset.ocFontsState = fontsPending ? 'pending' : 'ready';\n\n // Setup responsive resizing\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => {\n resize();\n });\n }\n\n return {\n update,\n resize,\n export: exportChart,\n destroy,\n get layout(): TileMapLayout {\n return currentLayout;\n },\n };\n}\n"],"mappings":"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","names":["live","cx","cy","start","SpatialIndex","SpatialIndex","renderChrome","renderLegend","escapeHtml","set","BRAND_FONT_SIZE","BRAND_MIN_WIDTH","textAscent","SVG_NS","XLINK_NS","BRAND_URL","createSVGElement","setAttrs","textAscent","renderChrome","BRAND_MIN_WIDTH","BRAND_FONT_SIZE","BRAND_FONT_SIZE","clampStaggerDelay","BRAND_URL","div","BRAND_FONT_SIZE","clampStaggerDelay"]}
1
+ {"version":3,"sources":["../src/barlist-mount.ts","../src/barlist-renderer.ts","../src/export-sequence.ts","../src/graph/simulation-worker-url.ts","../src/graph-mount.ts","../src/graph/camera.ts","../src/graph/zoom.ts","../src/graph/canvas-renderer.ts","../src/graph/entrance.ts","../src/graph/focus-transition.ts","../src/graph/interaction.ts","../src/graph/keyboard.ts","../src/graph/legend.ts","../src/graph/search.ts","../src/graph/seed.ts","../src/graph/simulation.ts","../src/graph/spatial-index.ts","../src/graph/update-diff-config.ts","../src/graph/update-diff.ts","../src/renderers/table-cells.ts","../src/sankey-mount.ts","../src/sankey-renderer.ts","../src/table-keyboard.ts","../src/table-mount.ts","../src/table-renderer.ts","../src/tilemap-mount.ts"],"sourcesContent":["/**\n * BarList mount API: the main entry point for vanilla JS barlist usage.\n *\n * createBarList() takes a container, BarListSpec, and options, compiles the\n * barlist, renders it as SVG, sets up responsive resizing, tooltip interaction,\n * and returns a BarListInstance with update/resize/export/destroy.\n */\n\nimport type {\n BarListLayout,\n BarListSpec,\n CompileOptions,\n DarkMode,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { compileBarList } from '@opendata-ai/openchart-engine';\nimport { cancelAnimations, setupAnimationCleanup } from './animation';\nimport { renderBarListSVG } from './barlist-renderer';\nimport {\n exportJPG,\n exportPNG,\n exportSVG,\n exportSVGWithFonts,\n type JPGExportOptions,\n type SVGExportOptions,\n} from './export';\nimport { createMeasureText, resolveFontFamily, scheduleFontReload } from './measure-text';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { createTooltipManager, type TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface BarListMountOptions {\n theme?: ThemeConfig;\n darkMode?: DarkMode;\n responsive?: boolean;\n watermark?: boolean;\n tooltip?: boolean;\n onRowClick?: (row: { label: string; value: number; data: Record<string, unknown> }) => void;\n onRowHover?: (\n row: { label: string; value: number; data: Record<string, unknown> } | null,\n ) => void;\n}\n\nexport interface BarListInstance {\n update(spec: BarListSpec): void;\n resize(): void;\n export(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options?: JPGExportOptions | SVGExportOptions,\n ): string | Promise<Blob> | Promise<string>;\n destroy(): void;\n readonly layout: BarListLayout;\n}\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\nexport function createBarList(\n container: HTMLElement,\n spec: BarListSpec,\n options?: BarListMountOptions,\n): BarListInstance {\n let currentSpec = spec;\n let currentLayout: BarListLayout;\n let destroyed = false;\n\n let svgElement: SVGSVGElement | null = null;\n let tooltipManager: TooltipManager | null = null;\n let cleanupTooltipEvents: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n let animationCleanup: (() => void) | null = null;\n let pendingResize = false;\n\n // Set when webfonts have loaded and a recompile is owed. The next render()\n // that recompiles flips data-oc-fonts-state to 'ready' and clears this, so\n // the attribute stays honest when resize() defers to pendingResize during\n // the entrance animation.\n let fontsReloadPending = false;\n\n // Apply the root class up front so getComputedStyle sees --oc-font-family\n // before the text measurer is built.\n container.classList.add('oc-barlist-root');\n\n // Resolve the effective font the way compile() will: compile merges\n // { ...spec.theme, ...options.theme }, so options.theme wins over the\n // spec-level theme; fall back to the container's computed font. Measuring a\n // different font than gets rendered desyncs layout metrics and the reload watcher.\n function resolveEffectiveFont(): string {\n return (\n options?.theme?.fonts?.family ??\n currentSpec.theme?.fonts?.family ??\n resolveFontFamily(container)\n );\n }\n let fontFamily = resolveEffectiveFont();\n let measureText = createMeasureText(fontFamily);\n let renderGen = 0;\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n function compile(): BarListLayout {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n measureText,\n };\n\n const layout = compileBarList(currentSpec, compileOpts);\n\n // Auto-size height to content so few-row lists don't leave empty space.\n if (layout.rows.length > 0) {\n const lastRow = layout.rows[layout.rows.length - 1];\n const contentBottom =\n lastRow.y + lastRow.height + layout.chrome.bottomHeight + layout.theme.spacing.padding;\n if (contentBottom < layout.height) {\n layout.height = contentBottom;\n layout.area.height =\n contentBottom - layout.area.y - layout.chrome.bottomHeight - layout.theme.spacing.padding;\n }\n }\n\n return layout;\n }\n\n function wireTooltipAndInteraction(svg: SVGSVGElement, layout: BarListLayout): () => void {\n const cleanups: Array<() => void> = [];\n\n const rowElements = svg.querySelectorAll('.oc-barlist-row');\n for (const el of rowElements) {\n const indexStr = el.getAttribute('data-row-index');\n if (indexStr === null) continue;\n\n const content = layout.tooltipDescriptors.get(indexStr);\n const row = layout.rows[Number(indexStr)];\n\n const handleMouseEnter = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n if (content && tooltipManager && options?.tooltip !== false) {\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n if (row) {\n options?.onRowHover?.({\n label: row.label.text,\n value: row.value,\n data: row.data,\n });\n }\n };\n\n const handleMouseMove = (e: Event) => {\n if (content && tooltipManager && options?.tooltip !== false) {\n const mouseEvent = e as MouseEvent;\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n };\n\n const handleMouseLeave = () => {\n tooltipManager?.hide();\n options?.onRowHover?.(null);\n };\n\n const handleClick = () => {\n if (row) {\n options?.onRowClick?.({\n label: row.label.text,\n value: row.value,\n data: row.data,\n });\n }\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n\n cleanups.push(() => {\n el.removeEventListener('mouseenter', handleMouseEnter);\n el.removeEventListener('mousemove', handleMouseMove);\n el.removeEventListener('mouseleave', handleMouseLeave);\n el.removeEventListener('click', handleClick);\n });\n }\n\n return () => {\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n function render(animate = false): void {\n if (animationCleanup) {\n animationCleanup();\n animationCleanup = null;\n }\n\n if (svgElement) {\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n svgElement.remove();\n }\n\n const newSvg = renderBarListSVG(currentLayout, { animate });\n container.appendChild(newSvg);\n svgElement = newSvg;\n\n cleanupTooltipEvents = wireTooltipAndInteraction(newSvg, currentLayout);\n\n if (options?.tooltip !== false) {\n if (!tooltipManager) {\n tooltipManager = createTooltipManager(container);\n }\n }\n\n if (currentLayout.animation?.enter) {\n animationCleanup = setupAnimationCleanup(newSvg, () => {\n // Null the handle BEFORE replaying, or the deferred resize hits the\n // entrance-in-flight gate and re-defers forever (mount.ts pattern).\n animationCleanup = null;\n if (pendingResize && !destroyed) {\n pendingResize = false;\n resize();\n }\n });\n }\n\n renderGen += 1;\n container.dataset.ocRenderGen = String(renderGen);\n\n // This render recompiled with the loaded webfonts; publish 'ready' now\n // rather than right after the fonts-ready resize() (which may have deferred\n // to pendingResize during the entrance animation).\n if (fontsReloadPending) {\n fontsReloadPending = false;\n container.dataset.ocFontsState = 'ready';\n }\n }\n\n function update(newSpec: BarListSpec): void {\n currentSpec = newSpec;\n // A new spec can change theme.fonts.family; rebuild the measurer so layout\n // measures the font compile will actually render with.\n const nextFont = resolveEffectiveFont();\n if (nextFont !== fontFamily) {\n fontFamily = nextFont;\n measureText = createMeasureText(fontFamily);\n }\n currentLayout = compile();\n render();\n }\n\n function resize(): void {\n if (destroyed) return;\n if (animationCleanup) {\n pendingResize = true;\n return;\n }\n currentLayout = compile();\n render();\n }\n\n function exportChart(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options_?: JPGExportOptions | SVGExportOptions,\n ): string | Promise<Blob> | Promise<string> {\n if (!svgElement) return '';\n switch (format) {\n case 'svg':\n return exportSVG(svgElement);\n case 'svg-with-fonts':\n return exportSVGWithFonts(svgElement);\n case 'png':\n return exportPNG(svgElement, options_ as JPGExportOptions);\n case 'jpg':\n return exportJPG(svgElement, options_ as JPGExportOptions);\n default:\n return '';\n }\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n if (animationCleanup) {\n cancelAnimations(svgElement);\n animationCleanup();\n animationCleanup = null;\n }\n\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n\n if (svgElement) {\n svgElement.remove();\n svgElement = null;\n }\n\n if (tooltipManager) {\n tooltipManager.destroy();\n tooltipManager = null;\n }\n\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n container.classList.remove('oc-barlist-root', 'oc-dark');\n }\n\n // Initialize\n if (resolveDarkMode(options?.darkMode)) {\n container.classList.add('oc-dark');\n }\n\n currentLayout = compile();\n render(true);\n\n // Recompile once after webfonts load so row labels aren't stuck measured\n // against fallback metrics on real devices.\n const fontsPending = scheduleFontReload(\n fontFamily,\n () => !destroyed,\n () => {\n // Mark the reload owed, then recompile. render() flips the attribute to\n // 'ready' once it actually recompiles, including the pendingResize replay\n // after the entrance animation finishes.\n fontsReloadPending = true;\n resize();\n },\n );\n container.dataset.ocFontsState = fontsPending ? 'pending' : 'ready';\n\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => resize());\n }\n\n return {\n update,\n resize,\n export: exportChart,\n destroy,\n get layout(): BarListLayout {\n return currentLayout;\n },\n };\n}\n","/**\n * BarList SVG renderer: converts a BarListLayout into SVG DOM elements.\n *\n * Creates an <svg> with rows of label + track + bar + value. Animation is\n * pure CSS, driven by data attributes and CSS custom properties.\n */\n\nimport type { BarListLayout, BarListRowMark, ResolvedAnimation } from '@opendata-ai/openchart-core';\nimport { textAscent } from '@opendata-ai/openchart-core';\nimport { stampAnimationVars } from './animation-vars';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst XLINK_NS = 'http://www.w3.org/1999/xlink';\nconst BRAND_URL = 'https://tryopendata.ai';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createSVGElement(tag: string): SVGElement {\n return document.createElementNS(SVG_NS, tag);\n}\n\nfunction setAttrs(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, String(value));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Chrome rendering\n// ---------------------------------------------------------------------------\n\nfunction renderChrome(parent: SVGElement, layout: BarListLayout): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-chrome');\n\n const { chrome } = layout;\n const bottomOffset = layout.area.y + layout.area.height;\n\n for (const key of ['title', 'subtitle', 'source', 'byline', 'footer'] as const) {\n const el = chrome[key];\n if (!el) continue;\n\n const isBottom = key === 'source' || key === 'byline' || key === 'footer';\n const text = createSVGElement('text');\n // el.y is the TOP of the text box; convert to the alphabetic baseline\n // (WebKit mishandles dominant-baseline:hanging, see renderers/chrome.ts).\n setAttrs(text, {\n x: el.x,\n y: (isBottom ? bottomOffset + el.y : el.y) + textAscent(el.style.fontSize),\n });\n text.setAttribute('class', `oc-${key}`);\n text.setAttribute('font-family', el.style.fontFamily);\n text.setAttribute('font-size', String(el.style.fontSize));\n text.setAttribute('font-weight', String(el.style.fontWeight));\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', el.style.fill);\n text.textContent = el.text;\n g.appendChild(text);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Watermark rendering\n// ---------------------------------------------------------------------------\n\nfunction renderWatermark(parent: SVGElement, layout: BarListLayout): void {\n if (layout.width < 480) return;\n\n const { width, height, theme } = layout;\n const padding = theme.spacing.padding;\n const rightEdge = width - padding;\n const bottomEdge = height - padding;\n const fill = theme.colors.axis;\n\n const a = createSVGElement('a');\n a.setAttribute('href', BRAND_URL);\n a.setAttributeNS(XLINK_NS, 'xlink:href', BRAND_URL);\n a.setAttribute('target', '_blank');\n a.setAttribute('rel', 'noopener');\n a.setAttribute('class', 'oc-chrome-ref');\n\n const text = createSVGElement('text');\n setAttrs(text, {\n x: rightEdge,\n y: bottomEdge,\n 'dominant-baseline': 'alphabetic',\n 'text-anchor': 'end',\n 'font-family': theme.fonts.family,\n 'font-size': 12,\n 'fill-opacity': 0.55,\n });\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', fill);\n\n setAttrs(text, { 'font-weight': 600, 'font-size': 16 });\n text.textContent = 'OpenData';\n a.appendChild(text);\n parent.appendChild(a);\n}\n\n// ---------------------------------------------------------------------------\n// Row rendering\n// ---------------------------------------------------------------------------\n\nfunction renderRows(\n parent: SVGElement,\n rows: BarListRowMark[],\n animation?: ResolvedAnimation,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-barlist-rows');\n g.setAttribute('role', 'list');\n\n for (const row of rows) {\n const rowGroup = createSVGElement('g');\n rowGroup.setAttribute('class', 'oc-barlist-row');\n rowGroup.setAttribute('data-row-index', String(row.index));\n rowGroup.setAttribute('role', 'listitem');\n if (row.aria?.label) {\n rowGroup.setAttribute('aria-label', row.aria.label);\n }\n\n if (animation?.enter) {\n rowGroup.setAttribute('data-animation-index', String(row.animationIndex));\n const style = (rowGroup as SVGElement & ElementCSSInlineStyle).style;\n style.setProperty('--oc-mark-index', String(row.animationIndex));\n style.setProperty('--oc-row-delay', `${row.animationIndex * 40}ms`);\n }\n\n // Label text\n const labelEl = createSVGElement('text');\n setAttrs(labelEl, {\n x: row.label.x,\n y: row.label.y,\n 'dominant-baseline': 'central',\n 'text-anchor': 'start',\n 'font-family': row.label.style.fontFamily,\n 'font-size': row.label.style.fontSize,\n 'font-weight': row.label.style.fontWeight,\n });\n (labelEl as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', row.label.style.fill);\n labelEl.textContent = row.label.text;\n rowGroup.appendChild(labelEl);\n\n // Subtitle text (if present)\n if (row.subtitle?.visible) {\n const subEl = createSVGElement('text');\n setAttrs(subEl, {\n x: row.subtitle.x,\n y: row.subtitle.y,\n 'dominant-baseline': 'central',\n 'text-anchor': 'start',\n 'font-family': row.subtitle.style.fontFamily,\n 'font-size': row.subtitle.style.fontSize,\n 'font-weight': row.subtitle.style.fontWeight,\n });\n (subEl as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n row.subtitle.style.fill,\n );\n subEl.textContent = row.subtitle.text;\n rowGroup.appendChild(subEl);\n }\n\n // Track (muted background bar)\n const trackRect = createSVGElement('rect');\n setAttrs(trackRect, {\n x: row.track.x,\n y: row.track.y,\n width: row.track.width,\n height: row.track.height,\n rx: row.track.cornerRadius,\n fill: 'currentColor',\n 'fill-opacity': 0.06,\n });\n trackRect.setAttribute('class', 'oc-barlist-track');\n rowGroup.appendChild(trackRect);\n\n // Fill bar\n const barRect = createSVGElement('rect');\n setAttrs(barRect, {\n x: row.bar.x,\n y: row.bar.y,\n width: row.bar.width,\n height: row.bar.height,\n rx: row.bar.cornerRadius,\n fill: row.bar.fill,\n });\n barRect.setAttribute('class', 'oc-barlist-bar');\n rowGroup.appendChild(barRect);\n\n // Value label (right-aligned)\n const valueEl = createSVGElement('text');\n setAttrs(valueEl, {\n x: row.valueLabel.x,\n y: row.valueLabel.y,\n 'dominant-baseline': 'central',\n 'text-anchor': 'end',\n 'font-family': row.valueLabel.style.fontFamily,\n 'font-size': row.valueLabel.style.fontSize,\n 'font-weight': row.valueLabel.style.fontWeight,\n });\n (valueEl as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n row.valueLabel.style.fill,\n );\n valueEl.textContent = row.valueLabel.text;\n rowGroup.appendChild(valueEl);\n\n g.appendChild(rowGroup);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport function renderBarListSVG(\n layout: BarListLayout,\n opts?: { animate?: boolean },\n): SVGSVGElement {\n const { width, height, rows, a11y, watermark, animation } = layout;\n const animate = opts?.animate && !!animation?.enter;\n\n const svg = createSVGElement('svg') as SVGSVGElement;\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`);\n svg.setAttribute('role', 'list');\n // No explicit width attribute — CSS width:100% on .oc-barlist-root handles it.\n // Explicit pixel height via inline style avoids iOS Safari's height:100% quirk.\n svg.style.height = `${height}px`;\n if (a11y.altText) {\n svg.setAttribute('aria-label', a11y.altText);\n }\n\n const classes = animate ? 'oc-barlist oc-animate' : 'oc-barlist';\n svg.setAttribute('class', classes);\n\n if (animate && animation?.enter) {\n stampAnimationVars(svg, { duration: animation.enter.duration, stagger: 40 });\n }\n\n renderChrome(svg, layout);\n renderRows(svg, rows, animate ? animation : undefined);\n\n if (watermark) {\n renderWatermark(svg, layout);\n }\n\n return svg;\n}\n","/**\n * Multi-spec (\"keyframe\") GIF export.\n *\n * The single-chart GIF export (`exportGIF`) reproduces ONE chart's entrance\n * animation. Some charts animate a different way: a component swaps in a whole\n * new spec on a timer (e.g. a year-by-year stepper), and the on-screen chart\n * tweens between them via `chart.update()` (bars grow/shrink smoothly). The DATA\n * changes across frames, and the motion is the update transition — neither of\n * which `exportGIF` can express.\n *\n * `exportSpecSequence` reproduces that. It mounts one offscreen chart, holds the\n * first spec, then walks the rest by driving `chart.beginManualUpdate(spec)` and\n * stepping the returned transition frame-by-frame (no rAF clock), rasterizing\n * each step. Between transitions it holds the settled frame for `dwellMs`. The\n * result is the same bar tween the reader sees on screen, captured to GIF.\n *\n * `gifenc` is an optional peer dependency, loaded dynamically.\n */\n\nimport type {\n AnimationSpec,\n ChartSpec,\n LayerSpec,\n ResolvedTheme,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport {\n embedFonts,\n ensureSVGDimensions,\n exportSVG,\n getSVGBackgroundColor,\n getSVGDimensions,\n rasterizeSVGToCanvas,\n} from './export';\nimport { isModuleNotFound, resolveRepeat } from './export-gif';\nimport { paletteFromCanvas, readCanvasSRGB } from './gif-encode';\nimport { createChart } from './mount';\nimport { injectThemeStyleBlock } from './theme-style-block';\n\nexport interface SpecSequenceOptions {\n /** DPI scaling factor, matching PNG export. Defaults to 2. */\n dpi?: number;\n /** Frames per second for the tween between specs. Defaults to 25. */\n fps?: number;\n /** How long to hold each spec's settled frame, in ms. Defaults to 1200 (the common stepper cadence). */\n dwellMs?: number;\n /**\n * Loop behavior, same semantics as `exportGIF`:\n * - `false` / omitted: play once, then hold the final frame\n * - `true`: loop forever\n * - a number: loop that many times, then hold\n */\n loop?: boolean | number;\n /** Render width in CSS px. Required — pass the on-screen chart width so the export matches. */\n width: number;\n /** Render height in CSS px. Required — pass the on-screen chart height so the export matches. */\n height: number;\n /** Dark mode for the rendered charts. Defaults to 'off' (light). */\n darkMode?: 'auto' | 'force' | 'off';\n /**\n * Rendering backend for point marks; see `MountOptions.renderer`. Pass the\n * SAME value your on-screen chart mounts with so the export matches.\n */\n renderer?: 'auto' | 'svg' | 'canvas';\n /**\n * Theme overrides applied when rendering, exactly as a consumer's live wrapper\n * would apply them. Pass the SAME theme your on-screen chart uses (e.g. the\n * blog's `blogChartTheme(isDark)`), or the export won't match — its background,\n * gridlines, and chrome colors fall back to openchart's raw defaults.\n */\n theme?: ThemeConfig;\n /** Embed fonts as base64 so text matches on-screen. Defaults to true. */\n embedFonts?: boolean;\n /** Opaque background fill. Defaults to the chart's own background (white if transparent). */\n backgroundColor?: string;\n}\n\n/**\n * Force `enter:false` (each spec must render settled) while KEEPING the author's\n * own update config when they wrote one as an object. Replacing it wholesale\n * would drop fields like `update.maxMarks`, so a spec that deliberately raised\n * the tween cap above 500 marks would silently swap instead of tween on export.\n */\nexport function settleAnimation(spec: ChartSpec | LayerSpec): ChartSpec | LayerSpec {\n const authored = (spec as { animation?: AnimationSpec }).animation;\n const update =\n typeof authored === 'object' && typeof authored.update === 'object'\n ? { ...authored.update }\n : true;\n return {\n ...(spec as unknown as Record<string, unknown>),\n animation: { enter: false, update },\n } as unknown as ChartSpec | LayerSpec;\n}\n\n/**\n * Mount one live chart offscreen (used as the persistent host we step through\n * every spec). Entrance is disabled so the first spec renders settled; update\n * transitions are enabled so `beginManualUpdate` can tween between specs.\n */\nfunction mountOffscreen(\n spec: ChartSpec | LayerSpec,\n width: number,\n height: number,\n darkMode: 'auto' | 'force' | 'off',\n themeConfig: ThemeConfig | undefined,\n renderer: 'auto' | 'svg' | 'canvas' | undefined,\n): {\n instance: ReturnType<typeof createChart>;\n svg: SVGElement;\n container: HTMLElement;\n} {\n const container = document.createElement('div');\n // Offscreen but laid out, so text measurement (needs a live DOM) works.\n container.style.cssText = `position:absolute;left:-99999px;top:0;width:${width}px;height:${height}px;visibility:hidden;`;\n document.body.appendChild(container);\n\n // enter:false → first spec settled immediately; update:true → tween on\n // beginManualUpdate. responsive:false → no ResizeObserver on a detached node.\n const settledSpec = settleAnimation(spec);\n let instance: ReturnType<typeof createChart>;\n try {\n instance = createChart(container, settledSpec, {\n darkMode,\n theme: themeConfig,\n renderer,\n responsive: false,\n });\n } catch (err) {\n // A spec that fails to compile must not leak the offscreen container.\n container.remove();\n throw err;\n }\n\n const svg = container.querySelector('svg');\n if (!svg) {\n instance.destroy();\n container.remove();\n throw new Error('exportSpecSequence: chart produced no SVG');\n }\n return { instance, svg, container };\n}\n\n/**\n * Render an ordered list of specs to an animated GIF Blob. Holds the first spec,\n * then tweens to each subsequent spec (openchart's update transition, driven\n * frame-by-frame) and holds it — reproducing the on-screen bar grow/shrink.\n *\n * @param specs - Ordered specs, one per keyframe (e.g. one per year).\n * @param options - dpi, fps, dwell, loop, size (required), dark mode, theme, fonts.\n * @returns A Promise resolving to the GIF Blob.\n */\nexport async function exportSpecSequence(\n specs: Array<ChartSpec | LayerSpec>,\n options: SpecSequenceOptions,\n): Promise<Blob> {\n if (specs.length === 0) {\n throw new Error('exportSpecSequence requires at least one spec');\n }\n\n let gifenc: typeof import('gifenc');\n try {\n gifenc = await import('gifenc');\n } catch (err) {\n if (isModuleNotFound(err)) {\n throw new Error(\n \"GIF export requires the optional 'gifenc' peer dependency. Install it: npm install gifenc\",\n );\n }\n throw new Error('GIF export failed to load the gifenc encoder', {\n cause: err,\n });\n }\n const { GIFEncoder, quantize, applyPalette } = gifenc;\n\n const dpi = options.dpi ?? 2;\n const fps = options.fps ?? 25;\n const dwellMs = options.dwellMs ?? 1200;\n const frameDelay = Math.round(1000 / fps);\n const { width, height } = options;\n const darkMode = options.darkMode ?? 'off';\n const themeConfig = options.theme;\n const shouldEmbed = options.embedFonts ?? true;\n const repeat = resolveRepeat(options.loop);\n\n const { instance, svg, container } = mountOffscreen(\n specs[0],\n width,\n height,\n darkMode,\n themeConfig,\n options.renderer,\n );\n\n try {\n const w = getSVGDimensions(svg).width || width;\n const h = getSVGDimensions(svg).height || height;\n const theme = (): ResolvedTheme | undefined => {\n const layout = instance.layout;\n return layout && 'theme' in layout ? (layout.theme as ResolvedTheme) : undefined;\n };\n // The theme background is the fallback: canvas mark mode suppresses the SVG\n // background rect, so there's nothing in the markup to read it from.\n const bgColor =\n options.backgroundColor ?? getSVGBackgroundColor(svg, theme()?.colors.background);\n const fillBackground = (ctx: CanvasRenderingContext2D, cv: HTMLCanvasElement): void => {\n ctx.fillStyle = bgColor;\n ctx.fillRect(0, 0, cv.width, cv.height);\n };\n\n // `beginManualUpdate`/`render` REPLACES the live <svg> node each spec, so the\n // injected theme + @font-face blocks don't carry over. Re-inject them into\n // whatever <svg> is currently live. Fonts embed once per spec (not per frame\n // — embedding fetches font files, which across a tween would be far too slow).\n const prepareLiveSvg = async (): Promise<SVGElement> => {\n const live = container.querySelector('svg');\n if (!live) throw new Error('exportSpecSequence: live SVG vanished mid-sequence');\n injectThemeStyleBlock(live, theme());\n if (shouldEmbed) await embedFonts(live);\n return live;\n };\n\n // Rasterize the given live svg at its current (stepped) state.\n const grab = async (live: SVGElement): Promise<HTMLCanvasElement> => {\n const svgString = ensureSVGDimensions(exportSVG(live), w, h);\n return rasterizeSVGToCanvas(svgString, w, h, dpi, fillBackground);\n };\n\n // frames: each canvas with its hold duration. Hold the first spec, then for\n // each subsequent spec step its tween (frameDelay each) and hold the result.\n const frames: Array<{ canvas: HTMLCanvasElement; delayMs: number }> = [];\n let live = await prepareLiveSvg();\n frames.push({ canvas: await grab(live), delayMs: dwellMs });\n\n for (let i = 1; i < specs.length; i++) {\n // Re-apply the same settle/animation wrapper the offscreen mount used, so\n // update transitions stay enabled for each step.\n const nextSpec = settleAnimation(specs[i]);\n const handle = instance.beginManualUpdate(nextSpec);\n // render() replaced the <svg>; re-inject theme/fonts into the new one.\n live = await prepareLiveSvg();\n\n if (handle) {\n const totalMs = handle.totalMs;\n // Step across the tween at fps; capture each frame. Start one frame in\n // (t=0 is the previous held frame) and go through the final step.\n for (let t = frameDelay; t < totalMs; t += frameDelay) {\n handle.step(t);\n frames.push({ canvas: await grab(live), delayMs: frameDelay });\n }\n // Final settled frame of this spec, held for the dwell.\n handle.step(totalMs);\n handle.cancel();\n }\n frames.push({ canvas: await grab(live), delayMs: dwellMs });\n }\n\n // One global palette from the last frame — a full-color settled frame, and\n // stepper frames share a palette by construction.\n const globalPalette = paletteFromCanvas(frames[frames.length - 1].canvas, quantize);\n\n const gif = GIFEncoder();\n frames.forEach(({ canvas, delayMs }, i) => {\n const index = applyPalette(readCanvasSRGB(canvas), globalPalette);\n gif.writeFrame(index, canvas.width, canvas.height, {\n palette: globalPalette,\n delay: delayMs,\n // `repeat` writes the loop extension; only meaningful on the first frame.\n ...(i === 0 ? { repeat } : {}),\n });\n });\n\n gif.finish();\n return new Blob([gif.bytes() as BlobPart], { type: 'image/gif' });\n } finally {\n instance.destroy();\n container.remove();\n }\n}\n","/**\n * Creates a Web Worker running the force simulation.\n *\n * References the built .js file via `new URL` + `import.meta.url`.\n * The consuming app's bundler resolves the worker path at build time.\n *\n * Note: SimulationManager handles .js/.ts fallback internally. This\n * helper is exported for consumers who want to manage the worker directly.\n */\nexport function createSimulationWorker(): Worker {\n return new Worker(new URL('./simulation-worker.js', import.meta.url), { type: 'module' });\n}\n","/**\n * Graph mount API: the main entry point for vanilla JS graph usage.\n *\n * createGraph() takes a container, GraphSpec, and options, compiles the graph,\n * creates a force simulation, canvas renderer, spatial index, interaction\n * manager, and search manager, then runs an animation loop driven by\n * simulation ticks. Returns a GraphInstance with update/search/zoom/destroy.\n */\n\nimport type {\n CompileOptions,\n DarkMode,\n GraphSpec,\n ThemeConfig,\n TooltipContent,\n} from '@opendata-ai/openchart-core';\nimport type {\n CompiledGraphEdge,\n CompiledGraphNode,\n GraphCompilation,\n} from '@opendata-ai/openchart-engine';\nimport { buildEdgeTooltip, compileGraph } from '@opendata-ai/openchart-engine';\nimport {\n type CameraFlightOptions,\n clampK,\n createCameraFlight,\n createCameraFollow,\n} from './graph/camera';\nimport { GraphCanvasRenderer } from './graph/canvas-renderer';\nimport { ENTRANCE_STAGGER_MAX_NODES, entranceOffsets, entranceOrder } from './graph/entrance';\nimport {\n composeStandingFocus,\n type FocusSnapshot,\n FocusTransition,\n layerHoverFocus,\n} from './graph/focus-transition';\nimport { GraphInteractionManager } from './graph/interaction';\nimport { attachGraphKeyboardNav } from './graph/keyboard';\nimport { createGraphLegend, type GraphLegendController } from './graph/legend';\nimport { createTween, prefersReducedMotion, resolveEase } from './graph/motion';\nimport { AnimationScheduler, type GraphAnimation } from './graph/scheduler';\nimport { GraphSearchManager } from './graph/search';\nimport { seedNodePositions } from './graph/seed';\nimport { SimulationManager } from './graph/simulation';\nimport { SpatialIndex } from './graph/spatial-index';\nimport type { GraphRenderState, PositionedEdge, PositionedNode } from './graph/types';\nimport { diffGraphUpdate } from './graph/update-diff';\nimport type { SimEdge, SimNode } from './graph/worker-protocol';\nimport { ZoomTransform } from './graph/zoom';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { createTooltipManager, type TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A programmatic highlight target. Resolved once at call time into a node id set\n * and rendered through the focus model (eased crossfade). See {@link GraphInstance.highlight}.\n */\nexport type GraphHighlightTarget =\n | { nodeIds: string[] }\n | { category: { field: string; value: string | string[] } }\n | { neighborsOf: string; includeSelf?: boolean };\n\n/** A hovered node or edge, passed to a tooltip formatter. */\nexport interface GraphTooltipItem {\n kind: 'node' | 'edge';\n /** The raw datum (node record, or edge record with source/target). */\n data: Record<string, unknown>;\n}\n\n/**\n * Custom tooltip content builder. Receives the hovered item and the library's\n * default {@link TooltipContent}, returns replacement content.\n *\n * Safety contract:\n * - A returned `TooltipContent` or `string` is escaped by the library (strings\n * are inserted via `textContent`, never `innerHTML`).\n * - A returned `HTMLElement` is trusted verbatim — the host owns sanitization.\n * - `null` suppresses the tooltip for that item.\n */\nexport type GraphTooltipFormatter = (\n item: GraphTooltipItem,\n defaults: TooltipContent,\n) => TooltipContent | string | HTMLElement | null;\n\n/** Built-in legend data (headless mirror of the rendered legend). */\nexport interface GraphLegendData {\n field: string | null;\n nodes: Array<{ label: string; color: string; count?: number; active: boolean }>;\n edges: Array<{ label: string; color: string; count?: number }>;\n}\n\nexport interface GraphMountOptions {\n theme?: ThemeConfig;\n darkMode?: DarkMode;\n /**\n * Sink for advisory spec warnings from the compiler (unknown `seedNode` id,\n * deprecations, and so on). Defaults to `console.warn`. Each distinct message\n * is emitted at most once per instance, so a warning that survives every\n * recompile doesn't spam the console on `update()`.\n */\n onWarn?: (message: string) => void;\n responsive?: boolean;\n /** Show the OpenData watermark. Defaults to true. */\n watermark?: boolean;\n /** Show the built-in tooltip; pass an object for a custom formatter. Defaults to true. */\n tooltip?: boolean | { formatter?: GraphTooltipFormatter };\n /**\n * Built-in legend. `true` (default) renders an interactive legend with counts.\n * `false` renders none (set this if you render your own legend). An object\n * toggles interactivity/counts.\n */\n legend?: boolean | { interactive?: boolean; counts?: boolean };\n onNodeClick?: (node: Record<string, unknown>) => void;\n onNodeDoubleClick?: (node: Record<string, unknown>) => void;\n onNodeHover?: (node: Record<string, unknown> | null) => void;\n onEdgeHover?: (edge: Record<string, unknown> | null) => void;\n onSelectionChange?: (nodeIds: string[]) => void;\n /** Fired when the user hovers a legend entry (null on leave). */\n onLegendHover?: (entry: { field: string; value: string } | null) => void;\n /** Fired when legend toggle state changes; `activeValues` is the active category set (empty = all). */\n onLegendToggle?: (activeValues: string[]) => void;\n /** Fired whenever the highlight set changes (programmatic or legend), null when cleared. */\n onHighlightChange?: (nodeIds: string[] | null) => void;\n /**\n * Fit the graph to the viewport on the first tick. Default true. Set false to\n * restore a saved camera (e.g. getCamera() + flyTo) without the initial fit.\n */\n fitOnLoad?: boolean;\n /** Camera change callback, rAF-coalesced (fires at most once per rendered frame). */\n onCameraChange?: (camera: { x: number; y: number; k: number }) => void;\n /** Skip the entrance reveal/flight on mount (spec unchanged; used by wrappers when recreating for a theme/darkMode-only change so the entrance doesn't replay). Warmup still runs. */\n suppressEntrance?: boolean;\n}\n\nexport interface GraphInstance {\n update(spec: GraphSpec): void;\n /** Re-compile encoding/legend/chrome without restarting the simulation. Preserves node positions. */\n updateVisuals(spec: GraphSpec): void;\n search(query: string): void;\n clearSearch(): void;\n /** Fit all nodes into the viewport. Animated by default; `{ duration: 0 }` snaps. */\n zoomToFit(opts?: CameraFlightOptions & { padding?: number }): void;\n /** Fly to a node and zoom in (default scale 2). Tracks the node while it settles. */\n zoomToNode(nodeId: string, opts?: CameraFlightOptions & { scale?: number }): void;\n /** Fly the camera to a graph-space target. */\n flyTo(target: { x: number; y: number; k?: number }, opts?: CameraFlightOptions): void;\n /** Center the camera on a graph-space point (keeps current zoom). */\n centerAt(x: number, y: number, opts?: CameraFlightOptions): void;\n /** Current camera (graph-space center-ish transform components). */\n getCamera(): { x: number; y: number; k: number };\n /** Select a node; `{ fly: true }` also flies to it (default follows interaction.select.flyTo). */\n selectNode(nodeId: string, opts?: { fly?: boolean } & CameraFlightOptions): void;\n getSelectedNodes(): string[];\n /** Node ids currently matching the active search query. */\n getSearchMatches(): string[];\n /**\n * Emphasize a set of nodes; eased via the focus model. Layers over the\n * standing category filter (`setActiveCategories`) rather than replacing it:\n * the effective set is the intersection, or the highlight alone when the two\n * are disjoint. Legend toggle state is untouched.\n */\n highlight(target: GraphHighlightTarget, opts?: { dimOpacity?: number }): void;\n /**\n * Clear the transient highlight, returning to the standing category filter.\n * Does NOT clear the filter — use `setActiveCategories([])` for that.\n */\n clearHighlight(): void;\n /** The effective highlighted node ids, or null when nothing is highlighted. */\n getHighlight(): string[] | null;\n /** Headless snapshot of the legend (node categories + edge categories). */\n getLegend(): GraphLegendData;\n /** Set the active category filter declaratively (replaces legend toggle state). */\n setActiveCategories(values: string[]): void;\n /** Current active category values (empty = all active, no filter). */\n getActiveCategories(): string[];\n resize(): void;\n destroy(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Physics-feel gates (Phase 8)\n// ---------------------------------------------------------------------------\n\n/** Springy drag disables above this node count (warm-sim cost at scale). */\nconst SPRINGY_DRAG_MAX_NODES = 5000;\n/** Cursor-repulsion disables above this node count (mirrors the glow gate). */\nconst CURSOR_FORCE_MAX_NODES = 2000;\n/** Cursor pointer-feed throttle (~30Hz) so we don't post on every mousemove. */\nconst CURSOR_POINTER_THROTTLE_MS = 33;\n\n/** Post-flight camera follow stops once the sim alpha settles below this. */\nconst FOLLOW_SETTLE_ALPHA = 0.05;\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a graph instance from a spec and mount it into a container.\n *\n * @param container - The DOM element to render into.\n * @param spec - The graph spec.\n * @param options - Mount options.\n * @returns A GraphInstance with update/search/zoom/destroy methods.\n */\nexport function createGraph(\n container: HTMLElement,\n spec: GraphSpec,\n options?: GraphMountOptions,\n): GraphInstance {\n let currentSpec = spec;\n let compilation: GraphCompilation;\n let destroyed = false;\n\n // DOM elements\n let wrapper: HTMLElement | null = null;\n let canvas: HTMLCanvasElement | null = null;\n let chromeEl: HTMLElement | null = null;\n let legendEl: HTMLElement | null = null;\n let legendController: GraphLegendController | null = null;\n\n // Subsystems\n let renderer: GraphCanvasRenderer | null = null;\n let simulation: SimulationManager | null = null;\n const spatialIndex = new SpatialIndex();\n let interactionManager: GraphInteractionManager | null = null;\n const searchManager = new GraphSearchManager();\n let tooltipManager: TooltipManager | null = null;\n let cleanupKeyboard: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n\n // State\n let positionedNodes: PositionedNode[] = [];\n let positionedEdges: PositionedEdge[] = [];\n let adjacencyMap = new Map<string, Set<string>>();\n let nodeDataMap = new Map<string, Record<string, unknown>>();\n let edgeDataMap = new Map<string, Record<string, unknown>>();\n let hoveredNodeId: string | null = null;\n let hoveredEdgeId: string | null = null;\n let selectedNodeIds = new Set<string>();\n let animFrameId: number | null = null;\n let needsRender = false;\n let isGesturing = false;\n // Continuous-animation scheduler. Arms the first frame via scheduleRender on\n // the idle→active transition; the render loop ticks it each frame and re-arms\n // only while it stays active, so the base loop stays strictly dirty-flag.\n const scheduler = new AnimationScheduler(() => scheduleRender());\n let gestureTimeout: ReturnType<typeof setTimeout> | null = null;\n let lastEdgeHitTime = 0;\n // Cursor-repulsion pointer-feed throttle timestamp (Phase 8).\n let lastPointerFeedTime = 0;\n // Camera flight state.\n let activeFlight: GraphAnimation | null = null;\n // Post-flight follow for provider-form flights (tracks a still-settling node).\n let activeFollow: GraphAnimation | null = null;\n // Latest simulation alpha, fed by onTick; the follow stops below the threshold.\n let lastAlpha = 1;\n let cameraChangePending = false;\n\n // Focus / highlight state (Phase 5). Two independent layers compose into one\n // effective set:\n // - `activeCategories` is the STICKY category filter (empty = no filter),\n // written only by setActiveCategories / toggleLegendCategory /\n // applyInitialHighlight. Legend `active` flags track this and only this.\n // - `transientHighlight` is the TRANSIENT emphasis owned solely by\n // highlight() / clearHighlight(), along with `highlightDimOpacity`.\n // `transientTarget` keeps the unresolved target so a category-form\n // highlight can be re-resolved against new data on update().\n // `highlightSet` is the cached composition of the two (null = nothing\n // highlighted), assigned only by recomputeHighlight(). It is the cache\n // standingSnapshot() reads, so armFocus stays allocation-free per frame.\n let highlightSet: Set<string> | null = null;\n let highlightDimOpacity: number | null = null;\n // Ids exempt from highlight/filter dimming (the spec's `seedNode`). Kept OUT\n // of `highlightSet` on purpose: composeStandingFocus expands the core set to\n // `core ∪ neighbors(core)`, and a seed is by construction a hub, so unioning\n // it would light most of the graph and defeat the category filter. Keeping it\n // out also means getHighlight()/onHighlightChange never leak the seed id.\n // Re-derived wherever `compilation` is reassigned.\n let seedIds = new Set<string>();\n let activeCategories = new Set<string>();\n let transientHighlight: Set<string> | null = null;\n let transientTarget: GraphHighlightTarget | null = null;\n // Compiler warnings already emitted by this instance (see warnOnce).\n const seenWarnings = new Set<string>();\n // Node id → its legend-field category value, for category-based highlight.\n let nodeCategory = new Map<string, string>();\n // The live focus crossfade, driving eased dimming. Rebuilt on first render.\n let focusTransition: FocusTransition | null = null;\n // Scheduler animation that keeps frames dirty while the focus crossfade runs.\n let focusAnim: GraphAnimation | null = null;\n // Hovered-node radius tween (1 → 1.15), null when settled at 1.\n let hoverRadiusTween: { nodeId: string; scale: number } | null = null;\n\n // Entrance choreography state (Phase 6). `entranceProgress` is a mount-level\n // 0→1 value read by buildRenderState; < 1 means the reveal is mid-flight.\n // `entranceActive` gates the render-state `entrance` field. `entranceFitInFlight`\n // tracks the entrance camera flight so a resize can cancel just it (keeping the\n // reveal). `entranceReveal` is the scheduler tween driving `entranceProgress`.\n let entranceProgress = 1;\n let entranceActive = false;\n let entranceStagger = false;\n // Pop choreography inputs (staggered entrances only): hash-scattered stagger\n // rank and per-node convergence drift vectors, built once at entrance start.\n let entranceOrderMap: Map<string, number> | null = null;\n let entranceOffsetMap: Map<string, { x: number; y: number }> | null = null;\n let entranceFitInFlight = false;\n let entranceReveal: GraphAnimation | null = null;\n // Mount-level opt-out (Phase 9): when set, the FIRST entrance takes the instant\n // -fit branch (no 0.92 pullback, no reveal tween, no camera flight) even under\n // normal motion. Wrappers set this when recreating for a theme/darkMode-only\n // change so the entrance doesn't replay. Warmup still runs. One-shot.\n let suppressEntranceOnce = options?.suppressEntrance ?? false;\n\n // Data-update transition state (Phase 7). `enterAlphaMap` fades newly-added\n // nodes in over update.duration (null when no enter-fade is live). `exiting`\n // holds ghost marks (removed nodes/edges) fading out over exit.duration; both\n // are read by buildRenderState and drawn by the canvas renderer.\n let enterAlphaMap: Map<string, number> | null = null;\n let exitingGhosts: { nodes: PositionedNode[]; edges: PositionedEdge[]; alpha: number } | null =\n null;\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function markGesture(): void {\n isGesturing = true;\n if (gestureTimeout !== null) clearTimeout(gestureTimeout);\n gestureTimeout = setTimeout(() => {\n isGesturing = false;\n gestureTimeout = null;\n needsRender = true;\n scheduleRender();\n }, 150);\n }\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n /**\n * Compiler warning sink. `update()` recompiles, so a warning about a standing\n * spec condition (an unknown `seedNode` id, say) would otherwise repeat on\n * every data change. Emit each distinct message once per instance.\n */\n function warnOnce(message: string): void {\n if (seenWarnings.has(message)) return;\n seenWarnings.add(message);\n if (options?.onWarn) options.onWarn(message);\n else console.warn(message);\n }\n\n function compile(): GraphCompilation {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n onWarn: warnOnce,\n };\n\n return compileGraph(currentSpec, compileOpts);\n }\n\n function buildDataMaps(): void {\n nodeDataMap = new Map(compilation.nodes.map((n) => [n.id, n.data ?? {}]));\n edgeDataMap = new Map(compilation.edges.map((e) => [`${e.source}->${e.target}`, e.data ?? {}]));\n\n // Node → legend-field category value, for category hover/highlight. Empty\n // when the graph has no categorical color field.\n nodeCategory = new Map();\n const field = compilation.legendField;\n if (field) {\n for (const n of compilation.nodes) {\n const v = n.data?.[field];\n if (v != null) nodeCategory.set(n.id, String(v));\n }\n }\n }\n\n function buildAdjacencyMap(edges: CompiledGraphEdge[]): Map<string, Set<string>> {\n const map = new Map<string, Set<string>>();\n for (const edge of edges) {\n if (!map.has(edge.source)) map.set(edge.source, new Set());\n if (!map.has(edge.target)) map.set(edge.target, new Set());\n map.get(edge.source)!.add(edge.target);\n map.get(edge.target)!.add(edge.source);\n }\n return map;\n }\n\n function toSimNodes(nodes: CompiledGraphNode[]): SimNode[] {\n return nodes.map((n) => ({\n id: n.id,\n radius: n.radius,\n community: n.community,\n }));\n }\n\n function toSimEdges(edges: CompiledGraphEdge[]): SimEdge[] {\n return edges.map((e) => ({\n source: e.source,\n target: e.target,\n }));\n }\n\n /**\n * Look up a node's data from the compilation by id.\n * Falls back to an empty object if not found.\n */\n function nodeDataById(nodeId: string): Record<string, unknown> {\n return nodeDataMap.get(nodeId) ?? {};\n }\n\n /**\n * Point-to-line-segment distance for edge hit testing.\n * Returns the shortest distance from point (px, py) to the segment (ax, ay)-(bx, by).\n */\n function pointToSegmentDist(\n px: number,\n py: number,\n ax: number,\n ay: number,\n bx: number,\n by: number,\n ): number {\n const dx = bx - ax;\n const dy = by - ay;\n const lenSq = dx * dx + dy * dy;\n if (lenSq === 0) return Math.hypot(px - ax, py - ay);\n const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));\n return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));\n }\n\n /**\n * Find the edge closest to a graph-space point, within a threshold.\n * Returns an edge key \"source->target\" or null.\n */\n function hitTestEdge(graphX: number, graphY: number, threshold: number): string | null {\n let bestDist = threshold;\n let bestEdgeId: string | null = null;\n\n for (const edge of positionedEdges) {\n const dist = pointToSegmentDist(\n graphX,\n graphY,\n edge.sourceX,\n edge.sourceY,\n edge.targetX,\n edge.targetY,\n );\n if (dist < bestDist) {\n bestDist = dist;\n bestEdgeId = `${edge.source}->${edge.target}`;\n }\n }\n\n return bestEdgeId;\n }\n\n /**\n * Look up edge data by edge id (\"source->target\").\n */\n function edgeDataById(edgeId: string): Record<string, unknown> | null {\n return edgeDataMap.get(edgeId) ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // DOM creation\n // ---------------------------------------------------------------------------\n\n function createDOM(): void {\n const { width, height } = getContainerDimensions();\n const isDark = resolveDarkMode(options?.darkMode);\n\n // Wrapper\n wrapper = document.createElement('div');\n wrapper.className = isDark ? 'oc-graph-wrapper oc-dark' : 'oc-graph-wrapper';\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Apply theme colors as CSS custom properties so chrome HTML picks them up.\n // Without this, consumer-supplied theme.colors.text only affects canvas-drawn\n // labels but not the HTML title/subtitle which read from --oc-text.\n const resolvedTheme = compilation.theme;\n if (resolvedTheme) {\n const s = wrapper.style;\n s.setProperty('--oc-bg', resolvedTheme.colors.background);\n s.setProperty('--oc-text', resolvedTheme.colors.text);\n s.setProperty('--oc-text-secondary', resolvedTheme.colors.axis ?? resolvedTheme.colors.text);\n s.setProperty('--oc-font-family', resolvedTheme.fonts.family);\n s.fontFamily = resolvedTheme.fonts.family;\n }\n\n // Chrome (title, subtitle)\n chromeEl = document.createElement('div');\n chromeEl.className = 'oc-graph-chrome';\n renderChrome();\n wrapper.appendChild(chromeEl);\n\n // Canvas\n canvas = document.createElement('canvas');\n canvas.className = 'oc-graph-canvas';\n canvas.setAttribute('role', 'img');\n if (compilation.a11y?.altText) {\n canvas.setAttribute('aria-label', compilation.a11y.altText);\n }\n wrapper.appendChild(canvas);\n\n // Legend\n if (legendSetting() !== false) {\n legendEl = document.createElement('div');\n legendEl.className = 'oc-graph-legend';\n renderLegend();\n wrapper.appendChild(legendEl);\n }\n\n container.appendChild(wrapper);\n syncChromeInset();\n\n // Canvas uses the full container height; chrome overlays on top\n const canvasHeight = Math.max(height, 200);\n renderer = new GraphCanvasRenderer(canvas);\n renderer.resize(width, canvasHeight);\n }\n\n function renderChrome(): void {\n if (!chromeEl) return;\n let html = '';\n\n if (compilation.chrome.title) {\n html += `<h2 class=\"oc-title\">${escapeHtml(compilation.chrome.title.text)}</h2>`;\n }\n if (compilation.chrome.subtitle) {\n html += `<p class=\"oc-subtitle\">${escapeHtml(compilation.chrome.subtitle.text)}</p>`;\n }\n\n chromeEl.innerHTML = html;\n\n // Hide chrome if empty\n if (!html) {\n chromeEl.style.display = 'none';\n } else {\n chromeEl.style.display = '';\n }\n }\n\n /**\n * The effective legend setting. `GraphSpec.legend` and the mount option say\n * the same thing at two levels; the mount option wins when both are set, so a\n * host can override a spec it doesn't own.\n */\n function legendSetting(): GraphMountOptions['legend'] {\n return options?.legend ?? currentSpec.legend;\n }\n\n /** Resolve legend interactive/counts flags from the legend setting. */\n function legendConfig(): { interactive: boolean; counts: boolean } {\n const l = legendSetting();\n if (l && typeof l === 'object') {\n return { interactive: l.interactive ?? true, counts: l.counts ?? true };\n }\n return { interactive: true, counts: true };\n }\n\n /** Build the legend view data from the compilation + current toggle state. */\n function legendViewData(): { nodes: GraphLegendData['nodes']; edges: GraphLegendData['edges'] } {\n return { nodes: getLegend().nodes, edges: getLegend().edges };\n }\n\n /** Create or refresh the interactive legend from current state. */\n function renderLegend(): void {\n if (!legendEl) return;\n const cfg = legendConfig();\n if (!legendController) {\n legendController = createGraphLegend(legendEl, legendViewData(), {\n interactive: cfg.interactive,\n counts: cfg.counts,\n onToggle: (value) => toggleLegendCategory(value),\n onHover: (value) => {\n const field = compilation.legendField;\n options?.onLegendHover?.(value !== null && field ? { field, value } : null);\n },\n });\n } else {\n legendController.update(legendViewData());\n }\n }\n\n /** Re-render the legend to reflect the current active-category state. */\n function syncLegendActiveState(): void {\n if (legendController) legendController.update(legendViewData());\n syncChromeInset();\n }\n\n /**\n * Keep the chrome block out of the legend's column: the title/subtitle wrap\n * before they reach the legend box instead of running underneath it. No-op\n * when there's no legend (or it has no measurable width, e.g. in happy-dom).\n */\n function syncChromeInset(): void {\n if (!chromeEl) return;\n const legendW = legendEl?.offsetWidth ?? 0;\n chromeEl.style.right = legendW > 0 ? `${legendW + 24}px` : '';\n }\n\n /**\n * Height of the chrome overlay band (title + subtitle) the camera fit should\n * reserve, so nodes never settle underneath the text. 0 when chrome is empty\n * or unmeasurable (happy-dom).\n */\n function chromeInsetTop(): number {\n if (!chromeEl || chromeEl.style.display === 'none') return 0;\n return chromeEl.offsetHeight;\n }\n\n // ---------------------------------------------------------------------------\n // Physics-feel gates (Phase 8)\n // ---------------------------------------------------------------------------\n\n /**\n * Springy drag on: config opts in AND the graph is small enough that holding\n * the sim warm during a drag is cheap. Off/above threshold → legacy pin/unpin.\n */\n function springyDragEnabled(): boolean {\n return (\n compilation.interaction.springyDrag && compilation.nodes.length <= SPRINGY_DRAG_MAX_NODES\n );\n }\n\n /**\n * Cursor repulsion on: config opts in, the graph is small enough (mirrors the\n * glow gate), and reduced motion is off (ambient pointer-driven motion is\n * exactly what reduced-motion suppresses).\n */\n function cursorForceEnabled(): boolean {\n return (\n compilation.interaction.cursorRepulsion !== null &&\n compilation.nodes.length <= CURSOR_FORCE_MAX_NODES &&\n !prefersReducedMotion()\n );\n }\n\n // ---------------------------------------------------------------------------\n // Simulation and animation\n // ---------------------------------------------------------------------------\n\n /**\n * Create the simulation for the current compilation.\n *\n * On the initial mount (no `opts`), every node is seeded deterministically and\n * a fresh warmup runs. On a data update, `opts` supplies pre-known positions\n * (survivors keep their prior x/y; enterers get spawn positions), suppresses\n * the center force (it snaps every node by the full centroid error on tick 1,\n * the global-jump artifact this phase eliminates), and overrides the initial\n * alpha to the local-reheat impulse. Update sims skip warmup — survivors are\n * already settled, so a headless warmup would just churn them.\n */\n function initSimulation(opts?: {\n positions?: Map<string, { x: number; y: number }>;\n suppressCenter?: boolean;\n initialAlpha?: number;\n skipWarmup?: boolean;\n skipEntrance?: boolean;\n }): void {\n const simNodes = toSimNodes(compilation.nodes);\n const simEdges = toSimEdges(compilation.edges);\n const config = compilation.simulationConfig;\n\n if (opts?.positions) {\n // Update path: place each node at its known (survivor or spawn) position.\n // Any node without a supplied position (shouldn't happen) falls back to the\n // seeded disc so it isn't stuck at the origin.\n seedNodePositions(simNodes, config.seed ?? 0);\n for (const n of simNodes) {\n const p = opts.positions.get(n.id);\n if (p) {\n n.x = p.x;\n n.y = p.y;\n }\n }\n } else {\n // Initial mount: seed deterministic positions BEFORE the simulation starts,\n // so the settled layout is reproducible for a given (spec, seed).\n seedNodePositions(simNodes, config.seed ?? 0);\n }\n\n simulation = SimulationManager.create(simNodes, simEdges, {\n chargeStrength: config.chargeStrength,\n linkDistance: config.linkDistance,\n clustering: config.clustering,\n alphaDecay: config.alphaDecay,\n velocityDecay: config.velocityDecay,\n collisionRadius: config.collisionRadius,\n collisionPadding: config.collisionPadding,\n linkStrength: config.linkStrength,\n // Update sims suppress the (non-alpha-scaled) center force to avoid a\n // global jump on tick 1; the alpha-scaled forceX/forceY gravity still\n // holds the layout centered.\n centerForce: opts?.suppressCenter ? false : config.centerForce,\n warmupTicks: opts?.skipWarmup ? 0 : config.warmupTicks,\n warmupBudgetMs: config.warmupBudgetMs,\n initialAlpha: opts?.initialAlpha ?? config.initialAlpha,\n // Cursor force radius/strength (null when disabled or gated off by node\n // count). The mount only feeds pointer positions when the same gate holds.\n cursorRepulsion: cursorForceEnabled() ? compilation.interaction.cursorRepulsion : null,\n });\n\n // A fresh sim starts hot; don't let a settled previous sim's alpha linger\n // (it would end a post-flight camera follow before the first tick lands).\n lastAlpha = opts?.initialAlpha ?? 1;\n\n let initialSettleDone = false;\n // Update sims keep the current camera and don't run the entrance reveal, so\n // pre-mark the fit as done — the first update tick just streams positions.\n let initialFitDone = opts?.skipEntrance ?? false;\n\n simulation.onTick((positions, alpha) => {\n if (destroyed) return;\n lastAlpha = alpha;\n\n // Build position lookup\n const posMap = new Map<string, { x: number; y: number }>();\n for (const p of positions) {\n posMap.set(p.id, { x: p.x, y: p.y });\n }\n\n // Build positioned nodes\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = posMap.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n\n // Build positioned edges\n positionedEdges = compilation.edges.map((edge) => {\n const src = posMap.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = posMap.get(edge.target) ?? { x: 0, y: 0 };\n return {\n ...edge,\n sourceX: src.x,\n sourceY: src.y,\n targetX: tgt.x,\n targetY: tgt.y,\n };\n });\n\n // Rebuild spatial index\n spatialIndex.rebuild(positionedNodes);\n\n // Fit + entrance choreography on the FIRST post-warmup tick (the sim only\n // starts streaming ticks once warmup, if any, has completed). After that,\n // let the user interact freely while the simulation keeps settling.\n if (\n !initialFitDone &&\n positionedNodes.length > 0 &&\n interactionManager &&\n options?.fitOnLoad !== false\n ) {\n initialFitDone = true;\n startEntrance();\n } else if (!initialFitDone && options?.fitOnLoad === false) {\n // Skip the fit but mark it done so a saved camera (getCamera/flyTo) sticks.\n initialFitDone = true;\n }\n\n needsRender = true;\n scheduleRender();\n });\n\n simulation.onSettled(() => {\n if (initialSettleDone) return;\n initialSettleDone = true;\n });\n }\n\n /**\n * Compute the initial fit transform. Bypasses fitBounds' spread inflation when\n * warmup ran (warmed bounds are near-final; inflating them fits too small).\n */\n function computeInitialFit(): ZoomTransform {\n const { width: cw, height: ch } = getCanvasDimensions();\n const warmed = (compilation.simulationConfig.warmupTicks ?? 0) > 0;\n const { transform } = ZoomTransform.fitBounds(positionedNodes, cw, ch, undefined, {\n spread: !warmed,\n insetTop: chromeInsetTop(),\n });\n return transform;\n }\n\n /**\n * Fit + run the entrance reveal on the first post-warmup tick. Under an enabled\n * `enter` phase and normal motion, the camera starts pulled back to 0.92× the\n * fit and (optionally) flies in while a mount-level `entranceProgress` tween\n * ramps node/edge/label reveal. Under reduced motion or `animation: false`,\n * it's an instant fit (warmup still ran — it reduces motion, it isn't motion).\n */\n function startEntrance(): void {\n if (!interactionManager) return;\n const fit = computeInitialFit();\n const enter = compilation.animation?.enter ?? null;\n // One-shot mount-level suppression (theme/darkMode remount): take the same\n // instant-fit branch as reduced motion so the entrance doesn't replay.\n const suppressed = suppressEntranceOnce;\n suppressEntranceOnce = false;\n if (suppressed || !enter || prefersReducedMotion()) {\n interactionManager.setTransform(fit);\n cameraChangePending = true;\n entranceActive = false;\n entranceProgress = 1;\n return;\n }\n\n // Start pulled back so the reveal has somewhere to fly in from. 0.7 gives a\n // clearly readable pull-in on load (0.85 was too subtle to register as motion).\n const { width: cw, height: ch } = getCanvasDimensions();\n const pulledBack = fit.zoomAt(fit.k * 0.7, cw / 2, ch / 2);\n interactionManager.setTransform(pulledBack);\n cameraChangePending = true;\n\n entranceActive = true;\n entranceProgress = 0;\n // Stagger only when the spec asks for it AND the graph is small enough that\n // per-node start times still batch. Above the cap: a single global fade.\n entranceStagger = enter.stagger && positionedNodes.length <= ENTRANCE_STAGGER_MAX_NODES;\n // Pop choreography inputs, computed once against the warmed (near-final)\n // positions: hash-scattered stagger order + per-node convergence drift.\n if (entranceStagger) {\n entranceOrderMap = entranceOrder(positionedNodes);\n entranceOffsetMap = entranceOffsets(positionedNodes);\n } else {\n entranceOrderMap = null;\n entranceOffsetMap = null;\n }\n\n // Optional camera flight from the pulled-back framing to the true fit.\n if (enter.cameraFit) {\n entranceFitInFlight = true;\n flyCamera(fit, { duration: enter.duration + 100 }, () => {\n entranceFitInFlight = false;\n });\n }\n\n // Reveal tween drives entranceProgress 0→1; ends the entrance on completion.\n const ease = resolveEase(enter.ease);\n entranceReveal = createTween({\n duration: enter.duration,\n ease,\n apply: (t) => {\n entranceProgress = t;\n needsRender = true;\n },\n onDone: () => {\n entranceProgress = 1;\n entranceActive = false;\n entranceReveal = null;\n needsRender = true;\n },\n });\n scheduler.add(entranceReveal);\n }\n\n function getCanvasDimensions(): { width: number; height: number } {\n if (!canvas) return { width: 600, height: 400 };\n const rect = canvas.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n function scheduleRender(): void {\n if (animFrameId !== null || destroyed) return;\n animFrameId = requestAnimationFrame(renderFrame);\n }\n\n // -------------------------------------------------------------------------\n // Focus model (Phase 5): highlight ∩ search + hover neighborhood, eased.\n // -------------------------------------------------------------------------\n\n /** Neighborhood of the hovered node under the resolved hover mode. */\n function hoverConnectedSet(nodeId: string | null): Set<string> | null {\n if (nodeId === null) return null;\n if (compilation.interaction.hoverMode === 'category') {\n const cat = nodeCategory.get(nodeId);\n const set = new Set<string>([nodeId]);\n if (cat !== undefined) {\n for (const [id, c] of nodeCategory) if (c === cat) set.add(id);\n }\n return set;\n }\n // 'neighbors' (default): the node plus its adjacency.\n const set = new Set<string>([nodeId]);\n const neighbors = adjacencyMap.get(nodeId);\n if (neighbors) for (const nid of neighbors) set.add(nid);\n return set;\n }\n\n /** The standing (non-hover) focus from highlight + search + selection. */\n function standingSnapshot(): FocusSnapshot {\n return composeStandingFocus(\n highlightSet,\n searchManager.getMatches(),\n selectedNodeIds,\n adjacencyMap,\n );\n }\n\n /** The full target snapshot = standing state with the hover layer on top. */\n function targetSnapshot(): FocusSnapshot {\n return layerHoverFocus(standingSnapshot(), hoveredNodeId, hoverConnectedSet(hoveredNodeId));\n }\n\n /**\n * Point the focus crossfade at the current target and arm a scheduler\n * animation that keeps frames dirty until it settles. Snaps instantly under\n * reduced motion or when hover animation is disabled.\n */\n function armFocus(now: number): void {\n const target = targetSnapshot();\n const hoverCfg = compilation.animation?.hover ?? null;\n const duration = hoverCfg && !prefersReducedMotion() ? hoverCfg.duration : 0;\n const ease = resolveEase(hoverCfg?.ease ?? 'smooth');\n\n if (!focusTransition) {\n focusTransition = new FocusTransition(target, duration, ease, now);\n return;\n }\n focusTransition.retarget(target, now);\n\n // Keep the crossfade running via a scheduler animation until settled.\n if (focusAnim) scheduler.remove(focusAnim);\n focusAnim = {\n tick: (t: number): boolean => {\n const running = focusTransition !== null && !focusTransition.isSettled(t);\n if (!running) focusAnim = null;\n return running;\n },\n finish: (): void => {\n focusAnim = null;\n },\n cancel: (): void => {\n focusAnim = null;\n },\n };\n scheduler.add(focusAnim);\n }\n\n /** Recompute the highlight set and re-arm the focus crossfade. */\n function refreshFocus(): void {\n armFocus(performance.now());\n needsRender = true;\n scheduleRender();\n }\n\n /** Resolve a highlight target into a concrete node id set. */\n function resolveHighlightTarget(target: GraphHighlightTarget): Set<string> {\n if ('nodeIds' in target) return new Set(target.nodeIds);\n if ('neighborsOf' in target) {\n const set = new Set<string>();\n if (target.includeSelf !== false) set.add(target.neighborsOf);\n const neighbors = adjacencyMap.get(target.neighborsOf);\n if (neighbors) for (const nid of neighbors) set.add(nid);\n return set;\n }\n // Category form: match nodes whose `field` value is in `value`.\n const values = new Set(\n Array.isArray(target.category.value) ? target.category.value : [target.category.value],\n );\n const field = target.category.field;\n const set = new Set<string>();\n for (const n of compilation.nodes) {\n const v = n.data?.[field];\n if (v != null && values.has(String(v))) set.add(n.id);\n }\n return set;\n }\n\n /** Node ids for the active legend categories (empty categories = no filter). */\n function categoryHighlightSet(): Set<string> | null {\n if (activeCategories.size === 0) return null;\n const set = new Set<string>();\n for (const [id, cat] of nodeCategory) if (activeCategories.has(cat)) set.add(id);\n return set;\n }\n\n /**\n * Recompose the effective highlight from the sticky category filter and the\n * transient highlight(). THE ONLY assigner of `highlightSet`.\n *\n * Composition rule: both present → their intersection, except when the\n * intersection is empty, in which case the transient wins (a host legend\n * hovering an out-of-filter row previews that category — the built-in legend\n * only fires `onLegendHover`, so this path is host-driven). Exactly one\n * present → that one. Neither → null. Same shape as the highlight ∩ search\n * rule in composeStandingFocus.\n *\n * An EMPTY transient is not a transient: `resolveHighlightTarget` returns an\n * empty set for `{ nodeIds: [] }` or a category matching zero nodes, and\n * treating that as a layer would silently wipe the standing filter's dimming.\n */\n function recomputeHighlight(): void {\n const filter = categoryHighlightSet();\n const transient =\n transientHighlight !== null && transientHighlight.size > 0 ? transientHighlight : null;\n if (filter !== null && transient !== null) {\n const inter = new Set<string>();\n for (const id of transient) if (filter.has(id)) inter.add(id);\n highlightSet = inter.size > 0 ? inter : transient;\n return;\n }\n highlightSet = filter ?? transient;\n }\n\n /**\n * Re-resolve the standing transient target against the current compilation\n * and drop ids that no longer exist. Category-form targets track data changes\n * this way; an unpruned id set would otherwise resurrect deleted nodes.\n */\n function refreshTransientHighlight(): void {\n if (transientTarget === null) {\n transientHighlight = null;\n return;\n }\n const nextIds = new Set(compilation.nodes.map((n) => n.id));\n const resolved = new Set(\n [...resolveHighlightTarget(transientTarget)].filter((id) => nextIds.has(id)),\n );\n transientHighlight = resolved.size > 0 ? resolved : null;\n }\n\n /** The custom `dimOpacity` applies only while a transient highlight is up. */\n function effectiveDimOpacity(): number {\n const custom = transientHighlight !== null ? highlightDimOpacity : null;\n return custom ?? compilation.interaction.dimOpacity;\n }\n\n /** Fire onHighlightChange with the current highlight set. */\n function emitHighlightChange(): void {\n options?.onHighlightChange?.(highlightSet ? [...highlightSet] : null);\n }\n\n /** Apply the resolved initialHighlight from the compilation, if any. */\n function applyInitialHighlight(): void {\n const init = compilation.initialHighlight;\n if (!init) return;\n activeCategories = new Set(init.values);\n recomputeHighlight();\n }\n\n /** Start (or cancel) the hovered node's 1 → 1.15 radius tween. */\n function startHoverRadiusTween(nodeId: string | null): void {\n const hoverCfg = compilation.animation?.hover ?? null;\n // No hover animation, reduced motion, or hover-off → snap (no tween).\n if (nodeId === null || !hoverCfg || prefersReducedMotion()) {\n hoverRadiusTween = nodeId ? { nodeId, scale: 1.15 } : null;\n return;\n }\n hoverRadiusTween = { nodeId, scale: 1 };\n const ease = resolveEase(hoverCfg.ease);\n const tween = createTween({\n duration: hoverCfg.duration,\n ease,\n apply: (t) => {\n // Guard against a newer hover having replaced the target mid-tween.\n if (hoverRadiusTween?.nodeId === nodeId) hoverRadiusTween.scale = 1 + 0.15 * t;\n needsRender = true;\n },\n onDone: () => {\n if (hoverRadiusTween?.nodeId === nodeId) hoverRadiusTween.scale = 1.15;\n },\n });\n scheduler.add(tween);\n }\n\n /** Resolve tooltip content for a node, applying a custom formatter if set. */\n function showNodeTooltip(nodeId: string): void {\n if (!tooltipManager || !interactionManager) return;\n const defaults = compilation.tooltipDescriptors.get(nodeId);\n if (!defaults) return;\n const node = positionedNodes.find((n) => n.id === nodeId);\n if (!node) return;\n const screen = interactionManager.getTransform().graphToScreen(node.x, node.y);\n\n const formatter = tooltipFormatter();\n if (!formatter) {\n tooltipManager.show(defaults, screen.x, screen.y);\n return;\n }\n const result = formatter({ kind: 'node', data: nodeDataById(nodeId) }, defaults);\n applyFormatterResult(result, screen.x, screen.y);\n }\n\n /** Resolve tooltip content for an edge (lazy default), applying a formatter. */\n function showEdgeTooltip(\n edgeId: string,\n data: Record<string, unknown>,\n screenX: number,\n screenY: number,\n ): void {\n if (!tooltipManager) return;\n const edge = compilation.edges.find((e) => `${e.source}->${e.target}` === edgeId);\n const defaults = edge ? buildEdgeTooltip(edge) : { title: edgeId, fields: [] };\n\n const formatter = tooltipFormatter();\n if (!formatter) {\n tooltipManager.show(defaults, screenX, screenY);\n return;\n }\n const result = formatter({ kind: 'edge', data }, defaults);\n applyFormatterResult(result, screenX, screenY);\n }\n\n /** The configured tooltip formatter, or null when tooltips are plain. */\n function tooltipFormatter(): GraphTooltipFormatter | null {\n const t = options?.tooltip;\n return t && typeof t === 'object' ? (t.formatter ?? null) : null;\n }\n\n /** Route a formatter's return value to the tooltip manager (or hide on null). */\n function applyFormatterResult(\n result: TooltipContent | string | HTMLElement | null,\n x: number,\n y: number,\n ): void {\n if (!tooltipManager) return;\n if (result === null) {\n tooltipManager.hide();\n } else if (typeof result === 'string') {\n tooltipManager.show({ text: result }, x, y);\n } else if (result instanceof HTMLElement) {\n tooltipManager.show({ element: result }, x, y);\n } else {\n tooltipManager.show(result, x, y);\n }\n }\n\n /** Build the immutable per-frame render state from current mount state. */\n function buildRenderState(now: number): GraphRenderState {\n const transform = interactionManager!.getTransform();\n\n // Ensure a focus transition exists (first render), pointed at current state.\n if (!focusTransition) armFocus(now);\n const ft = focusTransition as FocusTransition;\n const t = ft.progress(now);\n const focus = t < 1 ? { t, prev: ft.prev, next: ft.next } : undefined;\n // When settled we still pass the resting snapshot as `next` for the fast path.\n const settledNext = ft.next;\n\n const hoverRadiusScale = hoverRadiusTween\n ? new Map([[hoverRadiusTween.nodeId, hoverRadiusTween.scale]])\n : undefined;\n\n return {\n nodes: positionedNodes,\n edges: positionedEdges,\n transform: { x: transform.x, y: transform.y, k: transform.k },\n hoveredNodeId,\n hoveredEdgeId,\n selectedNodeIds,\n adjacencyMap,\n theme: compilation.theme,\n searchMatches: searchManager.getMatches(),\n exemptIds: seedIds,\n isGesturing,\n watermark: compilation.watermark,\n focus: focus ?? { t: 1, prev: settledNext, next: settledNext },\n hoverRadiusScale,\n dimOpacity: effectiveDimOpacity(),\n entrance:\n entranceActive && entranceProgress < 1\n ? {\n t: entranceProgress,\n stagger: entranceStagger,\n order: entranceOrderMap ?? undefined,\n offsets: entranceOffsetMap ?? undefined,\n }\n : undefined,\n enterAlpha: enterAlphaMap ?? undefined,\n exiting: exitingGhosts ?? undefined,\n };\n }\n\n function renderFrame(now: number): void {\n animFrameId = null;\n if (destroyed || !renderer || !interactionManager) return;\n\n // Tick animations first; a running animation dirties the frame. Animations\n // mutate mount state only — they never render or arm rAF themselves.\n if (scheduler.tick(now)) needsRender = true;\n\n if (needsRender) {\n needsRender = false;\n renderer.render(buildRenderState(now));\n }\n\n // Re-arm only while animations are active; otherwise the loop goes idle.\n if (scheduler.active) scheduleRender();\n\n // Emit a coalesced camera-change after the frame renders (at most once/frame).\n if (cameraChangePending) {\n cameraChangePending = false;\n const t = interactionManager.getTransform();\n options?.onCameraChange?.({ x: t.x, y: t.y, k: t.k });\n }\n }\n\n /**\n * Fly the camera to a target transform (or a provider that tracks a moving\n * target). Cancels any prior flight. Snaps instead of flying when animation is\n * disabled, reduced motion is active, or duration is 0.\n */\n function flyCamera(\n to: ZoomTransform | (() => ZoomTransform),\n opts?: CameraFlightOptions,\n onDone?: () => void,\n ): void {\n if (destroyed || !interactionManager) return;\n\n // Cancel any in-flight camera animation (and a lingering post-flight follow).\n cancelFlight();\n\n const cameraCfg = compilation.animation?.camera ?? null;\n const resolveTarget = () => (typeof to === 'function' ? to() : to);\n const snap = cameraCfg === null || prefersReducedMotion() || opts?.duration === 0;\n\n if (snap) {\n interactionManager.setTransform(resolveTarget());\n cameraChangePending = true;\n needsRender = true;\n scheduleRender();\n onDone?.();\n return;\n }\n\n const { width, height } = getCanvasDimensions();\n const duration = opts?.duration ?? cameraCfg?.duration ?? 'auto';\n const ease = opts?.ease ?? cameraCfg?.ease ?? 'smooth';\n // Large graphs skip labels/glow while flying; the final frame is full quality.\n const heavy = positionedNodes.length > 1000;\n\n const flight = createCameraFlight({\n from: interactionManager.getTransform(),\n to,\n viewport: { width, height },\n apply: (t) => {\n interactionManager!.setTransform(t);\n isGesturing = heavy;\n cameraChangePending = true;\n needsRender = true;\n },\n onDone: () => {\n activeFlight = null;\n isGesturing = false;\n needsRender = true;\n // Provider-form flights converge at t=1 while the tracked node may\n // still be settling — keep following it until the sim quiets down.\n if (typeof to === 'function') startFollow(to);\n scheduleRender();\n onDone?.();\n },\n opts: { duration, ease },\n });\n activeFlight = flight;\n scheduler.add(flight);\n }\n\n /** Snap the camera to a provider each frame until the sim settles. */\n function startFollow(target: () => ZoomTransform): void {\n const follow = createCameraFollow({\n target,\n apply: (t) => {\n interactionManager!.setTransform(t);\n cameraChangePending = true;\n needsRender = true;\n },\n isActive: () => !destroyed && lastAlpha >= FOLLOW_SETTLE_ALPHA,\n });\n activeFollow = follow;\n scheduler.add(follow);\n }\n\n /** Cancel any active camera flight/follow (called by user-initiated pan/zoom). */\n function cancelFlight(): void {\n if (activeFlight) {\n scheduler.remove(activeFlight);\n activeFlight = null;\n isGesturing = false;\n }\n if (activeFollow) {\n scheduler.remove(activeFollow);\n activeFollow = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Interaction wiring\n // ---------------------------------------------------------------------------\n\n function initInteraction(): void {\n if (!canvas) return;\n\n if (options?.tooltip !== false) {\n tooltipManager = createTooltipManager(wrapper!);\n }\n\n interactionManager = new GraphInteractionManager(canvas, spatialIndex, {\n onTransformChange(_transform) {\n // User-initiated pan/zoom cancels any camera flight. Programmatic\n // setTransform does NOT call this callback, so there's no feedback loop;\n // node-drag correctly doesn't reach here either.\n cancelFlight();\n markGesture();\n // User pan/zoom is a camera change too — zoom UIs and camera\n // persistence rely on the coalesced onCameraChange, not polling.\n cameraChangePending = true;\n needsRender = true;\n scheduleRender();\n },\n onHoverChange(nodeId) {\n // Skip redundant work (and a duplicate onNodeHover fire) when the hovered\n // node id is unchanged.\n if (nodeId === hoveredNodeId) return;\n hoveredNodeId = nodeId;\n // Re-arm the focus crossfade so the hover neighborhood eases in/out.\n armFocus(performance.now());\n startHoverRadiusTween(nodeId);\n needsRender = true;\n scheduleRender();\n\n // Race-safe ordering: clear any edge hover and fire onEdgeHover(null)\n // BEFORE onNodeHover(node), so an edge session never interleaves inside\n // a node hover session.\n if (nodeId && hoveredEdgeId) {\n hoveredEdgeId = null;\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n\n // Fire onNodeHover callback\n options?.onNodeHover?.(nodeId ? nodeDataById(nodeId) : null);\n\n // Show or hide tooltip\n if (nodeId && tooltipManager) {\n showNodeTooltip(nodeId);\n } else if (!nodeId) {\n // Tooltip hiding handled in onBackgroundHover (edge may show tooltip).\n tooltipManager?.hide();\n }\n },\n onBackgroundHover(graphX, graphY, screenX, screenY) {\n // A live node hover owns the tooltip; don't let edge hit-testing steal it.\n if (hoveredNodeId) return;\n // Throttle edge hit testing to avoid O(n) scan on every mousemove\n const now = performance.now();\n if (now - lastEdgeHitTime < 32) {\n // When throttled, clear edge hover so hover-off transitions stay snappy\n if (hoveredEdgeId) {\n hoveredEdgeId = null;\n needsRender = true;\n scheduleRender();\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n return;\n }\n lastEdgeHitTime = now;\n\n // Edge hit testing: check proximity to edge line segments\n const transform = interactionManager?.getTransform();\n const threshold = 5 / (transform?.k ?? 1); // 5px in screen space\n const edgeId = hitTestEdge(graphX, graphY, threshold);\n\n if (edgeId !== hoveredEdgeId) {\n hoveredEdgeId = edgeId;\n needsRender = true;\n scheduleRender();\n\n if (edgeId) {\n const data = edgeDataById(edgeId);\n options?.onEdgeHover?.(data);\n if (tooltipManager && data) showEdgeTooltip(edgeId, data, screenX, screenY);\n } else {\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n }\n },\n onSelectionChange(nodeIds) {\n selectedNodeIds = new Set(nodeIds);\n armFocus(performance.now());\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.(nodeIds);\n\n // Fire onNodeClick for the most recently added node\n if (nodeIds.length > 0) {\n const lastId = nodeIds[nodeIds.length - 1];\n options?.onNodeClick?.(nodeDataById(lastId));\n }\n },\n onNodeDragStart(nodeId) {\n // Pin at the node's current position to avoid visual snap to origin\n const node = positionedNodes.find((n) => n.id === nodeId);\n const x = node?.x ?? 0;\n const y = node?.y ?? 0;\n // Springy: hold the sim warm (alphaTarget 0.3) so neighbors follow. Off\n // → legacy pin with no alphaTarget field (byte-identical message).\n simulation?.pinNode(nodeId, x, y, springyDragEnabled() ? 0.3 : undefined);\n canvas?.classList.add('oc-graph-canvas--dragging');\n },\n onNodeDrag(nodeId, x, y) {\n simulation?.dragNode(nodeId, x, y);\n },\n onNodeDragEnd(nodeId) {\n // Springy: cool the sim back down (alphaTarget 0). Off → legacy unpin\n // with no alphaTarget field (preserves the legacy reheat behavior).\n simulation?.unpinNode(nodeId, springyDragEnabled() ? 0 : undefined);\n canvas?.classList.remove('oc-graph-canvas--dragging');\n },\n onPointerMove(graphX, graphY) {\n if (!cursorForceEnabled()) return;\n // Throttle the pointer feed to ~30Hz so we don't post on every mousemove.\n const now = performance.now();\n if (now - lastPointerFeedTime < CURSOR_POINTER_THROTTLE_MS) return;\n lastPointerFeedTime = now;\n simulation?.setPointer(graphX, graphY, true);\n },\n onPointerLeave() {\n if (!cursorForceEnabled()) return;\n simulation?.setPointer(0, 0, false);\n },\n onDoubleClick(nodeId) {\n options?.onNodeDoubleClick?.(nodeDataById(nodeId));\n },\n });\n\n // Wire keyboard navigation\n cleanupKeyboard = attachGraphKeyboardNav({\n canvas,\n getNodes: () => positionedNodes,\n getSelectedIds: () => [...selectedNodeIds],\n getAdjacency: () => adjacencyMap,\n onSelect(nodeId) {\n selectedNodeIds = new Set([nodeId]);\n needsRender = true;\n scheduleRender();\n options?.onNodeClick?.(nodeDataById(nodeId));\n options?.onSelectionChange?.([nodeId]);\n },\n onDeselect() {\n selectedNodeIds.clear();\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.([]);\n },\n onZoom(direction) {\n if (!interactionManager || !canvas) return;\n const t = interactionManager.getTransform();\n const { width: cw, height: ch } = getCanvasDimensions();\n const factor = direction === 'in' ? 1.2 : 0.8;\n const newK = t.k * factor;\n const newTransform = t.zoomAt(newK, cw / 2, ch / 2);\n flyCamera(newTransform, { duration: 200 });\n },\n onFitAll() {\n zoomToFit();\n },\n });\n\n // Handle node clicks (from interaction manager selection change wiring above)\n // We catch clicks via the interaction manager's onSelectionChange callback\n }\n\n // ---------------------------------------------------------------------------\n // Public API methods\n // ---------------------------------------------------------------------------\n\n function search(query: string): void {\n if (destroyed) return;\n searchManager.search(query, positionedNodes);\n needsRender = true;\n scheduleRender();\n }\n\n function clearSearch(): void {\n if (destroyed) return;\n searchManager.clearSearch();\n needsRender = true;\n scheduleRender();\n }\n\n function zoomToFit(opts?: CameraFlightOptions & { padding?: number }): void {\n if (destroyed || !interactionManager || positionedNodes.length === 0) return;\n const { width: cw, height: ch } = getCanvasDimensions();\n const { transform: fitTransform } = ZoomTransform.fitBounds(\n positionedNodes,\n cw,\n ch,\n opts?.padding,\n {\n insetTop: chromeInsetTop(),\n },\n );\n flyCamera(fitTransform, opts);\n }\n\n function zoomToNode(nodeId: string, opts?: CameraFlightOptions & { scale?: number }): void {\n if (destroyed || !interactionManager || !canvas) return;\n const node = positionedNodes.find((n) => n.id === nodeId);\n if (!node) return;\n\n const { width: cw, height: ch } = getCanvasDimensions();\n const k = clampK(opts?.scale ?? 2);\n // Provider form: re-read the node's live position each frame so the camera\n // tracks it while the simulation is still settling.\n const provider = (): ZoomTransform => {\n const live = positionedNodes.find((n) => n.id === nodeId) ?? node;\n return new ZoomTransform(cw / 2 - live.x * k, ch / 2 - live.y * k, k);\n };\n flyCamera(provider, opts);\n }\n\n function flyTo(target: { x: number; y: number; k?: number }, opts?: CameraFlightOptions): void {\n if (destroyed || !interactionManager) return;\n const { width: cw, height: ch } = getCanvasDimensions();\n const k = clampK(target.k ?? interactionManager.getTransform().k);\n flyCamera(new ZoomTransform(cw / 2 - target.x * k, ch / 2 - target.y * k, k), opts);\n }\n\n function centerAt(x: number, y: number, opts?: CameraFlightOptions): void {\n flyTo({ x, y }, opts);\n }\n\n function getCamera(): { x: number; y: number; k: number } {\n const t = interactionManager?.getTransform() ?? ZoomTransform.identity();\n return { x: t.x, y: t.y, k: t.k };\n }\n\n function selectNode(nodeId: string, opts?: { fly?: boolean } & CameraFlightOptions): void {\n if (destroyed) return;\n selectedNodeIds = new Set([nodeId]);\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.([nodeId]);\n const shouldFly = opts?.fly ?? compilation.interaction.selectFlyTo;\n if (shouldFly) zoomToNode(nodeId, opts);\n }\n\n function getSelectedNodes(): string[] {\n return [...selectedNodeIds];\n }\n\n function getSearchMatches(): string[] {\n return [...(searchManager.getMatches() ?? [])];\n }\n\n // -------------------------------------------------------------------------\n // Highlight API (transient layer over the sticky category filter)\n // -------------------------------------------------------------------------\n\n /**\n * Emphasize a set of nodes on top of the standing category filter. Does not\n * touch the filter; legend `active` flags are unaffected. A target that\n * resolves to no nodes is a no-op layer: the standing filter keeps dimming.\n * The target is retained and re-resolved on `update()`, so a category-form\n * highlight tracks data changes.\n */\n function highlight(target: GraphHighlightTarget, opts?: { dimOpacity?: number }): void {\n if (destroyed) return;\n transientTarget = target;\n refreshTransientHighlight();\n highlightDimOpacity = opts?.dimOpacity ?? null;\n recomputeHighlight();\n refreshFocus();\n emitHighlightChange();\n }\n\n /**\n * Drop the transient highlight. The category filter stands — clearing that is\n * `setActiveCategories([])`.\n */\n function clearHighlight(): void {\n if (destroyed) return;\n transientTarget = null;\n transientHighlight = null;\n highlightDimOpacity = null;\n recomputeHighlight();\n refreshFocus();\n emitHighlightChange();\n }\n\n function getHighlight(): string[] | null {\n return highlightSet ? [...highlightSet] : null;\n }\n\n function getLegend(): GraphLegendData {\n const nodeEntries = 'entries' in compilation.legend ? compilation.legend.entries : [];\n return {\n field: compilation.legendField,\n nodes: nodeEntries\n .filter((e) => !e.overflow)\n .map((e) => ({\n label: e.label,\n color: e.color,\n count: e.count,\n active: activeCategories.size === 0 || activeCategories.has(e.label),\n })),\n edges: (compilation.edgeLegend ?? []).map((e) => ({\n label: e.label,\n color: e.color,\n count: e.count,\n })),\n };\n }\n\n /**\n * Set the sticky category filter (built-in legend + headless). A transient\n * highlight() layers over this rather than replacing it. Empty active set =\n * all categories shown (no filter, no dimming).\n */\n function setActiveCategories(values: string[]): void {\n if (destroyed) return;\n activeCategories = new Set(values);\n recomputeHighlight();\n syncLegendActiveState();\n refreshFocus();\n emitHighlightChange();\n }\n\n function getActiveCategories(): string[] {\n return [...activeCategories];\n }\n\n function toggleLegendCategory(value: string): void {\n if (activeCategories.has(value)) activeCategories.delete(value);\n else activeCategories.add(value);\n recomputeHighlight();\n syncLegendActiveState();\n refreshFocus();\n options?.onLegendToggle?.([...activeCategories]);\n emitHighlightChange();\n }\n\n function doResize(): void {\n if (destroyed || !canvas || !renderer || !wrapper) return;\n const { width, height } = getContainerDimensions();\n const canvasHeight = Math.max(height, 200);\n renderer.resize(width, canvasHeight);\n // A width change can rewrap the title or move the legend; re-derive the\n // chrome/legend separation before any fit below measures the chrome band.\n syncChromeInset();\n\n // Mid-entrance: the in-flight camera fit targets the OLD viewport, so cancel\n // just that flight and snap to the new-viewport fit. The reveal tween (node\n // alpha/scale ramp) is orthogonal to the camera and keeps running.\n if (entranceFitInFlight && interactionManager) {\n cancelFlight();\n entranceFitInFlight = false;\n interactionManager.setTransform(computeInitialFit());\n cameraChangePending = true;\n }\n\n needsRender = true;\n scheduleRender();\n }\n\n /**\n * Unified data update.\n *\n * 1. Finish in-flight animations, then compile the new spec.\n * 2. Diff prev↔next. A `visualOnly` change (identical node AND edge id sets AND\n * equal simulationConfig) takes the position-preserving refresh — no sim\n * restart. Anything else (added/removed marks, or a physics change) is a\n * structural update.\n * 3. Structural update: tear down the SIM ONLY (interaction manager, camera\n * transform, and surviving selection are kept). Survivors keep their prior\n * x/y; enterers get spawn positions. A new sim is created with the center\n * force suppressed and a low reheat alpha, so survivors barely move and\n * enterers locally settle. Enter fades and exit ghosts animate the delta.\n */\n function update(newSpec: GraphSpec): void {\n if (destroyed) return;\n currentSpec = newSpec;\n\n // Finish any in-flight animations (e.g. an entrance reveal or a prior\n // update's enter/exit fade) so they snap to their final state and fire\n // onDone rather than being hard-cancelled mid-flight.\n scheduler.finishAll();\n entranceActive = false;\n entranceProgress = 1;\n entranceFitInFlight = false;\n entranceReveal = null;\n // Clear any lingering update-transition state (finishAll ran their onDone).\n enterAlphaMap = null;\n exitingGhosts = null;\n\n // Capture prev state BEFORE recompiling.\n const prevNodes = positionedNodes;\n const prevEdges = positionedEdges;\n const prevConfig = compilation.simulationConfig;\n\n // Recompile with the new spec.\n compilation = compile();\n seedIds = new Set(compilation.seedNodeIds);\n\n const diff = diffGraphUpdate(\n prevNodes,\n prevEdges,\n compilation,\n prevConfig,\n prevConfig.seed ?? 0,\n );\n\n if (diff.visualOnly) {\n runVisualOnlyUpdate();\n return;\n }\n\n runStructuralUpdate(diff);\n }\n\n /**\n * Position-preserving visual refresh: recompile changed encoding/chrome/legend\n * and transfer existing node positions. No simulation restart. (Assumes the\n * new `compilation` is already set and node/edge id sets are unchanged.)\n */\n function runVisualOnlyUpdate(): void {\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n\n // Build a position lookup from the current positioned nodes.\n const posMap = new Map<string, { x: number; y: number }>();\n for (const node of positionedNodes) {\n posMap.set(node.id, { x: node.x, y: node.y });\n }\n\n // Transfer positions to the newly compiled nodes.\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = posMap.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n\n positionedEdges = compilation.edges.map((edge) => {\n const src = posMap.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = posMap.get(edge.target) ?? { x: 0, y: 0 };\n return { ...edge, sourceX: src.x, sourceY: src.y, targetX: tgt.x, targetY: tgt.y };\n });\n\n spatialIndex.rebuild(positionedNodes);\n\n // Highlight persists: re-resolve both layers against the new nodes. Node\n // ids are identical on a visual-only update, but a category-form transient\n // can still change membership if the underlying field values changed.\n refreshTransientHighlight();\n recomputeHighlight();\n\n // Search survives: re-run the stored query against the new nodes.\n reRunSearch();\n\n renderChrome();\n renderLegend();\n syncLegendActiveState();\n\n needsRender = true;\n scheduleRender();\n }\n\n /**\n * Structural update: nodes/edges added/removed or physics changed. Tears down\n * only the simulation, recreates it with survivor/spawn positions and a local\n * reheat, and animates the enter/exit delta.\n */\n function runStructuralUpdate(diff: ReturnType<typeof diffGraphUpdate>): void {\n // Tear down the SIM ONLY. Interaction manager, camera transform, and\n // selection all survive (the plan's key divergence from the old full update).\n teardownSimOnly();\n\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n\n // Known positions for the new sim: survivors keep prior x/y, enterers spawn.\n const positions = new Map<string, { x: number; y: number }>();\n for (const [id, p] of diff.survivingPositions) positions.set(id, p);\n for (const [id, p] of diff.spawnPositions) positions.set(id, p);\n\n // changeRatio = max(node churn, edge churn). Node churn is\n // (|entering| + |exiting nodes|) / max(prevNodeCount, nextNodeCount); the\n // edge analog uses the same shape. Low alpha IS the local reheat.\n const prevNodeCount = diff.survivingPositions.size + diff.exitingNodes.length;\n const nextNodeCount = diff.survivingPositions.size + diff.enteringIds.length;\n const nodeRatio = ratio(\n diff.enteringIds.length + diff.exitingNodes.length,\n Math.max(prevNodeCount, nextNodeCount),\n );\n const prevEdgeCount =\n compilation.edges.length - diff.enteringEdgeCount + diff.exitingEdges.length;\n const nextEdgeCount = compilation.edges.length;\n const edgeRatio = ratio(\n diff.enteringEdgeCount + diff.exitingEdges.length,\n Math.max(prevEdgeCount, nextEdgeCount),\n );\n const changeRatio = Math.max(nodeRatio, edgeRatio);\n const initialAlpha = Math.min(1, 0.3 + 0.7 * changeRatio);\n\n // Seed positioned nodes/edges immediately so the first frame (before the sim\n // streams its first tick) draws survivors at their prior spots and enterers\n // at their spawn spots — no flash at the origin.\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = positions.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n positionedEdges = compilation.edges.map((edge) => {\n const src = positions.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = positions.get(edge.target) ?? { x: 0, y: 0 };\n return { ...edge, sourceX: src.x, sourceY: src.y, targetX: tgt.x, targetY: tgt.y };\n });\n spatialIndex.rebuild(positionedNodes);\n\n initSimulation({\n positions,\n suppressCenter: true,\n initialAlpha,\n skipWarmup: true,\n skipEntrance: true,\n });\n\n // Update DOM chrome/legend for the new graph.\n renderChrome();\n renderLegend();\n\n // Reconcile interaction/highlight/search state against the new node set.\n reconcileStateAfterUpdate();\n\n // Wire the enter-fade and exit-ghost transitions.\n startUpdateTransitions(diff);\n\n needsRender = true;\n scheduleRender();\n }\n\n /** Safe ratio (0 when the denominator is 0). */\n function ratio(numerator: number, denominator: number): number {\n return denominator > 0 ? numerator / denominator : 0;\n }\n\n /**\n * Prune stale interaction state after a structural update: drop hovered\n * node/edge ids that no longer exist, intersect the selection with the new\n * node set (pushing the pruned set into the interaction manager so a later\n * shift-click can't resurrect deleted ids), re-resolve highlight, and re-run\n * any active search.\n */\n function reconcileStateAfterUpdate(): void {\n const nextIds = new Set(compilation.nodes.map((n) => n.id));\n\n // Hovered node: clear if gone.\n if (hoveredNodeId && !nextIds.has(hoveredNodeId)) hoveredNodeId = null;\n // Hovered edge: clear if either endpoint is gone.\n if (hoveredEdgeId) {\n const [src, tgt] = hoveredEdgeId.split('->');\n if (!nextIds.has(src) || !nextIds.has(tgt)) hoveredEdgeId = null;\n }\n\n // Selection: intersect with survivors, then push into the interaction manager.\n const survivingSelection = [...selectedNodeIds].filter((id) => nextIds.has(id));\n selectedNodeIds = new Set(survivingSelection);\n interactionManager?.setSelection(survivingSelection);\n\n // Highlight persists. Re-resolve the transient layer against the new nodes\n // first (this both tracks category membership changes and drops deleted\n // ids, which an unpruned set would resurrect), then recompose.\n refreshTransientHighlight();\n recomputeHighlight();\n syncLegendActiveState();\n\n // Search survives: re-run the stored query against the new nodes.\n reRunSearch();\n\n // Re-arm the focus crossfade against the reconciled state (a fresh transition\n // so it doesn't blend from stale prev/next snapshots).\n focusTransition = null;\n armFocus(performance.now());\n }\n\n /** Re-run the active search query (if any) against the current positioned nodes. */\n function reRunSearch(): void {\n const q = searchManager.getQuery();\n if (q !== null) searchManager.search(q, positionedNodes);\n }\n\n /**\n * Start the enter-fade (new nodes 0→1 over update.duration, quantized for\n * batching) and the exit-ghost fade (removed marks 1→0 over exit.duration).\n * Snaps instantly under reduced motion or when the phase is disabled.\n */\n function startUpdateTransitions(diff: ReturnType<typeof diffGraphUpdate>): void {\n const updateCfg = compilation.animation?.update ?? null;\n const exitCfg = compilation.animation?.exit ?? null;\n const reduced = prefersReducedMotion();\n\n // -- Enter fade --\n if (diff.enteringIds.length > 0 && updateCfg && !reduced) {\n const entering = diff.enteringIds;\n enterAlphaMap = new Map(entering.map((id) => [id, 0]));\n const ease = resolveEase(updateCfg.ease);\n const tween = createTween({\n duration: updateCfg.duration,\n ease,\n apply: (t) => {\n // Quantize to 8 buckets so per-node alpha keeps fill-batching bounded.\n const q = Math.round(t * 8) / 8;\n if (enterAlphaMap) for (const id of entering) enterAlphaMap.set(id, q);\n needsRender = true;\n },\n onDone: () => {\n enterAlphaMap = null;\n needsRender = true;\n },\n });\n scheduler.add(tween);\n }\n\n // -- Exit ghosts --\n if ((diff.exitingNodes.length > 0 || diff.exitingEdges.length > 0) && exitCfg && !reduced) {\n exitingGhosts = { nodes: diff.exitingNodes, edges: diff.exitingEdges, alpha: 1 };\n const ease = resolveEase(exitCfg.ease);\n const tween = createTween({\n duration: exitCfg.duration,\n ease,\n apply: (t) => {\n if (exitingGhosts) exitingGhosts.alpha = 1 - t;\n needsRender = true;\n },\n onDone: () => {\n exitingGhosts = null;\n needsRender = true;\n },\n });\n scheduler.add(tween);\n }\n }\n\n /**\n * @deprecated Use {@link GraphInstance.update} instead. `update` now handles\n * both visual-only and structural changes (diffed automatically) and preserves\n * node positions when nothing structural changed. Kept as an alias for backward\n * compatibility.\n */\n function updateVisuals(newSpec: GraphSpec): void {\n update(newSpec);\n }\n\n /** Tear down ONLY the simulation, keeping interaction/transform/selection. */\n function teardownSimOnly(): void {\n simulation?.destroy();\n simulation = null;\n }\n\n function teardownSubsystems(): void {\n // Cancel animations BEFORE tearing down the sim/DOM, so no in-flight tick\n // writes to removed state.\n scheduler.cancelAll();\n activeFlight = null;\n activeFollow = null;\n // Reset entrance state so a remount (React StrictMode) starts clean.\n entranceReveal = null;\n entranceActive = false;\n entranceProgress = 1;\n entranceFitInFlight = false;\n // Reset update-transition state too (cancelAll already stopped the tweens).\n enterAlphaMap = null;\n exitingGhosts = null;\n if (animFrameId !== null) {\n cancelAnimationFrame(animFrameId);\n animFrameId = null;\n }\n if (cleanupKeyboard) {\n cleanupKeyboard();\n cleanupKeyboard = null;\n }\n interactionManager?.destroy();\n interactionManager = null;\n simulation?.destroy();\n simulation = null;\n tooltipManager?.destroy();\n tooltipManager = null;\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n if (gestureTimeout !== null) {\n clearTimeout(gestureTimeout);\n gestureTimeout = null;\n }\n\n teardownSubsystems();\n\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n legendController?.destroy();\n legendController = null;\n\n if (wrapper?.parentNode) {\n wrapper.parentNode.removeChild(wrapper);\n }\n wrapper = null;\n canvas = null;\n chromeEl = null;\n legendEl = null;\n renderer = null;\n\n container.classList.remove('oc-dark');\n }\n\n // ---------------------------------------------------------------------------\n // Initialize\n // ---------------------------------------------------------------------------\n\n try {\n compilation = compile();\n seedIds = new Set(compilation.seedNodeIds);\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n applyInitialHighlight();\n createDOM();\n initSimulation();\n initInteraction();\n } catch (err) {\n // Same failure semantics as createChart and createSankey: a spec that\n // cannot compile or mount is a caller bug, and a throw is the only signal\n // that reaches them. (A silent no-op instance used to be returned here.)\n console.error('[viz] Graph mount failed:', err);\n throw err;\n }\n\n // Responsive resize\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => {\n doResize();\n });\n }\n\n return {\n update,\n updateVisuals,\n search,\n clearSearch,\n zoomToFit,\n zoomToNode,\n flyTo,\n centerAt,\n getCamera,\n selectNode,\n getSelectedNodes,\n getSearchMatches,\n highlight,\n clearHighlight,\n getHighlight,\n getLegend,\n setActiveCategories,\n getActiveCategories,\n resize: doResize,\n destroy,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Util\n// ---------------------------------------------------------------------------\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n","/**\n * Animated camera for the graph.\n *\n * Flights interpolate between two `ZoomTransform`s along d3's geodesic\n * `interpolateZoom` path (smooth zoom-and-pan that pulls back to see both\n * endpoints before diving in). A flight is a {@link GraphAnimation}, driven by\n * the mount's AnimationScheduler.\n *\n * `interpolateZoom` operates on a `[cx, cy, width]` \"view\" — the graph-space\n * center and the graph-space width visible across the viewport. We convert the\n * ZoomTransform ⇄ view around a fixed viewport size.\n */\n\nimport type { AnimationEase } from '@opendata-ai/openchart-core';\nimport { interpolateZoom } from 'd3-interpolate';\nimport { resolveEase } from './motion';\nimport type { GraphAnimation } from './scheduler';\nimport { ZoomTransform } from './zoom';\n\n/** Minimum/maximum zoom scale a flight may resolve to. */\nconst K_MIN = 0.05;\nconst K_MAX = 15;\n\n/** Auto-duration bounds (ms) and the interpolateZoom.duration scale factor. */\nconst AUTO_MIN_MS = 300;\nconst AUTO_MAX_MS = 1200;\nconst AUTO_SCALE = 0.6;\n\n/** A d3 zoom \"view\": graph-space center x/y and the graph-space width in view. */\nexport type ZoomView = [number, number, number];\n\n/** Viewport size a flight is framed against. */\nexport interface Viewport {\n width: number;\n height: number;\n}\n\n/**\n * Convert a ZoomTransform to a d3 zoom view against a viewport.\n *\n * The view width is the graph-space span visible across `viewport.width`\n * (= viewport.width / k); the center is the graph point under the viewport\n * center.\n */\nexport function transformToView(t: ZoomTransform, viewport: Viewport): ZoomView {\n const cx = (viewport.width / 2 - t.x) / t.k;\n const cy = (viewport.height / 2 - t.y) / t.k;\n const width = viewport.width / t.k;\n return [cx, cy, width];\n}\n\n/** Inverse of {@link transformToView}: a zoom view back to a ZoomTransform. */\nexport function viewToTransform(view: ZoomView, viewport: Viewport): ZoomTransform {\n const [cx, cy, width] = view;\n const k = clampK(viewport.width / width);\n const x = viewport.width / 2 - cx * k;\n const y = viewport.height / 2 - cy * k;\n return new ZoomTransform(x, y, k);\n}\n\n/** Clamp a zoom scale into the allowed range. */\nexport function clampK(k: number): number {\n if (!Number.isFinite(k) || k <= 0) return K_MIN;\n return Math.min(K_MAX, Math.max(K_MIN, k));\n}\n\n/** Options controlling a camera flight. */\nexport interface CameraFlightOptions {\n /** Duration in ms, or `'auto'` to derive from the zoom distance. */\n duration?: number | 'auto';\n /** Easing preset. Default `'smooth'`. */\n ease?: AnimationEase;\n}\n\n/** Inputs to {@link createCameraFlight}. */\nexport interface CameraFlightInputs {\n /** Starting transform. */\n from: ZoomTransform;\n /**\n * Target transform, or a provider that returns the current target each frame\n * (used to track a node that is still settling). When a provider is passed,\n * the interpolator is rebuilt each frame from the ORIGINAL `from` to the\n * current target, so a moving target stays smooth.\n */\n to: ZoomTransform | (() => ZoomTransform);\n /** Viewport the flight is framed against. */\n viewport: Viewport;\n /** Applies an interpolated transform to the scene each frame. */\n apply: (t: ZoomTransform) => void;\n /** Fired once on natural completion (not on cancel). */\n onDone?: () => void;\n /** Flight options. */\n opts?: CameraFlightOptions;\n}\n\n/**\n * Create a camera flight as a {@link GraphAnimation}.\n *\n * Degenerate from≈to is guarded: duration is forced to at least 1ms and NaN\n * interpolation results fall back to the target. The start time locks on the\n * first tick for deterministic tests.\n */\nexport function createCameraFlight(inputs: CameraFlightInputs): GraphAnimation {\n const { from, viewport, apply, onDone, opts } = inputs;\n const ease = resolveEase(opts?.ease ?? 'smooth');\n const isProvider = typeof inputs.to === 'function';\n\n const fromView = transformToView(from, viewport);\n\n function targetView(): ZoomView {\n const to = isProvider ? (inputs.to as () => ZoomTransform)() : (inputs.to as ZoomTransform);\n return transformToView(to, viewport);\n }\n\n // Build the interpolator (rebuilt each frame in provider mode).\n let interp = interpolateZoom(fromView, targetView());\n const resolvedDuration = resolveDuration(opts?.duration, interp.duration);\n\n let startTime: number | null = null;\n let finished = false;\n\n function applyAt(t: number): void {\n if (isProvider) interp = interpolateZoom(fromView, targetView());\n const view = interp(t) as ZoomView;\n if (view.some((v) => !Number.isFinite(v))) {\n apply(viewToTransform(targetView(), viewport));\n return;\n }\n apply(viewToTransform(view, viewport));\n }\n\n return {\n tick(now: number): boolean {\n if (finished) return false;\n if (startTime === null) startTime = now;\n const raw = resolvedDuration <= 0 ? 1 : Math.min(1, (now - startTime) / resolvedDuration);\n applyAt(ease(raw));\n if (raw >= 1) {\n finished = true;\n onDone?.();\n return false;\n }\n return true;\n },\n finish(): void {\n if (finished) return;\n finished = true;\n applyAt(1);\n onDone?.();\n },\n cancel(): void {\n finished = true;\n },\n };\n}\n\n/** Inputs to {@link createCameraFollow}. */\nexport interface CameraFollowInputs {\n /** Provider returning the current target transform each frame. */\n target: () => ZoomTransform;\n /** Applies the followed transform to the scene each frame. */\n apply: (t: ZoomTransform) => void;\n /** True while the tracked target may still move (e.g. sim alpha ≥ threshold). */\n isActive: () => boolean;\n}\n\n/**\n * Post-flight follow for provider-form flights: the flight converges at t=1\n * while the tracked node may still be settling, so this cheap animation snaps\n * the camera to the provider each frame until `isActive()` reports the sim has\n * settled. User input or a new flight cancels it via the scheduler.\n */\nexport function createCameraFollow(inputs: CameraFollowInputs): GraphAnimation {\n let finished = false;\n return {\n tick(): boolean {\n if (finished) return false;\n if (!inputs.isActive()) {\n finished = true;\n return false;\n }\n inputs.apply(inputs.target());\n return true;\n },\n finish(): void {\n finished = true;\n },\n cancel(): void {\n finished = true;\n },\n };\n}\n\n/**\n * Resolve a flight duration. `'auto'` (or undefined) derives from d3's\n * interpolateZoom.duration (a perceptual distance), scaled and clamped. An\n * explicit number is honored but floored at 1ms so degenerate flights still tick.\n */\nexport function resolveDuration(\n duration: number | 'auto' | undefined,\n interpDuration: number,\n): number {\n if (typeof duration === 'number') return Math.max(1, duration);\n const scaled = interpDuration * AUTO_SCALE;\n return Math.max(AUTO_MIN_MS, Math.min(AUTO_MAX_MS, Math.max(1, scaled)));\n}\n","/**\n * Immutable zoom transform for graph pan/zoom.\n *\n * Provides coordinate conversion between screen space (canvas pixels)\n * and graph space (simulation coordinates). All mutations return new\n * instances rather than modifying in place.\n */\n\nimport type { PositionedNode } from './types';\n\nexport class ZoomTransform {\n constructor(\n readonly x: number,\n readonly y: number,\n readonly k: number,\n ) {}\n\n /** Convert screen coordinates to graph coordinates. */\n screenToGraph(sx: number, sy: number): { x: number; y: number } {\n return {\n x: (sx - this.x) / this.k,\n y: (sy - this.y) / this.k,\n };\n }\n\n /** Convert graph coordinates to screen coordinates. */\n graphToScreen(gx: number, gy: number): { x: number; y: number } {\n return {\n x: gx * this.k + this.x,\n y: gy * this.k + this.y,\n };\n }\n\n /**\n * Zoom to a target scale, keeping the given screen-space pivot\n * point fixed (content under the cursor stays under the cursor).\n */\n zoomAt(targetK: number, pivotX: number, pivotY: number): ZoomTransform {\n // The graph point under the pivot should remain at the same screen position.\n // Before: pivotX = gx * k + x => gx = (pivotX - x) / k\n // After: pivotX = gx * targetK + newX => newX = pivotX - gx * targetK\n const gx = (pivotX - this.x) / this.k;\n const gy = (pivotY - this.y) / this.k;\n return new ZoomTransform(pivotX - gx * targetK, pivotY - gy * targetK, targetK);\n }\n\n /** Pan by a screen-space delta. */\n pan(dx: number, dy: number): ZoomTransform {\n return new ZoomTransform(this.x + dx, this.y + dy, this.k);\n }\n\n /**\n * Compute a transform that fits all nodes within the given canvas\n * dimensions with the specified padding.\n *\n * Returns the transform and the ideal content height (in screen pixels)\n * so callers can shrink the canvas to eliminate dead space.\n */\n static fitBounds(\n nodes: PositionedNode[],\n canvasW: number,\n canvasH: number,\n padding: number = 40,\n opts?: { spread?: boolean; insetTop?: number },\n ): { transform: ZoomTransform; contentHeight: number } {\n if (nodes.length === 0) {\n return { transform: ZoomTransform.identity(), contentHeight: canvasH };\n }\n\n // Reserved band at the top of the canvas (the HTML chrome overlay). The fit\n // centers within the remaining area so nodes don't sit under the title.\n // Clamped so a degenerate measurement can't consume the whole viewport.\n const insetTop = Math.min(Math.max(0, opts?.insetTop ?? 0), canvasH * 0.4);\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n for (const n of nodes) {\n const r = n.radius;\n if (n.x - r < minX) minX = n.x - r;\n if (n.y - r < minY) minY = n.y - r;\n if (n.x + r > maxX) maxX = n.x + r;\n if (n.y + r > maxY) maxY = n.y + r;\n }\n\n let graphW = maxX - minX;\n let graphH = maxY - minY;\n\n if (graphW === 0 && graphH === 0) {\n // All nodes at the same point; center within the area below the inset\n return {\n transform: new ZoomTransform(\n canvasW / 2 - minX,\n insetTop + (canvasH - insetTop) / 2 - minY,\n 1,\n ),\n contentHeight: padding * 2 + insetTop,\n };\n }\n\n // When called early in the simulation (first tick), the bounding box\n // underestimates the final spread. Apply a spread multiplier based on\n // node count: larger graphs expand more as charge forces push nodes\n // apart over subsequent ticks. The sqrt scaling mirrors how d3-force\n // charge repulsion grows with node count.\n //\n // Warmup (Phase 6) settles the layout headlessly before the first fit, so\n // the bounds are already near-final; inflating them then fits at roughly\n // half the correct zoom at 10k nodes. Callers pass `spread: false` after a\n // warmup ran to skip the inflation. Default true = the original behavior.\n if (opts?.spread !== false && nodes.length > 50) {\n const spread = 1 + Math.sqrt(nodes.length) / 120;\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n graphW *= spread;\n graphH *= spread;\n minX = cx - graphW / 2;\n maxX = cx + graphW / 2;\n minY = cy - graphH / 2;\n maxY = cy + graphH / 2;\n }\n\n const availW = canvasW - padding * 2;\n const availH = canvasH - insetTop - padding * 2;\n // Cap at 1 so the graph never renders larger than its natural size\n const k = Math.min(1, availW / graphW, availH / graphH);\n\n // Center horizontally; center vertically within the area below the inset.\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n const tx = canvasW / 2 - cx * k;\n const ty = insetTop + (canvasH - insetTop) / 2 - cy * k;\n\n // Content height = scaled graph extent + top and bottom padding + inset\n const contentHeight = graphH * k + padding * 2 + insetTop;\n\n return {\n transform: new ZoomTransform(tx, ty, k),\n contentHeight,\n };\n }\n\n /** Identity transform (no pan, no zoom). */\n static identity(): ZoomTransform {\n return new ZoomTransform(0, 0, 1);\n }\n}\n","/**\n * Canvas 2D renderer for force-directed graph visualization.\n *\n * Stateless renderer: receives a GraphRenderState each frame and draws it.\n * Handles DPR scaling, viewport culling, LOD labels, dark mode glow effects,\n * and batched drawing for performance at 10k+ nodes.\n *\n * Performance strategy:\n * - Edges batched by (stroke, strokeWidth, dash) key → one stroke() per group\n * - Nodes batched by fill color → one fill() per color group\n * - Node strokes batched by stroke color\n * - Labels and glow skipped during active pan/zoom gestures\n */\n\nimport { BRAND_FONT_SIZE, BRAND_MIN_WIDTH } from '@opendata-ai/openchart-core';\nimport { driftFactor, nodeEnterProgress, popAlpha, popScale } from './entrance';\nimport type { FocusSnapshot } from './focus-transition';\nimport type { GraphRenderState, PositionedEdge, PositionedNode } from './types';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst LABEL_FONT_MIN = 8;\nconst LABEL_FONT_MAX = 12;\nconst EDGE_ALPHA_DEFAULT = 0.35;\nconst EDGE_ALPHA_CONNECTED = 1.0;\nconst SEARCH_NON_MATCH_ALPHA = 0.15;\n/** Default node dim tier — the ratio the edge dim tier derives against. */\nconst DEFAULT_DIM_OPACITY = 0.15;\n/** Above this visible-edge count a focus crossfade snaps (no per-frame blend). */\nconst CROSSFADE_MAX_EDGES = 20000;\n\n/**\n * The three emphasis tiers an edge/node can fall into for a given focus state.\n * `default` is the resting alpha (nothing emphasized); `connected` is fully lit;\n * `dimmed` is de-emphasized while something else is in focus.\n */\ntype FocusTier = 'default' | 'connected' | 'dimmed';\n\n/** Classify an edge under a focus snapshot into its emphasis tier. */\nfunction edgeTier(edge: PositionedEdge, focus: FocusSnapshot): FocusTier {\n if (!focus.hasActive) return 'default';\n return focus.connected.has(edge.source) && focus.connected.has(edge.target)\n ? 'connected'\n : 'dimmed';\n}\n\n/**\n * Classify a node under a focus snapshot into its emphasis tier.\n *\n * Ids in `exemptIds` (the graph's seed node) never dim: they classify as\n * `connected` under any focus state — highlight, category filter, hover, or\n * selection. (Search dimming is a separate alpha multiplier keyed off\n * `searchMatches` and is deliberately not exempted: a seed that doesn't match\n * the query shouldn't pretend to.) The exemption lives here rather than in the highlight set on\n * purpose -- `composeStandingFocus` expands the core set to\n * `core ∪ neighbors(core)`, and a seed is by construction a hub, so unioning it\n * into the highlight would light most of the graph and defeat the category\n * filter. `edgeTier` is deliberately NOT exempted: the seed stays lit while its\n * edges dim with everything else, which is exactly \"lit without lighting its\n * neighborhood\". Exempt nodes land in the existing connected-alpha bucket, so\n * the node fill/stroke batching keys are unaffected.\n */\nfunction nodeTier(\n node: PositionedNode,\n focus: FocusSnapshot,\n exemptIds: Set<string> | undefined,\n): FocusTier {\n if (!focus.hasActive) return 'default';\n if (exemptIds?.has(node.id)) return 'connected';\n return focus.connected.has(node.id) ? 'connected' : 'dimmed';\n}\n\n/**\n * Resolve a tier to its edge alpha. The dimmed tier derives from the node dim\n * knob (`dimOpacity / 3`), preserving the deliberate 0.15-node / 0.05-edge ratio\n * that keeps dense hairballs quiet during hover.\n */\nfunction edgeTierAlpha(tier: FocusTier, dimOpacity: number): number {\n switch (tier) {\n case 'connected':\n return EDGE_ALPHA_CONNECTED;\n case 'dimmed':\n return dimOpacity / 3;\n default:\n return EDGE_ALPHA_DEFAULT;\n }\n}\n\n/** Resolve a tier to its node alpha. The dimmed tier is the raw dim knob. */\nfunction nodeTierAlpha(tier: FocusTier, dimOpacity: number): number {\n switch (tier) {\n case 'dimmed':\n return dimOpacity;\n default:\n return 1;\n }\n}\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n/**\n * Per-frame entrance reveal helper, built from the mount's `entrance` state.\n *\n * Staggered (≤ ENTRANCE_STAGGER_MAX_NODES): each node POPS — scale runs 0→~1.1→1\n * (back-out overshoot), alpha 0→1 over the first 60% of its window, and the node\n * converges from a small centroid-outward drift offset to its final position.\n * `nodeT` is a per-node staggered+quantized progress ordered by `entrance.order`\n * (hash-scattered rank), so nodes reveal at independent, scattered times.\n *\n * Unstaggered (large graphs): the legacy single global fade — alpha and scale\n * both ramp `0.6 + 0.4·g`, no drift.\n *\n * Edges lag 30% behind the global progress; labels fade with the raw global\n * progress. `total` is fixed at build time so the stagger window is stable.\n */\ninterface EntranceReveal {\n nodeAlpha(node: PositionedNode): number;\n nodeScale(node: PositionedNode): number;\n /** Drift offset for a node id at the current frame (graph-space px). */\n shift(id: string): { x: number; y: number };\n edgeAlpha: number;\n labelAlpha: number;\n}\n\nconst ZERO_SHIFT = { x: 0, y: 0 };\n\nfunction makeEntranceReveal(\n entrance: GraphRenderState['entrance'] & { t: number },\n total: number,\n): EntranceReveal {\n const g = entrance.t;\n const { stagger, order, offsets } = entrance;\n const rankOf = (node: PositionedNode) => order?.get(node.id) ?? node.index;\n const nodeT = (node: PositionedNode) => (stagger ? nodeEnterProgress(g, rankOf(node), total) : g);\n const edgeAlpha = Math.max(0, (g - 0.3) / 0.7); // lag 30%\n const globalRamp = 0.6 + 0.4 * g;\n return {\n nodeAlpha: (node) => (stagger ? popAlpha(nodeT(node)) : globalRamp),\n nodeScale: (node) => (stagger ? popScale(nodeT(node)) : globalRamp),\n shift: (id) => {\n if (!stagger || !offsets || !order) return ZERO_SHIFT;\n const off = offsets.get(id);\n if (!off) return ZERO_SHIFT;\n const f = driftFactor(nodeEnterProgress(g, order.get(id) ?? 0, total));\n return f > 0 ? { x: off.x * f, y: off.y * f } : ZERO_SHIFT;\n },\n edgeAlpha,\n labelAlpha: g,\n };\n}\n\n/**\n * Derive a focus snapshot from hovered/selected nodes when the mount doesn't\n * supply an explicit crossfade. Preserves the legacy \"hover/select dims the\n * rest\" behavior for callers (and tests) that pass raw render state.\n */\nfunction deriveFocus(\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n adjacencyMap: Map<string, Set<string>>,\n): FocusSnapshot {\n const hasActive = hoveredNodeId !== null || selectedNodeIds.size > 0;\n const connected = new Set<string>();\n if (hasActive) {\n const active = new Set<string>();\n if (hoveredNodeId) active.add(hoveredNodeId);\n for (const id of selectedNodeIds) active.add(id);\n for (const id of active) {\n connected.add(id);\n const neighbors = adjacencyMap.get(id);\n if (neighbors) for (const nid of neighbors) connected.add(nid);\n }\n }\n return { hasActive, connected, searchMatches: null, selected: selectedNodeIds };\n}\nconst GLOW_NODE_THRESHOLD = 2000;\nconst GLOW_RADIUS_MULTIPLIER = 1.3;\nconst GLOW_ALPHA = 0.15;\nconst CULL_MARGIN = 50;\nconst TWO_PI = Math.PI * 2;\n\n/** Minimum node radius in screen pixels. Keeps nodes visible when zoomed out. */\nconst MIN_SCREEN_RADIUS = 2.5;\n\n// ---------------------------------------------------------------------------\n// Helpers (exported for testing)\n// ---------------------------------------------------------------------------\n\n/**\n * Compute label visibility threshold from zoom level.\n * At zoom 0.2 (zoomed out): threshold ~1.0 (only top ~5% visible).\n * At zoom 2.0+: threshold ~0.0 (all visible).\n */\nexport function labelThreshold(zoom: number): number {\n const t = Math.max(0, Math.min(1, (zoom - 0.2) / 1.8));\n return 1 - t;\n}\n\n/** Compute visible rect in graph coordinates from canvas size + transform. */\nexport function visibleRect(\n canvasWidth: number,\n canvasHeight: number,\n transform: { x: number; y: number; k: number },\n margin: number = CULL_MARGIN,\n): { minX: number; minY: number; maxX: number; maxY: number } {\n const { x, y, k } = transform;\n return {\n minX: (-x - margin) / k,\n minY: (-y - margin) / k,\n maxX: (canvasWidth - x + margin) / k,\n maxY: (canvasHeight - y + margin) / k,\n };\n}\n\n/** Check if a node falls within the visible rect. */\nfunction nodeInView(\n node: PositionedNode,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n): boolean {\n return (\n node.x + node.radius >= rect.minX &&\n node.x - node.radius <= rect.maxX &&\n node.y + node.radius >= rect.minY &&\n node.y - node.radius <= rect.maxY\n );\n}\n\n/** Check if an edge has at least one endpoint in view. */\nfunction edgeInView(\n edge: PositionedEdge,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n): boolean {\n return (\n (edge.sourceX >= rect.minX &&\n edge.sourceX <= rect.maxX &&\n edge.sourceY >= rect.minY &&\n edge.sourceY <= rect.maxY) ||\n (edge.targetX >= rect.minX &&\n edge.targetX <= rect.maxX &&\n edge.targetY >= rect.minY &&\n edge.targetY <= rect.maxY)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Dash patterns for edge styles\n// ---------------------------------------------------------------------------\n\nconst DASH_PATTERNS: Record<string, number[]> = {\n solid: [],\n dashed: [6, 4],\n dotted: [2, 3],\n};\n\n// ---------------------------------------------------------------------------\n// GraphCanvasRenderer\n// ---------------------------------------------------------------------------\n\nexport class GraphCanvasRenderer {\n private canvas: HTMLCanvasElement;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private ctx: CanvasRenderingContext2D;\n private dpr: number;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private cssWidth = 0;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private cssHeight = 0;\n\n constructor(canvas: HTMLCanvasElement) {\n this.canvas = canvas;\n this.ctx = canvas.getContext('2d')!;\n this.dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n }\n\n /** Update canvas dimensions with DPR scaling. CSS size stays at css values. */\n resize(width: number, height: number): void {\n this.cssWidth = width;\n this.cssHeight = height;\n this.canvas.width = width * this.dpr;\n this.canvas.height = height * this.dpr;\n }\n\n /** Clear canvas and render the full graph state. */\n render(state: GraphRenderState): void {\n const { ctx, dpr, cssWidth, cssHeight } = this;\n const {\n nodes,\n edges,\n transform,\n hoveredNodeId,\n hoveredEdgeId,\n selectedNodeIds,\n adjacencyMap,\n theme,\n searchMatches,\n isGesturing,\n } = state;\n const dimOpacity = state.dimOpacity ?? DEFAULT_DIM_OPACITY;\n\n // Resolve the current (next) focus snapshot. When the mount supplies a\n // crossfade in `state.focus`, use its endpoints; otherwise derive a snapshot\n // from hovered/selected nodes (backward-compatible, no crossfade).\n const nextFocus: FocusSnapshot =\n state.focus?.next ?? deriveFocus(hoveredNodeId, selectedNodeIds, adjacencyMap);\n // A crossfade is live only when focus is present, mid-flight, and (cheaply)\n // the graph isn't gesturing (during gestures we snap for perf).\n const crossfade = state.focus && state.focus.t < 1 && !isGesturing ? state.focus : null;\n\n // Entrance reveal (Phase 6): present only while the mount is mid-entrance.\n const entrance =\n state.entrance && state.entrance.t < 1\n ? makeEntranceReveal(state.entrance, nodes.length)\n : null;\n\n // Data-update enter fade (Phase 7): per-node alpha for newly-added nodes,\n // multiplied into node/edge/label alpha. Absent id → full alpha (1).\n const enterAlpha = state.enterAlpha ?? null;\n const enterAlphaFor = (id: string): number => enterAlpha?.get(id) ?? 1;\n\n // Viewport culling\n const rect = visibleRect(cssWidth, cssHeight, transform);\n const visibleNodes = nodes.filter((n) => nodeInView(n, rect));\n const visibleEdges = edges.filter((e) => edgeInView(e, rect));\n\n const isDark = theme.isDark;\n const showGlow = isDark && !isGesturing && visibleNodes.length < GLOW_NODE_THRESHOLD;\n const threshold = labelThreshold(transform.k);\n // Minimum radius in graph coordinates so nodes stay visible when zoomed out\n const minRadius = MIN_SCREEN_RADIUS / transform.k;\n\n // -- Clear and apply transform --\n ctx.save();\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, cssWidth, cssHeight);\n\n // Fill background (skip if transparent to let page background show through)\n if (theme.colors.background !== 'transparent') {\n ctx.fillStyle = theme.colors.background;\n ctx.fillRect(0, 0, cssWidth, cssHeight);\n }\n\n ctx.translate(transform.x, transform.y);\n ctx.scale(transform.k, transform.k);\n\n // -- Draw exit ghosts FIRST/UNDER the live marks (Phase 7) --\n // Removed nodes/edges fade out beneath the live graph. Not hit-tested (the\n // mount never rebuilds the spatial index with them), just painted.\n if (state.exiting && state.exiting.alpha > 0) {\n this.drawGhosts(ctx, state.exiting, rect);\n }\n\n // -- Draw edges (batched) -- crossfade path only mid-transition, else the\n // fast 3-bucket steady-state path. Degrade to snap for huge edge sets.\n if (crossfade && visibleEdges.length <= CROSSFADE_MAX_EDGES) {\n this.drawEdgesCrossfade(\n ctx,\n visibleEdges,\n crossfade.prev,\n crossfade.next,\n crossfade.t,\n dimOpacity,\n isGesturing ? null : searchMatches,\n hoveredEdgeId,\n entrance,\n enterAlphaFor,\n );\n } else {\n this.drawEdgesBatched(\n ctx,\n visibleEdges,\n nextFocus,\n dimOpacity,\n isGesturing ? null : searchMatches,\n hoveredEdgeId,\n entrance,\n enterAlphaFor,\n );\n }\n\n // -- Draw nodes (batched by fill color) --\n this.drawNodesBatched(\n ctx,\n visibleNodes,\n hoveredNodeId,\n selectedNodeIds,\n isGesturing ? null : searchMatches,\n showGlow,\n theme,\n minRadius,\n nextFocus,\n dimOpacity,\n state.exemptIds,\n crossfade,\n state.hoverRadiusScale,\n entrance,\n enterAlphaFor,\n );\n\n // -- Draw labels (skipped during gestures) --\n if (!isGesturing) {\n this.drawLabels(\n ctx,\n visibleNodes,\n threshold,\n hoveredNodeId,\n selectedNodeIds,\n searchMatches,\n transform.k,\n theme,\n entrance,\n enterAlphaFor,\n );\n }\n\n ctx.restore();\n\n // Brand watermark in screen coordinates (unaffected by pan/zoom)\n if (state.watermark) {\n this.drawBrand(ctx, cssWidth, cssHeight, theme);\n }\n }\n\n // -------------------------------------------------------------------------\n // Brand rendering\n // -------------------------------------------------------------------------\n\n private drawBrand(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n theme: GraphRenderState['theme'],\n ): void {\n if (w < BRAND_MIN_WIDTH) return;\n const { dpr } = this;\n ctx.save();\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n const padding = theme.spacing.padding;\n const x = w - padding;\n const y = h - padding;\n ctx.font = `600 ${BRAND_FONT_SIZE}px ${theme.fonts.family}`;\n ctx.fillStyle = theme.colors.axis;\n ctx.globalAlpha = 0.55;\n ctx.textAlign = 'right';\n ctx.textBaseline = 'alphabetic';\n ctx.fillText('OpenData', x, y);\n ctx.restore();\n }\n\n // -------------------------------------------------------------------------\n // Batched edge drawing\n // -------------------------------------------------------------------------\n\n private drawEdgesBatched(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n focus: FocusSnapshot,\n dimOpacity: number,\n searchMatches: Set<string> | null,\n hoveredEdgeId: string | null,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Settled fast path: classify each edge into one of 3 tiers, batch within.\n const buckets: Record<FocusTier, PositionedEdge[]> = {\n dimmed: [],\n default: [],\n connected: [],\n };\n let hoveredEdge: PositionedEdge | null = null;\n\n for (const edge of edges) {\n const edgeId = `${edge.source}->${edge.target}`;\n if (edgeId === hoveredEdgeId) {\n hoveredEdge = edge;\n continue; // Draw hovered edge last, on top\n }\n buckets[edgeTier(edge, focus)].push(edge);\n }\n\n // Entrance: edges lag 30% behind the reveal, so scale every tier's alpha.\n const ea = entrance ? entrance.edgeAlpha : 1;\n\n // Draw dimmed first, then default, then connected (on top)\n this.drawEdgeGroupBatched(\n ctx,\n buckets.dimmed,\n edgeTierAlpha('dimmed', dimOpacity) * ea,\n searchMatches,\n enterAlphaFor,\n );\n this.drawEdgeGroupBatched(\n ctx,\n buckets.default,\n EDGE_ALPHA_DEFAULT * ea,\n searchMatches,\n enterAlphaFor,\n );\n this.drawEdgeGroupBatched(\n ctx,\n buckets.connected,\n EDGE_ALPHA_CONNECTED * ea,\n searchMatches,\n enterAlphaFor,\n );\n\n this.drawHoveredEdge(ctx, hoveredEdge);\n }\n\n /**\n * Crossfade edges between a prev and next focus state. Each edge is classified\n * under BOTH snapshots → at most 9 (prevTier × nextTier) buckets, each drawn\n * batched at `lerp(edgeTierAlpha[prev], edgeTierAlpha[next], t)`. Preserves the\n * per-group style batching within each bucket.\n */\n private drawEdgesCrossfade(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n prev: FocusSnapshot,\n next: FocusSnapshot,\n t: number,\n dimOpacity: number,\n searchMatches: Set<string> | null,\n hoveredEdgeId: string | null,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n const ea = entrance ? entrance.edgeAlpha : 1;\n // Bucket by (prevTier, nextTier). Key encodes both tiers.\n const buckets = new Map<string, PositionedEdge[]>();\n let hoveredEdge: PositionedEdge | null = null;\n\n for (const edge of edges) {\n const edgeId = `${edge.source}->${edge.target}`;\n if (edgeId === hoveredEdgeId) {\n hoveredEdge = edge;\n continue;\n }\n const key = `${edgeTier(edge, prev)}|${edgeTier(edge, next)}`;\n let bucket = buckets.get(key);\n if (!bucket) {\n bucket = [];\n buckets.set(key, bucket);\n }\n bucket.push(edge);\n }\n\n // Draw dim→dim first (lowest alpha) up to connected→connected, so brighter\n // buckets paint over dimmer ones. Sort by blended alpha ascending.\n const ordered = [...buckets.entries()]\n .map(([key, bucket]) => {\n const [prevTier, nextTier] = key.split('|') as [FocusTier, FocusTier];\n const alpha =\n lerp(edgeTierAlpha(prevTier, dimOpacity), edgeTierAlpha(nextTier, dimOpacity), t) * ea;\n return { alpha, bucket };\n })\n .sort((a, b) => a.alpha - b.alpha);\n\n for (const { alpha, bucket } of ordered) {\n this.drawEdgeGroupBatched(ctx, bucket, alpha, searchMatches, enterAlphaFor);\n }\n\n this.drawHoveredEdge(ctx, hoveredEdge);\n }\n\n /** Draw the hovered edge on top with a thickened highlight stroke. */\n private drawHoveredEdge(ctx: CanvasRenderingContext2D, hoveredEdge: PositionedEdge | null): void {\n if (!hoveredEdge) return;\n const dash = DASH_PATTERNS[hoveredEdge.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = hoveredEdge.stroke;\n ctx.lineWidth = hoveredEdge.strokeWidth * 2;\n ctx.globalAlpha = EDGE_ALPHA_CONNECTED;\n ctx.beginPath();\n ctx.moveTo(hoveredEdge.sourceX, hoveredEdge.sourceY);\n ctx.lineTo(hoveredEdge.targetX, hoveredEdge.targetY);\n ctx.stroke();\n ctx.setLineDash([]);\n ctx.globalAlpha = 1;\n }\n\n /**\n * Draw a group of edges at a given alpha, batched by (stroke, strokeWidth, style).\n * When search is inactive, all edges of the same style are drawn in a single path.\n * When search is active, edges split by search-match status for alpha dimming.\n */\n private drawEdgeGroupBatched(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n alpha: number,\n searchMatches: Set<string> | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n if (edges.length === 0) return;\n\n // Group by visual key: stroke + strokeWidth + style + quantized edge-enter\n // alpha. An edge touching a newly-added node fades with the min of its\n // endpoints' enter alpha (8-bucket quantized so batching stays bounded). When\n // no enter fade is active every edge quantizes to 1 → a single batch (no\n // overhead in the common case).\n const groups = new Map<string, { edges: PositionedEdge[]; enter: number }>();\n for (const edge of edges) {\n const rawEnter = Math.min(enterAlphaFor(edge.source), enterAlphaFor(edge.target));\n const enter = Math.round(rawEnter * 8) / 8;\n const key = `${edge.stroke}|${edge.strokeWidth}|${edge.style}|${enter}`;\n let group = groups.get(key);\n if (!group) {\n group = { edges: [], enter };\n groups.set(key, group);\n }\n group.edges.push(edge);\n }\n\n for (const [, { edges: group, enter }] of groups) {\n const sample = group[0];\n const dash = DASH_PATTERNS[sample.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = sample.stroke;\n ctx.lineWidth = sample.strokeWidth;\n const groupAlpha = alpha * enter;\n\n if (!searchMatches) {\n // Fast path: single batched path for all edges in this group\n ctx.globalAlpha = groupAlpha;\n ctx.beginPath();\n for (const edge of group) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n } else {\n // Search active: split into matched and non-matched batches\n ctx.globalAlpha = groupAlpha;\n ctx.beginPath();\n let hasMatched = false;\n\n const nonMatchPath: PositionedEdge[] = [];\n\n for (const edge of group) {\n const srcMatch = searchMatches.has(edge.source);\n const tgtMatch = searchMatches.has(edge.target);\n if (srcMatch || tgtMatch) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n hasMatched = true;\n } else {\n nonMatchPath.push(edge);\n }\n }\n if (hasMatched) ctx.stroke();\n\n // Draw non-matching edges dimmed\n if (nonMatchPath.length > 0) {\n ctx.globalAlpha = SEARCH_NON_MATCH_ALPHA * groupAlpha;\n ctx.beginPath();\n for (const edge of nonMatchPath) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n }\n }\n }\n\n ctx.setLineDash([]);\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Batched node drawing\n // -------------------------------------------------------------------------\n\n private drawNodesBatched(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n searchMatches: Set<string> | null,\n showGlow: boolean,\n theme: GraphRenderState['theme'],\n minRadius: number,\n nextFocus: FocusSnapshot,\n dimOpacity: number,\n exemptIds: Set<string> | undefined,\n crossfade: { t: number; prev: FocusSnapshot; next: FocusSnapshot } | null,\n hoverRadiusScale: Map<string, number> | undefined,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Effective per-node alpha = focus dim × search dim. Focus dim crossfades\n // between prev/next tiers when a transition is live; otherwise it's the\n // settled next-tier alpha. Batching is preserved by grouping nodes that\n // share (fill, quantized-alpha) — at most 2 focus tiers × 2 search tiers.\n const focusAlpha = (node: PositionedNode): number => {\n if (crossfade) {\n return lerp(\n nodeTierAlpha(nodeTier(node, crossfade.prev, exemptIds), dimOpacity),\n nodeTierAlpha(nodeTier(node, crossfade.next, exemptIds), dimOpacity),\n crossfade.t,\n );\n }\n return nodeTierAlpha(nodeTier(node, nextFocus, exemptIds), dimOpacity);\n };\n const searchAlpha = (node: PositionedNode): number =>\n searchMatches !== null && !searchMatches.has(node.id) ? SEARCH_NON_MATCH_ALPHA : 1;\n // Entrance pop: alpha and scale run separate curves (alpha 0→1, scale\n // 0→overshoot→1). Both derive from the same quantized per-node progress, so\n // the fill/stroke batching keys stay bounded during the reveal.\n const entranceAlpha = (node: PositionedNode): number =>\n entrance ? entrance.nodeAlpha(node) : 1;\n const entranceScale = (node: PositionedNode): number =>\n entrance ? entrance.nodeScale(node) : 1;\n // Convergence drift: nodes pop in slightly outside their final spot and\n // slide home. Zero when the entrance is settled or unstaggered.\n const entranceShift = (node: PositionedNode): { x: number; y: number } =>\n entrance ? entrance.shift(node.id) : ZERO_SHIFT;\n // Data-update enter fade: newly-added nodes ramp 0→1 (already bucket-quantized\n // by the mount), preserving fill/stroke batching keys.\n const effectiveAlpha = (node: PositionedNode): number =>\n focusAlpha(node) * searchAlpha(node) * entranceAlpha(node) * enterAlphaFor(node.id);\n\n // Separate special nodes (hovered/selected, or mid radius-tween) from bulk.\n const bulkNodes: PositionedNode[] = [];\n const specialNodes: PositionedNode[] = [];\n\n for (const node of nodes) {\n if (\n node.id === hoveredNodeId ||\n selectedNodeIds.has(node.id) ||\n (hoverRadiusScale?.has(node.id) ?? false)\n ) {\n specialNodes.push(node);\n } else {\n bulkNodes.push(node);\n }\n }\n\n // Helper: effective radius clamped to minimum screen size, then scaled by\n // the entrance pop curve.\n const r = (node: PositionedNode) => Math.max(node.radius, minRadius) * entranceScale(node);\n\n // --- Glow pass (dark mode only, before fills) ---\n // Skipped mid-entrance: the glow draws at final positions and would visibly\n // detach from nodes still on their convergence drift.\n if (showGlow && !entrance) {\n this.drawGlowBatched(ctx, bulkNodes, searchMatches, minRadius);\n }\n\n // --- Bulk fill pass: batch by (fill color, quantized alpha) ---\n const fillGroups = new Map<string, { fill: string; alpha: number; nodes: PositionedNode[] }>();\n for (const node of bulkNodes) {\n const alpha = effectiveAlpha(node);\n const key = `${node.fill}|${alpha.toFixed(3)}`;\n let group = fillGroups.get(key);\n if (!group) {\n group = { fill: node.fill, alpha, nodes: [] };\n fillGroups.set(key, group);\n }\n group.nodes.push(node);\n }\n\n for (const { fill, alpha, nodes: group } of fillGroups.values()) {\n ctx.fillStyle = fill;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n const nr = r(node);\n if (nr <= 0) continue;\n const s = entranceShift(node);\n ctx.moveTo(node.x + s.x + nr, node.y + s.y);\n ctx.arc(node.x + s.x, node.y + s.y, nr, 0, TWO_PI);\n }\n ctx.fill();\n }\n\n // --- Bulk stroke pass: batch by (stroke color+width, quantized alpha) ---\n const strokeGroups = new Map<\n string,\n { stroke: string; width: number; alpha: number; nodes: PositionedNode[] }\n >();\n for (const node of bulkNodes) {\n const alpha = effectiveAlpha(node);\n const key = `${node.stroke}|${node.strokeWidth}|${alpha.toFixed(3)}`;\n let group = strokeGroups.get(key);\n if (!group) {\n group = { stroke: node.stroke, width: node.strokeWidth, alpha, nodes: [] };\n strokeGroups.set(key, group);\n }\n group.nodes.push(node);\n }\n\n for (const { stroke, width, alpha, nodes: group } of strokeGroups.values()) {\n ctx.strokeStyle = stroke;\n ctx.lineWidth = width;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n const nr = r(node);\n if (nr <= 0) continue;\n const s = entranceShift(node);\n ctx.moveTo(node.x + s.x + nr, node.y + s.y);\n ctx.arc(node.x + s.x, node.y + s.y, nr, 0, TWO_PI);\n }\n ctx.stroke();\n }\n\n // --- Special nodes (hovered/selected) drawn individually ---\n for (const node of specialNodes) {\n const isHovered = node.id === hoveredNodeId;\n const isSelected = selectedNodeIds.has(node.id);\n const dimmed = searchMatches !== null && !searchMatches.has(node.id);\n const baseRadius = Math.max(node.radius, minRadius);\n // Hover radius tween: scale routes through here until it returns to 1.\n const hoverScale = hoverRadiusScale?.get(node.id) ?? (isHovered ? 1.15 : 1);\n const radius = baseRadius * hoverScale;\n // brighten() switches at the scale midpoint (>1.075 of the 1→1.15 range).\n const brightened = isHovered && hoverScale >= 1.075;\n const s = entranceShift(node);\n const nx = node.x + s.x;\n const ny = node.y + s.y;\n\n ctx.globalAlpha = dimmed ? SEARCH_NON_MATCH_ALPHA : 1;\n\n // Glow for special nodes\n if (showGlow && !dimmed) {\n ctx.beginPath();\n ctx.arc(nx, ny, radius * GLOW_RADIUS_MULTIPLIER, 0, TWO_PI);\n ctx.fillStyle = node.fill;\n ctx.globalAlpha = GLOW_ALPHA;\n ctx.fill();\n ctx.globalAlpha = dimmed ? SEARCH_NON_MATCH_ALPHA : 1;\n }\n\n // Fill\n ctx.beginPath();\n ctx.arc(nx, ny, radius, 0, TWO_PI);\n ctx.fillStyle = brightened ? brighten(node.fill) : node.fill;\n ctx.fill();\n\n // Stroke\n ctx.strokeStyle = node.stroke;\n ctx.lineWidth = node.strokeWidth;\n ctx.stroke();\n\n // Selection ring\n if (isSelected) {\n ctx.beginPath();\n ctx.arc(nx, ny, radius + 3, 0, TWO_PI);\n ctx.strokeStyle = theme.colors.categorical[0] ?? '#3b82f6';\n ctx.lineWidth = 2;\n ctx.stroke();\n }\n }\n\n ctx.globalAlpha = 1;\n }\n\n /** Batch glow circles by fill color. */\n private drawGlowBatched(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n searchMatches: Set<string> | null,\n minRadius: number,\n ): void {\n const glowGroups = new Map<string, PositionedNode[]>();\n for (const node of nodes) {\n if (searchMatches && !searchMatches.has(node.id)) continue;\n let group = glowGroups.get(node.fill);\n if (!group) {\n group = [];\n glowGroups.set(node.fill, group);\n }\n group.push(node);\n }\n\n ctx.globalAlpha = GLOW_ALPHA;\n for (const [fill, group] of glowGroups) {\n ctx.fillStyle = fill;\n ctx.beginPath();\n for (const node of group) {\n const gr = Math.max(node.radius, minRadius) * GLOW_RADIUS_MULTIPLIER;\n ctx.moveTo(node.x + gr, node.y);\n ctx.arc(node.x, node.y, gr, 0, TWO_PI);\n }\n ctx.fill();\n }\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Labels (drawn individually, skipped during gestures)\n // -------------------------------------------------------------------------\n\n private drawLabels(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n threshold: number,\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n searchMatches: Set<string> | null,\n zoom: number,\n theme: GraphRenderState['theme'],\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Labels fade in with the raw entrance progress (× on top of dim alpha).\n const la = entrance ? entrance.labelAlpha : 1;\n // Font size inversely scaled by zoom, clamped to readable range\n const rawSize = 10 / zoom;\n const fontSize = Math.max(LABEL_FONT_MIN, Math.min(LABEL_FONT_MAX, rawSize));\n\n ctx.font = `${fontSize}px ${theme.fonts.family}`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n\n for (const node of nodes) {\n if (!node.label) continue;\n\n const isHovered = node.id === hoveredNodeId;\n const isSelected = selectedNodeIds.has(node.id);\n const forced = isHovered || isSelected;\n const dimmed = searchMatches !== null && !searchMatches.has(node.id);\n\n // LOD: skip labels below threshold unless forced\n if (!forced && node.labelPriority < threshold) continue;\n\n ctx.globalAlpha = (dimmed ? SEARCH_NON_MATCH_ALPHA : 1) * la * enterAlphaFor(node.id);\n\n const labelY = node.y + node.radius + 3;\n\n // Halo for readability: stroke behind text in the background color\n // so labels stay legible over edges and other nodes.\n if (theme.colors.background !== 'transparent') {\n ctx.strokeStyle = theme.colors.background;\n } else {\n // Transparent bg inherits its mode from the theme's darkMode flag, not\n // from text luminance. Dark mode = dark page = dark halo behind light\n // text; light mode = light page = light halo.\n ctx.strokeStyle = theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.85)';\n }\n ctx.lineWidth = 3;\n ctx.lineJoin = 'round';\n ctx.miterLimit = 2;\n ctx.strokeText(node.label, node.x, labelY);\n\n ctx.fillStyle = theme.colors.text;\n ctx.fillText(node.label, node.x, labelY);\n }\n\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Exit ghosts (Phase 7)\n // -------------------------------------------------------------------------\n\n /**\n * Draw exit ghosts (removed nodes/edges) UNDER the live marks at a global fade\n * alpha. Neutral rendering: no focus dim, no search dim, no selection ring —\n * they're on their way out. Batched by (stroke/style) for edges and (fill) for\n * nodes, culled to the visible rect.\n */\n private drawGhosts(\n ctx: CanvasRenderingContext2D,\n exiting: NonNullable<GraphRenderState['exiting']>,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n ): void {\n const alpha = exiting.alpha;\n\n // Ghost edges: batch by (stroke, strokeWidth, style).\n const edgeGroups = new Map<string, PositionedEdge[]>();\n for (const edge of exiting.edges) {\n if (!edgeInView(edge, rect)) continue;\n const key = `${edge.stroke}|${edge.strokeWidth}|${edge.style}`;\n const group = edgeGroups.get(key);\n if (group) group.push(edge);\n else edgeGroups.set(key, [edge]);\n }\n for (const [, group] of edgeGroups) {\n const sample = group[0];\n const dash = DASH_PATTERNS[sample.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = sample.stroke;\n ctx.lineWidth = sample.strokeWidth;\n ctx.globalAlpha = EDGE_ALPHA_DEFAULT * alpha;\n ctx.beginPath();\n for (const edge of group) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n }\n ctx.setLineDash([]);\n\n // Ghost nodes: batch by fill color.\n const nodeGroups = new Map<string, PositionedNode[]>();\n for (const node of exiting.nodes) {\n if (!nodeInView(node, rect)) continue;\n const group = nodeGroups.get(node.fill);\n if (group) group.push(node);\n else nodeGroups.set(node.fill, [node]);\n }\n for (const [fill, group] of nodeGroups) {\n ctx.fillStyle = fill;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n ctx.moveTo(node.x + node.radius, node.y);\n ctx.arc(node.x, node.y, node.radius, 0, TWO_PI);\n }\n ctx.fill();\n }\n\n ctx.globalAlpha = 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Color helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Brighten a hex/rgb color by ~20% for hover effect.\n * Quick and dirty approach: parse hex, lighten each channel.\n */\nfunction brighten(color: string): string {\n // Handle rgb(r,g,b) or rgb(r, g, b)\n const rgbMatch = color.match(/^rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n if (rgbMatch) {\n const r = Math.min(255, parseInt(rgbMatch[1], 10) + 40);\n const g = Math.min(255, parseInt(rgbMatch[2], 10) + 40);\n const b = Math.min(255, parseInt(rgbMatch[3], 10) + 40);\n return `rgb(${r},${g},${b})`;\n }\n\n // Handle hex colors (#rgb and #rrggbb)\n const hex = color.replace('#', '');\n const full =\n hex.length === 3\n ? hex\n .split('')\n .map((c) => c + c)\n .join('')\n : hex;\n\n if (full.length === 6) {\n const r = Math.min(255, parseInt(full.slice(0, 2), 16) + 40);\n const g = Math.min(255, parseInt(full.slice(2, 4), 16) + 40);\n const b = Math.min(255, parseInt(full.slice(4, 6), 16) + 40);\n return `rgb(${r},${g},${b})`;\n }\n\n return color;\n}\n","/**\n * Entrance choreography math for the graph reveal.\n *\n * The mount drives a single 0→1 `entranceProgress`; this module turns it into a\n * per-node reveal alpha with a staggered start. The per-node alpha is quantized\n * to a small number of levels so the canvas renderer can still batch fills by\n * (color, alpha) — an unquantized stagger would force one fill() per node.\n *\n * Above ENTRANCE_STAGGER_MAX_NODES the mount uses a single global fade (no\n * per-node stagger) because thousands of distinct start times defeat batching\n * and read as noise anyway.\n */\n\n/**\n * Per-node reveal progress at global progress `globalT`.\n *\n * Node `index` starts revealing at `(index/total)·0.4` and ramps over the next\n * `0.6` of the timeline, so the first node is fully in at t=0.6 and the last at\n * t=1.0. The result is quantized to `buckets` (≤8) levels to preserve batching.\n */\nexport function nodeEnterProgress(\n globalT: number,\n index: number,\n total: number,\n buckets = 8,\n): number {\n const g = globalT <= 0 ? 0 : globalT >= 1 ? 1 : globalT;\n const start = total > 0 ? (index / total) * 0.4 : 0;\n const local = (g - start) / 0.6;\n const clamped = local <= 0 ? 0 : local >= 1 ? 1 : local;\n // Quantize to `buckets` levels (0, 1/b, ..., 1) so fills stay batchable.\n const b = Math.max(1, Math.floor(buckets));\n return Math.round(clamped * b) / b;\n}\n\n/**\n * Above this node count the entrance uses a single global fade with no per-node\n * stagger — thousands of start times kill batching and read as noise.\n */\nexport const ENTRANCE_STAGGER_MAX_NODES = 3000;\n\n// ---------------------------------------------------------------------------\n// Organic pop: order, scale curve, and convergence drift\n// ---------------------------------------------------------------------------\n\n/** How far (px, graph space) a node starts from its final spot during the pop. */\nexport const ENTRANCE_DRIFT_PX = 16;\n\ntype XYNode = { id: string; x: number; y: number };\n\nfunction centroid(nodes: XYNode[]): { cx: number; cy: number } {\n let cx = 0;\n let cy = 0;\n for (const n of nodes) {\n cx += n.x;\n cy += n.y;\n }\n cx /= nodes.length;\n cy /= nodes.length;\n return { cx, cy };\n}\n\n/**\n * Deterministic hash of a node id to a well-distributed 32-bit unsigned int\n * (FNV-1a). Used to scatter entrance timing: same id → same slot every render,\n * so the reveal stays stable and testable without a spatial or index bias.\n */\nfunction hashId(id: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < id.length; i++) {\n h ^= id.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n/**\n * Stagger rank for each node: a deterministic hash-shuffle of the node ids, so\n * each node reveals at an independent, scattered time rather than in a uniform\n * center-outward ripple. The scatter reads as many independent elements popping\n * in; a spatial (centroid-radial) or index order reads as one coordinated sweep.\n * Hashing the id keeps the order stable per render (pure function of ids), so it\n * survives re-renders and stays testable.\n */\nexport function entranceOrder(nodes: XYNode[]): Map<string, number> {\n const rank = new Map<string, number>();\n if (nodes.length === 0) return rank;\n const sorted = [...nodes].sort((a, b) => {\n const ha = hashId(a.id);\n const hb = hashId(b.id);\n // Tie-break on id so equal hashes (rare) still give a total, stable order.\n return ha - hb || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\n });\n for (let i = 0; i < sorted.length; i++) rank.set(sorted[i].id, i);\n return rank;\n}\n\n/**\n * Per-node drift offset: a unit vector pointing away from the layout centroid,\n * scaled to ENTRANCE_DRIFT_PX. Nodes pop in slightly outside their final spot\n * and converge inward. Deterministic (pure function of positions), zero-safe\n * at the centroid.\n */\nexport function entranceOffsets(\n nodes: XYNode[],\n dist: number = ENTRANCE_DRIFT_PX,\n): Map<string, { x: number; y: number }> {\n const offsets = new Map<string, { x: number; y: number }>();\n if (nodes.length === 0) return offsets;\n const { cx, cy } = centroid(nodes);\n for (const n of nodes) {\n const dx = n.x - cx;\n const dy = n.y - cy;\n const len = Math.sqrt(dx * dx + dy * dy);\n if (len < 1e-6) {\n offsets.set(n.id, { x: 0, y: -dist });\n } else {\n offsets.set(n.id, { x: (dx / len) * dist, y: (dy / len) * dist });\n }\n }\n return offsets;\n}\n\n/**\n * Pop scale curve: 0 at t=0, overshoots to ~1.1 around t≈0.7, settles at 1.\n * Standard back-out easing applied to scale, so nodes pop rather than fade.\n */\nexport function popScale(t: number): number {\n if (t <= 0) return 0;\n if (t >= 1) return 1;\n const c1 = 1.70158;\n const c3 = c1 + 1;\n const u = t - 1;\n return 1 + c3 * u * u * u + c1 * u * u;\n}\n\n/**\n * Pop alpha curve: reaches full opacity by 60% of the node's window so the\n * overshoot phase of the scale pop is fully visible, not half-faded.\n */\nexport function popAlpha(t: number): number {\n const a = t / 0.6;\n return a >= 1 ? 1 : a <= 0 ? 0 : a;\n}\n\n/**\n * Drift factor: 1 at t=0 (full offset from final position) easing quadratically\n * to 0 (node at rest). Multiplied into the per-node entrance offset.\n */\nexport function driftFactor(t: number): number {\n const u = 1 - (t <= 0 ? 0 : t >= 1 ? 1 : t);\n return u * u;\n}\n","/**\n * Per-frame focus model for the graph: unifies the three emphasis sources\n * (programmatic highlight, search matches, hover-neighborhood) into a single\n * snapshot pair that the renderer crossfades between.\n *\n * Composition, not strict precedence (see plan §5a):\n * - Standing state = highlight ∩ search when both active; if that intersection\n * is empty, search matches win (fresher user intent). This preserves the\n * \"filter by topic, then search within it\" workflow.\n * - Hover-neighborhood is the transient top layer over whatever the standing\n * state is.\n *\n * The transition tweens between two discrete FocusSnapshots. Rapid hover sweeps\n * retarget mid-flight: `retarget(next, now)` snapshots the endpoint CLOSEST to\n * the current display (`prev = p < 0.5 ? old prev : old next`), so a low-p\n * retarget keeps the old prev — no forward snap, worst-case visual jump halved\n * vs. a naive \"always start from current next\" rule. Exact per-edge capture\n * would destroy the renderer's tier batching, so this discrete approximation is\n * deliberate.\n */\n\n/** The set of emphasis relationships in effect for one steady state. */\nexport interface FocusSnapshot {\n /** True when any emphasis source is active (something is dimmed). */\n hasActive: boolean;\n /** Nodes connected to the active/hovered set (includes the active nodes). */\n connected: Set<string>;\n /** Search matches, or null when search is inactive. */\n searchMatches: Set<string> | null;\n /** Selected nodes (selection rings; always emphasized). */\n selected: Set<string>;\n}\n\n/** An empty (nothing emphasized) snapshot. */\nexport function emptyFocusSnapshot(): FocusSnapshot {\n return { hasActive: false, connected: new Set(), searchMatches: null, selected: new Set() };\n}\n\n/** Whether two snapshots describe the same emphasis state (skip re-tween). */\nexport function focusSnapshotsEqual(a: FocusSnapshot, b: FocusSnapshot): boolean {\n return (\n a.hasActive === b.hasActive &&\n setsEqual(a.connected, b.connected) &&\n nullableSetsEqual(a.searchMatches, b.searchMatches) &&\n setsEqual(a.selected, b.selected)\n );\n}\n\nfunction setsEqual(a: Set<string>, b: Set<string>): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction nullableSetsEqual(a: Set<string> | null, b: Set<string> | null): boolean {\n if (a === null || b === null) return a === b;\n return setsEqual(a, b);\n}\n\n/**\n * A crossfade between two focus snapshots, eased over `duration` ms.\n *\n * Time is injected (`now` passed to `retarget`/`progress`) so the mount's\n * scheduler drives it deterministically and tests use a controllable clock.\n */\nexport class FocusTransition {\n prev: FocusSnapshot;\n next: FocusSnapshot;\n private startTime: number;\n private duration: number;\n private ease: (t: number) => number;\n\n constructor(initial: FocusSnapshot, duration: number, ease: (t: number) => number, now: number) {\n this.prev = initial;\n this.next = initial;\n this.startTime = now;\n this.duration = Math.max(0, duration);\n this.ease = ease;\n }\n\n /**\n * Point a fresh transition at `target`, capturing the endpoint closest to the\n * current display as the new `prev` (the `p < 0.5` rule). A no-op when\n * `target` already equals `next`.\n */\n retarget(target: FocusSnapshot, now: number): void {\n if (focusSnapshotsEqual(target, this.next)) return;\n const p = this.rawProgress(now);\n // Below the midpoint the display is still nearer the old prev; keep it to\n // avoid a forward snap. At/after the midpoint the old next is nearer.\n this.prev = p < 0.5 ? this.prev : this.next;\n this.next = target;\n this.startTime = now;\n }\n\n /** Raw (un-eased) 0..1 progress. */\n private rawProgress(now: number): number {\n if (this.duration <= 0) return 1;\n return Math.min(1, Math.max(0, (now - this.startTime) / this.duration));\n }\n\n /** Eased 0..1 progress toward `next`. */\n progress(now: number): number {\n return this.ease(this.rawProgress(now));\n }\n\n /** True once the transition has fully settled onto `next`. */\n isSettled(now: number): boolean {\n return this.rawProgress(now) >= 1;\n }\n}\n\n/**\n * Build the standing (non-hover) focus snapshot from highlight + search sets.\n *\n * Composition rule: emphasized = highlight ∩ search when both non-empty; if the\n * intersection is empty, search wins. Selection is always carried through.\n */\nexport function composeStandingFocus(\n highlight: Set<string> | null,\n searchMatches: Set<string> | null,\n selected: Set<string>,\n adjacency: Map<string, Set<string>>,\n): FocusSnapshot {\n const hasHighlight = highlight !== null && highlight.size > 0;\n const hasSearch = searchMatches !== null && searchMatches.size > 0;\n\n // Determine the emphasized \"core\" set that drives connected-neighborhood dim.\n let core: Set<string> | null = null;\n if (highlight !== null && hasHighlight && searchMatches !== null && hasSearch) {\n const inter = intersect(highlight, searchMatches);\n core = inter.size > 0 ? inter : searchMatches;\n } else if (hasHighlight) {\n core = highlight;\n } else if (hasSearch) {\n // Search dims via searchMatches directly (renderer's search path), not the\n // connected-neighborhood path — leave core null so hover still works.\n core = null;\n }\n\n const connected = new Set<string>();\n if (core) {\n for (const id of core) {\n connected.add(id);\n const neighbors = adjacency.get(id);\n if (neighbors) for (const nid of neighbors) connected.add(nid);\n }\n }\n\n const hasActive = (core !== null && core.size > 0) || hasSearch || selected.size > 0;\n\n return {\n hasActive,\n connected,\n searchMatches: hasSearch ? searchMatches : null,\n selected,\n };\n}\n\n/**\n * Layer a hover neighborhood on top of a standing snapshot. The hovered node\n * and its neighbors become the connected set; the standing search/selection\n * carry through so search dimming and selection rings persist under hover.\n */\nexport function layerHoverFocus(\n standing: FocusSnapshot,\n hoveredId: string | null,\n hoverConnected: Set<string> | null,\n): FocusSnapshot {\n if (hoveredId === null || hoverConnected === null) return standing;\n return {\n hasActive: true,\n connected: hoverConnected,\n searchMatches: standing.searchMatches,\n selected: standing.selected,\n };\n}\n\nfunction intersect(a: Set<string>, b: Set<string>): Set<string> {\n const [small, large] = a.size <= b.size ? [a, b] : [b, a];\n const out = new Set<string>();\n for (const v of small) if (large.has(v)) out.add(v);\n return out;\n}\n","/**\n * Graph interaction manager.\n *\n * Handles mouse/touch events on the canvas and translates them into\n * high-level graph interactions: pan, zoom, hover, select, drag nodes.\n * Uses the spatial index for hit testing and ZoomTransform for coordinate\n * conversion.\n */\n\nimport type { SpatialIndex } from './spatial-index';\nimport { ZoomTransform } from './zoom';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst ZOOM_MIN = 0.05;\nconst ZOOM_MAX = 15;\nconst ZOOM_STEP = -0.001;\nconst HIT_DISTANCE = 5;\n\n// ---------------------------------------------------------------------------\n// Callback interface\n// ---------------------------------------------------------------------------\n\nexport interface InteractionCallbacks {\n onTransformChange(transform: ZoomTransform): void;\n onHoverChange(nodeId: string | null): void;\n /** Called during mouse move when no node is hit, with graph-space coordinates for edge hit testing. */\n onBackgroundHover?(graphX: number, graphY: number, screenX: number, screenY: number): void;\n onSelectionChange(nodeIds: string[]): void;\n onNodeDragStart(nodeId: string): void;\n onNodeDrag(nodeId: string, x: number, y: number): void;\n onNodeDragEnd(nodeId: string): void;\n onDoubleClick(nodeId: string): void;\n /** Pointer moved over the canvas (graph-space coords). Used by cursor repulsion. */\n onPointerMove?(graphX: number, graphY: number): void;\n /** Pointer left the canvas. Used to deactivate cursor repulsion. */\n onPointerLeave?(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\ninterface DragState {\n nodeId: string;\n started: boolean;\n}\n\ninterface PanState {\n startX: number;\n startY: number;\n}\n\n// ---------------------------------------------------------------------------\n// GraphInteractionManager\n// ---------------------------------------------------------------------------\n\nexport class GraphInteractionManager {\n private canvas: HTMLCanvasElement;\n private spatialIndex: SpatialIndex;\n private callbacks: InteractionCallbacks;\n private transform = ZoomTransform.identity();\n\n private dragState: DragState | null = null;\n private panState: PanState | null = null;\n private mousedownNodeId: string | null = null;\n private selectedIds: Set<string> = new Set();\n\n // Touch state\n private lastTouchDist: number | null = null;\n private lastTouchCenter: { x: number; y: number } | null = null;\n\n // Bound handlers for cleanup\n private boundWheel: (e: WheelEvent) => void;\n private boundMouseDown: (e: MouseEvent) => void;\n private boundMouseMove: (e: MouseEvent) => void;\n private boundMouseUp: (e: MouseEvent) => void;\n private boundDblClick: (e: MouseEvent) => void;\n private boundTouchStart: (e: TouchEvent) => void;\n private boundTouchMove: (e: TouchEvent) => void;\n private boundTouchEnd: (e: TouchEvent) => void;\n private boundMouseLeave: (e: MouseEvent) => void;\n\n constructor(\n canvas: HTMLCanvasElement,\n spatialIndex: SpatialIndex,\n callbacks: InteractionCallbacks,\n ) {\n this.canvas = canvas;\n this.spatialIndex = spatialIndex;\n this.callbacks = callbacks;\n\n // Bind handlers\n this.boundWheel = this.onWheel.bind(this);\n this.boundMouseDown = this.onMouseDown.bind(this);\n this.boundMouseMove = this.onMouseMove.bind(this);\n this.boundMouseUp = this.onMouseUp.bind(this);\n this.boundMouseLeave = this.onMouseLeave.bind(this);\n this.boundDblClick = this.onDblClick.bind(this);\n this.boundTouchStart = this.onTouchStart.bind(this);\n this.boundTouchMove = this.onTouchMove.bind(this);\n this.boundTouchEnd = this.onTouchEnd.bind(this);\n\n // Attach event listeners\n canvas.addEventListener('wheel', this.boundWheel, { passive: false });\n canvas.addEventListener('mousedown', this.boundMouseDown);\n canvas.addEventListener('mousemove', this.boundMouseMove);\n canvas.addEventListener('mouseup', this.boundMouseUp);\n canvas.addEventListener('mouseleave', this.boundMouseLeave);\n canvas.addEventListener('dblclick', this.boundDblClick);\n canvas.addEventListener('touchstart', this.boundTouchStart, {\n passive: false,\n });\n canvas.addEventListener('touchmove', this.boundTouchMove, {\n passive: false,\n });\n canvas.addEventListener('touchend', this.boundTouchEnd);\n }\n\n setTransform(transform: ZoomTransform): void {\n this.transform = transform;\n }\n\n getTransform(): ZoomTransform {\n return this.transform;\n }\n\n /**\n * Replace the internal selection set. Used by the mount to prune deleted ids\n * after a data update so a later shift-click can't resurrect them through\n * `onSelectionChange`. Does NOT fire `onSelectionChange` — the caller owns any\n * downstream sync (the mount already holds the pruned set).\n */\n setSelection(ids: string[]): void {\n this.selectedIds = new Set(ids);\n }\n\n destroy(): void {\n this.canvas.removeEventListener('wheel', this.boundWheel);\n this.canvas.removeEventListener('mousedown', this.boundMouseDown);\n this.canvas.removeEventListener('mousemove', this.boundMouseMove);\n this.canvas.removeEventListener('mouseup', this.boundMouseUp);\n this.canvas.removeEventListener('mouseleave', this.boundMouseLeave);\n this.canvas.removeEventListener('dblclick', this.boundDblClick);\n this.canvas.removeEventListener('touchstart', this.boundTouchStart);\n this.canvas.removeEventListener('touchmove', this.boundTouchMove);\n this.canvas.removeEventListener('touchend', this.boundTouchEnd);\n }\n\n // -------------------------------------------------------------------------\n // Mouse handlers\n // -------------------------------------------------------------------------\n\n private canvasXY(e: MouseEvent): { x: number; y: number } {\n const rect = this.canvas.getBoundingClientRect();\n return { x: e.clientX - rect.left, y: e.clientY - rect.top };\n }\n\n private hitTest(screenX: number, screenY: number): string | null {\n const graph = this.transform.screenToGraph(screenX, screenY);\n const node = this.spatialIndex.findNearest(graph.x, graph.y, HIT_DISTANCE / this.transform.k);\n return node?.id ?? null;\n }\n\n private onWheel(e: WheelEvent): void {\n e.preventDefault();\n const { x, y } = this.canvasXY(e);\n const factor = e.deltaY * ZOOM_STEP;\n const newK = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, this.transform.k * (1 + factor)));\n this.transform = this.transform.zoomAt(newK, x, y);\n this.callbacks.onTransformChange(this.transform);\n }\n\n private onMouseDown(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n const hitId = this.hitTest(x, y);\n\n if (hitId) {\n // Start potential node drag\n this.dragState = { nodeId: hitId, started: false };\n this.mousedownNodeId = hitId;\n } else {\n // Start pan\n this.panState = { startX: x, startY: y };\n this.mousedownNodeId = null;\n }\n }\n\n private onMouseMove(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n\n // Feed the pointer position (graph-space) to cursor repulsion, if wired.\n // Fires on every move; the mount owns throttling and the node-count gate.\n if (this.callbacks.onPointerMove) {\n const gp = this.transform.screenToGraph(x, y);\n this.callbacks.onPointerMove(gp.x, gp.y);\n }\n\n if (this.dragState) {\n const graph = this.transform.screenToGraph(x, y);\n if (!this.dragState.started) {\n this.dragState.started = true;\n this.callbacks.onNodeDragStart(this.dragState.nodeId);\n }\n this.callbacks.onNodeDrag(this.dragState.nodeId, graph.x, graph.y);\n return;\n }\n\n if (this.panState) {\n const dx = x - this.panState.startX;\n const dy = y - this.panState.startY;\n this.transform = this.transform.pan(dx, dy);\n this.panState = { startX: x, startY: y };\n this.callbacks.onTransformChange(this.transform);\n return;\n }\n\n // Hover detection\n const hitId = this.hitTest(x, y);\n this.callbacks.onHoverChange(hitId);\n\n // If no node hit, check edges via callback\n if (!hitId) {\n const graph = this.transform.screenToGraph(x, y);\n this.callbacks.onBackgroundHover?.(graph.x, graph.y, x, y);\n }\n\n // Update cursor\n this.canvas.style.cursor = hitId ? 'pointer' : 'default';\n }\n\n private onMouseUp(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n\n if (this.dragState) {\n if (this.dragState.started) {\n this.callbacks.onNodeDragEnd(this.dragState.nodeId);\n } else {\n // Was a click on a node (no drag movement)\n this.handleNodeClick(this.dragState.nodeId, e.shiftKey);\n }\n this.dragState = null;\n return;\n }\n\n if (this.panState) {\n this.panState = null;\n\n // If mouse up is on background (no node), treat as background click\n if (!this.mousedownNodeId) {\n const hitId = this.hitTest(x, y);\n if (!hitId) {\n // Background click: clear selection\n this.selectedIds.clear();\n this.callbacks.onSelectionChange([]);\n }\n }\n return;\n }\n }\n\n private onDblClick(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n const hitId = this.hitTest(x, y);\n if (hitId) {\n this.callbacks.onDoubleClick(hitId);\n }\n }\n\n private onMouseLeave(_e: MouseEvent): void {\n this.callbacks.onHoverChange(null);\n this.canvas.style.cursor = 'default';\n // Deactivate cursor repulsion when the pointer leaves the canvas.\n this.callbacks.onPointerLeave?.();\n\n // Cancel any in-progress pan\n if (this.panState) {\n this.panState = null;\n }\n }\n\n private handleNodeClick(nodeId: string, shiftKey: boolean): void {\n if (shiftKey) {\n // Toggle node in multi-select\n if (this.selectedIds.has(nodeId)) {\n this.selectedIds.delete(nodeId);\n } else {\n this.selectedIds.add(nodeId);\n }\n } else {\n // Single select\n this.selectedIds.clear();\n this.selectedIds.add(nodeId);\n }\n\n this.callbacks.onSelectionChange([...this.selectedIds]);\n }\n\n // -------------------------------------------------------------------------\n // Touch handlers\n // -------------------------------------------------------------------------\n\n private onTouchStart(e: TouchEvent): void {\n e.preventDefault();\n\n if (e.touches.length === 2) {\n // Pinch-zoom start\n const [t0, t1] = [e.touches[0], e.touches[1]];\n this.lastTouchDist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);\n this.lastTouchCenter = {\n x: (t0.clientX + t1.clientX) / 2,\n y: (t0.clientY + t1.clientY) / 2,\n };\n } else if (e.touches.length === 1) {\n const touch = e.touches[0];\n const rect = this.canvas.getBoundingClientRect();\n const x = touch.clientX - rect.left;\n const y = touch.clientY - rect.top;\n\n const hitId = this.hitTest(x, y);\n if (hitId) {\n this.mousedownNodeId = hitId;\n } else {\n this.panState = { startX: x, startY: y };\n this.mousedownNodeId = null;\n }\n }\n }\n\n private onTouchMove(e: TouchEvent): void {\n e.preventDefault();\n\n if (e.touches.length === 2 && this.lastTouchDist !== null) {\n const [t0, t1] = [e.touches[0], e.touches[1]];\n const newDist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);\n const rect = this.canvas.getBoundingClientRect();\n const centerX = (t0.clientX + t1.clientX) / 2 - rect.left;\n const centerY = (t0.clientY + t1.clientY) / 2 - rect.top;\n\n const scale = newDist / this.lastTouchDist;\n const newK = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, this.transform.k * scale));\n this.transform = this.transform.zoomAt(newK, centerX, centerY);\n\n // Pan from center movement\n if (this.lastTouchCenter) {\n const dx = centerX - (this.lastTouchCenter.x - rect.left);\n const dy = centerY - (this.lastTouchCenter.y - rect.top);\n this.transform = this.transform.pan(dx, dy);\n }\n\n this.lastTouchDist = newDist;\n this.lastTouchCenter = {\n x: (t0.clientX + t1.clientX) / 2,\n y: (t0.clientY + t1.clientY) / 2,\n };\n this.callbacks.onTransformChange(this.transform);\n } else if (e.touches.length === 1 && this.panState) {\n const touch = e.touches[0];\n const rect = this.canvas.getBoundingClientRect();\n const x = touch.clientX - rect.left;\n const y = touch.clientY - rect.top;\n\n const dx = x - this.panState.startX;\n const dy = y - this.panState.startY;\n this.transform = this.transform.pan(dx, dy);\n this.panState = { startX: x, startY: y };\n this.callbacks.onTransformChange(this.transform);\n }\n }\n\n private onTouchEnd(e: TouchEvent): void {\n if (e.touches.length === 0) {\n // Tap-select\n if (this.mousedownNodeId && !this.panState) {\n this.handleNodeClick(this.mousedownNodeId, false);\n } else if (!this.mousedownNodeId && this.panState) {\n // Background tap: clear selection\n this.selectedIds.clear();\n this.callbacks.onSelectionChange([]);\n }\n\n this.panState = null;\n this.mousedownNodeId = null;\n this.lastTouchDist = null;\n this.lastTouchCenter = null;\n }\n }\n}\n","/**\n * Keyboard navigation for the graph canvas.\n *\n * Provides accessible keyboard control: Tab to focus, arrow keys to\n * navigate between adjacent nodes (following edges), Enter to select,\n * Escape to clear, +/- to zoom, Home to fit all, / to focus search.\n */\n\nimport type { PositionedNode } from './types';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface KeyboardNavOptions {\n canvas: HTMLCanvasElement;\n getNodes(): PositionedNode[];\n getSelectedIds(): string[];\n getAdjacency(): Map<string, Set<string>>;\n onSelect(nodeId: string): void;\n onDeselect(): void;\n onZoom(direction: 'in' | 'out'): void;\n onFitAll(): void;\n onFocusSearch?(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Attach keyboard navigation to a graph canvas.\n * Returns a cleanup function that removes all listeners.\n */\nexport function attachGraphKeyboardNav(options: KeyboardNavOptions): () => void {\n const {\n canvas,\n getNodes,\n getSelectedIds,\n getAdjacency,\n onSelect,\n onDeselect,\n onZoom,\n onFitAll,\n onFocusSearch,\n } = options;\n\n let focusedNodeId: string | null = null;\n\n // Make canvas focusable\n if (!canvas.hasAttribute('tabindex')) {\n canvas.setAttribute('tabindex', '0');\n }\n\n function findNodeById(id: string): PositionedNode | undefined {\n return getNodes().find((n) => n.id === id);\n }\n\n /**\n * Given a set of neighbor node ids, pick the one that best matches\n * the arrow key direction relative to the current focused node.\n */\n function pickDirectionalNeighbor(\n fromNode: PositionedNode,\n neighborIds: Set<string>,\n direction: 'up' | 'down' | 'left' | 'right',\n ): string | null {\n const nodes = getNodes();\n const candidates = nodes.filter((n) => neighborIds.has(n.id));\n if (candidates.length === 0) return null;\n\n // Score each candidate by how well it matches the desired direction\n let best: PositionedNode | null = null;\n let bestScore = -Infinity;\n\n for (const c of candidates) {\n const dx = c.x - fromNode.x;\n const dy = c.y - fromNode.y;\n let score: number;\n\n switch (direction) {\n case 'right':\n score = dx - Math.abs(dy) * 0.5;\n break;\n case 'left':\n score = -dx - Math.abs(dy) * 0.5;\n break;\n case 'down':\n score = dy - Math.abs(dx) * 0.5;\n break;\n case 'up':\n score = -dy - Math.abs(dx) * 0.5;\n break;\n }\n\n if (score > bestScore) {\n bestScore = score;\n best = c;\n }\n }\n\n return best?.id ?? null;\n }\n\n function onKeyDown(e: KeyboardEvent): void {\n switch (e.key) {\n case 'Tab': {\n // Focus first/selected node\n const selected = getSelectedIds();\n const nodes = getNodes();\n if (nodes.length === 0) return;\n\n if (selected.length > 0) {\n focusedNodeId = selected[0];\n } else if (!focusedNodeId || !findNodeById(focusedNodeId)) {\n focusedNodeId = nodes[0].id;\n }\n\n e.preventDefault();\n break;\n }\n\n case 'ArrowUp':\n case 'ArrowDown':\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (!focusedNodeId) return;\n e.preventDefault();\n\n const focusedNode = findNodeById(focusedNodeId);\n if (!focusedNode) return;\n\n const adjacency = getAdjacency();\n const neighbors = adjacency.get(focusedNodeId);\n if (!neighbors || neighbors.size === 0) return;\n\n const dirMap: Record<string, 'up' | 'down' | 'left' | 'right'> = {\n ArrowUp: 'up',\n ArrowDown: 'down',\n ArrowLeft: 'left',\n ArrowRight: 'right',\n };\n\n const nextId = pickDirectionalNeighbor(focusedNode, neighbors, dirMap[e.key]);\n if (nextId) {\n focusedNodeId = nextId;\n onSelect(nextId);\n }\n break;\n }\n\n case 'Enter': {\n if (focusedNodeId) {\n e.preventDefault();\n const selected = getSelectedIds();\n if (selected.includes(focusedNodeId)) {\n onDeselect();\n } else {\n onSelect(focusedNodeId);\n }\n }\n break;\n }\n\n case 'Escape': {\n e.preventDefault();\n focusedNodeId = null;\n onDeselect();\n break;\n }\n\n case '+':\n case '=': {\n e.preventDefault();\n onZoom('in');\n break;\n }\n\n case '-':\n case '_': {\n e.preventDefault();\n onZoom('out');\n break;\n }\n\n case 'Home': {\n e.preventDefault();\n onFitAll();\n break;\n }\n\n case '/': {\n if (onFocusSearch) {\n e.preventDefault();\n onFocusSearch();\n }\n break;\n }\n }\n }\n\n canvas.addEventListener('keydown', onKeyDown);\n\n // Return cleanup function\n return () => {\n canvas.removeEventListener('keydown', onKeyDown);\n };\n}\n","/**\n * Interactive built-in graph legend.\n *\n * Replaces the old innerHTML legend build in graph-mount with a keyboard- and\n * pointer-accessible control. Node-category rows are buttons (`aria-pressed`)\n * that toggle emphasis; edge-category rows are non-interactive line swatches.\n *\n * The legend is a thin view: it holds no highlight state of its own. Toggling a\n * row calls back into the mount, which owns the single highlight slot and\n * re-renders the legend from the resulting active-category set. Hovering a row\n * previews that category via the same highlight code path.\n */\n\n/** Escape a string for safe interpolation into innerHTML. */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;');\n}\n\n/** A node-category legend row. */\nexport interface GraphLegendNodeEntry {\n label: string;\n color: string;\n count?: number;\n /** Whether this category is currently emphasized (drives the inactive style). */\n active: boolean;\n}\n\n/** An edge-category legend row (non-interactive). */\nexport interface GraphLegendEdgeEntry {\n label: string;\n color: string;\n count?: number;\n}\n\n/** Data the legend renders. */\nexport interface GraphLegendViewData {\n nodes: GraphLegendNodeEntry[];\n edges: GraphLegendEdgeEntry[];\n}\n\n/** Callbacks the legend fires; the mount owns the resulting state. */\nexport interface GraphLegendCallbacks {\n /** Whether node rows toggle emphasis. When false, rows are static labels. */\n interactive: boolean;\n /** Whether to show per-category counts. */\n counts: boolean;\n /** Toggle a node category's emphasis. */\n onToggle(value: string): void;\n /** Hover a node category (null on leave) for a live preview. */\n onHover(value: string | null): void;\n}\n\nexport interface GraphLegendController {\n /** Re-render from fresh data (e.g. after a toggle or update()). */\n update(data: GraphLegendViewData): void;\n /** Remove listeners and clear the host. */\n destroy(): void;\n}\n\n/**\n * Create an interactive legend inside `host`. Returns a controller with\n * `update`/`destroy`. The host is fully owned by the legend (cleared on each\n * update and on destroy).\n */\nexport function createGraphLegend(\n host: HTMLElement,\n data: GraphLegendViewData,\n callbacks: GraphLegendCallbacks,\n): GraphLegendController {\n const listeners: Array<() => void> = [];\n\n function render(view: GraphLegendViewData): void {\n teardownListeners();\n host.replaceChildren();\n\n if (view.nodes.length === 0 && view.edges.length === 0) {\n host.style.display = 'none';\n return;\n }\n host.style.display = '';\n\n for (const entry of view.nodes) {\n host.appendChild(nodeRow(entry));\n }\n for (const entry of view.edges) {\n host.appendChild(edgeRow(entry));\n }\n }\n\n function nodeRow(entry: GraphLegendNodeEntry): HTMLElement {\n const interactive = callbacks.interactive;\n const el = document.createElement(interactive ? 'button' : 'div');\n el.className = 'oc-graph-legend-item';\n if (!entry.active) el.classList.add('oc-graph-legend-item--inactive');\n\n if (interactive) {\n const btn = el as HTMLButtonElement;\n btn.type = 'button';\n btn.setAttribute('aria-pressed', String(entry.active));\n const onClick = () => callbacks.onToggle(entry.label);\n const onEnter = () => callbacks.onHover(entry.label);\n const onLeave = () => callbacks.onHover(null);\n btn.addEventListener('click', onClick);\n btn.addEventListener('mouseenter', onEnter);\n btn.addEventListener('mouseleave', onLeave);\n listeners.push(() => {\n btn.removeEventListener('click', onClick);\n btn.removeEventListener('mouseenter', onEnter);\n btn.removeEventListener('mouseleave', onLeave);\n });\n }\n\n el.innerHTML =\n `<span class=\"oc-graph-legend-swatch\" style=\"background:${escapeHtml(entry.color)}\"></span>` +\n `<span class=\"oc-graph-legend-label\">${escapeHtml(entry.label)}</span>` +\n (callbacks.counts && entry.count != null\n ? `<span class=\"oc-graph-legend-count\">${entry.count.toLocaleString()}</span>`\n : '');\n return el;\n }\n\n function edgeRow(entry: GraphLegendEdgeEntry): HTMLElement {\n const el = document.createElement('div');\n el.className = 'oc-graph-legend-item oc-graph-legend-item--edge';\n el.innerHTML =\n `<span class=\"oc-graph-legend-swatch oc-graph-legend-swatch--line\" style=\"background:${escapeHtml(entry.color)}\"></span>` +\n `<span class=\"oc-graph-legend-label\">${escapeHtml(entry.label)}</span>` +\n (callbacks.counts && entry.count != null\n ? `<span class=\"oc-graph-legend-count\">${entry.count.toLocaleString()}</span>`\n : '');\n return el;\n }\n\n function teardownListeners(): void {\n for (const off of listeners) off();\n listeners.length = 0;\n }\n\n render(data);\n\n return {\n update: render,\n destroy(): void {\n teardownListeners();\n host.replaceChildren();\n },\n };\n}\n","/**\n * Graph search manager.\n *\n * Provides case-insensitive substring matching against node labels/ids.\n * Returns a Set of matching node ids that the renderer uses to dim\n * non-matching nodes and edges.\n */\n\nexport class GraphSearchManager {\n private matchedIds: Set<string> | null = null;\n /** The last active (non-empty) query, so a data update can re-run it. */\n private query: string | null = null;\n\n /**\n * Search for nodes matching the query string.\n * Returns a Set of matching node ids, or an empty set if nothing matches.\n */\n search(query: string, nodes: Array<{ id: string; label?: string }>): Set<string> {\n const q = query.toLowerCase().trim();\n\n if (q === '') {\n this.matchedIds = null;\n this.query = null;\n return new Set();\n }\n\n this.query = query;\n\n const matches = new Set<string>();\n for (const node of nodes) {\n const label = (node.label ?? '').toLowerCase();\n const id = node.id.toLowerCase();\n if (label.includes(q) || id.includes(q)) {\n matches.add(node.id);\n }\n }\n\n this.matchedIds = matches;\n return matches;\n }\n\n /**\n * Clear the current search.\n * Returns null to indicate no active search.\n */\n clearSearch(): Set<string> | null {\n this.matchedIds = null;\n this.query = null;\n return null;\n }\n\n /** Get the current set of matched ids, or null if no search is active. */\n getMatches(): Set<string> | null {\n return this.matchedIds;\n }\n\n /** The last active query string, or null when no search is active. */\n getQuery(): string | null {\n return this.query;\n }\n}\n","/**\n * Deterministic seeded initial layout for the force simulation.\n *\n * Without seeding, d3-force places nodes on a phyllotaxis spiral, so the settled\n * layout depends on node ORDER and isn't reproducible across data reshapes. With\n * a seed, each node's start position is a pure function of `(id, seed, community)`,\n * so the same spec + seed produces the same settled layout — and adding a node\n * leaves the others' start positions untouched (their hash inputs don't change).\n *\n * Determinism is scoped PER EXECUTION PATH (worker OR sync): identical within a\n * path, not guaranteed identical across the two.\n */\n\nimport type { SimNode } from './worker-protocol';\n\n/** FNV-1a 32-bit hash of a string. Fast, dependency-free, well-distributed. */\nexport function hash32(str: string): number {\n let h = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n // 32-bit FNV prime multiply via shifts (stays within Math.imul's 32-bit range).\n h = Math.imul(h, 0x01000193);\n }\n // Coerce to an unsigned 32-bit integer.\n return h >>> 0;\n}\n\n/** Map a 32-bit hash to a float in [0, 1). */\nfunction unit(h: number): number {\n return (h >>> 0) / 0x100000000;\n}\n\n/**\n * Seed each node's initial `x`/`y` as a pure function of `(id, seed, community)`.\n * Mutates the nodes in place (matching how d3 mutates its node objects).\n *\n * Placement is a uniform disc: `r = R0·sqrt(u1)`, `θ = u2·2π`. When a node has a\n * `community`, its disc center is biased by `0.6·R0` toward a per-community angle,\n * so clusters start near their eventual region and settle faster/cleaner.\n *\n * DEVIATION from the plan's `R0 ≈ 10·√n`: R0 is a FIXED constant, not derived\n * from the live node count. An n-dependent R0 would rescale every node's start\n * position whenever a node is added, which directly violates the reproducibility\n * guarantee (\"adding a node leaves the others' seed positions unchanged\"). Since\n * a deterministic seed is pointless if one insertion reshuffles the layout, the\n * per-node determinism wins; d3-force's charge repulsion still spreads the cloud\n * to fill the viewport regardless of the initial disc radius. R0 is sized for a\n * ~900-node graph (10·√900 = 300), a reasonable middle for the initial cloud.\n */\nconst R0 = 300;\n\nexport function seedNodePositions(nodes: SimNode[], seed: number): void {\n const n = nodes.length;\n if (n === 0) return;\n\n for (const node of nodes) {\n // Two independent hash streams per node from distinct salts.\n const u1 = unit(hash32(`${node.id}|${seed}|r`));\n const u2 = unit(hash32(`${node.id}|${seed}|t`));\n const r = R0 * Math.sqrt(u1);\n const theta = u2 * Math.PI * 2;\n\n let cx = 0;\n let cy = 0;\n if (node.community) {\n // Per-community region angle: stable across runs, varies with seed.\n const ca = unit(hash32(`${node.community}|${seed}`)) * Math.PI * 2;\n const bias = 0.6 * R0;\n cx = Math.cos(ca) * bias;\n cy = Math.sin(ca) * bias;\n }\n\n node.x = cx + r * Math.cos(theta);\n node.y = cy + r * Math.sin(theta);\n }\n}\n","/**\n * SimulationManager: spawns a Web Worker for the force simulation,\n * or falls back to synchronous d3-force on the main thread.\n *\n * The worker is always preferred when available. Synchronous fallback\n * is only used when Web Workers are unavailable (SSR, test environments).\n * The sync path batches ticks via requestAnimationFrame to avoid\n * blocking the main thread.\n */\n\nimport {\n forceCenter,\n forceCollide,\n forceLink,\n forceManyBody,\n forceSimulation,\n forceX,\n forceY,\n type Simulation,\n type SimulationNodeDatum,\n} from 'd3-force';\n\nimport type { SimEdge, SimNode, WorkerOutMessage, WorkerSimulationConfig } from './worker-protocol';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYNC_TICKS_PER_BATCH = 15;\n/** Absolute ceiling on the derived sync tick cap (guards tiny alphaDecay). */\nconst SYNC_MAX_TICKS_CEIL = 800;\n/** d3's default alphaMin — the alpha at which forceSimulation stops. */\nconst DEFAULT_ALPHA_MIN = 0.001;\n/** Default warmup budget (ms) mirroring the engine's DEFAULT_WARMUP_BUDGET_MS. */\nconst DEFAULT_WARMUP_BUDGET_MS = 250;\n\n/**\n * Ticks d3 needs to reach `alphaMin` from alpha=1 for a given `alphaDecay`,\n * ceilinged. d3 stops when `alpha < alphaMin`, where `alpha *= (1 - alphaDecay)`\n * each tick, so `alpha(n) = (1 - alphaDecay)^n`. Solving `(1 - d)^n = alphaMin`\n * gives `n = log(alphaMin) / log(1 - d)`. This aligns the sync cap with the\n * worker path (which runs to `alpha < alphaMin`) instead of a fixed 300, so a\n * `settle: 'thorough'` graph (alphaDecay 0.01, ~690 ticks) settles fully on both.\n *\n * Determinism is scoped PER EXECUTION PATH: same spec + seed ⇒ identical settled\n * layout within a given path. Worker-vs-sync parity is not guaranteed as-is.\n */\nexport function ticksToAlphaMin(alphaDecay: number, alphaMin = DEFAULT_ALPHA_MIN): number {\n if (!(alphaDecay > 0) || alphaDecay >= 1) return SYNC_MAX_TICKS_CEIL;\n const n = Math.ceil(Math.log(alphaMin) / Math.log(1 - alphaDecay));\n return Math.min(SYNC_MAX_TICKS_CEIL, Math.max(1, n));\n}\n\n// ---------------------------------------------------------------------------\n// Internal node shape for sync simulation\n// ---------------------------------------------------------------------------\n\ninterface SyncNode extends SimulationNodeDatum {\n id: string;\n radius: number;\n community?: string;\n fx?: number | null;\n fy?: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Cluster force (duplicated in simulation-worker.ts for the Web Worker path.\n// Worker can't import from workspace packages, so both copies must stay in sync.)\n// ---------------------------------------------------------------------------\n\nfunction forceCluster(nodes: SyncNode[], strength: number) {\n return (alpha: number) => {\n const cx = new Map<string, number>();\n const cy = new Map<string, number>();\n const count = new Map<string, number>();\n\n for (const node of nodes) {\n if (!node.community) continue;\n const c = node.community;\n cx.set(c, (cx.get(c) ?? 0) + (node.x ?? 0));\n cy.set(c, (cy.get(c) ?? 0) + (node.y ?? 0));\n count.set(c, (count.get(c) ?? 0) + 1);\n }\n\n for (const [c, n] of count) {\n cx.set(c, cx.get(c)! / n);\n cy.set(c, cy.get(c)! / n);\n }\n\n const k = strength * alpha;\n for (const node of nodes) {\n if (!node.community) continue;\n const targetX = cx.get(node.community)!;\n const targetY = cy.get(node.community)!;\n node.vx = (node.vx ?? 0) + (targetX - (node.x ?? 0)) * k;\n node.vy = (node.vy ?? 0) + (targetY - (node.y ?? 0)) * k;\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// Cursor-repulsion force (duplicated in simulation-worker.ts for the Web Worker\n// path. Worker can't import from workspace packages, so both copies stay in sync.)\n// ---------------------------------------------------------------------------\n\n/** Live pointer position + radius/strength, mutated by setPointer(). */\ninterface PointerState {\n x: number;\n y: number;\n active: boolean;\n radius: number;\n strength: number;\n}\n\nfunction forceCursor(nodes: SyncNode[], pointer: PointerState) {\n return (alpha: number) => {\n if (!pointer.active || pointer.radius <= 0) return;\n const r2 = pointer.radius * pointer.radius;\n const k = pointer.strength * alpha;\n for (const node of nodes) {\n const dx = (node.x ?? 0) - pointer.x;\n const dy = (node.y ?? 0) - pointer.y;\n const dist2 = dx * dx + dy * dy;\n if (dist2 >= r2) continue;\n const dist = Math.sqrt(dist2) || 1e-6;\n // Linear falloff: full push at the pointer, zero at the radius edge.\n const falloff = (pointer.radius - dist) / pointer.radius;\n const push = (k * falloff) / dist;\n node.vx = (node.vx ?? 0) + dx * push;\n node.vy = (node.vy ?? 0) + dy * push;\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// SimulationManager\n// ---------------------------------------------------------------------------\n\ntype TickCallback = (positions: Array<{ id: string; x: number; y: number }>, alpha: number) => void;\n\ntype SettledCallback = () => void;\n\nexport class SimulationManager {\n private worker: Worker | null = null;\n private syncSim: Simulation<SyncNode, undefined> | null = null;\n private syncNodes: SyncNode[] = [];\n private syncNodeMap: Map<string, SyncNode> = new Map();\n private tickCb: TickCallback | null = null;\n private settledCb: SettledCallback | null = null;\n private destroyed = false;\n private syncRafId: number | null = null;\n /** Derived per-graph cap on sync ticks, from alphaDecay via ticksToAlphaMin. */\n private syncMaxTicks = SYNC_MAX_TICKS_CEIL;\n /** True until the sync warmup loop has completed (nothing renders before then). */\n private syncWarmupPending = false;\n /** Remaining warmup ticks and the ms budget, consumed by the pre-reveal loop. */\n private syncWarmupTicks = 0;\n private syncWarmupBudgetMs = DEFAULT_WARMUP_BUDGET_MS;\n /** Injectable clock for the warmup ms budget (deterministic in tests). */\n private now: () => number =\n typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();\n\n // Stored for worker->sync fallback\n private initNodes: SimNode[] = [];\n private initEdges: SimEdge[] = [];\n private initConfig: WorkerSimulationConfig | null = null;\n\n // Cursor-repulsion pointer state (sync path). radius=0 keeps the force inert.\n private pointer: PointerState = { x: 0, y: 0, active: false, radius: 0, strength: 0 };\n // Separately tracked alpha-target intents so drag and cursor don't stomp each\n // other. The higher intent wins; releasing one falls back to the other.\n private dragAlphaTarget = 0;\n private cursorAlphaTarget = 0;\n\n private constructor() {}\n\n /**\n * Create a SimulationManager. Uses Web Worker for large graphs,\n * synchronous fallback for small graphs or when Worker unavailable.\n *\n * `opts.now` injects a clock for the warmup ms budget (tests pass a fake one).\n */\n static create(\n nodes: SimNode[],\n edges: SimEdge[],\n config: WorkerSimulationConfig,\n opts?: { now?: () => number },\n ): SimulationManager {\n const mgr = new SimulationManager();\n if (opts?.now) mgr.now = opts.now;\n\n const useWorker = typeof Worker !== 'undefined';\n\n if (useWorker) {\n mgr.initWorker(nodes, edges, config);\n } else {\n mgr.initSync(nodes, edges, config);\n }\n\n return mgr;\n }\n\n /** Register a callback for position updates. */\n onTick(cb: TickCallback): void {\n this.tickCb = cb;\n }\n\n /** Register a callback for when the simulation has settled. */\n onSettled(cb: SettledCallback): void {\n this.settledCb = cb;\n }\n\n /** Reheat the simulation. */\n reheat(alpha?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'reheat', alpha });\n } else if (this.syncSim) {\n this.syncSim.alpha(alpha ?? 0.3).restart();\n this.runSyncTicks();\n }\n }\n\n /**\n * Pin a node to fixed x/y coordinates.\n *\n * When `alphaTarget` is provided (springy drag), the sim is held warm so the\n * pinned node's neighbors follow springily. Omitting it posts a byte-identical\n * legacy message and leaves alpha untouched.\n */\n pinNode(id: string, x: number, y: number, alphaTarget?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n // Only include alphaTarget when springy, so the legacy message stays\n // byte-identical (and a stale worker sees exactly the old shape).\n this.worker.postMessage(\n alphaTarget != null\n ? { type: 'pin', nodeId: id, x, y, alphaTarget }\n : { type: 'pin', nodeId: id, x, y },\n );\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = x;\n node.fy = y;\n }\n if (alphaTarget != null && this.syncSim) {\n this.dragAlphaTarget = alphaTarget;\n this.syncAlphaTarget();\n this.syncSim.restart();\n this.runSyncTicks();\n }\n }\n }\n\n /**\n * Unpin a node and reheat so forces settle it into equilibrium.\n *\n * When `alphaTarget` is provided (springy release), the sim cools back toward\n * that target instead of the legacy gentle reheat. Omitting it preserves the\n * exact legacy reheat behavior (and posts a byte-identical message).\n */\n unpinNode(id: string, alphaTarget?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage(\n alphaTarget != null\n ? { type: 'unpin', nodeId: id, alphaTarget }\n : { type: 'unpin', nodeId: id },\n );\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = null;\n node.fy = null;\n }\n if (alphaTarget != null) {\n // Springy release: cool toward the requested target (typically 0). Keep\n // the tick loop running so the sim actually eases down to the target;\n // the alpha-target path owns re-settling (no legacy reheat).\n this.dragAlphaTarget = alphaTarget;\n this.syncAlphaTarget();\n this.runSyncTicks();\n } else if (this.syncSim && this.syncSim.alpha() < 0.1) {\n // LEGACY path: gentle reheat, byte-identical to pre-springy behavior.\n this.syncSim.alpha(0.1).restart();\n this.runSyncTicks();\n }\n }\n }\n\n /**\n * Feed the cursor-repulsion force a pointer position. `active: false` clears\n * the force. No-op when the graph has no cursor force configured (radius 0).\n */\n setPointer(x: number, y: number, active: boolean): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'pointer', x, y, active });\n } else {\n // No cursor force configured (radius 0) → a true no-op; don't warm the sim\n // or the toy-force alpha would keep an otherwise-settled graph ticking.\n if (this.pointer.radius <= 0) return;\n this.pointer.x = x;\n this.pointer.y = y;\n this.pointer.active = active;\n this.cursorAlphaTarget = active ? 0.03 : 0;\n if (this.syncSim) {\n this.syncAlphaTarget();\n if (active) {\n this.syncSim.restart();\n this.runSyncTicks();\n }\n }\n }\n }\n\n /** Apply the max of the tracked alpha-target intents to the sync sim. */\n private syncAlphaTarget(): void {\n if (!this.syncSim) return;\n this.syncSim.alphaTarget(Math.max(this.dragAlphaTarget, this.cursorAlphaTarget));\n }\n\n /** Drag a node (pins it and reheats slightly). */\n dragNode(id: string, x: number, y: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'drag', nodeId: id, x, y });\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = x;\n node.fy = y;\n }\n if (this.syncSim && this.syncSim.alpha() < 0.1) {\n this.syncSim.alpha(0.1).restart();\n this.runSyncTicks();\n }\n }\n }\n\n /** Tear down the simulation and release resources. */\n destroy(): void {\n this.destroyed = true;\n\n if (this.syncRafId !== null) {\n cancelAnimationFrame(this.syncRafId);\n this.syncRafId = null;\n }\n\n if (this.worker) {\n this.worker.postMessage({ type: 'stop' });\n this.worker.terminate();\n this.worker = null;\n }\n\n if (this.syncSim) {\n this.syncSim.stop();\n this.syncSim = null;\n }\n\n this.tickCb = null;\n this.settledCb = null;\n }\n\n // -------------------------------------------------------------------------\n // Worker path\n // -------------------------------------------------------------------------\n\n private initWorker(nodes: SimNode[], edges: SimEdge[], config: WorkerSimulationConfig): void {\n // Store for fallback if worker fails to load\n this.initNodes = nodes;\n this.initEdges = edges;\n this.initConfig = config;\n\n // Worker URL resolution:\n // - Built dist/ consumers: import.meta.url points at dist/index.js,\n // so ./simulation-worker.js resolves to dist/simulation-worker.js.\n // - Vite dev with source aliases (Ladle): import.meta.url points at\n // src/graph/simulation.ts, so ./simulation-worker.js doesn't exist.\n // The .js worker fails to load, and the onerror handler retries\n // with .ts which Vite transforms on the fly.\n // - Vite production build: detects `new Worker(new URL(...))` and\n // bundles the worker as a hashed .js asset.\n const initMsg = { type: 'init' as const, nodes, edges, config };\n const wireWorker = (worker: Worker) => {\n this.worker = worker;\n\n worker.onmessage = (event: MessageEvent<WorkerOutMessage>) => {\n if (this.destroyed) return;\n const msg = event.data;\n\n switch (msg.type) {\n case 'positions':\n this.tickCb?.(msg.nodes, msg.alpha);\n break;\n case 'settled':\n this.settledCb?.();\n break;\n case 'error':\n console.error('[SimulationManager] Worker error:', msg.message);\n break;\n }\n };\n\n worker.postMessage(initMsg);\n };\n\n try {\n const w = new Worker(new URL('./simulation-worker.js', import.meta.url), {\n type: 'module',\n });\n\n w.onerror = () => {\n // .js failed (likely Vite dev with source aliases). Try .ts.\n // The URL is constructed dynamically to prevent bundlers (Rollup)\n // from statically analyzing it and trying to resolve the .ts file\n // as an asset entry point in production builds.\n if (this.destroyed) return;\n w.terminate();\n this.worker = null;\n\n try {\n const tsUrl = new URL(import.meta.url.replace(/\\/[^/]+$/, '/simulation-worker.ts'));\n const w2 = new Worker(tsUrl, { type: 'module' });\n\n w2.onerror = () => {\n // Both .js and .ts failed - fall back to sync.\n if (this.destroyed) return;\n console.warn('[SimulationManager] Worker failed to load, falling back to sync');\n w2.terminate();\n this.worker = null;\n this.initSync(this.initNodes, this.initEdges, this.initConfig!);\n };\n\n wireWorker(w2);\n } catch {\n console.warn('[SimulationManager] Worker creation failed, using sync fallback');\n this.initSync(this.initNodes, this.initEdges, this.initConfig!);\n }\n };\n\n wireWorker(w);\n } catch {\n // Worker construction failed (e.g. SSR or restrictive CSP)\n console.warn('[SimulationManager] Worker creation failed, using sync fallback');\n this.initSync(nodes, edges, config);\n }\n }\n\n // -------------------------------------------------------------------------\n // Synchronous fallback\n // -------------------------------------------------------------------------\n\n private initSync(nodes: SimNode[], edges: SimEdge[], config: WorkerSimulationConfig): void {\n this.syncNodes = nodes.map((n) => ({\n id: n.id,\n x: n.x,\n y: n.y,\n radius: n.radius,\n community: n.community,\n }));\n\n this.syncNodeMap = new Map(this.syncNodes.map((n) => [n.id, n]));\n\n const linkForce = forceLink(edges.map((e) => ({ ...e })))\n .id((d) => (d as SyncNode).id)\n .distance(config.linkDistance);\n if (config.linkStrength != null) {\n linkForce.strength(config.linkStrength);\n }\n\n const padding = config.collisionPadding ?? 2;\n\n this.syncSim = forceSimulation<SyncNode>(this.syncNodes)\n .force('link', linkForce)\n .force('charge', forceManyBody().strength(config.chargeStrength))\n .force(\n 'collide',\n forceCollide<SyncNode>().radius((d) => d.radius + padding),\n )\n // Weak gravity keeps disconnected nodes from drifting far from center\n .force('gravityX', forceX<SyncNode>(0).strength(0.05))\n .force('gravityY', forceY<SyncNode>(0).strength(0.05))\n .alphaDecay(config.alphaDecay)\n .velocityDecay(config.velocityDecay)\n .stop(); // Don't auto-run; we tick manually\n\n // Center force (default true)\n if (config.centerForce !== false) {\n this.syncSim.force('center', forceCenter(0, 0));\n }\n\n // Add clustering force if configured\n if (config.clustering) {\n const clusterFn = forceCluster(this.syncNodes, config.clustering.strength);\n // d3 calls force functions with (alpha) on each tick\n this.syncSim.force('cluster', clusterFn as unknown as ReturnType<typeof forceCenter>);\n }\n\n // Cursor-repulsion force: always registered but inert until setPointer()\n // activates it (pointer.active + radius > 0). Reset intent so a re-init\n // (worker->sync fallback) doesn't inherit stale state.\n this.pointer.active = false;\n this.pointer.radius = config.cursorRepulsion?.radius ?? 0;\n this.pointer.strength = config.cursorRepulsion?.strength ?? 0;\n this.dragAlphaTarget = 0;\n this.cursorAlphaTarget = 0;\n const cursorFn = forceCursor(this.syncNodes, this.pointer);\n this.syncSim.force('cursor', cursorFn as unknown as ReturnType<typeof forceCenter>);\n\n // Initial alpha (entrance / reheat impulse) applied before warmup.\n if (config.initialAlpha != null) {\n this.syncSim.alpha(config.initialAlpha);\n }\n\n // Derive the tick cap from alphaDecay so the sync path settles as far as the\n // worker (which runs to alpha < alphaMin) rather than a fixed 300.\n this.syncMaxTicks = ticksToAlphaMin(config.alphaDecay);\n\n // Warmup: settle a bounded number of ticks BEFORE the first reveal so the\n // entrance doesn't start from an explosive layout. The sync path is on the\n // main thread, so warmup is chunked across rAF frames (a single 100-tick\n // batch at 10k nodes is a 1s+ freeze). Nothing renders until warmup finishes.\n this.syncWarmupTicks = config.warmupTicks ?? 0;\n this.syncWarmupBudgetMs = config.warmupBudgetMs ?? DEFAULT_WARMUP_BUDGET_MS;\n this.syncWarmupPending = this.syncWarmupTicks > 0;\n\n // Defer initial delivery: callbacks aren't wired yet at create() time\n this.runSyncTicks(true);\n }\n\n /**\n * Run simulation ticks in batches, yielding to the main thread between\n * batches via requestAnimationFrame. This prevents a multi-second freeze\n * when the sync fallback handles large graphs (1k+ nodes).\n *\n * Each batch runs SYNC_TICKS_PER_BATCH ticks, emits positions for\n * progressive rendering, then schedules the next batch.\n *\n * @param deferred - When true, start via microtask (initial run where\n * callbacks aren't wired yet). Otherwise start immediately.\n */\n private runSyncTicks(deferred = false): void {\n if (!this.syncSim || this.destroyed) return;\n\n // Cancel any in-flight batched run (e.g. from a previous reheat)\n if (this.syncRafId !== null) {\n cancelAnimationFrame(this.syncRafId);\n this.syncRafId = null;\n }\n\n const sim = this.syncSim;\n const maxTicks = this.syncMaxTicks;\n let tickCount = 0;\n\n // Pre-reveal warmup: chunk the headless settle across rAF frames, bounded by\n // BOTH the remaining tick count AND the ms budget. Nothing is delivered to\n // the tick callback until this completes, so the entrance never starts from\n // an explosive layout and the main thread never freezes. Duplicated in the\n // worker (simulation-worker.ts) as a synchronous loop (it's off-thread).\n const runWarmup = () => {\n if (this.destroyed || !this.syncSim) return;\n this.syncRafId = null;\n\n const start = this.now();\n while (this.syncWarmupTicks > 0) {\n for (let i = 0; i < SYNC_TICKS_PER_BATCH && this.syncWarmupTicks > 0; i++) {\n sim.tick();\n this.syncWarmupTicks--;\n if (sim.alpha() < DEFAULT_ALPHA_MIN) {\n this.syncWarmupTicks = 0;\n break;\n }\n }\n // Budget check between chunks: bail (accept a truncated warmup) at scale.\n if (this.syncWarmupTicks > 0 && this.now() - start >= this.syncWarmupBudgetMs) {\n this.syncWarmupTicks = 0;\n break;\n }\n }\n\n this.syncWarmupPending = false;\n // Warmup done → proceed to the normal reveal/settle loop.\n runBatch();\n };\n\n const runBatch = () => {\n if (this.destroyed || !this.syncSim) return;\n this.syncRafId = null;\n\n for (let i = 0; i < SYNC_TICKS_PER_BATCH && tickCount < maxTicks; i++, tickCount++) {\n sim.tick();\n if (sim.alpha() < DEFAULT_ALPHA_MIN) {\n tickCount = maxTicks;\n break;\n }\n }\n\n const positions = this.syncNodes.map((n) => ({\n id: n.id,\n x: n.x ?? 0,\n y: n.y ?? 0,\n }));\n const alpha = sim.alpha();\n const settled = alpha < DEFAULT_ALPHA_MIN || tickCount >= maxTicks;\n\n this.tickCb?.(positions, alpha);\n\n if (settled) {\n this.settledCb?.();\n } else {\n this.syncRafId = requestAnimationFrame(runBatch);\n }\n };\n\n const start = this.syncWarmupPending ? runWarmup : runBatch;\n\n if (deferred) {\n queueMicrotask(start);\n } else {\n start();\n }\n }\n}\n","/**\n * Re-export shim: the spatial index moved to `../spatial-index` and is now\n * generic over any `{ x, y, radius }` entry so the scatter canvas layer can\n * reuse it. Graph call sites get the node-typed instantiation.\n */\nimport { SpatialIndex as GenericSpatialIndex } from '../spatial-index';\nimport type { PositionedNode } from './types';\n\nexport type { SpatialEntry } from '../spatial-index';\n\n/** Quadtree index over positioned graph nodes. */\nexport class SpatialIndex extends GenericSpatialIndex<PositionedNode> {}\n","/**\n * Resolved simulationConfig equality for update-diff's visual-only detection.\n *\n * The old React heuristic only compared `clustering.field`, so a physics change\n * (charge strength, settle/energy, raw force numbers) with the same ids silently\n * took the position-preserving path and never reheated. That's a correctness bug\n * for the flagship API. Here we compare the FULL resolved config via a stable\n * stringify so any physics change forces a structural update.\n */\n\n/** The subset of engine SimulationConfig fields that affect the simulation. */\nexport interface SimulationConfigLike {\n chargeStrength: number;\n linkDistance: number;\n clustering: { field: string; strength: number } | null;\n alphaDecay: number;\n velocityDecay: number;\n collisionRadius: number;\n collisionPadding?: number;\n linkStrength?: number;\n centerForce?: boolean;\n seed?: number;\n warmupTicks?: number;\n warmupBudgetMs?: number;\n initialAlpha?: number;\n}\n\n/**\n * A stable, key-ordered stringify of the config fields that influence the\n * settled layout. `initialAlpha` is INTENTIONALLY excluded: it's the update\n * reheat impulse the mount sets per-update, not a spec-level physics knob, so it\n * must not force a structural update on its own.\n */\nfunction stableKey(c: SimulationConfigLike): string {\n return JSON.stringify([\n c.chargeStrength,\n c.linkDistance,\n c.clustering ? [c.clustering.field, c.clustering.strength] : null,\n c.alphaDecay,\n c.velocityDecay,\n c.collisionRadius,\n c.collisionPadding ?? null,\n c.linkStrength ?? null,\n c.centerForce ?? null,\n c.seed ?? null,\n c.warmupTicks ?? null,\n c.warmupBudgetMs ?? null,\n ]);\n}\n\n/** Deep-equal two resolved simulation configs (excluding `initialAlpha`). */\nexport function simulationConfigEqual(a: SimulationConfigLike, b: SimulationConfigLike): boolean {\n return stableKey(a) === stableKey(b);\n}\n","/**\n * Data-update diff for the graph mount's unified `update()`.\n *\n * Given the previously positioned nodes/edges and a freshly compiled graph,\n * classify the change into:\n * - `visualOnly`: same node AND edge id sets AND equal simulationConfig. The\n * mount can re-run the position-preserving visual refresh with no sim restart.\n * NOTE: unlike the old React JSON-diff heuristic (which only compared\n * `clustering.field`), visual-only here requires the FULL resolved\n * simulationConfig to match — a physics change (charge/settle/energy/etc.)\n * must reheat, not silently skip.\n * - Otherwise a structural update: which nodes enter, where they spawn, which\n * survive (and keep their prior x/y), and which nodes/edges exit (ghosts).\n *\n * Spawn placement: an entering node spawns at its first surviving neighbor's\n * NEW-adjacency position, nudged by a deterministic ±8px hash jitter (so a node\n * doesn't land exactly on its neighbor). A node with no surviving neighbor falls\n * back to a seeded disc position (same hash family as seed.ts).\n */\n\nimport { hash32 } from './seed';\nimport type { PositionedEdge, PositionedNode } from './types';\nimport type { SimulationConfigLike } from './update-diff-config';\nimport { simulationConfigEqual } from './update-diff-config';\n\n/** Minimal position record kept for survivors and computed for spawns. */\nexport interface XY {\n x: number;\n y: number;\n}\n\n/** A freshly compiled graph, reduced to what the diff needs. */\nexport interface NextGraph {\n nodes: Array<{ id: string; community?: string }>;\n edges: Array<{ source: string; target: string }>;\n simulationConfig: SimulationConfigLike;\n}\n\nexport interface GraphUpdateDiff {\n /** True when node+edge id sets are identical AND simulationConfig is equal. */\n visualOnly: boolean;\n /** Ids of nodes present in `next` but not in prev. */\n enteringIds: string[];\n /** Prior x/y for nodes present in BOTH prev and next, keyed by id. */\n survivingPositions: Map<string, XY>;\n /** Computed spawn x/y for entering nodes, keyed by id. */\n spawnPositions: Map<string, XY>;\n /** Prev-positioned nodes removed in `next` (ghosts). */\n exitingNodes: PositionedNode[];\n /** Prev-positioned edges removed in `next` (ghosts). */\n exitingEdges: PositionedEdge[];\n /**\n * Count of edges present in `next` but not in prev — including edges added\n * between two surviving nodes, which \"touches an entering node\" would miss.\n */\n enteringEdgeCount: number;\n}\n\n/** ±8px deterministic jitter from an id+seed hash (two independent streams). */\nconst JITTER = 8;\n\nfunction jitter(id: string, seed: number): XY {\n // Map two hash streams to [-1, 1) then scale, so a spawn doesn't land exactly\n // on its neighbor. Deterministic in (id, seed).\n const jx = (hash32(`${id}|${seed}|jx`) / 0x100000000) * 2 - 1;\n const jy = (hash32(`${id}|${seed}|jy`) / 0x100000000) * 2 - 1;\n return { x: jx * JITTER, y: jy * JITTER };\n}\n\n/**\n * Seeded disc fallback for a node with no surviving neighbor. Mirrors seed.ts's\n * disc placement (r = R0·√u1, θ = u2·2π) so orphan spawns land in the same cloud.\n */\nconst FALLBACK_R0 = 300;\n\nfunction seededDisc(id: string, seed: number): XY {\n const u1 = hash32(`${id}|${seed}|r`) / 0x100000000;\n const u2 = hash32(`${id}|${seed}|t`) / 0x100000000;\n const r = FALLBACK_R0 * Math.sqrt(u1);\n const theta = u2 * Math.PI * 2;\n return { x: r * Math.cos(theta), y: r * Math.sin(theta) };\n}\n\n/**\n * Diff the previous positioned graph against a freshly compiled `next`.\n *\n * @param prevNodes - Previously positioned nodes (carry x/y).\n * @param prevEdges - Previously positioned edges.\n * @param next - The freshly compiled graph (nodes/edges/simulationConfig).\n * @param prevConfig - The prior simulationConfig, for visual-only detection.\n * @param seed - Layout seed for deterministic jitter/fallback placement.\n */\nexport function diffGraphUpdate(\n prevNodes: PositionedNode[],\n prevEdges: PositionedEdge[],\n next: NextGraph,\n prevConfig: SimulationConfigLike,\n seed: number,\n): GraphUpdateDiff {\n const prevIds = new Set(prevNodes.map((n) => n.id));\n const nextIds = new Set(next.nodes.map((n) => n.id));\n\n const survivingPositions = new Map<string, XY>();\n const enteringIds: string[] = [];\n\n for (const n of prevNodes) {\n if (nextIds.has(n.id)) survivingPositions.set(n.id, { x: n.x, y: n.y });\n }\n for (const n of next.nodes) {\n if (!prevIds.has(n.id)) enteringIds.push(n.id);\n }\n\n // Exiting marks: prev nodes/edges gone from next. An edge exits when its\n // (source,target) key is absent from the next edge set — this catches BOTH\n // edges whose endpoint was removed AND edges removed between two survivors.\n const exitingNodes = prevNodes.filter((n) => !nextIds.has(n.id));\n const nextEdgeKeys = new Set(next.edges.map((e) => `${e.source} ${e.target}`));\n const exitingEdges = prevEdges.filter((e) => !nextEdgeKeys.has(`${e.source} ${e.target}`));\n const prevEdgeCounts = new Map<string, number>();\n for (const e of prevEdges) {\n const k = `${e.source} ${e.target}`;\n prevEdgeCounts.set(k, (prevEdgeCounts.get(k) ?? 0) + 1);\n }\n let enteringEdgeCount = 0;\n const remainingPrev = new Map(prevEdgeCounts);\n for (const e of next.edges) {\n const k = `${e.source} ${e.target}`;\n const c = remainingPrev.get(k);\n if (c && c > 0) {\n remainingPrev.set(k, c - 1);\n } else {\n enteringEdgeCount++;\n }\n }\n\n // Visual-only: identical node AND edge id sets AND equal simulationConfig.\n const sameNodes = prevIds.size === nextIds.size && enteringIds.length === 0;\n const sameEdges = edgeSetsEqual(prevEdges, next.edges);\n const visualOnly =\n sameNodes && sameEdges && simulationConfigEqual(prevConfig, next.simulationConfig);\n\n // Spawn placement for enterers: first surviving neighbor in the NEW adjacency,\n // plus ±8px jitter; fallback to a seeded disc when no surviving neighbor.\n const spawnPositions = new Map<string, XY>();\n if (enteringIds.length > 0) {\n const enteringSet = new Set(enteringIds);\n const nextAdjacency = buildAdjacency(next.edges);\n for (const id of enteringIds) {\n const neighborPos = firstSurvivingNeighborPos(\n id,\n nextAdjacency,\n survivingPositions,\n enteringSet,\n );\n const j = jitter(id, seed);\n if (neighborPos) {\n spawnPositions.set(id, { x: neighborPos.x + j.x, y: neighborPos.y + j.y });\n } else {\n const disc = seededDisc(id, seed);\n spawnPositions.set(id, { x: disc.x + j.x, y: disc.y + j.y });\n }\n }\n }\n\n return {\n visualOnly,\n enteringIds,\n survivingPositions,\n spawnPositions,\n exitingNodes,\n exitingEdges,\n enteringEdgeCount,\n };\n}\n\n/** Build an undirected adjacency map from an edge list. */\nfunction buildAdjacency(edges: Array<{ source: string; target: string }>): Map<string, string[]> {\n const map = new Map<string, string[]>();\n const push = (a: string, b: string) => {\n const list = map.get(a);\n if (list) list.push(b);\n else map.set(a, [b]);\n };\n for (const e of edges) {\n push(e.source, e.target);\n push(e.target, e.source);\n }\n return map;\n}\n\n/**\n * First neighbor of `id` (in the new adjacency) that already has a known\n * position — i.e. a survivor, not another enterer. Deterministic: neighbors are\n * scanned in edge-declaration order.\n */\nfunction firstSurvivingNeighborPos(\n id: string,\n adjacency: Map<string, string[]>,\n survivingPositions: Map<string, XY>,\n enteringSet: Set<string>,\n): XY | null {\n const neighbors = adjacency.get(id);\n if (!neighbors) return null;\n for (const nid of neighbors) {\n if (enteringSet.has(nid)) continue;\n const pos = survivingPositions.get(nid);\n if (pos) return pos;\n }\n return null;\n}\n\n/**\n * Order-insensitive multiset equality of two edge lists by (source,target) key.\n * Counts matter: prev [A→B, A→B, C→D] vs next [A→B, C→D, C→D] is a structural\n * change, not visual-only.\n */\nfunction edgeSetsEqual(\n prev: Array<{ source: string; target: string }>,\n next: Array<{ source: string; target: string }>,\n): boolean {\n if (prev.length !== next.length) return false;\n const key = (e: { source: string; target: string }) => `${e.source}->${e.target}`;\n const counts = new Map<string, number>();\n for (const e of prev) {\n const k = key(e);\n counts.set(k, (counts.get(k) ?? 0) + 1);\n }\n for (const e of next) {\n const k = key(e);\n const c = counts.get(k);\n if (!c) return false;\n counts.set(k, c - 1);\n }\n return true;\n}\n","/**\n * Table cell renderers: produce DOM elements for each cell type.\n *\n * Each renderer takes a resolved TableCell and returns an HTMLElement\n * (typically a <td>) with appropriate content, styling, and classes.\n */\n\nimport type {\n BarTableCell,\n CategoryTableCell,\n FlagTableCell,\n HeatmapTableCell,\n ImageTableCell,\n SparklineData,\n SparklineTableCell,\n TableCell,\n TextTableCell,\n} from '@opendata-ai/openchart-core';\n\n// ---------------------------------------------------------------------------\n// Utility\n// ---------------------------------------------------------------------------\n\n/** Apply common cell styles (background, color, font weight, font variant). */\nfunction applyCellStyle(td: HTMLTableCellElement, cell: TableCell): void {\n if (cell.style.backgroundColor) {\n td.style.background = cell.style.backgroundColor;\n }\n if (cell.style.color) {\n td.style.color = cell.style.color;\n }\n if (cell.style.fontWeight) {\n td.style.fontWeight = String(cell.style.fontWeight);\n }\n if (cell.style.fontVariant) {\n td.style.fontVariant = cell.style.fontVariant;\n }\n if (cell.aria) {\n td.setAttribute('aria-label', cell.aria);\n }\n}\n\n/**\n * Convert a 2-char ISO 3166-1 alpha-2 country code to a flag emoji.\n * Each letter maps to a Regional Indicator Symbol (offset 0x1F1A5 from ASCII).\n */\nfunction countryToEmoji(code: string): string {\n return [...code.toUpperCase()]\n .map((c) => String.fromCodePoint(c.charCodeAt(0) + 0x1f1a5))\n .join('');\n}\n\n/**\n * Lookup map for common country names by ISO 3166-1 alpha-2 code.\n * Falls back to the raw code for unrecognized values.\n */\nconst COUNTRY_NAMES: Record<string, string> = {\n US: 'United States',\n CN: 'China',\n IN: 'India',\n ID: 'Indonesia',\n BR: 'Brazil',\n PK: 'Pakistan',\n NG: 'Nigeria',\n BD: 'Bangladesh',\n RU: 'Russia',\n MX: 'Mexico',\n JP: 'Japan',\n DE: 'Germany',\n GB: 'United Kingdom',\n FR: 'France',\n IT: 'Italy',\n CA: 'Canada',\n AU: 'Australia',\n KR: 'South Korea',\n ES: 'Spain',\n AR: 'Argentina',\n CO: 'Colombia',\n ZA: 'South Africa',\n TR: 'Turkey',\n SA: 'Saudi Arabia',\n UA: 'Ukraine',\n PL: 'Poland',\n NL: 'Netherlands',\n SE: 'Sweden',\n NO: 'Norway',\n DK: 'Denmark',\n FI: 'Finland',\n CH: 'Switzerland',\n AT: 'Austria',\n BE: 'Belgium',\n PT: 'Portugal',\n IE: 'Ireland',\n NZ: 'New Zealand',\n SG: 'Singapore',\n IL: 'Israel',\n AE: 'United Arab Emirates',\n EG: 'Egypt',\n TH: 'Thailand',\n VN: 'Vietnam',\n PH: 'Philippines',\n MY: 'Malaysia',\n CL: 'Chile',\n PE: 'Peru',\n CZ: 'Czech Republic',\n GR: 'Greece',\n HU: 'Hungary',\n RO: 'Romania',\n ET: 'Ethiopia',\n};\n\n/** Get a human-readable name for a country code. */\nfunction getCountryName(code: string): string {\n return COUNTRY_NAMES[code.toUpperCase()] || code.toUpperCase();\n}\n\n/**\n * Describe a sparkline trend for screen readers.\n * Compares first and last values to determine direction.\n */\nfunction describeSparklineTrend(data: SparklineData): string {\n if (data.type === 'line' && data.points.length >= 2) {\n const first = data.points[0].y;\n const last = data.points[data.points.length - 1].y;\n const count = data.points.length;\n if (last > first) return `Sparkline with ${count} points, trending upward`;\n if (last < first) return `Sparkline with ${count} points, trending downward`;\n return `Sparkline with ${count} points, roughly flat`;\n }\n if ((data.type === 'bar' || data.type === 'column') && data.bars.length > 0) {\n return `${data.type === 'column' ? 'Column' : 'Bar'} sparkline with ${data.bars.length} values`;\n }\n return 'Sparkline';\n}\n\n// ---------------------------------------------------------------------------\n// Cell renderers\n// ---------------------------------------------------------------------------\n\n/** Render a plain text cell. */\nexport function renderTextCell(cell: TextTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n td.textContent = cell.formattedValue;\n applyCellStyle(td, cell);\n return td;\n}\n\n/** Render a heatmap-colored cell. */\nexport function renderHeatmapCell(cell: HeatmapTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n td.className = 'oc-table-heatmap';\n td.textContent = cell.formattedValue;\n applyCellStyle(td, cell);\n return td;\n}\n\n/** Render a category-colored cell. */\nexport function renderCategoryCell(cell: CategoryTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n td.className = 'oc-table-category';\n td.textContent = cell.formattedValue;\n applyCellStyle(td, cell);\n return td;\n}\n\n/** Render a cell with an inline bar visualization. */\nexport function renderBarCell(cell: BarTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n td.className = 'oc-table-bar';\n applyCellStyle(td, cell);\n\n const fill = document.createElement('div');\n fill.className = 'oc-table-bar-fill';\n fill.style.width = `${Math.round(cell.barWidth * 100)}%`;\n fill.style.left = `${Math.round(cell.barOffset * 100)}%`;\n fill.style.background = cell.barColor;\n td.appendChild(fill);\n\n const valueSpan = document.createElement('span');\n valueSpan.className = 'oc-table-bar-value';\n valueSpan.textContent = cell.formattedValue;\n td.appendChild(valueSpan);\n\n return td;\n}\n\n/**\n * Format a sparkline endpoint value for display.\n * Keeps it compact: no decimals for values >= 100, one decimal otherwise.\n */\nfunction formatSparklineValue(v: number): string {\n if (Math.abs(v) >= 1000) {\n return v.toLocaleString('en-US', { maximumFractionDigits: 0 });\n }\n if (Math.abs(v) >= 100) {\n return v.toFixed(0);\n }\n return v.toFixed(1);\n}\n\n/** Render a cell with an inline sparkline SVG. */\nexport function renderSparklineCell(cell: SparklineTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n applyCellStyle(td, cell);\n\n const sparklineData = cell.sparklineData;\n if (!sparklineData || sparklineData.count === 0) {\n td.textContent = cell.formattedValue || '';\n return td;\n }\n\n // Add aria-label describing the trend for screen readers\n const trendDescription = describeSparklineTrend(sparklineData);\n if (!td.getAttribute('aria-label')) {\n td.setAttribute('aria-label', trendDescription);\n }\n\n const wrapper = document.createElement('span');\n wrapper.className = 'oc-table-sparkline';\n\n const svgNS = 'http://www.w3.org/2000/svg';\n\n if (sparklineData.type === 'line') {\n // Infrographic-style sparkline: SVG polyline fills cell width via percentage x-coords.\n // Dots are HTML elements (not SVG circles) to avoid aspect-ratio distortion.\n // Labels are HTML below the SVG.\n const svgH = 28;\n const padY = 4;\n const lineH = svgH - padY * 2;\n\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('aria-hidden', 'true');\n svg.setAttribute('xmlns', svgNS);\n svg.style.height = `${svgH}px`;\n\n // Compute y positions in pixel space (no viewBox scaling)\n const yPositions = sparklineData.points.map((p) => padY + (1 - p.y) * lineH);\n\n // SVG polyline doesn't support % in points attribute, so use a viewBox\n // with a wide aspect ratio and map x to 0-1000 range. preserveAspectRatio=\"none\"\n // stretches the x-axis to fill the cell width. Y values stay in pixel space\n // since viewBox height matches the SVG height. Only the polyline is in the SVG;\n // dots are HTML elements to avoid circle distortion from the non-uniform scaling.\n const viewW = 1000;\n svg.setAttribute('viewBox', `0 0 ${viewW} ${svgH}`);\n svg.setAttribute('preserveAspectRatio', 'none');\n\n const ptsScaled = sparklineData.points.map((p, i) => ({\n x: p.x * viewW,\n y: yPositions[i],\n }));\n const scaledPointsStr = ptsScaled.map((p) => `${p.x},${p.y}`).join(' ');\n\n const polyline = document.createElementNS(svgNS, 'polyline');\n polyline.setAttribute('points', scaledPointsStr);\n polyline.setAttribute('fill', 'none');\n polyline.setAttribute('stroke', sparklineData.color);\n polyline.setAttribute('stroke-width', '1.5');\n polyline.setAttribute('stroke-linejoin', 'round');\n polyline.setAttribute('vector-effect', 'non-scaling-stroke');\n svg.appendChild(polyline);\n\n wrapper.appendChild(svg);\n\n // HTML dots at start and end (positioned absolutely over the SVG)\n const firstY = yPositions[0];\n const lastY = yPositions[yPositions.length - 1];\n const dotSize = 5;\n\n const startDot = document.createElement('span');\n startDot.className = 'oc-table-sparkline-dot';\n startDot.style.left = '0';\n startDot.style.top = `${firstY - dotSize / 2}px`;\n startDot.style.background = sparklineData.color;\n wrapper.appendChild(startDot);\n\n const endDot = document.createElement('span');\n endDot.className = 'oc-table-sparkline-dot';\n endDot.style.right = '0';\n endDot.style.top = `${lastY - dotSize / 2}px`;\n endDot.style.background = sparklineData.color;\n wrapper.appendChild(endDot);\n\n // HTML labels below the SVG, positioned at left and right edges\n const labelsRow = document.createElement('span');\n labelsRow.className = 'oc-table-sparkline-labels';\n labelsRow.style.color = sparklineData.color;\n\n const startLabel = document.createElement('span');\n startLabel.textContent = formatSparklineValue(sparklineData.startValue);\n labelsRow.appendChild(startLabel);\n\n const endLabel = document.createElement('span');\n endLabel.textContent = formatSparklineValue(sparklineData.endValue);\n labelsRow.appendChild(endLabel);\n\n wrapper.appendChild(labelsRow);\n } else if (sparklineData.type === 'column') {\n // Vertical bars at proportional heights\n const width = 80;\n const height = 20;\n const padding = 2;\n const innerW = width - padding * 2;\n const innerH = height - padding * 2;\n\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('width', String(width));\n svg.setAttribute('height', String(height));\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`);\n svg.setAttribute('aria-hidden', 'true');\n\n const barCount = sparklineData.bars.length;\n if (barCount > 0) {\n const gap = 1;\n const barW = Math.max(1, (innerW - gap * (barCount - 1)) / barCount);\n\n for (let i = 0; i < barCount; i++) {\n const barH = Math.max(1, sparklineData.bars[i] * innerH);\n const x = padding + i * (barW + gap);\n const y = padding + innerH - barH;\n\n const rect = document.createElementNS(svgNS, 'rect');\n rect.setAttribute('x', String(x));\n rect.setAttribute('y', String(y));\n rect.setAttribute('width', String(barW));\n rect.setAttribute('height', String(barH));\n rect.setAttribute('rx', '1.5');\n rect.setAttribute('fill', sparklineData.color);\n svg.appendChild(rect);\n }\n }\n\n wrapper.appendChild(svg);\n } else {\n // 'bar' type: horizontal bars at proportional widths\n const width = 80;\n const height = 20;\n const padding = 2;\n const innerW = width - padding * 2;\n const innerH = height - padding * 2;\n\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('width', String(width));\n svg.setAttribute('height', String(height));\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`);\n svg.setAttribute('aria-hidden', 'true');\n\n const barCount = sparklineData.bars.length;\n if (barCount > 0) {\n const gap = 1;\n const barH = Math.max(1, (innerH - gap * (barCount - 1)) / barCount);\n\n for (let i = 0; i < barCount; i++) {\n const barW = Math.max(1, sparklineData.bars[i] * innerW);\n const x = padding;\n const y = padding + i * (barH + gap);\n\n const rect = document.createElementNS(svgNS, 'rect');\n rect.setAttribute('x', String(x));\n rect.setAttribute('y', String(y));\n rect.setAttribute('width', String(barW));\n rect.setAttribute('height', String(barH));\n rect.setAttribute('rx', '1.5');\n rect.setAttribute('fill', sparklineData.color);\n svg.appendChild(rect);\n }\n }\n\n wrapper.appendChild(svg);\n }\n\n td.appendChild(wrapper);\n\n return td;\n}\n\n/** Render a cell with an image. */\nexport function renderImageCell(cell: ImageTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n applyCellStyle(td, cell);\n\n const wrapper = document.createElement('span');\n wrapper.className = `oc-table-image${cell.rounded ? ' oc-table-image-rounded' : ''}`;\n\n const img = document.createElement('img');\n img.src = cell.src;\n img.alt = cell.formattedValue || '';\n img.width = cell.imageWidth;\n img.height = cell.imageHeight;\n img.loading = 'lazy';\n\n wrapper.appendChild(img);\n td.appendChild(wrapper);\n\n return td;\n}\n\n/** Render a cell with a country flag emoji. */\nexport function renderFlagCell(cell: FlagTableCell): HTMLTableCellElement {\n const td = document.createElement('td');\n applyCellStyle(td, cell);\n\n const span = document.createElement('span');\n span.className = 'oc-table-flag';\n span.setAttribute('role', 'img');\n\n if (cell.countryCode && cell.countryCode.length === 2) {\n const countryName = getCountryName(cell.countryCode);\n span.textContent = countryToEmoji(cell.countryCode);\n span.setAttribute('aria-label', `Flag: ${countryName}`);\n } else {\n span.textContent = cell.formattedValue;\n span.setAttribute('aria-label', cell.formattedValue);\n }\n\n td.appendChild(span);\n\n return td;\n}\n\n// ---------------------------------------------------------------------------\n// Dispatcher\n// ---------------------------------------------------------------------------\n\n/** Render any table cell by dispatching on its cellType. */\nexport function renderCell(cell: TableCell): HTMLTableCellElement {\n switch (cell.cellType) {\n case 'text':\n return renderTextCell(cell);\n case 'heatmap':\n return renderHeatmapCell(cell);\n case 'category':\n return renderCategoryCell(cell);\n case 'bar':\n return renderBarCell(cell);\n case 'sparkline':\n return renderSparklineCell(cell);\n case 'image':\n return renderImageCell(cell);\n case 'flag':\n return renderFlagCell(cell);\n default:\n // Exhaustive check fallback\n return renderTextCell(cell as TextTableCell);\n }\n}\n","/**\n * Sankey mount API: the main entry point for vanilla JS sankey usage.\n *\n * createSankey() takes a container, SankeySpec, and options, compiles the\n * sankey, renders it as SVG, sets up responsive resizing, tooltip interaction,\n * hover highlighting, and returns a SankeyInstance with update/resize/export/destroy.\n */\n\nimport type {\n CompileOptions,\n DarkMode,\n SankeyLayout,\n SankeySpec,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { compileSankey } from '@opendata-ai/openchart-engine';\nimport { cancelAnimations, setupAnimationCleanup } from './animation';\nimport {\n exportJPG,\n exportPNG,\n exportSVG,\n exportSVGWithFonts,\n type JPGExportOptions,\n type SVGExportOptions,\n} from './export';\nimport { createMeasureText, resolveFontFamily, scheduleFontReload } from './measure-text';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { renderSankeySVG } from './sankey-renderer';\nimport { createTooltipManager, type TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SankeyMountOptions {\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode setting: \"auto\" (system pref), \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Enable responsive resizing. Defaults to true. */\n responsive?: boolean;\n /** Show the OpenData watermark. Defaults to true. */\n watermark?: boolean;\n /** Show tooltips on hover. Defaults to true. */\n tooltip?: boolean;\n /** Callback when a node is clicked. */\n onNodeClick?: (node: Record<string, unknown>) => void;\n /** Callback when a link is clicked. */\n onLinkClick?: (link: Record<string, unknown>) => void;\n /** Callback when a node is hovered (null on mouse leave). */\n onNodeHover?: (node: Record<string, unknown> | null) => void;\n /** Callback when a link is hovered (null on mouse leave). */\n onLinkHover?: (link: Record<string, unknown> | null) => void;\n}\n\nexport interface SankeyInstance {\n /** Re-compile and re-render with a new spec. */\n update(spec: SankeySpec): void;\n /** Re-compile at current container dimensions. */\n resize(): void;\n /** Export the sankey diagram. */\n export(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options?: JPGExportOptions,\n ): string | Promise<Blob> | Promise<string>;\n /** Remove all DOM elements and disconnect observers. */\n destroy(): void;\n /** The current compiled layout. */\n readonly layout: SankeyLayout;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Opacity for links connected to hovered node. */\nconst HIGHLIGHT_OPACITY = 0.7;\n/** Opacity for links NOT connected to hovered node. */\nconst DIM_OPACITY = 0.15;\n/** Opacity for nodes NOT connected to hovered node. */\nconst NODE_DIM_OPACITY = 0.2;\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a sankey instance from a spec and mount it into a container.\n */\nexport function createSankey(\n container: HTMLElement,\n spec: SankeySpec,\n options?: SankeyMountOptions,\n): SankeyInstance {\n let currentSpec = spec;\n let currentLayout: SankeyLayout;\n let destroyed = false;\n\n // DOM\n let svgElement: SVGSVGElement | null = null;\n\n // Subsystems\n let tooltipManager: TooltipManager | null = null;\n let cleanupTooltipEvents: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n\n // Animation state\n let isFirstRender = true;\n let animationCleanup: (() => void) | null = null;\n let pendingResize = false;\n\n // Set when webfonts have loaded and a recompile is owed. The next render()\n // that recompiles flips data-oc-fonts-state to 'ready' and clears this, so\n // the attribute stays honest when resize() defers to pendingResize during\n // the entrance animation.\n let fontsReloadPending = false;\n\n // Apply the root class up front so getComputedStyle sees --oc-font-family\n // before the text measurer is built.\n container.classList.add('oc-sankey-root');\n\n // Resolve the effective font the way compile() will: compile merges\n // { ...spec.theme, ...options.theme }, so options.theme wins over the\n // spec-level theme; fall back to the container's computed font. Measuring a\n // different font than gets rendered desyncs layout metrics and the reload watcher.\n function resolveEffectiveFont(): string {\n return (\n options?.theme?.fonts?.family ??\n currentSpec.theme?.fonts?.family ??\n resolveFontFamily(container)\n );\n }\n let fontFamily = resolveEffectiveFont();\n let measureText = createMeasureText(fontFamily);\n let renderGen = 0;\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n function compile(): SankeyLayout {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n measureText,\n };\n\n return compileSankey(currentSpec, compileOpts);\n }\n\n // ---------------------------------------------------------------------------\n // Tooltip and interaction wiring\n // ---------------------------------------------------------------------------\n\n function wireTooltipAndInteraction(svg: SVGSVGElement, layout: SankeyLayout): () => void {\n const cleanups: Array<() => void> = [];\n\n const nodesById = new Map<string, (typeof layout.nodes)[number]>();\n for (const n of layout.nodes) {\n if (!nodesById.has(n.nodeId)) nodesById.set(n.nodeId, n);\n }\n\n // Wire tooltip on node elements\n const nodeElements = svg.querySelectorAll('.oc-sankey-node');\n for (const el of nodeElements) {\n const markId = el.getAttribute('data-mark-id');\n if (!markId) continue;\n\n const content = layout.tooltipDescriptors.get(markId);\n const nodeId = el.getAttribute('data-node-id');\n const nodeData = nodeId ? (nodesById.get(nodeId)?.data ?? {}) : {};\n\n const handleMouseEnter = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n if (content && tooltipManager) {\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n options?.onNodeHover?.(nodeData);\n if (nodeId) highlightConnectedLinks(svg, nodeId, layout);\n };\n\n const handleMouseMove = (e: Event) => {\n if (content && tooltipManager) {\n const mouseEvent = e as MouseEvent;\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n };\n\n const handleMouseLeave = () => {\n tooltipManager?.hide();\n options?.onNodeHover?.(null);\n resetLinkOpacity(svg, layout);\n };\n\n const handleClick = () => {\n options?.onNodeClick?.(nodeData);\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n\n cleanups.push(() => {\n el.removeEventListener('mouseenter', handleMouseEnter);\n el.removeEventListener('mousemove', handleMouseMove);\n el.removeEventListener('mouseleave', handleMouseLeave);\n el.removeEventListener('click', handleClick);\n });\n }\n\n // Wire tooltip on link elements\n const linkElements = svg.querySelectorAll('.oc-sankey-link');\n for (const el of linkElements) {\n const markId = el.getAttribute('data-mark-id');\n if (!markId) continue;\n\n const content = layout.tooltipDescriptors.get(markId);\n const sourceId = el.getAttribute('data-source');\n const targetId = el.getAttribute('data-target');\n const linkData = findLinkData(layout, sourceId, targetId);\n\n const handleMouseEnter = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n if (content && tooltipManager) {\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n options?.onLinkHover?.(linkData);\n };\n\n const handleMouseMove = (e: Event) => {\n if (content && tooltipManager) {\n const mouseEvent = e as MouseEvent;\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n };\n\n const handleMouseLeave = () => {\n tooltipManager?.hide();\n options?.onLinkHover?.(null);\n };\n\n const handleClick = () => {\n options?.onLinkClick?.(linkData);\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n\n cleanups.push(() => {\n el.removeEventListener('mouseenter', handleMouseEnter);\n el.removeEventListener('mousemove', handleMouseMove);\n el.removeEventListener('mouseleave', handleMouseLeave);\n el.removeEventListener('click', handleClick);\n });\n }\n\n return () => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n };\n }\n\n /**\n * Find the data record for a link by source/target ids.\n */\n function findLinkData(\n layout: SankeyLayout,\n sourceId: string | null,\n targetId: string | null,\n ): Record<string, unknown> {\n if (!sourceId || !targetId) return {};\n const link = layout.links.find((l) => l.sourceId === sourceId && l.targetId === targetId);\n return link?.data ?? {};\n }\n\n /**\n * Highlight links connected to a node and dim unconnected links.\n */\n function highlightConnectedLinks(\n svg: SVGSVGElement,\n nodeId: string,\n _layout: SankeyLayout,\n ): void {\n // Collect connected node IDs (the hovered node + its direct neighbors)\n const connectedNodeIds = new Set<string>([nodeId]);\n const linkElements = svg.querySelectorAll('.oc-sankey-link');\n for (const el of linkElements) {\n const source = el.getAttribute('data-source');\n const target = el.getAttribute('data-target');\n const path = el.querySelector('path');\n if (!path) continue;\n\n const isConnected = source === nodeId || target === nodeId;\n path.setAttribute('fill-opacity', String(isConnected ? HIGHLIGHT_OPACITY : DIM_OPACITY));\n if (isConnected) {\n if (source) connectedNodeIds.add(source);\n if (target) connectedNodeIds.add(target);\n }\n }\n\n // Dim unconnected nodes (rect + label)\n const nodeElements = svg.querySelectorAll('.oc-sankey-node');\n for (const el of nodeElements) {\n const nid = el.getAttribute('data-node-id');\n if (!nid) continue;\n const isConnected = connectedNodeIds.has(nid);\n (el as SVGElement).style.opacity = isConnected ? '1' : String(NODE_DIM_OPACITY);\n }\n }\n\n /**\n * Reset all link opacities and node opacities to their original values.\n */\n function resetLinkOpacity(svg: SVGSVGElement, layout: SankeyLayout): void {\n const linkElements = svg.querySelectorAll('.oc-sankey-link');\n for (const el of linkElements) {\n const path = el.querySelector('path');\n if (!path) continue;\n // Look up the original opacity via data attributes rather than positional index\n const source = el.getAttribute('data-source');\n const target = el.getAttribute('data-target');\n const link = layout.links.find((l) => l.sourceId === source && l.targetId === target);\n path.setAttribute('fill-opacity', String(link?.fillOpacity ?? 0.5));\n }\n\n // Restore all node opacities\n const nodeElements = svg.querySelectorAll('.oc-sankey-node');\n for (const el of nodeElements) {\n (el as SVGElement).style.opacity = '1';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n\n function render(): void {\n if (destroyed) return;\n\n // Cancel in-progress animations before tearing down\n if (animationCleanup) {\n animationCleanup();\n animationCleanup = null;\n }\n if (svgElement) {\n cancelAnimations(svgElement);\n }\n\n // Clean up previous tooltip listeners\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n\n // Remove old SVG\n if (svgElement?.parentNode) {\n svgElement.parentNode.removeChild(svgElement);\n }\n\n // Compile\n currentLayout = compile();\n\n // Determine if we should animate\n const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;\n isFirstRender = false;\n\n // Render\n const animation = shouldAnimate ? currentLayout.animation : undefined;\n svgElement = renderSankeySVG(currentLayout, animation);\n container.appendChild(svgElement);\n\n // Dark mode class on container\n const isDark = resolveDarkMode(options?.darkMode);\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Wire tooltip + interaction events\n if (options?.tooltip !== false && svgElement) {\n if (!tooltipManager) {\n tooltipManager = createTooltipManager(container);\n }\n cleanupTooltipEvents = wireTooltipAndInteraction(svgElement, currentLayout);\n }\n\n // Animation cleanup on first render\n if (shouldAnimate && svgElement) {\n animationCleanup = setupAnimationCleanup(svgElement, () => {\n animationCleanup = null;\n if (pendingResize) {\n pendingResize = false;\n resize();\n }\n });\n }\n\n renderGen += 1;\n container.dataset.ocRenderGen = String(renderGen);\n\n // This render recompiled with the loaded webfonts; publish 'ready' now\n // rather than right after the fonts-ready resize() (which may have deferred\n // to pendingResize during the entrance animation).\n if (fontsReloadPending) {\n fontsReloadPending = false;\n container.dataset.ocFontsState = 'ready';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n function update(newSpec: SankeySpec): void {\n if (destroyed) return;\n currentSpec = newSpec;\n // A new spec can change theme.fonts.family; rebuild the measurer so layout\n // measures the font compile will actually render with.\n const nextFont = resolveEffectiveFont();\n if (nextFont !== fontFamily) {\n fontFamily = nextFont;\n measureText = createMeasureText(fontFamily);\n }\n isFirstRender = true; // Allow animation on update\n render();\n }\n\n function resize(): void {\n if (destroyed) return;\n // Skip resize during entrance animation to avoid tearing down the animated SVG.\n // Queued resizes replay once animation completes.\n if (animationCleanup) {\n pendingResize = true;\n return;\n }\n render();\n }\n\n function doExport(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n exportOptions?: JPGExportOptions,\n ): string | Promise<Blob> | Promise<string> {\n if (!svgElement) {\n throw new Error('Sankey is not rendered yet');\n }\n\n switch (format) {\n case 'svg':\n return exportSVG(svgElement);\n case 'svg-with-fonts':\n return exportSVGWithFonts(svgElement, exportOptions as SVGExportOptions);\n case 'png':\n return exportPNG(svgElement, exportOptions);\n case 'jpg':\n return exportJPG(svgElement, exportOptions);\n default:\n throw new Error(`Unsupported export format: ${format}`);\n }\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n // Cancel entrance animations\n if (animationCleanup) {\n animationCleanup();\n animationCleanup = null;\n pendingResize = false;\n }\n if (svgElement) {\n cancelAnimations(svgElement);\n }\n\n // Disconnect resize observer\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n // Clean up tooltip events\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n\n // Destroy tooltip manager\n if (tooltipManager) {\n tooltipManager.destroy();\n tooltipManager = null;\n }\n\n // Remove SVG\n if (svgElement?.parentNode) {\n svgElement.parentNode.removeChild(svgElement);\n }\n svgElement = null;\n\n container.classList.remove('oc-dark');\n container.classList.remove('oc-sankey-root');\n }\n\n // ---------------------------------------------------------------------------\n // Initialize\n // ---------------------------------------------------------------------------\n\n try {\n currentLayout = compile();\n\n // Determine if we should animate\n const shouldAnimate = !!currentLayout.animation?.enter;\n isFirstRender = false;\n\n // Render\n const animation = shouldAnimate ? currentLayout.animation : undefined;\n svgElement = renderSankeySVG(currentLayout, animation);\n container.appendChild(svgElement);\n\n // Dark mode class on container\n const isDark = resolveDarkMode(options?.darkMode);\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Wire tooltip + interaction events\n if (options?.tooltip !== false && svgElement) {\n tooltipManager = createTooltipManager(container);\n cleanupTooltipEvents = wireTooltipAndInteraction(svgElement, currentLayout);\n }\n\n // Animation cleanup\n if (shouldAnimate && svgElement) {\n animationCleanup = setupAnimationCleanup(svgElement, () => {\n animationCleanup = null;\n if (pendingResize) {\n pendingResize = false;\n resize();\n }\n });\n }\n\n renderGen += 1;\n container.dataset.ocRenderGen = String(renderGen);\n } catch (err) {\n console.error('[viz] Sankey mount failed:', err);\n // Re-throw so callers can handle the error rather than silently returning a broken instance\n throw err;\n }\n\n // Recompile once after webfonts load so late-swapping fonts don't leave\n // node labels measured against fallback metrics.\n const fontsPending = scheduleFontReload(\n fontFamily,\n () => !destroyed,\n () => {\n // Mark the reload owed, then recompile. render() flips the attribute to\n // 'ready' once it actually recompiles, including the pendingResize replay\n // after the entrance animation finishes.\n fontsReloadPending = true;\n resize();\n },\n );\n container.dataset.ocFontsState = fontsPending ? 'pending' : 'ready';\n\n // Responsive resize\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => {\n resize();\n });\n }\n\n return {\n update,\n resize,\n export: doExport,\n destroy,\n get layout() {\n return currentLayout;\n },\n };\n}\n","/**\n * Sankey SVG renderer: converts a SankeyLayout into SVG DOM elements.\n *\n * Creates an <svg> with gradient defs, links (behind), nodes, labels,\n * legend, and chrome. All styling via inline SVG attributes from layout data.\n * Animation is pure CSS, driven by data attributes and CSS custom properties.\n */\n\nimport type {\n CategoricalLegendLayout,\n LegendLayout,\n MeasureTextFn,\n ResolvedAnimation,\n ResolvedChromeElement,\n SankeyLayout,\n SankeyLinkMark,\n SankeyNodeMark,\n TextStyle,\n} from '@opendata-ai/openchart-core';\nimport {\n BRAND_FONT_SIZE,\n BRAND_MIN_WIDTH,\n estimateTextWidth,\n textAscent,\n wrapText,\n} from '@opendata-ai/openchart-core';\nimport { clampStaggerDelay } from '@opendata-ai/openchart-engine';\nimport { stampAnimationVars } from './animation-vars';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst XLINK_NS = 'http://www.w3.org/1999/xlink';\nconst BRAND_URL = 'https://tryopendata.ai';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createSVGElement(tag: string): SVGElement {\n return document.createElementNS(SVG_NS, tag);\n}\n\nfunction setAttrs(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, String(value));\n }\n}\n\nfunction applyTextStyle(el: SVGElement, style: TextStyle): void {\n setAttrs(el, {\n 'font-family': style.fontFamily,\n 'font-size': style.fontSize,\n 'font-weight': style.fontWeight,\n });\n (el as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', style.fill);\n if (style.textAnchor) {\n el.setAttribute('text-anchor', style.textAnchor);\n }\n if (style.dominantBaseline) {\n el.setAttribute('dominant-baseline', style.dominantBaseline);\n }\n if (style.fontVariant) {\n el.setAttribute('font-variant', style.fontVariant);\n }\n}\n\n/**\n * Stamp animation index attributes on a mark element when animation is enabled.\n */\nfunction stampAnimationAttrs(\n el: SVGElement,\n mark: { animationIndex?: number },\n fallbackIndex: number,\n animation?: ResolvedAnimation,\n): void {\n if (!animation?.enter) return;\n const idx = mark.animationIndex ?? fallbackIndex;\n el.setAttribute('data-animation-index', String(idx));\n (el as SVGElement & ElementCSSInlineStyle).style.setProperty('--oc-mark-index', String(idx));\n}\n\n// ---------------------------------------------------------------------------\n// Chrome rendering\n// ---------------------------------------------------------------------------\n\nfunction renderChromeElement(\n parent: SVGElement,\n element: ResolvedChromeElement,\n className: string,\n chromeKey: string,\n measureText?: MeasureTextFn,\n): void {\n const text = createSVGElement('text');\n // element.y is the TOP of the text box; convert to the alphabetic baseline\n // (see renderers/chrome.ts — WebKit mishandles dominant-baseline:hanging).\n setAttrs(text, { x: element.x, y: element.y + textAscent(element.style.fontSize) });\n applyTextStyle(text, element.style);\n text.setAttribute('class', className);\n text.setAttribute('data-chrome-key', chromeKey);\n\n const lines = wrapText(\n element.text,\n element.style.fontSize,\n element.style.fontWeight,\n element.maxWidth,\n measureText,\n );\n\n if (lines.length === 1) {\n text.textContent = element.text;\n } else {\n const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);\n for (let i = 0; i < lines.length; i++) {\n const tspan = createSVGElement('tspan');\n setAttrs(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });\n tspan.textContent = lines[i];\n text.appendChild(tspan);\n }\n }\n\n parent.appendChild(text);\n}\n\nfunction renderChrome(parent: SVGElement, layout: SankeyLayout): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-chrome');\n\n const { chrome, measureText } = layout;\n\n if (chrome.title) {\n renderChromeElement(g, chrome.title, 'oc-title', 'title', measureText);\n }\n if (chrome.subtitle) {\n renderChromeElement(g, chrome.subtitle, 'oc-subtitle', 'subtitle', measureText);\n }\n\n // Bottom chrome: positioned below the sankey drawing area.\n // Sankey has no x-axis, so no extra axis extent to account for.\n const bottomOffset = layout.area.y + layout.area.height;\n if (chrome.source) {\n renderChromeElement(\n g,\n { ...chrome.source, y: bottomOffset + chrome.source.y },\n 'oc-source',\n 'source',\n measureText,\n );\n }\n if (chrome.byline) {\n renderChromeElement(\n g,\n { ...chrome.byline, y: bottomOffset + chrome.byline.y },\n 'oc-byline',\n 'byline',\n measureText,\n );\n }\n if (chrome.footer) {\n renderChromeElement(\n g,\n { ...chrome.footer, y: bottomOffset + chrome.footer.y },\n 'oc-footer',\n 'footer',\n measureText,\n );\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Brand rendering\n// ---------------------------------------------------------------------------\n\nfunction renderBrand(parent: SVGElement, layout: SankeyLayout): void {\n if (layout.dimensions.width < BRAND_MIN_WIDTH) return;\n\n const { width } = layout.dimensions;\n const padding = layout.theme.spacing.padding;\n const rightEdge = width - padding;\n const fill = layout.theme.colors.axis;\n\n const bottomOffset = layout.area.y + layout.area.height;\n const { chrome } = layout;\n const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;\n const chromeY = firstBottom\n ? bottomOffset + firstBottom.y\n : bottomOffset + layout.theme.spacing.chartToFooter;\n\n const a = createSVGElement('a');\n a.setAttribute('href', BRAND_URL);\n a.setAttributeNS(XLINK_NS, 'xlink:href', BRAND_URL);\n a.setAttribute('target', '_blank');\n a.setAttribute('rel', 'noopener');\n a.setAttribute('class', 'oc-chrome-ref');\n\n const BRAND_LARGE = 16;\n const text = createSVGElement('text');\n setAttrs(text, {\n x: rightEdge,\n y: chromeY + BRAND_LARGE,\n 'dominant-baseline': 'alphabetic',\n 'text-anchor': 'end',\n 'font-family': layout.theme.fonts.family,\n 'font-size': BRAND_FONT_SIZE,\n 'fill-opacity': 0.55,\n });\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', fill);\n\n const trySpan = createSVGElement('tspan');\n setAttrs(trySpan, { 'font-weight': 500 });\n trySpan.textContent = 'try';\n\n const openDataSpan = createSVGElement('tspan');\n setAttrs(openDataSpan, { 'font-weight': 600, 'font-size': BRAND_LARGE });\n openDataSpan.textContent = 'OpenData';\n\n const aiSpan = createSVGElement('tspan');\n setAttrs(aiSpan, { 'font-weight': 500 });\n aiSpan.textContent = '.ai';\n\n text.appendChild(trySpan);\n text.appendChild(openDataSpan);\n text.appendChild(aiSpan);\n a.appendChild(text);\n parent.appendChild(a);\n}\n\n// ---------------------------------------------------------------------------\n// Legend rendering\n// ---------------------------------------------------------------------------\n\nfunction renderLegend(parent: SVGElement, legendLayout: LegendLayout): void {\n if (!('entries' in legendLayout) || legendLayout.entries.length === 0) return;\n const legend = legendLayout as CategoricalLegendLayout;\n\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-legend');\n g.setAttribute('role', 'list');\n g.setAttribute('aria-label', 'Chart legend');\n\n const isHorizontal = legend.position === 'top' || legend.position === 'bottom';\n let offsetX = legend.bounds.x;\n let offsetY = legend.bounds.y;\n\n for (let i = 0; i < legend.entries.length; i++) {\n const entry = legend.entries[i];\n\n // Wrap to next line if this entry would overflow bounds\n if (isHorizontal && i > 0) {\n const labelWidth = estimateTextWidth(\n entry.label,\n legend.labelStyle.fontSize,\n legend.labelStyle.fontWeight,\n );\n const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;\n if (offsetX + entryWidth > legend.bounds.x + legend.bounds.width) {\n offsetX = legend.bounds.x;\n offsetY += legend.swatchSize + 6;\n }\n }\n\n const entryG = createSVGElement('g');\n entryG.setAttribute('class', 'oc-legend-entry');\n entryG.setAttribute('role', 'listitem');\n entryG.setAttribute('data-legend-index', String(i));\n entryG.setAttribute('data-legend-label', entry.label);\n\n if (entry.overflow) {\n entryG.setAttribute('data-legend-overflow', 'true');\n entryG.setAttribute('aria-label', entry.label);\n entryG.setAttribute('opacity', '0.5');\n } else {\n entryG.setAttribute(\n 'aria-label',\n `${entry.label}: ${entry.active !== false ? 'visible' : 'hidden'}`,\n );\n entryG.setAttribute('style', 'cursor: pointer');\n if (entry.active === false) {\n entryG.setAttribute('opacity', '0.3');\n }\n }\n\n // Swatch (always rect for sankey)\n const rect = createSVGElement('rect');\n setAttrs(rect, {\n x: offsetX,\n y: offsetY,\n width: legend.swatchSize,\n height: legend.swatchSize,\n fill: entry.color,\n rx: 2,\n });\n entryG.appendChild(rect);\n\n // Label\n const label = createSVGElement('text');\n setAttrs(label, {\n x: offsetX + legend.swatchSize + legend.swatchGap,\n y: offsetY + legend.swatchSize / 2,\n 'dominant-baseline': 'central',\n });\n applyTextStyle(label, legend.labelStyle);\n label.textContent = entry.label;\n entryG.appendChild(label);\n\n g.appendChild(entryG);\n\n // Advance position for next entry\n if (isHorizontal) {\n const labelWidth = estimateTextWidth(\n entry.label,\n legend.labelStyle.fontSize,\n legend.labelStyle.fontWeight,\n );\n const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;\n offsetX += entryWidth;\n } else {\n offsetY += legend.swatchSize + legend.entryGap;\n }\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Gradient defs\n// ---------------------------------------------------------------------------\n\n/**\n * Build a node position lookup for gradient x1/x2 coordinates.\n * Maps nodeId to { x, width } so we can compute gradient endpoints.\n */\nfunction buildNodePositionMap(nodes: SankeyNodeMark[]): Map<string, { x: number; width: number }> {\n const map = new Map<string, { x: number; width: number }>();\n for (const node of nodes) {\n map.set(node.nodeId, { x: node.x, width: node.width });\n }\n return map;\n}\n\n/** Sanitize a string for use as an SVG element ID (no spaces, $, or other invalid chars). */\nfunction sanitizeId(s: string): string {\n return s.replace(/[^a-zA-Z0-9_-]/g, '_');\n}\n\nfunction renderGradientDefs(\n defs: SVGElement,\n links: SankeyLinkMark[],\n nodePositions: Map<string, { x: number; width: number }>,\n): void {\n for (let i = 0; i < links.length; i++) {\n const link = links[i];\n // Only create gradients when source and target colors differ\n if (link.sourceColor === link.targetColor) continue;\n\n // Use sanitized node names for debuggability, with index suffix for uniqueness\n const gradId = `oc-sg-${sanitizeId(link.sourceId)}-${sanitizeId(link.targetId)}-${i}`;\n const gradient = createSVGElement('linearGradient');\n gradient.setAttribute('id', gradId);\n gradient.setAttribute('gradientUnits', 'userSpaceOnUse');\n\n // x1 = right edge of source node, x2 = left edge of target node\n const sourcePos = nodePositions.get(link.sourceId);\n const targetPos = nodePositions.get(link.targetId);\n const x1 = sourcePos ? sourcePos.x + sourcePos.width : 0;\n const x2 = targetPos ? targetPos.x : 0;\n\n gradient.setAttribute('x1', String(x1));\n gradient.setAttribute('x2', String(x2));\n\n const stop0 = createSVGElement('stop');\n setAttrs(stop0, { offset: '0%', 'stop-color': link.sourceColor });\n gradient.appendChild(stop0);\n\n const stop1 = createSVGElement('stop');\n setAttrs(stop1, { offset: '100%', 'stop-color': link.targetColor });\n gradient.appendChild(stop1);\n\n defs.appendChild(gradient);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Links rendering\n// ---------------------------------------------------------------------------\n\nfunction renderLinks(\n parent: SVGElement,\n links: SankeyLinkMark[],\n _nodePositions: Map<string, { x: number; width: number }>,\n animation?: ResolvedAnimation,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-sankey-links');\n\n for (let i = 0; i < links.length; i++) {\n const link = links[i];\n\n const linkG = createSVGElement('g');\n linkG.setAttribute('class', 'oc-sankey-link');\n linkG.setAttribute('data-mark-id', `link-${link.sourceId}-${link.targetId}-${i}`);\n linkG.setAttribute('data-source', link.sourceId);\n linkG.setAttribute('data-target', link.targetId);\n\n if (link.aria?.label) {\n linkG.setAttribute('aria-label', link.aria.label);\n }\n\n stampAnimationAttrs(linkG, link, i, animation);\n\n const path = createSVGElement('path');\n path.setAttribute('d', link.path);\n path.setAttribute('stroke', 'none');\n path.setAttribute('fill-opacity', String(link.fillOpacity));\n\n // Use gradient fill when colors differ, otherwise solid fill\n if (link.sourceColor !== link.targetColor) {\n const gradId = `oc-sg-${sanitizeId(link.sourceId)}-${sanitizeId(link.targetId)}-${i}`;\n path.setAttribute('fill', `url(#${gradId})`);\n } else {\n path.setAttribute('fill', link.sourceColor);\n }\n\n linkG.appendChild(path);\n g.appendChild(linkG);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Nodes rendering\n// ---------------------------------------------------------------------------\n\nfunction renderNodes(\n parent: SVGElement,\n nodes: SankeyNodeMark[],\n animation?: ResolvedAnimation,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-sankey-nodes');\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n\n const nodeG = createSVGElement('g');\n nodeG.setAttribute('class', 'oc-sankey-node');\n nodeG.setAttribute('data-mark-id', `node-${node.nodeId}`);\n nodeG.setAttribute('data-node-id', node.nodeId);\n\n if (node.aria?.label) {\n nodeG.setAttribute('aria-label', node.aria.label);\n }\n\n stampAnimationAttrs(nodeG, node, i, animation);\n\n const rect = createSVGElement('rect');\n setAttrs(rect, {\n x: node.x,\n y: node.y,\n width: node.width,\n height: Math.max(node.height, 1), // Ensure at least 1px visibility\n fill: node.fill,\n rx: node.cornerRadius,\n });\n\n if (node.stroke) {\n rect.setAttribute('stroke', node.stroke);\n }\n if (node.strokeWidth) {\n rect.setAttribute('stroke-width', String(node.strokeWidth));\n }\n\n nodeG.appendChild(rect);\n g.appendChild(nodeG);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Labels rendering\n// ---------------------------------------------------------------------------\n\nfunction renderLabels(\n parent: SVGElement,\n nodes: SankeyNodeMark[],\n measureText?: MeasureTextFn,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-sankey-labels');\n\n for (const node of nodes) {\n const { label } = node;\n if (!label.visible) continue;\n\n const text = createSVGElement('text');\n setAttrs(text, { x: label.x, y: label.y });\n applyTextStyle(text, label.style);\n\n // Wrap label text when maxWidth is set and text would overflow\n if (label.maxWidth !== undefined && label.maxWidth > 0) {\n const fontSize = label.style.fontSize ?? 12;\n const fontWeight = label.style.fontWeight ?? 400;\n const lines = wrapText(label.text, fontSize, fontWeight, label.maxWidth, measureText);\n if (lines.length > 1) {\n const lineHeight = fontSize * (label.style.lineHeight ?? 1.3);\n // Center the multi-line block vertically around the label y position\n const totalHeight = (lines.length - 1) * lineHeight;\n const startY = label.y - totalHeight / 2;\n for (let i = 0; i < lines.length; i++) {\n const tspan = createSVGElement('tspan');\n tspan.setAttribute('x', String(label.x));\n tspan.setAttribute('y', String(startY + i * lineHeight));\n tspan.textContent = lines[i];\n text.appendChild(tspan);\n }\n } else {\n text.textContent = label.text;\n }\n } else {\n text.textContent = label.text;\n }\n\n g.appendChild(text);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Main render function\n// ---------------------------------------------------------------------------\n\n/**\n * Render a SankeyLayout into an SVG element.\n *\n * The SVG structure is: background > defs (gradients) > links > nodes > labels > legend > chrome > brand.\n * Links render before nodes so they appear visually behind.\n */\nexport function renderSankeySVG(\n layout: SankeyLayout,\n animation?: ResolvedAnimation,\n): SVGSVGElement {\n const { width, height } = layout.dimensions;\n\n const svg = createSVGElement('svg') as SVGSVGElement;\n setAttrs(svg, {\n viewBox: `0 0 ${width} ${height}`,\n xmlns: SVG_NS,\n overflow: 'visible',\n });\n svg.style.height = `${height}px`;\n svg.setAttribute('role', layout.a11y.role);\n svg.setAttribute('aria-label', layout.a11y.altText);\n\n // Classes: oc-chart oc-sankey, plus oc-animate if animated\n const enterPhase = animation?.enter;\n const animate = !!enterPhase;\n const classes = animate ? 'oc-chart oc-sankey oc-animate' : 'oc-chart oc-sankey';\n svg.setAttribute('class', classes);\n\n // Set animation CSS custom properties when the enter phase is enabled\n if (enterPhase) {\n const totalMarks = layout.nodes.length + layout.links.length;\n const stagger = clampStaggerDelay(enterPhase.staggerDelay, totalMarks);\n stampAnimationVars(svg, {\n duration: enterPhase.duration,\n stagger,\n annotationDelay: animation!.annotationDelay,\n ease: enterPhase.ease,\n });\n }\n\n // Background\n const bg = createSVGElement('rect');\n bg.setAttribute('class', 'oc-background');\n setAttrs(bg, {\n x: 0,\n y: 0,\n width,\n height,\n fill: layout.theme.colors.background,\n });\n svg.appendChild(bg);\n\n // Defs: gradient definitions for links\n const nodePositions = buildNodePositionMap(layout.nodes);\n const defs = createSVGElement('defs');\n renderGradientDefs(defs, layout.links, nodePositions);\n svg.appendChild(defs);\n\n // Links (behind nodes)\n renderLinks(svg, layout.links, nodePositions, animation);\n\n // Nodes\n renderNodes(svg, layout.nodes, animation);\n\n // Labels\n renderLabels(svg, layout.nodes, layout.measureText);\n\n // Legend\n renderLegend(svg, layout.legend);\n\n // Chrome (title, subtitle, source, byline, footer) renders on top\n renderChrome(svg, layout);\n\n // Brand\n if (layout.watermark) {\n renderBrand(svg, layout);\n }\n\n return svg;\n}\n","/**\n * Table keyboard navigation: arrow-key cell navigation, Enter to sort,\n * Escape to clear search, and aria-activedescendant management.\n *\n * Designed to be wired up by table-mount.ts after render. Returns a\n * cleanup function to remove listeners on re-render or destroy.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface KeyboardNavOptions {\n /** The wrapper element containing the whole table UI. */\n wrapper: HTMLElement;\n /** Callback to trigger sort on a column. */\n onSort: (columnKey: string) => void;\n /** Callback to clear search and return focus to the table body. */\n onClearSearch: () => void;\n /** Callback to announce text to screen readers via the live region. */\n onAnnounce: (message: string) => void;\n}\n\ninterface CellPosition {\n row: number;\n col: number;\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\n/**\n * Attach keyboard navigation to a rendered table.\n *\n * @returns A cleanup function that removes all event listeners.\n */\nexport function attachKeyboardNav(options: KeyboardNavOptions): () => void {\n const { wrapper, onSort, onClearSearch, onAnnounce } = options;\n\n let focusedCell: CellPosition = { row: -1, col: 0 };\n\n const table = wrapper.querySelector('table');\n if (!table) return () => {};\n\n const tbody = table.querySelector('tbody');\n const thead = table.querySelector('thead');\n if (!tbody || !thead) return () => {};\n\n // Make tbody focusable\n tbody.setAttribute('tabindex', '0');\n\n function getRows(): HTMLTableRowElement[] {\n if (!tbody) return [];\n return Array.from(tbody.querySelectorAll('tr'));\n }\n\n function getHeaderCells(): HTMLTableCellElement[] {\n if (!thead) return [];\n const headerRow = thead.querySelector('tr');\n if (!headerRow) return [];\n return Array.from(headerRow.querySelectorAll('th'));\n }\n\n function getCellsInRow(tr: HTMLTableRowElement): HTMLTableCellElement[] {\n return Array.from(tr.querySelectorAll('td'));\n }\n\n function getColCount(): number {\n const rows = getRows();\n if (rows.length === 0) return getHeaderCells().length;\n return getCellsInRow(rows[0]).length;\n }\n\n function clearFocusHighlight(): void {\n const prev = wrapper.querySelector('.oc-table-cell-focus');\n if (prev) {\n prev.classList.remove('oc-table-cell-focus');\n prev.removeAttribute('id');\n }\n }\n\n function setFocusedCell(row: number, col: number): void {\n clearFocusHighlight();\n const rows = getRows();\n const colCount = getColCount();\n\n // Clamp values\n if (rows.length === 0) return;\n row = Math.max(0, Math.min(row, rows.length - 1));\n col = Math.max(0, Math.min(col, colCount - 1));\n\n focusedCell = { row, col };\n\n // Highlight the cell\n const tr = rows[row];\n if (!tr) return;\n const cells = getCellsInRow(tr);\n const cell = cells[col];\n if (!cell) return;\n\n const cellId = `oc-cell-${row}-${col}`;\n cell.id = cellId;\n cell.classList.add('oc-table-cell-focus');\n cell.setAttribute('data-row', String(row));\n cell.setAttribute('data-col', String(col));\n\n // Set aria-activedescendant on tbody\n if (tbody) {\n tbody.setAttribute('aria-activedescendant', cellId);\n }\n\n // Scroll cell into view if needed\n cell.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n\n function handleTbodyFocus(): void {\n // When tbody receives focus, highlight the first cell (or restore last)\n if (focusedCell.row < 0) {\n setFocusedCell(0, 0);\n } else {\n setFocusedCell(focusedCell.row, focusedCell.col);\n }\n }\n\n function handleTbodyKeydown(e: KeyboardEvent): void {\n const rows = getRows();\n if (rows.length === 0) return;\n\n const colCount = getColCount();\n const { row, col } = focusedCell;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n if (row < rows.length - 1) {\n setFocusedCell(row + 1, col);\n }\n break;\n case 'ArrowUp':\n e.preventDefault();\n if (row > 0) {\n setFocusedCell(row - 1, col);\n } else {\n // Move focus to the header\n focusHeaderCell(col);\n }\n break;\n case 'ArrowRight':\n e.preventDefault();\n if (col < colCount - 1) {\n setFocusedCell(row, col + 1);\n }\n break;\n case 'ArrowLeft':\n e.preventDefault();\n if (col > 0) {\n setFocusedCell(row, col - 1);\n }\n break;\n case 'Home':\n e.preventDefault();\n setFocusedCell(row, 0);\n break;\n case 'End':\n e.preventDefault();\n setFocusedCell(row, colCount - 1);\n break;\n }\n }\n\n // Header cell keyboard handling\n function focusHeaderCell(col: number): void {\n const headers = getHeaderCells();\n if (col >= 0 && col < headers.length) {\n clearFocusHighlight();\n headers[col].focus();\n }\n }\n\n function handleHeaderKeydown(e: KeyboardEvent): void {\n const th = e.currentTarget as HTMLTableCellElement;\n const headers = getHeaderCells();\n const colIndex = headers.indexOf(th);\n if (colIndex < 0) return;\n\n switch (e.key) {\n case 'ArrowRight':\n e.preventDefault();\n if (colIndex < headers.length - 1) {\n headers[colIndex + 1].focus();\n }\n break;\n case 'ArrowLeft':\n e.preventDefault();\n if (colIndex > 0) {\n headers[colIndex - 1].focus();\n }\n break;\n case 'ArrowDown':\n e.preventDefault();\n // Move focus to first body row at this column\n if (tbody) {\n tbody.focus();\n setFocusedCell(0, colIndex);\n }\n break;\n case 'Enter':\n case ' ': {\n e.preventDefault();\n const sortColumn = th.getAttribute('data-column');\n const sortBtn = th.querySelector('[data-sort-column]');\n if (sortColumn && sortBtn) {\n onSort(sortColumn);\n }\n break;\n }\n }\n }\n\n // Search escape handling\n const searchInput = wrapper.querySelector('.oc-table-search input') as HTMLInputElement | null;\n\n function handleSearchKeydown(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n e.preventDefault();\n onClearSearch();\n // Return focus to tbody\n if (tbody) {\n tbody.focus();\n onAnnounce('Search cleared');\n }\n }\n }\n\n // Wire up event listeners\n tbody.addEventListener('focus', handleTbodyFocus);\n tbody.addEventListener('keydown', handleTbodyKeydown as EventListener);\n\n // Make header cells focusable and wire keyboard\n const headerCells = getHeaderCells();\n for (const th of headerCells) {\n th.setAttribute('tabindex', '0');\n th.addEventListener('keydown', handleHeaderKeydown as EventListener);\n }\n\n if (searchInput) {\n searchInput.addEventListener('keydown', handleSearchKeydown as EventListener);\n }\n\n // Cleanup\n return () => {\n tbody.removeEventListener('focus', handleTbodyFocus);\n tbody.removeEventListener('keydown', handleTbodyKeydown as EventListener);\n\n for (const th of headerCells) {\n th.removeEventListener('keydown', handleHeaderKeydown as EventListener);\n }\n\n if (searchInput) {\n searchInput.removeEventListener('keydown', handleSearchKeydown as EventListener);\n }\n\n clearFocusHighlight();\n };\n}\n","/**\n * Table mount API: the main entry point for vanilla JS table usage.\n *\n * createTable() takes a container, spec, and options, compiles the table,\n * renders it as HTML, sets up responsive resizing, sort/search/pagination\n * interactivity, and returns a TableInstance with update/resize/export/destroy.\n *\n * Supports both controlled and uncontrolled modes:\n * - Uncontrolled (default): manages sort/search/page internally\n * - Controlled: reads state from externalState, fires onStateChange\n */\n\nimport type {\n CompileTableOptions,\n DarkMode,\n SortState,\n TableLayout,\n TableSpec,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { getBreakpoint } from '@opendata-ai/openchart-core';\nimport { compileTable } from '@opendata-ai/openchart-engine';\nimport { setupTableAnimationCleanup } from './animation';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { attachKeyboardNav } from './table-keyboard';\nimport { renderTable } from './table-renderer';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TableState {\n sort: SortState | null;\n search: string;\n page: number;\n}\n\nexport interface TableMountOptions {\n theme?: ThemeConfig;\n darkMode?: DarkMode;\n responsive?: boolean;\n /** Show the OpenData watermark. Defaults to true. */\n watermark?: boolean;\n onRowClick?: (row: Record<string, unknown>) => void;\n onStateChange?: (state: TableState) => void;\n externalState?: { sort?: SortState | null; search?: string; page?: number };\n}\n\nexport interface TableInstance {\n update(spec: TableSpec): void;\n resize(): void;\n export(format: 'csv'): string;\n getState(): TableState;\n setState(partial: Partial<TableState>): void;\n destroy(): void;\n}\n\n// ---------------------------------------------------------------------------\n// CSV export\n// ---------------------------------------------------------------------------\n\nfunction csvEscape(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n') || value.includes('\\r')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\n// ---------------------------------------------------------------------------\n// Sort cycling\n// ---------------------------------------------------------------------------\n\n/**\n * Cycle sort state: clicking same column cycles none -> asc -> desc -> none.\n * Clicking a different column resets to asc.\n */\nfunction cycleSort(current: SortState | null, column: string): SortState | null {\n if (!current || current.column !== column) {\n return { column, direction: 'asc' };\n }\n if (current.direction === 'asc') {\n return { column, direction: 'desc' };\n }\n // desc -> none\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a table instance from a spec and mount it into a container.\n *\n * @param container - The DOM element to render into.\n * @param spec - The table spec.\n * @param options - Mount options.\n * @returns A TableInstance with update/resize/export/destroy methods.\n */\nexport function createTable(\n container: HTMLElement,\n spec: TableSpec,\n options?: TableMountOptions,\n): TableInstance {\n let currentSpec = spec;\n let currentLayout: TableLayout;\n let wrapperElement: HTMLElement | null = null;\n let disconnectResize: (() => void) | null = null;\n let cleanupKeyboard: (() => void) | null = null;\n let cleanupAnimations: (() => void) | null = null;\n let isFirstRender = true;\n let destroyed = false;\n\n // Internal state (used in uncontrolled mode)\n const internalState: TableState = {\n sort: null,\n search: '',\n page: 0,\n };\n\n // Debounce timers\n let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n let resizeDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const isControlled = options?.externalState !== undefined;\n\n function getState(): TableState {\n if (isControlled && options?.externalState) {\n return {\n sort: options.externalState.sort ?? null,\n search: options.externalState.search ?? '',\n page: options.externalState.page ?? 0,\n };\n }\n return { ...internalState };\n }\n\n function updateState(partial: Partial<TableState>): void {\n if (isControlled) {\n // In controlled mode, notify parent\n const current = getState();\n const next: TableState = {\n sort: partial.sort !== undefined ? partial.sort : current.sort,\n search: partial.search !== undefined ? partial.search : current.search,\n page: partial.page !== undefined ? partial.page : current.page,\n };\n options?.onStateChange?.(next);\n } else {\n // In uncontrolled mode, update internal state\n if (partial.sort !== undefined) internalState.sort = partial.sort;\n if (partial.search !== undefined) internalState.search = partial.search;\n if (partial.page !== undefined) internalState.page = partial.page;\n options?.onStateChange?.({ ...internalState });\n }\n }\n\n function compile(): TableLayout {\n const state = getState();\n const darkMode = resolveDarkMode(options?.darkMode);\n const { width } = getContainerDimensions();\n\n const compileOpts: CompileTableOptions = {\n width,\n height: 600,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n sort: state.sort ?? undefined,\n search: state.search || undefined,\n page: state.page,\n };\n\n return compileTable(currentSpec, compileOpts);\n }\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n /**\n * Announce a message to screen readers via the live region.\n */\n function announce(message: string): void {\n if (!wrapperElement) return;\n const liveRegion = wrapperElement.querySelector('.oc-table-live-region');\n if (liveRegion) {\n liveRegion.textContent = message;\n }\n }\n\n /**\n * Apply responsive breakpoint class based on container width.\n * Only auto-adds compact mode at small sizes. Never removes compact\n * if the spec explicitly requests it (layout.compact === true).\n */\n function applyBreakpointClass(): void {\n if (!wrapperElement) return;\n const { width } = getContainerDimensions();\n const bp = getBreakpoint(width);\n\n if (bp === 'compact' || bp === 'medium') {\n wrapperElement.classList.add('oc-table--compact');\n } else if (!currentLayout?.compact) {\n // Only remove compact if the spec didn't explicitly request it\n wrapperElement.classList.remove('oc-table--compact');\n }\n }\n\n function render(): void {\n if (destroyed) return;\n\n try {\n // Cancel any in-flight animations before re-rendering\n if (cleanupAnimations) {\n cleanupAnimations();\n cleanupAnimations = null;\n }\n\n // Clean up previous keyboard nav\n if (cleanupKeyboard) {\n cleanupKeyboard();\n cleanupKeyboard = null;\n }\n\n // Clean up previous render\n if (wrapperElement?.parentNode) {\n wrapperElement.parentNode.removeChild(wrapperElement);\n wrapperElement = null;\n }\n\n currentLayout = compile();\n const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;\n wrapperElement = renderTable(currentLayout, container, { animate: shouldAnimate });\n\n // Set up animation cleanup on first animated render\n if (shouldAnimate && wrapperElement) {\n cleanupAnimations = setupTableAnimationCleanup(wrapperElement);\n }\n if (isFirstRender) {\n isFirstRender = false;\n }\n\n // Apply dark mode class\n const isDark = resolveDarkMode(options?.darkMode);\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Apply responsive breakpoint\n applyBreakpointClass();\n\n // Add clickable class if onRowClick is provided\n if (options?.onRowClick) {\n wrapperElement.classList.add('oc-table--clickable');\n }\n\n // Wire up event handlers\n wireEvents();\n\n // Wire up keyboard navigation\n if (wrapperElement) {\n cleanupKeyboard = attachKeyboardNav({\n wrapper: wrapperElement,\n onSort: (columnKey: string) => {\n const state = getState();\n const newSort = cycleSort(state.sort, columnKey);\n updateState({ sort: newSort, page: 0 });\n\n // Announce sort change\n if (newSort) {\n const dir = newSort.direction === 'asc' ? 'ascending' : 'descending';\n announce(`Sorted by ${columnKey} ${dir}`);\n } else {\n announce('Sort cleared');\n }\n\n if (!isControlled) {\n rerender();\n }\n },\n onClearSearch: () => {\n updateState({ search: '', page: 0 });\n if (!isControlled) {\n rerender();\n }\n },\n onAnnounce: announce,\n });\n }\n } catch (err) {\n console.error('[viz] Table render failed:', err);\n }\n }\n\n function wireEvents(): void {\n if (!wrapperElement) return;\n\n // Sort click handlers on thead buttons\n const sortBtns = wrapperElement.querySelectorAll('[data-sort-column]');\n for (const btn of sortBtns) {\n btn.addEventListener('click', handleSortClick);\n }\n\n // Search input\n const searchInput = wrapperElement.querySelector(\n '.oc-table-search input',\n ) as HTMLInputElement | null;\n if (searchInput) {\n searchInput.addEventListener('input', handleSearchInput);\n }\n\n // Pagination buttons\n const pageButtons = wrapperElement.querySelectorAll('[data-page-action]');\n for (const btn of pageButtons) {\n btn.addEventListener('click', handlePageClick);\n }\n\n // Row click\n if (options?.onRowClick) {\n const rows = wrapperElement.querySelectorAll('tbody tr');\n for (const row of rows) {\n row.addEventListener('click', handleRowClick);\n }\n }\n }\n\n function handleSortClick(e: Event): void {\n const btn = e.currentTarget as HTMLElement;\n const column = btn.getAttribute('data-sort-column');\n if (!column) return;\n\n const state = getState();\n const newSort = cycleSort(state.sort, column);\n\n updateState({ sort: newSort, page: 0 });\n\n // Announce sort change for screen readers\n if (newSort) {\n const dir = newSort.direction === 'asc' ? 'ascending' : 'descending';\n announce(`Sorted by ${column} ${dir}`);\n } else {\n announce('Sort cleared');\n }\n\n if (!isControlled) {\n rerender();\n }\n }\n\n function handleSearchInput(e: Event): void {\n const input = e.target as HTMLInputElement;\n const query = input.value;\n\n if (searchDebounceTimer !== null) {\n clearTimeout(searchDebounceTimer);\n }\n\n searchDebounceTimer = setTimeout(() => {\n searchDebounceTimer = null;\n updateState({ search: query, page: 0 });\n\n if (!isControlled) {\n rerender();\n // Announce search result count\n const rowCount = currentLayout?.rows?.length ?? 0;\n if (query) {\n announce(`${rowCount} result${rowCount !== 1 ? 's' : ''} found`);\n }\n }\n }, 200);\n }\n\n function handlePageClick(e: Event): void {\n const btn = e.currentTarget as HTMLElement;\n const action = btn.getAttribute('data-page-action');\n const state = getState();\n\n if (action === 'prev' && state.page > 0) {\n updateState({ page: state.page - 1 });\n } else if (action === 'next') {\n updateState({ page: state.page + 1 });\n }\n\n if (!isControlled) {\n rerender();\n }\n }\n\n function handleRowClick(e: Event): void {\n const tr = e.currentTarget as HTMLElement;\n const rowId = tr.getAttribute('data-row-id');\n if (!rowId || !currentLayout) return;\n\n const row = currentLayout.rows.find((r) => r.id === rowId);\n if (row) {\n options?.onRowClick?.(row.data);\n }\n }\n\n /**\n * Re-render the table, preserving search input focus across the DOM rebuild.\n */\n function rerender(): void {\n if (destroyed) return;\n\n // Capture current search input state before re-render\n const searchInput = wrapperElement?.querySelector(\n '.oc-table-search input',\n ) as HTMLInputElement | null;\n const hadFocus = searchInput && document.activeElement === searchInput;\n const selectionStart = searchInput?.selectionStart ?? 0;\n const selectionEnd = searchInput?.selectionEnd ?? 0;\n\n render();\n\n // Restore search focus after re-render\n if (hadFocus) {\n const newInput = wrapperElement?.querySelector(\n '.oc-table-search input',\n ) as HTMLInputElement | null;\n if (newInput) {\n newInput.focus();\n newInput.setSelectionRange(selectionStart, selectionEnd);\n }\n }\n }\n\n function update(newSpec: TableSpec): void {\n if (destroyed) return;\n currentSpec = newSpec;\n render();\n }\n\n function resize(): void {\n if (destroyed) return;\n if (cleanupAnimations) return; // Skip resize during entrance animation\n render();\n }\n\n function doExport(format: 'csv'): string {\n if (format !== 'csv') {\n throw new Error(`Unsupported export format: ${format}`);\n }\n\n // Export all filtered/sorted data (not just current page)\n // Re-compile without pagination\n const state = getState();\n const darkMode = resolveDarkMode(options?.darkMode);\n const { width } = getContainerDimensions();\n\n const fullLayout = compileTable(currentSpec, {\n width,\n height: 600,\n theme: options?.theme,\n darkMode,\n sort: state.sort ?? undefined,\n search: state.search || undefined,\n // No page/pageSize: get all rows\n });\n\n const headers = fullLayout.columns.map((c) => c.label);\n const csvRows = [headers.map(csvEscape).join(',')];\n\n for (const row of fullLayout.rows) {\n const values = row.cells.map((cell) => csvEscape(cell.formattedValue));\n csvRows.push(values.join(','));\n }\n\n return csvRows.join('\\n');\n }\n\n function setState(partial: Partial<TableState>): void {\n if (destroyed) return;\n\n // In controlled mode compile() reads from options.externalState, so\n // updating internalState alone would be ignored. Push the new values onto\n // externalState (the source getState() reads) so the render reflects them.\n if (isControlled && options?.externalState) {\n if (partial.sort !== undefined) options.externalState.sort = partial.sort;\n if (partial.search !== undefined) options.externalState.search = partial.search;\n if (partial.page !== undefined) options.externalState.page = partial.page;\n } else {\n if (partial.sort !== undefined) internalState.sort = partial.sort;\n if (partial.search !== undefined) internalState.search = partial.search;\n if (partial.page !== undefined) internalState.page = partial.page;\n }\n\n render();\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n if (cleanupAnimations) {\n cleanupAnimations();\n cleanupAnimations = null;\n }\n if (cleanupKeyboard) {\n cleanupKeyboard();\n cleanupKeyboard = null;\n }\n if (searchDebounceTimer !== null) {\n clearTimeout(searchDebounceTimer);\n searchDebounceTimer = null;\n }\n if (resizeDebounceTimer !== null) {\n clearTimeout(resizeDebounceTimer);\n resizeDebounceTimer = null;\n }\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n if (wrapperElement?.parentNode) {\n wrapperElement.parentNode.removeChild(wrapperElement);\n wrapperElement = null;\n }\n container.classList.remove('oc-dark');\n }\n\n // Initial render\n render();\n\n // Set up responsive resize with breakpoint detection\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => {\n if (resizeDebounceTimer !== null) {\n clearTimeout(resizeDebounceTimer);\n }\n resizeDebounceTimer = setTimeout(() => {\n resizeDebounceTimer = null;\n // Update breakpoint class without full re-render when possible\n applyBreakpointClass();\n resize();\n }, 100);\n });\n }\n\n return {\n update,\n resize,\n export: doExport,\n getState,\n setState,\n destroy,\n };\n}\n","/**\n * Table renderer: produces semantic HTML from a TableLayout.\n *\n * renderTable() creates the full DOM structure: chrome, search bar,\n * scrollable table with sticky column support, pagination, and footer.\n * The returned element replaces or appends to the given container.\n */\n\nimport type { ResolvedColumn, TableLayout, TableRow } from '@opendata-ai/openchart-core';\nimport { BRAND_FONT_SIZE } from '@opendata-ai/openchart-core';\nimport { clampStaggerDelay } from '@opendata-ai/openchart-engine';\nimport { stampAnimationVars } from './animation-vars';\nimport { applySrOnlyStyles } from './dom-helpers';\nimport { renderCell } from './renderers/table-cells';\n\n/** Options for renderTable(). */\nexport interface TableRenderOptions {\n /** Whether to apply entrance animation on this render. */\n animate?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst BRAND_URL = 'https://tryopendata.ai';\n\n// ---------------------------------------------------------------------------\n// Chrome rendering\n// ---------------------------------------------------------------------------\n\n/** Create chrome (title/subtitle or source/footer) block. */\nfunction renderChromeBlock(\n layout: TableLayout,\n position: 'header' | 'footer',\n): HTMLDivElement | null {\n const chrome = layout.chrome;\n\n if (position === 'header') {\n if (!chrome.title && !chrome.subtitle) return null;\n\n const div = document.createElement('div');\n div.className = 'oc-chrome';\n\n if (chrome.title) {\n const h = document.createElement('div');\n h.className = 'oc-table-title';\n h.textContent = chrome.title.text;\n div.appendChild(h);\n }\n if (chrome.subtitle) {\n const sub = document.createElement('div');\n sub.className = 'oc-table-subtitle';\n sub.textContent = chrome.subtitle.text;\n div.appendChild(sub);\n }\n\n return div;\n }\n\n // Footer position\n if (!chrome.source && !chrome.footer) return null;\n\n const div = document.createElement('div');\n div.className = 'oc-chrome oc-chrome-footer';\n\n if (chrome.source) {\n const src = document.createElement('div');\n src.className = 'oc-table-source';\n src.textContent = chrome.source.text;\n div.appendChild(src);\n }\n if (chrome.footer) {\n const foot = document.createElement('div');\n foot.className = 'oc-table-footer-text';\n foot.textContent = chrome.footer.text;\n div.appendChild(foot);\n }\n\n return div;\n}\n\n// ---------------------------------------------------------------------------\n// Table head\n// ---------------------------------------------------------------------------\n\nfunction renderThead(\n columns: ResolvedColumn[],\n sort: TableLayout['sort'],\n): HTMLTableSectionElement {\n const thead = document.createElement('thead');\n const tr = document.createElement('tr');\n tr.setAttribute('role', 'row');\n\n for (const col of columns) {\n const th = document.createElement('th');\n th.setAttribute('scope', 'col');\n th.setAttribute('role', 'columnheader');\n th.style.textAlign = col.align;\n th.style.width = `${col.width}px`;\n\n // Sort state: use 'ascending'/'descending' per WAI-ARIA spec\n let ariaSortValue: string = 'none';\n if (sort && sort.column === col.key) {\n ariaSortValue = sort.direction === 'asc' ? 'ascending' : 'descending';\n }\n th.setAttribute('aria-sort', ariaSortValue);\n th.setAttribute('data-column', col.key);\n\n // Label\n const labelSpan = document.createTextNode(col.label);\n th.appendChild(labelSpan);\n\n // Sort button\n if (col.sortable) {\n const btn = document.createElement('button');\n btn.className = 'oc-table-sort-btn';\n btn.setAttribute('aria-label', `Sort by ${col.label}`);\n btn.setAttribute('data-sort-column', col.key);\n btn.type = 'button';\n th.appendChild(btn);\n }\n\n tr.appendChild(th);\n }\n\n thead.appendChild(tr);\n return thead;\n}\n\n// ---------------------------------------------------------------------------\n// Table body\n// ---------------------------------------------------------------------------\n\nfunction renderTbody(rows: TableRow[], columns: ResolvedColumn[]): HTMLTableSectionElement {\n const tbody = document.createElement('tbody');\n\n for (let r = 0; r < rows.length; r++) {\n const row = rows[r];\n const tr = document.createElement('tr');\n tr.setAttribute('role', 'row');\n tr.setAttribute('data-row-id', row.id);\n tr.style.setProperty('--oc-row-index', String(r));\n\n for (let c = 0; c < columns.length; c++) {\n const cell = row.cells[c];\n if (!cell) continue;\n\n const td = renderCell(cell);\n td.setAttribute('role', 'gridcell');\n td.style.textAlign = columns[c].align;\n tr.appendChild(td);\n }\n\n tbody.appendChild(tr);\n }\n\n return tbody;\n}\n\n// ---------------------------------------------------------------------------\n// Search bar\n// ---------------------------------------------------------------------------\n\nfunction renderSearchBar(layout: TableLayout): HTMLDivElement | null {\n if (!layout.search.enabled) return null;\n\n const div = document.createElement('div');\n div.className = 'oc-table-search';\n\n const input = document.createElement('input');\n input.type = 'search';\n input.placeholder = layout.search.placeholder;\n input.setAttribute('aria-label', 'Search table');\n input.value = layout.search.query;\n\n div.appendChild(input);\n return div;\n}\n\n// ---------------------------------------------------------------------------\n// Pagination\n// ---------------------------------------------------------------------------\n\nfunction renderPagination(layout: TableLayout): HTMLDivElement | null {\n if (!layout.pagination) return null;\n\n const { page, pageSize, totalRows, totalPages } = layout.pagination;\n\n const div = document.createElement('div');\n div.className = 'oc-table-pagination';\n\n const info = document.createElement('span');\n info.className = 'oc-table-pagination-info';\n\n if (totalRows === 0) {\n info.textContent = 'No results';\n } else {\n const start = page * pageSize + 1;\n const end = Math.min((page + 1) * pageSize, totalRows);\n info.textContent = `Showing ${start}-${end} of ${totalRows}`;\n }\n\n div.appendChild(info);\n\n const btnGroup = document.createElement('span');\n btnGroup.className = 'oc-table-pagination-btns';\n\n const prevBtn = document.createElement('button');\n prevBtn.setAttribute('aria-label', 'Previous page');\n prevBtn.setAttribute('data-page-action', 'prev');\n prevBtn.textContent = 'Prev';\n prevBtn.disabled = page <= 0;\n btnGroup.appendChild(prevBtn);\n\n const nextBtn = document.createElement('button');\n nextBtn.setAttribute('aria-label', 'Next page');\n nextBtn.setAttribute('data-page-action', 'next');\n nextBtn.textContent = 'Next';\n nextBtn.disabled = page >= totalPages - 1;\n btnGroup.appendChild(nextBtn);\n\n div.appendChild(btnGroup);\n return div;\n}\n\n// ---------------------------------------------------------------------------\n// Empty state\n// ---------------------------------------------------------------------------\n\nfunction renderEmptyState(message: string): HTMLDivElement {\n const div = document.createElement('div');\n div.className = 'oc-table-empty';\n div.setAttribute('aria-live', 'polite');\n div.textContent = message;\n return div;\n}\n\n// ---------------------------------------------------------------------------\n// Main render\n// ---------------------------------------------------------------------------\n\n/**\n * Render a TableLayout into a full DOM structure.\n *\n * @param layout - The compiled table layout.\n * @param container - The container element to render into.\n * @returns The wrapper element that was created.\n */\nexport function renderTable(\n layout: TableLayout,\n container: HTMLElement,\n opts?: TableRenderOptions,\n): HTMLElement {\n const wrapper = document.createElement('div');\n wrapper.className = 'oc-table-wrapper';\n\n // Apply theme colors as CSS custom properties so table CSS picks them up.\n // Without this, dark-background themes show invisible text since the\n // CSS defaults (--oc-text etc.) are light-mode values.\n const { theme, chrome } = layout;\n if (theme) {\n const s = wrapper.style;\n s.setProperty('--oc-bg', theme.colors.background);\n s.setProperty('--oc-text', theme.colors.text);\n s.setProperty('--oc-text-secondary', theme.colors.axis ?? theme.colors.text);\n s.setProperty('--oc-text-muted', theme.colors.axis ?? theme.colors.text);\n s.setProperty('--oc-gridline', theme.colors.gridline);\n s.setProperty('--oc-border', theme.colors.gridline);\n s.setProperty('--oc-font-family', theme.fonts.family);\n s.fontFamily = theme.fonts.family;\n }\n\n // Set computed chrome CSS custom properties so chrome elements pick up\n // theme-resolved values via CSS fallbacks (e.g. --oc-title-computed-size).\n {\n const s = wrapper.style;\n if (chrome.title) {\n s.setProperty('--oc-title-computed-size', `${chrome.title.style.fontSize}px`);\n s.setProperty('--oc-title-computed-weight', String(chrome.title.style.fontWeight));\n s.setProperty('--oc-title-computed-color', chrome.title.style.fill);\n }\n if (chrome.subtitle) {\n s.setProperty('--oc-subtitle-computed-size', `${chrome.subtitle.style.fontSize}px`);\n s.setProperty('--oc-subtitle-computed-weight', String(chrome.subtitle.style.fontWeight));\n s.setProperty('--oc-subtitle-computed-color', chrome.subtitle.style.fill);\n }\n if (chrome.source) {\n s.setProperty('--oc-source-computed-size', `${chrome.source.style.fontSize}px`);\n s.setProperty('--oc-source-computed-color', chrome.source.style.fill);\n }\n if (chrome.footer) {\n s.setProperty('--oc-footer-computed-size', `${chrome.footer.style.fontSize}px`);\n s.setProperty('--oc-footer-computed-color', chrome.footer.style.fill);\n }\n }\n\n // Apply class modifiers\n if (layout.compact) {\n wrapper.classList.add('oc-table--compact');\n }\n\n // Header chrome\n const headerChrome = renderChromeBlock(layout, 'header');\n if (headerChrome) {\n wrapper.appendChild(headerChrome);\n }\n\n // Search bar\n const searchBar = renderSearchBar(layout);\n if (searchBar) {\n wrapper.appendChild(searchBar);\n }\n\n // Handle empty data\n if (layout.rows.length === 0) {\n const message = layout.search.query ? 'No results found' : 'No data';\n wrapper.appendChild(renderEmptyState(message));\n } else {\n // Scroll container\n const scroll = document.createElement('div');\n scroll.className = 'oc-table-scroll';\n\n // Table\n const table = document.createElement('table');\n table.setAttribute('role', 'grid');\n table.setAttribute('aria-label', layout.a11y.caption);\n\n if (layout.stickyFirstColumn) {\n table.classList.add('oc-table--sticky');\n }\n\n // Caption (screen reader only – inline styles ensure hiding even without\n // the external stylesheet, e.g. CDN / esm.sh usage)\n const caption = document.createElement('caption');\n caption.className = 'oc-sr-only';\n applySrOnlyStyles(caption);\n caption.textContent = layout.a11y.summary;\n table.appendChild(caption);\n\n // Thead\n table.appendChild(renderThead(layout.columns, layout.sort));\n\n // Tbody\n table.appendChild(renderTbody(layout.rows, layout.columns));\n\n scroll.appendChild(table);\n wrapper.appendChild(scroll);\n }\n\n // Pagination\n const pagination = renderPagination(layout);\n if (pagination) {\n wrapper.appendChild(pagination);\n }\n\n // Footer row: source/footer chrome on the left, brand watermark on the\n // right, laid out on a single line via flex. Built up here and appended\n // after the live region so source text and watermark share a baseline.\n const footerRow = document.createElement('div');\n footerRow.className = 'oc-table-footer-row';\n\n const footerChrome = renderChromeBlock(layout, 'footer');\n if (footerChrome) {\n footerRow.appendChild(footerChrome);\n }\n\n // Live region for screen reader announcements (sort changes, search results)\n const liveRegion = document.createElement('div');\n liveRegion.className = 'oc-table-live-region oc-sr-only';\n applySrOnlyStyles(liveRegion);\n liveRegion.setAttribute('aria-live', 'polite');\n liveRegion.setAttribute('aria-atomic', 'true');\n liveRegion.setAttribute('role', 'status');\n wrapper.appendChild(liveRegion);\n\n // Brand watermark — joins the footer row, pushed to the right by the\n // flex container so it sits on the same line as the source text.\n if (layout.watermark) {\n const brandColor = theme ? theme.colors.axis : '#999999';\n const brand = document.createElement('div');\n brand.className = 'oc-table-ref';\n const brandLink = document.createElement('a');\n brandLink.href = BRAND_URL;\n brandLink.target = '_blank';\n brandLink.rel = 'noopener';\n brandLink.style.cssText = `font-size: ${BRAND_FONT_SIZE}px; font-weight: 600; color: ${brandColor}; opacity: 0.55; text-decoration: none; font-family: ${theme ? theme.fonts.family : 'sans-serif'};`;\n brandLink.textContent = 'OpenData';\n brand.appendChild(brandLink);\n footerRow.appendChild(brand);\n }\n\n // Append the footer row only when it has content (source/footer/watermark).\n if (footerRow.childElementCount > 0) {\n wrapper.appendChild(footerRow);\n }\n\n // Animation: stamp CSS custom properties and add oc-animate class BEFORE\n // DOM insertion to avoid a flash of final state.\n if (opts?.animate && layout.animation?.enter) {\n const enterPhase = layout.animation.enter;\n const rowCount = layout.rows.length;\n const stagger = clampStaggerDelay(enterPhase.staggerDelay, rowCount);\n stampAnimationVars(wrapper, {\n duration: enterPhase.duration,\n stagger,\n ease: enterPhase.ease,\n });\n wrapper.classList.add('oc-animate');\n }\n\n container.appendChild(wrapper);\n return wrapper;\n}\n","/**\n * TileMap mount API: the main entry point for vanilla JS tilemap usage.\n *\n * createTileMap() takes a container, TileMapSpec, and options, compiles the\n * tilemap, renders it as SVG, sets up responsive resizing, tooltip interaction,\n * and returns a TileMapInstance with update/resize/export/destroy.\n */\n\nimport type {\n CompileOptions,\n DarkMode,\n ThemeConfig,\n TileMapLayout,\n TileMapSpec,\n} from '@opendata-ai/openchart-core';\nimport { BREAKPOINT_COMPACT_MAX, BREAKPOINT_MEDIUM_MAX } from '@opendata-ai/openchart-core';\nimport { compileTileMap } from '@opendata-ai/openchart-engine';\nimport { cancelAnimations, setupAnimationCleanup } from './animation';\nimport {\n exportJPG,\n exportPNG,\n exportSVG,\n exportSVGWithFonts,\n type JPGExportOptions,\n type SVGExportOptions,\n} from './export';\nimport { createMeasureText, resolveFontFamily, scheduleFontReload } from './measure-text';\nimport { observeResize } from './resize-observer';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport { renderTileMapSVG } from './tilemap-renderer';\nimport { createTooltipManager, type TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Responsive tile sizing\n// ---------------------------------------------------------------------------\n\n// The state grid is 8 rows tall (see engine/tilemap/layout.ts GRID_ROWS).\n// TILEMAP_TILE_GAP must match the gap passed to computeTilePositions in\n// compile-tilemap.ts (currently 5) so the mount's height budget matches the\n// grid the compiler builds.\nconst TILEMAP_GRID_ROWS = 8;\nconst TILEMAP_TILE_GAP = 5;\n// Generous vertical reserve for chrome (title/subtitle/source) + padding +\n// legend. Intentionally over-reserved: on narrow widths we WANT tile size to be\n// bound by width, so any excess height budget just lets the map grow taller\n// (which is the desired mobile behavior) rather than starving the tiles.\nconst TILEMAP_CHROME_RESERVE = 180;\n\n/**\n * Target tile size (px) for a given container width. Drives the height budget\n * on non-desktop widths so the 8-row grid isn't starved to tiny tiles after\n * chrome is subtracted from a square drawing area. Width still caps the actual\n * tile size, so these are ceilings the layout grows toward, not guarantees.\n */\nfunction pickTargetTileSize(width: number): number {\n if (width < BREAKPOINT_COMPACT_MAX) return 34; // compact / mobile\n return 40; // medium (tablet, sidebars)\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TileMapMountOptions {\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode setting: \"auto\" (system pref), \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Enable responsive resizing. Defaults to true. */\n responsive?: boolean;\n /** Show the OpenData watermark. Defaults to true. */\n watermark?: boolean;\n /** Show tooltips on hover. Defaults to true. */\n tooltip?: boolean;\n /** Callback when a tile is clicked. */\n onTileClick?: (tile: {\n stateCode: string;\n stateName: string;\n value: number | null;\n data: Record<string, unknown>;\n }) => void;\n /** Callback when a tile is hovered (null on mouse leave). */\n onTileHover?: (\n tile: {\n stateCode: string;\n stateName: string;\n value: number | null;\n data: Record<string, unknown>;\n } | null,\n ) => void;\n}\n\nexport interface TileMapInstance {\n /** Re-compile and re-render with a new spec. */\n update(spec: TileMapSpec): void;\n /** Re-compile at current container dimensions. */\n resize(): void;\n /** Export the tilemap. */\n export(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options?: JPGExportOptions | SVGExportOptions,\n ): string | Promise<Blob> | Promise<string>;\n /** Remove all DOM elements and disconnect observers. */\n destroy(): void;\n /** The current compiled layout. */\n readonly layout: TileMapLayout;\n}\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a tilemap instance from a spec and mount it into a container.\n */\nexport function createTileMap(\n container: HTMLElement,\n spec: TileMapSpec,\n options?: TileMapMountOptions,\n): TileMapInstance {\n let currentSpec = spec;\n let currentLayout: TileMapLayout;\n let destroyed = false;\n\n // DOM\n let svgElement: SVGSVGElement | null = null;\n\n // Subsystems\n let tooltipManager: TooltipManager | null = null;\n let cleanupTooltipEvents: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n\n // Animation state\n let animationCleanup: (() => void) | null = null;\n let pendingResize = false;\n\n // Latches whether the host constrains our height. Measured once, on the\n // first render that produces a real rect: height 0 with a real width means\n // the host is auto-height and we pick our own budget. Any other height is\n // an explicit box we must fit inside. Re-reading this after the first paint\n // would just measure our own SVG, so the value has to latch.\n let isAutoHeight: boolean | null = null;\n\n // Set when webfonts have loaded and a recompile is owed. The next render()\n // that recompiles flips data-oc-fonts-state to 'ready' and clears this, so\n // the attribute stays honest when resize() defers to pendingResize during\n // the entrance animation.\n let fontsReloadPending = false;\n\n // Apply the root class up front so getComputedStyle sees --oc-font-family\n // before the text measurer is built.\n container.classList.add('oc-tilemap-root');\n\n // Resolve the effective font the way compile() will: compile merges\n // { ...spec.theme, ...options.theme }, so options.theme wins over the\n // spec-level theme; fall back to the container's computed font. Measuring a\n // different font than gets rendered desyncs layout metrics and the reload watcher.\n function resolveEffectiveFont(): string {\n return (\n options?.theme?.fonts?.family ??\n currentSpec.theme?.fonts?.family ??\n resolveFontFamily(container)\n );\n }\n let fontFamily = resolveEffectiveFont();\n let measureText = createMeasureText(fontFamily);\n let renderGen = 0;\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function getContainerDimensions(): { width: number; height: number } {\n const rect = container.getBoundingClientRect();\n const width = Math.max(rect.width || 600, 100);\n\n if (isAutoHeight === null && (rect.width > 0 || rect.height > 0)) {\n isAutoHeight = rect.height === 0 && rect.width > 0;\n }\n\n // An explicit host height is a hard budget: the compiler derives a tight\n // content height from whatever we pass, so handing it the container's own\n // height keeps the SVG inside its box instead of overflowing onto whatever\n // sits below it.\n if (isAutoHeight === false && rect.height > 0) {\n return { width, height: Math.max(rect.height, 100) };\n }\n\n // Auto-height host: pick our own budget. The compiler derives its own tight\n // content height and treats the passed height as a cap. On desktop, a square\n // budget (height = width) keeps tiles from ballooning (the 8-row grid becomes\n // the binding constraint at ~width/8). Below desktop, a square budget starves\n // the grid after chrome, so give it a height budget sized to a comfortable\n // target tile instead — the map then grows taller as needed rather than\n // shrinking the tiles.\n if (width >= BREAKPOINT_MEDIUM_MAX) {\n return { width, height: width };\n }\n const targetTile = pickTargetTileSize(width);\n const height =\n targetTile * TILEMAP_GRID_ROWS +\n TILEMAP_TILE_GAP * (TILEMAP_GRID_ROWS - 1) +\n TILEMAP_CHROME_RESERVE;\n return { width, height };\n }\n\n function compile(): TileMapLayout {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n measureText,\n };\n\n return compileTileMap(currentSpec, compileOpts);\n }\n\n // ---------------------------------------------------------------------------\n // Tooltip and interaction wiring\n // ---------------------------------------------------------------------------\n\n function wireTooltipAndInteraction(svg: SVGSVGElement, layout: TileMapLayout): () => void {\n const cleanups: Array<() => void> = [];\n\n const tilesByStateCode = new Map<string, (typeof layout.tiles)[number]>();\n for (const t of layout.tiles) {\n if (!tilesByStateCode.has(t.stateCode)) tilesByStateCode.set(t.stateCode, t);\n }\n\n // Wire tooltip on tile elements\n const tileElements = svg.querySelectorAll('.oc-tilemap-tile');\n for (const el of tileElements) {\n const stateCode = el.getAttribute('data-state');\n if (!stateCode) continue;\n\n const content = layout.tooltipDescriptors.get(stateCode);\n const tile = tilesByStateCode.get(stateCode);\n\n const handleMouseEnter = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n if (content && tooltipManager && options?.tooltip !== false) {\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n if (tile) {\n options?.onTileHover?.({\n stateCode: tile.stateCode,\n stateName: tile.data.stateName as string,\n value: tile.value,\n data: tile.data,\n });\n }\n };\n\n const handleMouseMove = (e: Event) => {\n if (content && tooltipManager && options?.tooltip !== false) {\n const mouseEvent = e as MouseEvent;\n const svgRect = svg.getBoundingClientRect();\n const x = mouseEvent.clientX - svgRect.left;\n const y = mouseEvent.clientY - svgRect.top;\n tooltipManager.show(content, x, y);\n }\n };\n\n const handleMouseLeave = () => {\n tooltipManager?.hide();\n options?.onTileHover?.(null);\n };\n\n const handleClick = () => {\n if (tile) {\n options?.onTileClick?.({\n stateCode: tile.stateCode,\n stateName: tile.data.stateName as string,\n value: tile.value,\n data: tile.data,\n });\n }\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n\n cleanups.push(() => {\n el.removeEventListener('mouseenter', handleMouseEnter);\n el.removeEventListener('mousemove', handleMouseMove);\n el.removeEventListener('mouseleave', handleMouseLeave);\n el.removeEventListener('click', handleClick);\n });\n }\n\n return () => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n };\n }\n\n // ---------------------------------------------------------------------------\n // Render + update\n // ---------------------------------------------------------------------------\n\n function render(animate = false): void {\n // Cancel running animations\n if (animationCleanup) {\n animationCleanup();\n animationCleanup = null;\n }\n\n // Remove old SVG\n if (svgElement) {\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n svgElement.remove();\n }\n\n // Render new SVG\n const newSvg = renderTileMapSVG(currentLayout, { animate });\n container.appendChild(newSvg);\n svgElement = newSvg;\n\n // Wire interactions\n cleanupTooltipEvents = wireTooltipAndInteraction(newSvg, currentLayout);\n\n // Setup tooltips if enabled\n if (options?.tooltip !== false) {\n if (!tooltipManager) {\n tooltipManager = createTooltipManager(container);\n }\n }\n\n // Setup animation cleanup only when actually animating\n if (animate && currentLayout.animation?.enter) {\n animationCleanup = setupAnimationCleanup(newSvg, () => {\n // On animation complete, check if resize was pending\n if (pendingResize && !destroyed) {\n pendingResize = false;\n resize();\n }\n });\n }\n\n renderGen += 1;\n container.dataset.ocRenderGen = String(renderGen);\n\n // This render recompiled with the loaded webfonts; publish 'ready' now\n // rather than right after the fonts-ready resize() (which may have deferred\n // to pendingResize during the entrance animation).\n if (fontsReloadPending) {\n fontsReloadPending = false;\n container.dataset.ocFontsState = 'ready';\n }\n }\n\n function update(newSpec: TileMapSpec): void {\n currentSpec = newSpec;\n // A new spec can change theme.fonts.family; rebuild the measurer so layout\n // measures the font compile will actually render with.\n const nextFont = resolveEffectiveFont();\n if (nextFont !== fontFamily) {\n fontFamily = nextFont;\n measureText = createMeasureText(fontFamily);\n }\n currentLayout = compile();\n render();\n }\n\n function resize(): void {\n if (destroyed) return;\n\n // If animation is running, queue the resize for after it completes\n if (animationCleanup) {\n pendingResize = true;\n return;\n }\n\n currentLayout = compile();\n render();\n }\n\n // ---------------------------------------------------------------------------\n // Export\n // ---------------------------------------------------------------------------\n\n function exportChart(\n format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg',\n options_?: JPGExportOptions | SVGExportOptions,\n ): string | Promise<Blob> | Promise<string> {\n if (!svgElement) return '';\n\n switch (format) {\n case 'svg':\n return exportSVG(svgElement);\n case 'svg-with-fonts':\n return exportSVGWithFonts(svgElement);\n case 'png':\n return exportPNG(svgElement, options_ as JPGExportOptions);\n case 'jpg':\n return exportJPG(svgElement, options_ as JPGExportOptions);\n default:\n return '';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Destroy\n // ---------------------------------------------------------------------------\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n // Cancel running animations\n if (animationCleanup) {\n cancelAnimations(svgElement);\n animationCleanup();\n animationCleanup = null;\n }\n\n // Clean up events\n if (cleanupTooltipEvents) {\n cleanupTooltipEvents();\n cleanupTooltipEvents = null;\n }\n\n // Remove SVG\n if (svgElement) {\n svgElement.remove();\n svgElement = null;\n }\n\n // Unmount tooltip manager\n if (tooltipManager) {\n tooltipManager.destroy();\n tooltipManager = null;\n }\n\n // Disconnect resize observer\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n // Remove root classes\n container.classList.remove('oc-tilemap-root', 'oc-dark');\n }\n\n // ---------------------------------------------------------------------------\n // Initialize\n // ---------------------------------------------------------------------------\n\n // Root class was applied before the measurer was built (see above); dark\n // mode class still applies here.\n if (resolveDarkMode(options?.darkMode)) {\n container.classList.add('oc-dark');\n }\n\n // Initial compile and render (animate on first mount)\n currentLayout = compile();\n render(true);\n\n // Recompile once after webfonts load so labels aren't stuck measured\n // against fallback metrics on real devices.\n const fontsPending = scheduleFontReload(\n fontFamily,\n () => !destroyed,\n () => {\n // Mark the reload owed, then recompile. render() flips the attribute to\n // 'ready' once it actually recompiles, including the pendingResize replay\n // after the entrance animation finishes.\n fontsReloadPending = true;\n resize();\n },\n );\n container.dataset.ocFontsState = fontsPending ? 'pending' : 'ready';\n\n // Setup responsive resizing\n if (options?.responsive !== false) {\n disconnectResize = observeResize(container, () => {\n resize();\n });\n }\n\n return {\n update,\n resize,\n export: exportChart,\n destroy,\n get layout(): TileMapLayout {\n return currentLayout;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,SAAS,sBAAsB;;;ACP/B,SAAS,kBAAkB;AAG3B,IAAM,SAAS;AACf,IAAM,WAAW;AACjB,IAAM,YAAY;AAMlB,SAAS,iBAAiB,KAAyB;AACjD,SAAO,SAAS,gBAAgB,QAAQ,GAAG;AAC7C;AAEA,SAAS,SAAS,IAAgB,OAA8C;AAC9E,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,OAAG,aAAa,KAAK,OAAO,KAAK,CAAC;AAAA,EACpC;AACF;AAMA,SAAS,aAAa,QAAoB,QAA6B;AACrE,QAAM,IAAI,iBAAiB,GAAG;AAC9B,IAAE,aAAa,SAAS,WAAW;AAEnC,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,eAAe,OAAO,KAAK,IAAI,OAAO,KAAK;AAEjD,aAAW,OAAO,CAAC,SAAS,YAAY,UAAU,UAAU,QAAQ,GAAY;AAC9E,UAAM,KAAK,OAAO,GAAG;AACrB,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,QAAQ,YAAY,QAAQ,YAAY,QAAQ;AACjE,UAAM,OAAO,iBAAiB,MAAM;AAGpC,aAAS,MAAM;AAAA,MACb,GAAG,GAAG;AAAA,MACN,IAAI,WAAW,eAAe,GAAG,IAAI,GAAG,KAAK,WAAW,GAAG,MAAM,QAAQ;AAAA,IAC3E,CAAC;AACD,SAAK,aAAa,SAAS,MAAM,GAAG,EAAE;AACtC,SAAK,aAAa,eAAe,GAAG,MAAM,UAAU;AACpD,SAAK,aAAa,aAAa,OAAO,GAAG,MAAM,QAAQ,CAAC;AACxD,SAAK,aAAa,eAAe,OAAO,GAAG,MAAM,UAAU,CAAC;AAC5D,IAAC,KAA4C,MAAM,YAAY,QAAQ,GAAG,MAAM,IAAI;AACpF,SAAK,cAAc,GAAG;AACtB,MAAE,YAAY,IAAI;AAAA,EACpB;AAEA,SAAO,YAAY,CAAC;AACtB;AAMA,SAAS,gBAAgB,QAAoB,QAA6B;AACxE,MAAI,OAAO,QAAQ,IAAK;AAExB,QAAM,EAAE,OAAO,QAAQ,MAAM,IAAI;AACjC,QAAM,UAAU,MAAM,QAAQ;AAC9B,QAAM,YAAY,QAAQ;AAC1B,QAAM,aAAa,SAAS;AAC5B,QAAM,OAAO,MAAM,OAAO;AAE1B,QAAM,IAAI,iBAAiB,GAAG;AAC9B,IAAE,aAAa,QAAQ,SAAS;AAChC,IAAE,eAAe,UAAU,cAAc,SAAS;AAClD,IAAE,aAAa,UAAU,QAAQ;AACjC,IAAE,aAAa,OAAO,UAAU;AAChC,IAAE,aAAa,SAAS,eAAe;AAEvC,QAAM,OAAO,iBAAiB,MAAM;AACpC,WAAS,MAAM;AAAA,IACb,GAAG;AAAA,IACH,GAAG;AAAA,IACH,qBAAqB;AAAA,IACrB,eAAe;AAAA,IACf,eAAe,MAAM,MAAM;AAAA,IAC3B,aAAa;AAAA,IACb,gBAAgB;AAAA,EAClB,CAAC;AACD,EAAC,KAA4C,MAAM,YAAY,QAAQ,IAAI;AAE3E,WAAS,MAAM,EAAE,eAAe,KAAK,aAAa,GAAG,CAAC;AACtD,OAAK,cAAc;AACnB,IAAE,YAAY,IAAI;AAClB,SAAO,YAAY,CAAC;AACtB;AAMA,SAAS,WACP,QACA,MACA,WACM;AACN,QAAM,IAAI,iBAAiB,GAAG;AAC9B,IAAE,aAAa,SAAS,iBAAiB;AACzC,IAAE,aAAa,QAAQ,MAAM;AAE7B,aAAW,OAAO,MAAM;AACtB,UAAM,WAAW,iBAAiB,GAAG;AACrC,aAAS,aAAa,SAAS,gBAAgB;AAC/C,aAAS,aAAa,kBAAkB,OAAO,IAAI,KAAK,CAAC;AACzD,aAAS,aAAa,QAAQ,UAAU;AACxC,QAAI,IAAI,MAAM,OAAO;AACnB,eAAS,aAAa,cAAc,IAAI,KAAK,KAAK;AAAA,IACpD;AAEA,QAAI,WAAW,OAAO;AACpB,eAAS,aAAa,wBAAwB,OAAO,IAAI,cAAc,CAAC;AACxE,YAAM,QAAS,SAAgD;AAC/D,YAAM,YAAY,mBAAmB,OAAO,IAAI,cAAc,CAAC;AAC/D,YAAM,YAAY,kBAAkB,GAAG,IAAI,iBAAiB,EAAE,IAAI;AAAA,IACpE;AAGA,UAAM,UAAU,iBAAiB,MAAM;AACvC,aAAS,SAAS;AAAA,MAChB,GAAG,IAAI,MAAM;AAAA,MACb,GAAG,IAAI,MAAM;AAAA,MACb,qBAAqB;AAAA,MACrB,eAAe;AAAA,MACf,eAAe,IAAI,MAAM,MAAM;AAAA,MAC/B,aAAa,IAAI,MAAM,MAAM;AAAA,MAC7B,eAAe,IAAI,MAAM,MAAM;AAAA,IACjC,CAAC;AACD,IAAC,QAA+C,MAAM,YAAY,QAAQ,IAAI,MAAM,MAAM,IAAI;AAC9F,YAAQ,cAAc,IAAI,MAAM;AAChC,aAAS,YAAY,OAAO;AAG5B,QAAI,IAAI,UAAU,SAAS;AACzB,YAAM,QAAQ,iBAAiB,MAAM;AACrC,eAAS,OAAO;AAAA,QACd,GAAG,IAAI,SAAS;AAAA,QAChB,GAAG,IAAI,SAAS;AAAA,QAChB,qBAAqB;AAAA,QACrB,eAAe;AAAA,QACf,eAAe,IAAI,SAAS,MAAM;AAAA,QAClC,aAAa,IAAI,SAAS,MAAM;AAAA,QAChC,eAAe,IAAI,SAAS,MAAM;AAAA,MACpC,CAAC;AACD,MAAC,MAA6C,MAAM;AAAA,QAClD;AAAA,QACA,IAAI,SAAS,MAAM;AAAA,MACrB;AACA,YAAM,cAAc,IAAI,SAAS;AACjC,eAAS,YAAY,KAAK;AAAA,IAC5B;AAGA,UAAM,YAAY,iBAAiB,MAAM;AACzC,aAAS,WAAW;AAAA,MAClB,GAAG,IAAI,MAAM;AAAA,MACb,GAAG,IAAI,MAAM;AAAA,MACb,OAAO,IAAI,MAAM;AAAA,MACjB,QAAQ,IAAI,MAAM;AAAA,MAClB,IAAI,IAAI,MAAM;AAAA,MACd,MAAM;AAAA,MACN,gBAAgB;AAAA,IAClB,CAAC;AACD,cAAU,aAAa,SAAS,kBAAkB;AAClD,aAAS,YAAY,SAAS;AAG9B,UAAM,UAAU,iBAAiB,MAAM;AACvC,aAAS,SAAS;AAAA,MAChB,GAAG,IAAI,IAAI;AAAA,MACX,GAAG,IAAI,IAAI;AAAA,MACX,OAAO,IAAI,IAAI;AAAA,MACf,QAAQ,IAAI,IAAI;AAAA,MAChB,IAAI,IAAI,IAAI;AAAA,MACZ,MAAM,IAAI,IAAI;AAAA,IAChB,CAAC;AACD,YAAQ,aAAa,SAAS,gBAAgB;AAC9C,aAAS,YAAY,OAAO;AAG5B,UAAM,UAAU,iBAAiB,MAAM;AACvC,aAAS,SAAS;AAAA,MAChB,GAAG,IAAI,WAAW;AAAA,MAClB,GAAG,IAAI,WAAW;AAAA,MAClB,qBAAqB;AAAA,MACrB,eAAe;AAAA,MACf,eAAe,IAAI,WAAW,MAAM;AAAA,MACpC,aAAa,IAAI,WAAW,MAAM;AAAA,MAClC,eAAe,IAAI,WAAW,MAAM;AAAA,IACtC,CAAC;AACD,IAAC,QAA+C,MAAM;AAAA,MACpD;AAAA,MACA,IAAI,WAAW,MAAM;AAAA,IACvB;AACA,YAAQ,cAAc,IAAI,WAAW;AACrC,aAAS,YAAY,OAAO;AAE5B,MAAE,YAAY,QAAQ;AAAA,EACxB;AAEA,SAAO,YAAY,CAAC;AACtB;AAMO,SAAS,iBACd,QACA,MACe;AACf,QAAM,EAAE,OAAO,QAAQ,MAAM,MAAM,WAAW,UAAU,IAAI;AAC5D,QAAM,UAAU,MAAM,WAAW,CAAC,CAAC,WAAW;AAE9C,QAAM,MAAM,iBAAiB,KAAK;AAClC,MAAI,aAAa,WAAW,OAAO,KAAK,IAAI,MAAM,EAAE;AACpD,MAAI,aAAa,QAAQ,MAAM;AAG/B,MAAI,MAAM,SAAS,GAAG,MAAM;AAC5B,MAAI,KAAK,SAAS;AAChB,QAAI,aAAa,cAAc,KAAK,OAAO;AAAA,EAC7C;AAEA,QAAM,UAAU,UAAU,0BAA0B;AACpD,MAAI,aAAa,SAAS,OAAO;AAEjC,MAAI,WAAW,WAAW,OAAO;AAC/B,uBAAmB,KAAK,EAAE,UAAU,UAAU,MAAM,UAAU,SAAS,GAAG,CAAC;AAAA,EAC7E;AAEA,eAAa,KAAK,MAAM;AACxB,aAAW,KAAK,MAAM,UAAU,YAAY,MAAS;AAErD,MAAI,WAAW;AACb,oBAAgB,KAAK,MAAM;AAAA,EAC7B;AAEA,SAAO;AACT;;;AD/LO,SAAS,cACd,WACA,MACA,SACiB;AACjB,MAAI,cAAc;AAClB,MAAI;AACJ,MAAI,YAAY;AAEhB,MAAI,aAAmC;AACvC,MAAI,iBAAwC;AAC5C,MAAI,uBAA4C;AAChD,MAAI,mBAAwC;AAC5C,MAAI,mBAAwC;AAC5C,MAAI,gBAAgB;AAMpB,MAAI,qBAAqB;AAIzB,YAAU,UAAU,IAAI,iBAAiB;AAMzC,WAAS,uBAA+B;AACtC,WACE,SAAS,OAAO,OAAO,UACvB,YAAY,OAAO,OAAO,UAC1B,kBAAkB,SAAS;AAAA,EAE/B;AACA,MAAI,aAAa,qBAAqB;AACtC,MAAI,cAAc,kBAAkB,UAAU;AAC9C,MAAI,YAAY;AAEhB,WAAS,yBAA4D;AACnE,UAAM,OAAO,UAAU,sBAAsB;AAC7C,WAAO;AAAA,MACL,OAAO,KAAK,IAAI,KAAK,SAAS,KAAK,GAAG;AAAA,MACtC,QAAQ,KAAK,IAAI,KAAK,UAAU,KAAK,GAAG;AAAA,IAC1C;AAAA,EACF;AAEA,WAAS,UAAyB;AAChC,UAAM,EAAE,OAAO,OAAO,IAAI,uBAAuB;AACjD,UAAM,WAAW,gBAAgB,SAAS,QAAQ;AAElD,UAAM,cAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB;AAAA,MACA,WAAW,SAAS;AAAA,MACpB;AAAA,IACF;AAEA,UAAM,SAAS,eAAe,aAAa,WAAW;AAGtD,QAAI,OAAO,KAAK,SAAS,GAAG;AAC1B,YAAM,UAAU,OAAO,KAAK,OAAO,KAAK,SAAS,CAAC;AAClD,YAAM,gBACJ,QAAQ,IAAI,QAAQ,SAAS,OAAO,OAAO,eAAe,OAAO,MAAM,QAAQ;AACjF,UAAI,gBAAgB,OAAO,QAAQ;AACjC,eAAO,SAAS;AAChB,eAAO,KAAK,SACV,gBAAgB,OAAO,KAAK,IAAI,OAAO,OAAO,eAAe,OAAO,MAAM,QAAQ;AAAA,MACtF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,WAAS,0BAA0B,KAAoB,QAAmC;AACxF,UAAM,WAA8B,CAAC;AAErC,UAAM,cAAc,IAAI,iBAAiB,iBAAiB;AAC1D,eAAW,MAAM,aAAa;AAC5B,YAAM,WAAW,GAAG,aAAa,gBAAgB;AACjD,UAAI,aAAa,KAAM;AAEvB,YAAM,UAAU,OAAO,mBAAmB,IAAI,QAAQ;AACtD,YAAM,MAAM,OAAO,KAAK,OAAO,QAAQ,CAAC;AAExC,YAAM,mBAAmB,CAAC,MAAa;AACrC,cAAM,aAAa;AACnB,YAAI,WAAW,kBAAkB,SAAS,YAAY,OAAO;AAC3D,gBAAM,UAAU,IAAI,sBAAsB;AAC1C,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,yBAAe,KAAK,SAAS,GAAG,CAAC;AAAA,QACnC;AACA,YAAI,KAAK;AACP,mBAAS,aAAa;AAAA,YACpB,OAAO,IAAI,MAAM;AAAA,YACjB,OAAO,IAAI;AAAA,YACX,MAAM,IAAI;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAEA,YAAM,kBAAkB,CAAC,MAAa;AACpC,YAAI,WAAW,kBAAkB,SAAS,YAAY,OAAO;AAC3D,gBAAM,aAAa;AACnB,gBAAM,UAAU,IAAI,sBAAsB;AAC1C,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,yBAAe,KAAK,SAAS,GAAG,CAAC;AAAA,QACnC;AAAA,MACF;AAEA,YAAM,mBAAmB,MAAM;AAC7B,wBAAgB,KAAK;AACrB,iBAAS,aAAa,IAAI;AAAA,MAC5B;AAEA,YAAM,cAAc,MAAM;AACxB,YAAI,KAAK;AACP,mBAAS,aAAa;AAAA,YACpB,OAAO,IAAI,MAAM;AAAA,YACjB,OAAO,IAAI;AAAA,YACX,MAAM,IAAI;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAEA,SAAG,iBAAiB,cAAc,gBAAgB;AAClD,SAAG,iBAAiB,aAAa,eAAe;AAChD,SAAG,iBAAiB,cAAc,gBAAgB;AAClD,SAAG,iBAAiB,SAAS,WAAW;AAExC,eAAS,KAAK,MAAM;AAClB,WAAG,oBAAoB,cAAc,gBAAgB;AACrD,WAAG,oBAAoB,aAAa,eAAe;AACnD,WAAG,oBAAoB,cAAc,gBAAgB;AACrD,WAAG,oBAAoB,SAAS,WAAW;AAAA,MAC7C,CAAC;AAAA,IACH;AAEA,WAAO,MAAM;AACX,iBAAW,WAAW,SAAU,SAAQ;AAAA,IAC1C;AAAA,EACF;AAEA,WAAS,OAAO,UAAU,OAAa;AACrC,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AAEA,QAAI,YAAY;AACd,UAAI,sBAAsB;AACxB,6BAAqB;AACrB,+BAAuB;AAAA,MACzB;AACA,iBAAW,OAAO;AAAA,IACpB;AAEA,UAAM,SAAS,iBAAiB,eAAe,EAAE,QAAQ,CAAC;AAC1D,cAAU,YAAY,MAAM;AAC5B,iBAAa;AAEb,2BAAuB,0BAA0B,QAAQ,aAAa;AAEtE,QAAI,SAAS,YAAY,OAAO;AAC9B,UAAI,CAAC,gBAAgB;AACnB,yBAAiB,qBAAqB,SAAS;AAAA,MACjD;AAAA,IACF;AAEA,QAAI,cAAc,WAAW,OAAO;AAClC,yBAAmB,sBAAsB,QAAQ,MAAM;AAGrD,2BAAmB;AACnB,YAAI,iBAAiB,CAAC,WAAW;AAC/B,0BAAgB;AAChB,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAEA,iBAAa;AACb,cAAU,QAAQ,cAAc,OAAO,SAAS;AAKhD,QAAI,oBAAoB;AACtB,2BAAqB;AACrB,gBAAU,QAAQ,eAAe;AAAA,IACnC;AAAA,EACF;AAEA,WAAS,OAAO,SAA4B;AAC1C,kBAAc;AAGd,UAAM,WAAW,qBAAqB;AACtC,QAAI,aAAa,YAAY;AAC3B,mBAAa;AACb,oBAAc,kBAAkB,UAAU;AAAA,IAC5C;AACA,oBAAgB,QAAQ;AACxB,WAAO;AAAA,EACT;AAEA,WAAS,SAAe;AACtB,QAAI,UAAW;AACf,QAAI,kBAAkB;AACpB,sBAAgB;AAChB;AAAA,IACF;AACA,oBAAgB,QAAQ;AACxB,WAAO;AAAA,EACT;AAEA,WAAS,YACP,QACA,UAC0C;AAC1C,QAAI,CAAC,WAAY,QAAO;AACxB,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,UAAU,UAAU;AAAA,MAC7B,KAAK;AACH,eAAO,mBAAmB,UAAU;AAAA,MACtC,KAAK;AACH,eAAO,UAAU,YAAY,QAA4B;AAAA,MAC3D,KAAK;AACH,eAAO,UAAU,YAAY,QAA4B;AAAA,MAC3D;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,QAAI,UAAW;AACf,gBAAY;AAEZ,QAAI,kBAAkB;AACpB,uBAAiB,UAAU;AAC3B,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AAEA,QAAI,sBAAsB;AACxB,2BAAqB;AACrB,6BAAuB;AAAA,IACzB;AAEA,QAAI,YAAY;AACd,iBAAW,OAAO;AAClB,mBAAa;AAAA,IACf;AAEA,QAAI,gBAAgB;AAClB,qBAAe,QAAQ;AACvB,uBAAiB;AAAA,IACnB;AAEA,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AAEA,cAAU,UAAU,OAAO,mBAAmB,SAAS;AAAA,EACzD;AAGA,MAAI,gBAAgB,SAAS,QAAQ,GAAG;AACtC,cAAU,UAAU,IAAI,SAAS;AAAA,EACnC;AAEA,kBAAgB,QAAQ;AACxB,SAAO,IAAI;AAIX,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,MAAM,CAAC;AAAA,IACP,MAAM;AAIJ,2BAAqB;AACrB,aAAO;AAAA,IACT;AAAA,EACF;AACA,YAAU,QAAQ,eAAe,eAAe,YAAY;AAE5D,MAAI,SAAS,eAAe,OAAO;AACjC,uBAAmB,cAAc,WAAW,MAAM,OAAO,CAAC;AAAA,EAC5D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,IAAI,SAAwB;AAC1B,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AElSO,SAAS,gBAAgB,MAAoD;AAClF,QAAM,WAAY,KAAuC;AACzD,QAAM,SACJ,OAAO,aAAa,YAAY,OAAO,SAAS,WAAW,WACvD,EAAE,GAAG,SAAS,OAAO,IACrB;AACN,SAAO;AAAA,IACL,GAAI;AAAA,IACJ,WAAW,EAAE,OAAO,OAAO,OAAO;AAAA,EACpC;AACF;AAOA,SAAS,eACP,MACA,OACA,QACA,UACA,aACA,UAKA;AACA,QAAM,YAAY,SAAS,cAAc,KAAK;AAE9C,YAAU,MAAM,UAAU,+CAA+C,KAAK,aAAa,MAAM;AACjG,WAAS,KAAK,YAAY,SAAS;AAInC,QAAM,cAAc,gBAAgB,IAAI;AACxC,MAAI;AACJ,MAAI;AACF,eAAW,YAAY,WAAW,aAAa;AAAA,MAC7C;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,YAAY;AAAA,IACd,CAAC;AAAA,EACH,SAAS,KAAK;AAEZ,cAAU,OAAO;AACjB,UAAM;AAAA,EACR;AAEA,QAAM,MAAM,UAAU,cAAc,KAAK;AACzC,MAAI,CAAC,KAAK;AACR,aAAS,QAAQ;AACjB,cAAU,OAAO;AACjB,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,SAAO,EAAE,UAAU,KAAK,UAAU;AACpC;AAWA,eAAsB,mBACpB,OACA,SACe;AACf,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,OAAO,QAAQ;AAAA,EAChC,SAAS,KAAK;AACZ,QAAI,iBAAiB,GAAG,GAAG;AACzB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,IAAI,MAAM,gDAAgD;AAAA,MAC9D,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,QAAM,EAAE,YAAY,UAAU,aAAa,IAAI;AAE/C,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,aAAa,KAAK,MAAM,MAAO,GAAG;AACxC,QAAM,EAAE,OAAO,OAAO,IAAI;AAC1B,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,cAAc,QAAQ;AAC5B,QAAM,cAAc,QAAQ,cAAc;AAC1C,QAAM,SAAS,cAAc,QAAQ,IAAI;AAEzC,QAAM,EAAE,UAAU,KAAK,UAAU,IAAI;AAAA,IACnC,MAAM,CAAC;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,EACV;AAEA,MAAI;AACF,UAAM,IAAI,iBAAiB,GAAG,EAAE,SAAS;AACzC,UAAM,IAAI,iBAAiB,GAAG,EAAE,UAAU;AAC1C,UAAM,QAAQ,MAAiC;AAC7C,YAAM,SAAS,SAAS;AACxB,aAAO,UAAU,WAAW,SAAU,OAAO,QAA0B;AAAA,IACzE;AAGA,UAAM,UACJ,QAAQ,mBAAmB,sBAAsB,KAAK,MAAM,GAAG,OAAO,UAAU;AAClF,UAAM,iBAAiB,CAAC,KAA+B,OAAgC;AACrF,UAAI,YAAY;AAChB,UAAI,SAAS,GAAG,GAAG,GAAG,OAAO,GAAG,MAAM;AAAA,IACxC;AAMA,UAAM,iBAAiB,YAAiC;AACtD,YAAMA,QAAO,UAAU,cAAc,KAAK;AAC1C,UAAI,CAACA,MAAM,OAAM,IAAI,MAAM,oDAAoD;AAC/E,4BAAsBA,OAAM,MAAM,CAAC;AACnC,UAAI,YAAa,OAAM,WAAWA,KAAI;AACtC,aAAOA;AAAA,IACT;AAGA,UAAM,OAAO,OAAOA,UAAiD;AACnE,YAAM,YAAY,oBAAoB,UAAUA,KAAI,GAAG,GAAG,CAAC;AAC3D,aAAO,qBAAqB,WAAW,GAAG,GAAG,KAAK,cAAc;AAAA,IAClE;AAIA,UAAM,SAAgE,CAAC;AACvE,QAAI,OAAO,MAAM,eAAe;AAChC,WAAO,KAAK,EAAE,QAAQ,MAAM,KAAK,IAAI,GAAG,SAAS,QAAQ,CAAC;AAE1D,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AAGrC,YAAM,WAAW,gBAAgB,MAAM,CAAC,CAAC;AACzC,YAAM,SAAS,SAAS,kBAAkB,QAAQ;AAElD,aAAO,MAAM,eAAe;AAE5B,UAAI,QAAQ;AACV,cAAM,UAAU,OAAO;AAGvB,iBAAS,IAAI,YAAY,IAAI,SAAS,KAAK,YAAY;AACrD,iBAAO,KAAK,CAAC;AACb,iBAAO,KAAK,EAAE,QAAQ,MAAM,KAAK,IAAI,GAAG,SAAS,WAAW,CAAC;AAAA,QAC/D;AAEA,eAAO,KAAK,OAAO;AACnB,eAAO,OAAO;AAAA,MAChB;AACA,aAAO,KAAK,EAAE,QAAQ,MAAM,KAAK,IAAI,GAAG,SAAS,QAAQ,CAAC;AAAA,IAC5D;AAIA,UAAM,gBAAgB,kBAAkB,OAAO,OAAO,SAAS,CAAC,EAAE,QAAQ,QAAQ;AAElF,UAAM,MAAM,WAAW;AACvB,WAAO,QAAQ,CAAC,EAAE,QAAQ,QAAQ,GAAG,MAAM;AACzC,YAAM,QAAQ,aAAa,eAAe,MAAM,GAAG,aAAa;AAChE,UAAI,WAAW,OAAO,OAAO,OAAO,OAAO,QAAQ;AAAA,QACjD,SAAS;AAAA,QACT,OAAO;AAAA;AAAA,QAEP,GAAI,MAAM,IAAI,EAAE,OAAO,IAAI,CAAC;AAAA,MAC9B,CAAC;AAAA,IACH,CAAC;AAED,QAAI,OAAO;AACX,WAAO,IAAI,KAAK,CAAC,IAAI,MAAM,CAAa,GAAG,EAAE,MAAM,YAAY,CAAC;AAAA,EAClE,UAAE;AACA,aAAS,QAAQ;AACjB,cAAU,OAAO;AAAA,EACnB;AACF;;;AC7QO,SAAS,yBAAiC;AAC/C,SAAO,IAAI,OAAO,IAAI,IAAI,0BAA0B,YAAY,GAAG,GAAG,EAAE,MAAM,SAAS,CAAC;AAC1F;;;ACUA,SAAS,kBAAkB,oBAAoB;;;ACP/C,SAAS,uBAAuB;;;ACJzB,IAAM,gBAAN,MAAM,eAAc;AAAA,EACzB,YACW,GACA,GACA,GACT;AAHS;AACA;AACA;AAAA,EACR;AAAA;AAAA,EAGH,cAAc,IAAY,IAAsC;AAC9D,WAAO;AAAA,MACL,IAAI,KAAK,KAAK,KAAK,KAAK;AAAA,MACxB,IAAI,KAAK,KAAK,KAAK,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA,EAGA,cAAc,IAAY,IAAsC;AAC9D,WAAO;AAAA,MACL,GAAG,KAAK,KAAK,IAAI,KAAK;AAAA,MACtB,GAAG,KAAK,KAAK,IAAI,KAAK;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SAAiB,QAAgB,QAA+B;AAIrE,UAAM,MAAM,SAAS,KAAK,KAAK,KAAK;AACpC,UAAM,MAAM,SAAS,KAAK,KAAK,KAAK;AACpC,WAAO,IAAI,eAAc,SAAS,KAAK,SAAS,SAAS,KAAK,SAAS,OAAO;AAAA,EAChF;AAAA;AAAA,EAGA,IAAI,IAAY,IAA2B;AACzC,WAAO,IAAI,eAAc,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,UACL,OACA,SACA,SACA,UAAkB,IAClB,MACqD;AACrD,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO,EAAE,WAAW,eAAc,SAAS,GAAG,eAAe,QAAQ;AAAA,IACvE;AAKA,UAAM,WAAW,KAAK,IAAI,KAAK,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,UAAU,GAAG;AAEzE,QAAI,OAAO;AACX,QAAI,OAAO;AACX,QAAI,OAAO;AACX,QAAI,OAAO;AAEX,eAAW,KAAK,OAAO;AACrB,YAAM,IAAI,EAAE;AACZ,UAAI,EAAE,IAAI,IAAI,KAAM,QAAO,EAAE,IAAI;AACjC,UAAI,EAAE,IAAI,IAAI,KAAM,QAAO,EAAE,IAAI;AACjC,UAAI,EAAE,IAAI,IAAI,KAAM,QAAO,EAAE,IAAI;AACjC,UAAI,EAAE,IAAI,IAAI,KAAM,QAAO,EAAE,IAAI;AAAA,IACnC;AAEA,QAAI,SAAS,OAAO;AACpB,QAAI,SAAS,OAAO;AAEpB,QAAI,WAAW,KAAK,WAAW,GAAG;AAEhC,aAAO;AAAA,QACL,WAAW,IAAI;AAAA,UACb,UAAU,IAAI;AAAA,UACd,YAAY,UAAU,YAAY,IAAI;AAAA,UACtC;AAAA,QACF;AAAA,QACA,eAAe,UAAU,IAAI;AAAA,MAC/B;AAAA,IACF;AAYA,QAAI,MAAM,WAAW,SAAS,MAAM,SAAS,IAAI;AAC/C,YAAM,SAAS,IAAI,KAAK,KAAK,MAAM,MAAM,IAAI;AAC7C,YAAMC,OAAM,OAAO,QAAQ;AAC3B,YAAMC,OAAM,OAAO,QAAQ;AAC3B,gBAAU;AACV,gBAAU;AACV,aAAOD,MAAK,SAAS;AACrB,aAAOA,MAAK,SAAS;AACrB,aAAOC,MAAK,SAAS;AACrB,aAAOA,MAAK,SAAS;AAAA,IACvB;AAEA,UAAM,SAAS,UAAU,UAAU;AACnC,UAAM,SAAS,UAAU,WAAW,UAAU;AAE9C,UAAM,IAAI,KAAK,IAAI,GAAG,SAAS,QAAQ,SAAS,MAAM;AAGtD,UAAM,MAAM,OAAO,QAAQ;AAC3B,UAAM,MAAM,OAAO,QAAQ;AAC3B,UAAM,KAAK,UAAU,IAAI,KAAK;AAC9B,UAAM,KAAK,YAAY,UAAU,YAAY,IAAI,KAAK;AAGtD,UAAM,gBAAgB,SAAS,IAAI,UAAU,IAAI;AAEjD,WAAO;AAAA,MACL,WAAW,IAAI,eAAc,IAAI,IAAI,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,WAA0B;AAC/B,WAAO,IAAI,eAAc,GAAG,GAAG,CAAC;AAAA,EAClC;AACF;;;ADhIA,IAAM,QAAQ;AACd,IAAM,QAAQ;AAGd,IAAM,cAAc;AACpB,IAAM,cAAc;AACpB,IAAM,aAAa;AAkBZ,SAAS,gBAAgB,GAAkB,UAA8B;AAC9E,QAAM,MAAM,SAAS,QAAQ,IAAI,EAAE,KAAK,EAAE;AAC1C,QAAM,MAAM,SAAS,SAAS,IAAI,EAAE,KAAK,EAAE;AAC3C,QAAM,QAAQ,SAAS,QAAQ,EAAE;AACjC,SAAO,CAAC,IAAI,IAAI,KAAK;AACvB;AAGO,SAAS,gBAAgB,MAAgB,UAAmC;AACjF,QAAM,CAAC,IAAI,IAAI,KAAK,IAAI;AACxB,QAAM,IAAI,OAAO,SAAS,QAAQ,KAAK;AACvC,QAAM,IAAI,SAAS,QAAQ,IAAI,KAAK;AACpC,QAAM,IAAI,SAAS,SAAS,IAAI,KAAK;AACrC,SAAO,IAAI,cAAc,GAAG,GAAG,CAAC;AAClC;AAGO,SAAS,OAAO,GAAmB;AACxC,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG,QAAO;AAC1C,SAAO,KAAK,IAAI,OAAO,KAAK,IAAI,OAAO,CAAC,CAAC;AAC3C;AAsCO,SAAS,mBAAmB,QAA4C;AAC7E,QAAM,EAAE,MAAM,UAAU,OAAO,QAAQ,KAAK,IAAI;AAChD,QAAM,OAAO,YAAY,MAAM,QAAQ,QAAQ;AAC/C,QAAM,aAAa,OAAO,OAAO,OAAO;AAExC,QAAM,WAAW,gBAAgB,MAAM,QAAQ;AAE/C,WAAS,aAAuB;AAC9B,UAAM,KAAK,aAAc,OAAO,GAA2B,IAAK,OAAO;AACvE,WAAO,gBAAgB,IAAI,QAAQ;AAAA,EACrC;AAGA,MAAI,SAAS,gBAAgB,UAAU,WAAW,CAAC;AACnD,QAAM,mBAAmB,gBAAgB,MAAM,UAAU,OAAO,QAAQ;AAExE,MAAI,YAA2B;AAC/B,MAAI,WAAW;AAEf,WAAS,QAAQ,GAAiB;AAChC,QAAI,WAAY,UAAS,gBAAgB,UAAU,WAAW,CAAC;AAC/D,UAAM,OAAO,OAAO,CAAC;AACrB,QAAI,KAAK,KAAK,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC,GAAG;AACzC,YAAM,gBAAgB,WAAW,GAAG,QAAQ,CAAC;AAC7C;AAAA,IACF;AACA,UAAM,gBAAgB,MAAM,QAAQ,CAAC;AAAA,EACvC;AAEA,SAAO;AAAA,IACL,KAAK,KAAsB;AACzB,UAAI,SAAU,QAAO;AACrB,UAAI,cAAc,KAAM,aAAY;AACpC,YAAM,MAAM,oBAAoB,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,aAAa,gBAAgB;AACxF,cAAQ,KAAK,GAAG,CAAC;AACjB,UAAI,OAAO,GAAG;AACZ,mBAAW;AACX,iBAAS;AACT,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAAA,IACA,SAAe;AACb,UAAI,SAAU;AACd,iBAAW;AACX,cAAQ,CAAC;AACT,eAAS;AAAA,IACX;AAAA,IACA,SAAe;AACb,iBAAW;AAAA,IACb;AAAA,EACF;AACF;AAkBO,SAAS,mBAAmB,QAA4C;AAC7E,MAAI,WAAW;AACf,SAAO;AAAA,IACL,OAAgB;AACd,UAAI,SAAU,QAAO;AACrB,UAAI,CAAC,OAAO,SAAS,GAAG;AACtB,mBAAW;AACX,eAAO;AAAA,MACT;AACA,aAAO,MAAM,OAAO,OAAO,CAAC;AAC5B,aAAO;AAAA,IACT;AAAA,IACA,SAAe;AACb,iBAAW;AAAA,IACb;AAAA,IACA,SAAe;AACb,iBAAW;AAAA,IACb;AAAA,EACF;AACF;AAOO,SAAS,gBACd,UACA,gBACQ;AACR,MAAI,OAAO,aAAa,SAAU,QAAO,KAAK,IAAI,GAAG,QAAQ;AAC7D,QAAM,SAAS,iBAAiB;AAChC,SAAO,KAAK,IAAI,aAAa,KAAK,IAAI,aAAa,KAAK,IAAI,GAAG,MAAM,CAAC,CAAC;AACzE;;;AE/LA,SAAS,iBAAiB,uBAAuB;;;ACM1C,SAAS,kBACd,SACA,OACA,OACA,UAAU,GACF;AACR,QAAM,IAAI,WAAW,IAAI,IAAI,WAAW,IAAI,IAAI;AAChD,QAAM,QAAQ,QAAQ,IAAK,QAAQ,QAAS,MAAM;AAClD,QAAM,SAAS,IAAI,SAAS;AAC5B,QAAM,UAAU,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI;AAElD,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC;AACzC,SAAO,KAAK,MAAM,UAAU,CAAC,IAAI;AACnC;AAMO,IAAM,6BAA6B;AAOnC,IAAM,oBAAoB;AAIjC,SAAS,SAAS,OAA6C;AAC7D,MAAI,KAAK;AACT,MAAI,KAAK;AACT,aAAW,KAAK,OAAO;AACrB,UAAM,EAAE;AACR,UAAM,EAAE;AAAA,EACV;AACA,QAAM,MAAM;AACZ,QAAM,MAAM;AACZ,SAAO,EAAE,IAAI,GAAG;AAClB;AAOA,SAAS,OAAO,IAAoB;AAClC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK;AAClC,SAAK,GAAG,WAAW,CAAC;AACpB,QAAI,KAAK,KAAK,GAAG,QAAU;AAAA,EAC7B;AACA,SAAO,MAAM;AACf;AAUO,SAAS,cAAc,OAAsC;AAClE,QAAM,OAAO,oBAAI,IAAoB;AACrC,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM;AACvC,UAAM,KAAK,OAAO,EAAE,EAAE;AACtB,UAAM,KAAK,OAAO,EAAE,EAAE;AAEtB,WAAO,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI;AAAA,EAC1D,CAAC;AACD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,MAAK,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC;AAChE,SAAO;AACT;AAQO,SAAS,gBACd,OACA,OAAe,mBACwB;AACvC,QAAM,UAAU,oBAAI,IAAsC;AAC1D,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,EAAE,IAAI,GAAG,IAAI,SAAS,KAAK;AACjC,aAAW,KAAK,OAAO;AACrB,UAAM,KAAK,EAAE,IAAI;AACjB,UAAM,KAAK,EAAE,IAAI;AACjB,UAAM,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;AACvC,QAAI,MAAM,MAAM;AACd,cAAQ,IAAI,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC;AAAA,IACtC,OAAO;AACL,cAAQ,IAAI,EAAE,IAAI,EAAE,GAAI,KAAK,MAAO,MAAM,GAAI,KAAK,MAAO,KAAK,CAAC;AAAA,IAClE;AAAA,EACF;AACA,SAAO;AACT;AAMO,SAAS,SAAS,GAAmB;AAC1C,MAAI,KAAK,EAAG,QAAO;AACnB,MAAI,KAAK,EAAG,QAAO;AACnB,QAAM,KAAK;AACX,QAAM,KAAK,KAAK;AAChB,QAAM,IAAI,IAAI;AACd,SAAO,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;AACvC;AAMO,SAAS,SAAS,GAAmB;AAC1C,QAAM,IAAI,IAAI;AACd,SAAO,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI;AACnC;AAMO,SAAS,YAAY,GAAmB;AAC7C,QAAM,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI;AACzC,SAAO,IAAI;AACb;;;ADjIA,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AACvB,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAC7B,IAAM,yBAAyB;AAE/B,IAAM,sBAAsB;AAE5B,IAAM,sBAAsB;AAU5B,SAAS,SAAS,MAAsB,OAAiC;AACvE,MAAI,CAAC,MAAM,UAAW,QAAO;AAC7B,SAAO,MAAM,UAAU,IAAI,KAAK,MAAM,KAAK,MAAM,UAAU,IAAI,KAAK,MAAM,IACtE,cACA;AACN;AAkBA,SAAS,SACP,MACA,OACA,WACW;AACX,MAAI,CAAC,MAAM,UAAW,QAAO;AAC7B,MAAI,WAAW,IAAI,KAAK,EAAE,EAAG,QAAO;AACpC,SAAO,MAAM,UAAU,IAAI,KAAK,EAAE,IAAI,cAAc;AACtD;AAOA,SAAS,cAAc,MAAiB,YAA4B;AAClE,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,aAAa;AAAA,IACtB;AACE,aAAO;AAAA,EACX;AACF;AAGA,SAAS,cAAc,MAAiB,YAA4B;AAClE,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,KAAK,GAAW,GAAW,GAAmB;AACrD,SAAO,KAAK,IAAI,KAAK;AACvB;AA0BA,IAAM,aAAa,EAAE,GAAG,GAAG,GAAG,EAAE;AAEhC,SAAS,mBACP,UACA,OACgB;AAChB,QAAM,IAAI,SAAS;AACnB,QAAM,EAAE,SAAS,OAAO,QAAQ,IAAI;AACpC,QAAM,SAAS,CAAC,SAAyB,OAAO,IAAI,KAAK,EAAE,KAAK,KAAK;AACrE,QAAM,QAAQ,CAAC,SAA0B,UAAU,kBAAkB,GAAG,OAAO,IAAI,GAAG,KAAK,IAAI;AAC/F,QAAM,YAAY,KAAK,IAAI,IAAI,IAAI,OAAO,GAAG;AAC7C,QAAM,aAAa,MAAM,MAAM;AAC/B,SAAO;AAAA,IACL,WAAW,CAAC,SAAU,UAAU,SAAS,MAAM,IAAI,CAAC,IAAI;AAAA,IACxD,WAAW,CAAC,SAAU,UAAU,SAAS,MAAM,IAAI,CAAC,IAAI;AAAA,IACxD,OAAO,CAAC,OAAO;AACb,UAAI,CAAC,WAAW,CAAC,WAAW,CAAC,MAAO,QAAO;AAC3C,YAAM,MAAM,QAAQ,IAAI,EAAE;AAC1B,UAAI,CAAC,IAAK,QAAO;AACjB,YAAM,IAAI,YAAY,kBAAkB,GAAG,MAAM,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;AACrE,aAAO,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,GAAG,GAAG,IAAI,IAAI,EAAE,IAAI;AAAA,IAClD;AAAA,IACA;AAAA,IACA,YAAY;AAAA,EACd;AACF;AAOA,SAAS,YACP,eACA,iBACA,cACe;AACf,QAAM,YAAY,kBAAkB,QAAQ,gBAAgB,OAAO;AACnE,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,WAAW;AACb,UAAM,SAAS,oBAAI,IAAY;AAC/B,QAAI,cAAe,QAAO,IAAI,aAAa;AAC3C,eAAW,MAAM,gBAAiB,QAAO,IAAI,EAAE;AAC/C,eAAW,MAAM,QAAQ;AACvB,gBAAU,IAAI,EAAE;AAChB,YAAM,YAAY,aAAa,IAAI,EAAE;AACrC,UAAI,UAAW,YAAW,OAAO,UAAW,WAAU,IAAI,GAAG;AAAA,IAC/D;AAAA,EACF;AACA,SAAO,EAAE,WAAW,WAAW,eAAe,MAAM,UAAU,gBAAgB;AAChF;AACA,IAAM,sBAAsB;AAC5B,IAAM,yBAAyB;AAC/B,IAAM,aAAa;AACnB,IAAM,cAAc;AACpB,IAAM,SAAS,KAAK,KAAK;AAGzB,IAAM,oBAAoB;AAWnB,SAAS,eAAe,MAAsB;AACnD,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,OAAO,GAAG,CAAC;AACrD,SAAO,IAAI;AACb;AAGO,SAAS,YACd,aACA,cACA,WACA,SAAiB,aAC2C;AAC5D,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI;AACpB,SAAO;AAAA,IACL,OAAO,CAAC,IAAI,UAAU;AAAA,IACtB,OAAO,CAAC,IAAI,UAAU;AAAA,IACtB,OAAO,cAAc,IAAI,UAAU;AAAA,IACnC,OAAO,eAAe,IAAI,UAAU;AAAA,EACtC;AACF;AAGA,SAAS,WACP,MACA,MACS;AACT,SACE,KAAK,IAAI,KAAK,UAAU,KAAK,QAC7B,KAAK,IAAI,KAAK,UAAU,KAAK,QAC7B,KAAK,IAAI,KAAK,UAAU,KAAK,QAC7B,KAAK,IAAI,KAAK,UAAU,KAAK;AAEjC;AAGA,SAAS,WACP,MACA,MACS;AACT,SACG,KAAK,WAAW,KAAK,QACpB,KAAK,WAAW,KAAK,QACrB,KAAK,WAAW,KAAK,QACrB,KAAK,WAAW,KAAK,QACtB,KAAK,WAAW,KAAK,QACpB,KAAK,WAAW,KAAK,QACrB,KAAK,WAAW,KAAK,QACrB,KAAK,WAAW,KAAK;AAE3B;AAMA,IAAM,gBAA0C;AAAA,EAC9C,OAAO,CAAC;AAAA,EACR,QAAQ,CAAC,GAAG,CAAC;AAAA,EACb,QAAQ,CAAC,GAAG,CAAC;AACf;AAMO,IAAM,sBAAN,MAA0B;AAAA,EACvB;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,EAEpB,YAAY,QAA2B;AACrC,SAAK,SAAS;AACd,SAAK,MAAM,OAAO,WAAW,IAAI;AACjC,SAAK,MAAM,OAAO,WAAW,cAAc,OAAO,oBAAoB,IAAI;AAAA,EAC5E;AAAA;AAAA,EAGA,OAAO,OAAe,QAAsB;AAC1C,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,OAAO,QAAQ,QAAQ,KAAK;AACjC,SAAK,OAAO,SAAS,SAAS,KAAK;AAAA,EACrC;AAAA;AAAA,EAGA,OAAO,OAA+B;AACpC,UAAM,EAAE,KAAK,KAAK,UAAU,UAAU,IAAI;AAC1C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AACJ,UAAM,aAAa,MAAM,cAAc;AAKvC,UAAM,YACJ,MAAM,OAAO,QAAQ,YAAY,eAAe,iBAAiB,YAAY;AAG/E,UAAM,YAAY,MAAM,SAAS,MAAM,MAAM,IAAI,KAAK,CAAC,cAAc,MAAM,QAAQ;AAGnF,UAAM,WACJ,MAAM,YAAY,MAAM,SAAS,IAAI,IACjC,mBAAmB,MAAM,UAAU,MAAM,MAAM,IAC/C;AAIN,UAAM,aAAa,MAAM,cAAc;AACvC,UAAM,gBAAgB,CAAC,OAAuB,YAAY,IAAI,EAAE,KAAK;AAGrE,UAAM,OAAO,YAAY,UAAU,WAAW,SAAS;AACvD,UAAM,eAAe,MAAM,OAAO,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC;AAC5D,UAAM,eAAe,MAAM,OAAO,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC;AAE5D,UAAM,SAAS,MAAM;AACrB,UAAM,WAAW,UAAU,CAAC,eAAe,aAAa,SAAS;AACjE,UAAM,YAAY,eAAe,UAAU,CAAC;AAE5C,UAAM,YAAY,oBAAoB,UAAU;AAGhD,QAAI,KAAK;AACT,QAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;AACrC,QAAI,UAAU,GAAG,GAAG,UAAU,SAAS;AAGvC,QAAI,MAAM,OAAO,eAAe,eAAe;AAC7C,UAAI,YAAY,MAAM,OAAO;AAC7B,UAAI,SAAS,GAAG,GAAG,UAAU,SAAS;AAAA,IACxC;AAEA,QAAI,UAAU,UAAU,GAAG,UAAU,CAAC;AACtC,QAAI,MAAM,UAAU,GAAG,UAAU,CAAC;AAKlC,QAAI,MAAM,WAAW,MAAM,QAAQ,QAAQ,GAAG;AAC5C,WAAK,WAAW,KAAK,MAAM,SAAS,IAAI;AAAA,IAC1C;AAIA,QAAI,aAAa,aAAa,UAAU,qBAAqB;AAC3D,WAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,UAAU;AAAA,QACV,UAAU;AAAA,QACV;AAAA,QACA,cAAc,OAAO;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AACL,WAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,OAAO;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,OAAO;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAGA,QAAI,CAAC,aAAa;AAChB,WAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,QAAQ;AAGZ,QAAI,MAAM,WAAW;AACnB,WAAK,UAAU,KAAK,UAAU,WAAW,KAAK;AAAA,IAChD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,UACN,KACA,GACA,GACA,OACM;AACN,QAAI,IAAI,gBAAiB;AACzB,UAAM,EAAE,IAAI,IAAI;AAChB,QAAI,KAAK;AACT,QAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;AACrC,UAAM,UAAU,MAAM,QAAQ;AAC9B,UAAM,IAAI,IAAI;AACd,UAAM,IAAI,IAAI;AACd,QAAI,OAAO,OAAO,eAAe,MAAM,MAAM,MAAM,MAAM;AACzD,QAAI,YAAY,MAAM,OAAO;AAC7B,QAAI,cAAc;AAClB,QAAI,YAAY;AAChB,QAAI,eAAe;AACnB,QAAI,SAAS,YAAY,GAAG,CAAC;AAC7B,QAAI,QAAQ;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAMQ,iBACN,KACA,OACA,OACA,YACA,eACA,eACA,UACA,eACM;AAEN,UAAM,UAA+C;AAAA,MACnD,QAAQ,CAAC;AAAA,MACT,SAAS,CAAC;AAAA,MACV,WAAW,CAAC;AAAA,IACd;AACA,QAAI,cAAqC;AAEzC,eAAW,QAAQ,OAAO;AACxB,YAAM,SAAS,GAAG,KAAK,MAAM,KAAK,KAAK,MAAM;AAC7C,UAAI,WAAW,eAAe;AAC5B,sBAAc;AACd;AAAA,MACF;AACA,cAAQ,SAAS,MAAM,KAAK,CAAC,EAAE,KAAK,IAAI;AAAA,IAC1C;AAGA,UAAM,KAAK,WAAW,SAAS,YAAY;AAG3C,SAAK;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR,cAAc,UAAU,UAAU,IAAI;AAAA,MACtC;AAAA,MACA;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR,qBAAqB;AAAA,MACrB;AAAA,MACA;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR,uBAAuB;AAAA,MACvB;AAAA,MACA;AAAA,IACF;AAEA,SAAK,gBAAgB,KAAK,WAAW;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,mBACN,KACA,OACA,MACA,MACA,GACA,YACA,eACA,eACA,UACA,eACM;AACN,UAAM,KAAK,WAAW,SAAS,YAAY;AAE3C,UAAM,UAAU,oBAAI,IAA8B;AAClD,QAAI,cAAqC;AAEzC,eAAW,QAAQ,OAAO;AACxB,YAAM,SAAS,GAAG,KAAK,MAAM,KAAK,KAAK,MAAM;AAC7C,UAAI,WAAW,eAAe;AAC5B,sBAAc;AACd;AAAA,MACF;AACA,YAAM,MAAM,GAAG,SAAS,MAAM,IAAI,CAAC,IAAI,SAAS,MAAM,IAAI,CAAC;AAC3D,UAAI,SAAS,QAAQ,IAAI,GAAG;AAC5B,UAAI,CAAC,QAAQ;AACX,iBAAS,CAAC;AACV,gBAAQ,IAAI,KAAK,MAAM;AAAA,MACzB;AACA,aAAO,KAAK,IAAI;AAAA,IAClB;AAIA,UAAM,UAAU,CAAC,GAAG,QAAQ,QAAQ,CAAC,EAClC,IAAI,CAAC,CAAC,KAAK,MAAM,MAAM;AACtB,YAAM,CAAC,UAAU,QAAQ,IAAI,IAAI,MAAM,GAAG;AAC1C,YAAM,QACJ,KAAK,cAAc,UAAU,UAAU,GAAG,cAAc,UAAU,UAAU,GAAG,CAAC,IAAI;AACtF,aAAO,EAAE,OAAO,OAAO;AAAA,IACzB,CAAC,EACA,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAEnC,eAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,WAAK,qBAAqB,KAAK,QAAQ,OAAO,eAAe,aAAa;AAAA,IAC5E;AAEA,SAAK,gBAAgB,KAAK,WAAW;AAAA,EACvC;AAAA;AAAA,EAGQ,gBAAgB,KAA+B,aAA0C;AAC/F,QAAI,CAAC,YAAa;AAClB,UAAM,OAAO,cAAc,YAAY,KAAK,KAAK,cAAc;AAC/D,QAAI,YAAY,IAAI;AACpB,QAAI,cAAc,YAAY;AAC9B,QAAI,YAAY,YAAY,cAAc;AAC1C,QAAI,cAAc;AAClB,QAAI,UAAU;AACd,QAAI,OAAO,YAAY,SAAS,YAAY,OAAO;AACnD,QAAI,OAAO,YAAY,SAAS,YAAY,OAAO;AACnD,QAAI,OAAO;AACX,QAAI,YAAY,CAAC,CAAC;AAClB,QAAI,cAAc;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,qBACN,KACA,OACA,OACA,eACA,eACM;AACN,QAAI,MAAM,WAAW,EAAG;AAOxB,UAAM,SAAS,oBAAI,IAAwD;AAC3E,eAAW,QAAQ,OAAO;AACxB,YAAM,WAAW,KAAK,IAAI,cAAc,KAAK,MAAM,GAAG,cAAc,KAAK,MAAM,CAAC;AAChF,YAAM,QAAQ,KAAK,MAAM,WAAW,CAAC,IAAI;AACzC,YAAM,MAAM,GAAG,KAAK,MAAM,IAAI,KAAK,WAAW,IAAI,KAAK,KAAK,IAAI,KAAK;AACrE,UAAI,QAAQ,OAAO,IAAI,GAAG;AAC1B,UAAI,CAAC,OAAO;AACV,gBAAQ,EAAE,OAAO,CAAC,GAAG,MAAM;AAC3B,eAAO,IAAI,KAAK,KAAK;AAAA,MACvB;AACA,YAAM,MAAM,KAAK,IAAI;AAAA,IACvB;AAEA,eAAW,CAAC,EAAE,EAAE,OAAO,OAAO,MAAM,CAAC,KAAK,QAAQ;AAChD,YAAM,SAAS,MAAM,CAAC;AACtB,YAAM,OAAO,cAAc,OAAO,KAAK,KAAK,cAAc;AAC1D,UAAI,YAAY,IAAI;AACpB,UAAI,cAAc,OAAO;AACzB,UAAI,YAAY,OAAO;AACvB,YAAM,aAAa,QAAQ;AAE3B,UAAI,CAAC,eAAe;AAElB,YAAI,cAAc;AAClB,YAAI,UAAU;AACd,mBAAW,QAAQ,OAAO;AACxB,cAAI,OAAO,KAAK,SAAS,KAAK,OAAO;AACrC,cAAI,OAAO,KAAK,SAAS,KAAK,OAAO;AAAA,QACvC;AACA,YAAI,OAAO;AAAA,MACb,OAAO;AAEL,YAAI,cAAc;AAClB,YAAI,UAAU;AACd,YAAI,aAAa;AAEjB,cAAM,eAAiC,CAAC;AAExC,mBAAW,QAAQ,OAAO;AACxB,gBAAM,WAAW,cAAc,IAAI,KAAK,MAAM;AAC9C,gBAAM,WAAW,cAAc,IAAI,KAAK,MAAM;AAC9C,cAAI,YAAY,UAAU;AACxB,gBAAI,OAAO,KAAK,SAAS,KAAK,OAAO;AACrC,gBAAI,OAAO,KAAK,SAAS,KAAK,OAAO;AACrC,yBAAa;AAAA,UACf,OAAO;AACL,yBAAa,KAAK,IAAI;AAAA,UACxB;AAAA,QACF;AACA,YAAI,WAAY,KAAI,OAAO;AAG3B,YAAI,aAAa,SAAS,GAAG;AAC3B,cAAI,cAAc,yBAAyB;AAC3C,cAAI,UAAU;AACd,qBAAW,QAAQ,cAAc;AAC/B,gBAAI,OAAO,KAAK,SAAS,KAAK,OAAO;AACrC,gBAAI,OAAO,KAAK,SAAS,KAAK,OAAO;AAAA,UACvC;AACA,cAAI,OAAO;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,QAAI,YAAY,CAAC,CAAC;AAClB,QAAI,cAAc;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAMQ,iBACN,KACA,OACA,eACA,iBACA,eACA,UACA,OACA,WACA,WACA,YACA,WACA,WACA,kBACA,UACA,eACM;AAKN,UAAM,aAAa,CAAC,SAAiC;AACnD,UAAI,WAAW;AACb,eAAO;AAAA,UACL,cAAc,SAAS,MAAM,UAAU,MAAM,SAAS,GAAG,UAAU;AAAA,UACnE,cAAc,SAAS,MAAM,UAAU,MAAM,SAAS,GAAG,UAAU;AAAA,UACnE,UAAU;AAAA,QACZ;AAAA,MACF;AACA,aAAO,cAAc,SAAS,MAAM,WAAW,SAAS,GAAG,UAAU;AAAA,IACvE;AACA,UAAM,cAAc,CAAC,SACnB,kBAAkB,QAAQ,CAAC,cAAc,IAAI,KAAK,EAAE,IAAI,yBAAyB;AAInF,UAAM,gBAAgB,CAAC,SACrB,WAAW,SAAS,UAAU,IAAI,IAAI;AACxC,UAAM,gBAAgB,CAAC,SACrB,WAAW,SAAS,UAAU,IAAI,IAAI;AAGxC,UAAM,gBAAgB,CAAC,SACrB,WAAW,SAAS,MAAM,KAAK,EAAE,IAAI;AAGvC,UAAM,iBAAiB,CAAC,SACtB,WAAW,IAAI,IAAI,YAAY,IAAI,IAAI,cAAc,IAAI,IAAI,cAAc,KAAK,EAAE;AAGpF,UAAM,YAA8B,CAAC;AACrC,UAAM,eAAiC,CAAC;AAExC,eAAW,QAAQ,OAAO;AACxB,UACE,KAAK,OAAO,iBACZ,gBAAgB,IAAI,KAAK,EAAE,MAC1B,kBAAkB,IAAI,KAAK,EAAE,KAAK,QACnC;AACA,qBAAa,KAAK,IAAI;AAAA,MACxB,OAAO;AACL,kBAAU,KAAK,IAAI;AAAA,MACrB;AAAA,IACF;AAIA,UAAM,IAAI,CAAC,SAAyB,KAAK,IAAI,KAAK,QAAQ,SAAS,IAAI,cAAc,IAAI;AAKzF,QAAI,YAAY,CAAC,UAAU;AACzB,WAAK,gBAAgB,KAAK,WAAW,eAAe,SAAS;AAAA,IAC/D;AAGA,UAAM,aAAa,oBAAI,IAAsE;AAC7F,eAAW,QAAQ,WAAW;AAC5B,YAAM,QAAQ,eAAe,IAAI;AACjC,YAAM,MAAM,GAAG,KAAK,IAAI,IAAI,MAAM,QAAQ,CAAC,CAAC;AAC5C,UAAI,QAAQ,WAAW,IAAI,GAAG;AAC9B,UAAI,CAAC,OAAO;AACV,gBAAQ,EAAE,MAAM,KAAK,MAAM,OAAO,OAAO,CAAC,EAAE;AAC5C,mBAAW,IAAI,KAAK,KAAK;AAAA,MAC3B;AACA,YAAM,MAAM,KAAK,IAAI;AAAA,IACvB;AAEA,eAAW,EAAE,MAAM,OAAO,OAAO,MAAM,KAAK,WAAW,OAAO,GAAG;AAC/D,UAAI,YAAY;AAChB,UAAI,cAAc;AAClB,UAAI,UAAU;AACd,iBAAW,QAAQ,OAAO;AACxB,cAAM,KAAK,EAAE,IAAI;AACjB,YAAI,MAAM,EAAG;AACb,cAAM,IAAI,cAAc,IAAI;AAC5B,YAAI,OAAO,KAAK,IAAI,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;AAC1C,YAAI,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,GAAG,IAAI,GAAG,MAAM;AAAA,MACnD;AACA,UAAI,KAAK;AAAA,IACX;AAGA,UAAM,eAAe,oBAAI,IAGvB;AACF,eAAW,QAAQ,WAAW;AAC5B,YAAM,QAAQ,eAAe,IAAI;AACjC,YAAM,MAAM,GAAG,KAAK,MAAM,IAAI,KAAK,WAAW,IAAI,MAAM,QAAQ,CAAC,CAAC;AAClE,UAAI,QAAQ,aAAa,IAAI,GAAG;AAChC,UAAI,CAAC,OAAO;AACV,gBAAQ,EAAE,QAAQ,KAAK,QAAQ,OAAO,KAAK,aAAa,OAAO,OAAO,CAAC,EAAE;AACzE,qBAAa,IAAI,KAAK,KAAK;AAAA,MAC7B;AACA,YAAM,MAAM,KAAK,IAAI;AAAA,IACvB;AAEA,eAAW,EAAE,QAAQ,OAAO,OAAO,OAAO,MAAM,KAAK,aAAa,OAAO,GAAG;AAC1E,UAAI,cAAc;AAClB,UAAI,YAAY;AAChB,UAAI,cAAc;AAClB,UAAI,UAAU;AACd,iBAAW,QAAQ,OAAO;AACxB,cAAM,KAAK,EAAE,IAAI;AACjB,YAAI,MAAM,EAAG;AACb,cAAM,IAAI,cAAc,IAAI;AAC5B,YAAI,OAAO,KAAK,IAAI,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;AAC1C,YAAI,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,GAAG,IAAI,GAAG,MAAM;AAAA,MACnD;AACA,UAAI,OAAO;AAAA,IACb;AAGA,eAAW,QAAQ,cAAc;AAC/B,YAAM,YAAY,KAAK,OAAO;AAC9B,YAAM,aAAa,gBAAgB,IAAI,KAAK,EAAE;AAC9C,YAAM,SAAS,kBAAkB,QAAQ,CAAC,cAAc,IAAI,KAAK,EAAE;AACnE,YAAM,aAAa,KAAK,IAAI,KAAK,QAAQ,SAAS;AAElD,YAAM,aAAa,kBAAkB,IAAI,KAAK,EAAE,MAAM,YAAY,OAAO;AACzE,YAAM,SAAS,aAAa;AAE5B,YAAM,aAAa,aAAa,cAAc;AAC9C,YAAM,IAAI,cAAc,IAAI;AAC5B,YAAM,KAAK,KAAK,IAAI,EAAE;AACtB,YAAM,KAAK,KAAK,IAAI,EAAE;AAEtB,UAAI,cAAc,SAAS,yBAAyB;AAGpD,UAAI,YAAY,CAAC,QAAQ;AACvB,YAAI,UAAU;AACd,YAAI,IAAI,IAAI,IAAI,SAAS,wBAAwB,GAAG,MAAM;AAC1D,YAAI,YAAY,KAAK;AACrB,YAAI,cAAc;AAClB,YAAI,KAAK;AACT,YAAI,cAAc,SAAS,yBAAyB;AAAA,MACtD;AAGA,UAAI,UAAU;AACd,UAAI,IAAI,IAAI,IAAI,QAAQ,GAAG,MAAM;AACjC,UAAI,YAAY,aAAa,SAAS,KAAK,IAAI,IAAI,KAAK;AACxD,UAAI,KAAK;AAGT,UAAI,cAAc,KAAK;AACvB,UAAI,YAAY,KAAK;AACrB,UAAI,OAAO;AAGX,UAAI,YAAY;AACd,YAAI,UAAU;AACd,YAAI,IAAI,IAAI,IAAI,SAAS,GAAG,GAAG,MAAM;AACrC,YAAI,cAAc,MAAM,OAAO,YAAY,CAAC,KAAK;AACjD,YAAI,YAAY;AAChB,YAAI,OAAO;AAAA,MACb;AAAA,IACF;AAEA,QAAI,cAAc;AAAA,EACpB;AAAA;AAAA,EAGQ,gBACN,KACA,OACA,eACA,WACM;AACN,UAAM,aAAa,oBAAI,IAA8B;AACrD,eAAW,QAAQ,OAAO;AACxB,UAAI,iBAAiB,CAAC,cAAc,IAAI,KAAK,EAAE,EAAG;AAClD,UAAI,QAAQ,WAAW,IAAI,KAAK,IAAI;AACpC,UAAI,CAAC,OAAO;AACV,gBAAQ,CAAC;AACT,mBAAW,IAAI,KAAK,MAAM,KAAK;AAAA,MACjC;AACA,YAAM,KAAK,IAAI;AAAA,IACjB;AAEA,QAAI,cAAc;AAClB,eAAW,CAAC,MAAM,KAAK,KAAK,YAAY;AACtC,UAAI,YAAY;AAChB,UAAI,UAAU;AACd,iBAAW,QAAQ,OAAO;AACxB,cAAM,KAAK,KAAK,IAAI,KAAK,QAAQ,SAAS,IAAI;AAC9C,YAAI,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC;AAC9B,YAAI,IAAI,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,MAAM;AAAA,MACvC;AACA,UAAI,KAAK;AAAA,IACX;AACA,QAAI,cAAc;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAMQ,WACN,KACA,OACA,WACA,eACA,iBACA,eACA,MACA,OACA,UACA,eACM;AAEN,UAAM,KAAK,WAAW,SAAS,aAAa;AAE5C,UAAM,UAAU,KAAK;AACrB,UAAM,WAAW,KAAK,IAAI,gBAAgB,KAAK,IAAI,gBAAgB,OAAO,CAAC;AAE3E,QAAI,OAAO,GAAG,QAAQ,MAAM,MAAM,MAAM,MAAM;AAC9C,QAAI,YAAY;AAChB,QAAI,eAAe;AAEnB,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,MAAO;AAEjB,YAAM,YAAY,KAAK,OAAO;AAC9B,YAAM,aAAa,gBAAgB,IAAI,KAAK,EAAE;AAC9C,YAAM,SAAS,aAAa;AAC5B,YAAM,SAAS,kBAAkB,QAAQ,CAAC,cAAc,IAAI,KAAK,EAAE;AAGnE,UAAI,CAAC,UAAU,KAAK,gBAAgB,UAAW;AAE/C,UAAI,eAAe,SAAS,yBAAyB,KAAK,KAAK,cAAc,KAAK,EAAE;AAEpF,YAAM,SAAS,KAAK,IAAI,KAAK,SAAS;AAItC,UAAI,MAAM,OAAO,eAAe,eAAe;AAC7C,YAAI,cAAc,MAAM,OAAO;AAAA,MACjC,OAAO;AAIL,YAAI,cAAc,MAAM,SAAS,uBAAuB;AAAA,MAC1D;AACA,UAAI,YAAY;AAChB,UAAI,WAAW;AACf,UAAI,aAAa;AACjB,UAAI,WAAW,KAAK,OAAO,KAAK,GAAG,MAAM;AAEzC,UAAI,YAAY,MAAM,OAAO;AAC7B,UAAI,SAAS,KAAK,OAAO,KAAK,GAAG,MAAM;AAAA,IACzC;AAEA,QAAI,cAAc;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,WACN,KACA,SACA,MACM;AACN,UAAM,QAAQ,QAAQ;AAGtB,UAAM,aAAa,oBAAI,IAA8B;AACrD,eAAW,QAAQ,QAAQ,OAAO;AAChC,UAAI,CAAC,WAAW,MAAM,IAAI,EAAG;AAC7B,YAAM,MAAM,GAAG,KAAK,MAAM,IAAI,KAAK,WAAW,IAAI,KAAK,KAAK;AAC5D,YAAM,QAAQ,WAAW,IAAI,GAAG;AAChC,UAAI,MAAO,OAAM,KAAK,IAAI;AAAA,UACrB,YAAW,IAAI,KAAK,CAAC,IAAI,CAAC;AAAA,IACjC;AACA,eAAW,CAAC,EAAE,KAAK,KAAK,YAAY;AAClC,YAAM,SAAS,MAAM,CAAC;AACtB,YAAM,OAAO,cAAc,OAAO,KAAK,KAAK,cAAc;AAC1D,UAAI,YAAY,IAAI;AACpB,UAAI,cAAc,OAAO;AACzB,UAAI,YAAY,OAAO;AACvB,UAAI,cAAc,qBAAqB;AACvC,UAAI,UAAU;AACd,iBAAW,QAAQ,OAAO;AACxB,YAAI,OAAO,KAAK,SAAS,KAAK,OAAO;AACrC,YAAI,OAAO,KAAK,SAAS,KAAK,OAAO;AAAA,MACvC;AACA,UAAI,OAAO;AAAA,IACb;AACA,QAAI,YAAY,CAAC,CAAC;AAGlB,UAAM,aAAa,oBAAI,IAA8B;AACrD,eAAW,QAAQ,QAAQ,OAAO;AAChC,UAAI,CAAC,WAAW,MAAM,IAAI,EAAG;AAC7B,YAAM,QAAQ,WAAW,IAAI,KAAK,IAAI;AACtC,UAAI,MAAO,OAAM,KAAK,IAAI;AAAA,UACrB,YAAW,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC;AAAA,IACvC;AACA,eAAW,CAAC,MAAM,KAAK,KAAK,YAAY;AACtC,UAAI,YAAY;AAChB,UAAI,cAAc;AAClB,UAAI,UAAU;AACd,iBAAW,QAAQ,OAAO;AACxB,YAAI,OAAO,KAAK,IAAI,KAAK,QAAQ,KAAK,CAAC;AACvC,YAAI,IAAI,KAAK,GAAG,KAAK,GAAG,KAAK,QAAQ,GAAG,MAAM;AAAA,MAChD;AACA,UAAI,KAAK;AAAA,IACX;AAEA,QAAI,cAAc;AAAA,EACpB;AACF;AAUA,SAAS,SAAS,OAAuB;AAEvC,QAAM,WAAW,MAAM,MAAM,8CAA8C;AAC3E,MAAI,UAAU;AACZ,UAAM,IAAI,KAAK,IAAI,KAAK,SAAS,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE;AACtD,UAAM,IAAI,KAAK,IAAI,KAAK,SAAS,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE;AACtD,UAAM,IAAI,KAAK,IAAI,KAAK,SAAS,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE;AACtD,WAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;AAAA,EAC3B;AAGA,QAAM,MAAM,MAAM,QAAQ,KAAK,EAAE;AACjC,QAAM,OACJ,IAAI,WAAW,IACX,IACG,MAAM,EAAE,EACR,IAAI,CAAC,MAAM,IAAI,CAAC,EAChB,KAAK,EAAE,IACV;AAEN,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,IAAI,KAAK,IAAI,KAAK,SAAS,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE;AAC3D,UAAM,IAAI,KAAK,IAAI,KAAK,SAAS,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE;AAC3D,UAAM,IAAI,KAAK,IAAI,KAAK,SAAS,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE;AAC3D,WAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;AAAA,EAC3B;AAEA,SAAO;AACT;;;AEz/BO,SAAS,oBAAoB,GAAkB,GAA2B;AAC/E,SACE,EAAE,cAAc,EAAE,aAClB,UAAU,EAAE,WAAW,EAAE,SAAS,KAClC,kBAAkB,EAAE,eAAe,EAAE,aAAa,KAClD,UAAU,EAAE,UAAU,EAAE,QAAQ;AAEpC;AAEA,SAAS,UAAU,GAAgB,GAAyB;AAC1D,MAAI,EAAE,SAAS,EAAE,KAAM,QAAO;AAC9B,aAAW,KAAK,EAAG,KAAI,CAAC,EAAE,IAAI,CAAC,EAAG,QAAO;AACzC,SAAO;AACT;AAEA,SAAS,kBAAkB,GAAuB,GAAgC;AAChF,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO,MAAM;AAC3C,SAAO,UAAU,GAAG,CAAC;AACvB;AAQO,IAAM,kBAAN,MAAsB;AAAA,EAC3B;AAAA,EACA;AAAA,EACQ;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,SAAwB,UAAkB,MAA6B,KAAa;AAC9F,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,SAAK,WAAW,KAAK,IAAI,GAAG,QAAQ;AACpC,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,QAAuB,KAAmB;AACjD,QAAI,oBAAoB,QAAQ,KAAK,IAAI,EAAG;AAC5C,UAAM,IAAI,KAAK,YAAY,GAAG;AAG9B,SAAK,OAAO,IAAI,MAAM,KAAK,OAAO,KAAK;AACvC,SAAK,OAAO;AACZ,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,YAAY,KAAqB;AACvC,QAAI,KAAK,YAAY,EAAG,QAAO;AAC/B,WAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,MAAM,KAAK,aAAa,KAAK,QAAQ,CAAC;AAAA,EACxE;AAAA;AAAA,EAGA,SAAS,KAAqB;AAC5B,WAAO,KAAK,KAAK,KAAK,YAAY,GAAG,CAAC;AAAA,EACxC;AAAA;AAAA,EAGA,UAAU,KAAsB;AAC9B,WAAO,KAAK,YAAY,GAAG,KAAK;AAAA,EAClC;AACF;AAQO,SAAS,qBACd,WACA,eACA,UACA,WACe;AACf,QAAM,eAAe,cAAc,QAAQ,UAAU,OAAO;AAC5D,QAAM,YAAY,kBAAkB,QAAQ,cAAc,OAAO;AAGjE,MAAI,OAA2B;AAC/B,MAAI,cAAc,QAAQ,gBAAgB,kBAAkB,QAAQ,WAAW;AAC7E,UAAM,QAAQ,UAAU,WAAW,aAAa;AAChD,WAAO,MAAM,OAAO,IAAI,QAAQ;AAAA,EAClC,WAAW,cAAc;AACvB,WAAO;AAAA,EACT,WAAW,WAAW;AAGpB,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,MAAM;AACR,eAAW,MAAM,MAAM;AACrB,gBAAU,IAAI,EAAE;AAChB,YAAM,YAAY,UAAU,IAAI,EAAE;AAClC,UAAI,UAAW,YAAW,OAAO,UAAW,WAAU,IAAI,GAAG;AAAA,IAC/D;AAAA,EACF;AAEA,QAAM,YAAa,SAAS,QAAQ,KAAK,OAAO,KAAM,aAAa,SAAS,OAAO;AAEnF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe,YAAY,gBAAgB;AAAA,IAC3C;AAAA,EACF;AACF;AAOO,SAAS,gBACd,UACA,WACA,gBACe;AACf,MAAI,cAAc,QAAQ,mBAAmB,KAAM,QAAO;AAC1D,SAAO;AAAA,IACL,WAAW;AAAA,IACX,WAAW;AAAA,IACX,eAAe,SAAS;AAAA,IACxB,UAAU,SAAS;AAAA,EACrB;AACF;AAEA,SAAS,UAAU,GAAgB,GAA6B;AAC9D,QAAM,CAAC,OAAO,KAAK,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;AACxD,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,MAAO,KAAI,MAAM,IAAI,CAAC,EAAG,KAAI,IAAI,CAAC;AAClD,SAAO;AACT;;;ACvKA,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,YAAY;AAClB,IAAM,eAAe;AAwCd,IAAM,0BAAN,MAA8B;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY,cAAc,SAAS;AAAA,EAEnC,YAA8B;AAAA,EAC9B,WAA4B;AAAA,EAC5B,kBAAiC;AAAA,EACjC,cAA2B,oBAAI,IAAI;AAAA;AAAA,EAGnC,gBAA+B;AAAA,EAC/B,kBAAmD;AAAA;AAAA,EAGnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YACE,QACA,cACA,WACA;AACA,SAAK,SAAS;AACd,SAAK,eAAe;AACpB,SAAK,YAAY;AAGjB,SAAK,aAAa,KAAK,QAAQ,KAAK,IAAI;AACxC,SAAK,iBAAiB,KAAK,YAAY,KAAK,IAAI;AAChD,SAAK,iBAAiB,KAAK,YAAY,KAAK,IAAI;AAChD,SAAK,eAAe,KAAK,UAAU,KAAK,IAAI;AAC5C,SAAK,kBAAkB,KAAK,aAAa,KAAK,IAAI;AAClD,SAAK,gBAAgB,KAAK,WAAW,KAAK,IAAI;AAC9C,SAAK,kBAAkB,KAAK,aAAa,KAAK,IAAI;AAClD,SAAK,iBAAiB,KAAK,YAAY,KAAK,IAAI;AAChD,SAAK,gBAAgB,KAAK,WAAW,KAAK,IAAI;AAG9C,WAAO,iBAAiB,SAAS,KAAK,YAAY,EAAE,SAAS,MAAM,CAAC;AACpE,WAAO,iBAAiB,aAAa,KAAK,cAAc;AACxD,WAAO,iBAAiB,aAAa,KAAK,cAAc;AACxD,WAAO,iBAAiB,WAAW,KAAK,YAAY;AACpD,WAAO,iBAAiB,cAAc,KAAK,eAAe;AAC1D,WAAO,iBAAiB,YAAY,KAAK,aAAa;AACtD,WAAO,iBAAiB,cAAc,KAAK,iBAAiB;AAAA,MAC1D,SAAS;AAAA,IACX,CAAC;AACD,WAAO,iBAAiB,aAAa,KAAK,gBAAgB;AAAA,MACxD,SAAS;AAAA,IACX,CAAC;AACD,WAAO,iBAAiB,YAAY,KAAK,aAAa;AAAA,EACxD;AAAA,EAEA,aAAa,WAAgC;AAC3C,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,eAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,KAAqB;AAChC,SAAK,cAAc,IAAI,IAAI,GAAG;AAAA,EAChC;AAAA,EAEA,UAAgB;AACd,SAAK,OAAO,oBAAoB,SAAS,KAAK,UAAU;AACxD,SAAK,OAAO,oBAAoB,aAAa,KAAK,cAAc;AAChE,SAAK,OAAO,oBAAoB,aAAa,KAAK,cAAc;AAChE,SAAK,OAAO,oBAAoB,WAAW,KAAK,YAAY;AAC5D,SAAK,OAAO,oBAAoB,cAAc,KAAK,eAAe;AAClE,SAAK,OAAO,oBAAoB,YAAY,KAAK,aAAa;AAC9D,SAAK,OAAO,oBAAoB,cAAc,KAAK,eAAe;AAClE,SAAK,OAAO,oBAAoB,aAAa,KAAK,cAAc;AAChE,SAAK,OAAO,oBAAoB,YAAY,KAAK,aAAa;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAMQ,SAAS,GAAyC;AACxD,UAAM,OAAO,KAAK,OAAO,sBAAsB;AAC/C,WAAO,EAAE,GAAG,EAAE,UAAU,KAAK,MAAM,GAAG,EAAE,UAAU,KAAK,IAAI;AAAA,EAC7D;AAAA,EAEQ,QAAQ,SAAiB,SAAgC;AAC/D,UAAM,QAAQ,KAAK,UAAU,cAAc,SAAS,OAAO;AAC3D,UAAM,OAAO,KAAK,aAAa,YAAY,MAAM,GAAG,MAAM,GAAG,eAAe,KAAK,UAAU,CAAC;AAC5F,WAAO,MAAM,MAAM;AAAA,EACrB;AAAA,EAEQ,QAAQ,GAAqB;AACnC,MAAE,eAAe;AACjB,UAAM,EAAE,GAAG,EAAE,IAAI,KAAK,SAAS,CAAC;AAChC,UAAM,SAAS,EAAE,SAAS;AAC1B,UAAM,OAAO,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU,KAAK,UAAU,KAAK,IAAI,OAAO,CAAC;AACnF,SAAK,YAAY,KAAK,UAAU,OAAO,MAAM,GAAG,CAAC;AACjD,SAAK,UAAU,kBAAkB,KAAK,SAAS;AAAA,EACjD;AAAA,EAEQ,YAAY,GAAqB;AACvC,UAAM,EAAE,GAAG,EAAE,IAAI,KAAK,SAAS,CAAC;AAChC,UAAM,QAAQ,KAAK,QAAQ,GAAG,CAAC;AAE/B,QAAI,OAAO;AAET,WAAK,YAAY,EAAE,QAAQ,OAAO,SAAS,MAAM;AACjD,WAAK,kBAAkB;AAAA,IACzB,OAAO;AAEL,WAAK,WAAW,EAAE,QAAQ,GAAG,QAAQ,EAAE;AACvC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA,EAEQ,YAAY,GAAqB;AACvC,UAAM,EAAE,GAAG,EAAE,IAAI,KAAK,SAAS,CAAC;AAIhC,QAAI,KAAK,UAAU,eAAe;AAChC,YAAM,KAAK,KAAK,UAAU,cAAc,GAAG,CAAC;AAC5C,WAAK,UAAU,cAAc,GAAG,GAAG,GAAG,CAAC;AAAA,IACzC;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,QAAQ,KAAK,UAAU,cAAc,GAAG,CAAC;AAC/C,UAAI,CAAC,KAAK,UAAU,SAAS;AAC3B,aAAK,UAAU,UAAU;AACzB,aAAK,UAAU,gBAAgB,KAAK,UAAU,MAAM;AAAA,MACtD;AACA,WAAK,UAAU,WAAW,KAAK,UAAU,QAAQ,MAAM,GAAG,MAAM,CAAC;AACjE;AAAA,IACF;AAEA,QAAI,KAAK,UAAU;AACjB,YAAM,KAAK,IAAI,KAAK,SAAS;AAC7B,YAAM,KAAK,IAAI,KAAK,SAAS;AAC7B,WAAK,YAAY,KAAK,UAAU,IAAI,IAAI,EAAE;AAC1C,WAAK,WAAW,EAAE,QAAQ,GAAG,QAAQ,EAAE;AACvC,WAAK,UAAU,kBAAkB,KAAK,SAAS;AAC/C;AAAA,IACF;AAGA,UAAM,QAAQ,KAAK,QAAQ,GAAG,CAAC;AAC/B,SAAK,UAAU,cAAc,KAAK;AAGlC,QAAI,CAAC,OAAO;AACV,YAAM,QAAQ,KAAK,UAAU,cAAc,GAAG,CAAC;AAC/C,WAAK,UAAU,oBAAoB,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC;AAAA,IAC3D;AAGA,SAAK,OAAO,MAAM,SAAS,QAAQ,YAAY;AAAA,EACjD;AAAA,EAEQ,UAAU,GAAqB;AACrC,UAAM,EAAE,GAAG,EAAE,IAAI,KAAK,SAAS,CAAC;AAEhC,QAAI,KAAK,WAAW;AAClB,UAAI,KAAK,UAAU,SAAS;AAC1B,aAAK,UAAU,cAAc,KAAK,UAAU,MAAM;AAAA,MACpD,OAAO;AAEL,aAAK,gBAAgB,KAAK,UAAU,QAAQ,EAAE,QAAQ;AAAA,MACxD;AACA,WAAK,YAAY;AACjB;AAAA,IACF;AAEA,QAAI,KAAK,UAAU;AACjB,WAAK,WAAW;AAGhB,UAAI,CAAC,KAAK,iBAAiB;AACzB,cAAM,QAAQ,KAAK,QAAQ,GAAG,CAAC;AAC/B,YAAI,CAAC,OAAO;AAEV,eAAK,YAAY,MAAM;AACvB,eAAK,UAAU,kBAAkB,CAAC,CAAC;AAAA,QACrC;AAAA,MACF;AACA;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,WAAW,GAAqB;AACtC,UAAM,EAAE,GAAG,EAAE,IAAI,KAAK,SAAS,CAAC;AAChC,UAAM,QAAQ,KAAK,QAAQ,GAAG,CAAC;AAC/B,QAAI,OAAO;AACT,WAAK,UAAU,cAAc,KAAK;AAAA,IACpC;AAAA,EACF;AAAA,EAEQ,aAAa,IAAsB;AACzC,SAAK,UAAU,cAAc,IAAI;AACjC,SAAK,OAAO,MAAM,SAAS;AAE3B,SAAK,UAAU,iBAAiB;AAGhC,QAAI,KAAK,UAAU;AACjB,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEQ,gBAAgB,QAAgB,UAAyB;AAC/D,QAAI,UAAU;AAEZ,UAAI,KAAK,YAAY,IAAI,MAAM,GAAG;AAChC,aAAK,YAAY,OAAO,MAAM;AAAA,MAChC,OAAO;AACL,aAAK,YAAY,IAAI,MAAM;AAAA,MAC7B;AAAA,IACF,OAAO;AAEL,WAAK,YAAY,MAAM;AACvB,WAAK,YAAY,IAAI,MAAM;AAAA,IAC7B;AAEA,SAAK,UAAU,kBAAkB,CAAC,GAAG,KAAK,WAAW,CAAC;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,GAAqB;AACxC,MAAE,eAAe;AAEjB,QAAI,EAAE,QAAQ,WAAW,GAAG;AAE1B,YAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC5C,WAAK,gBAAgB,KAAK,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO;AAChF,WAAK,kBAAkB;AAAA,QACrB,IAAI,GAAG,UAAU,GAAG,WAAW;AAAA,QAC/B,IAAI,GAAG,UAAU,GAAG,WAAW;AAAA,MACjC;AAAA,IACF,WAAW,EAAE,QAAQ,WAAW,GAAG;AACjC,YAAM,QAAQ,EAAE,QAAQ,CAAC;AACzB,YAAM,OAAO,KAAK,OAAO,sBAAsB;AAC/C,YAAM,IAAI,MAAM,UAAU,KAAK;AAC/B,YAAM,IAAI,MAAM,UAAU,KAAK;AAE/B,YAAM,QAAQ,KAAK,QAAQ,GAAG,CAAC;AAC/B,UAAI,OAAO;AACT,aAAK,kBAAkB;AAAA,MACzB,OAAO;AACL,aAAK,WAAW,EAAE,QAAQ,GAAG,QAAQ,EAAE;AACvC,aAAK,kBAAkB;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,YAAY,GAAqB;AACvC,MAAE,eAAe;AAEjB,QAAI,EAAE,QAAQ,WAAW,KAAK,KAAK,kBAAkB,MAAM;AACzD,YAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC5C,YAAM,UAAU,KAAK,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO;AAC3E,YAAM,OAAO,KAAK,OAAO,sBAAsB;AAC/C,YAAM,WAAW,GAAG,UAAU,GAAG,WAAW,IAAI,KAAK;AACrD,YAAM,WAAW,GAAG,UAAU,GAAG,WAAW,IAAI,KAAK;AAErD,YAAM,QAAQ,UAAU,KAAK;AAC7B,YAAM,OAAO,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU,KAAK,UAAU,IAAI,KAAK,CAAC;AAC5E,WAAK,YAAY,KAAK,UAAU,OAAO,MAAM,SAAS,OAAO;AAG7D,UAAI,KAAK,iBAAiB;AACxB,cAAM,KAAK,WAAW,KAAK,gBAAgB,IAAI,KAAK;AACpD,cAAM,KAAK,WAAW,KAAK,gBAAgB,IAAI,KAAK;AACpD,aAAK,YAAY,KAAK,UAAU,IAAI,IAAI,EAAE;AAAA,MAC5C;AAEA,WAAK,gBAAgB;AACrB,WAAK,kBAAkB;AAAA,QACrB,IAAI,GAAG,UAAU,GAAG,WAAW;AAAA,QAC/B,IAAI,GAAG,UAAU,GAAG,WAAW;AAAA,MACjC;AACA,WAAK,UAAU,kBAAkB,KAAK,SAAS;AAAA,IACjD,WAAW,EAAE,QAAQ,WAAW,KAAK,KAAK,UAAU;AAClD,YAAM,QAAQ,EAAE,QAAQ,CAAC;AACzB,YAAM,OAAO,KAAK,OAAO,sBAAsB;AAC/C,YAAM,IAAI,MAAM,UAAU,KAAK;AAC/B,YAAM,IAAI,MAAM,UAAU,KAAK;AAE/B,YAAM,KAAK,IAAI,KAAK,SAAS;AAC7B,YAAM,KAAK,IAAI,KAAK,SAAS;AAC7B,WAAK,YAAY,KAAK,UAAU,IAAI,IAAI,EAAE;AAC1C,WAAK,WAAW,EAAE,QAAQ,GAAG,QAAQ,EAAE;AACvC,WAAK,UAAU,kBAAkB,KAAK,SAAS;AAAA,IACjD;AAAA,EACF;AAAA,EAEQ,WAAW,GAAqB;AACtC,QAAI,EAAE,QAAQ,WAAW,GAAG;AAE1B,UAAI,KAAK,mBAAmB,CAAC,KAAK,UAAU;AAC1C,aAAK,gBAAgB,KAAK,iBAAiB,KAAK;AAAA,MAClD,WAAW,CAAC,KAAK,mBAAmB,KAAK,UAAU;AAEjD,aAAK,YAAY,MAAM;AACvB,aAAK,UAAU,kBAAkB,CAAC,CAAC;AAAA,MACrC;AAEA,WAAK,WAAW;AAChB,WAAK,kBAAkB;AACvB,WAAK,gBAAgB;AACrB,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AACF;;;ACnWO,SAAS,uBAAuB,SAAyC;AAC9E,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI,gBAA+B;AAGnC,MAAI,CAAC,OAAO,aAAa,UAAU,GAAG;AACpC,WAAO,aAAa,YAAY,GAAG;AAAA,EACrC;AAEA,WAAS,aAAa,IAAwC;AAC5D,WAAO,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,EAC3C;AAMA,WAAS,wBACP,UACA,aACA,WACe;AACf,UAAM,QAAQ,SAAS;AACvB,UAAM,aAAa,MAAM,OAAO,CAAC,MAAM,YAAY,IAAI,EAAE,EAAE,CAAC;AAC5D,QAAI,WAAW,WAAW,EAAG,QAAO;AAGpC,QAAI,OAA8B;AAClC,QAAI,YAAY;AAEhB,eAAW,KAAK,YAAY;AAC1B,YAAM,KAAK,EAAE,IAAI,SAAS;AAC1B,YAAM,KAAK,EAAE,IAAI,SAAS;AAC1B,UAAI;AAEJ,cAAQ,WAAW;AAAA,QACjB,KAAK;AACH,kBAAQ,KAAK,KAAK,IAAI,EAAE,IAAI;AAC5B;AAAA,QACF,KAAK;AACH,kBAAQ,CAAC,KAAK,KAAK,IAAI,EAAE,IAAI;AAC7B;AAAA,QACF,KAAK;AACH,kBAAQ,KAAK,KAAK,IAAI,EAAE,IAAI;AAC5B;AAAA,QACF,KAAK;AACH,kBAAQ,CAAC,KAAK,KAAK,IAAI,EAAE,IAAI;AAC7B;AAAA,MACJ;AAEA,UAAI,QAAQ,WAAW;AACrB,oBAAY;AACZ,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO,MAAM,MAAM;AAAA,EACrB;AAEA,WAAS,UAAU,GAAwB;AACzC,YAAQ,EAAE,KAAK;AAAA,MACb,KAAK,OAAO;AAEV,cAAM,WAAW,eAAe;AAChC,cAAM,QAAQ,SAAS;AACvB,YAAI,MAAM,WAAW,EAAG;AAExB,YAAI,SAAS,SAAS,GAAG;AACvB,0BAAgB,SAAS,CAAC;AAAA,QAC5B,WAAW,CAAC,iBAAiB,CAAC,aAAa,aAAa,GAAG;AACzD,0BAAgB,MAAM,CAAC,EAAE;AAAA,QAC3B;AAEA,UAAE,eAAe;AACjB;AAAA,MACF;AAAA,MAEA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,cAAc;AACjB,YAAI,CAAC,cAAe;AACpB,UAAE,eAAe;AAEjB,cAAM,cAAc,aAAa,aAAa;AAC9C,YAAI,CAAC,YAAa;AAElB,cAAM,YAAY,aAAa;AAC/B,cAAM,YAAY,UAAU,IAAI,aAAa;AAC7C,YAAI,CAAC,aAAa,UAAU,SAAS,EAAG;AAExC,cAAM,SAA2D;AAAA,UAC/D,SAAS;AAAA,UACT,WAAW;AAAA,UACX,WAAW;AAAA,UACX,YAAY;AAAA,QACd;AAEA,cAAM,SAAS,wBAAwB,aAAa,WAAW,OAAO,EAAE,GAAG,CAAC;AAC5E,YAAI,QAAQ;AACV,0BAAgB;AAChB,mBAAS,MAAM;AAAA,QACjB;AACA;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,YAAI,eAAe;AACjB,YAAE,eAAe;AACjB,gBAAM,WAAW,eAAe;AAChC,cAAI,SAAS,SAAS,aAAa,GAAG;AACpC,uBAAW;AAAA,UACb,OAAO;AACL,qBAAS,aAAa;AAAA,UACxB;AAAA,QACF;AACA;AAAA,MACF;AAAA,MAEA,KAAK,UAAU;AACb,UAAE,eAAe;AACjB,wBAAgB;AAChB,mBAAW;AACX;AAAA,MACF;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,KAAK;AACR,UAAE,eAAe;AACjB,eAAO,IAAI;AACX;AAAA,MACF;AAAA,MAEA,KAAK;AAAA,MACL,KAAK,KAAK;AACR,UAAE,eAAe;AACjB,eAAO,KAAK;AACZ;AAAA,MACF;AAAA,MAEA,KAAK,QAAQ;AACX,UAAE,eAAe;AACjB,iBAAS;AACT;AAAA,MACF;AAAA,MAEA,KAAK,KAAK;AACR,YAAI,eAAe;AACjB,YAAE,eAAe;AACjB,wBAAc;AAAA,QAChB;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,iBAAiB,WAAW,SAAS;AAG5C,SAAO,MAAM;AACX,WAAO,oBAAoB,WAAW,SAAS;AAAA,EACjD;AACF;;;ACjMA,SAAS,WAAW,KAAqB;AACvC,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAgDO,SAAS,kBACd,MACA,MACA,WACuB;AACvB,QAAM,YAA+B,CAAC;AAEtC,WAAS,OAAO,MAAiC;AAC/C,sBAAkB;AAClB,SAAK,gBAAgB;AAErB,QAAI,KAAK,MAAM,WAAW,KAAK,KAAK,MAAM,WAAW,GAAG;AACtD,WAAK,MAAM,UAAU;AACrB;AAAA,IACF;AACA,SAAK,MAAM,UAAU;AAErB,eAAW,SAAS,KAAK,OAAO;AAC9B,WAAK,YAAY,QAAQ,KAAK,CAAC;AAAA,IACjC;AACA,eAAW,SAAS,KAAK,OAAO;AAC9B,WAAK,YAAY,QAAQ,KAAK,CAAC;AAAA,IACjC;AAAA,EACF;AAEA,WAAS,QAAQ,OAA0C;AACzD,UAAM,cAAc,UAAU;AAC9B,UAAM,KAAK,SAAS,cAAc,cAAc,WAAW,KAAK;AAChE,OAAG,YAAY;AACf,QAAI,CAAC,MAAM,OAAQ,IAAG,UAAU,IAAI,gCAAgC;AAEpE,QAAI,aAAa;AACf,YAAM,MAAM;AACZ,UAAI,OAAO;AACX,UAAI,aAAa,gBAAgB,OAAO,MAAM,MAAM,CAAC;AACrD,YAAM,UAAU,MAAM,UAAU,SAAS,MAAM,KAAK;AACpD,YAAM,UAAU,MAAM,UAAU,QAAQ,MAAM,KAAK;AACnD,YAAM,UAAU,MAAM,UAAU,QAAQ,IAAI;AAC5C,UAAI,iBAAiB,SAAS,OAAO;AACrC,UAAI,iBAAiB,cAAc,OAAO;AAC1C,UAAI,iBAAiB,cAAc,OAAO;AAC1C,gBAAU,KAAK,MAAM;AACnB,YAAI,oBAAoB,SAAS,OAAO;AACxC,YAAI,oBAAoB,cAAc,OAAO;AAC7C,YAAI,oBAAoB,cAAc,OAAO;AAAA,MAC/C,CAAC;AAAA,IACH;AAEA,OAAG,YACD,0DAA0D,WAAW,MAAM,KAAK,CAAC,gDAC1C,WAAW,MAAM,KAAK,CAAC,aAC7D,UAAU,UAAU,MAAM,SAAS,OAChC,uCAAuC,MAAM,MAAM,eAAe,CAAC,YACnE;AACN,WAAO;AAAA,EACT;AAEA,WAAS,QAAQ,OAA0C;AACzD,UAAM,KAAK,SAAS,cAAc,KAAK;AACvC,OAAG,YAAY;AACf,OAAG,YACD,uFAAuF,WAAW,MAAM,KAAK,CAAC,gDACvE,WAAW,MAAM,KAAK,CAAC,aAC7D,UAAU,UAAU,MAAM,SAAS,OAChC,uCAAuC,MAAM,MAAM,eAAe,CAAC,YACnE;AACN,WAAO;AAAA,EACT;AAEA,WAAS,oBAA0B;AACjC,eAAW,OAAO,UAAW,KAAI;AACjC,cAAU,SAAS;AAAA,EACrB;AAEA,SAAO,IAAI;AAEX,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,UAAgB;AACd,wBAAkB;AAClB,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AACF;;;AChJO,IAAM,qBAAN,MAAyB;AAAA,EACtB,aAAiC;AAAA;AAAA,EAEjC,QAAuB;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/B,OAAO,OAAe,OAA2D;AAC/E,UAAM,IAAI,MAAM,YAAY,EAAE,KAAK;AAEnC,QAAI,MAAM,IAAI;AACZ,WAAK,aAAa;AAClB,WAAK,QAAQ;AACb,aAAO,oBAAI,IAAI;AAAA,IACjB;AAEA,SAAK,QAAQ;AAEb,UAAM,UAAU,oBAAI,IAAY;AAChC,eAAW,QAAQ,OAAO;AACxB,YAAM,SAAS,KAAK,SAAS,IAAI,YAAY;AAC7C,YAAM,KAAK,KAAK,GAAG,YAAY;AAC/B,UAAI,MAAM,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG;AACvC,gBAAQ,IAAI,KAAK,EAAE;AAAA,MACrB;AAAA,IACF;AAEA,SAAK,aAAa;AAClB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAkC;AAChC,SAAK,aAAa;AAClB,SAAK,QAAQ;AACb,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,aAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,WAA0B;AACxB,WAAO,KAAK;AAAA,EACd;AACF;;;AC5CO,SAAS,OAAO,KAAqB;AAC1C,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,SAAK,IAAI,WAAW,CAAC;AAErB,QAAI,KAAK,KAAK,GAAG,QAAU;AAAA,EAC7B;AAEA,SAAO,MAAM;AACf;AAGA,SAAS,KAAK,GAAmB;AAC/B,UAAQ,MAAM,KAAK;AACrB;AAmBA,IAAM,KAAK;AAEJ,SAAS,kBAAkB,OAAkB,MAAoB;AACtE,QAAM,IAAI,MAAM;AAChB,MAAI,MAAM,EAAG;AAEb,aAAW,QAAQ,OAAO;AAExB,UAAM,KAAK,KAAK,OAAO,GAAG,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC;AAC9C,UAAM,KAAK,KAAK,OAAO,GAAG,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC;AAC9C,UAAM,IAAI,KAAK,KAAK,KAAK,EAAE;AAC3B,UAAM,QAAQ,KAAK,KAAK,KAAK;AAE7B,QAAI,KAAK;AACT,QAAI,KAAK;AACT,QAAI,KAAK,WAAW;AAElB,YAAM,KAAK,KAAK,OAAO,GAAG,KAAK,SAAS,IAAI,IAAI,EAAE,CAAC,IAAI,KAAK,KAAK;AACjE,YAAM,OAAO,MAAM;AACnB,WAAK,KAAK,IAAI,EAAE,IAAI;AACpB,WAAK,KAAK,IAAI,EAAE,IAAI;AAAA,IACtB;AAEA,SAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK;AAChC,SAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK;AAAA,EAClC;AACF;;;ACjEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAQP,IAAM,uBAAuB;AAE7B,IAAM,sBAAsB;AAE5B,IAAM,oBAAoB;AAE1B,IAAM,2BAA2B;AAa1B,SAAS,gBAAgB,YAAoB,WAAW,mBAA2B;AACxF,MAAI,EAAE,aAAa,MAAM,cAAc,EAAG,QAAO;AACjD,QAAM,IAAI,KAAK,KAAK,KAAK,IAAI,QAAQ,IAAI,KAAK,IAAI,IAAI,UAAU,CAAC;AACjE,SAAO,KAAK,IAAI,qBAAqB,KAAK,IAAI,GAAG,CAAC,CAAC;AACrD;AAmBA,SAAS,aAAa,OAAmB,UAAkB;AACzD,SAAO,CAAC,UAAkB;AACxB,UAAM,KAAK,oBAAI,IAAoB;AACnC,UAAM,KAAK,oBAAI,IAAoB;AACnC,UAAM,QAAQ,oBAAI,IAAoB;AAEtC,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,UAAW;AACrB,YAAM,IAAI,KAAK;AACf,SAAG,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,MAAM,KAAK,KAAK,EAAE;AAC1C,SAAG,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,MAAM,KAAK,KAAK,EAAE;AAC1C,YAAM,IAAI,IAAI,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,IACtC;AAEA,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO;AAC1B,SAAG,IAAI,GAAG,GAAG,IAAI,CAAC,IAAK,CAAC;AACxB,SAAG,IAAI,GAAG,GAAG,IAAI,CAAC,IAAK,CAAC;AAAA,IAC1B;AAEA,UAAM,IAAI,WAAW;AACrB,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,UAAW;AACrB,YAAM,UAAU,GAAG,IAAI,KAAK,SAAS;AACrC,YAAM,UAAU,GAAG,IAAI,KAAK,SAAS;AACrC,WAAK,MAAM,KAAK,MAAM,MAAM,WAAW,KAAK,KAAK,MAAM;AACvD,WAAK,MAAM,KAAK,MAAM,MAAM,WAAW,KAAK,KAAK,MAAM;AAAA,IACzD;AAAA,EACF;AACF;AAgBA,SAAS,YAAY,OAAmB,SAAuB;AAC7D,SAAO,CAAC,UAAkB;AACxB,QAAI,CAAC,QAAQ,UAAU,QAAQ,UAAU,EAAG;AAC5C,UAAM,KAAK,QAAQ,SAAS,QAAQ;AACpC,UAAM,IAAI,QAAQ,WAAW;AAC7B,eAAW,QAAQ,OAAO;AACxB,YAAM,MAAM,KAAK,KAAK,KAAK,QAAQ;AACnC,YAAM,MAAM,KAAK,KAAK,KAAK,QAAQ;AACnC,YAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,UAAI,SAAS,GAAI;AACjB,YAAM,OAAO,KAAK,KAAK,KAAK,KAAK;AAEjC,YAAM,WAAW,QAAQ,SAAS,QAAQ,QAAQ;AAClD,YAAM,OAAQ,IAAI,UAAW;AAC7B,WAAK,MAAM,KAAK,MAAM,KAAK,KAAK;AAChC,WAAK,MAAM,KAAK,MAAM,KAAK,KAAK;AAAA,IAClC;AAAA,EACF;AACF;AAUO,IAAM,oBAAN,MAAM,mBAAkB;AAAA,EACrB,SAAwB;AAAA,EACxB,UAAkD;AAAA,EAClD,YAAwB,CAAC;AAAA,EACzB,cAAqC,oBAAI,IAAI;AAAA,EAC7C,SAA8B;AAAA,EAC9B,YAAoC;AAAA,EACpC,YAAY;AAAA,EACZ,YAA2B;AAAA;AAAA,EAE3B,eAAe;AAAA;AAAA,EAEf,oBAAoB;AAAA;AAAA,EAEpB,kBAAkB;AAAA,EAClB,qBAAqB;AAAA;AAAA,EAErB,MACN,OAAO,gBAAgB,cAAc,MAAM,YAAY,IAAI,IAAI,MAAM,KAAK,IAAI;AAAA;AAAA,EAGxE,YAAuB,CAAC;AAAA,EACxB,YAAuB,CAAC;AAAA,EACxB,aAA4C;AAAA;AAAA,EAG5C,UAAwB,EAAE,GAAG,GAAG,GAAG,GAAG,QAAQ,OAAO,QAAQ,GAAG,UAAU,EAAE;AAAA;AAAA;AAAA,EAG5E,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EAEpB,cAAc;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQvB,OAAO,OACL,OACA,OACA,QACA,MACmB;AACnB,UAAM,MAAM,IAAI,mBAAkB;AAClC,QAAI,MAAM,IAAK,KAAI,MAAM,KAAK;AAE9B,UAAM,YAAY,OAAO,WAAW;AAEpC,QAAI,WAAW;AACb,UAAI,WAAW,OAAO,OAAO,MAAM;AAAA,IACrC,OAAO;AACL,UAAI,SAAS,OAAO,OAAO,MAAM;AAAA,IACnC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,OAAO,IAAwB;AAC7B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,UAAU,IAA2B;AACnC,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAGA,OAAO,OAAsB;AAC3B,QAAI,KAAK,UAAW;AAEpB,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,YAAY,EAAE,MAAM,UAAU,MAAM,CAAC;AAAA,IACnD,WAAW,KAAK,SAAS;AACvB,WAAK,QAAQ,MAAM,SAAS,GAAG,EAAE,QAAQ;AACzC,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAQ,IAAY,GAAW,GAAW,aAA4B;AACpE,QAAI,KAAK,UAAW;AAEpB,QAAI,KAAK,QAAQ;AAGf,WAAK,OAAO;AAAA,QACV,eAAe,OACX,EAAE,MAAM,OAAO,QAAQ,IAAI,GAAG,GAAG,YAAY,IAC7C,EAAE,MAAM,OAAO,QAAQ,IAAI,GAAG,EAAE;AAAA,MACtC;AAAA,IACF,OAAO;AACL,YAAM,OAAO,KAAK,YAAY,IAAI,EAAE;AACpC,UAAI,MAAM;AACR,aAAK,KAAK;AACV,aAAK,KAAK;AAAA,MACZ;AACA,UAAI,eAAe,QAAQ,KAAK,SAAS;AACvC,aAAK,kBAAkB;AACvB,aAAK,gBAAgB;AACrB,aAAK,QAAQ,QAAQ;AACrB,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,UAAU,IAAY,aAA4B;AAChD,QAAI,KAAK,UAAW;AAEpB,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO;AAAA,QACV,eAAe,OACX,EAAE,MAAM,SAAS,QAAQ,IAAI,YAAY,IACzC,EAAE,MAAM,SAAS,QAAQ,GAAG;AAAA,MAClC;AAAA,IACF,OAAO;AACL,YAAM,OAAO,KAAK,YAAY,IAAI,EAAE;AACpC,UAAI,MAAM;AACR,aAAK,KAAK;AACV,aAAK,KAAK;AAAA,MACZ;AACA,UAAI,eAAe,MAAM;AAIvB,aAAK,kBAAkB;AACvB,aAAK,gBAAgB;AACrB,aAAK,aAAa;AAAA,MACpB,WAAW,KAAK,WAAW,KAAK,QAAQ,MAAM,IAAI,KAAK;AAErD,aAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ;AAChC,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,GAAW,GAAW,QAAuB;AACtD,QAAI,KAAK,UAAW;AAEpB,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,YAAY,EAAE,MAAM,WAAW,GAAG,GAAG,OAAO,CAAC;AAAA,IAC3D,OAAO;AAGL,UAAI,KAAK,QAAQ,UAAU,EAAG;AAC9B,WAAK,QAAQ,IAAI;AACjB,WAAK,QAAQ,IAAI;AACjB,WAAK,QAAQ,SAAS;AACtB,WAAK,oBAAoB,SAAS,OAAO;AACzC,UAAI,KAAK,SAAS;AAChB,aAAK,gBAAgB;AACrB,YAAI,QAAQ;AACV,eAAK,QAAQ,QAAQ;AACrB,eAAK,aAAa;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,kBAAwB;AAC9B,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,QAAQ,YAAY,KAAK,IAAI,KAAK,iBAAiB,KAAK,iBAAiB,CAAC;AAAA,EACjF;AAAA;AAAA,EAGA,SAAS,IAAY,GAAW,GAAiB;AAC/C,QAAI,KAAK,UAAW;AAEpB,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,YAAY,EAAE,MAAM,QAAQ,QAAQ,IAAI,GAAG,EAAE,CAAC;AAAA,IAC5D,OAAO;AACL,YAAM,OAAO,KAAK,YAAY,IAAI,EAAE;AACpC,UAAI,MAAM;AACR,aAAK,KAAK;AACV,aAAK,KAAK;AAAA,MACZ;AACA,UAAI,KAAK,WAAW,KAAK,QAAQ,MAAM,IAAI,KAAK;AAC9C,aAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ;AAChC,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,UAAgB;AACd,SAAK,YAAY;AAEjB,QAAI,KAAK,cAAc,MAAM;AAC3B,2BAAqB,KAAK,SAAS;AACnC,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,YAAY,EAAE,MAAM,OAAO,CAAC;AACxC,WAAK,OAAO,UAAU;AACtB,WAAK,SAAS;AAAA,IAChB;AAEA,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,KAAK;AAClB,WAAK,UAAU;AAAA,IACjB;AAEA,SAAK,SAAS;AACd,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAMQ,WAAW,OAAkB,OAAkB,QAAsC;AAE3F,SAAK,YAAY;AACjB,SAAK,YAAY;AACjB,SAAK,aAAa;AAWlB,UAAM,UAAU,EAAE,MAAM,QAAiB,OAAO,OAAO,OAAO;AAC9D,UAAM,aAAa,CAAC,WAAmB;AACrC,WAAK,SAAS;AAEd,aAAO,YAAY,CAAC,UAA0C;AAC5D,YAAI,KAAK,UAAW;AACpB,cAAM,MAAM,MAAM;AAElB,gBAAQ,IAAI,MAAM;AAAA,UAChB,KAAK;AACH,iBAAK,SAAS,IAAI,OAAO,IAAI,KAAK;AAClC;AAAA,UACF,KAAK;AACH,iBAAK,YAAY;AACjB;AAAA,UACF,KAAK;AACH,oBAAQ,MAAM,qCAAqC,IAAI,OAAO;AAC9D;AAAA,QACJ;AAAA,MACF;AAEA,aAAO,YAAY,OAAO;AAAA,IAC5B;AAEA,QAAI;AACF,YAAM,IAAI,IAAI,OAAO,IAAI,IAAI,0BAA0B,YAAY,GAAG,GAAG;AAAA,QACvE,MAAM;AAAA,MACR,CAAC;AAED,QAAE,UAAU,MAAM;AAKhB,YAAI,KAAK,UAAW;AACpB,UAAE,UAAU;AACZ,aAAK,SAAS;AAEd,YAAI;AACF,gBAAM,QAAQ,IAAI,IAAI,YAAY,IAAI,QAAQ,YAAY,uBAAuB,CAAC;AAClF,gBAAM,KAAK,IAAI,OAAO,OAAO,EAAE,MAAM,SAAS,CAAC;AAE/C,aAAG,UAAU,MAAM;AAEjB,gBAAI,KAAK,UAAW;AACpB,oBAAQ,KAAK,iEAAiE;AAC9E,eAAG,UAAU;AACb,iBAAK,SAAS;AACd,iBAAK,SAAS,KAAK,WAAW,KAAK,WAAW,KAAK,UAAW;AAAA,UAChE;AAEA,qBAAW,EAAE;AAAA,QACf,QAAQ;AACN,kBAAQ,KAAK,iEAAiE;AAC9E,eAAK,SAAS,KAAK,WAAW,KAAK,WAAW,KAAK,UAAW;AAAA,QAChE;AAAA,MACF;AAEA,iBAAW,CAAC;AAAA,IACd,QAAQ;AAEN,cAAQ,KAAK,iEAAiE;AAC9E,WAAK,SAAS,OAAO,OAAO,MAAM;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,SAAS,OAAkB,OAAkB,QAAsC;AACzF,SAAK,YAAY,MAAM,IAAI,CAAC,OAAO;AAAA,MACjC,IAAI,EAAE;AAAA,MACN,GAAG,EAAE;AAAA,MACL,GAAG,EAAE;AAAA,MACL,QAAQ,EAAE;AAAA,MACV,WAAW,EAAE;AAAA,IACf,EAAE;AAEF,SAAK,cAAc,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAE/D,UAAM,YAAY,UAAU,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,EACrD,GAAG,CAAC,MAAO,EAAe,EAAE,EAC5B,SAAS,OAAO,YAAY;AAC/B,QAAI,OAAO,gBAAgB,MAAM;AAC/B,gBAAU,SAAS,OAAO,YAAY;AAAA,IACxC;AAEA,UAAM,UAAU,OAAO,oBAAoB;AAE3C,SAAK,UAAU,gBAA0B,KAAK,SAAS,EACpD,MAAM,QAAQ,SAAS,EACvB,MAAM,UAAU,cAAc,EAAE,SAAS,OAAO,cAAc,CAAC,EAC/D;AAAA,MACC;AAAA,MACA,aAAuB,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AAAA,IAC3D,EAEC,MAAM,YAAY,OAAiB,CAAC,EAAE,SAAS,IAAI,CAAC,EACpD,MAAM,YAAY,OAAiB,CAAC,EAAE,SAAS,IAAI,CAAC,EACpD,WAAW,OAAO,UAAU,EAC5B,cAAc,OAAO,aAAa,EAClC,KAAK;AAGR,QAAI,OAAO,gBAAgB,OAAO;AAChC,WAAK,QAAQ,MAAM,UAAU,YAAY,GAAG,CAAC,CAAC;AAAA,IAChD;AAGA,QAAI,OAAO,YAAY;AACrB,YAAM,YAAY,aAAa,KAAK,WAAW,OAAO,WAAW,QAAQ;AAEzE,WAAK,QAAQ,MAAM,WAAW,SAAsD;AAAA,IACtF;AAKA,SAAK,QAAQ,SAAS;AACtB,SAAK,QAAQ,SAAS,OAAO,iBAAiB,UAAU;AACxD,SAAK,QAAQ,WAAW,OAAO,iBAAiB,YAAY;AAC5D,SAAK,kBAAkB;AACvB,SAAK,oBAAoB;AACzB,UAAM,WAAW,YAAY,KAAK,WAAW,KAAK,OAAO;AACzD,SAAK,QAAQ,MAAM,UAAU,QAAqD;AAGlF,QAAI,OAAO,gBAAgB,MAAM;AAC/B,WAAK,QAAQ,MAAM,OAAO,YAAY;AAAA,IACxC;AAIA,SAAK,eAAe,gBAAgB,OAAO,UAAU;AAMrD,SAAK,kBAAkB,OAAO,eAAe;AAC7C,SAAK,qBAAqB,OAAO,kBAAkB;AACnD,SAAK,oBAAoB,KAAK,kBAAkB;AAGhD,SAAK,aAAa,IAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,aAAa,WAAW,OAAa;AAC3C,QAAI,CAAC,KAAK,WAAW,KAAK,UAAW;AAGrC,QAAI,KAAK,cAAc,MAAM;AAC3B,2BAAqB,KAAK,SAAS;AACnC,WAAK,YAAY;AAAA,IACnB;AAEA,UAAM,MAAM,KAAK;AACjB,UAAM,WAAW,KAAK;AACtB,QAAI,YAAY;AAOhB,UAAM,YAAY,MAAM;AACtB,UAAI,KAAK,aAAa,CAAC,KAAK,QAAS;AACrC,WAAK,YAAY;AAEjB,YAAMC,SAAQ,KAAK,IAAI;AACvB,aAAO,KAAK,kBAAkB,GAAG;AAC/B,iBAAS,IAAI,GAAG,IAAI,wBAAwB,KAAK,kBAAkB,GAAG,KAAK;AACzE,cAAI,KAAK;AACT,eAAK;AACL,cAAI,IAAI,MAAM,IAAI,mBAAmB;AACnC,iBAAK,kBAAkB;AACvB;AAAA,UACF;AAAA,QACF;AAEA,YAAI,KAAK,kBAAkB,KAAK,KAAK,IAAI,IAAIA,UAAS,KAAK,oBAAoB;AAC7E,eAAK,kBAAkB;AACvB;AAAA,QACF;AAAA,MACF;AAEA,WAAK,oBAAoB;AAEzB,eAAS;AAAA,IACX;AAEA,UAAM,WAAW,MAAM;AACrB,UAAI,KAAK,aAAa,CAAC,KAAK,QAAS;AACrC,WAAK,YAAY;AAEjB,eAAS,IAAI,GAAG,IAAI,wBAAwB,YAAY,UAAU,KAAK,aAAa;AAClF,YAAI,KAAK;AACT,YAAI,IAAI,MAAM,IAAI,mBAAmB;AACnC,sBAAY;AACZ;AAAA,QACF;AAAA,MACF;AAEA,YAAM,YAAY,KAAK,UAAU,IAAI,CAAC,OAAO;AAAA,QAC3C,IAAI,EAAE;AAAA,QACN,GAAG,EAAE,KAAK;AAAA,QACV,GAAG,EAAE,KAAK;AAAA,MACZ,EAAE;AACF,YAAM,QAAQ,IAAI,MAAM;AACxB,YAAM,UAAU,QAAQ,qBAAqB,aAAa;AAE1D,WAAK,SAAS,WAAW,KAAK;AAE9B,UAAI,SAAS;AACX,aAAK,YAAY;AAAA,MACnB,OAAO;AACL,aAAK,YAAY,sBAAsB,QAAQ;AAAA,MACjD;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,oBAAoB,YAAY;AAEnD,QAAI,UAAU;AACZ,qBAAe,KAAK;AAAA,IACtB,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AACF;;;AC1mBO,IAAMC,gBAAN,cAA2B,aAAoC;AAAC;;;ACsBvE,SAAS,UAAU,GAAiC;AAClD,SAAO,KAAK,UAAU;AAAA,IACpB,EAAE;AAAA,IACF,EAAE;AAAA,IACF,EAAE,aAAa,CAAC,EAAE,WAAW,OAAO,EAAE,WAAW,QAAQ,IAAI;AAAA,IAC7D,EAAE;AAAA,IACF,EAAE;AAAA,IACF,EAAE;AAAA,IACF,EAAE,oBAAoB;AAAA,IACtB,EAAE,gBAAgB;AAAA,IAClB,EAAE,eAAe;AAAA,IACjB,EAAE,QAAQ;AAAA,IACV,EAAE,eAAe;AAAA,IACjB,EAAE,kBAAkB;AAAA,EACtB,CAAC;AACH;AAGO,SAAS,sBAAsB,GAAyB,GAAkC;AAC/F,SAAO,UAAU,CAAC,MAAM,UAAU,CAAC;AACrC;;;ACMA,IAAM,SAAS;AAEf,SAAS,OAAO,IAAY,MAAkB;AAG5C,QAAM,KAAM,OAAO,GAAG,EAAE,IAAI,IAAI,KAAK,IAAI,aAAe,IAAI;AAC5D,QAAM,KAAM,OAAO,GAAG,EAAE,IAAI,IAAI,KAAK,IAAI,aAAe,IAAI;AAC5D,SAAO,EAAE,GAAG,KAAK,QAAQ,GAAG,KAAK,OAAO;AAC1C;AAMA,IAAM,cAAc;AAEpB,SAAS,WAAW,IAAY,MAAkB;AAChD,QAAM,KAAK,OAAO,GAAG,EAAE,IAAI,IAAI,IAAI,IAAI;AACvC,QAAM,KAAK,OAAO,GAAG,EAAE,IAAI,IAAI,IAAI,IAAI;AACvC,QAAM,IAAI,cAAc,KAAK,KAAK,EAAE;AACpC,QAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,SAAO,EAAE,GAAG,IAAI,KAAK,IAAI,KAAK,GAAG,GAAG,IAAI,KAAK,IAAI,KAAK,EAAE;AAC1D;AAWO,SAAS,gBACd,WACA,WACA,MACA,YACA,MACiB;AACjB,QAAM,UAAU,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAClD,QAAM,UAAU,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAEnD,QAAM,qBAAqB,oBAAI,IAAgB;AAC/C,QAAM,cAAwB,CAAC;AAE/B,aAAW,KAAK,WAAW;AACzB,QAAI,QAAQ,IAAI,EAAE,EAAE,EAAG,oBAAmB,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,CAAC;AAAA,EACxE;AACA,aAAW,KAAK,KAAK,OAAO;AAC1B,QAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,EAAG,aAAY,KAAK,EAAE,EAAE;AAAA,EAC/C;AAKA,QAAM,eAAe,UAAU,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AAC/D,QAAM,eAAe,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;AAC7E,QAAM,eAAe,UAAU,OAAO,CAAC,MAAM,CAAC,aAAa,IAAI,GAAG,EAAE,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;AACzF,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,aAAW,KAAK,WAAW;AACzB,UAAM,IAAI,GAAG,EAAE,MAAM,IAAI,EAAE,MAAM;AACjC,mBAAe,IAAI,IAAI,eAAe,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,EACxD;AACA,MAAI,oBAAoB;AACxB,QAAM,gBAAgB,IAAI,IAAI,cAAc;AAC5C,aAAW,KAAK,KAAK,OAAO;AAC1B,UAAM,IAAI,GAAG,EAAE,MAAM,IAAI,EAAE,MAAM;AACjC,UAAM,IAAI,cAAc,IAAI,CAAC;AAC7B,QAAI,KAAK,IAAI,GAAG;AACd,oBAAc,IAAI,GAAG,IAAI,CAAC;AAAA,IAC5B,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,QAAQ,SAAS,QAAQ,QAAQ,YAAY,WAAW;AAC1E,QAAM,YAAY,cAAc,WAAW,KAAK,KAAK;AACrD,QAAM,aACJ,aAAa,aAAa,sBAAsB,YAAY,KAAK,gBAAgB;AAInF,QAAM,iBAAiB,oBAAI,IAAgB;AAC3C,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,cAAc,IAAI,IAAI,WAAW;AACvC,UAAM,gBAAgB,eAAe,KAAK,KAAK;AAC/C,eAAW,MAAM,aAAa;AAC5B,YAAM,cAAc;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,IAAI,OAAO,IAAI,IAAI;AACzB,UAAI,aAAa;AACf,uBAAe,IAAI,IAAI,EAAE,GAAG,YAAY,IAAI,EAAE,GAAG,GAAG,YAAY,IAAI,EAAE,EAAE,CAAC;AAAA,MAC3E,OAAO;AACL,cAAM,OAAO,WAAW,IAAI,IAAI;AAChC,uBAAe,IAAI,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,GAAG,GAAG,KAAK,IAAI,EAAE,EAAE,CAAC;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,SAAS,eAAe,OAAyE;AAC/F,QAAM,MAAM,oBAAI,IAAsB;AACtC,QAAM,OAAO,CAAC,GAAW,MAAc;AACrC,UAAM,OAAO,IAAI,IAAI,CAAC;AACtB,QAAI,KAAM,MAAK,KAAK,CAAC;AAAA,QAChB,KAAI,IAAI,GAAG,CAAC,CAAC,CAAC;AAAA,EACrB;AACA,aAAW,KAAK,OAAO;AACrB,SAAK,EAAE,QAAQ,EAAE,MAAM;AACvB,SAAK,EAAE,QAAQ,EAAE,MAAM;AAAA,EACzB;AACA,SAAO;AACT;AAOA,SAAS,0BACP,IACA,WACA,oBACA,aACW;AACX,QAAM,YAAY,UAAU,IAAI,EAAE;AAClC,MAAI,CAAC,UAAW,QAAO;AACvB,aAAW,OAAO,WAAW;AAC3B,QAAI,YAAY,IAAI,GAAG,EAAG;AAC1B,UAAM,MAAM,mBAAmB,IAAI,GAAG;AACtC,QAAI,IAAK,QAAO;AAAA,EAClB;AACA,SAAO;AACT;AAOA,SAAS,cACP,MACA,MACS;AACT,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,QAAM,MAAM,CAAC,MAA0C,GAAG,EAAE,MAAM,KAAK,EAAE,MAAM;AAC/E,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,KAAK,MAAM;AACpB,UAAM,IAAI,IAAI,CAAC;AACf,WAAO,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,EACxC;AACA,aAAW,KAAK,MAAM;AACpB,UAAM,IAAI,IAAI,CAAC;AACf,UAAM,IAAI,OAAO,IAAI,CAAC;AACtB,QAAI,CAAC,EAAG,QAAO;AACf,WAAO,IAAI,GAAG,IAAI,CAAC;AAAA,EACrB;AACA,SAAO;AACT;;;Ad9CA,IAAM,yBAAyB;AAE/B,IAAM,yBAAyB;AAE/B,IAAM,6BAA6B;AAGnC,IAAM,sBAAsB;AAcrB,SAAS,YACd,WACA,MACA,SACe;AACf,MAAI,cAAc;AAClB,MAAI;AACJ,MAAI,YAAY;AAGhB,MAAI,UAA8B;AAClC,MAAI,SAAmC;AACvC,MAAI,WAA+B;AACnC,MAAI,WAA+B;AACnC,MAAI,mBAAiD;AAGrD,MAAI,WAAuC;AAC3C,MAAI,aAAuC;AAC3C,QAAM,eAAe,IAAIC,cAAa;AACtC,MAAI,qBAAqD;AACzD,QAAM,gBAAgB,IAAI,mBAAmB;AAC7C,MAAI,iBAAwC;AAC5C,MAAI,kBAAuC;AAC3C,MAAI,mBAAwC;AAG5C,MAAI,kBAAoC,CAAC;AACzC,MAAI,kBAAoC,CAAC;AACzC,MAAI,eAAe,oBAAI,IAAyB;AAChD,MAAI,cAAc,oBAAI,IAAqC;AAC3D,MAAI,cAAc,oBAAI,IAAqC;AAC3D,MAAI,gBAA+B;AACnC,MAAI,gBAA+B;AACnC,MAAI,kBAAkB,oBAAI,IAAY;AACtC,MAAI,cAA6B;AACjC,MAAI,cAAc;AAClB,MAAI,cAAc;AAIlB,QAAM,YAAY,IAAI,mBAAmB,MAAM,eAAe,CAAC;AAC/D,MAAI,iBAAuD;AAC3D,MAAI,kBAAkB;AAEtB,MAAI,sBAAsB;AAE1B,MAAI,eAAsC;AAE1C,MAAI,eAAsC;AAE1C,MAAI,YAAY;AAChB,MAAI,sBAAsB;AAc1B,MAAI,eAAmC;AACvC,MAAI,sBAAqC;AAOzC,MAAI,UAAU,oBAAI,IAAY;AAC9B,MAAI,mBAAmB,oBAAI,IAAY;AACvC,MAAI,qBAAyC;AAC7C,MAAI,kBAA+C;AAEnD,QAAM,eAAe,oBAAI,IAAY;AAErC,MAAI,eAAe,oBAAI,IAAoB;AAE3C,MAAI,kBAA0C;AAE9C,MAAI,YAAmC;AAEvC,MAAI,mBAA6D;AAOjE,MAAI,mBAAmB;AACvB,MAAI,iBAAiB;AACrB,MAAI,kBAAkB;AAGtB,MAAI,mBAA+C;AACnD,MAAI,oBAAkE;AACtE,MAAI,sBAAsB;AAC1B,MAAI,iBAAwC;AAK5C,MAAI,uBAAuB,SAAS,oBAAoB;AAMxD,MAAI,gBAA4C;AAChD,MAAI,gBACF;AAMF,WAAS,cAAoB;AAC3B,kBAAc;AACd,QAAI,mBAAmB,KAAM,cAAa,cAAc;AACxD,qBAAiB,WAAW,MAAM;AAChC,oBAAc;AACd,uBAAiB;AACjB,oBAAc;AACd,qBAAe;AAAA,IACjB,GAAG,GAAG;AAAA,EACR;AAEA,WAAS,yBAA4D;AACnE,UAAM,OAAO,UAAU,sBAAsB;AAC7C,WAAO;AAAA,MACL,OAAO,KAAK,IAAI,KAAK,SAAS,KAAK,GAAG;AAAA,MACtC,QAAQ,KAAK,IAAI,KAAK,UAAU,KAAK,GAAG;AAAA,IAC1C;AAAA,EACF;AAOA,WAAS,SAAS,SAAuB;AACvC,QAAI,aAAa,IAAI,OAAO,EAAG;AAC/B,iBAAa,IAAI,OAAO;AACxB,QAAI,SAAS,OAAQ,SAAQ,OAAO,OAAO;AAAA,QACtC,SAAQ,KAAK,OAAO;AAAA,EAC3B;AAEA,WAAS,UAA4B;AACnC,UAAM,EAAE,OAAO,OAAO,IAAI,uBAAuB;AACjD,UAAM,WAAW,gBAAgB,SAAS,QAAQ;AAElD,UAAM,cAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB;AAAA,MACA,WAAW,SAAS;AAAA,MACpB,QAAQ;AAAA,IACV;AAEA,WAAO,aAAa,aAAa,WAAW;AAAA,EAC9C;AAEA,WAAS,gBAAsB;AAC7B,kBAAc,IAAI,IAAI,YAAY,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AACxE,kBAAc,IAAI,IAAI,YAAY,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,KAAK,EAAE,MAAM,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AAI9F,mBAAe,oBAAI,IAAI;AACvB,UAAM,QAAQ,YAAY;AAC1B,QAAI,OAAO;AACT,iBAAW,KAAK,YAAY,OAAO;AACjC,cAAM,IAAI,EAAE,OAAO,KAAK;AACxB,YAAI,KAAK,KAAM,cAAa,IAAI,EAAE,IAAI,OAAO,CAAC,CAAC;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAEA,WAAS,kBAAkB,OAAsD;AAC/E,UAAM,MAAM,oBAAI,IAAyB;AACzC,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,IAAI,IAAI,KAAK,MAAM,EAAG,KAAI,IAAI,KAAK,QAAQ,oBAAI,IAAI,CAAC;AACzD,UAAI,CAAC,IAAI,IAAI,KAAK,MAAM,EAAG,KAAI,IAAI,KAAK,QAAQ,oBAAI,IAAI,CAAC;AACzD,UAAI,IAAI,KAAK,MAAM,EAAG,IAAI,KAAK,MAAM;AACrC,UAAI,IAAI,KAAK,MAAM,EAAG,IAAI,KAAK,MAAM;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AAEA,WAAS,WAAW,OAAuC;AACzD,WAAO,MAAM,IAAI,CAAC,OAAO;AAAA,MACvB,IAAI,EAAE;AAAA,MACN,QAAQ,EAAE;AAAA,MACV,WAAW,EAAE;AAAA,IACf,EAAE;AAAA,EACJ;AAEA,WAAS,WAAW,OAAuC;AACzD,WAAO,MAAM,IAAI,CAAC,OAAO;AAAA,MACvB,QAAQ,EAAE;AAAA,MACV,QAAQ,EAAE;AAAA,IACZ,EAAE;AAAA,EACJ;AAMA,WAAS,aAAa,QAAyC;AAC7D,WAAO,YAAY,IAAI,MAAM,KAAK,CAAC;AAAA,EACrC;AAMA,WAAS,mBACP,IACA,IACA,IACA,IACA,IACA,IACQ;AACR,UAAM,KAAK,KAAK;AAChB,UAAM,KAAK,KAAK;AAChB,UAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,QAAI,UAAU,EAAG,QAAO,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE;AACnD,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,MAAM,KAAK,MAAM,MAAM,KAAK,CAAC;AAC5E,WAAO,KAAK,MAAM,MAAM,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,GAAG;AAAA,EAC1D;AAMA,WAAS,YAAY,QAAgB,QAAgB,WAAkC;AACrF,QAAI,WAAW;AACf,QAAI,aAA4B;AAEhC,eAAW,QAAQ,iBAAiB;AAClC,YAAM,OAAO;AAAA,QACX;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AACA,UAAI,OAAO,UAAU;AACnB,mBAAW;AACX,qBAAa,GAAG,KAAK,MAAM,KAAK,KAAK,MAAM;AAAA,MAC7C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAKA,WAAS,aAAa,QAAgD;AACpE,WAAO,YAAY,IAAI,MAAM,KAAK;AAAA,EACpC;AAMA,WAAS,YAAkB;AACzB,UAAM,EAAE,OAAO,OAAO,IAAI,uBAAuB;AACjD,UAAM,SAAS,gBAAgB,SAAS,QAAQ;AAGhD,cAAU,SAAS,cAAc,KAAK;AACtC,YAAQ,YAAY,SAAS,6BAA6B;AAC1D,QAAI,QAAQ;AACV,gBAAU,UAAU,IAAI,SAAS;AAAA,IACnC,OAAO;AACL,gBAAU,UAAU,OAAO,SAAS;AAAA,IACtC;AAKA,UAAM,gBAAgB,YAAY;AAClC,QAAI,eAAe;AACjB,YAAM,IAAI,QAAQ;AAClB,QAAE,YAAY,WAAW,cAAc,OAAO,UAAU;AACxD,QAAE,YAAY,aAAa,cAAc,OAAO,IAAI;AACpD,QAAE,YAAY,uBAAuB,cAAc,OAAO,QAAQ,cAAc,OAAO,IAAI;AAC3F,QAAE,YAAY,oBAAoB,cAAc,MAAM,MAAM;AAC5D,QAAE,aAAa,cAAc,MAAM;AAAA,IACrC;AAGA,eAAW,SAAS,cAAc,KAAK;AACvC,aAAS,YAAY;AACrB,IAAAC,cAAa;AACb,YAAQ,YAAY,QAAQ;AAG5B,aAAS,SAAS,cAAc,QAAQ;AACxC,WAAO,YAAY;AACnB,WAAO,aAAa,QAAQ,KAAK;AACjC,QAAI,YAAY,MAAM,SAAS;AAC7B,aAAO,aAAa,cAAc,YAAY,KAAK,OAAO;AAAA,IAC5D;AACA,YAAQ,YAAY,MAAM;AAG1B,QAAI,cAAc,MAAM,OAAO;AAC7B,iBAAW,SAAS,cAAc,KAAK;AACvC,eAAS,YAAY;AACrB,MAAAC,cAAa;AACb,cAAQ,YAAY,QAAQ;AAAA,IAC9B;AAEA,cAAU,YAAY,OAAO;AAC7B,oBAAgB;AAGhB,UAAM,eAAe,KAAK,IAAI,QAAQ,GAAG;AACzC,eAAW,IAAI,oBAAoB,MAAM;AACzC,aAAS,OAAO,OAAO,YAAY;AAAA,EACrC;AAEA,WAASD,gBAAqB;AAC5B,QAAI,CAAC,SAAU;AACf,QAAI,OAAO;AAEX,QAAI,YAAY,OAAO,OAAO;AAC5B,cAAQ,wBAAwBE,YAAW,YAAY,OAAO,MAAM,IAAI,CAAC;AAAA,IAC3E;AACA,QAAI,YAAY,OAAO,UAAU;AAC/B,cAAQ,0BAA0BA,YAAW,YAAY,OAAO,SAAS,IAAI,CAAC;AAAA,IAChF;AAEA,aAAS,YAAY;AAGrB,QAAI,CAAC,MAAM;AACT,eAAS,MAAM,UAAU;AAAA,IAC3B,OAAO;AACL,eAAS,MAAM,UAAU;AAAA,IAC3B;AAAA,EACF;AAOA,WAAS,gBAA6C;AACpD,WAAO,SAAS,UAAU,YAAY;AAAA,EACxC;AAGA,WAAS,eAA0D;AACjE,UAAM,IAAI,cAAc;AACxB,QAAI,KAAK,OAAO,MAAM,UAAU;AAC9B,aAAO,EAAE,aAAa,EAAE,eAAe,MAAM,QAAQ,EAAE,UAAU,KAAK;AAAA,IACxE;AACA,WAAO,EAAE,aAAa,MAAM,QAAQ,KAAK;AAAA,EAC3C;AAGA,WAAS,iBAAuF;AAC9F,WAAO,EAAE,OAAO,UAAU,EAAE,OAAO,OAAO,UAAU,EAAE,MAAM;AAAA,EAC9D;AAGA,WAASD,gBAAqB;AAC5B,QAAI,CAAC,SAAU;AACf,UAAM,MAAM,aAAa;AACzB,QAAI,CAAC,kBAAkB;AACrB,yBAAmB,kBAAkB,UAAU,eAAe,GAAG;AAAA,QAC/D,aAAa,IAAI;AAAA,QACjB,QAAQ,IAAI;AAAA,QACZ,UAAU,CAAC,UAAU,qBAAqB,KAAK;AAAA,QAC/C,SAAS,CAAC,UAAU;AAClB,gBAAM,QAAQ,YAAY;AAC1B,mBAAS,gBAAgB,UAAU,QAAQ,QAAQ,EAAE,OAAO,MAAM,IAAI,IAAI;AAAA,QAC5E;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AACL,uBAAiB,OAAO,eAAe,CAAC;AAAA,IAC1C;AAAA,EACF;AAGA,WAAS,wBAA8B;AACrC,QAAI,iBAAkB,kBAAiB,OAAO,eAAe,CAAC;AAC9D,oBAAgB;AAAA,EAClB;AAOA,WAAS,kBAAwB;AAC/B,QAAI,CAAC,SAAU;AACf,UAAM,UAAU,UAAU,eAAe;AACzC,aAAS,MAAM,QAAQ,UAAU,IAAI,GAAG,UAAU,EAAE,OAAO;AAAA,EAC7D;AAOA,WAAS,iBAAyB;AAChC,QAAI,CAAC,YAAY,SAAS,MAAM,YAAY,OAAQ,QAAO;AAC3D,WAAO,SAAS;AAAA,EAClB;AAUA,WAAS,qBAA8B;AACrC,WACE,YAAY,YAAY,eAAe,YAAY,MAAM,UAAU;AAAA,EAEvE;AAOA,WAAS,qBAA8B;AACrC,WACE,YAAY,YAAY,oBAAoB,QAC5C,YAAY,MAAM,UAAU,0BAC5B,CAAC,qBAAqB;AAAA,EAE1B;AAiBA,WAAS,eAAe,MAMf;AACP,UAAM,WAAW,WAAW,YAAY,KAAK;AAC7C,UAAM,WAAW,WAAW,YAAY,KAAK;AAC7C,UAAM,SAAS,YAAY;AAE3B,QAAI,MAAM,WAAW;AAInB,wBAAkB,UAAU,OAAO,QAAQ,CAAC;AAC5C,iBAAW,KAAK,UAAU;AACxB,cAAM,IAAI,KAAK,UAAU,IAAI,EAAE,EAAE;AACjC,YAAI,GAAG;AACL,YAAE,IAAI,EAAE;AACR,YAAE,IAAI,EAAE;AAAA,QACV;AAAA,MACF;AAAA,IACF,OAAO;AAGL,wBAAkB,UAAU,OAAO,QAAQ,CAAC;AAAA,IAC9C;AAEA,iBAAa,kBAAkB,OAAO,UAAU,UAAU;AAAA,MACxD,gBAAgB,OAAO;AAAA,MACvB,cAAc,OAAO;AAAA,MACrB,YAAY,OAAO;AAAA,MACnB,YAAY,OAAO;AAAA,MACnB,eAAe,OAAO;AAAA,MACtB,iBAAiB,OAAO;AAAA,MACxB,kBAAkB,OAAO;AAAA,MACzB,cAAc,OAAO;AAAA;AAAA;AAAA;AAAA,MAIrB,aAAa,MAAM,iBAAiB,QAAQ,OAAO;AAAA,MACnD,aAAa,MAAM,aAAa,IAAI,OAAO;AAAA,MAC3C,gBAAgB,OAAO;AAAA,MACvB,cAAc,MAAM,gBAAgB,OAAO;AAAA;AAAA;AAAA,MAG3C,iBAAiB,mBAAmB,IAAI,YAAY,YAAY,kBAAkB;AAAA,IACpF,CAAC;AAID,gBAAY,MAAM,gBAAgB;AAElC,QAAI,oBAAoB;AAGxB,QAAI,iBAAiB,MAAM,gBAAgB;AAE3C,eAAW,OAAO,CAAC,WAAW,UAAU;AACtC,UAAI,UAAW;AACf,kBAAY;AAGZ,YAAM,SAAS,oBAAI,IAAsC;AACzD,iBAAW,KAAK,WAAW;AACzB,eAAO,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,CAAC;AAAA,MACrC;AAGA,wBAAkB,YAAY,MAAM,IAAI,CAAC,MAAM,UAAU;AACvD,cAAM,MAAM,OAAO,IAAI,KAAK,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AAChD,eAAO,EAAE,GAAG,MAAM,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,MAAM;AAAA,MAC9C,CAAC;AAGD,wBAAkB,YAAY,MAAM,IAAI,CAAC,SAAS;AAChD,cAAM,MAAM,OAAO,IAAI,KAAK,MAAM,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AACpD,cAAM,MAAM,OAAO,IAAI,KAAK,MAAM,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AACpD,eAAO;AAAA,UACL,GAAG;AAAA,UACH,SAAS,IAAI;AAAA,UACb,SAAS,IAAI;AAAA,UACb,SAAS,IAAI;AAAA,UACb,SAAS,IAAI;AAAA,QACf;AAAA,MACF,CAAC;AAGD,mBAAa,QAAQ,eAAe;AAKpC,UACE,CAAC,kBACD,gBAAgB,SAAS,KACzB,sBACA,SAAS,cAAc,OACvB;AACA,yBAAiB;AACjB,sBAAc;AAAA,MAChB,WAAW,CAAC,kBAAkB,SAAS,cAAc,OAAO;AAE1D,yBAAiB;AAAA,MACnB;AAEA,oBAAc;AACd,qBAAe;AAAA,IACjB,CAAC;AAED,eAAW,UAAU,MAAM;AACzB,UAAI,kBAAmB;AACvB,0BAAoB;AAAA,IACtB,CAAC;AAAA,EACH;AAMA,WAAS,oBAAmC;AAC1C,UAAM,EAAE,OAAO,IAAI,QAAQ,GAAG,IAAI,oBAAoB;AACtD,UAAM,UAAU,YAAY,iBAAiB,eAAe,KAAK;AACjE,UAAM,EAAE,UAAU,IAAI,cAAc,UAAU,iBAAiB,IAAI,IAAI,QAAW;AAAA,MAChF,QAAQ,CAAC;AAAA,MACT,UAAU,eAAe;AAAA,IAC3B,CAAC;AACD,WAAO;AAAA,EACT;AASA,WAAS,gBAAsB;AAC7B,QAAI,CAAC,mBAAoB;AACzB,UAAM,MAAM,kBAAkB;AAC9B,UAAM,QAAQ,YAAY,WAAW,SAAS;AAG9C,UAAM,aAAa;AACnB,2BAAuB;AACvB,QAAI,cAAc,CAAC,SAAS,qBAAqB,GAAG;AAClD,yBAAmB,aAAa,GAAG;AACnC,4BAAsB;AACtB,uBAAiB;AACjB,yBAAmB;AACnB;AAAA,IACF;AAIA,UAAM,EAAE,OAAO,IAAI,QAAQ,GAAG,IAAI,oBAAoB;AACtD,UAAM,aAAa,IAAI,OAAO,IAAI,IAAI,KAAK,KAAK,GAAG,KAAK,CAAC;AACzD,uBAAmB,aAAa,UAAU;AAC1C,0BAAsB;AAEtB,qBAAiB;AACjB,uBAAmB;AAGnB,sBAAkB,MAAM,WAAW,gBAAgB,UAAU;AAG7D,QAAI,iBAAiB;AACnB,yBAAmB,cAAc,eAAe;AAChD,0BAAoB,gBAAgB,eAAe;AAAA,IACrD,OAAO;AACL,yBAAmB;AACnB,0BAAoB;AAAA,IACtB;AAGA,QAAI,MAAM,WAAW;AACnB,4BAAsB;AACtB,gBAAU,KAAK,EAAE,UAAU,MAAM,WAAW,IAAI,GAAG,MAAM;AACvD,8BAAsB;AAAA,MACxB,CAAC;AAAA,IACH;AAGA,UAAM,OAAO,YAAY,MAAM,IAAI;AACnC,qBAAiB,YAAY;AAAA,MAC3B,UAAU,MAAM;AAAA,MAChB;AAAA,MACA,OAAO,CAAC,MAAM;AACZ,2BAAmB;AACnB,sBAAc;AAAA,MAChB;AAAA,MACA,QAAQ,MAAM;AACZ,2BAAmB;AACnB,yBAAiB;AACjB,yBAAiB;AACjB,sBAAc;AAAA,MAChB;AAAA,IACF,CAAC;AACD,cAAU,IAAI,cAAc;AAAA,EAC9B;AAEA,WAAS,sBAAyD;AAChE,QAAI,CAAC,OAAQ,QAAO,EAAE,OAAO,KAAK,QAAQ,IAAI;AAC9C,UAAM,OAAO,OAAO,sBAAsB;AAC1C,WAAO;AAAA,MACL,OAAO,KAAK,IAAI,KAAK,SAAS,KAAK,GAAG;AAAA,MACtC,QAAQ,KAAK,IAAI,KAAK,UAAU,KAAK,GAAG;AAAA,IAC1C;AAAA,EACF;AAEA,WAAS,iBAAuB;AAC9B,QAAI,gBAAgB,QAAQ,UAAW;AACvC,kBAAc,sBAAsB,WAAW;AAAA,EACjD;AAOA,WAAS,kBAAkB,QAA2C;AACpE,QAAI,WAAW,KAAM,QAAO;AAC5B,QAAI,YAAY,YAAY,cAAc,YAAY;AACpD,YAAM,MAAM,aAAa,IAAI,MAAM;AACnC,YAAME,OAAM,oBAAI,IAAY,CAAC,MAAM,CAAC;AACpC,UAAI,QAAQ,QAAW;AACrB,mBAAW,CAAC,IAAI,CAAC,KAAK,aAAc,KAAI,MAAM,IAAK,CAAAA,KAAI,IAAI,EAAE;AAAA,MAC/D;AACA,aAAOA;AAAA,IACT;AAEA,UAAM,MAAM,oBAAI,IAAY,CAAC,MAAM,CAAC;AACpC,UAAM,YAAY,aAAa,IAAI,MAAM;AACzC,QAAI,UAAW,YAAW,OAAO,UAAW,KAAI,IAAI,GAAG;AACvD,WAAO;AAAA,EACT;AAGA,WAAS,mBAAkC;AACzC,WAAO;AAAA,MACL;AAAA,MACA,cAAc,WAAW;AAAA,MACzB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,WAAS,iBAAgC;AACvC,WAAO,gBAAgB,iBAAiB,GAAG,eAAe,kBAAkB,aAAa,CAAC;AAAA,EAC5F;AAOA,WAAS,SAAS,KAAmB;AACnC,UAAM,SAAS,eAAe;AAC9B,UAAM,WAAW,YAAY,WAAW,SAAS;AACjD,UAAM,WAAW,YAAY,CAAC,qBAAqB,IAAI,SAAS,WAAW;AAC3E,UAAM,OAAO,YAAY,UAAU,QAAQ,QAAQ;AAEnD,QAAI,CAAC,iBAAiB;AACpB,wBAAkB,IAAI,gBAAgB,QAAQ,UAAU,MAAM,GAAG;AACjE;AAAA,IACF;AACA,oBAAgB,SAAS,QAAQ,GAAG;AAGpC,QAAI,UAAW,WAAU,OAAO,SAAS;AACzC,gBAAY;AAAA,MACV,MAAM,CAAC,MAAuB;AAC5B,cAAM,UAAU,oBAAoB,QAAQ,CAAC,gBAAgB,UAAU,CAAC;AACxE,YAAI,CAAC,QAAS,aAAY;AAC1B,eAAO;AAAA,MACT;AAAA,MACA,QAAQ,MAAY;AAClB,oBAAY;AAAA,MACd;AAAA,MACA,QAAQ,MAAY;AAClB,oBAAY;AAAA,MACd;AAAA,IACF;AACA,cAAU,IAAI,SAAS;AAAA,EACzB;AAGA,WAAS,eAAqB;AAC5B,aAAS,YAAY,IAAI,CAAC;AAC1B,kBAAc;AACd,mBAAe;AAAA,EACjB;AAGA,WAAS,uBAAuB,QAA2C;AACzE,QAAI,aAAa,OAAQ,QAAO,IAAI,IAAI,OAAO,OAAO;AACtD,QAAI,iBAAiB,QAAQ;AAC3B,YAAMA,OAAM,oBAAI,IAAY;AAC5B,UAAI,OAAO,gBAAgB,MAAO,CAAAA,KAAI,IAAI,OAAO,WAAW;AAC5D,YAAM,YAAY,aAAa,IAAI,OAAO,WAAW;AACrD,UAAI,UAAW,YAAW,OAAO,UAAW,CAAAA,KAAI,IAAI,GAAG;AACvD,aAAOA;AAAA,IACT;AAEA,UAAM,SAAS,IAAI;AAAA,MACjB,MAAM,QAAQ,OAAO,SAAS,KAAK,IAAI,OAAO,SAAS,QAAQ,CAAC,OAAO,SAAS,KAAK;AAAA,IACvF;AACA,UAAM,QAAQ,OAAO,SAAS;AAC9B,UAAM,MAAM,oBAAI,IAAY;AAC5B,eAAW,KAAK,YAAY,OAAO;AACjC,YAAM,IAAI,EAAE,OAAO,KAAK;AACxB,UAAI,KAAK,QAAQ,OAAO,IAAI,OAAO,CAAC,CAAC,EAAG,KAAI,IAAI,EAAE,EAAE;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AAGA,WAAS,uBAA2C;AAClD,QAAI,iBAAiB,SAAS,EAAG,QAAO;AACxC,UAAM,MAAM,oBAAI,IAAY;AAC5B,eAAW,CAAC,IAAI,GAAG,KAAK,aAAc,KAAI,iBAAiB,IAAI,GAAG,EAAG,KAAI,IAAI,EAAE;AAC/E,WAAO;AAAA,EACT;AAiBA,WAAS,qBAA2B;AAClC,UAAM,SAAS,qBAAqB;AACpC,UAAM,YACJ,uBAAuB,QAAQ,mBAAmB,OAAO,IAAI,qBAAqB;AACpF,QAAI,WAAW,QAAQ,cAAc,MAAM;AACzC,YAAM,QAAQ,oBAAI,IAAY;AAC9B,iBAAW,MAAM,UAAW,KAAI,OAAO,IAAI,EAAE,EAAG,OAAM,IAAI,EAAE;AAC5D,qBAAe,MAAM,OAAO,IAAI,QAAQ;AACxC;AAAA,IACF;AACA,mBAAe,UAAU;AAAA,EAC3B;AAOA,WAAS,4BAAkC;AACzC,QAAI,oBAAoB,MAAM;AAC5B,2BAAqB;AACrB;AAAA,IACF;AACA,UAAM,UAAU,IAAI,IAAI,YAAY,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC1D,UAAM,WAAW,IAAI;AAAA,MACnB,CAAC,GAAG,uBAAuB,eAAe,CAAC,EAAE,OAAO,CAAC,OAAO,QAAQ,IAAI,EAAE,CAAC;AAAA,IAC7E;AACA,yBAAqB,SAAS,OAAO,IAAI,WAAW;AAAA,EACtD;AAGA,WAAS,sBAA8B;AACrC,UAAM,SAAS,uBAAuB,OAAO,sBAAsB;AACnE,WAAO,UAAU,YAAY,YAAY;AAAA,EAC3C;AAGA,WAAS,sBAA4B;AACnC,aAAS,oBAAoB,eAAe,CAAC,GAAG,YAAY,IAAI,IAAI;AAAA,EACtE;AAGA,WAAS,wBAA8B;AACrC,UAAM,OAAO,YAAY;AACzB,QAAI,CAAC,KAAM;AACX,uBAAmB,IAAI,IAAI,KAAK,MAAM;AACtC,uBAAmB;AAAA,EACrB;AAGA,WAAS,sBAAsB,QAA6B;AAC1D,UAAM,WAAW,YAAY,WAAW,SAAS;AAEjD,QAAI,WAAW,QAAQ,CAAC,YAAY,qBAAqB,GAAG;AAC1D,yBAAmB,SAAS,EAAE,QAAQ,OAAO,KAAK,IAAI;AACtD;AAAA,IACF;AACA,uBAAmB,EAAE,QAAQ,OAAO,EAAE;AACtC,UAAM,OAAO,YAAY,SAAS,IAAI;AACtC,UAAM,QAAQ,YAAY;AAAA,MACxB,UAAU,SAAS;AAAA,MACnB;AAAA,MACA,OAAO,CAAC,MAAM;AAEZ,YAAI,kBAAkB,WAAW,OAAQ,kBAAiB,QAAQ,IAAI,OAAO;AAC7E,sBAAc;AAAA,MAChB;AAAA,MACA,QAAQ,MAAM;AACZ,YAAI,kBAAkB,WAAW,OAAQ,kBAAiB,QAAQ;AAAA,MACpE;AAAA,IACF,CAAC;AACD,cAAU,IAAI,KAAK;AAAA,EACrB;AAGA,WAAS,gBAAgB,QAAsB;AAC7C,QAAI,CAAC,kBAAkB,CAAC,mBAAoB;AAC5C,UAAM,WAAW,YAAY,mBAAmB,IAAI,MAAM;AAC1D,QAAI,CAAC,SAAU;AACf,UAAM,OAAO,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AACxD,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,mBAAmB,aAAa,EAAE,cAAc,KAAK,GAAG,KAAK,CAAC;AAE7E,UAAM,YAAY,iBAAiB;AACnC,QAAI,CAAC,WAAW;AACd,qBAAe,KAAK,UAAU,OAAO,GAAG,OAAO,CAAC;AAChD;AAAA,IACF;AACA,UAAM,SAAS,UAAU,EAAE,MAAM,QAAQ,MAAM,aAAa,MAAM,EAAE,GAAG,QAAQ;AAC/E,yBAAqB,QAAQ,OAAO,GAAG,OAAO,CAAC;AAAA,EACjD;AAGA,WAAS,gBACP,QACA,MACA,SACA,SACM;AACN,QAAI,CAAC,eAAgB;AACrB,UAAM,OAAO,YAAY,MAAM,KAAK,CAAC,MAAM,GAAG,EAAE,MAAM,KAAK,EAAE,MAAM,OAAO,MAAM;AAChF,UAAM,WAAW,OAAO,iBAAiB,IAAI,IAAI,EAAE,OAAO,QAAQ,QAAQ,CAAC,EAAE;AAE7E,UAAM,YAAY,iBAAiB;AACnC,QAAI,CAAC,WAAW;AACd,qBAAe,KAAK,UAAU,SAAS,OAAO;AAC9C;AAAA,IACF;AACA,UAAM,SAAS,UAAU,EAAE,MAAM,QAAQ,KAAK,GAAG,QAAQ;AACzD,yBAAqB,QAAQ,SAAS,OAAO;AAAA,EAC/C;AAGA,WAAS,mBAAiD;AACxD,UAAM,IAAI,SAAS;AACnB,WAAO,KAAK,OAAO,MAAM,WAAY,EAAE,aAAa,OAAQ;AAAA,EAC9D;AAGA,WAAS,qBACP,QACA,GACA,GACM;AACN,QAAI,CAAC,eAAgB;AACrB,QAAI,WAAW,MAAM;AACnB,qBAAe,KAAK;AAAA,IACtB,WAAW,OAAO,WAAW,UAAU;AACrC,qBAAe,KAAK,EAAE,MAAM,OAAO,GAAG,GAAG,CAAC;AAAA,IAC5C,WAAW,kBAAkB,aAAa;AACxC,qBAAe,KAAK,EAAE,SAAS,OAAO,GAAG,GAAG,CAAC;AAAA,IAC/C,OAAO;AACL,qBAAe,KAAK,QAAQ,GAAG,CAAC;AAAA,IAClC;AAAA,EACF;AAGA,WAAS,iBAAiB,KAA+B;AACvD,UAAM,YAAY,mBAAoB,aAAa;AAGnD,QAAI,CAAC,gBAAiB,UAAS,GAAG;AAClC,UAAM,KAAK;AACX,UAAM,IAAI,GAAG,SAAS,GAAG;AACzB,UAAM,QAAQ,IAAI,IAAI,EAAE,GAAG,MAAM,GAAG,MAAM,MAAM,GAAG,KAAK,IAAI;AAE5D,UAAM,cAAc,GAAG;AAEvB,UAAM,mBAAmB,mBACrB,oBAAI,IAAI,CAAC,CAAC,iBAAiB,QAAQ,iBAAiB,KAAK,CAAC,CAAC,IAC3D;AAEJ,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,WAAW,EAAE,GAAG,UAAU,GAAG,GAAG,UAAU,GAAG,GAAG,UAAU,EAAE;AAAA,MAC5D;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,YAAY;AAAA,MACnB,eAAe,cAAc,WAAW;AAAA,MACxC,WAAW;AAAA,MACX;AAAA,MACA,WAAW,YAAY;AAAA,MACvB,OAAO,SAAS,EAAE,GAAG,GAAG,MAAM,aAAa,MAAM,YAAY;AAAA,MAC7D;AAAA,MACA,YAAY,oBAAoB;AAAA,MAChC,UACE,kBAAkB,mBAAmB,IACjC;AAAA,QACE,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO,oBAAoB;AAAA,QAC3B,SAAS,qBAAqB;AAAA,MAChC,IACA;AAAA,MACN,YAAY,iBAAiB;AAAA,MAC7B,SAAS,iBAAiB;AAAA,IAC5B;AAAA,EACF;AAEA,WAAS,YAAY,KAAmB;AACtC,kBAAc;AACd,QAAI,aAAa,CAAC,YAAY,CAAC,mBAAoB;AAInD,QAAI,UAAU,KAAK,GAAG,EAAG,eAAc;AAEvC,QAAI,aAAa;AACf,oBAAc;AACd,eAAS,OAAO,iBAAiB,GAAG,CAAC;AAAA,IACvC;AAGA,QAAI,UAAU,OAAQ,gBAAe;AAGrC,QAAI,qBAAqB;AACvB,4BAAsB;AACtB,YAAM,IAAI,mBAAmB,aAAa;AAC1C,eAAS,iBAAiB,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,CAAC;AAAA,IACtD;AAAA,EACF;AAOA,WAAS,UACP,IACA,MACA,QACM;AACN,QAAI,aAAa,CAAC,mBAAoB;AAGtC,iBAAa;AAEb,UAAM,YAAY,YAAY,WAAW,UAAU;AACnD,UAAM,gBAAgB,MAAO,OAAO,OAAO,aAAa,GAAG,IAAI;AAC/D,UAAM,OAAO,cAAc,QAAQ,qBAAqB,KAAK,MAAM,aAAa;AAEhF,QAAI,MAAM;AACR,yBAAmB,aAAa,cAAc,CAAC;AAC/C,4BAAsB;AACtB,oBAAc;AACd,qBAAe;AACf,eAAS;AACT;AAAA,IACF;AAEA,UAAM,EAAE,OAAO,OAAO,IAAI,oBAAoB;AAC9C,UAAM,WAAW,MAAM,YAAY,WAAW,YAAY;AAC1D,UAAM,OAAO,MAAM,QAAQ,WAAW,QAAQ;AAE9C,UAAM,QAAQ,gBAAgB,SAAS;AAEvC,UAAM,SAAS,mBAAmB;AAAA,MAChC,MAAM,mBAAmB,aAAa;AAAA,MACtC;AAAA,MACA,UAAU,EAAE,OAAO,OAAO;AAAA,MAC1B,OAAO,CAAC,MAAM;AACZ,2BAAoB,aAAa,CAAC;AAClC,sBAAc;AACd,8BAAsB;AACtB,sBAAc;AAAA,MAChB;AAAA,MACA,QAAQ,MAAM;AACZ,uBAAe;AACf,sBAAc;AACd,sBAAc;AAGd,YAAI,OAAO,OAAO,WAAY,aAAY,EAAE;AAC5C,uBAAe;AACf,iBAAS;AAAA,MACX;AAAA,MACA,MAAM,EAAE,UAAU,KAAK;AAAA,IACzB,CAAC;AACD,mBAAe;AACf,cAAU,IAAI,MAAM;AAAA,EACtB;AAGA,WAAS,YAAY,QAAmC;AACtD,UAAM,SAAS,mBAAmB;AAAA,MAChC;AAAA,MACA,OAAO,CAAC,MAAM;AACZ,2BAAoB,aAAa,CAAC;AAClC,8BAAsB;AACtB,sBAAc;AAAA,MAChB;AAAA,MACA,UAAU,MAAM,CAAC,aAAa,aAAa;AAAA,IAC7C,CAAC;AACD,mBAAe;AACf,cAAU,IAAI,MAAM;AAAA,EACtB;AAGA,WAAS,eAAqB;AAC5B,QAAI,cAAc;AAChB,gBAAU,OAAO,YAAY;AAC7B,qBAAe;AACf,oBAAc;AAAA,IAChB;AACA,QAAI,cAAc;AAChB,gBAAU,OAAO,YAAY;AAC7B,qBAAe;AAAA,IACjB;AAAA,EACF;AAMA,WAAS,kBAAwB;AAC/B,QAAI,CAAC,OAAQ;AAEb,QAAI,SAAS,YAAY,OAAO;AAC9B,uBAAiB,qBAAqB,OAAQ;AAAA,IAChD;AAEA,yBAAqB,IAAI,wBAAwB,QAAQ,cAAc;AAAA,MACrE,kBAAkB,YAAY;AAI5B,qBAAa;AACb,oBAAY;AAGZ,8BAAsB;AACtB,sBAAc;AACd,uBAAe;AAAA,MACjB;AAAA,MACA,cAAc,QAAQ;AAGpB,YAAI,WAAW,cAAe;AAC9B,wBAAgB;AAEhB,iBAAS,YAAY,IAAI,CAAC;AAC1B,8BAAsB,MAAM;AAC5B,sBAAc;AACd,uBAAe;AAKf,YAAI,UAAU,eAAe;AAC3B,0BAAgB;AAChB,mBAAS,cAAc,IAAI;AAC3B,0BAAgB,KAAK;AAAA,QACvB;AAGA,iBAAS,cAAc,SAAS,aAAa,MAAM,IAAI,IAAI;AAG3D,YAAI,UAAU,gBAAgB;AAC5B,0BAAgB,MAAM;AAAA,QACxB,WAAW,CAAC,QAAQ;AAElB,0BAAgB,KAAK;AAAA,QACvB;AAAA,MACF;AAAA,MACA,kBAAkB,QAAQ,QAAQ,SAAS,SAAS;AAElD,YAAI,cAAe;AAEnB,cAAM,MAAM,YAAY,IAAI;AAC5B,YAAI,MAAM,kBAAkB,IAAI;AAE9B,cAAI,eAAe;AACjB,4BAAgB;AAChB,0BAAc;AACd,2BAAe;AACf,qBAAS,cAAc,IAAI;AAC3B,4BAAgB,KAAK;AAAA,UACvB;AACA;AAAA,QACF;AACA,0BAAkB;AAGlB,cAAM,YAAY,oBAAoB,aAAa;AACnD,cAAM,YAAY,KAAK,WAAW,KAAK;AACvC,cAAM,SAAS,YAAY,QAAQ,QAAQ,SAAS;AAEpD,YAAI,WAAW,eAAe;AAC5B,0BAAgB;AAChB,wBAAc;AACd,yBAAe;AAEf,cAAI,QAAQ;AACV,kBAAM,OAAO,aAAa,MAAM;AAChC,qBAAS,cAAc,IAAI;AAC3B,gBAAI,kBAAkB,KAAM,iBAAgB,QAAQ,MAAM,SAAS,OAAO;AAAA,UAC5E,OAAO;AACL,qBAAS,cAAc,IAAI;AAC3B,4BAAgB,KAAK;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAAA,MACA,kBAAkB,SAAS;AACzB,0BAAkB,IAAI,IAAI,OAAO;AACjC,iBAAS,YAAY,IAAI,CAAC;AAC1B,sBAAc;AACd,uBAAe;AACf,iBAAS,oBAAoB,OAAO;AAGpC,YAAI,QAAQ,SAAS,GAAG;AACtB,gBAAM,SAAS,QAAQ,QAAQ,SAAS,CAAC;AACzC,mBAAS,cAAc,aAAa,MAAM,CAAC;AAAA,QAC7C;AAAA,MACF;AAAA,MACA,gBAAgB,QAAQ;AAEtB,cAAM,OAAO,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AACxD,cAAM,IAAI,MAAM,KAAK;AACrB,cAAM,IAAI,MAAM,KAAK;AAGrB,oBAAY,QAAQ,QAAQ,GAAG,GAAG,mBAAmB,IAAI,MAAM,MAAS;AACxE,gBAAQ,UAAU,IAAI,2BAA2B;AAAA,MACnD;AAAA,MACA,WAAW,QAAQ,GAAG,GAAG;AACvB,oBAAY,SAAS,QAAQ,GAAG,CAAC;AAAA,MACnC;AAAA,MACA,cAAc,QAAQ;AAGpB,oBAAY,UAAU,QAAQ,mBAAmB,IAAI,IAAI,MAAS;AAClE,gBAAQ,UAAU,OAAO,2BAA2B;AAAA,MACtD;AAAA,MACA,cAAc,QAAQ,QAAQ;AAC5B,YAAI,CAAC,mBAAmB,EAAG;AAE3B,cAAM,MAAM,YAAY,IAAI;AAC5B,YAAI,MAAM,sBAAsB,2BAA4B;AAC5D,8BAAsB;AACtB,oBAAY,WAAW,QAAQ,QAAQ,IAAI;AAAA,MAC7C;AAAA,MACA,iBAAiB;AACf,YAAI,CAAC,mBAAmB,EAAG;AAC3B,oBAAY,WAAW,GAAG,GAAG,KAAK;AAAA,MACpC;AAAA,MACA,cAAc,QAAQ;AACpB,iBAAS,oBAAoB,aAAa,MAAM,CAAC;AAAA,MACnD;AAAA,IACF,CAAC;AAGD,sBAAkB,uBAAuB;AAAA,MACvC;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM,CAAC,GAAG,eAAe;AAAA,MACzC,cAAc,MAAM;AAAA,MACpB,SAAS,QAAQ;AACf,0BAAkB,oBAAI,IAAI,CAAC,MAAM,CAAC;AAClC,sBAAc;AACd,uBAAe;AACf,iBAAS,cAAc,aAAa,MAAM,CAAC;AAC3C,iBAAS,oBAAoB,CAAC,MAAM,CAAC;AAAA,MACvC;AAAA,MACA,aAAa;AACX,wBAAgB,MAAM;AACtB,sBAAc;AACd,uBAAe;AACf,iBAAS,oBAAoB,CAAC,CAAC;AAAA,MACjC;AAAA,MACA,OAAO,WAAW;AAChB,YAAI,CAAC,sBAAsB,CAAC,OAAQ;AACpC,cAAM,IAAI,mBAAmB,aAAa;AAC1C,cAAM,EAAE,OAAO,IAAI,QAAQ,GAAG,IAAI,oBAAoB;AACtD,cAAM,SAAS,cAAc,OAAO,MAAM;AAC1C,cAAM,OAAO,EAAE,IAAI;AACnB,cAAM,eAAe,EAAE,OAAO,MAAM,KAAK,GAAG,KAAK,CAAC;AAClD,kBAAU,cAAc,EAAE,UAAU,IAAI,CAAC;AAAA,MAC3C;AAAA,MACA,WAAW;AACT,kBAAU;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EAIH;AAMA,WAAS,OAAO,OAAqB;AACnC,QAAI,UAAW;AACf,kBAAc,OAAO,OAAO,eAAe;AAC3C,kBAAc;AACd,mBAAe;AAAA,EACjB;AAEA,WAAS,cAAoB;AAC3B,QAAI,UAAW;AACf,kBAAc,YAAY;AAC1B,kBAAc;AACd,mBAAe;AAAA,EACjB;AAEA,WAAS,UAAU,MAAyD;AAC1E,QAAI,aAAa,CAAC,sBAAsB,gBAAgB,WAAW,EAAG;AACtE,UAAM,EAAE,OAAO,IAAI,QAAQ,GAAG,IAAI,oBAAoB;AACtD,UAAM,EAAE,WAAW,aAAa,IAAI,cAAc;AAAA,MAChD;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,QACE,UAAU,eAAe;AAAA,MAC3B;AAAA,IACF;AACA,cAAU,cAAc,IAAI;AAAA,EAC9B;AAEA,WAAS,WAAW,QAAgB,MAAuD;AACzF,QAAI,aAAa,CAAC,sBAAsB,CAAC,OAAQ;AACjD,UAAM,OAAO,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AACxD,QAAI,CAAC,KAAM;AAEX,UAAM,EAAE,OAAO,IAAI,QAAQ,GAAG,IAAI,oBAAoB;AACtD,UAAM,IAAI,OAAO,MAAM,SAAS,CAAC;AAGjC,UAAM,WAAW,MAAqB;AACpC,YAAM,OAAO,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,KAAK;AAC7D,aAAO,IAAI,cAAc,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC;AAAA,IACtE;AACA,cAAU,UAAU,IAAI;AAAA,EAC1B;AAEA,WAAS,MAAM,QAA8C,MAAkC;AAC7F,QAAI,aAAa,CAAC,mBAAoB;AACtC,UAAM,EAAE,OAAO,IAAI,QAAQ,GAAG,IAAI,oBAAoB;AACtD,UAAM,IAAI,OAAO,OAAO,KAAK,mBAAmB,aAAa,EAAE,CAAC;AAChE,cAAU,IAAI,cAAc,KAAK,IAAI,OAAO,IAAI,GAAG,KAAK,IAAI,OAAO,IAAI,GAAG,CAAC,GAAG,IAAI;AAAA,EACpF;AAEA,WAAS,SAAS,GAAW,GAAW,MAAkC;AACxE,UAAM,EAAE,GAAG,EAAE,GAAG,IAAI;AAAA,EACtB;AAEA,WAAS,YAAiD;AACxD,UAAM,IAAI,oBAAoB,aAAa,KAAK,cAAc,SAAS;AACvE,WAAO,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE;AAAA,EAClC;AAEA,WAAS,WAAW,QAAgB,MAAsD;AACxF,QAAI,UAAW;AACf,sBAAkB,oBAAI,IAAI,CAAC,MAAM,CAAC;AAClC,kBAAc;AACd,mBAAe;AACf,aAAS,oBAAoB,CAAC,MAAM,CAAC;AACrC,UAAM,YAAY,MAAM,OAAO,YAAY,YAAY;AACvD,QAAI,UAAW,YAAW,QAAQ,IAAI;AAAA,EACxC;AAEA,WAAS,mBAA6B;AACpC,WAAO,CAAC,GAAG,eAAe;AAAA,EAC5B;AAEA,WAAS,mBAA6B;AACpC,WAAO,CAAC,GAAI,cAAc,WAAW,KAAK,CAAC,CAAE;AAAA,EAC/C;AAaA,WAAS,UAAU,QAA8B,MAAsC;AACrF,QAAI,UAAW;AACf,sBAAkB;AAClB,8BAA0B;AAC1B,0BAAsB,MAAM,cAAc;AAC1C,uBAAmB;AACnB,iBAAa;AACb,wBAAoB;AAAA,EACtB;AAMA,WAAS,iBAAuB;AAC9B,QAAI,UAAW;AACf,sBAAkB;AAClB,yBAAqB;AACrB,0BAAsB;AACtB,uBAAmB;AACnB,iBAAa;AACb,wBAAoB;AAAA,EACtB;AAEA,WAAS,eAAgC;AACvC,WAAO,eAAe,CAAC,GAAG,YAAY,IAAI;AAAA,EAC5C;AAEA,WAAS,YAA6B;AACpC,UAAM,cAAc,aAAa,YAAY,SAAS,YAAY,OAAO,UAAU,CAAC;AACpF,WAAO;AAAA,MACL,OAAO,YAAY;AAAA,MACnB,OAAO,YACJ,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EACzB,IAAI,CAAC,OAAO;AAAA,QACX,OAAO,EAAE;AAAA,QACT,OAAO,EAAE;AAAA,QACT,OAAO,EAAE;AAAA,QACT,QAAQ,iBAAiB,SAAS,KAAK,iBAAiB,IAAI,EAAE,KAAK;AAAA,MACrE,EAAE;AAAA,MACJ,QAAQ,YAAY,cAAc,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,QAChD,OAAO,EAAE;AAAA,QACT,OAAO,EAAE;AAAA,QACT,OAAO,EAAE;AAAA,MACX,EAAE;AAAA,IACJ;AAAA,EACF;AAOA,WAAS,oBAAoB,QAAwB;AACnD,QAAI,UAAW;AACf,uBAAmB,IAAI,IAAI,MAAM;AACjC,uBAAmB;AACnB,0BAAsB;AACtB,iBAAa;AACb,wBAAoB;AAAA,EACtB;AAEA,WAAS,sBAAgC;AACvC,WAAO,CAAC,GAAG,gBAAgB;AAAA,EAC7B;AAEA,WAAS,qBAAqB,OAAqB;AACjD,QAAI,iBAAiB,IAAI,KAAK,EAAG,kBAAiB,OAAO,KAAK;AAAA,QACzD,kBAAiB,IAAI,KAAK;AAC/B,uBAAmB;AACnB,0BAAsB;AACtB,iBAAa;AACb,aAAS,iBAAiB,CAAC,GAAG,gBAAgB,CAAC;AAC/C,wBAAoB;AAAA,EACtB;AAEA,WAAS,WAAiB;AACxB,QAAI,aAAa,CAAC,UAAU,CAAC,YAAY,CAAC,QAAS;AACnD,UAAM,EAAE,OAAO,OAAO,IAAI,uBAAuB;AACjD,UAAM,eAAe,KAAK,IAAI,QAAQ,GAAG;AACzC,aAAS,OAAO,OAAO,YAAY;AAGnC,oBAAgB;AAKhB,QAAI,uBAAuB,oBAAoB;AAC7C,mBAAa;AACb,4BAAsB;AACtB,yBAAmB,aAAa,kBAAkB,CAAC;AACnD,4BAAsB;AAAA,IACxB;AAEA,kBAAc;AACd,mBAAe;AAAA,EACjB;AAgBA,WAAS,OAAO,SAA0B;AACxC,QAAI,UAAW;AACf,kBAAc;AAKd,cAAU,UAAU;AACpB,qBAAiB;AACjB,uBAAmB;AACnB,0BAAsB;AACtB,qBAAiB;AAEjB,oBAAgB;AAChB,oBAAgB;AAGhB,UAAM,YAAY;AAClB,UAAM,YAAY;AAClB,UAAM,aAAa,YAAY;AAG/B,kBAAc,QAAQ;AACtB,cAAU,IAAI,IAAI,YAAY,WAAW;AAEzC,UAAM,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,QAAQ;AAAA,IACrB;AAEA,QAAI,KAAK,YAAY;AACnB,0BAAoB;AACpB;AAAA,IACF;AAEA,wBAAoB,IAAI;AAAA,EAC1B;AAOA,WAAS,sBAA4B;AACnC,mBAAe,kBAAkB,YAAY,KAAK;AAClD,kBAAc;AAGd,UAAM,SAAS,oBAAI,IAAsC;AACzD,eAAW,QAAQ,iBAAiB;AAClC,aAAO,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,CAAC;AAAA,IAC9C;AAGA,sBAAkB,YAAY,MAAM,IAAI,CAAC,MAAM,UAAU;AACvD,YAAM,MAAM,OAAO,IAAI,KAAK,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AAChD,aAAO,EAAE,GAAG,MAAM,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,MAAM;AAAA,IAC9C,CAAC;AAED,sBAAkB,YAAY,MAAM,IAAI,CAAC,SAAS;AAChD,YAAM,MAAM,OAAO,IAAI,KAAK,MAAM,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AACpD,YAAM,MAAM,OAAO,IAAI,KAAK,MAAM,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AACpD,aAAO,EAAE,GAAG,MAAM,SAAS,IAAI,GAAG,SAAS,IAAI,GAAG,SAAS,IAAI,GAAG,SAAS,IAAI,EAAE;AAAA,IACnF,CAAC;AAED,iBAAa,QAAQ,eAAe;AAKpC,8BAA0B;AAC1B,uBAAmB;AAGnB,gBAAY;AAEZ,IAAAH,cAAa;AACb,IAAAC,cAAa;AACb,0BAAsB;AAEtB,kBAAc;AACd,mBAAe;AAAA,EACjB;AAOA,WAAS,oBAAoB,MAAgD;AAG3E,oBAAgB;AAEhB,mBAAe,kBAAkB,YAAY,KAAK;AAClD,kBAAc;AAGd,UAAM,YAAY,oBAAI,IAAsC;AAC5D,eAAW,CAAC,IAAI,CAAC,KAAK,KAAK,mBAAoB,WAAU,IAAI,IAAI,CAAC;AAClE,eAAW,CAAC,IAAI,CAAC,KAAK,KAAK,eAAgB,WAAU,IAAI,IAAI,CAAC;AAK9D,UAAM,gBAAgB,KAAK,mBAAmB,OAAO,KAAK,aAAa;AACvE,UAAM,gBAAgB,KAAK,mBAAmB,OAAO,KAAK,YAAY;AACtE,UAAM,YAAY;AAAA,MAChB,KAAK,YAAY,SAAS,KAAK,aAAa;AAAA,MAC5C,KAAK,IAAI,eAAe,aAAa;AAAA,IACvC;AACA,UAAM,gBACJ,YAAY,MAAM,SAAS,KAAK,oBAAoB,KAAK,aAAa;AACxE,UAAM,gBAAgB,YAAY,MAAM;AACxC,UAAM,YAAY;AAAA,MAChB,KAAK,oBAAoB,KAAK,aAAa;AAAA,MAC3C,KAAK,IAAI,eAAe,aAAa;AAAA,IACvC;AACA,UAAM,cAAc,KAAK,IAAI,WAAW,SAAS;AACjD,UAAM,eAAe,KAAK,IAAI,GAAG,MAAM,MAAM,WAAW;AAKxD,sBAAkB,YAAY,MAAM,IAAI,CAAC,MAAM,UAAU;AACvD,YAAM,MAAM,UAAU,IAAI,KAAK,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AACnD,aAAO,EAAE,GAAG,MAAM,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,MAAM;AAAA,IAC9C,CAAC;AACD,sBAAkB,YAAY,MAAM,IAAI,CAAC,SAAS;AAChD,YAAM,MAAM,UAAU,IAAI,KAAK,MAAM,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AACvD,YAAM,MAAM,UAAU,IAAI,KAAK,MAAM,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AACvD,aAAO,EAAE,GAAG,MAAM,SAAS,IAAI,GAAG,SAAS,IAAI,GAAG,SAAS,IAAI,GAAG,SAAS,IAAI,EAAE;AAAA,IACnF,CAAC;AACD,iBAAa,QAAQ,eAAe;AAEpC,mBAAe;AAAA,MACb;AAAA,MACA,gBAAgB;AAAA,MAChB;AAAA,MACA,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAGD,IAAAD,cAAa;AACb,IAAAC,cAAa;AAGb,8BAA0B;AAG1B,2BAAuB,IAAI;AAE3B,kBAAc;AACd,mBAAe;AAAA,EACjB;AAGA,WAAS,MAAM,WAAmB,aAA6B;AAC7D,WAAO,cAAc,IAAI,YAAY,cAAc;AAAA,EACrD;AASA,WAAS,4BAAkC;AACzC,UAAM,UAAU,IAAI,IAAI,YAAY,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAG1D,QAAI,iBAAiB,CAAC,QAAQ,IAAI,aAAa,EAAG,iBAAgB;AAElE,QAAI,eAAe;AACjB,YAAM,CAAC,KAAK,GAAG,IAAI,cAAc,MAAM,IAAI;AAC3C,UAAI,CAAC,QAAQ,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,GAAG,EAAG,iBAAgB;AAAA,IAC9D;AAGA,UAAM,qBAAqB,CAAC,GAAG,eAAe,EAAE,OAAO,CAAC,OAAO,QAAQ,IAAI,EAAE,CAAC;AAC9E,sBAAkB,IAAI,IAAI,kBAAkB;AAC5C,wBAAoB,aAAa,kBAAkB;AAKnD,8BAA0B;AAC1B,uBAAmB;AACnB,0BAAsB;AAGtB,gBAAY;AAIZ,sBAAkB;AAClB,aAAS,YAAY,IAAI,CAAC;AAAA,EAC5B;AAGA,WAAS,cAAoB;AAC3B,UAAM,IAAI,cAAc,SAAS;AACjC,QAAI,MAAM,KAAM,eAAc,OAAO,GAAG,eAAe;AAAA,EACzD;AAOA,WAAS,uBAAuB,MAAgD;AAC9E,UAAM,YAAY,YAAY,WAAW,UAAU;AACnD,UAAM,UAAU,YAAY,WAAW,QAAQ;AAC/C,UAAM,UAAU,qBAAqB;AAGrC,QAAI,KAAK,YAAY,SAAS,KAAK,aAAa,CAAC,SAAS;AACxD,YAAM,WAAW,KAAK;AACtB,sBAAgB,IAAI,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AACrD,YAAM,OAAO,YAAY,UAAU,IAAI;AACvC,YAAM,QAAQ,YAAY;AAAA,QACxB,UAAU,UAAU;AAAA,QACpB;AAAA,QACA,OAAO,CAAC,MAAM;AAEZ,gBAAM,IAAI,KAAK,MAAM,IAAI,CAAC,IAAI;AAC9B,cAAI,cAAe,YAAW,MAAM,SAAU,eAAc,IAAI,IAAI,CAAC;AACrE,wBAAc;AAAA,QAChB;AAAA,QACA,QAAQ,MAAM;AACZ,0BAAgB;AAChB,wBAAc;AAAA,QAChB;AAAA,MACF,CAAC;AACD,gBAAU,IAAI,KAAK;AAAA,IACrB;AAGA,SAAK,KAAK,aAAa,SAAS,KAAK,KAAK,aAAa,SAAS,MAAM,WAAW,CAAC,SAAS;AACzF,sBAAgB,EAAE,OAAO,KAAK,cAAc,OAAO,KAAK,cAAc,OAAO,EAAE;AAC/E,YAAM,OAAO,YAAY,QAAQ,IAAI;AACrC,YAAM,QAAQ,YAAY;AAAA,QACxB,UAAU,QAAQ;AAAA,QAClB;AAAA,QACA,OAAO,CAAC,MAAM;AACZ,cAAI,cAAe,eAAc,QAAQ,IAAI;AAC7C,wBAAc;AAAA,QAChB;AAAA,QACA,QAAQ,MAAM;AACZ,0BAAgB;AAChB,wBAAc;AAAA,QAChB;AAAA,MACF,CAAC;AACD,gBAAU,IAAI,KAAK;AAAA,IACrB;AAAA,EACF;AAQA,WAAS,cAAc,SAA0B;AAC/C,WAAO,OAAO;AAAA,EAChB;AAGA,WAAS,kBAAwB;AAC/B,gBAAY,QAAQ;AACpB,iBAAa;AAAA,EACf;AAEA,WAAS,qBAA2B;AAGlC,cAAU,UAAU;AACpB,mBAAe;AACf,mBAAe;AAEf,qBAAiB;AACjB,qBAAiB;AACjB,uBAAmB;AACnB,0BAAsB;AAEtB,oBAAgB;AAChB,oBAAgB;AAChB,QAAI,gBAAgB,MAAM;AACxB,2BAAqB,WAAW;AAChC,oBAAc;AAAA,IAChB;AACA,QAAI,iBAAiB;AACnB,sBAAgB;AAChB,wBAAkB;AAAA,IACpB;AACA,wBAAoB,QAAQ;AAC5B,yBAAqB;AACrB,gBAAY,QAAQ;AACpB,iBAAa;AACb,oBAAgB,QAAQ;AACxB,qBAAiB;AAAA,EACnB;AAEA,WAAS,UAAgB;AACvB,QAAI,UAAW;AACf,gBAAY;AAEZ,QAAI,mBAAmB,MAAM;AAC3B,mBAAa,cAAc;AAC3B,uBAAiB;AAAA,IACnB;AAEA,uBAAmB;AAEnB,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AAEA,sBAAkB,QAAQ;AAC1B,uBAAmB;AAEnB,QAAI,SAAS,YAAY;AACvB,cAAQ,WAAW,YAAY,OAAO;AAAA,IACxC;AACA,cAAU;AACV,aAAS;AACT,eAAW;AACX,eAAW;AACX,eAAW;AAEX,cAAU,UAAU,OAAO,SAAS;AAAA,EACtC;AAMA,MAAI;AACF,kBAAc,QAAQ;AACtB,cAAU,IAAI,IAAI,YAAY,WAAW;AACzC,mBAAe,kBAAkB,YAAY,KAAK;AAClD,kBAAc;AACd,0BAAsB;AACtB,cAAU;AACV,mBAAe;AACf,oBAAgB;AAAA,EAClB,SAAS,KAAK;AAIZ,YAAQ,MAAM,6BAA6B,GAAG;AAC9C,UAAM;AAAA,EACR;AAGA,MAAI,SAAS,eAAe,OAAO;AACjC,uBAAmB,cAAc,WAAW,MAAM;AAChD,eAAS;AAAA,IACX,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF;AACF;AAMA,SAASC,YAAW,KAAqB;AACvC,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ;AAC3B;;;AezhEA,SAAS,eAAe,IAA0B,MAAuB;AACvE,MAAI,KAAK,MAAM,iBAAiB;AAC9B,OAAG,MAAM,aAAa,KAAK,MAAM;AAAA,EACnC;AACA,MAAI,KAAK,MAAM,OAAO;AACpB,OAAG,MAAM,QAAQ,KAAK,MAAM;AAAA,EAC9B;AACA,MAAI,KAAK,MAAM,YAAY;AACzB,OAAG,MAAM,aAAa,OAAO,KAAK,MAAM,UAAU;AAAA,EACpD;AACA,MAAI,KAAK,MAAM,aAAa;AAC1B,OAAG,MAAM,cAAc,KAAK,MAAM;AAAA,EACpC;AACA,MAAI,KAAK,MAAM;AACb,OAAG,aAAa,cAAc,KAAK,IAAI;AAAA,EACzC;AACF;AAMA,SAAS,eAAe,MAAsB;AAC5C,SAAO,CAAC,GAAG,KAAK,YAAY,CAAC,EAC1B,IAAI,CAAC,MAAM,OAAO,cAAc,EAAE,WAAW,CAAC,IAAI,MAAO,CAAC,EAC1D,KAAK,EAAE;AACZ;AAMA,IAAM,gBAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,SAAS,eAAe,MAAsB;AAC5C,SAAO,cAAc,KAAK,YAAY,CAAC,KAAK,KAAK,YAAY;AAC/D;AAMA,SAAS,uBAAuB,MAA6B;AAC3D,MAAI,KAAK,SAAS,UAAU,KAAK,OAAO,UAAU,GAAG;AACnD,UAAM,QAAQ,KAAK,OAAO,CAAC,EAAE;AAC7B,UAAM,OAAO,KAAK,OAAO,KAAK,OAAO,SAAS,CAAC,EAAE;AACjD,UAAM,QAAQ,KAAK,OAAO;AAC1B,QAAI,OAAO,MAAO,QAAO,kBAAkB,KAAK;AAChD,QAAI,OAAO,MAAO,QAAO,kBAAkB,KAAK;AAChD,WAAO,kBAAkB,KAAK;AAAA,EAChC;AACA,OAAK,KAAK,SAAS,SAAS,KAAK,SAAS,aAAa,KAAK,KAAK,SAAS,GAAG;AAC3E,WAAO,GAAG,KAAK,SAAS,WAAW,WAAW,KAAK,mBAAmB,KAAK,KAAK,MAAM;AAAA,EACxF;AACA,SAAO;AACT;AAOO,SAAS,eAAe,MAA2C;AACxE,QAAM,KAAK,SAAS,cAAc,IAAI;AACtC,KAAG,cAAc,KAAK;AACtB,iBAAe,IAAI,IAAI;AACvB,SAAO;AACT;AAGO,SAAS,kBAAkB,MAA8C;AAC9E,QAAM,KAAK,SAAS,cAAc,IAAI;AACtC,KAAG,YAAY;AACf,KAAG,cAAc,KAAK;AACtB,iBAAe,IAAI,IAAI;AACvB,SAAO;AACT;AAGO,SAAS,mBAAmB,MAA+C;AAChF,QAAM,KAAK,SAAS,cAAc,IAAI;AACtC,KAAG,YAAY;AACf,KAAG,cAAc,KAAK;AACtB,iBAAe,IAAI,IAAI;AACvB,SAAO;AACT;AAGO,SAAS,cAAc,MAA0C;AACtE,QAAM,KAAK,SAAS,cAAc,IAAI;AACtC,KAAG,YAAY;AACf,iBAAe,IAAI,IAAI;AAEvB,QAAM,OAAO,SAAS,cAAc,KAAK;AACzC,OAAK,YAAY;AACjB,OAAK,MAAM,QAAQ,GAAG,KAAK,MAAM,KAAK,WAAW,GAAG,CAAC;AACrD,OAAK,MAAM,OAAO,GAAG,KAAK,MAAM,KAAK,YAAY,GAAG,CAAC;AACrD,OAAK,MAAM,aAAa,KAAK;AAC7B,KAAG,YAAY,IAAI;AAEnB,QAAM,YAAY,SAAS,cAAc,MAAM;AAC/C,YAAU,YAAY;AACtB,YAAU,cAAc,KAAK;AAC7B,KAAG,YAAY,SAAS;AAExB,SAAO;AACT;AAMA,SAAS,qBAAqB,GAAmB;AAC/C,MAAI,KAAK,IAAI,CAAC,KAAK,KAAM;AACvB,WAAO,EAAE,eAAe,SAAS,EAAE,uBAAuB,EAAE,CAAC;AAAA,EAC/D;AACA,MAAI,KAAK,IAAI,CAAC,KAAK,KAAK;AACtB,WAAO,EAAE,QAAQ,CAAC;AAAA,EACpB;AACA,SAAO,EAAE,QAAQ,CAAC;AACpB;AAGO,SAAS,oBAAoB,MAAgD;AAClF,QAAM,KAAK,SAAS,cAAc,IAAI;AACtC,iBAAe,IAAI,IAAI;AAEvB,QAAM,gBAAgB,KAAK;AAC3B,MAAI,CAAC,iBAAiB,cAAc,UAAU,GAAG;AAC/C,OAAG,cAAc,KAAK,kBAAkB;AACxC,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB,uBAAuB,aAAa;AAC7D,MAAI,CAAC,GAAG,aAAa,YAAY,GAAG;AAClC,OAAG,aAAa,cAAc,gBAAgB;AAAA,EAChD;AAEA,QAAM,UAAU,SAAS,cAAc,MAAM;AAC7C,UAAQ,YAAY;AAEpB,QAAM,QAAQ;AAEd,MAAI,cAAc,SAAS,QAAQ;AAIjC,UAAM,OAAO;AACb,UAAM,OAAO;AACb,UAAM,QAAQ,OAAO,OAAO;AAE5B,UAAM,MAAM,SAAS,gBAAgB,OAAO,KAAK;AACjD,QAAI,aAAa,eAAe,MAAM;AACtC,QAAI,aAAa,SAAS,KAAK;AAC/B,QAAI,MAAM,SAAS,GAAG,IAAI;AAG1B,UAAM,aAAa,cAAc,OAAO,IAAI,CAAC,MAAM,QAAQ,IAAI,EAAE,KAAK,KAAK;AAO3E,UAAM,QAAQ;AACd,QAAI,aAAa,WAAW,OAAO,KAAK,IAAI,IAAI,EAAE;AAClD,QAAI,aAAa,uBAAuB,MAAM;AAE9C,UAAM,YAAY,cAAc,OAAO,IAAI,CAAC,GAAG,OAAO;AAAA,MACpD,GAAG,EAAE,IAAI;AAAA,MACT,GAAG,WAAW,CAAC;AAAA,IACjB,EAAE;AACF,UAAM,kBAAkB,UAAU,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,KAAK,GAAG;AAEtE,UAAM,WAAW,SAAS,gBAAgB,OAAO,UAAU;AAC3D,aAAS,aAAa,UAAU,eAAe;AAC/C,aAAS,aAAa,QAAQ,MAAM;AACpC,aAAS,aAAa,UAAU,cAAc,KAAK;AACnD,aAAS,aAAa,gBAAgB,KAAK;AAC3C,aAAS,aAAa,mBAAmB,OAAO;AAChD,aAAS,aAAa,iBAAiB,oBAAoB;AAC3D,QAAI,YAAY,QAAQ;AAExB,YAAQ,YAAY,GAAG;AAGvB,UAAM,SAAS,WAAW,CAAC;AAC3B,UAAM,QAAQ,WAAW,WAAW,SAAS,CAAC;AAC9C,UAAM,UAAU;AAEhB,UAAM,WAAW,SAAS,cAAc,MAAM;AAC9C,aAAS,YAAY;AACrB,aAAS,MAAM,OAAO;AACtB,aAAS,MAAM,MAAM,GAAG,SAAS,UAAU,CAAC;AAC5C,aAAS,MAAM,aAAa,cAAc;AAC1C,YAAQ,YAAY,QAAQ;AAE5B,UAAM,SAAS,SAAS,cAAc,MAAM;AAC5C,WAAO,YAAY;AACnB,WAAO,MAAM,QAAQ;AACrB,WAAO,MAAM,MAAM,GAAG,QAAQ,UAAU,CAAC;AACzC,WAAO,MAAM,aAAa,cAAc;AACxC,YAAQ,YAAY,MAAM;AAG1B,UAAM,YAAY,SAAS,cAAc,MAAM;AAC/C,cAAU,YAAY;AACtB,cAAU,MAAM,QAAQ,cAAc;AAEtC,UAAM,aAAa,SAAS,cAAc,MAAM;AAChD,eAAW,cAAc,qBAAqB,cAAc,UAAU;AACtE,cAAU,YAAY,UAAU;AAEhC,UAAM,WAAW,SAAS,cAAc,MAAM;AAC9C,aAAS,cAAc,qBAAqB,cAAc,QAAQ;AAClE,cAAU,YAAY,QAAQ;AAE9B,YAAQ,YAAY,SAAS;AAAA,EAC/B,WAAW,cAAc,SAAS,UAAU;AAE1C,UAAM,QAAQ;AACd,UAAM,SAAS;AACf,UAAM,UAAU;AAChB,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,SAAS,SAAS,UAAU;AAElC,UAAM,MAAM,SAAS,gBAAgB,OAAO,KAAK;AACjD,QAAI,aAAa,SAAS,OAAO,KAAK,CAAC;AACvC,QAAI,aAAa,UAAU,OAAO,MAAM,CAAC;AACzC,QAAI,aAAa,WAAW,OAAO,KAAK,IAAI,MAAM,EAAE;AACpD,QAAI,aAAa,eAAe,MAAM;AAEtC,UAAM,WAAW,cAAc,KAAK;AACpC,QAAI,WAAW,GAAG;AAChB,YAAM,MAAM;AACZ,YAAM,OAAO,KAAK,IAAI,IAAI,SAAS,OAAO,WAAW,MAAM,QAAQ;AAEnE,eAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,cAAM,OAAO,KAAK,IAAI,GAAG,cAAc,KAAK,CAAC,IAAI,MAAM;AACvD,cAAM,IAAI,UAAU,KAAK,OAAO;AAChC,cAAM,IAAI,UAAU,SAAS;AAE7B,cAAM,OAAO,SAAS,gBAAgB,OAAO,MAAM;AACnD,aAAK,aAAa,KAAK,OAAO,CAAC,CAAC;AAChC,aAAK,aAAa,KAAK,OAAO,CAAC,CAAC;AAChC,aAAK,aAAa,SAAS,OAAO,IAAI,CAAC;AACvC,aAAK,aAAa,UAAU,OAAO,IAAI,CAAC;AACxC,aAAK,aAAa,MAAM,KAAK;AAC7B,aAAK,aAAa,QAAQ,cAAc,KAAK;AAC7C,YAAI,YAAY,IAAI;AAAA,MACtB;AAAA,IACF;AAEA,YAAQ,YAAY,GAAG;AAAA,EACzB,OAAO;AAEL,UAAM,QAAQ;AACd,UAAM,SAAS;AACf,UAAM,UAAU;AAChB,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,SAAS,SAAS,UAAU;AAElC,UAAM,MAAM,SAAS,gBAAgB,OAAO,KAAK;AACjD,QAAI,aAAa,SAAS,OAAO,KAAK,CAAC;AACvC,QAAI,aAAa,UAAU,OAAO,MAAM,CAAC;AACzC,QAAI,aAAa,WAAW,OAAO,KAAK,IAAI,MAAM,EAAE;AACpD,QAAI,aAAa,eAAe,MAAM;AAEtC,UAAM,WAAW,cAAc,KAAK;AACpC,QAAI,WAAW,GAAG;AAChB,YAAM,MAAM;AACZ,YAAM,OAAO,KAAK,IAAI,IAAI,SAAS,OAAO,WAAW,MAAM,QAAQ;AAEnE,eAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,cAAM,OAAO,KAAK,IAAI,GAAG,cAAc,KAAK,CAAC,IAAI,MAAM;AACvD,cAAM,IAAI;AACV,cAAM,IAAI,UAAU,KAAK,OAAO;AAEhC,cAAM,OAAO,SAAS,gBAAgB,OAAO,MAAM;AACnD,aAAK,aAAa,KAAK,OAAO,CAAC,CAAC;AAChC,aAAK,aAAa,KAAK,OAAO,CAAC,CAAC;AAChC,aAAK,aAAa,SAAS,OAAO,IAAI,CAAC;AACvC,aAAK,aAAa,UAAU,OAAO,IAAI,CAAC;AACxC,aAAK,aAAa,MAAM,KAAK;AAC7B,aAAK,aAAa,QAAQ,cAAc,KAAK;AAC7C,YAAI,YAAY,IAAI;AAAA,MACtB;AAAA,IACF;AAEA,YAAQ,YAAY,GAAG;AAAA,EACzB;AAEA,KAAG,YAAY,OAAO;AAEtB,SAAO;AACT;AAGO,SAAS,gBAAgB,MAA4C;AAC1E,QAAM,KAAK,SAAS,cAAc,IAAI;AACtC,iBAAe,IAAI,IAAI;AAEvB,QAAM,UAAU,SAAS,cAAc,MAAM;AAC7C,UAAQ,YAAY,iBAAiB,KAAK,UAAU,4BAA4B,EAAE;AAElF,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,MAAM,KAAK;AACf,MAAI,MAAM,KAAK,kBAAkB;AACjC,MAAI,QAAQ,KAAK;AACjB,MAAI,SAAS,KAAK;AAClB,MAAI,UAAU;AAEd,UAAQ,YAAY,GAAG;AACvB,KAAG,YAAY,OAAO;AAEtB,SAAO;AACT;AAGO,SAAS,eAAe,MAA2C;AACxE,QAAM,KAAK,SAAS,cAAc,IAAI;AACtC,iBAAe,IAAI,IAAI;AAEvB,QAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,OAAK,YAAY;AACjB,OAAK,aAAa,QAAQ,KAAK;AAE/B,MAAI,KAAK,eAAe,KAAK,YAAY,WAAW,GAAG;AACrD,UAAM,cAAc,eAAe,KAAK,WAAW;AACnD,SAAK,cAAc,eAAe,KAAK,WAAW;AAClD,SAAK,aAAa,cAAc,SAAS,WAAW,EAAE;AAAA,EACxD,OAAO;AACL,SAAK,cAAc,KAAK;AACxB,SAAK,aAAa,cAAc,KAAK,cAAc;AAAA,EACrD;AAEA,KAAG,YAAY,IAAI;AAEnB,SAAO;AACT;AAOO,SAAS,WAAW,MAAuC;AAChE,UAAQ,KAAK,UAAU;AAAA,IACrB,KAAK;AACH,aAAO,eAAe,IAAI;AAAA,IAC5B,KAAK;AACH,aAAO,kBAAkB,IAAI;AAAA,IAC/B,KAAK;AACH,aAAO,mBAAmB,IAAI;AAAA,IAChC,KAAK;AACH,aAAO,cAAc,IAAI;AAAA,IAC3B,KAAK;AACH,aAAO,oBAAoB,IAAI;AAAA,IACjC,KAAK;AACH,aAAO,gBAAgB,IAAI;AAAA,IAC7B,KAAK;AACH,aAAO,eAAe,IAAI;AAAA,IAC5B;AAEE,aAAO,eAAe,IAAqB;AAAA,EAC/C;AACF;;;AC9aA,SAAS,qBAAqB;;;ACI9B;AAAA,EACE,mBAAAE;AAAA,EACA,mBAAAC;AAAA,EACA;AAAA,EACA,cAAAC;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAGlC,IAAMC,UAAS;AACf,IAAMC,YAAW;AACjB,IAAMC,aAAY;AAMlB,SAASC,kBAAiB,KAAyB;AACjD,SAAO,SAAS,gBAAgBH,SAAQ,GAAG;AAC7C;AAEA,SAASI,UAAS,IAAgB,OAA8C;AAC9E,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,OAAG,aAAa,KAAK,OAAO,KAAK,CAAC;AAAA,EACpC;AACF;AAEA,SAAS,eAAe,IAAgB,OAAwB;AAC9D,EAAAA,UAAS,IAAI;AAAA,IACX,eAAe,MAAM;AAAA,IACrB,aAAa,MAAM;AAAA,IACnB,eAAe,MAAM;AAAA,EACvB,CAAC;AACD,EAAC,GAA0C,MAAM,YAAY,QAAQ,MAAM,IAAI;AAC/E,MAAI,MAAM,YAAY;AACpB,OAAG,aAAa,eAAe,MAAM,UAAU;AAAA,EACjD;AACA,MAAI,MAAM,kBAAkB;AAC1B,OAAG,aAAa,qBAAqB,MAAM,gBAAgB;AAAA,EAC7D;AACA,MAAI,MAAM,aAAa;AACrB,OAAG,aAAa,gBAAgB,MAAM,WAAW;AAAA,EACnD;AACF;AAKA,SAAS,oBACP,IACA,MACA,eACA,WACM;AACN,MAAI,CAAC,WAAW,MAAO;AACvB,QAAM,MAAM,KAAK,kBAAkB;AACnC,KAAG,aAAa,wBAAwB,OAAO,GAAG,CAAC;AACnD,EAAC,GAA0C,MAAM,YAAY,mBAAmB,OAAO,GAAG,CAAC;AAC7F;AAMA,SAAS,oBACP,QACA,SACA,WACA,WACA,aACM;AACN,QAAM,OAAOD,kBAAiB,MAAM;AAGpC,EAAAC,UAAS,MAAM,EAAE,GAAG,QAAQ,GAAG,GAAG,QAAQ,IAAIC,YAAW,QAAQ,MAAM,QAAQ,EAAE,CAAC;AAClF,iBAAe,MAAM,QAAQ,KAAK;AAClC,OAAK,aAAa,SAAS,SAAS;AACpC,OAAK,aAAa,mBAAmB,SAAS;AAE9C,QAAM,QAAQ;AAAA,IACZ,QAAQ;AAAA,IACR,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,QAAQ;AAAA,IACR;AAAA,EACF;AAEA,MAAI,MAAM,WAAW,GAAG;AACtB,SAAK,cAAc,QAAQ;AAAA,EAC7B,OAAO;AACL,UAAM,aAAa,QAAQ,MAAM,YAAY,QAAQ,MAAM,cAAc;AACzE,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,QAAQF,kBAAiB,OAAO;AACtC,MAAAC,UAAS,OAAO,EAAE,GAAG,QAAQ,GAAG,IAAI,MAAM,IAAI,IAAI,WAAW,CAAC;AAC9D,YAAM,cAAc,MAAM,CAAC;AAC3B,WAAK,YAAY,KAAK;AAAA,IACxB;AAAA,EACF;AAEA,SAAO,YAAY,IAAI;AACzB;AAEA,SAASE,cAAa,QAAoB,QAA4B;AACpE,QAAM,IAAIH,kBAAiB,GAAG;AAC9B,IAAE,aAAa,SAAS,WAAW;AAEnC,QAAM,EAAE,QAAQ,YAAY,IAAI;AAEhC,MAAI,OAAO,OAAO;AAChB,wBAAoB,GAAG,OAAO,OAAO,YAAY,SAAS,WAAW;AAAA,EACvE;AACA,MAAI,OAAO,UAAU;AACnB,wBAAoB,GAAG,OAAO,UAAU,eAAe,YAAY,WAAW;AAAA,EAChF;AAIA,QAAM,eAAe,OAAO,KAAK,IAAI,OAAO,KAAK;AACjD,MAAI,OAAO,QAAQ;AACjB;AAAA,MACE;AAAA,MACA,EAAE,GAAG,OAAO,QAAQ,GAAG,eAAe,OAAO,OAAO,EAAE;AAAA,MACtD;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,QAAQ;AACjB;AAAA,MACE;AAAA,MACA,EAAE,GAAG,OAAO,QAAQ,GAAG,eAAe,OAAO,OAAO,EAAE;AAAA,MACtD;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,QAAQ;AACjB;AAAA,MACE;AAAA,MACA,EAAE,GAAG,OAAO,QAAQ,GAAG,eAAe,OAAO,OAAO,EAAE;AAAA,MACtD;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,YAAY,CAAC;AACtB;AAMA,SAAS,YAAY,QAAoB,QAA4B;AACnE,MAAI,OAAO,WAAW,QAAQI,iBAAiB;AAE/C,QAAM,EAAE,MAAM,IAAI,OAAO;AACzB,QAAM,UAAU,OAAO,MAAM,QAAQ;AACrC,QAAM,YAAY,QAAQ;AAC1B,QAAM,OAAO,OAAO,MAAM,OAAO;AAEjC,QAAM,eAAe,OAAO,KAAK,IAAI,OAAO,KAAK;AACjD,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,cAAc,OAAO,UAAU,OAAO,UAAU,OAAO;AAC7D,QAAM,UAAU,cACZ,eAAe,YAAY,IAC3B,eAAe,OAAO,MAAM,QAAQ;AAExC,QAAM,IAAIJ,kBAAiB,GAAG;AAC9B,IAAE,aAAa,QAAQD,UAAS;AAChC,IAAE,eAAeD,WAAU,cAAcC,UAAS;AAClD,IAAE,aAAa,UAAU,QAAQ;AACjC,IAAE,aAAa,OAAO,UAAU;AAChC,IAAE,aAAa,SAAS,eAAe;AAEvC,QAAM,cAAc;AACpB,QAAM,OAAOC,kBAAiB,MAAM;AACpC,EAAAC,UAAS,MAAM;AAAA,IACb,GAAG;AAAA,IACH,GAAG,UAAU;AAAA,IACb,qBAAqB;AAAA,IACrB,eAAe;AAAA,IACf,eAAe,OAAO,MAAM,MAAM;AAAA,IAClC,aAAaI;AAAA,IACb,gBAAgB;AAAA,EAClB,CAAC;AACD,EAAC,KAA4C,MAAM,YAAY,QAAQ,IAAI;AAE3E,QAAM,UAAUL,kBAAiB,OAAO;AACxC,EAAAC,UAAS,SAAS,EAAE,eAAe,IAAI,CAAC;AACxC,UAAQ,cAAc;AAEtB,QAAM,eAAeD,kBAAiB,OAAO;AAC7C,EAAAC,UAAS,cAAc,EAAE,eAAe,KAAK,aAAa,YAAY,CAAC;AACvE,eAAa,cAAc;AAE3B,QAAM,SAASD,kBAAiB,OAAO;AACvC,EAAAC,UAAS,QAAQ,EAAE,eAAe,IAAI,CAAC;AACvC,SAAO,cAAc;AAErB,OAAK,YAAY,OAAO;AACxB,OAAK,YAAY,YAAY;AAC7B,OAAK,YAAY,MAAM;AACvB,IAAE,YAAY,IAAI;AAClB,SAAO,YAAY,CAAC;AACtB;AAMA,SAAS,aAAa,QAAoB,cAAkC;AAC1E,MAAI,EAAE,aAAa,iBAAiB,aAAa,QAAQ,WAAW,EAAG;AACvE,QAAM,SAAS;AAEf,QAAM,IAAID,kBAAiB,GAAG;AAC9B,IAAE,aAAa,SAAS,WAAW;AACnC,IAAE,aAAa,QAAQ,MAAM;AAC7B,IAAE,aAAa,cAAc,cAAc;AAE3C,QAAM,eAAe,OAAO,aAAa,SAAS,OAAO,aAAa;AACtE,MAAI,UAAU,OAAO,OAAO;AAC5B,MAAI,UAAU,OAAO,OAAO;AAE5B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAC9C,UAAM,QAAQ,OAAO,QAAQ,CAAC;AAG9B,QAAI,gBAAgB,IAAI,GAAG;AACzB,YAAM,aAAa;AAAA,QACjB,MAAM;AAAA,QACN,OAAO,WAAW;AAAA,QAClB,OAAO,WAAW;AAAA,MACpB;AACA,YAAM,aAAa,OAAO,aAAa,OAAO,YAAY,aAAa,OAAO;AAC9E,UAAI,UAAU,aAAa,OAAO,OAAO,IAAI,OAAO,OAAO,OAAO;AAChE,kBAAU,OAAO,OAAO;AACxB,mBAAW,OAAO,aAAa;AAAA,MACjC;AAAA,IACF;AAEA,UAAM,SAASA,kBAAiB,GAAG;AACnC,WAAO,aAAa,SAAS,iBAAiB;AAC9C,WAAO,aAAa,QAAQ,UAAU;AACtC,WAAO,aAAa,qBAAqB,OAAO,CAAC,CAAC;AAClD,WAAO,aAAa,qBAAqB,MAAM,KAAK;AAEpD,QAAI,MAAM,UAAU;AAClB,aAAO,aAAa,wBAAwB,MAAM;AAClD,aAAO,aAAa,cAAc,MAAM,KAAK;AAC7C,aAAO,aAAa,WAAW,KAAK;AAAA,IACtC,OAAO;AACL,aAAO;AAAA,QACL;AAAA,QACA,GAAG,MAAM,KAAK,KAAK,MAAM,WAAW,QAAQ,YAAY,QAAQ;AAAA,MAClE;AACA,aAAO,aAAa,SAAS,iBAAiB;AAC9C,UAAI,MAAM,WAAW,OAAO;AAC1B,eAAO,aAAa,WAAW,KAAK;AAAA,MACtC;AAAA,IACF;AAGA,UAAM,OAAOA,kBAAiB,MAAM;AACpC,IAAAC,UAAS,MAAM;AAAA,MACb,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,MAAM,MAAM;AAAA,MACZ,IAAI;AAAA,IACN,CAAC;AACD,WAAO,YAAY,IAAI;AAGvB,UAAM,QAAQD,kBAAiB,MAAM;AACrC,IAAAC,UAAS,OAAO;AAAA,MACd,GAAG,UAAU,OAAO,aAAa,OAAO;AAAA,MACxC,GAAG,UAAU,OAAO,aAAa;AAAA,MACjC,qBAAqB;AAAA,IACvB,CAAC;AACD,mBAAe,OAAO,OAAO,UAAU;AACvC,UAAM,cAAc,MAAM;AAC1B,WAAO,YAAY,KAAK;AAExB,MAAE,YAAY,MAAM;AAGpB,QAAI,cAAc;AAChB,YAAM,aAAa;AAAA,QACjB,MAAM;AAAA,QACN,OAAO,WAAW;AAAA,QAClB,OAAO,WAAW;AAAA,MACpB;AACA,YAAM,aAAa,OAAO,aAAa,OAAO,YAAY,aAAa,OAAO;AAC9E,iBAAW;AAAA,IACb,OAAO;AACL,iBAAW,OAAO,aAAa,OAAO;AAAA,IACxC;AAAA,EACF;AAEA,SAAO,YAAY,CAAC;AACtB;AAUA,SAAS,qBAAqB,OAAoE;AAChG,QAAM,MAAM,oBAAI,IAA0C;AAC1D,aAAW,QAAQ,OAAO;AACxB,QAAI,IAAI,KAAK,QAAQ,EAAE,GAAG,KAAK,GAAG,OAAO,KAAK,MAAM,CAAC;AAAA,EACvD;AACA,SAAO;AACT;AAGA,SAAS,WAAW,GAAmB;AACrC,SAAO,EAAE,QAAQ,mBAAmB,GAAG;AACzC;AAEA,SAAS,mBACP,MACA,OACA,eACM;AACN,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AAEpB,QAAI,KAAK,gBAAgB,KAAK,YAAa;AAG3C,UAAM,SAAS,SAAS,WAAW,KAAK,QAAQ,CAAC,IAAI,WAAW,KAAK,QAAQ,CAAC,IAAI,CAAC;AACnF,UAAM,WAAWD,kBAAiB,gBAAgB;AAClD,aAAS,aAAa,MAAM,MAAM;AAClC,aAAS,aAAa,iBAAiB,gBAAgB;AAGvD,UAAM,YAAY,cAAc,IAAI,KAAK,QAAQ;AACjD,UAAM,YAAY,cAAc,IAAI,KAAK,QAAQ;AACjD,UAAM,KAAK,YAAY,UAAU,IAAI,UAAU,QAAQ;AACvD,UAAM,KAAK,YAAY,UAAU,IAAI;AAErC,aAAS,aAAa,MAAM,OAAO,EAAE,CAAC;AACtC,aAAS,aAAa,MAAM,OAAO,EAAE,CAAC;AAEtC,UAAM,QAAQA,kBAAiB,MAAM;AACrC,IAAAC,UAAS,OAAO,EAAE,QAAQ,MAAM,cAAc,KAAK,YAAY,CAAC;AAChE,aAAS,YAAY,KAAK;AAE1B,UAAM,QAAQD,kBAAiB,MAAM;AACrC,IAAAC,UAAS,OAAO,EAAE,QAAQ,QAAQ,cAAc,KAAK,YAAY,CAAC;AAClE,aAAS,YAAY,KAAK;AAE1B,SAAK,YAAY,QAAQ;AAAA,EAC3B;AACF;AAMA,SAAS,YACP,QACA,OACA,gBACA,WACM;AACN,QAAM,IAAID,kBAAiB,GAAG;AAC9B,IAAE,aAAa,SAAS,iBAAiB;AAEzC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AAEpB,UAAM,QAAQA,kBAAiB,GAAG;AAClC,UAAM,aAAa,SAAS,gBAAgB;AAC5C,UAAM,aAAa,gBAAgB,QAAQ,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,CAAC,EAAE;AAChF,UAAM,aAAa,eAAe,KAAK,QAAQ;AAC/C,UAAM,aAAa,eAAe,KAAK,QAAQ;AAE/C,QAAI,KAAK,MAAM,OAAO;AACpB,YAAM,aAAa,cAAc,KAAK,KAAK,KAAK;AAAA,IAClD;AAEA,wBAAoB,OAAO,MAAM,GAAG,SAAS;AAE7C,UAAM,OAAOA,kBAAiB,MAAM;AACpC,SAAK,aAAa,KAAK,KAAK,IAAI;AAChC,SAAK,aAAa,UAAU,MAAM;AAClC,SAAK,aAAa,gBAAgB,OAAO,KAAK,WAAW,CAAC;AAG1D,QAAI,KAAK,gBAAgB,KAAK,aAAa;AACzC,YAAM,SAAS,SAAS,WAAW,KAAK,QAAQ,CAAC,IAAI,WAAW,KAAK,QAAQ,CAAC,IAAI,CAAC;AACnF,WAAK,aAAa,QAAQ,QAAQ,MAAM,GAAG;AAAA,IAC7C,OAAO;AACL,WAAK,aAAa,QAAQ,KAAK,WAAW;AAAA,IAC5C;AAEA,UAAM,YAAY,IAAI;AACtB,MAAE,YAAY,KAAK;AAAA,EACrB;AAEA,SAAO,YAAY,CAAC;AACtB;AAMA,SAAS,YACP,QACA,OACA,WACM;AACN,QAAM,IAAIA,kBAAiB,GAAG;AAC9B,IAAE,aAAa,SAAS,iBAAiB;AAEzC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AAEpB,UAAM,QAAQA,kBAAiB,GAAG;AAClC,UAAM,aAAa,SAAS,gBAAgB;AAC5C,UAAM,aAAa,gBAAgB,QAAQ,KAAK,MAAM,EAAE;AACxD,UAAM,aAAa,gBAAgB,KAAK,MAAM;AAE9C,QAAI,KAAK,MAAM,OAAO;AACpB,YAAM,aAAa,cAAc,KAAK,KAAK,KAAK;AAAA,IAClD;AAEA,wBAAoB,OAAO,MAAM,GAAG,SAAS;AAE7C,UAAM,OAAOA,kBAAiB,MAAM;AACpC,IAAAC,UAAS,MAAM;AAAA,MACb,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK,IAAI,KAAK,QAAQ,CAAC;AAAA;AAAA,MAC/B,MAAM,KAAK;AAAA,MACX,IAAI,KAAK;AAAA,IACX,CAAC;AAED,QAAI,KAAK,QAAQ;AACf,WAAK,aAAa,UAAU,KAAK,MAAM;AAAA,IACzC;AACA,QAAI,KAAK,aAAa;AACpB,WAAK,aAAa,gBAAgB,OAAO,KAAK,WAAW,CAAC;AAAA,IAC5D;AAEA,UAAM,YAAY,IAAI;AACtB,MAAE,YAAY,KAAK;AAAA,EACrB;AAEA,SAAO,YAAY,CAAC;AACtB;AAMA,SAAS,aACP,QACA,OACA,aACM;AACN,QAAM,IAAID,kBAAiB,GAAG;AAC9B,IAAE,aAAa,SAAS,kBAAkB;AAE1C,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,MAAM,IAAI;AAClB,QAAI,CAAC,MAAM,QAAS;AAEpB,UAAM,OAAOA,kBAAiB,MAAM;AACpC,IAAAC,UAAS,MAAM,EAAE,GAAG,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;AACzC,mBAAe,MAAM,MAAM,KAAK;AAGhC,QAAI,MAAM,aAAa,UAAa,MAAM,WAAW,GAAG;AACtD,YAAM,WAAW,MAAM,MAAM,YAAY;AACzC,YAAM,aAAa,MAAM,MAAM,cAAc;AAC7C,YAAM,QAAQ,SAAS,MAAM,MAAM,UAAU,YAAY,MAAM,UAAU,WAAW;AACpF,UAAI,MAAM,SAAS,GAAG;AACpB,cAAM,aAAa,YAAY,MAAM,MAAM,cAAc;AAEzD,cAAM,eAAe,MAAM,SAAS,KAAK;AACzC,cAAM,SAAS,MAAM,IAAI,cAAc;AACvC,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,gBAAM,QAAQD,kBAAiB,OAAO;AACtC,gBAAM,aAAa,KAAK,OAAO,MAAM,CAAC,CAAC;AACvC,gBAAM,aAAa,KAAK,OAAO,SAAS,IAAI,UAAU,CAAC;AACvD,gBAAM,cAAc,MAAM,CAAC;AAC3B,eAAK,YAAY,KAAK;AAAA,QACxB;AAAA,MACF,OAAO;AACL,aAAK,cAAc,MAAM;AAAA,MAC3B;AAAA,IACF,OAAO;AACL,WAAK,cAAc,MAAM;AAAA,IAC3B;AAEA,MAAE,YAAY,IAAI;AAAA,EACpB;AAEA,SAAO,YAAY,CAAC;AACtB;AAYO,SAAS,gBACd,QACA,WACe;AACf,QAAM,EAAE,OAAO,OAAO,IAAI,OAAO;AAEjC,QAAM,MAAMA,kBAAiB,KAAK;AAClC,EAAAC,UAAS,KAAK;AAAA,IACZ,SAAS,OAAO,KAAK,IAAI,MAAM;AAAA,IAC/B,OAAOJ;AAAA,IACP,UAAU;AAAA,EACZ,CAAC;AACD,MAAI,MAAM,SAAS,GAAG,MAAM;AAC5B,MAAI,aAAa,QAAQ,OAAO,KAAK,IAAI;AACzC,MAAI,aAAa,cAAc,OAAO,KAAK,OAAO;AAGlD,QAAM,aAAa,WAAW;AAC9B,QAAM,UAAU,CAAC,CAAC;AAClB,QAAM,UAAU,UAAU,kCAAkC;AAC5D,MAAI,aAAa,SAAS,OAAO;AAGjC,MAAI,YAAY;AACd,UAAM,aAAa,OAAO,MAAM,SAAS,OAAO,MAAM;AACtD,UAAM,UAAU,kBAAkB,WAAW,cAAc,UAAU;AACrE,uBAAmB,KAAK;AAAA,MACtB,UAAU,WAAW;AAAA,MACrB;AAAA,MACA,iBAAiB,UAAW;AAAA,MAC5B,MAAM,WAAW;AAAA,IACnB,CAAC;AAAA,EACH;AAGA,QAAM,KAAKG,kBAAiB,MAAM;AAClC,KAAG,aAAa,SAAS,eAAe;AACxC,EAAAC,UAAS,IAAI;AAAA,IACX,GAAG;AAAA,IACH,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA,MAAM,OAAO,MAAM,OAAO;AAAA,EAC5B,CAAC;AACD,MAAI,YAAY,EAAE;AAGlB,QAAM,gBAAgB,qBAAqB,OAAO,KAAK;AACvD,QAAM,OAAOD,kBAAiB,MAAM;AACpC,qBAAmB,MAAM,OAAO,OAAO,aAAa;AACpD,MAAI,YAAY,IAAI;AAGpB,cAAY,KAAK,OAAO,OAAO,eAAe,SAAS;AAGvD,cAAY,KAAK,OAAO,OAAO,SAAS;AAGxC,eAAa,KAAK,OAAO,OAAO,OAAO,WAAW;AAGlD,eAAa,KAAK,OAAO,MAAM;AAG/B,EAAAG,cAAa,KAAK,MAAM;AAGxB,MAAI,OAAO,WAAW;AACpB,gBAAY,KAAK,MAAM;AAAA,EACzB;AAEA,SAAO;AACT;;;ADxhBA,IAAM,oBAAoB;AAE1B,IAAM,cAAc;AAEpB,IAAM,mBAAmB;AASlB,SAAS,aACd,WACA,MACA,SACgB;AAChB,MAAI,cAAc;AAClB,MAAI;AACJ,MAAI,YAAY;AAGhB,MAAI,aAAmC;AAGvC,MAAI,iBAAwC;AAC5C,MAAI,uBAA4C;AAChD,MAAI,mBAAwC;AAG5C,MAAI,gBAAgB;AACpB,MAAI,mBAAwC;AAC5C,MAAI,gBAAgB;AAMpB,MAAI,qBAAqB;AAIzB,YAAU,UAAU,IAAI,gBAAgB;AAMxC,WAAS,uBAA+B;AACtC,WACE,SAAS,OAAO,OAAO,UACvB,YAAY,OAAO,OAAO,UAC1B,kBAAkB,SAAS;AAAA,EAE/B;AACA,MAAI,aAAa,qBAAqB;AACtC,MAAI,cAAc,kBAAkB,UAAU;AAC9C,MAAI,YAAY;AAMhB,WAAS,yBAA4D;AACnE,UAAM,OAAO,UAAU,sBAAsB;AAC7C,WAAO;AAAA,MACL,OAAO,KAAK,IAAI,KAAK,SAAS,KAAK,GAAG;AAAA,MACtC,QAAQ,KAAK,IAAI,KAAK,UAAU,KAAK,GAAG;AAAA,IAC1C;AAAA,EACF;AAEA,WAAS,UAAwB;AAC/B,UAAM,EAAE,OAAO,OAAO,IAAI,uBAAuB;AACjD,UAAM,WAAW,gBAAgB,SAAS,QAAQ;AAElD,UAAM,cAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB;AAAA,MACA,WAAW,SAAS;AAAA,MACpB;AAAA,IACF;AAEA,WAAO,cAAc,aAAa,WAAW;AAAA,EAC/C;AAMA,WAAS,0BAA0B,KAAoB,QAAkC;AACvF,UAAM,WAA8B,CAAC;AAErC,UAAM,YAAY,oBAAI,IAA2C;AACjE,eAAW,KAAK,OAAO,OAAO;AAC5B,UAAI,CAAC,UAAU,IAAI,EAAE,MAAM,EAAG,WAAU,IAAI,EAAE,QAAQ,CAAC;AAAA,IACzD;AAGA,UAAM,eAAe,IAAI,iBAAiB,iBAAiB;AAC3D,eAAW,MAAM,cAAc;AAC7B,YAAM,SAAS,GAAG,aAAa,cAAc;AAC7C,UAAI,CAAC,OAAQ;AAEb,YAAM,UAAU,OAAO,mBAAmB,IAAI,MAAM;AACpD,YAAM,SAAS,GAAG,aAAa,cAAc;AAC7C,YAAM,WAAW,SAAU,UAAU,IAAI,MAAM,GAAG,QAAQ,CAAC,IAAK,CAAC;AAEjE,YAAM,mBAAmB,CAAC,MAAa;AACrC,cAAM,aAAa;AACnB,YAAI,WAAW,gBAAgB;AAC7B,gBAAM,UAAU,IAAI,sBAAsB;AAC1C,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,yBAAe,KAAK,SAAS,GAAG,CAAC;AAAA,QACnC;AACA,iBAAS,cAAc,QAAQ;AAC/B,YAAI,OAAQ,yBAAwB,KAAK,QAAQ,MAAM;AAAA,MACzD;AAEA,YAAM,kBAAkB,CAAC,MAAa;AACpC,YAAI,WAAW,gBAAgB;AAC7B,gBAAM,aAAa;AACnB,gBAAM,UAAU,IAAI,sBAAsB;AAC1C,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,yBAAe,KAAK,SAAS,GAAG,CAAC;AAAA,QACnC;AAAA,MACF;AAEA,YAAM,mBAAmB,MAAM;AAC7B,wBAAgB,KAAK;AACrB,iBAAS,cAAc,IAAI;AAC3B,yBAAiB,KAAK,MAAM;AAAA,MAC9B;AAEA,YAAM,cAAc,MAAM;AACxB,iBAAS,cAAc,QAAQ;AAAA,MACjC;AAEA,SAAG,iBAAiB,cAAc,gBAAgB;AAClD,SAAG,iBAAiB,aAAa,eAAe;AAChD,SAAG,iBAAiB,cAAc,gBAAgB;AAClD,SAAG,iBAAiB,SAAS,WAAW;AAExC,eAAS,KAAK,MAAM;AAClB,WAAG,oBAAoB,cAAc,gBAAgB;AACrD,WAAG,oBAAoB,aAAa,eAAe;AACnD,WAAG,oBAAoB,cAAc,gBAAgB;AACrD,WAAG,oBAAoB,SAAS,WAAW;AAAA,MAC7C,CAAC;AAAA,IACH;AAGA,UAAM,eAAe,IAAI,iBAAiB,iBAAiB;AAC3D,eAAW,MAAM,cAAc;AAC7B,YAAM,SAAS,GAAG,aAAa,cAAc;AAC7C,UAAI,CAAC,OAAQ;AAEb,YAAM,UAAU,OAAO,mBAAmB,IAAI,MAAM;AACpD,YAAM,WAAW,GAAG,aAAa,aAAa;AAC9C,YAAM,WAAW,GAAG,aAAa,aAAa;AAC9C,YAAM,WAAW,aAAa,QAAQ,UAAU,QAAQ;AAExD,YAAM,mBAAmB,CAAC,MAAa;AACrC,cAAM,aAAa;AACnB,YAAI,WAAW,gBAAgB;AAC7B,gBAAM,UAAU,IAAI,sBAAsB;AAC1C,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,yBAAe,KAAK,SAAS,GAAG,CAAC;AAAA,QACnC;AACA,iBAAS,cAAc,QAAQ;AAAA,MACjC;AAEA,YAAM,kBAAkB,CAAC,MAAa;AACpC,YAAI,WAAW,gBAAgB;AAC7B,gBAAM,aAAa;AACnB,gBAAM,UAAU,IAAI,sBAAsB;AAC1C,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,yBAAe,KAAK,SAAS,GAAG,CAAC;AAAA,QACnC;AAAA,MACF;AAEA,YAAM,mBAAmB,MAAM;AAC7B,wBAAgB,KAAK;AACrB,iBAAS,cAAc,IAAI;AAAA,MAC7B;AAEA,YAAM,cAAc,MAAM;AACxB,iBAAS,cAAc,QAAQ;AAAA,MACjC;AAEA,SAAG,iBAAiB,cAAc,gBAAgB;AAClD,SAAG,iBAAiB,aAAa,eAAe;AAChD,SAAG,iBAAiB,cAAc,gBAAgB;AAClD,SAAG,iBAAiB,SAAS,WAAW;AAExC,eAAS,KAAK,MAAM;AAClB,WAAG,oBAAoB,cAAc,gBAAgB;AACrD,WAAG,oBAAoB,aAAa,eAAe;AACnD,WAAG,oBAAoB,cAAc,gBAAgB;AACrD,WAAG,oBAAoB,SAAS,WAAW;AAAA,MAC7C,CAAC;AAAA,IACH;AAEA,WAAO,MAAM;AACX,iBAAW,WAAW,UAAU;AAC9B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAKA,WAAS,aACP,QACA,UACA,UACyB;AACzB,QAAI,CAAC,YAAY,CAAC,SAAU,QAAO,CAAC;AACpC,UAAM,OAAO,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,YAAY,EAAE,aAAa,QAAQ;AACxF,WAAO,MAAM,QAAQ,CAAC;AAAA,EACxB;AAKA,WAAS,wBACP,KACA,QACA,SACM;AAEN,UAAM,mBAAmB,oBAAI,IAAY,CAAC,MAAM,CAAC;AACjD,UAAM,eAAe,IAAI,iBAAiB,iBAAiB;AAC3D,eAAW,MAAM,cAAc;AAC7B,YAAM,SAAS,GAAG,aAAa,aAAa;AAC5C,YAAM,SAAS,GAAG,aAAa,aAAa;AAC5C,YAAM,OAAO,GAAG,cAAc,MAAM;AACpC,UAAI,CAAC,KAAM;AAEX,YAAM,cAAc,WAAW,UAAU,WAAW;AACpD,WAAK,aAAa,gBAAgB,OAAO,cAAc,oBAAoB,WAAW,CAAC;AACvF,UAAI,aAAa;AACf,YAAI,OAAQ,kBAAiB,IAAI,MAAM;AACvC,YAAI,OAAQ,kBAAiB,IAAI,MAAM;AAAA,MACzC;AAAA,IACF;AAGA,UAAM,eAAe,IAAI,iBAAiB,iBAAiB;AAC3D,eAAW,MAAM,cAAc;AAC7B,YAAM,MAAM,GAAG,aAAa,cAAc;AAC1C,UAAI,CAAC,IAAK;AACV,YAAM,cAAc,iBAAiB,IAAI,GAAG;AAC5C,MAAC,GAAkB,MAAM,UAAU,cAAc,MAAM,OAAO,gBAAgB;AAAA,IAChF;AAAA,EACF;AAKA,WAAS,iBAAiB,KAAoB,QAA4B;AACxE,UAAM,eAAe,IAAI,iBAAiB,iBAAiB;AAC3D,eAAW,MAAM,cAAc;AAC7B,YAAM,OAAO,GAAG,cAAc,MAAM;AACpC,UAAI,CAAC,KAAM;AAEX,YAAM,SAAS,GAAG,aAAa,aAAa;AAC5C,YAAM,SAAS,GAAG,aAAa,aAAa;AAC5C,YAAM,OAAO,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,UAAU,EAAE,aAAa,MAAM;AACpF,WAAK,aAAa,gBAAgB,OAAO,MAAM,eAAe,GAAG,CAAC;AAAA,IACpE;AAGA,UAAM,eAAe,IAAI,iBAAiB,iBAAiB;AAC3D,eAAW,MAAM,cAAc;AAC7B,MAAC,GAAkB,MAAM,UAAU;AAAA,IACrC;AAAA,EACF;AAMA,WAAS,SAAe;AACtB,QAAI,UAAW;AAGf,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AACA,QAAI,YAAY;AACd,uBAAiB,UAAU;AAAA,IAC7B;AAGA,QAAI,sBAAsB;AACxB,2BAAqB;AACrB,6BAAuB;AAAA,IACzB;AAGA,QAAI,YAAY,YAAY;AAC1B,iBAAW,WAAW,YAAY,UAAU;AAAA,IAC9C;AAGA,oBAAgB,QAAQ;AAGxB,UAAM,gBAAgB,iBAAiB,CAAC,CAAC,cAAc,WAAW;AAClE,oBAAgB;AAGhB,UAAM,YAAY,gBAAgB,cAAc,YAAY;AAC5D,iBAAa,gBAAgB,eAAe,SAAS;AACrD,cAAU,YAAY,UAAU;AAGhC,UAAM,SAAS,gBAAgB,SAAS,QAAQ;AAChD,QAAI,QAAQ;AACV,gBAAU,UAAU,IAAI,SAAS;AAAA,IACnC,OAAO;AACL,gBAAU,UAAU,OAAO,SAAS;AAAA,IACtC;AAGA,QAAI,SAAS,YAAY,SAAS,YAAY;AAC5C,UAAI,CAAC,gBAAgB;AACnB,yBAAiB,qBAAqB,SAAS;AAAA,MACjD;AACA,6BAAuB,0BAA0B,YAAY,aAAa;AAAA,IAC5E;AAGA,QAAI,iBAAiB,YAAY;AAC/B,yBAAmB,sBAAsB,YAAY,MAAM;AACzD,2BAAmB;AACnB,YAAI,eAAe;AACjB,0BAAgB;AAChB,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAEA,iBAAa;AACb,cAAU,QAAQ,cAAc,OAAO,SAAS;AAKhD,QAAI,oBAAoB;AACtB,2BAAqB;AACrB,gBAAU,QAAQ,eAAe;AAAA,IACnC;AAAA,EACF;AAMA,WAAS,OAAO,SAA2B;AACzC,QAAI,UAAW;AACf,kBAAc;AAGd,UAAM,WAAW,qBAAqB;AACtC,QAAI,aAAa,YAAY;AAC3B,mBAAa;AACb,oBAAc,kBAAkB,UAAU;AAAA,IAC5C;AACA,oBAAgB;AAChB,WAAO;AAAA,EACT;AAEA,WAAS,SAAe;AACtB,QAAI,UAAW;AAGf,QAAI,kBAAkB;AACpB,sBAAgB;AAChB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,WAAS,SACP,QACA,eAC0C;AAC1C,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,UAAU,UAAU;AAAA,MAC7B,KAAK;AACH,eAAO,mBAAmB,YAAY,aAAiC;AAAA,MACzE,KAAK;AACH,eAAO,UAAU,YAAY,aAAa;AAAA,MAC5C,KAAK;AACH,eAAO,UAAU,YAAY,aAAa;AAAA,MAC5C;AACE,cAAM,IAAI,MAAM,8BAA8B,MAAM,EAAE;AAAA,IAC1D;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,QAAI,UAAW;AACf,gBAAY;AAGZ,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AACnB,sBAAgB;AAAA,IAClB;AACA,QAAI,YAAY;AACd,uBAAiB,UAAU;AAAA,IAC7B;AAGA,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AAGA,QAAI,sBAAsB;AACxB,2BAAqB;AACrB,6BAAuB;AAAA,IACzB;AAGA,QAAI,gBAAgB;AAClB,qBAAe,QAAQ;AACvB,uBAAiB;AAAA,IACnB;AAGA,QAAI,YAAY,YAAY;AAC1B,iBAAW,WAAW,YAAY,UAAU;AAAA,IAC9C;AACA,iBAAa;AAEb,cAAU,UAAU,OAAO,SAAS;AACpC,cAAU,UAAU,OAAO,gBAAgB;AAAA,EAC7C;AAMA,MAAI;AACF,oBAAgB,QAAQ;AAGxB,UAAM,gBAAgB,CAAC,CAAC,cAAc,WAAW;AACjD,oBAAgB;AAGhB,UAAM,YAAY,gBAAgB,cAAc,YAAY;AAC5D,iBAAa,gBAAgB,eAAe,SAAS;AACrD,cAAU,YAAY,UAAU;AAGhC,UAAM,SAAS,gBAAgB,SAAS,QAAQ;AAChD,QAAI,QAAQ;AACV,gBAAU,UAAU,IAAI,SAAS;AAAA,IACnC,OAAO;AACL,gBAAU,UAAU,OAAO,SAAS;AAAA,IACtC;AAGA,QAAI,SAAS,YAAY,SAAS,YAAY;AAC5C,uBAAiB,qBAAqB,SAAS;AAC/C,6BAAuB,0BAA0B,YAAY,aAAa;AAAA,IAC5E;AAGA,QAAI,iBAAiB,YAAY;AAC/B,yBAAmB,sBAAsB,YAAY,MAAM;AACzD,2BAAmB;AACnB,YAAI,eAAe;AACjB,0BAAgB;AAChB,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAEA,iBAAa;AACb,cAAU,QAAQ,cAAc,OAAO,SAAS;AAAA,EAClD,SAAS,KAAK;AACZ,YAAQ,MAAM,8BAA8B,GAAG;AAE/C,UAAM;AAAA,EACR;AAIA,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,MAAM,CAAC;AAAA,IACP,MAAM;AAIJ,2BAAqB;AACrB,aAAO;AAAA,IACT;AAAA,EACF;AACA,YAAU,QAAQ,eAAe,eAAe,YAAY;AAG5D,MAAI,SAAS,eAAe,OAAO;AACjC,uBAAmB,cAAc,WAAW,MAAM;AAChD,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AEjkBO,SAAS,kBAAkB,SAAyC;AACzE,QAAM,EAAE,SAAS,QAAQ,eAAe,WAAW,IAAI;AAEvD,MAAI,cAA4B,EAAE,KAAK,IAAI,KAAK,EAAE;AAElD,QAAM,QAAQ,QAAQ,cAAc,OAAO;AAC3C,MAAI,CAAC,MAAO,QAAO,MAAM;AAAA,EAAC;AAE1B,QAAM,QAAQ,MAAM,cAAc,OAAO;AACzC,QAAM,QAAQ,MAAM,cAAc,OAAO;AACzC,MAAI,CAAC,SAAS,CAAC,MAAO,QAAO,MAAM;AAAA,EAAC;AAGpC,QAAM,aAAa,YAAY,GAAG;AAElC,WAAS,UAAiC;AACxC,QAAI,CAAC,MAAO,QAAO,CAAC;AACpB,WAAO,MAAM,KAAK,MAAM,iBAAiB,IAAI,CAAC;AAAA,EAChD;AAEA,WAAS,iBAAyC;AAChD,QAAI,CAAC,MAAO,QAAO,CAAC;AACpB,UAAM,YAAY,MAAM,cAAc,IAAI;AAC1C,QAAI,CAAC,UAAW,QAAO,CAAC;AACxB,WAAO,MAAM,KAAK,UAAU,iBAAiB,IAAI,CAAC;AAAA,EACpD;AAEA,WAAS,cAAc,IAAiD;AACtE,WAAO,MAAM,KAAK,GAAG,iBAAiB,IAAI,CAAC;AAAA,EAC7C;AAEA,WAAS,cAAsB;AAC7B,UAAM,OAAO,QAAQ;AACrB,QAAI,KAAK,WAAW,EAAG,QAAO,eAAe,EAAE;AAC/C,WAAO,cAAc,KAAK,CAAC,CAAC,EAAE;AAAA,EAChC;AAEA,WAAS,sBAA4B;AACnC,UAAM,OAAO,QAAQ,cAAc,sBAAsB;AACzD,QAAI,MAAM;AACR,WAAK,UAAU,OAAO,qBAAqB;AAC3C,WAAK,gBAAgB,IAAI;AAAA,IAC3B;AAAA,EACF;AAEA,WAAS,eAAe,KAAa,KAAmB;AACtD,wBAAoB;AACpB,UAAM,OAAO,QAAQ;AACrB,UAAM,WAAW,YAAY;AAG7B,QAAI,KAAK,WAAW,EAAG;AACvB,UAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC;AAChD,UAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,WAAW,CAAC,CAAC;AAE7C,kBAAc,EAAE,KAAK,IAAI;AAGzB,UAAM,KAAK,KAAK,GAAG;AACnB,QAAI,CAAC,GAAI;AACT,UAAM,QAAQ,cAAc,EAAE;AAC9B,UAAM,OAAO,MAAM,GAAG;AACtB,QAAI,CAAC,KAAM;AAEX,UAAM,SAAS,WAAW,GAAG,IAAI,GAAG;AACpC,SAAK,KAAK;AACV,SAAK,UAAU,IAAI,qBAAqB;AACxC,SAAK,aAAa,YAAY,OAAO,GAAG,CAAC;AACzC,SAAK,aAAa,YAAY,OAAO,GAAG,CAAC;AAGzC,QAAI,OAAO;AACT,YAAM,aAAa,yBAAyB,MAAM;AAAA,IACpD;AAGA,SAAK,eAAe,EAAE,OAAO,WAAW,QAAQ,UAAU,CAAC;AAAA,EAC7D;AAEA,WAAS,mBAAyB;AAEhC,QAAI,YAAY,MAAM,GAAG;AACvB,qBAAe,GAAG,CAAC;AAAA,IACrB,OAAO;AACL,qBAAe,YAAY,KAAK,YAAY,GAAG;AAAA,IACjD;AAAA,EACF;AAEA,WAAS,mBAAmB,GAAwB;AAClD,UAAM,OAAO,QAAQ;AACrB,QAAI,KAAK,WAAW,EAAG;AAEvB,UAAM,WAAW,YAAY;AAC7B,UAAM,EAAE,KAAK,IAAI,IAAI;AAErB,YAAQ,EAAE,KAAK;AAAA,MACb,KAAK;AACH,UAAE,eAAe;AACjB,YAAI,MAAM,KAAK,SAAS,GAAG;AACzB,yBAAe,MAAM,GAAG,GAAG;AAAA,QAC7B;AACA;AAAA,MACF,KAAK;AACH,UAAE,eAAe;AACjB,YAAI,MAAM,GAAG;AACX,yBAAe,MAAM,GAAG,GAAG;AAAA,QAC7B,OAAO;AAEL,0BAAgB,GAAG;AAAA,QACrB;AACA;AAAA,MACF,KAAK;AACH,UAAE,eAAe;AACjB,YAAI,MAAM,WAAW,GAAG;AACtB,yBAAe,KAAK,MAAM,CAAC;AAAA,QAC7B;AACA;AAAA,MACF,KAAK;AACH,UAAE,eAAe;AACjB,YAAI,MAAM,GAAG;AACX,yBAAe,KAAK,MAAM,CAAC;AAAA,QAC7B;AACA;AAAA,MACF,KAAK;AACH,UAAE,eAAe;AACjB,uBAAe,KAAK,CAAC;AACrB;AAAA,MACF,KAAK;AACH,UAAE,eAAe;AACjB,uBAAe,KAAK,WAAW,CAAC;AAChC;AAAA,IACJ;AAAA,EACF;AAGA,WAAS,gBAAgB,KAAmB;AAC1C,UAAM,UAAU,eAAe;AAC/B,QAAI,OAAO,KAAK,MAAM,QAAQ,QAAQ;AACpC,0BAAoB;AACpB,cAAQ,GAAG,EAAE,MAAM;AAAA,IACrB;AAAA,EACF;AAEA,WAAS,oBAAoB,GAAwB;AACnD,UAAM,KAAK,EAAE;AACb,UAAM,UAAU,eAAe;AAC/B,UAAM,WAAW,QAAQ,QAAQ,EAAE;AACnC,QAAI,WAAW,EAAG;AAElB,YAAQ,EAAE,KAAK;AAAA,MACb,KAAK;AACH,UAAE,eAAe;AACjB,YAAI,WAAW,QAAQ,SAAS,GAAG;AACjC,kBAAQ,WAAW,CAAC,EAAE,MAAM;AAAA,QAC9B;AACA;AAAA,MACF,KAAK;AACH,UAAE,eAAe;AACjB,YAAI,WAAW,GAAG;AAChB,kBAAQ,WAAW,CAAC,EAAE,MAAM;AAAA,QAC9B;AACA;AAAA,MACF,KAAK;AACH,UAAE,eAAe;AAEjB,YAAI,OAAO;AACT,gBAAM,MAAM;AACZ,yBAAe,GAAG,QAAQ;AAAA,QAC5B;AACA;AAAA,MACF,KAAK;AAAA,MACL,KAAK,KAAK;AACR,UAAE,eAAe;AACjB,cAAM,aAAa,GAAG,aAAa,aAAa;AAChD,cAAM,UAAU,GAAG,cAAc,oBAAoB;AACrD,YAAI,cAAc,SAAS;AACzB,iBAAO,UAAU;AAAA,QACnB;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,cAAc,QAAQ,cAAc,wBAAwB;AAElE,WAAS,oBAAoB,GAAwB;AACnD,QAAI,EAAE,QAAQ,UAAU;AACtB,QAAE,eAAe;AACjB,oBAAc;AAEd,UAAI,OAAO;AACT,cAAM,MAAM;AACZ,mBAAW,gBAAgB;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAGA,QAAM,iBAAiB,SAAS,gBAAgB;AAChD,QAAM,iBAAiB,WAAW,kBAAmC;AAGrE,QAAM,cAAc,eAAe;AACnC,aAAW,MAAM,aAAa;AAC5B,OAAG,aAAa,YAAY,GAAG;AAC/B,OAAG,iBAAiB,WAAW,mBAAoC;AAAA,EACrE;AAEA,MAAI,aAAa;AACf,gBAAY,iBAAiB,WAAW,mBAAoC;AAAA,EAC9E;AAGA,SAAO,MAAM;AACX,UAAM,oBAAoB,SAAS,gBAAgB;AACnD,UAAM,oBAAoB,WAAW,kBAAmC;AAExE,eAAW,MAAM,aAAa;AAC5B,SAAG,oBAAoB,WAAW,mBAAoC;AAAA,IACxE;AAEA,QAAI,aAAa;AACf,kBAAY,oBAAoB,WAAW,mBAAoC;AAAA,IACjF;AAEA,wBAAoB;AAAA,EACtB;AACF;;;ACrPA,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;;;ACZ7B,SAAS,mBAAAG,wBAAuB;AAChC,SAAS,qBAAAC,0BAAyB;AAelC,IAAMC,aAAY;AAOlB,SAAS,kBACP,QACA,UACuB;AACvB,QAAM,SAAS,OAAO;AAEtB,MAAI,aAAa,UAAU;AACzB,QAAI,CAAC,OAAO,SAAS,CAAC,OAAO,SAAU,QAAO;AAE9C,UAAMC,OAAM,SAAS,cAAc,KAAK;AACxC,IAAAA,KAAI,YAAY;AAEhB,QAAI,OAAO,OAAO;AAChB,YAAM,IAAI,SAAS,cAAc,KAAK;AACtC,QAAE,YAAY;AACd,QAAE,cAAc,OAAO,MAAM;AAC7B,MAAAA,KAAI,YAAY,CAAC;AAAA,IACnB;AACA,QAAI,OAAO,UAAU;AACnB,YAAM,MAAM,SAAS,cAAc,KAAK;AACxC,UAAI,YAAY;AAChB,UAAI,cAAc,OAAO,SAAS;AAClC,MAAAA,KAAI,YAAY,GAAG;AAAA,IACrB;AAEA,WAAOA;AAAA,EACT;AAGA,MAAI,CAAC,OAAO,UAAU,CAAC,OAAO,OAAQ,QAAO;AAE7C,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,YAAY;AAEhB,MAAI,OAAO,QAAQ;AACjB,UAAM,MAAM,SAAS,cAAc,KAAK;AACxC,QAAI,YAAY;AAChB,QAAI,cAAc,OAAO,OAAO;AAChC,QAAI,YAAY,GAAG;AAAA,EACrB;AACA,MAAI,OAAO,QAAQ;AACjB,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,YAAY;AACjB,SAAK,cAAc,OAAO,OAAO;AACjC,QAAI,YAAY,IAAI;AAAA,EACtB;AAEA,SAAO;AACT;AAMA,SAAS,YACP,SACA,MACyB;AACzB,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,KAAK,SAAS,cAAc,IAAI;AACtC,KAAG,aAAa,QAAQ,KAAK;AAE7B,aAAW,OAAO,SAAS;AACzB,UAAM,KAAK,SAAS,cAAc,IAAI;AACtC,OAAG,aAAa,SAAS,KAAK;AAC9B,OAAG,aAAa,QAAQ,cAAc;AACtC,OAAG,MAAM,YAAY,IAAI;AACzB,OAAG,MAAM,QAAQ,GAAG,IAAI,KAAK;AAG7B,QAAI,gBAAwB;AAC5B,QAAI,QAAQ,KAAK,WAAW,IAAI,KAAK;AACnC,sBAAgB,KAAK,cAAc,QAAQ,cAAc;AAAA,IAC3D;AACA,OAAG,aAAa,aAAa,aAAa;AAC1C,OAAG,aAAa,eAAe,IAAI,GAAG;AAGtC,UAAM,YAAY,SAAS,eAAe,IAAI,KAAK;AACnD,OAAG,YAAY,SAAS;AAGxB,QAAI,IAAI,UAAU;AAChB,YAAM,MAAM,SAAS,cAAc,QAAQ;AAC3C,UAAI,YAAY;AAChB,UAAI,aAAa,cAAc,WAAW,IAAI,KAAK,EAAE;AACrD,UAAI,aAAa,oBAAoB,IAAI,GAAG;AAC5C,UAAI,OAAO;AACX,SAAG,YAAY,GAAG;AAAA,IACpB;AAEA,OAAG,YAAY,EAAE;AAAA,EACnB;AAEA,QAAM,YAAY,EAAE;AACpB,SAAO;AACT;AAMA,SAAS,YAAY,MAAkB,SAAoD;AACzF,QAAM,QAAQ,SAAS,cAAc,OAAO;AAE5C,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,KAAK,SAAS,cAAc,IAAI;AACtC,OAAG,aAAa,QAAQ,KAAK;AAC7B,OAAG,aAAa,eAAe,IAAI,EAAE;AACrC,OAAG,MAAM,YAAY,kBAAkB,OAAO,CAAC,CAAC;AAEhD,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,OAAO,IAAI,MAAM,CAAC;AACxB,UAAI,CAAC,KAAM;AAEX,YAAM,KAAK,WAAW,IAAI;AAC1B,SAAG,aAAa,QAAQ,UAAU;AAClC,SAAG,MAAM,YAAY,QAAQ,CAAC,EAAE;AAChC,SAAG,YAAY,EAAE;AAAA,IACnB;AAEA,UAAM,YAAY,EAAE;AAAA,EACtB;AAEA,SAAO;AACT;AAMA,SAAS,gBAAgB,QAA4C;AACnE,MAAI,CAAC,OAAO,OAAO,QAAS,QAAO;AAEnC,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,YAAY;AAEhB,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,OAAO;AACb,QAAM,cAAc,OAAO,OAAO;AAClC,QAAM,aAAa,cAAc,cAAc;AAC/C,QAAM,QAAQ,OAAO,OAAO;AAE5B,MAAI,YAAY,KAAK;AACrB,SAAO;AACT;AAMA,SAAS,iBAAiB,QAA4C;AACpE,MAAI,CAAC,OAAO,WAAY,QAAO;AAE/B,QAAM,EAAE,MAAM,UAAU,WAAW,WAAW,IAAI,OAAO;AAEzD,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,YAAY;AAEhB,QAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,OAAK,YAAY;AAEjB,MAAI,cAAc,GAAG;AACnB,SAAK,cAAc;AAAA,EACrB,OAAO;AACL,UAAM,QAAQ,OAAO,WAAW;AAChC,UAAM,MAAM,KAAK,KAAK,OAAO,KAAK,UAAU,SAAS;AACrD,SAAK,cAAc,WAAW,KAAK,IAAI,GAAG,OAAO,SAAS;AAAA,EAC5D;AAEA,MAAI,YAAY,IAAI;AAEpB,QAAM,WAAW,SAAS,cAAc,MAAM;AAC9C,WAAS,YAAY;AAErB,QAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,UAAQ,aAAa,cAAc,eAAe;AAClD,UAAQ,aAAa,oBAAoB,MAAM;AAC/C,UAAQ,cAAc;AACtB,UAAQ,WAAW,QAAQ;AAC3B,WAAS,YAAY,OAAO;AAE5B,QAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,UAAQ,aAAa,cAAc,WAAW;AAC9C,UAAQ,aAAa,oBAAoB,MAAM;AAC/C,UAAQ,cAAc;AACtB,UAAQ,WAAW,QAAQ,aAAa;AACxC,WAAS,YAAY,OAAO;AAE5B,MAAI,YAAY,QAAQ;AACxB,SAAO;AACT;AAMA,SAAS,iBAAiB,SAAiC;AACzD,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,YAAY;AAChB,MAAI,aAAa,aAAa,QAAQ;AACtC,MAAI,cAAc;AAClB,SAAO;AACT;AAaO,SAAS,YACd,QACA,WACA,MACa;AACb,QAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,UAAQ,YAAY;AAKpB,QAAM,EAAE,OAAO,OAAO,IAAI;AAC1B,MAAI,OAAO;AACT,UAAM,IAAI,QAAQ;AAClB,MAAE,YAAY,WAAW,MAAM,OAAO,UAAU;AAChD,MAAE,YAAY,aAAa,MAAM,OAAO,IAAI;AAC5C,MAAE,YAAY,uBAAuB,MAAM,OAAO,QAAQ,MAAM,OAAO,IAAI;AAC3E,MAAE,YAAY,mBAAmB,MAAM,OAAO,QAAQ,MAAM,OAAO,IAAI;AACvE,MAAE,YAAY,iBAAiB,MAAM,OAAO,QAAQ;AACpD,MAAE,YAAY,eAAe,MAAM,OAAO,QAAQ;AAClD,MAAE,YAAY,oBAAoB,MAAM,MAAM,MAAM;AACpD,MAAE,aAAa,MAAM,MAAM;AAAA,EAC7B;AAIA;AACE,UAAM,IAAI,QAAQ;AAClB,QAAI,OAAO,OAAO;AAChB,QAAE,YAAY,4BAA4B,GAAG,OAAO,MAAM,MAAM,QAAQ,IAAI;AAC5E,QAAE,YAAY,8BAA8B,OAAO,OAAO,MAAM,MAAM,UAAU,CAAC;AACjF,QAAE,YAAY,6BAA6B,OAAO,MAAM,MAAM,IAAI;AAAA,IACpE;AACA,QAAI,OAAO,UAAU;AACnB,QAAE,YAAY,+BAA+B,GAAG,OAAO,SAAS,MAAM,QAAQ,IAAI;AAClF,QAAE,YAAY,iCAAiC,OAAO,OAAO,SAAS,MAAM,UAAU,CAAC;AACvF,QAAE,YAAY,gCAAgC,OAAO,SAAS,MAAM,IAAI;AAAA,IAC1E;AACA,QAAI,OAAO,QAAQ;AACjB,QAAE,YAAY,6BAA6B,GAAG,OAAO,OAAO,MAAM,QAAQ,IAAI;AAC9E,QAAE,YAAY,8BAA8B,OAAO,OAAO,MAAM,IAAI;AAAA,IACtE;AACA,QAAI,OAAO,QAAQ;AACjB,QAAE,YAAY,6BAA6B,GAAG,OAAO,OAAO,MAAM,QAAQ,IAAI;AAC9E,QAAE,YAAY,8BAA8B,OAAO,OAAO,MAAM,IAAI;AAAA,IACtE;AAAA,EACF;AAGA,MAAI,OAAO,SAAS;AAClB,YAAQ,UAAU,IAAI,mBAAmB;AAAA,EAC3C;AAGA,QAAM,eAAe,kBAAkB,QAAQ,QAAQ;AACvD,MAAI,cAAc;AAChB,YAAQ,YAAY,YAAY;AAAA,EAClC;AAGA,QAAM,YAAY,gBAAgB,MAAM;AACxC,MAAI,WAAW;AACb,YAAQ,YAAY,SAAS;AAAA,EAC/B;AAGA,MAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,UAAM,UAAU,OAAO,OAAO,QAAQ,qBAAqB;AAC3D,YAAQ,YAAY,iBAAiB,OAAO,CAAC;AAAA,EAC/C,OAAO;AAEL,UAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,WAAO,YAAY;AAGnB,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,aAAa,QAAQ,MAAM;AACjC,UAAM,aAAa,cAAc,OAAO,KAAK,OAAO;AAEpD,QAAI,OAAO,mBAAmB;AAC5B,YAAM,UAAU,IAAI,kBAAkB;AAAA,IACxC;AAIA,UAAM,UAAU,SAAS,cAAc,SAAS;AAChD,YAAQ,YAAY;AACpB,sBAAkB,OAAO;AACzB,YAAQ,cAAc,OAAO,KAAK;AAClC,UAAM,YAAY,OAAO;AAGzB,UAAM,YAAY,YAAY,OAAO,SAAS,OAAO,IAAI,CAAC;AAG1D,UAAM,YAAY,YAAY,OAAO,MAAM,OAAO,OAAO,CAAC;AAE1D,WAAO,YAAY,KAAK;AACxB,YAAQ,YAAY,MAAM;AAAA,EAC5B;AAGA,QAAM,aAAa,iBAAiB,MAAM;AAC1C,MAAI,YAAY;AACd,YAAQ,YAAY,UAAU;AAAA,EAChC;AAKA,QAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,YAAU,YAAY;AAEtB,QAAM,eAAe,kBAAkB,QAAQ,QAAQ;AACvD,MAAI,cAAc;AAChB,cAAU,YAAY,YAAY;AAAA,EACpC;AAGA,QAAM,aAAa,SAAS,cAAc,KAAK;AAC/C,aAAW,YAAY;AACvB,oBAAkB,UAAU;AAC5B,aAAW,aAAa,aAAa,QAAQ;AAC7C,aAAW,aAAa,eAAe,MAAM;AAC7C,aAAW,aAAa,QAAQ,QAAQ;AACxC,UAAQ,YAAY,UAAU;AAI9B,MAAI,OAAO,WAAW;AACpB,UAAM,aAAa,QAAQ,MAAM,OAAO,OAAO;AAC/C,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,UAAM,YAAY,SAAS,cAAc,GAAG;AAC5C,cAAU,OAAOD;AACjB,cAAU,SAAS;AACnB,cAAU,MAAM;AAChB,cAAU,MAAM,UAAU,cAAcE,gBAAe,gCAAgC,UAAU,wDAAwD,QAAQ,MAAM,MAAM,SAAS,YAAY;AAClM,cAAU,cAAc;AACxB,UAAM,YAAY,SAAS;AAC3B,cAAU,YAAY,KAAK;AAAA,EAC7B;AAGA,MAAI,UAAU,oBAAoB,GAAG;AACnC,YAAQ,YAAY,SAAS;AAAA,EAC/B;AAIA,MAAI,MAAM,WAAW,OAAO,WAAW,OAAO;AAC5C,UAAM,aAAa,OAAO,UAAU;AACpC,UAAM,WAAW,OAAO,KAAK;AAC7B,UAAM,UAAUC,mBAAkB,WAAW,cAAc,QAAQ;AACnE,uBAAmB,SAAS;AAAA,MAC1B,UAAU,WAAW;AAAA,MACrB;AAAA,MACA,MAAM,WAAW;AAAA,IACnB,CAAC;AACD,YAAQ,UAAU,IAAI,YAAY;AAAA,EACpC;AAEA,YAAU,YAAY,OAAO;AAC7B,SAAO;AACT;;;AD/VA,SAAS,UAAU,OAAuB;AACxC,MAAI,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,IAAI,GAAG;AAC9F,WAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;AAAA,EACtC;AACA,SAAO;AACT;AAUA,SAAS,UAAU,SAA2B,QAAkC;AAC9E,MAAI,CAAC,WAAW,QAAQ,WAAW,QAAQ;AACzC,WAAO,EAAE,QAAQ,WAAW,MAAM;AAAA,EACpC;AACA,MAAI,QAAQ,cAAc,OAAO;AAC/B,WAAO,EAAE,QAAQ,WAAW,OAAO;AAAA,EACrC;AAEA,SAAO;AACT;AAcO,SAAS,YACd,WACA,MACA,SACe;AACf,MAAI,cAAc;AAClB,MAAI;AACJ,MAAI,iBAAqC;AACzC,MAAI,mBAAwC;AAC5C,MAAI,kBAAuC;AAC3C,MAAI,oBAAyC;AAC7C,MAAI,gBAAgB;AACpB,MAAI,YAAY;AAGhB,QAAM,gBAA4B;AAAA,IAChC,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,EACR;AAGA,MAAI,sBAA4D;AAChE,MAAI,sBAA4D;AAEhE,QAAM,eAAe,SAAS,kBAAkB;AAEhD,WAAS,WAAuB;AAC9B,QAAI,gBAAgB,SAAS,eAAe;AAC1C,aAAO;AAAA,QACL,MAAM,QAAQ,cAAc,QAAQ;AAAA,QACpC,QAAQ,QAAQ,cAAc,UAAU;AAAA,QACxC,MAAM,QAAQ,cAAc,QAAQ;AAAA,MACtC;AAAA,IACF;AACA,WAAO,EAAE,GAAG,cAAc;AAAA,EAC5B;AAEA,WAAS,YAAY,SAAoC;AACvD,QAAI,cAAc;AAEhB,YAAM,UAAU,SAAS;AACzB,YAAM,OAAmB;AAAA,QACvB,MAAM,QAAQ,SAAS,SAAY,QAAQ,OAAO,QAAQ;AAAA,QAC1D,QAAQ,QAAQ,WAAW,SAAY,QAAQ,SAAS,QAAQ;AAAA,QAChE,MAAM,QAAQ,SAAS,SAAY,QAAQ,OAAO,QAAQ;AAAA,MAC5D;AACA,eAAS,gBAAgB,IAAI;AAAA,IAC/B,OAAO;AAEL,UAAI,QAAQ,SAAS,OAAW,eAAc,OAAO,QAAQ;AAC7D,UAAI,QAAQ,WAAW,OAAW,eAAc,SAAS,QAAQ;AACjE,UAAI,QAAQ,SAAS,OAAW,eAAc,OAAO,QAAQ;AAC7D,eAAS,gBAAgB,EAAE,GAAG,cAAc,CAAC;AAAA,IAC/C;AAAA,EACF;AAEA,WAAS,UAAuB;AAC9B,UAAM,QAAQ,SAAS;AACvB,UAAM,WAAW,gBAAgB,SAAS,QAAQ;AAClD,UAAM,EAAE,MAAM,IAAI,uBAAuB;AAEzC,UAAM,cAAmC;AAAA,MACvC;AAAA,MACA,QAAQ;AAAA,MACR,OAAO,SAAS;AAAA,MAChB;AAAA,MACA,WAAW,SAAS;AAAA,MACpB,MAAM,MAAM,QAAQ;AAAA,MACpB,QAAQ,MAAM,UAAU;AAAA,MACxB,MAAM,MAAM;AAAA,IACd;AAEA,WAAO,aAAa,aAAa,WAAW;AAAA,EAC9C;AAEA,WAAS,yBAA4D;AACnE,UAAM,OAAO,UAAU,sBAAsB;AAC7C,WAAO;AAAA,MACL,OAAO,KAAK,IAAI,KAAK,SAAS,KAAK,GAAG;AAAA,MACtC,QAAQ,KAAK,IAAI,KAAK,UAAU,KAAK,GAAG;AAAA,IAC1C;AAAA,EACF;AAKA,WAAS,SAAS,SAAuB;AACvC,QAAI,CAAC,eAAgB;AACrB,UAAM,aAAa,eAAe,cAAc,uBAAuB;AACvE,QAAI,YAAY;AACd,iBAAW,cAAc;AAAA,IAC3B;AAAA,EACF;AAOA,WAAS,uBAA6B;AACpC,QAAI,CAAC,eAAgB;AACrB,UAAM,EAAE,MAAM,IAAI,uBAAuB;AACzC,UAAM,KAAK,cAAc,KAAK;AAE9B,QAAI,OAAO,aAAa,OAAO,UAAU;AACvC,qBAAe,UAAU,IAAI,mBAAmB;AAAA,IAClD,WAAW,CAAC,eAAe,SAAS;AAElC,qBAAe,UAAU,OAAO,mBAAmB;AAAA,IACrD;AAAA,EACF;AAEA,WAAS,SAAe;AACtB,QAAI,UAAW;AAEf,QAAI;AAEF,UAAI,mBAAmB;AACrB,0BAAkB;AAClB,4BAAoB;AAAA,MACtB;AAGA,UAAI,iBAAiB;AACnB,wBAAgB;AAChB,0BAAkB;AAAA,MACpB;AAGA,UAAI,gBAAgB,YAAY;AAC9B,uBAAe,WAAW,YAAY,cAAc;AACpD,yBAAiB;AAAA,MACnB;AAEA,sBAAgB,QAAQ;AACxB,YAAM,gBAAgB,iBAAiB,CAAC,CAAC,cAAc,WAAW;AAClE,uBAAiB,YAAY,eAAe,WAAW,EAAE,SAAS,cAAc,CAAC;AAGjF,UAAI,iBAAiB,gBAAgB;AACnC,4BAAoB,2BAA2B,cAAc;AAAA,MAC/D;AACA,UAAI,eAAe;AACjB,wBAAgB;AAAA,MAClB;AAGA,YAAM,SAAS,gBAAgB,SAAS,QAAQ;AAChD,UAAI,QAAQ;AACV,kBAAU,UAAU,IAAI,SAAS;AAAA,MACnC,OAAO;AACL,kBAAU,UAAU,OAAO,SAAS;AAAA,MACtC;AAGA,2BAAqB;AAGrB,UAAI,SAAS,YAAY;AACvB,uBAAe,UAAU,IAAI,qBAAqB;AAAA,MACpD;AAGA,iBAAW;AAGX,UAAI,gBAAgB;AAClB,0BAAkB,kBAAkB;AAAA,UAClC,SAAS;AAAA,UACT,QAAQ,CAAC,cAAsB;AAC7B,kBAAM,QAAQ,SAAS;AACvB,kBAAM,UAAU,UAAU,MAAM,MAAM,SAAS;AAC/C,wBAAY,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;AAGtC,gBAAI,SAAS;AACX,oBAAM,MAAM,QAAQ,cAAc,QAAQ,cAAc;AACxD,uBAAS,aAAa,SAAS,IAAI,GAAG,EAAE;AAAA,YAC1C,OAAO;AACL,uBAAS,cAAc;AAAA,YACzB;AAEA,gBAAI,CAAC,cAAc;AACjB,uBAAS;AAAA,YACX;AAAA,UACF;AAAA,UACA,eAAe,MAAM;AACnB,wBAAY,EAAE,QAAQ,IAAI,MAAM,EAAE,CAAC;AACnC,gBAAI,CAAC,cAAc;AACjB,uBAAS;AAAA,YACX;AAAA,UACF;AAAA,UACA,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,8BAA8B,GAAG;AAAA,IACjD;AAAA,EACF;AAEA,WAAS,aAAmB;AAC1B,QAAI,CAAC,eAAgB;AAGrB,UAAM,WAAW,eAAe,iBAAiB,oBAAoB;AACrE,eAAW,OAAO,UAAU;AAC1B,UAAI,iBAAiB,SAAS,eAAe;AAAA,IAC/C;AAGA,UAAM,cAAc,eAAe;AAAA,MACjC;AAAA,IACF;AACA,QAAI,aAAa;AACf,kBAAY,iBAAiB,SAAS,iBAAiB;AAAA,IACzD;AAGA,UAAM,cAAc,eAAe,iBAAiB,oBAAoB;AACxE,eAAW,OAAO,aAAa;AAC7B,UAAI,iBAAiB,SAAS,eAAe;AAAA,IAC/C;AAGA,QAAI,SAAS,YAAY;AACvB,YAAM,OAAO,eAAe,iBAAiB,UAAU;AACvD,iBAAW,OAAO,MAAM;AACtB,YAAI,iBAAiB,SAAS,cAAc;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AAEA,WAAS,gBAAgB,GAAgB;AACvC,UAAM,MAAM,EAAE;AACd,UAAM,SAAS,IAAI,aAAa,kBAAkB;AAClD,QAAI,CAAC,OAAQ;AAEb,UAAM,QAAQ,SAAS;AACvB,UAAM,UAAU,UAAU,MAAM,MAAM,MAAM;AAE5C,gBAAY,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;AAGtC,QAAI,SAAS;AACX,YAAM,MAAM,QAAQ,cAAc,QAAQ,cAAc;AACxD,eAAS,aAAa,MAAM,IAAI,GAAG,EAAE;AAAA,IACvC,OAAO;AACL,eAAS,cAAc;AAAA,IACzB;AAEA,QAAI,CAAC,cAAc;AACjB,eAAS;AAAA,IACX;AAAA,EACF;AAEA,WAAS,kBAAkB,GAAgB;AACzC,UAAM,QAAQ,EAAE;AAChB,UAAM,QAAQ,MAAM;AAEpB,QAAI,wBAAwB,MAAM;AAChC,mBAAa,mBAAmB;AAAA,IAClC;AAEA,0BAAsB,WAAW,MAAM;AACrC,4BAAsB;AACtB,kBAAY,EAAE,QAAQ,OAAO,MAAM,EAAE,CAAC;AAEtC,UAAI,CAAC,cAAc;AACjB,iBAAS;AAET,cAAM,WAAW,eAAe,MAAM,UAAU;AAChD,YAAI,OAAO;AACT,mBAAS,GAAG,QAAQ,UAAU,aAAa,IAAI,MAAM,EAAE,QAAQ;AAAA,QACjE;AAAA,MACF;AAAA,IACF,GAAG,GAAG;AAAA,EACR;AAEA,WAAS,gBAAgB,GAAgB;AACvC,UAAM,MAAM,EAAE;AACd,UAAM,SAAS,IAAI,aAAa,kBAAkB;AAClD,UAAM,QAAQ,SAAS;AAEvB,QAAI,WAAW,UAAU,MAAM,OAAO,GAAG;AACvC,kBAAY,EAAE,MAAM,MAAM,OAAO,EAAE,CAAC;AAAA,IACtC,WAAW,WAAW,QAAQ;AAC5B,kBAAY,EAAE,MAAM,MAAM,OAAO,EAAE,CAAC;AAAA,IACtC;AAEA,QAAI,CAAC,cAAc;AACjB,eAAS;AAAA,IACX;AAAA,EACF;AAEA,WAAS,eAAe,GAAgB;AACtC,UAAM,KAAK,EAAE;AACb,UAAM,QAAQ,GAAG,aAAa,aAAa;AAC3C,QAAI,CAAC,SAAS,CAAC,cAAe;AAE9B,UAAM,MAAM,cAAc,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK;AACzD,QAAI,KAAK;AACP,eAAS,aAAa,IAAI,IAAI;AAAA,IAChC;AAAA,EACF;AAKA,WAAS,WAAiB;AACxB,QAAI,UAAW;AAGf,UAAM,cAAc,gBAAgB;AAAA,MAClC;AAAA,IACF;AACA,UAAM,WAAW,eAAe,SAAS,kBAAkB;AAC3D,UAAM,iBAAiB,aAAa,kBAAkB;AACtD,UAAM,eAAe,aAAa,gBAAgB;AAElD,WAAO;AAGP,QAAI,UAAU;AACZ,YAAM,WAAW,gBAAgB;AAAA,QAC/B;AAAA,MACF;AACA,UAAI,UAAU;AACZ,iBAAS,MAAM;AACf,iBAAS,kBAAkB,gBAAgB,YAAY;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAEA,WAAS,OAAO,SAA0B;AACxC,QAAI,UAAW;AACf,kBAAc;AACd,WAAO;AAAA,EACT;AAEA,WAAS,SAAe;AACtB,QAAI,UAAW;AACf,QAAI,kBAAmB;AACvB,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAuB;AACvC,QAAI,WAAW,OAAO;AACpB,YAAM,IAAI,MAAM,8BAA8B,MAAM,EAAE;AAAA,IACxD;AAIA,UAAM,QAAQ,SAAS;AACvB,UAAM,WAAW,gBAAgB,SAAS,QAAQ;AAClD,UAAM,EAAE,MAAM,IAAI,uBAAuB;AAEzC,UAAM,aAAa,aAAa,aAAa;AAAA,MAC3C;AAAA,MACA,QAAQ;AAAA,MACR,OAAO,SAAS;AAAA,MAChB;AAAA,MACA,MAAM,MAAM,QAAQ;AAAA,MACpB,QAAQ,MAAM,UAAU;AAAA;AAAA,IAE1B,CAAC;AAED,UAAM,UAAU,WAAW,QAAQ,IAAI,CAAC,MAAM,EAAE,KAAK;AACrD,UAAM,UAAU,CAAC,QAAQ,IAAI,SAAS,EAAE,KAAK,GAAG,CAAC;AAEjD,eAAW,OAAO,WAAW,MAAM;AACjC,YAAM,SAAS,IAAI,MAAM,IAAI,CAAC,SAAS,UAAU,KAAK,cAAc,CAAC;AACrE,cAAQ,KAAK,OAAO,KAAK,GAAG,CAAC;AAAA,IAC/B;AAEA,WAAO,QAAQ,KAAK,IAAI;AAAA,EAC1B;AAEA,WAAS,SAAS,SAAoC;AACpD,QAAI,UAAW;AAKf,QAAI,gBAAgB,SAAS,eAAe;AAC1C,UAAI,QAAQ,SAAS,OAAW,SAAQ,cAAc,OAAO,QAAQ;AACrE,UAAI,QAAQ,WAAW,OAAW,SAAQ,cAAc,SAAS,QAAQ;AACzE,UAAI,QAAQ,SAAS,OAAW,SAAQ,cAAc,OAAO,QAAQ;AAAA,IACvE,OAAO;AACL,UAAI,QAAQ,SAAS,OAAW,eAAc,OAAO,QAAQ;AAC7D,UAAI,QAAQ,WAAW,OAAW,eAAc,SAAS,QAAQ;AACjE,UAAI,QAAQ,SAAS,OAAW,eAAc,OAAO,QAAQ;AAAA,IAC/D;AAEA,WAAO;AAAA,EACT;AAEA,WAAS,UAAgB;AACvB,QAAI,UAAW;AACf,gBAAY;AAEZ,QAAI,mBAAmB;AACrB,wBAAkB;AAClB,0BAAoB;AAAA,IACtB;AACA,QAAI,iBAAiB;AACnB,sBAAgB;AAChB,wBAAkB;AAAA,IACpB;AACA,QAAI,wBAAwB,MAAM;AAChC,mBAAa,mBAAmB;AAChC,4BAAsB;AAAA,IACxB;AACA,QAAI,wBAAwB,MAAM;AAChC,mBAAa,mBAAmB;AAChC,4BAAsB;AAAA,IACxB;AACA,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AACA,QAAI,gBAAgB,YAAY;AAC9B,qBAAe,WAAW,YAAY,cAAc;AACpD,uBAAiB;AAAA,IACnB;AACA,cAAU,UAAU,OAAO,SAAS;AAAA,EACtC;AAGA,SAAO;AAGP,MAAI,SAAS,eAAe,OAAO;AACjC,uBAAmB,cAAc,WAAW,MAAM;AAChD,UAAI,wBAAwB,MAAM;AAChC,qBAAa,mBAAmB;AAAA,MAClC;AACA,4BAAsB,WAAW,MAAM;AACrC,8BAAsB;AAEtB,6BAAqB;AACrB,eAAO;AAAA,MACT,GAAG,GAAG;AAAA,IACR,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AE3hBA,SAAS,wBAAwB,6BAA6B;AAC9D,SAAS,sBAAsB;AAwB/B,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AAKzB,IAAM,yBAAyB;AAQ/B,SAAS,mBAAmB,OAAuB;AACjD,MAAI,QAAQ,uBAAwB,QAAO;AAC3C,SAAO;AACT;AA0DO,SAAS,cACd,WACA,MACA,SACiB;AACjB,MAAI,cAAc;AAClB,MAAI;AACJ,MAAI,YAAY;AAGhB,MAAI,aAAmC;AAGvC,MAAI,iBAAwC;AAC5C,MAAI,uBAA4C;AAChD,MAAI,mBAAwC;AAG5C,MAAI,mBAAwC;AAC5C,MAAI,gBAAgB;AAOpB,MAAI,eAA+B;AAMnC,MAAI,qBAAqB;AAIzB,YAAU,UAAU,IAAI,iBAAiB;AAMzC,WAAS,uBAA+B;AACtC,WACE,SAAS,OAAO,OAAO,UACvB,YAAY,OAAO,OAAO,UAC1B,kBAAkB,SAAS;AAAA,EAE/B;AACA,MAAI,aAAa,qBAAqB;AACtC,MAAI,cAAc,kBAAkB,UAAU;AAC9C,MAAI,YAAY;AAMhB,WAAS,yBAA4D;AACnE,UAAM,OAAO,UAAU,sBAAsB;AAC7C,UAAM,QAAQ,KAAK,IAAI,KAAK,SAAS,KAAK,GAAG;AAE7C,QAAI,iBAAiB,SAAS,KAAK,QAAQ,KAAK,KAAK,SAAS,IAAI;AAChE,qBAAe,KAAK,WAAW,KAAK,KAAK,QAAQ;AAAA,IACnD;AAMA,QAAI,iBAAiB,SAAS,KAAK,SAAS,GAAG;AAC7C,aAAO,EAAE,OAAO,QAAQ,KAAK,IAAI,KAAK,QAAQ,GAAG,EAAE;AAAA,IACrD;AASA,QAAI,SAAS,uBAAuB;AAClC,aAAO,EAAE,OAAO,QAAQ,MAAM;AAAA,IAChC;AACA,UAAM,aAAa,mBAAmB,KAAK;AAC3C,UAAM,SACJ,aAAa,oBACb,oBAAoB,oBAAoB,KACxC;AACF,WAAO,EAAE,OAAO,OAAO;AAAA,EACzB;AAEA,WAAS,UAAyB;AAChC,UAAM,EAAE,OAAO,OAAO,IAAI,uBAAuB;AACjD,UAAM,WAAW,gBAAgB,SAAS,QAAQ;AAElD,UAAM,cAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB;AAAA,MACA,WAAW,SAAS;AAAA,MACpB;AAAA,IACF;AAEA,WAAO,eAAe,aAAa,WAAW;AAAA,EAChD;AAMA,WAAS,0BAA0B,KAAoB,QAAmC;AACxF,UAAM,WAA8B,CAAC;AAErC,UAAM,mBAAmB,oBAAI,IAA2C;AACxE,eAAW,KAAK,OAAO,OAAO;AAC5B,UAAI,CAAC,iBAAiB,IAAI,EAAE,SAAS,EAAG,kBAAiB,IAAI,EAAE,WAAW,CAAC;AAAA,IAC7E;AAGA,UAAM,eAAe,IAAI,iBAAiB,kBAAkB;AAC5D,eAAW,MAAM,cAAc;AAC7B,YAAM,YAAY,GAAG,aAAa,YAAY;AAC9C,UAAI,CAAC,UAAW;AAEhB,YAAM,UAAU,OAAO,mBAAmB,IAAI,SAAS;AACvD,YAAM,OAAO,iBAAiB,IAAI,SAAS;AAE3C,YAAM,mBAAmB,CAAC,MAAa;AACrC,cAAM,aAAa;AACnB,YAAI,WAAW,kBAAkB,SAAS,YAAY,OAAO;AAC3D,gBAAM,UAAU,IAAI,sBAAsB;AAC1C,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,yBAAe,KAAK,SAAS,GAAG,CAAC;AAAA,QACnC;AACA,YAAI,MAAM;AACR,mBAAS,cAAc;AAAA,YACrB,WAAW,KAAK;AAAA,YAChB,WAAW,KAAK,KAAK;AAAA,YACrB,OAAO,KAAK;AAAA,YACZ,MAAM,KAAK;AAAA,UACb,CAAC;AAAA,QACH;AAAA,MACF;AAEA,YAAM,kBAAkB,CAAC,MAAa;AACpC,YAAI,WAAW,kBAAkB,SAAS,YAAY,OAAO;AAC3D,gBAAM,aAAa;AACnB,gBAAM,UAAU,IAAI,sBAAsB;AAC1C,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,gBAAM,IAAI,WAAW,UAAU,QAAQ;AACvC,yBAAe,KAAK,SAAS,GAAG,CAAC;AAAA,QACnC;AAAA,MACF;AAEA,YAAM,mBAAmB,MAAM;AAC7B,wBAAgB,KAAK;AACrB,iBAAS,cAAc,IAAI;AAAA,MAC7B;AAEA,YAAM,cAAc,MAAM;AACxB,YAAI,MAAM;AACR,mBAAS,cAAc;AAAA,YACrB,WAAW,KAAK;AAAA,YAChB,WAAW,KAAK,KAAK;AAAA,YACrB,OAAO,KAAK;AAAA,YACZ,MAAM,KAAK;AAAA,UACb,CAAC;AAAA,QACH;AAAA,MACF;AAEA,SAAG,iBAAiB,cAAc,gBAAgB;AAClD,SAAG,iBAAiB,aAAa,eAAe;AAChD,SAAG,iBAAiB,cAAc,gBAAgB;AAClD,SAAG,iBAAiB,SAAS,WAAW;AAExC,eAAS,KAAK,MAAM;AAClB,WAAG,oBAAoB,cAAc,gBAAgB;AACrD,WAAG,oBAAoB,aAAa,eAAe;AACnD,WAAG,oBAAoB,cAAc,gBAAgB;AACrD,WAAG,oBAAoB,SAAS,WAAW;AAAA,MAC7C,CAAC;AAAA,IACH;AAEA,WAAO,MAAM;AACX,iBAAW,WAAW,UAAU;AAC9B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAMA,WAAS,OAAO,UAAU,OAAa;AAErC,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AAGA,QAAI,YAAY;AACd,UAAI,sBAAsB;AACxB,6BAAqB;AACrB,+BAAuB;AAAA,MACzB;AACA,iBAAW,OAAO;AAAA,IACpB;AAGA,UAAM,SAAS,iBAAiB,eAAe,EAAE,QAAQ,CAAC;AAC1D,cAAU,YAAY,MAAM;AAC5B,iBAAa;AAGb,2BAAuB,0BAA0B,QAAQ,aAAa;AAGtE,QAAI,SAAS,YAAY,OAAO;AAC9B,UAAI,CAAC,gBAAgB;AACnB,yBAAiB,qBAAqB,SAAS;AAAA,MACjD;AAAA,IACF;AAGA,QAAI,WAAW,cAAc,WAAW,OAAO;AAC7C,yBAAmB,sBAAsB,QAAQ,MAAM;AAErD,YAAI,iBAAiB,CAAC,WAAW;AAC/B,0BAAgB;AAChB,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAEA,iBAAa;AACb,cAAU,QAAQ,cAAc,OAAO,SAAS;AAKhD,QAAI,oBAAoB;AACtB,2BAAqB;AACrB,gBAAU,QAAQ,eAAe;AAAA,IACnC;AAAA,EACF;AAEA,WAAS,OAAO,SAA4B;AAC1C,kBAAc;AAGd,UAAM,WAAW,qBAAqB;AACtC,QAAI,aAAa,YAAY;AAC3B,mBAAa;AACb,oBAAc,kBAAkB,UAAU;AAAA,IAC5C;AACA,oBAAgB,QAAQ;AACxB,WAAO;AAAA,EACT;AAEA,WAAS,SAAe;AACtB,QAAI,UAAW;AAGf,QAAI,kBAAkB;AACpB,sBAAgB;AAChB;AAAA,IACF;AAEA,oBAAgB,QAAQ;AACxB,WAAO;AAAA,EACT;AAMA,WAAS,YACP,QACA,UAC0C;AAC1C,QAAI,CAAC,WAAY,QAAO;AAExB,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,UAAU,UAAU;AAAA,MAC7B,KAAK;AACH,eAAO,mBAAmB,UAAU;AAAA,MACtC,KAAK;AACH,eAAO,UAAU,YAAY,QAA4B;AAAA,MAC3D,KAAK;AACH,eAAO,UAAU,YAAY,QAA4B;AAAA,MAC3D;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAMA,WAAS,UAAgB;AACvB,QAAI,UAAW;AACf,gBAAY;AAGZ,QAAI,kBAAkB;AACpB,uBAAiB,UAAU;AAC3B,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AAGA,QAAI,sBAAsB;AACxB,2BAAqB;AACrB,6BAAuB;AAAA,IACzB;AAGA,QAAI,YAAY;AACd,iBAAW,OAAO;AAClB,mBAAa;AAAA,IACf;AAGA,QAAI,gBAAgB;AAClB,qBAAe,QAAQ;AACvB,uBAAiB;AAAA,IACnB;AAGA,QAAI,kBAAkB;AACpB,uBAAiB;AACjB,yBAAmB;AAAA,IACrB;AAGA,cAAU,UAAU,OAAO,mBAAmB,SAAS;AAAA,EACzD;AAQA,MAAI,gBAAgB,SAAS,QAAQ,GAAG;AACtC,cAAU,UAAU,IAAI,SAAS;AAAA,EACnC;AAGA,kBAAgB,QAAQ;AACxB,SAAO,IAAI;AAIX,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,MAAM,CAAC;AAAA,IACP,MAAM;AAIJ,2BAAqB;AACrB,aAAO;AAAA,IACT;AAAA,EACF;AACA,YAAU,QAAQ,eAAe,eAAe,YAAY;AAG5D,MAAI,SAAS,eAAe,OAAO;AACjC,uBAAmB,cAAc,WAAW,MAAM;AAChD,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,IAAI,SAAwB;AAC1B,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":["live","cx","cy","start","SpatialIndex","SpatialIndex","renderChrome","renderLegend","escapeHtml","set","BRAND_FONT_SIZE","BRAND_MIN_WIDTH","textAscent","SVG_NS","XLINK_NS","BRAND_URL","createSVGElement","setAttrs","textAscent","renderChrome","BRAND_MIN_WIDTH","BRAND_FONT_SIZE","BRAND_FONT_SIZE","clampStaggerDelay","BRAND_URL","div","BRAND_FONT_SIZE","clampStaggerDelay"]}